From 96b3049428ebc0f52f73976eb9a53e7093fc2882 Mon Sep 17 00:00:00 2001 From: YanchengZhou Date: Tue, 28 Nov 2023 17:17:05 -0500 Subject: [PATCH 01/44] build wm1110 library --- examples/lorawan/Makefile | 19 + examples/lorawan/README.md | 21 + examples/lorawan/main_lorawan.c | 522 ++ examples/wifi_scan/Makefile | 19 + examples/wifi_scan/README.md | 179 + examples/wifi_scan/main_geolocation_wifi.c | 597 ++ examples/wm1110/Makefile | 19 + examples/wm1110/main.c | 94 + libtock/nonvolatile_storage.c | 101 + libtock/nonvolatile_storage.h | 26 + lr1110/lr1110/seeed/README.md | 2 + lr1110/lr1110/seeed/apps/common/README.md | 35 + .../seeed/apps/common/apps_modem_common.c | 276 + .../seeed/apps/common/apps_modem_common.h | 111 + .../seeed/apps/common/apps_modem_event.c | 324 + .../seeed/apps/common/apps_modem_event.h | 222 + .../lr1110/seeed/apps/common/apps_utilities.c | 173 + .../lr1110/seeed/apps/common/apps_utilities.h | 145 + .../seeed/apps/common/lorawan_key_config.h | 110 + .../seeed/apps/common/smtc_modem_api_str.c | 854 +++ .../seeed/apps/common/smtc_modem_api_str.h | 69 + .../accelerometer/main_accelerometer.c | 21 + .../air_th_sensor/main_air_th_sensor.c | 20 + .../seeed/apps/examples/blinky/main_blinky.c | 18 + .../examples/full_almanac_update/README.md | 64 + .../examples/full_almanac_update/almanac.h | 5 + .../full_almanac_update/almanac_old.h | 6 + .../full_almanac_update/get_full_almanac.py | 92 + .../main_full_almanac_update.c | 257 + .../geolocation_application_server/README.md | 96 + .../geolocation.json | 1408 +++++ .../geolocation_application_server/modem.json | 1224 ++++ .../apps/examples/geolocation_gnss/README.md | 332 ++ .../geolocation_gnss/main_geolocation_gnss.c | 562 ++ .../geolocation_gnss/main_geolocation_gnss.h | 154 + .../examples/geolocation_gnss_wifi/README.md | 268 + .../main_geolocation_gnss_wifi.c | 631 ++ .../main_geolocation_gnss_wifi.h | 155 + .../apps/examples/geolocation_wifi/README.md | 179 + .../geolocation_wifi/main_geolocation_wifi.c | 409 ++ .../geolocation_wifi/main_geolocation_wifi.h | 110 + .../seeed/apps/examples/lorawan/README.md | 21 + .../apps/examples/lorawan/main_lorawan.c | 333 ++ .../apps/examples/lorawan/main_lorawan.h | 120 + .../apps/examples/low_power/main_low_power.c | 57 + .../seeed/geolocation_middleware/bsp/mw_bsp.h | 108 + .../geolocation_middleware/common/mw_assert.h | 134 + .../geolocation_middleware/common/mw_common.c | 159 + .../geolocation_middleware/common/mw_common.h | 118 + .../common/mw_dbg_trace.h | 158 + .../doc/applicationServer.rst | 111 + ...docApplicationServerAssistancePosition.png | Bin 0 -> 29889 bytes ...eoloc_docApplicationServerBlockDiagram.png | Bin 0 -> 103757 bytes .../geoloc_docMiddlewareSequenceOverview.png | Bin 0 -> 36728 bytes .../doc/geoloc_mobileScanScheme.png | Bin 0 -> 28762 bytes .../doc/geoloc_staticScanScheme.png | Bin 0 -> 34938 bytes .../doc/geolocationMiddleware.rst | 404 ++ .../gnss/src/gnss_helpers.c | 938 +++ .../gnss/src/gnss_helpers.h | 231 + .../gnss/src/gnss_helpers_defs.h | 73 + .../gnss/src/gnss_middleware.c | 1298 ++++ .../gnss/src/gnss_middleware.h | 334 ++ .../gnss/src/gnss_queue.c | 267 + .../gnss/src/gnss_queue.h | 202 + .../gnss/src/gnss_queue_defs.h | 73 + .../gnss/src/gnss_version.h | 73 + .../gnss/src/lr11xx_driver_extension.c | 179 + .../gnss/src/lr11xx_driver_extension.h | 140 + .../seeed/geolocation_middleware/readme.md | 13 + .../wifi/src/wifi_helpers.c | 237 + .../wifi/src/wifi_helpers.h | 120 + .../wifi/src/wifi_helpers_defs.h | 119 + .../wifi/src/wifi_middleware.c | 757 +++ .../wifi/src/wifi_middleware.h | 234 + .../wifi/src/wifi_version.h | 73 + .../seeed/lora_basics_modem/.gitlab-ci.yml | 124 + .../seeed/lora_basics_modem/CHANGELOG.md | 90 + .../seeed/lora_basics_modem/LICENSE.txt | 27 + .../seeed/lora_basics_modem/LICENSES.txt | 136 + .../lr1110/seeed/lora_basics_modem/Makefile | 196 + .../lr1110/seeed/lora_basics_modem/README.md | 85 + .../lora_basics_modem_version.h | 73 + .../lora_basics_modem/makefiles/common.mk | 551 ++ .../lora_basics_modem/makefiles/lr11xx.mk | 107 + .../lora_basics_modem/makefiles/printing.mk | 58 + .../lora_basics_modem/makefiles/regions.mk | 137 + .../lora_basics_modem/makefiles/sx126x.mk | 55 + .../lora_basics_modem/makefiles/sx128x.mk | 34 + .../smtc_modem_api/CHANGELOG.md | 36 + .../lora_basics_modem/smtc_modem_api/Makefile | 7 + .../smtc_modem_api/doxygen.config | 2494 ++++++++ .../smtc_basic_modem_lr11xx_api_extension.h | 128 + .../smtc_modem_api/smtc_modem_api.h | 1961 +++++++ .../smtc_modem_middleware_advanced_api.h | 303 + .../smtc_modem_api/smtc_modem_test_api.h | 283 + .../smtc_modem_api/smtc_modem_utilities.h | 96 + .../smtc_modem_core/README.md | 17 + .../device_management_defs.h | 388 ++ .../device_management/dm_downlink.c | 559 ++ .../device_management/dm_downlink.h | 85 + .../device_management/modem_context.c | 2122 +++++++ .../device_management/modem_context.h | 1177 ++++ .../smtc_modem_core/lorawan_api/lorawan_api.c | 991 ++++ .../smtc_modem_core/lorawan_api/lorawan_api.h | 1064 ++++ .../smtc_modem_core/lr1mac/license.md | 25 + .../smtc_modem_core/lr1mac/lr1mac_config.h | 89 + .../lr1mac/src/lr1_stack_mac_layer.c | 2348 ++++++++ .../lr1mac/src/lr1_stack_mac_layer.h | 441 ++ .../src/lr1mac_class_b/smtc_beacon_sniff.c | 757 +++ .../src/lr1mac_class_b/smtc_beacon_sniff.h | 294 + .../lr1mac/src/lr1mac_class_b/smtc_d2d.c | 603 ++ .../lr1mac/src/lr1mac_class_b/smtc_d2d.h | 117 + .../src/lr1mac_class_b/smtc_ping_slot.c | 1156 ++++ .../src/lr1mac_class_b/smtc_ping_slot.h | 284 + .../src/lr1mac_class_c/lr1mac_class_c.c | 747 +++ .../src/lr1mac_class_c/lr1mac_class_c.h | 215 + .../smtc_modem_core/lr1mac/src/lr1mac_core.c | 1300 ++++ .../smtc_modem_core/lr1mac/src/lr1mac_core.h | 667 +++ .../smtc_modem_core/lr1mac/src/lr1mac_defs.h | 506 ++ .../lr1mac/src/lr1mac_utilities.c | 306 + .../lr1mac/src/lr1mac_utilities.h | 191 + .../lr1mac/src/services/smtc_duty_cycle.c | 516 ++ .../lr1mac/src/services/smtc_duty_cycle.h | 240 + .../lr1mac/src/services/smtc_lbt.c | 226 + .../lr1mac/src/services/smtc_lbt.h | 158 + .../lr1mac/src/services/smtc_multicast.c | 209 + .../lr1mac/src/services/smtc_multicast.h | 148 + .../lr1mac/src/smtc_real/src/region_as_923.c | 374 ++ .../lr1mac/src/smtc_real/src/region_as_923.h | 157 + .../src/smtc_real/src/region_as_923_defs.h | 281 + .../lr1mac/src/smtc_real/src/region_au_915.c | 757 +++ .../lr1mac/src/smtc_real/src/region_au_915.h | 219 + .../src/smtc_real/src/region_au_915_defs.h | 296 + .../lr1mac/src/smtc_real/src/region_cn_470.c | 771 +++ .../lr1mac/src/smtc_real/src/region_cn_470.h | 220 + .../src/smtc_real/src/region_cn_470_defs.h | 382 ++ .../src/smtc_real/src/region_cn_470_rp_1_0.c | 464 ++ .../src/smtc_real/src/region_cn_470_rp_1_0.h | 203 + .../smtc_real/src/region_cn_470_rp_1_0_defs.h | 269 + .../lr1mac/src/smtc_real/src/region_eu_868.c | 370 ++ .../lr1mac/src/smtc_real/src/region_eu_868.h | 171 + .../src/smtc_real/src/region_eu_868_defs.h | 323 + .../lr1mac/src/smtc_real/src/region_in_865.c | 340 ++ .../lr1mac/src/smtc_real/src/region_in_865.h | 164 + .../src/smtc_real/src/region_in_865_defs.h | 248 + .../lr1mac/src/smtc_real/src/region_kr_920.c | 338 ++ .../lr1mac/src/smtc_real/src/region_kr_920.h | 163 + .../src/smtc_real/src/region_kr_920_defs.h | 231 + .../lr1mac/src/smtc_real/src/region_ru_864.c | 348 ++ .../lr1mac/src/smtc_real/src/region_ru_864.h | 162 + .../src/smtc_real/src/region_ru_864_defs.h | 272 + .../lr1mac/src/smtc_real/src/region_us_915.c | 754 +++ .../lr1mac/src/smtc_real/src/region_us_915.h | 218 + .../src/smtc_real/src/region_us_915_defs.h | 280 + .../lr1mac/src/smtc_real/src/region_ww2g4.c | 333 ++ .../lr1mac/src/smtc_real/src/region_ww2g4.h | 160 + .../src/smtc_real/src/region_ww2g4_defs.h | 245 + .../lr1mac/src/smtc_real/src/smtc_real.c | 2892 +++++++++ .../lr1mac/src/smtc_real/src/smtc_real.h | 731 +++ .../lr1mac/src/smtc_real/src/smtc_real_defs.h | 390 ++ .../src/smtc_real/src/smtc_real_defs_str.h | 119 + .../modem_config/smtc_modem_hal_dbg_trace.h | 253 + .../smtc_modem_core/modem_core/smtc_modem.c | 3218 ++++++++++ .../modem_core/smtc_modem_test.c | 902 +++ .../modem_services/fifo_ctrl.c | 289 + .../modem_services/fifo_ctrl.h | 170 + .../fragmentation/frag_decoder.c | 915 +++ .../fragmentation/frag_decoder.h | 218 + .../fragmentation/fragmented_data_block.c | 1423 +++++ .../fragmentation/fragmented_data_block.h | 188 + .../modem_services/fragmentation/patch_upd.c | 315 + .../modem_services/fragmentation/patch_upd.h | 90 + .../modem_services/lorawan_certification.c | 574 ++ .../modem_services/lorawan_certification.h | 383 ++ .../modem_services/modem_utilities.c | 61 + .../modem_services/modem_utilities.h | 78 + .../modem_services/smtc_clock_sync.c | 551 ++ .../modem_services/smtc_clock_sync.h | 316 + .../smtc_modem_services_config.h | 85 + .../smtc_modem_services_config.h.bak | 86 + .../modem_services/smtc_modem_services_hal.c | 137 + .../modem_supervisor/modem_supervisor.c | 1587 +++++ .../modem_supervisor/modem_supervisor.h | 251 + .../radio_drivers/lr11xx_driver/CHANGELOG.md | 35 + .../radio_drivers/lr11xx_driver/LICENSE.txt | 30 + .../radio_drivers/lr11xx_driver/README.md | 101 + .../lr11xx_driver/src/lr11xx_bootloader.c | 294 + .../lr11xx_driver/src/lr11xx_bootloader.h | 208 + .../src/lr11xx_bootloader_types.h | 179 + .../lr11xx_driver/src/lr11xx_crypto_engine.c | 491 ++ .../lr11xx_driver/src/lr11xx_crypto_engine.h | 292 + .../src/lr11xx_crypto_engine_types.h | 199 + .../src/lr11xx_driver_module.cmake | 48 + .../lr11xx_driver/src/lr11xx_driver_version.c | 88 + .../lr11xx_driver/src/lr11xx_driver_version.h | 90 + .../lr11xx_driver/src/lr11xx_gnss.c | 742 +++ .../lr11xx_driver/src/lr11xx_gnss.h | 437 ++ .../lr11xx_driver/src/lr11xx_gnss_types.h | 312 + .../lr11xx_driver/src/lr11xx_hal.h | 198 + .../lr11xx_driver/src/lr11xx_lr_fhss.c | 290 + .../lr11xx_driver/src/lr11xx_lr_fhss.h | 128 + .../lr11xx_driver/src/lr11xx_lr_fhss_types.h | 65 + .../lr11xx_driver/src/lr11xx_radio.c | 1167 ++++ .../lr11xx_driver/src/lr11xx_radio.h | 776 +++ .../lr11xx_driver/src/lr11xx_radio_timings.c | 233 + .../lr11xx_driver/src/lr11xx_radio_timings.h | 95 + .../lr11xx_driver/src/lr11xx_radio_types.h | 573 ++ .../lr11xx_driver/src/lr11xx_regmem.c | 314 + .../lr11xx_driver/src/lr11xx_regmem.h | 202 + .../lr11xx_driver/src/lr11xx_system.c | 659 +++ .../lr11xx_driver/src/lr11xx_system.h | 585 ++ .../lr11xx_driver/src/lr11xx_system_types.h | 330 ++ .../lr11xx_driver/src/lr11xx_types.h | 76 + .../lr11xx_driver/src/lr11xx_wifi.c | 934 +++ .../lr11xx_driver/src/lr11xx_wifi.h | 628 ++ .../lr11xx_driver/src/lr11xx_wifi_types.h | 410 ++ .../lr11xx_driver/src/lr_fhss_v1_base_types.h | 127 + .../radio_drivers/sx126x_driver/CHANGELOG.md | 35 + .../radio_drivers/sx126x_driver/LICENSE.txt | 27 + .../radio_drivers/sx126x_driver/README.md | 27 + .../sx126x_driver/src/lr_fhss_mac.c | 851 +++ .../sx126x_driver/src/lr_fhss_mac.h | 220 + .../sx126x_driver/src/lr_fhss_v1_base_types.h | 127 + .../radio_drivers/sx126x_driver/src/sx126x.c | 1518 +++++ .../radio_drivers/sx126x_driver/src/sx126x.h | 1668 ++++++ .../sx126x_driver/src/sx126x_hal.h | 141 + .../sx126x_driver/src/sx126x_lr_fhss.c | 434 ++ .../sx126x_driver/src/sx126x_lr_fhss.h | 244 + .../sx126x_driver/src/sx126x_regs.h | 231 + .../radio_drivers/sx128x_driver/LICENSE.txt | 27 + .../radio_drivers/sx128x_driver/readme.md | 0 .../radio_drivers/sx128x_driver/src/sx128x.c | 1851 ++++++ .../radio_drivers/sx128x_driver/src/sx128x.h | 1996 +++++++ .../sx128x_driver/src/sx128x_hal.h | 151 + .../sx128x_driver/src/sx128x_regs.h | 323 + .../smtc_modem_core/radio_planner/license.md | 25 + .../radio_planner/src/radio_planner.c | 1035 ++++ .../radio_planner/src/radio_planner.h | 170 + .../radio_planner/src/radio_planner_hal.c | 137 + .../radio_planner/src/radio_planner_hal.h | 133 + .../src/radio_planner_hook_id_defs.h | 98 + .../radio_planner/src/radio_planner_stats.h | 246 + .../radio_planner/src/radio_planner_types.h | 244 + .../lr11xx_crypto_engine/lr11xx_ce.c | 609 ++ .../smtc_modem_crypto/smtc_modem_crypto.c | 644 ++ .../smtc_modem_crypto/smtc_modem_crypto.h | 271 + .../smtc_secure_element/smtc_secure_element.h | 303 + .../soft_secure_element/aes.c | 936 +++ .../soft_secure_element/aes.h | 170 + .../soft_secure_element/cmac.c | 162 + .../soft_secure_element/cmac.h | 71 + .../soft_secure_element/soft_se.c | 763 +++ .../smtc_modem_services/headers/alc_sync.h | 322 + .../headers/almanac_update.h | 115 + .../smtc_modem_services/headers/file_upload.h | 177 + .../smtc_modem_services/headers/stream.h | 200 + .../smtc_modem_services_config.template.h | 84 + .../smtc_modem_services_hal.h | 152 + .../src/alc_sync/alc_sync.c | 553 ++ .../src/almanac_update/almanac_update.c | 111 + .../src/file_upload/file_upload.c | 438 ++ .../src/file_upload/file_upload_defs.h | 77 + .../src/modem_services_common.h | 144 + .../src/stream/lmic_defines.h | 53 + .../smtc_modem_services/src/stream/rose.c | 659 +++ .../smtc_modem_services/src/stream/rose.h | 127 + .../smtc_modem_services/src/stream/rose.rst | 493 ++ .../src/stream/rose_defs.h | 71 + .../smtc_modem_services/src/stream/stream.c | 232 + .../smtc_modem_core/smtc_ral/license.md | 27 + .../smtc_ral/src/lr_fhss_v1_base_types.h | 127 + .../smtc_modem_core/smtc_ral/src/ral.h | 940 +++ .../smtc_modem_core/smtc_ral/src/ral_defs.h | 504 ++ .../smtc_modem_core/smtc_ral/src/ral_drv.h | 222 + .../smtc_modem_core/smtc_ral/src/ral_llcc68.c | 1258 ++++ .../smtc_modem_core/smtc_ral/src/ral_llcc68.h | 427 ++ .../smtc_ral/src/ral_llcc68_bsp.h | 156 + .../smtc_modem_core/smtc_ral/src/ral_lr11xx.c | 1732 ++++++ .../smtc_modem_core/smtc_ral/src/ral_lr11xx.h | 433 ++ .../smtc_ral/src/ral_lr11xx_bsp.h | 150 + .../smtc_modem_core/smtc_ral/src/ral_sx126x.c | 1635 ++++++ .../smtc_modem_core/smtc_ral/src/ral_sx126x.h | 434 ++ .../smtc_ral/src/ral_sx126x_bsp.h | 156 + .../smtc_modem_core/smtc_ral/src/ral_sx128x.c | 1601 +++++ .../smtc_modem_core/smtc_ral/src/ral_sx128x.h | 434 ++ .../smtc_ral/src/ral_sx128x_bsp.h | 109 + .../smtc_modem_core/smtc_ralf/license.md | 24 + .../smtc_modem_core/smtc_ralf/src/ralf.h | 162 + .../smtc_modem_core/smtc_ralf/src/ralf_defs.h | 115 + .../smtc_modem_core/smtc_ralf/src/ralf_drv.h | 88 + .../smtc_ralf/src/ralf_lr11xx.c | 186 + .../smtc_ralf/src/ralf_lr11xx.h | 104 + .../smtc_ralf/src/ralf_lr11xx_bsp.h | 76 + .../smtc_ralf/src/ralf_sx126x.c | 187 + .../smtc_ralf/src/ralf_sx126x.h | 105 + .../smtc_ralf/src/ralf_sx126x_bsp.h | 76 + .../smtc_ralf/src/ralf_sx128x.c | 206 + .../smtc_ralf/src/ralf_sx128x.h | 105 + .../smtc_ralf/src/ralf_sx128x_bsp.h | 76 + .../smtc_modem_hal/CHANGELOG.md | 29 + .../smtc_modem_hal/smtc_modem_hal.h | 423 ++ .../seeed/pca10056/blank/config/sdk_config.h | 5209 +++++++++++++++++ .../ses_accelerometer/flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 221 + .../wm1110_dev_kit_pca10056.emSession | 63 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + .../ses_air_th_sensor/flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 221 + .../wm1110_dev_kit_pca10056.emSession | 65 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + .../blank/ses_blinky/flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 221 + .../wm1110_dev_kit_pca10056.emSession | 65 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + .../flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 221 + .../wm1110_dev_kit_pca10056.emSession | 65 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + .../ses_geolocation_gnss/flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 222 + .../wm1110_dev_kit_pca10056.emSession | 65 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + .../flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 222 + .../wm1110_dev_kit_pca10056.emSession | 65 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + .../ses_geolocation_wifi/flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 222 + .../wm1110_dev_kit_pca10056.emSession | 65 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + .../blank/ses_lorawan/flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 221 + .../wm1110_dev_kit_pca10056.emSession | 65 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + .../blank/ses_low_power/flash_placement.xml | 47 + .../wm1110_dev_kit_pca10056.emProject | 221 + .../wm1110_dev_kit_pca10056.emSession | 65 + .../wm1110_dev_kit_pca10056_Debug.jlink | 42 + lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal.h | 22 + .../seeed/smtc_hal/inc/smtc_hal_config.h | 39 + .../seeed/smtc_hal/inc/smtc_hal_dbg_trace.h | 189 + .../lr1110/seeed/smtc_hal/inc/smtc_hal_def.h | 44 + .../seeed/smtc_hal/inc/smtc_hal_flash.h | 50 + .../lr1110/seeed/smtc_hal/inc/smtc_hal_gpio.h | 82 + .../lr1110/seeed/smtc_hal/inc/smtc_hal_i2c.h | 30 + .../seeed/smtc_hal/inc/smtc_hal_lp_time.h | 35 + .../lr1110/seeed/smtc_hal/inc/smtc_hal_mcu.h | 64 + .../seeed/smtc_hal/inc/smtc_hal_options.h | 102 + .../lr1110/seeed/smtc_hal/inc/smtc_hal_rng.h | 25 + .../lr1110/seeed/smtc_hal/inc/smtc_hal_rtc.h | 29 + .../lr1110/seeed/smtc_hal/inc/smtc_hal_spi.h | 29 + .../seeed/smtc_hal/inc/smtc_hal_trace.h | 21 + .../lr1110/seeed/smtc_hal/inc/smtc_hal_uart.h | 39 + .../seeed/smtc_hal/inc/smtc_hal_watchdog.h | 17 + lr1110/lr1110/seeed/smtc_hal/smtc_modem_hal.c | 302 + .../seeed/smtc_hal/src/smtc_hal_flash.c | 85 + .../lr1110/seeed/smtc_hal/src/smtc_hal_gpio.c | 202 + .../lr1110/seeed/smtc_hal/src/smtc_hal_i2c.c | 208 + .../seeed/smtc_hal/src/smtc_hal_lp_time.c | 173 + .../lr1110/seeed/smtc_hal/src/smtc_hal_mcu.c | 143 + .../lr1110/seeed/smtc_hal/src/smtc_hal_rng.c | 85 + .../lr1110/seeed/smtc_hal/src/smtc_hal_rtc.c | 62 + .../lr1110/seeed/smtc_hal/src/smtc_hal_spi.c | 94 + .../seeed/smtc_hal/src/smtc_hal_trace.c | 27 + .../lr1110/seeed/smtc_hal/src/smtc_hal_uart.c | 244 + .../seeed/smtc_hal/src/smtc_hal_watchdog.c | 12 + .../wm1110/LR11XX/common/inc/modem_pinout.h | 87 + .../wm1110/LR11XX/common/src/ral_lr11xx_bsp.c | 195 + .../LR11XX/common/src/smtc_board_lr11xx.c | 134 + .../LR11XX/radio_drivers_hal/lr11xx_hal.c | 352 ++ .../radio_drivers_hal/lr11xx_hal_context.h | 80 + .../smtc_lr11xx_board/smtc_lr11xx_board.h | 100 + .../LR1110MB1DxS/smtc_shield_lr1110mb1dxs.c | 471 ++ .../LR1110MB1GxS/smtc_shield_lr1110mb1gxs.c | 471 ++ .../common/inc/smtc_shield_lr11xx_common_if.h | 155 + .../common/inc/smtc_shield_lr11xx_geoloc_if.h | 119 + .../smtc_shield_lr11xx/common/src/mw_bsp.c | 94 + .../common/src/smtc_shield_lr11x0_common.c | 177 + .../common/src/smtc_shield_lr11xx_common.c | 165 + .../seeed/wm1110/interface/smtc_board.h | 125 + .../seeed/wm1110/interface/smtc_board_ralf.h | 84 + .../seeed/wm1110/libraries/uart/app_uart.h | 292 + .../wm1110/libraries/uart/app_uart_fifo.c | 255 + .../seeed/wm1110/peripherals/inc/lis3dh.h | 170 + .../seeed/wm1110/peripherals/inc/sht41.h | 51 + .../seeed/wm1110/peripherals/src/lis3dh.c | 323 + .../seeed/wm1110/peripherals/src/sht41.c | 196 + wm1110/.zip | Bin 0 -> 1412473 bytes wm1110/Makefile | 80 + wm1110/Makefile.app | 61 + wm1110/Makefile.setup | 28 + wm1110/wm1110/inc_changed/main_lorawan.h | 120 + wm1110/wm1110/inc_changed/smtc_hal.h | 20 + wm1110/wm1110/inc_changed/smtc_hal_config.h | 39 + .../wm1110/inc_changed/smtc_hal_dbg_trace.h | 190 + wm1110/wm1110/inc_changed/smtc_hal_def.h | 44 + wm1110/wm1110/inc_changed/smtc_hal_flash.h | 30 + wm1110/wm1110/inc_changed/smtc_hal_gpio.h | 82 + wm1110/wm1110/inc_changed/smtc_hal_i2c.h | 30 + wm1110/wm1110/inc_changed/smtc_hal_lp_time.h | 35 + wm1110/wm1110/inc_changed/smtc_hal_mcu.h | 64 + wm1110/wm1110/inc_changed/smtc_hal_options.h | 102 + wm1110/wm1110/inc_changed/smtc_hal_rng.h | 25 + wm1110/wm1110/inc_changed/smtc_hal_rtc.h | 29 + wm1110/wm1110/inc_changed/smtc_hal_spi.h | 29 + wm1110/wm1110/inc_changed/smtc_hal_trace.h | 21 + wm1110/wm1110/inc_changed/smtc_hal_uart.h | 39 + wm1110/wm1110/inc_changed/smtc_hal_watchdog.h | 17 + wm1110/wm1110/inc_changed/smtc_real_defs.h | 390 ++ wm1110/wm1110/src_changed/apps_modem_common.c | 276 + wm1110/wm1110/src_changed/apps_modem_event.c | 327 ++ wm1110/wm1110/src_changed/lr11xx_hal.c | 422 ++ wm1110/wm1110/src_changed/lr11xx_system.c | 679 +++ wm1110/wm1110/src_changed/mw_common.c | 159 + wm1110/wm1110/src_changed/radio_planner.c | 1068 ++++ wm1110/wm1110/src_changed/region_us_915.c | 756 +++ wm1110/wm1110/src_changed/smtc_d2d.c | 603 ++ wm1110/wm1110/src_changed/smtc_hal_flash.c | 39 + wm1110/wm1110/src_changed/smtc_hal_gpio.c | 276 + wm1110/wm1110/src_changed/smtc_hal_i2c.c | 208 + wm1110/wm1110/src_changed/smtc_hal_lp_time.c | 215 + wm1110/wm1110/src_changed/smtc_hal_mcu.c | 145 + wm1110/wm1110/src_changed/smtc_hal_rng.c | 98 + wm1110/wm1110/src_changed/smtc_hal_rtc.c | 85 + wm1110/wm1110/src_changed/smtc_hal_spi.c | 117 + wm1110/wm1110/src_changed/smtc_hal_trace.c | 28 + wm1110/wm1110/src_changed/smtc_hal_uart.c | 244 + wm1110/wm1110/src_changed/smtc_hal_watchdog.c | 12 + wm1110/wm1110/src_changed/smtc_modem.c | 3230 ++++++++++ wm1110/wm1110/src_changed/smtc_modem_test.c | 902 +++ wm1110/wm1110/src_changed/wifi_helpers.c | 263 + wm1110/wm1110/src_changed/wifi_middleware.c | 815 +++ wm1110/wm1110/wm1110.h | 131 + 433 files changed, 138922 insertions(+) create mode 100644 examples/lorawan/Makefile create mode 100644 examples/lorawan/README.md create mode 100644 examples/lorawan/main_lorawan.c create mode 100644 examples/wifi_scan/Makefile create mode 100644 examples/wifi_scan/README.md create mode 100644 examples/wifi_scan/main_geolocation_wifi.c create mode 100644 examples/wm1110/Makefile create mode 100644 examples/wm1110/main.c create mode 100644 libtock/nonvolatile_storage.c create mode 100644 libtock/nonvolatile_storage.h create mode 100644 lr1110/lr1110/seeed/README.md create mode 100644 lr1110/lr1110/seeed/apps/common/README.md create mode 100644 lr1110/lr1110/seeed/apps/common/apps_modem_common.c create mode 100644 lr1110/lr1110/seeed/apps/common/apps_modem_common.h create mode 100644 lr1110/lr1110/seeed/apps/common/apps_modem_event.c create mode 100644 lr1110/lr1110/seeed/apps/common/apps_modem_event.h create mode 100644 lr1110/lr1110/seeed/apps/common/apps_utilities.c create mode 100644 lr1110/lr1110/seeed/apps/common/apps_utilities.h create mode 100644 lr1110/lr1110/seeed/apps/common/lorawan_key_config.h create mode 100644 lr1110/lr1110/seeed/apps/common/smtc_modem_api_str.c create mode 100644 lr1110/lr1110/seeed/apps/common/smtc_modem_api_str.h create mode 100644 lr1110/lr1110/seeed/apps/examples/accelerometer/main_accelerometer.c create mode 100644 lr1110/lr1110/seeed/apps/examples/air_th_sensor/main_air_th_sensor.c create mode 100644 lr1110/lr1110/seeed/apps/examples/blinky/main_blinky.c create mode 100644 lr1110/lr1110/seeed/apps/examples/full_almanac_update/README.md create mode 100644 lr1110/lr1110/seeed/apps/examples/full_almanac_update/almanac.h create mode 100644 lr1110/lr1110/seeed/apps/examples/full_almanac_update/almanac_old.h create mode 100644 lr1110/lr1110/seeed/apps/examples/full_almanac_update/get_full_almanac.py create mode 100644 lr1110/lr1110/seeed/apps/examples/full_almanac_update/main_full_almanac_update.c create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_application_server/README.md create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_application_server/geolocation.json create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_application_server/modem.json create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_gnss/README.md create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_gnss/main_geolocation_gnss.c create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_gnss/main_geolocation_gnss.h create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/README.md create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/main_geolocation_gnss_wifi.c create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/main_geolocation_gnss_wifi.h create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_wifi/README.md create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_wifi/main_geolocation_wifi.c create mode 100644 lr1110/lr1110/seeed/apps/examples/geolocation_wifi/main_geolocation_wifi.h create mode 100644 lr1110/lr1110/seeed/apps/examples/lorawan/README.md create mode 100644 lr1110/lr1110/seeed/apps/examples/lorawan/main_lorawan.c create mode 100644 lr1110/lr1110/seeed/apps/examples/lorawan/main_lorawan.h create mode 100644 lr1110/lr1110/seeed/apps/examples/low_power/main_low_power.c create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/bsp/mw_bsp.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/common/mw_assert.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/common/mw_common.c create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/common/mw_common.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/common/mw_dbg_trace.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/doc/applicationServer.rst create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docApplicationServerAssistancePosition.png create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docApplicationServerBlockDiagram.png create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docMiddlewareSequenceOverview.png create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_mobileScanScheme.png create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_staticScanScheme.png create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/doc/geolocationMiddleware.rst create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers.c create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers_defs.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_middleware.c create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_middleware.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue.c create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue_defs.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_version.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/lr11xx_driver_extension.c create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/gnss/src/lr11xx_driver_extension.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/readme.md create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers.c create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers_defs.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_middleware.c create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_middleware.h create mode 100644 lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_version.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/.gitlab-ci.yml create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/CHANGELOG.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/LICENSE.txt create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/LICENSES.txt create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/Makefile create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/README.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/lora_basics_modem_version.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/makefiles/common.mk create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/makefiles/lr11xx.mk create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/makefiles/printing.mk create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/makefiles/regions.mk create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/makefiles/sx126x.mk create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/makefiles/sx128x.mk create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/CHANGELOG.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/Makefile create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/doxygen.config create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/smtc_basic_modem_lr11xx_api_extension.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/smtc_modem_api.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/smtc_modem_middleware_advanced_api.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/smtc_modem_test_api.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/smtc_modem_utilities.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/README.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/device_management/device_management_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/device_management/dm_downlink.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/device_management/dm_downlink.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/device_management/modem_context.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/device_management/modem_context.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lorawan_api/lorawan_api.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lorawan_api/lorawan_api.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/license.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/lr1mac_config.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1_stack_mac_layer.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1_stack_mac_layer.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_beacon_sniff.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_beacon_sniff.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_d2d.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_d2d.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_ping_slot.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_ping_slot.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_class_c/lr1mac_class_c.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_class_c/lr1mac_class_c.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_core.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_core.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_utilities.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/lr1mac_utilities.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/services/smtc_duty_cycle.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/services/smtc_duty_cycle.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/services/smtc_lbt.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/services/smtc_lbt.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/services/smtc_multicast.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/services/smtc_multicast.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_as_923.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_as_923.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_as_923_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_au_915.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_au_915.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_au_915_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470_rp_1_0.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470_rp_1_0.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470_rp_1_0_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_eu_868.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_eu_868.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_eu_868_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_in_865.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_in_865.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_in_865_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_kr_920.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_kr_920.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_kr_920_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_ru_864.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_ru_864.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_ru_864_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_us_915.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_us_915.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_us_915_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_ww2g4.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_ww2g4.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/region_ww2g4_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/smtc_real.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/smtc_real.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/smtc_real_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/lr1mac/src/smtc_real/src/smtc_real_defs_str.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_config/smtc_modem_hal_dbg_trace.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_core/smtc_modem.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_core/smtc_modem_test.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/fifo_ctrl.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/fifo_ctrl.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/fragmentation/frag_decoder.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/fragmentation/frag_decoder.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/fragmentation/fragmented_data_block.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/fragmentation/fragmented_data_block.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/fragmentation/patch_upd.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/fragmentation/patch_upd.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/lorawan_certification.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/lorawan_certification.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/modem_utilities.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/modem_utilities.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/smtc_clock_sync.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/smtc_clock_sync.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/smtc_modem_services_config.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/smtc_modem_services_config.h.bak create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_services/smtc_modem_services_hal.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_supervisor/modem_supervisor.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/modem_supervisor/modem_supervisor.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/CHANGELOG.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/LICENSE.txt create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/README.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_bootloader.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_bootloader.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_bootloader_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_crypto_engine.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_crypto_engine.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_crypto_engine_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_driver_module.cmake create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_driver_version.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_driver_version.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_gnss.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_gnss.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_gnss_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_hal.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_lr_fhss.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_lr_fhss.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_lr_fhss_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_radio.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_radio.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_radio_timings.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_radio_timings.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_radio_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_regmem.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_regmem.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_system.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_system.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_system_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_wifi.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_wifi.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_wifi_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/lr11xx_driver/src/lr_fhss_v1_base_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/CHANGELOG.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/LICENSE.txt create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/README.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/lr_fhss_mac.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/lr_fhss_mac.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/lr_fhss_v1_base_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/sx126x.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/sx126x.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/sx126x_hal.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/sx126x_lr_fhss.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/sx126x_lr_fhss.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx126x_driver/src/sx126x_regs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx128x_driver/LICENSE.txt create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx128x_driver/readme.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx128x_driver/src/sx128x.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx128x_driver/src/sx128x.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx128x_driver/src/sx128x_hal.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_drivers/sx128x_driver/src/sx128x_regs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_planner/license.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_planner/src/radio_planner.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_planner/src/radio_planner.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_planner/src/radio_planner_hal.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_planner/src/radio_planner_hal.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_planner/src/radio_planner_hook_id_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_planner/src/radio_planner_stats.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/radio_planner/src/radio_planner_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/lr11xx_crypto_engine/lr11xx_ce.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/smtc_modem_crypto.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/smtc_modem_crypto.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/smtc_secure_element/smtc_secure_element.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/soft_secure_element/aes.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/soft_secure_element/aes.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/soft_secure_element/cmac.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/soft_secure_element/cmac.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_crypto/soft_secure_element/soft_se.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/headers/alc_sync.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/headers/almanac_update.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/headers/file_upload.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/headers/stream.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/smtc_modem_services_config.template.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/smtc_modem_services_hal.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/alc_sync/alc_sync.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/almanac_update/almanac_update.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/file_upload/file_upload.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/file_upload/file_upload_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/modem_services_common.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/stream/lmic_defines.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/stream/rose.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/stream/rose.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/stream/rose.rst create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/stream/rose_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_modem_services/src/stream/stream.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/license.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/lr_fhss_v1_base_types.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_drv.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_llcc68.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_llcc68.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_llcc68_bsp.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_lr11xx.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_lr11xx.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_lr11xx_bsp.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_sx126x.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_sx126x.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_sx126x_bsp.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_sx128x.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_sx128x.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ral/src/ral_sx128x_bsp.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/license.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_defs.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_drv.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_lr11xx.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_lr11xx.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_lr11xx_bsp.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_sx126x.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_sx126x.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_sx126x_bsp.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_sx128x.c create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_sx128x.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_core/smtc_ralf/src/ralf_sx128x_bsp.h create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_hal/CHANGELOG.md create mode 100644 lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_hal/smtc_modem_hal.h create mode 100644 lr1110/lr1110/seeed/pca10056/blank/config/sdk_config.h create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_accelerometer/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_accelerometer/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_accelerometer/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_accelerometer/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_air_th_sensor/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_air_th_sensor/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_air_th_sensor/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_air_th_sensor/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_blinky/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_blinky/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_blinky/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_blinky/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_full_almanac_update/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_full_almanac_update/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_full_almanac_update/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_full_almanac_update/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_gnss/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_gnss/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_gnss/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_gnss/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_gnss_wifi/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_gnss_wifi/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_gnss_wifi/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_gnss_wifi/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_wifi/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_wifi/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_wifi/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_geolocation_wifi/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_lorawan/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_lorawan/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_lorawan/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_lorawan/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_low_power/flash_placement.xml create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_low_power/wm1110_dev_kit_pca10056.emProject create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_low_power/wm1110_dev_kit_pca10056.emSession create mode 100644 lr1110/lr1110/seeed/pca10056/blank/ses_low_power/wm1110_dev_kit_pca10056_Debug.jlink create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_config.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_dbg_trace.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_def.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_flash.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_gpio.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_i2c.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_lp_time.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_mcu.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_options.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_rng.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_rtc.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_spi.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_trace.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_uart.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/inc/smtc_hal_watchdog.h create mode 100644 lr1110/lr1110/seeed/smtc_hal/smtc_modem_hal.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_flash.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_gpio.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_i2c.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_lp_time.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_mcu.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_rng.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_rtc.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_spi.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_trace.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_uart.c create mode 100644 lr1110/lr1110/seeed/smtc_hal/src/smtc_hal_watchdog.c create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/common/inc/modem_pinout.h create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/common/src/ral_lr11xx_bsp.c create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/common/src/smtc_board_lr11xx.c create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/radio_drivers_hal/lr11xx_hal.c create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/radio_drivers_hal/lr11xx_hal_context.h create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/smtc_lr11xx_board/smtc_lr11xx_board.h create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/smtc_shield_lr11xx/LR1110/LR1110MB1DxS/smtc_shield_lr1110mb1dxs.c create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/smtc_shield_lr11xx/LR1110/LR1110MB1GxS/smtc_shield_lr1110mb1gxs.c create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/smtc_shield_lr11xx/common/inc/smtc_shield_lr11xx_common_if.h create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/smtc_shield_lr11xx/common/inc/smtc_shield_lr11xx_geoloc_if.h create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/smtc_shield_lr11xx/common/src/mw_bsp.c create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/smtc_shield_lr11xx/common/src/smtc_shield_lr11x0_common.c create mode 100644 lr1110/lr1110/seeed/wm1110/LR11XX/smtc_shield_lr11xx/common/src/smtc_shield_lr11xx_common.c create mode 100644 lr1110/lr1110/seeed/wm1110/interface/smtc_board.h create mode 100644 lr1110/lr1110/seeed/wm1110/interface/smtc_board_ralf.h create mode 100644 lr1110/lr1110/seeed/wm1110/libraries/uart/app_uart.h create mode 100644 lr1110/lr1110/seeed/wm1110/libraries/uart/app_uart_fifo.c create mode 100644 lr1110/lr1110/seeed/wm1110/peripherals/inc/lis3dh.h create mode 100644 lr1110/lr1110/seeed/wm1110/peripherals/inc/sht41.h create mode 100644 lr1110/lr1110/seeed/wm1110/peripherals/src/lis3dh.c create mode 100644 lr1110/lr1110/seeed/wm1110/peripherals/src/sht41.c create mode 100644 wm1110/.zip create mode 100644 wm1110/Makefile create mode 100644 wm1110/Makefile.app create mode 100644 wm1110/Makefile.setup create mode 100644 wm1110/wm1110/inc_changed/main_lorawan.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_config.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_dbg_trace.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_def.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_flash.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_gpio.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_i2c.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_lp_time.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_mcu.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_options.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_rng.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_rtc.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_spi.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_trace.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_uart.h create mode 100644 wm1110/wm1110/inc_changed/smtc_hal_watchdog.h create mode 100644 wm1110/wm1110/inc_changed/smtc_real_defs.h create mode 100644 wm1110/wm1110/src_changed/apps_modem_common.c create mode 100644 wm1110/wm1110/src_changed/apps_modem_event.c create mode 100644 wm1110/wm1110/src_changed/lr11xx_hal.c create mode 100644 wm1110/wm1110/src_changed/lr11xx_system.c create mode 100644 wm1110/wm1110/src_changed/mw_common.c create mode 100644 wm1110/wm1110/src_changed/radio_planner.c create mode 100644 wm1110/wm1110/src_changed/region_us_915.c create mode 100644 wm1110/wm1110/src_changed/smtc_d2d.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_flash.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_gpio.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_i2c.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_lp_time.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_mcu.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_rng.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_rtc.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_spi.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_trace.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_uart.c create mode 100644 wm1110/wm1110/src_changed/smtc_hal_watchdog.c create mode 100644 wm1110/wm1110/src_changed/smtc_modem.c create mode 100644 wm1110/wm1110/src_changed/smtc_modem_test.c create mode 100644 wm1110/wm1110/src_changed/wifi_helpers.c create mode 100644 wm1110/wm1110/src_changed/wifi_middleware.c create mode 100644 wm1110/wm1110/wm1110.h diff --git a/examples/lorawan/Makefile b/examples/lorawan/Makefile new file mode 100644 index 000000000..453fb95a0 --- /dev/null +++ b/examples/lorawan/Makefile @@ -0,0 +1,19 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../.. + +# External libraries used +EXTERN_LIBS += $(TOCK_USERLAND_BASE_DIR)/wm1110 + +override CFLAGS += -I$(TOCK_USERLAND_BASE_DIR)/wm1110 + +# Which files to compile. +C_SRCS += $(wildcard *.c) + +APP_HEAP_SIZE := 40000 +STACK_SIZE := 4096 + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk \ No newline at end of file diff --git a/examples/lorawan/README.md b/examples/lorawan/README.md new file mode 100644 index 000000000..e03c847e8 --- /dev/null +++ b/examples/lorawan/README.md @@ -0,0 +1,21 @@ +# LoRa Basics Modem LoRaWAN Class A/C example + +## Description + +The application will automatically starts a procedure to join a LoRaWAN network (see [configuration](../../apps/common/lorawan_key_config.h)). + +Once a network is joined (i.e. when the corresponding event is triggered), uplinks are sent periodically. This periodic action is based on the LoRa Basics Modem alarm functionality. Each time the alarm-related event is triggered, the application requests an uplink. + +The content of the uplink is the value read out from the charge counter by calling `smtc_modem_get_charge()`. + +The application is also capable of displaying data and meta-data of a received downlink. + +## Configuration + +Several parameters can be updated in `main_lorawan.h` header file: + +| Constant | Description | +| -------------------------- | ----------------------------------------------------------------------------- | +| `LORAWAN_APP_PORT` | LoRaWAN FPort used for the uplink messages | +| `LORAWAN_CONFIRMED_MSG_ON` | Request a confirmation from the LNS that the uplink message has been received | +| `APP_TX_DUTYCYCLE` | Delay in second between two uplinks | diff --git a/examples/lorawan/main_lorawan.c b/examples/lorawan/main_lorawan.c new file mode 100644 index 000000000..bb79abcc3 --- /dev/null +++ b/examples/lorawan/main_lorawan.c @@ -0,0 +1,522 @@ +/*! + * @file main_lorawan.c + * + * @brief LoRa Basics Modem Class A/C device implementation + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +// #include "main_lorawan.h" +// #include "lorawan_key_config.h" +// #include "smtc_board.h" +// #include "smtc_hal.h" +// #include "apps_modem_common.h" +// #include "apps_modem_event.h" +// #include "smtc_modem_api.h" +// #include "device_management_defs.h" +// #include "smtc_board_ralf.h" +// #include "apps_utilities.h" +// #include "smtc_modem_utilities.h" + +#include +// #include +#include +#include +#include + +#include +#include +#include + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/*! + * @brief Stringify constants + */ +#define xstr( a ) str( a ) +#define str( a ) #a + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +#define LORAWAN_REGION SMTC_MODEM_REGION_US_915 +#define LORAWAN_CLASS SMTC_MODEM_CLASS_A + +// wm1110dev parameters +#define LORAWAN_DEVICE_EUI "70B3D57ED00650D9" +#define LORAWAN_JOIN_EUI "901AB1F40E1BCC81" +#define LORAWAN_APP_KEY "3356A7047ECF1F2F78C72AE9B1635BC1" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/*! + * @brief Stack identifier + */ +static uint8_t stack_id = 0; + +/*! + * @brief User application data + */ +static uint8_t app_data_buffer[LORAWAN_APP_DATA_MAX_SIZE]; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Send an application frame on LoRaWAN port defined by LORAWAN_APP_PORT + * + * @param [in] buffer Buffer containing the LoRaWAN buffer + * @param [in] length Payload length + * @param [in] confirmed Send a confirmed or unconfirmed uplink [false : unconfirmed / true : confirmed] + */ +static void send_frame( const uint8_t* buffer, const uint8_t length, const bool confirmed ); + +/*! + * @brief Parse the received downlink + * + * @remark Demonstrates how a TLV-encoded command sequence received by downlink can control the state of an LED. It can + * easily be extended to handle other commands received on the same port or another port. + * + * @param [in] port LoRaWAN port + * @param [in] payload Payload Buffer + * @param [in] size Payload size + */ +static void parse_downlink_frame( uint8_t port, const uint8_t* payload, uint8_t size ); + +/*! + * @brief Reset event callback + * + * @param [in] reset_count reset counter from the modem + */ +static void on_modem_reset( uint16_t reset_count ); + +/*! + * @brief Network Joined event callback + */ +static void on_modem_network_joined( void ); + +/*! + * @brief Alarm event callback + */ +static void on_modem_alarm( void ); + +/*! + * @brief Tx done event callback + * + * @param [in] status tx done status @ref smtc_modem_event_txdone_status_t + */ +static void on_modem_tx_done( smtc_modem_event_txdone_status_t status ); + +/*! + * @brief Downlink data event callback. + * + * @param [in] rssi RSSI in signed value in dBm + 64 + * @param [in] snr SNR signed value in 0.25 dB steps + * @param [in] rx_window RX window + * @param [in] port LoRaWAN port + * @param [in] payload Received buffer pointer + * @param [in] size Received buffer size + */ +static void on_modem_down_data( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ); + +static void on_modem_join_fail( void ); + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +/** + * @brief Main application entry point. + */ + +lr11xx_hal_context_t radio_context = { + .nss = LR1110_SPI_NSS_PIN, + .busy = LR1110_BUSY_PIN, + .reset = LR1110_NRESER_PIN, + .spi_id = 3, +}; + +int main( void ) +{ + //hal_gpio_init_out( LR1110_SPI_NSS_PIN, HAL_GPIO_SET ); + // hal_gpio_init_in( LR1110_BUSY_PIN, HAL_GPIO_PULL_MODE_NONE, HAL_GPIO_IRQ_MODE_OFF, NULL ); + // hal_gpio_init_in( LR1110_IRQ_PIN, HAL_GPIO_PULL_MODE_DOWN, HAL_GPIO_IRQ_MODE_RISING, NULL ); + // hal_gpio_init_out( LR1110_NRESER_PIN, HAL_GPIO_SET ); + + // lr11xx_system_version_t version; + // lr11xx_status_t status; + + // hal_spi_init( ); + + // printf("\nafter spi init\n"); + + // lr11xx_hal_reset(&radio_context); + + lr11xx_system_clear_errors( &radio_context ); + + lr11xx_status_t status; + lr11xx_system_version_t version; + status = lr11xx_system_get_version( &radio_context, &version ); + printf("Hardware Version: %u, 0x%04X\n", version.hw, version.hw); + printf("Type: %u, 0x%04X\n", version.type, version.type); + printf("Firmware Version: %u, 0x%04X\n", version.fw, version.fw); + // if( status != LR11XX_STATUS_OK ) + // { + // printf( "Failed to get LR11XX firmware version\n" ); + // } + // if( ( ( version.fw != LR1110_FW_VERSION ) && ( version.type = LR1110_FW_TYPE ) ) && + // ( ( version.fw != LR1120_FW_VERSION ) && ( version.type = LR1120_FW_TYPE ) ) ) + // { + // printf( "Wrong LR11XX firmware version, expected 0x%04X, got 0x%04X\n", LR1110_FW_VERSION, + // version.fw ); + // } + + lr11xx_system_uid_t unique_identifier; + status= lr11xx_system_read_uid( &radio_context, &unique_identifier ); + + printf("uid %x %x %x\n", unique_identifier[0], unique_identifier[1], unique_identifier[2]); + + + printf("start of lorawan app\n"); + + static apps_modem_event_callback_t smtc_event_callback = { + .adr_mobile_to_static = NULL, + .alarm = on_modem_alarm, + .almanac_update = NULL, + .down_data = on_modem_down_data, + .join_fail = on_modem_join_fail, + .joined = on_modem_network_joined, + .link_status = NULL, + .mute = NULL, + .new_link_adr = NULL, + .reset = on_modem_reset, + .set_conf = NULL, + .stream_done = NULL, + .time_updated_alc_sync = NULL, + .tx_done = on_modem_tx_done, + .upload_done = NULL, + }; + + /* Initialise the ralf_t object corresponding to the board */ + ralf_t* modem_radio = smtc_board_initialise_and_get_ralf( ); + + /* Disable IRQ to avoid unwanted behaviour during init */ + //hal_mcu_disable_irq( ); + + + /* Init board and peripherals */ + hal_mcu_init( ); + smtc_board_init_periph( ); + + /* Init the Lora Basics Modem event callbacks */ + apps_modem_event_init( &smtc_event_callback ); + + /* Init the modem and use apps_modem_event_process as event callback, please note that the callback will be called + * immediately after the first call to modem_run_engine because of the reset detection */ + smtc_modem_init( modem_radio, &apps_modem_event_process ); // cause process fault + + /* Re-enable IRQ */ + // hal_mcu_enable_irq( ); + + //HAL_DBG_TRACE_MSG( "\n" ); + //HAL_DBG_TRACE_INFO( "###### ===== LoRa Basics Modem LoRaWAN Class A/C demo application ==== ######\n\n" ); + printf("\n###### ===== LoRa Basics Modem LoRaWAN Class A/C demo application ==== ######\n\n"); + + /* LoRa Basics Modem Version */ + apps_modem_common_display_lbm_version( ); + + /* Configure the partial low power mode */ + // hal_mcu_partial_sleep_enable( APP_PARTIAL_SLEEP ); // smtc function implementation is empty + + lr11xx_system_clear_errors( &radio_context ); + lr11xx_system_clear_irq_status(&radio_context, 0xFFFFFFFF); + + printf("inside loop\n"); + + while( 1 ) + { + lr11xx_system_clear_errors( &radio_context ); + + // delay_ms(10); + /* Execute modem runtime, this function must be called again in sleep_time_ms milliseconds or sooner. */ + uint32_t sleep_time_ms = smtc_modem_run_engine( ); // cause process fault + + // printf("sleep %i\n", sleep_time_ms); + + /* go in low power */ + hal_mcu_set_sleep_for_ms( sleep_time_ms ); + + // delay_ms(1000); + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +static void on_modem_reset( uint16_t reset_count ) +{ + printf("on_modem_reset\n"); + + HAL_DBG_TRACE_INFO( "Application parameters:\n" ); + HAL_DBG_TRACE_INFO( " - LoRaWAN uplink Fport = %d\n", LORAWAN_APP_PORT ); + HAL_DBG_TRACE_INFO( " - DM report interval = %d\n", APP_TX_DUTYCYCLE ); + HAL_DBG_TRACE_INFO( " - Confirmed uplink = %s\n", ( LORAWAN_CONFIRMED_MSG_ON == true ) ? "Yes" : "No" ); + + apps_modem_common_configure_lorawan_params( stack_id ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_join_network( stack_id ) ); +} + +static void on_modem_network_joined( void ) +{ + printf("on_modem_network_joined successful!\n"); + + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( APP_TX_DUTYCYCLE ) ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_adr_set_profile( stack_id, LORAWAN_DEFAULT_DATARATE, adr_custom_list ) ); +} + +static void on_modem_join_fail( void ) +{ + printf("join failed!\n"); +} + +static void on_modem_alarm( void ) +{ + printf("on_modem_alarm\n"); + smtc_modem_status_mask_t modem_status; + // uint32_t charge = 0; + uint8_t app_data_size = 0; + + /* Schedule next packet transmission */ + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( APP_TX_DUTYCYCLE ) ); + HAL_DBG_TRACE_PRINTF( "smtc_modem_alarm_start_timer: %d s\n\n", APP_TX_DUTYCYCLE ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_status( stack_id, &modem_status ) ); + modem_status_to_string( modem_status ); + + // ASSERT_SMTC_MODEM_RC( smtc_modem_get_charge( &charge ) ); + + printf("[Sensors] Sampling Temperature and Humidity sensors once.\n"); + + bool temperature_available = driver_exists(DRIVER_NUM_TEMPERATURE); + bool humidity_available = driver_exists(DRIVER_NUM_HUMIDITY); + int temp = 0; + unsigned humi = 0; + if (temperature_available) { + temperature_read_sync(&temp); + printf("Temperature: %d.%d deg C, send %d to TTN\n", temp / 100, temp % 100, temp); + } else { + printf("Temperature sensor not available.\n"); + } + + if (humidity_available) { + humidity_read_sync(&humi); + printf("Humidity: %d.%d%%, send %d to TTN\n", humi / 100, humi % 100, humi); + } else { + printf("Humidity sensor not available.\n"); + } + + app_data_buffer[app_data_size++] = (uint8_t)((temp >> 8) & 0xFF); + app_data_buffer[app_data_size++] = (uint8_t)(temp & 0xFF); + app_data_buffer[app_data_size++] = (uint8_t)((humi >> 8) & 0xFF); + app_data_buffer[app_data_size++] = (uint8_t)(humi & 0xFF); + + send_frame( app_data_buffer, app_data_size, LORAWAN_CONFIRMED_MSG_ON ); +} + +static void on_modem_tx_done( smtc_modem_event_txdone_status_t status ) +{ + printf("on_modem_tx_done\n"); + static uint32_t uplink_count = 0; + + HAL_DBG_TRACE_INFO( "Uplink count: %d\n", ++uplink_count ); +} + +static void on_modem_down_data( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ) +{ + printf("on_modem_down_data\n"); + HAL_DBG_TRACE_INFO( "Downlink received:\n" ); + HAL_DBG_TRACE_INFO( " - LoRaWAN Fport = %d\n", port ); + HAL_DBG_TRACE_INFO( " - Payload size = %d\n", size ); + HAL_DBG_TRACE_INFO( " - RSSI = %d dBm\n", rssi - 64 ); + HAL_DBG_TRACE_INFO( " - SNR = %d dB\n", snr >> 2 ); + + switch( rx_window ) + { + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX1: + { + HAL_DBG_TRACE_INFO( " - Rx window = %s\n", xstr( SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX1 ) ); + break; + } + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX2: + { + HAL_DBG_TRACE_INFO( " - Rx window = %s\n", xstr( SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX2 ) ); + break; + } + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC: + { + HAL_DBG_TRACE_INFO( " - Rx window = %s\n", xstr( SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC ) ); + break; + } + } + + if( size != 0 ) + { + HAL_DBG_TRACE_ARRAY( "Payload", payload, size ); + } +} + +static void send_frame( const uint8_t* buffer, const uint8_t length, bool tx_confirmed ) +{ + printf("send_frame\n"); + uint8_t tx_max_payload; + int32_t duty_cycle; + + /* Check if duty cycle is available */ + ASSERT_SMTC_MODEM_RC( smtc_modem_get_duty_cycle_status( &duty_cycle ) ); + if( duty_cycle < 0 ) + { + HAL_DBG_TRACE_WARNING( "Duty-cycle limitation - next possible uplink in %d ms \n\n", duty_cycle ); + return; + } + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_next_tx_max_payload( stack_id, &tx_max_payload ) ); + if( length > tx_max_payload ) + { + HAL_DBG_TRACE_WARNING( "Not enough space in buffer - send empty uplink to flush MAC commands \n" ); + ASSERT_SMTC_MODEM_RC( smtc_modem_request_empty_uplink( stack_id, true, LORAWAN_APP_PORT, tx_confirmed ) ); + } + else + { + HAL_DBG_TRACE_INFO( "Request uplink\n" ); + ASSERT_SMTC_MODEM_RC( smtc_modem_request_uplink( stack_id, LORAWAN_APP_PORT, tx_confirmed, buffer, length ) ); + } +} + +void apps_modem_common_configure_lorawan_params( uint8_t stack_id ) +{ + printf("apps_modem_common_configure_lorawan_params\n"); + + smtc_modem_return_code_t rc = SMTC_MODEM_RC_OK; + uint8_t dev_eui[8] = { 0 }; + uint8_t join_eui[8] = { 0 }; + uint8_t app_key[16] = { 0 }; + + hal_hex_to_bin( LORAWAN_DEVICE_EUI, dev_eui, 8 ); + hal_hex_to_bin( LORAWAN_JOIN_EUI, join_eui, 8 ); + hal_hex_to_bin( LORAWAN_APP_KEY, app_key, 16 ); + + rc = smtc_modem_set_deveui( stack_id, dev_eui ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_deveui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_joineui( stack_id, join_eui ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_joineui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_nwkkey( stack_id, app_key ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_nwkkey failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + HAL_DBG_TRACE_INFO( "LoRaWAN parameters:\n" ); + + rc = smtc_modem_get_deveui( stack_id, dev_eui ); + if( rc == SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ARRAY( "DevEUI", dev_eui, SMTC_MODEM_EUI_LENGTH ); + } + else + { + printf( "smtc_modem_get_deveui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_get_joineui( stack_id, join_eui ); + if( rc == SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ARRAY( "JoinEUI", join_eui, SMTC_MODEM_EUI_LENGTH ); + } + else + { + printf( "smtc_modem_get_joineui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_class( stack_id, LORAWAN_CLASS ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_class failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + modem_class_to_string( LORAWAN_CLASS ); + + rc = smtc_modem_set_region( stack_id, LORAWAN_REGION ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_region failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + modem_region_to_string( LORAWAN_REGION ); + + /* adapt the tx power offet depending on the board */ + rc |= smtc_modem_set_tx_power_offset_db( stack_id, smtc_board_get_tx_power_offset( ) ); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/examples/wifi_scan/Makefile b/examples/wifi_scan/Makefile new file mode 100644 index 000000000..a336574e5 --- /dev/null +++ b/examples/wifi_scan/Makefile @@ -0,0 +1,19 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../.. + +# External libraries used +EXTERN_LIBS += $(TOCK_USERLAND_BASE_DIR)/wm1110 + +override CFLAGS += -I$(TOCK_USERLAND_BASE_DIR)/wm1110 + +# Which files to compile. +C_SRCS += $(wildcard *.c) + +APP_HEAP_SIZE := 40000 +STACK_SIZE := 4096 + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk diff --git a/examples/wifi_scan/README.md b/examples/wifi_scan/README.md new file mode 100644 index 000000000..831c8c2e6 --- /dev/null +++ b/examples/wifi_scan/README.md @@ -0,0 +1,179 @@ +# Geolocation Wi-Fi example application + +## Description + +This application demonstrates the usage of the Wi-Fi geolocation middleware and +how the LoRa Basics Modem should be configured to meet the prerequisites for +This example illustrates the Wi-Fi scan procedure: + +- configuration of the LoRa Basics Modem library; and +- execution of Wi-Fi *scan & send* feature using the *Wi-Fi geolocation middleware*. + +## Wi-Fi middleware + +The Wi-Fi middleware is a software layer used to simplify the integration of a +Wi-Fi scan & send operation in a user application. + +For more details about the middleware, please refer to the documentation here: + +[Geolocation middleware documentation](<../../../geolocation_middleware/readme.md>) + +## LoRa Basics Modem configuration + +There is no particular modem configuration prerequisite for Wi-Fi scanning. + +Once the modem has joined the network, the application will configure the ADR +custom profile for the selected region, disable the modem auto-switch to network +controlled ADR and finally initialize the Wi-Fi middleware, and immediately +initiate the first Wi-Fi scan & send. + +## Wi-Fi middleware events + +After the initial scan has been programmed, the application relies on the events +sent by the Wi-Fi middleware to progress in the scan sequence. + +For this, the application defines a callback function `on_middleware_wifi_event()` +that will be called when the Wi-Fi middleware sends an event. + +In this callback function, the application will process the pending events, and +for any event except the `WIFI_MW_EVENT_SCAN_DONE` event, it will program the +next scan with the delay defined by `WIFI_SCAN_GROUP_PERIOD`. + +## Configuration + +When compiling with *arm-none-eabi-gcc* toolchain, all these constant are configurable through command line with the `EXTRAFLAGS`. +See [README.md](../../../README.md#command-line-configuration). + +### LoRaWAN related configuration + +The `apps/common/lorawan_key_config.h` header file defines several constants to +configure the LoRaWAN parameters (profile, region, keys). + +| Constant | Comments | Possible values | Default Value | +| ---------------- | -------------------------------- | -------------------- | -------------------------- | +| `LORAWAN_REGION` | Selects the regulatory region | See here-below | `SMTC_MODEM_REGION_EU_868` | +| `LORAWAN_CLASS` | Selects the LoRaWAN class to use | `SMTC_MODEM_CLASS_A` | `SMTC_MODEM_CLASS_A` | + +Supported values for `LORAWAN_REGION`: + +* `SMTC_MODEM_REGION_EU_868 (default)` +* `SMTC_MODEM_REGION_US_915` +* `SMTC_MODEM_REGION_CN_470_RP_1_0` + +Supported values for `LORAWAN_CLASS`: + +* `SMTC_MODEM_CLASS_A` + +### Wi-Fi demonstration related configuration + +The `main_geolocation_wifi.h` header file defines several constants which can be +set to define the configurable parameters of the application. + +| Constant | Comments | Possible values | Default Value | +| ------------------ | --------------------------------------------------------------------------------------------- | --------------- | ------------- | +| `WIFI_SCAN_PERIOD` | Defines the duration between the end of a scan & send sequence and the start of next sequence | `uint32_t` | 30 | +## Build + +The demo can be built through GNU make command by doing the following: + +```shell +# Navigate to the build folder +$ cd apps/geolocation_wifi/makefile + +# Execute the make call +$ make -j +``` + +By default, the demonstration is compiled to use the EUIs and Application key +defined in the file *apps/common/lorawan_key_config.h*. + +## Usage + +### Serial console + +The application requires no user intervention after the static configuration +option have been set. + +Information messages are displayed on the serial console, starting with the +DevEUI, AppEUI/JoinEUI and PIN that you might need in order to register your +device with the LoRa Cloud Device Join service. + +### LoRaWAN Network Server / Application Server + +This application needs an Application Server to run in order to perform the Wi-Fi +solving. +Two possibilities : +* A Node-Red application server is provided in folder *apps/geolocation_application_server*. +Refer to the readme in this folder for details about setup and usage of the +application server. +* Use the LoRaCloud Locator https://atk.loracloud.com/ which embed a complete integration of an application server and an associated dashboard. + +### ADR configuration + +The Adaptive Data Rate (ADR) is configured in *Custom ADR profile* with datarate +distribution and number of repetition defined per regions. + +For more details about ADR configuration for geolocation middleware usage, +please refer to this [Geolocation middleware documentation](<../../../geolocation_middleware/doc/geolocationMiddleware.rst>), section "LoRaWAN datarate considerations". + +The actual datarate and number of retransmission values are defined in the +`main_geolocation_wifi.h` header file. + +| Constant | Comments | Default value | +| ----------------------- | -------------------------------------------------------- | ------------------------------------ | +| `CUSTOM_NB_TRANS_EU868` | The number of retransmission to be used for EU868 region | 1 | +| `ADR_CUSTOM_LIST_EU868` | The custom ADR list to be used for EU868 region | '5' repeated 9 times, '4' x5, '3' x2 | +| `CUSTOM_NB_TRANS_US915` | The number of retransmission to be used for US915 region | 2 | +| `ADR_CUSTOM_LIST_US915` | The custom ADR list to be used for US915 region | '5' repeated 9 times, '4' x5, '3' x2 | +| `CUSTOM_NB_TRANS_CN470` | The number of retransmission to be used for CN470 region | 2 | +| `ADR_CUSTOM_LIST_CN470` | The custom ADR list to be used for CN470 region | '3' repeated 9 times, '2' x5, '1' x2 | + +The values must be carefully set to match with duty cycle constraints, power +consumption targets etc... + +## Expected Behavior + +Here follow the steps that shall be seen in the logs to indicate the expected behavior of the application. + +### Device starts and resets + +``` +INFO: Modem Initialization + +INFO: ===== LoRa Basics Modem Geolocation Wi-Fi example ===== +``` + +Following this print you shall find application and parameter prints + +### Joined the network + +At first run no time is supposed to be available + +``` +INFO: ###### ===== JOINED EVENT ==== ###### +``` + +### Execute and send the Wi-Fi scan + +``` +INFO: RP_TASK_WIFI - new scan - task queued at 40097 + 30000 +---- internal Wi-Fi scan start ---- +INFO: start Wi-Fi scan +WARN: No time available. + +INFO: ###### ===== MIDDLEWARE_2 EVENT ==== ###### +INFO: Wi-Fi middleware event - SCAN DONE +SCAN_DONE info: +-- number of results: 3 +-- power consumption: 0 uah +-- Timestamp: 0 +64 70 02 D9 94 55 -- Channel: 1 -- Type: 1 -- RSSI: -78 +3C 17 10 B7 CD 90 -- Channel: 6 -- Type: 1 -- RSSI: -88 +74 B6 B6 42 B4 EB -- Channel: 1 -- Type: 2 -- RSSI: -87 + +---- internal TX DONE ---- +INFO: ###### ===== MIDDLEWARE_2 EVENT ==== ###### +INFO: Wi-Fi middleware event - TERMINATED +TERMINATED info: +-- number of scans sent: 1 +``` \ No newline at end of file diff --git a/examples/wifi_scan/main_geolocation_wifi.c b/examples/wifi_scan/main_geolocation_wifi.c new file mode 100644 index 000000000..705d94265 --- /dev/null +++ b/examples/wifi_scan/main_geolocation_wifi.c @@ -0,0 +1,597 @@ +/*! + * @ingroup apps_geolocation + * @file main_geolocation_wifi.c + * + * @brief LoRa Basics Modem LR11XX Geolocation Wi-Fi example + * + * @copyright + * @parblock + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + * @endparblock + + */ + +/*! + * @addtogroup apps_geolocation + * LoRa Basics Modem LR11XX Geolocation Wi-Fi example + * @{ + */ + +#include +#include +#include +#include + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +// included in wm1110 library + +// #include "main_geolocation_wifi.h" +// #include "smtc_board.h" +// #include "smtc_hal.h" +// #include "apps_utilities.h" +// #include "apps_modem_common.h" +// #include "apps_modem_event.h" + +// #include "wifi_middleware.h" +// #include "lr11xx_system.h" + +// #include "smtc_modem_utilities.h" +// #include "smtc_board_ralf.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ +/** + * @brief LR11XX radio firmware + */ +#define LR1110_FW_VERSION 0x0307 +#define LR1110_FW_TYPE 0x01 +// #define LR1120_FW_VERSION 0x0101 +// #define LR1120_FW_TYPE 0x02 + +#define LORAWAN_REGION SMTC_MODEM_REGION_US_915 +#define LORAWAN_CLASS SMTC_MODEM_CLASS_A + +// wm1110dev parameters +#define LORAWAN_DEVICE_EUI "70B3D57ED00650D9" +#define LORAWAN_JOIN_EUI "901AB1F40E1BCC81" +#define LORAWAN_APP_KEY "3356A7047ECF1F2F78C72AE9B1635BC1" + +/*! + * @brief Defines the delay before starting a new Wi-Fi scan, value in [s]. + */ +#define WIFI_SCAN_PERIOD_DEFAULT 30 + +/* + * ----------------------------------------------------------------------------- + * --- LoRaWAN Configuration --------------------------------------------------- + */ + +/*! + * @brief ADR custom list and retransmission parameters for EU868 / IN865 / RU864 / AU915 / CN470 /AS923 / KR920 regions + */ +#define CUSTOM_NB_TRANS_DR5_DR3 1 +#define ADR_CUSTOM_LIST_DR5_DR3 \ + { \ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3 \ + } /* 125kHz - SF7, SF8, SF9 */ + +/*! + * @brief ADR custom list and retransmission parameters for US915 region + */ +#define CUSTOM_NB_TRANS_US915 2 // 1 in code, 2 on README +#define ADR_CUSTOM_LIST_US915 \ + { \ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3 \ + } + // { \ + // 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1 \ + // } /* 125kHz - SF7, SF8, SF9 */ + +/*! + * @brief ADR custom list and retransmission parameters for WW2G4 region + */ +#define CUSTOM_NB_TRANS_WW2G4 1 +#define ADR_CUSTOM_LIST_WW2G4 \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ + } /* SF12 */ + +#define WIFI_SCAN_PERIOD WIFI_SCAN_PERIOD_DEFAULT + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/*! + * @brief Stack identifier + */ +static uint8_t stack_id = 0; + +/*! + * @brief Modem radio + */ +static ralf_t* modem_radio; + +/*! + * @brief Wi-Fi output results + */ +static wifi_mw_event_data_scan_done_t wifi_results; + +/*! + * @brief ADR custom list and retransmission definition for EU868 / IN865 / RU864 / AU915 / CN470 /AS923 / KR920 regions + */ +static const uint8_t adr_custom_list_dr5_dr3[16] = ADR_CUSTOM_LIST_DR5_DR3; +static const uint8_t custom_nb_trans_dr5_dr3 = CUSTOM_NB_TRANS_DR5_DR3; + +/*! + * @brief ADR custom list and retransmission definition for US915 region + */ +static const uint8_t adr_custom_list_us915[16] = ADR_CUSTOM_LIST_US915; +static const uint8_t custom_nb_trans_us915 = CUSTOM_NB_TRANS_US915; + +/*! + * @brief ADR custom list and retransmission definition for WW2G4 region + */ +static const uint8_t adr_custom_list_ww2g4[16] = ADR_CUSTOM_LIST_WW2G4; +static const uint8_t custom_nb_trans_ww2g4 = CUSTOM_NB_TRANS_WW2G4; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Helper function that configure the custom ADR configuration for geolocation scan & send, based on the region + * already configured in the stack. + * + * Prior using this function, the region must have been set already in the stack. + */ +static void configure_adr( void ); + +/*! + * @addtogroup basics_modem_evt_callback + * LoRa Basics Modem event callbacks + * @{ + */ + +/*! + * @brief Reset event callback + * + * @param [in] reset_count reset counter from the modem + */ +static void on_modem_reset( uint16_t reset_count ); + +/*! + * @brief Network Joined event callback + */ +static void on_modem_network_joined( void ); + +/*! + * @brief Alarm event callback + */ +static void on_modem_alarm( void ); + +/*! + * @brief Wi-Fi middleware event callback + */ +static void on_middleware_wifi_event( uint8_t pending_events ); + +static void on_modem_join_fail( void ); + +/*! + * @} + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +lr11xx_hal_context_t radio_context = { + .nss = LR1110_SPI_NSS_PIN, + .busy = LR1110_BUSY_PIN, + .reset = LR1110_NRESER_PIN, + .spi_id = 3, +}; + +/** + * @brief Main application entry point. + */ +int main( void ) +{ + lr11xx_system_version_t lr11xx_fw_version; + lr11xx_status_t status; + static apps_modem_event_callback_t smtc_event_callback = { + .adr_mobile_to_static = NULL, + .alarm = on_modem_alarm, + .almanac_update = NULL, + .down_data = NULL, + .join_fail = on_modem_join_fail, + .joined = on_modem_network_joined, + .link_status = NULL, + .mute = NULL, + .new_link_adr = NULL, + .reset = on_modem_reset, + .set_conf = NULL, + .stream_done = NULL, + .time_updated_alc_sync = NULL, + .tx_done = NULL, + .upload_done = NULL, + .user_radio_access = NULL, + .middleware_1 = NULL, + .middleware_2 = on_middleware_wifi_event, + }; + + lr11xx_system_clear_errors( &radio_context ); + /* Check LR11XX Firmware version */ + // ASSERT_SMTC_MODEM_RC( smtc_modem_suspend_before_user_radio_access( ) ); /* protect from radio access conflicts */ + // status = lr11xx_system_get_version( modem_radio->ral.context, &lr11xx_fw_version ); + + lr11xx_system_clear_errors( &radio_context ); + status = lr11xx_system_get_version( &radio_context, &lr11xx_fw_version ); + // printf("Hardware Version: %u, 0x%04X\n", lr11xx_fw_version.hw, lr11xx_fw_version.hw); + // printf("Type: %u, 0x%04X, should be 0x%04X\n", lr11xx_fw_version.type, lr11xx_fw_version.type, LR1110_FW_TYPE); + // printf("Firmware Version: %u, 0x%04X, should be 0x%04X\n", lr11xx_fw_version.fw, lr11xx_fw_version.fw, LR1110_FW_VERSION); + + /* Initialise the ralf_t object corresponding to the board */ + modem_radio = smtc_board_initialise_and_get_ralf( ); + + /* Disable IRQ to avoid unwanted behaviour during init */ + // hal_mcu_disable_irq( ); + + /* Init board and peripherals */ + hal_mcu_init( ); + smtc_board_init_periph( ); + + /* Init the Lora Basics Modem event callbacks */ + apps_modem_event_init( &smtc_event_callback ); + + /* Init the modem and use smtc_event_process as event callback, please note that the callback will be called + * immediately after the first call to modem_run_engine because of the reset detection */ + smtc_modem_init( modem_radio, &apps_modem_event_process ); + + /* Re-enable IRQ */ + // hal_mcu_enable_irq( ); + + // HAL_DBG_TRACE_MSG( "\n" ); + // HAL_DBG_TRACE_INFO( "###### ===== LoRa Basics Modem Geolocation Wi-Fi example ==== ######\n\n" ); + printf( "###### ===== LoRa Basics Modem Geolocation Wi-Fi example ==== ######\n\n" ); + apps_modem_common_display_lbm_version( ); + + // ASSERT_SMTC_MODEM_RC( smtc_modem_resume_after_user_radio_access( ) ); + + // if( status != LR11XX_STATUS_OK ) + // { + // HAL_DBG_TRACE_ERROR( "Failed to get LR11XX firmware version\n" ); + // mcu_panic( ); + // } + // if( ( ( lr11xx_fw_version.fw != LR1110_FW_VERSION ) && ( lr11xx_fw_version.type = LR1110_FW_TYPE ) ) && + // ( ( lr11xx_fw_version.fw != LR1120_FW_VERSION ) && ( lr11xx_fw_version.type = LR1120_FW_TYPE ) ) ) + // { + // HAL_DBG_TRACE_ERROR( "Wrong LR11XX firmware version, expected 0x%04X, got 0x%04X\n", LR1110_FW_VERSION, + // lr11xx_fw_version.fw ); + // mcu_panic( ); + // } + // HAL_DBG_TRACE_INFO( "LR11XX FW : 0x%04X\n", lr11xx_fw_version.fw ); + + lr11xx_system_clear_errors( &radio_context ); + lr11xx_system_clear_irq_status(&radio_context, 0xFFFFFFFF); + + // printf("inside loop\n"); + + while( 1 ) + { + lr11xx_system_clear_errors( &radio_context ); + + /* Execute modem runtime, this function must be called again in sleep_time_ms milliseconds or sooner. */ + uint32_t sleep_time_ms = smtc_modem_run_engine( ); + + /* go in low power */ + hal_mcu_set_sleep_for_ms( sleep_time_ms ); + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/*! + * @brief LoRa Basics Modem event callbacks called by smtc_event_process function + */ + +static void on_modem_reset( uint16_t reset_count ) +{ + // printf("on_modem_reset\n"); + + /* Basic LoRaWAN configuration */ + apps_modem_common_configure_lorawan_params( stack_id ); + + /* Start the Join process */ + ASSERT_SMTC_MODEM_RC( smtc_modem_join_network( stack_id ) ); + + // lr11xx_system_irq_mask_t lr11xx_irq_mask = LR11XX_SYSTEM_IRQ_NONE; + + // lr11xx_system_get_irq_status( &radio_context, &lr11xx_irq_mask ); + // printf("irq status after smtc join: %d\n", lr11xx_irq_mask); + + HAL_DBG_TRACE_INFO( "###### ===== JOINING ==== ######\n\n" ); +} + +static void on_modem_network_joined( void ) +{ + printf("on_modem_network_joined successful!\n"); + + mw_return_code_t wifi_rc; + mw_version_t mw_version; + + /* Set the custom ADR profile for geolocation scan & send */ + configure_adr( ); + + /* Initialize Wi-Fi middleware */ + wifi_mw_get_version( &mw_version ); + // printf( "Initializing Wi-Fi middleware v%d.%d.%d\n", mw_version.major, mw_version.minor, + // mw_version.patch ); + wifi_mw_init( modem_radio, stack_id ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( 5 ) ); + + // lr11xx_system_irq_mask_t lr11xx_irq_mask = LR11XX_SYSTEM_IRQ_NONE; + + // lr11xx_system_get_irq_status( &radio_context, &lr11xx_irq_mask ); + // printf("irq status before start scan: %d\n", lr11xx_irq_mask); + + /* Start the Wi-Fi scan sequence */ + //smtc_modem_suspend_before_user_radio_access( ); + + wifi_rc = wifi_mw_scan_start( 1 ); + if( wifi_rc != MW_RC_OK ) + { + printf( "Error: Failed to start WiFi scan\n" ); + } +} + +static void on_modem_alarm( void ) +{ + //printf("on_modem_alarm\n"); + //wifi_mw_scan_rp_task_done(); + + //ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( WIFI_SCAN_PERIOD ) ); + // printf( "smtc_modem_alarm_start_timer: %d s\n\n", WIFI_SCAN_PERIOD ); + + // mw_return_code_t wifi_rc; + // wifi_rc = wifi_mw_scan_start( 5 ); + // if( wifi_rc != MW_RC_OK ) + // { + // HAL_DBG_TRACE_ERROR( "Failed to start WiFi scan\n" ); + // } +} + +static void on_modem_join_fail( void ) +{ + printf("join failed!\n"); +} + +/*! + * @brief User private function + */ + +static void on_middleware_wifi_event( uint8_t pending_events ) +{ + // printf("on_middleware_wifi_event\n"); + + mw_return_code_t wifi_rc; + + /* Parse events */ + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_SCAN_DONE ) ) + { + // printf( "Wi-Fi middleware event - SCAN DONE\n" ); + wifi_mw_get_event_data_scan_done( &wifi_results ); + wifi_mw_display_results( &wifi_results ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_TERMINATED ) ) + { + wifi_mw_event_data_terminated_t event_data; + + // printf( "Wi-Fi middleware event - TERMINATED\n" ); + wifi_mw_get_event_data_terminated( &event_data ); + // HAL_DBG_TRACE_PRINTF( "TERMINATED info:\n" ); + printf( "-- number of scans sent: %u\n", event_data.nb_scans_sent ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_SCAN_CANCELLED ) ) + { + printf( "Wi-Fi middleware event - SCAN CANCELLED\n" ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_ERROR_UNKNOWN ) ) + { + printf( "Wi-Fi middleware event - UNEXPECTED ERROR\n" ); + } + + /* Program next scan */ + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_ERROR_UNKNOWN ) || + wifi_mw_has_event( pending_events, WIFI_MW_EVENT_TERMINATED ) ) + { + /* Program the next Wi-Fi group */ + // wifi_rc = wifi_mw_scan_start( WIFI_SCAN_PERIOD ); + //printf("receive unknown error\n"); + //smtc_modem_alarm_start_timer( 1 ); + // wifi_rc = wifi_mw_scan_start( 0 ); + // if( wifi_rc != MW_RC_OK ) + // { + // printf( "Error! Failed to start WiFi scan\n" ); + // } + } + + wifi_mw_clear_pending_events( ); +} + +void configure_adr( void ) +{ + // printf("configure_adr\n"); + + smtc_modem_region_t region; + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_region( stack_id, ®ion ) ); + + // printf("Region number: %d\n", region); + + /* Set the ADR profile once joined */ + switch( region ) + { + case SMTC_MODEM_REGION_EU_868: + case SMTC_MODEM_REGION_IN_865: + case SMTC_MODEM_REGION_RU_864: + case SMTC_MODEM_REGION_AU_915: + case SMTC_MODEM_REGION_AS_923_GRP1: + case SMTC_MODEM_REGION_AS_923_GRP2: + case SMTC_MODEM_REGION_AS_923_GRP3: + case SMTC_MODEM_REGION_CN_470: + case SMTC_MODEM_REGION_CN_470_RP_1_0: + case SMTC_MODEM_REGION_KR_920: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_dr5_dr3 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_dr5_dr3 ) ); + break; + case SMTC_MODEM_REGION_US_915: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_us915 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_us915 ) ); + break; + case SMTC_MODEM_REGION_WW2G4: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_ww2g4 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_ww2g4 ) ); + break; + default: + HAL_DBG_TRACE_ERROR( "Region not supported in this example, could not set custom ADR profile\n" ); + break; + } + + /* Disable auto switch to network controlled after a certain amount of TX without RX */ + ASSERT_SMTC_MODEM_RC( smtc_modem_connection_timeout_set_thresholds( stack_id, 0, 0 ) ); +} + +void apps_modem_common_configure_lorawan_params( uint8_t stack_id ) +{ + // printf("apps_modem_common_configure_lorawan_params\n"); + + smtc_modem_return_code_t rc = SMTC_MODEM_RC_OK; + uint8_t dev_eui[8] = { 0 }; + uint8_t join_eui[8] = { 0 }; + uint8_t app_key[16] = { 0 }; + + hal_hex_to_bin( LORAWAN_DEVICE_EUI, dev_eui, 8 ); + hal_hex_to_bin( LORAWAN_JOIN_EUI, join_eui, 8 ); + hal_hex_to_bin( LORAWAN_APP_KEY, app_key, 16 ); + + rc = smtc_modem_set_deveui( stack_id, dev_eui ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_deveui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_joineui( stack_id, join_eui ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_joineui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_nwkkey( stack_id, app_key ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_nwkkey failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + HAL_DBG_TRACE_INFO( "LoRaWAN parameters:\n" ); + + rc = smtc_modem_get_deveui( stack_id, dev_eui ); + if( rc == SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ARRAY( "DevEUI", dev_eui, SMTC_MODEM_EUI_LENGTH ); + } + else + { + printf( "smtc_modem_get_deveui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_get_joineui( stack_id, join_eui ); + if( rc == SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ARRAY( "JoinEUI", join_eui, SMTC_MODEM_EUI_LENGTH ); + } + else + { + printf( "smtc_modem_get_joineui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_class( stack_id, LORAWAN_CLASS ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_class failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + modem_class_to_string( LORAWAN_CLASS ); + + rc = smtc_modem_set_region( stack_id, LORAWAN_REGION ); + if( rc != SMTC_MODEM_RC_OK ) + { + printf( "smtc_modem_set_region failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + modem_region_to_string( LORAWAN_REGION ); + + /* adapt the tx power offet depending on the board */ + rc |= smtc_modem_set_tx_power_offset_db( stack_id, smtc_board_get_tx_power_offset( ) ); +} + +/*! + * @} + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/examples/wm1110/Makefile b/examples/wm1110/Makefile new file mode 100644 index 000000000..453fb95a0 --- /dev/null +++ b/examples/wm1110/Makefile @@ -0,0 +1,19 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../.. + +# External libraries used +EXTERN_LIBS += $(TOCK_USERLAND_BASE_DIR)/wm1110 + +override CFLAGS += -I$(TOCK_USERLAND_BASE_DIR)/wm1110 + +# Which files to compile. +C_SRCS += $(wildcard *.c) + +APP_HEAP_SIZE := 40000 +STACK_SIZE := 4096 + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk \ No newline at end of file diff --git a/examples/wm1110/main.c b/examples/wm1110/main.c new file mode 100644 index 000000000..ccad4197b --- /dev/null +++ b/examples/wm1110/main.c @@ -0,0 +1,94 @@ +// #include "nrf_pwr_mgmt.h" +// #include "nrf_gpio.h" + +#include +#include +#include + +#define LR1110_FW_VERSION 0x0307 +#define LR1110_FW_TYPE 0x01 +#define LR1120_FW_VERSION 0x0101 +#define LR1120_FW_TYPE 0x02 + + +lr11xx_hal_context_t radio_context = { + .nss = LR1110_SPI_NSS_PIN, + .busy = LR1110_BUSY_PIN, + .reset = LR1110_NRESER_PIN, + .spi_id = 3, +}; + +void lr1110_sleep_enter( uint32_t tick ) +{ + lr11xx_system_version_t version; + lr11xx_status_t status; + + hal_spi_init( ); + + printf("\nafter spi init\n"); + + lr11xx_hal_reset(&radio_context); + + lr11xx_system_clear_errors( &radio_context ); + // lr11xx_system_set_tcxo_mode( &radio_context , LR11XX_SYSTEM_TCXO_CTRL_3_3V, 50 ); + // lr11xx_system_cfg_lfclk( &radio_context , LR11XX_SYSTEM_LFCLK_XTAL, 1 ); + + // lr11xx_system_sleep_cfg_t radio_sleep_cfg; + // radio_sleep_cfg.is_warm_start = 1; + // radio_sleep_cfg.is_rtc_timeout = 1; + + // lr11xx_system_drive_dio_in_sleep_mode( &radio_context , true ); + // lr11xx_system_set_sleep( &radio_context , radio_sleep_cfg, tick ); + + status = lr11xx_system_get_version( &radio_context, &version ); + printf("Hardware Version: %u, 0x%04X\n", version.hw, version.hw); // 4 + printf("Type: %u, 0x%04X\n", version.type, version.type); // 4 + printf("Firmware Version: %u, 0x%04X\n", version.fw, version.fw); // 1028, 0x0404 + if( status != LR11XX_STATUS_OK ) + { + printf( "Failed to get LR11XX firmware version\n" ); + } + if( ( ( version.fw != LR1110_FW_VERSION ) && ( version.type = LR1110_FW_TYPE ) ) && + ( ( version.fw != LR1120_FW_VERSION ) && ( version.type = LR1120_FW_TYPE ) ) ) + { + printf( "Wrong LR11XX firmware version, expected 0x%04X, got 0x%04X\n", LR1110_FW_VERSION, + version.fw ); + } + + lr11xx_system_uid_t unique_identifier; + status= lr11xx_system_read_uid( &radio_context, &unique_identifier ); + + printf("uid %x %x %x\n", unique_identifier[0], unique_identifier[1], unique_identifier[2]); + + + + + // printf("\nbefore spi deinit\n"); + // hal_spi_deinit( ); +} + +int main(void) +{ + //hal_pwr_init( ); + printf("\nbefore initalize lora\n"); + + //hal_gpio_init_out( LR1110_SPI_NSS_PIN, HAL_GPIO_SET ); + hal_gpio_init_in( LR1110_BUSY_PIN, HAL_GPIO_PULL_MODE_NONE, HAL_GPIO_IRQ_MODE_OFF, NULL ); + hal_gpio_init_in( LR1110_IRQ_PIN, HAL_GPIO_PULL_MODE_DOWN, HAL_GPIO_IRQ_MODE_RISING, NULL ); + hal_gpio_init_out( LR1110_NRESER_PIN, HAL_GPIO_SET ); + + printf("\nbefore sleep enter\n"); + + lr1110_sleep_enter( 0 ); + + //hal_debug_init( ); + printf( "\r\nWX1110 low power\r\n" ); + //hal_debug_deinit( ); + + delay_ms( 5000 ); + + // while( 1 ) + // { + // nrf_pwr_mgmt_run( ); + // } +} diff --git a/libtock/nonvolatile_storage.c b/libtock/nonvolatile_storage.c new file mode 100644 index 000000000..b292d3026 --- /dev/null +++ b/libtock/nonvolatile_storage.c @@ -0,0 +1,101 @@ +#include +#include + +#include +#include + + + + + + + +static void read_done(int length, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + void* opaque) { + nonvolatile_storage_callback cb = (nonvolatile_storage_callback) opaque; + cb(RETURNCODE_SUCCESS, length); +} + +static void write_done(int length, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + void* opaque) { + nonvolatile_storage_callback cb = (nonvolatile_storage_callback) opaque; + cb(RETURNCODE_SUCCESS, length); +} + + +returncode_t nonvolatile_storage_read(uint8_t* buffer, uint32_t length, uint32_t address, nonvolatile_storage_callback cb) { + returncode_t ret; + + ret = nonvolatile_storage_internal_read_buffer(buffer, length); + if (ret != RETURNCODE_SUCCESS) return ret; + + ret = nonvolatile_storage_internal_read_done_subscribe(read_done, cb); + if (ret != RETURNCODE_SUCCESS) return ret; + + ret = nonvolatile_storage_internal_read(address, length); + return ret; +} + +returncode_t nonvolatile_storage_write(uint8_t* buffer, uint32_t length, uint32_t address, nonvolatile_storage_callback cb) { + returncode_t ret; + + ret = nonvolatile_storage_internal_write_buffer(buffer, length); + if (ret != RETURNCODE_SUCCESS) return ret; + + ret = nonvolatile_storage_internal_write_done_subscribe(write_done, cb); + if (ret != RETURNCODE_SUCCESS) return ret; + + ret = nonvolatile_storage_internal_write(address, length); + return ret; +} + + + +struct data { + bool fired; + int length; + returncode_t result; +}; + +static struct data result = { .fired = false }; + + +static void nv_storage_done(returncode_t ret, int length) { + result.fired = true; + result.length = length; + result.result = ret; +} + + + +returncode_t nonvolatile_storage_read_sync(uint8_t* buffer, uint32_t length, uint32_t address) { + returncode_t ret; + result.fired = false; + + + ret = nonvolatile_storage_read(buffer, length, address, nv_storage_done); + if (ret != RETURNCODE_SUCCESS) return ret; + + + yield_for(&result.fired); + return RETURNCODE_SUCCESS; +} + +returncode_t nonvolatile_storage_write_sync(uint8_t* buffer, uint32_t length, uint32_t address) { + returncode_t ret; + result.fired = false; + + + ret = nonvolatile_storage_write(buffer, length, address, nv_storage_done); + if (ret != RETURNCODE_SUCCESS) return ret; + + + yield_for(&result.fired); + return RETURNCODE_SUCCESS; +} + + diff --git a/libtock/nonvolatile_storage.h b/libtock/nonvolatile_storage.h new file mode 100644 index 000000000..085a536de --- /dev/null +++ b/libtock/nonvolatile_storage.h @@ -0,0 +1,26 @@ +#pragma once + +#include "tock.h" + + + +#ifdef __cplusplus +extern "C" { +#endif + + +// Function signature for button press callbacks. +// +// - `arg1` (`returncode_t`): Returncode indicating status of the operation. +// - `arg2` (`int`): Length. +typedef void (*nonvolatile_storage_callback)(returncode_t, int); + +returncode_t nonvolatile_storage_read(uint8_t* buffer, uint32_t length, uint32_t address, nonvolatile_storage_callback cb); +returncode_t nonvolatile_storage_write(uint8_t* buffer, uint32_t length, uint32_t address, nonvolatile_storage_callback cb); +returncode_t nonvolatile_storage_read_sync(uint8_t* buffer, uint32_t length, uint32_t address); +returncode_t nonvolatile_storage_write_sync(uint8_t* buffer, uint32_t length, uint32_t address); + + +#ifdef __cplusplus +} +#endif diff --git a/lr1110/lr1110/seeed/README.md b/lr1110/lr1110/seeed/README.md new file mode 100644 index 000000000..fde0cf62a --- /dev/null +++ b/lr1110/lr1110/seeed/README.md @@ -0,0 +1,2 @@ +# Seeed_Wio_WM1110_Dev_Board +Applications on WM1110 Development Board diff --git a/lr1110/lr1110/seeed/apps/common/README.md b/lr1110/lr1110/seeed/apps/common/README.md new file mode 100644 index 000000000..bb8fd5cd8 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/README.md @@ -0,0 +1,35 @@ +# LoRa Basics Modem common content + +## Description + +In order to bring some clarity, several generic operations (i.e. operations that are used no matter the LoRa Basics Modem functionality being demonstrated in an example) are implemented in this common folder. + +## LoRaWAN configuration + +In this SDK, the LoRaWAN configuration is handled by the function `apps_modem_common_configure_lorawan_params()`. + +The join parameters (DevEUI, JoinEUI and AppKey) can be taken from different places: + +* if `USER_DEFINED_JOIN_PARAMETERS` is defined: the join parameters are taken from [lorawan_key_config.h](./lorawan_key_config.h) +* if `LR11XX_DEFINED_JOIN_PARAMETERS` is defined: the join parameters are taken from the LR11XX and the application key is derived according to the algorithm described in LoRa Cloud. + +In the user-defined case, the configuration has to be updated in [lorawan_key_config.h](./lorawan_key_config.h): + +| Constant | Description | Possible values | Default value | Note | +| -------------------- | ----------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| `LORAWAN_DEVICE_EUI` | LoRaWAN device EUI | Any 8 bytes c-array | `{0xFE, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0x00, 0x00}` | Used if `USER_DEFINED_JOIN_PARAMETERS` is defined | +| `LORAWAN_JOIN_EUI` | LoRaWAN join EUI | Any 8 bytes c-array | `{0x00, 0x16, 0xC0, 0x01, 0xFF, 0xFE, 0x00, 0x01}` | Used if `USER_DEFINED_JOIN_PARAMETERS` is defined | +| `LORAWAN_APP_KEY` | LoRaWAN application key | Any 16 bytes c-array | `{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}` | Used if `USER_DEFINED_JOIN_PARAMETERS` is defined | +| `LORAWAN_REGION` | LoRaWAN region | Values in `smtc_modem_region_t` enumeration | `SMTC_MODEM_REGION_EU_868` | See supported LoRaWAN regions below | +| `LORAWAN_CLASS` | LoRaWAN class | Values in `smtc_modem_class_t` enumeration | `SMTC_MODEM_CLASS_A` | All LoRaWAN classes (A, B & C) are supported | + +When compiling with *arm-none-eabi-gcc* toolchain, all these constant are configurable through command line with the `EXTRAFLAGS`. +See [README.md](../../../README.md#command-line-configuration). + +The LoRaWAN regions supported are EU868, US915, CN470, AS923 (Australia, Japan, Indonesia), KR920, IN865, RU864, AU915. + +## LoRa Basics Modem event management + +When LoRa Basics Modem is initialized, a callback is given as parameter to `smtc_modem_init()` so the application can be informed of events. In a final application, it is up to the user to implement this function. + +In this SDK, this function is pre-defined to bring consistency - it is `apps_modem_event_process()` implemented in [apps_modem_event.c](./apps_modem_event.c). Each example needs to implement the functions defined in `apps_modem_event_callback_t` that are useful to it - others have to be set to `NULL`. diff --git a/lr1110/lr1110/seeed/apps/common/apps_modem_common.c b/lr1110/lr1110/seeed/apps/common/apps_modem_common.c new file mode 100644 index 000000000..3681fb52b --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/apps_modem_common.c @@ -0,0 +1,276 @@ +/*! + * @file apps_modem_common.c + * + * @brief Common functions shared by the examples + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include +#include + +#include "apps_modem_common.h" +#include "apps_utilities.h" +#include "lorawan_key_config.h" +#include "smtc_modem_api.h" +#include "smtc_basic_modem_lr11xx_api_extension.h" +#include "smtc_board.h" +#include "smtc_hal.h" +#include "smtc_modem_api_str.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/*! + * @brief Offset in second between GPS EPOCH and UNIX EPOCH time + */ +#define OFFSET_BETWEEN_GPS_EPOCH_AND_UNIX_EPOCH 315964800 + +/*! + * @brief Number of leap seconds as of September 15th 2021 + */ +#define OFFSET_LEAP_SECONDS 18 + +/*! + * @brief Size of the buffer used as placeholder for the human-readable time + */ +#define TIME_BUFFER_SIZE 80 + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC VARIABLES -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +uint32_t apps_modem_common_get_gps_time( void ) +{ + uint32_t gps_time_s = 0; + uint32_t gps_fractional_s = 0; + + const smtc_modem_return_code_t status = smtc_modem_get_time( &gps_time_s, &gps_fractional_s ); + + switch( status ) + { + case SMTC_MODEM_RC_OK: + { + HAL_DBG_TRACE_INFO( "Current UTC time: %d s\n", gps_time_s ); + + break; + } + case SMTC_MODEM_RC_NO_TIME: + { + HAL_DBG_TRACE_WARNING( "No time available.\n" ); + break; + } + default: + { + HAL_DBG_TRACE_ERROR( "Cannot get time from modem\n" ); + break; + } + } + + return gps_time_s; +} + +uint32_t apps_modem_common_get_utc_time( void ) +{ + uint32_t gps_time_s = 0; + uint32_t gps_fractional_s = 0; + time_t time_utc = 0; + + const smtc_modem_return_code_t status = smtc_modem_get_time( &gps_time_s, &gps_fractional_s ); + + switch( status ) + { + case SMTC_MODEM_RC_OK: + { + time_utc = ( time_t )( gps_time_s + OFFSET_BETWEEN_GPS_EPOCH_AND_UNIX_EPOCH - OFFSET_LEAP_SECONDS ); + + char buf[TIME_BUFFER_SIZE]; + const struct tm* time = localtime( &time_utc ); // localtime() is used here instead of gmtime() because the + // latter is not implemented in the libraries used by Keil + + strftime( buf, TIME_BUFFER_SIZE, "%a %Y-%m-%d %H:%M:%S %Z", time ); + HAL_DBG_TRACE_INFO( "Current UTC time: %s\n", buf ); + + break; + } + case SMTC_MODEM_RC_NO_TIME: + { + HAL_DBG_TRACE_WARNING( "No time available.\n" ); + time_utc = 0; + break; + } + default: + { + HAL_DBG_TRACE_ERROR( "Cannot get time from modem\n" ); + time_utc = 0; + break; + } + } + + return ( ( uint32_t ) time_utc ); +} + +uint32_t apps_modem_common_convert_gps_to_utc_time( uint32_t gps_time_s ) +{ + return gps_time_s + OFFSET_BETWEEN_GPS_EPOCH_AND_UNIX_EPOCH - OFFSET_LEAP_SECONDS; +} + +void apps_modem_common_configure_lorawan_params( uint8_t stack_id ) +{ + smtc_modem_return_code_t rc = SMTC_MODEM_RC_OK; + uint8_t dev_eui[8] = { 0 }; + uint8_t join_eui[8] = { 0 }; + uint8_t app_key[16] = { 0 }; + + hal_hex_to_bin( LORAWAN_DEVICE_EUI, dev_eui, 8 ); + hal_hex_to_bin( LORAWAN_JOIN_EUI, join_eui, 8 ); + hal_hex_to_bin( LORAWAN_APP_KEY, app_key, 16 ); + + rc = smtc_modem_set_deveui( stack_id, dev_eui ); + if( rc != SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "smtc_modem_set_deveui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_joineui( stack_id, join_eui ); + if( rc != SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "smtc_modem_set_joineui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_nwkkey( stack_id, app_key ); + if( rc != SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "smtc_modem_set_nwkkey failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + HAL_DBG_TRACE_INFO( "LoRaWAN parameters:\n" ); + + rc = smtc_modem_get_deveui( stack_id, dev_eui ); + if( rc == SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ARRAY( "DevEUI", dev_eui, SMTC_MODEM_EUI_LENGTH ); + } + else + { + HAL_DBG_TRACE_ERROR( "smtc_modem_get_deveui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_get_joineui( stack_id, join_eui ); + if( rc == SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ARRAY( "JoinEUI", join_eui, SMTC_MODEM_EUI_LENGTH ); + } + else + { + HAL_DBG_TRACE_ERROR( "smtc_modem_get_joineui failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + rc = smtc_modem_set_class( stack_id, LORAWAN_CLASS ); + if( rc != SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "smtc_modem_set_class failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + modem_class_to_string( LORAWAN_CLASS ); + + rc = smtc_modem_set_region( stack_id, LORAWAN_REGION ); + if( rc != SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "smtc_modem_set_region failed: rc=%s (%d)\n", smtc_modem_return_code_to_str( rc ), rc ); + } + + modem_region_to_string( LORAWAN_REGION ); + + /* adapt the tx power offet depending on the board */ + rc |= smtc_modem_set_tx_power_offset_db( stack_id, smtc_board_get_tx_power_offset( ) ); +} + +void apps_modem_common_display_lbm_version( void ) +{ + smtc_modem_return_code_t modem_response_code = SMTC_MODEM_RC_OK; + smtc_modem_lorawan_version_t lorawan_version; + smtc_modem_version_t firmware_version; + + modem_response_code = smtc_modem_get_lorawan_version( &lorawan_version ); + if( modem_response_code == SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_INFO( "LoRaWAN version: %.2x.%.2x.%.2x.%.2x\n", lorawan_version.major, lorawan_version.minor, + lorawan_version.patch, lorawan_version.revision ); + } + + modem_response_code = smtc_modem_get_modem_version( &firmware_version ); + if( modem_response_code == SMTC_MODEM_RC_OK ) + { + HAL_DBG_TRACE_INFO( "LoRa Basics Modem version: %.2x.%.2x.%.2x\n", firmware_version.major, + firmware_version.minor, firmware_version.patch ); + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/common/apps_modem_common.h b/lr1110/lr1110/seeed/apps/common/apps_modem_common.h new file mode 100644 index 000000000..4587b32ee --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/apps_modem_common.h @@ -0,0 +1,111 @@ +/*! + * @file apps_modem_common.h + * + * @brief Common functions shared by the examples + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 APPS_MODEM_COMMON_H +#define APPS_MODEM_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include + +#include "smtc_modem_api.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/*! + * @brief Get the GPS time from the modem + * + * @returns GPS time in second + */ +uint32_t apps_modem_common_get_gps_time( void ); + +/*! + * @brief Get the UTC time from the modem + * + * @returns UTC time in second, if no time available returns 0 + */ +uint32_t apps_modem_common_get_utc_time( void ); + +/*! + * @brief convert GPS time to UTC time + */ +uint32_t apps_modem_common_convert_gps_to_utc_time( uint32_t gps ); + +/*! + * @brief Configure LoRaWAN parameters (DevEUI, JoinEUI, AppKey, region and class) + * + * @remark All parameters are defined in lorawan_key_config.h file + * + * @param [in] stack_id Stack identifier + */ +void apps_modem_common_configure_lorawan_params( uint8_t stack_id ); + +/*! + * @brief Display the Lora Basics Modem current version + */ +void apps_modem_common_display_lbm_version( void ); + +#ifdef __cplusplus +} +#endif + +#endif // APPS_MODEM_COMMON_H + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/common/apps_modem_event.c b/lr1110/lr1110/seeed/apps/common/apps_modem_event.c new file mode 100644 index 000000000..c6612e29b --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/apps_modem_event.c @@ -0,0 +1,324 @@ +/*! + * @file apps_modem_event.c + * + * @brief LoRa Basics Modem event manager implementation + * + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "apps_modem_event.h" +#include "smtc_hal_dbg_trace.h" +#include "smtc_modem_api_str.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/*! + * @brief Lora Basics Modem event callback functions + */ +apps_modem_event_callback_t* apps_modem_event_callback; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +void apps_modem_event_init( apps_modem_event_callback_t* event_callback ) +{ + apps_modem_event_callback = event_callback; +} + +void apps_modem_event_process( void ) +{ + smtc_modem_event_t current_event; + smtc_modem_return_code_t return_code = SMTC_MODEM_RC_OK; + uint8_t event_pending_count; + + do + { + /* Read modem event */ + return_code = smtc_modem_get_event( ¤t_event, &event_pending_count ); + + if( return_code == SMTC_MODEM_RC_OK ) + { + if( apps_modem_event_callback != NULL ) + { + switch( current_event.event_type ) + { + case SMTC_MODEM_EVENT_RESET: + HAL_DBG_TRACE_INFO( "###### ===== BASICS MODEM RESET EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Reset count : %lu \n", current_event.event_data.reset.count ); + if( apps_modem_event_callback->reset != NULL ) + { + apps_modem_event_callback->reset( current_event.event_data.reset.count ); + } + break; + case SMTC_MODEM_EVENT_ALARM: + HAL_DBG_TRACE_INFO( "###### ===== ALARM EVENT ==== ######\n" ); + if( apps_modem_event_callback->alarm != NULL ) + { + apps_modem_event_callback->alarm( ); + } + break; + case SMTC_MODEM_EVENT_JOINED: + HAL_DBG_TRACE_INFO( "###### ===== JOINED EVENT ==== ######\n" ); + if( apps_modem_event_callback->joined != NULL ) + { + apps_modem_event_callback->joined( ); + } + break; + case SMTC_MODEM_EVENT_JOINFAIL: + HAL_DBG_TRACE_INFO( "###### ===== JOINED FAIL EVENT ==== ######\n" ); + if( apps_modem_event_callback->join_fail != NULL ) + { + apps_modem_event_callback->join_fail( ); + } + break; + case SMTC_MODEM_EVENT_TXDONE: + HAL_DBG_TRACE_INFO( "###### ===== TX DONE EVENT ==== ######\n" ); + switch( current_event.event_data.txdone.status ) + { + case SMTC_MODEM_EVENT_TXDONE_NOT_SENT: + HAL_DBG_TRACE_ERROR( "TX Done status: %s\n", smtc_modem_event_txdone_status_to_str( + current_event.event_data.txdone.status ) ); + break; + case SMTC_MODEM_EVENT_TXDONE_SENT: + case SMTC_MODEM_EVENT_TXDONE_CONFIRMED: + default: + HAL_DBG_TRACE_PRINTF( "TX Done status: %s\n", smtc_modem_event_txdone_status_to_str( + current_event.event_data.txdone.status ) ); + break; + } + if( apps_modem_event_callback->tx_done != NULL ) + { + apps_modem_event_callback->tx_done( current_event.event_data.txdone.status ); + } + break; + case SMTC_MODEM_EVENT_DOWNDATA: + HAL_DBG_TRACE_INFO( "###### ===== DOWNLINK EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Rx window: %s\n", smtc_modem_event_downdata_window_to_str( + current_event.event_data.downdata.window ) ); + HAL_DBG_TRACE_PRINTF( "Rx port: %d\n", current_event.event_data.downdata.fport ); + HAL_DBG_TRACE_PRINTF( "Rx RSSI: %d\n", current_event.event_data.downdata.rssi - 64 ); + HAL_DBG_TRACE_PRINTF( "Rx SNR: %d\n", current_event.event_data.downdata.snr / 4 ); + + if( apps_modem_event_callback->down_data != NULL ) + { + apps_modem_event_callback->down_data( + current_event.event_data.downdata.rssi, current_event.event_data.downdata.snr, + current_event.event_data.downdata.window, current_event.event_data.downdata.fport, + current_event.event_data.downdata.data, current_event.event_data.downdata.length ); + } + break; + case SMTC_MODEM_EVENT_UPLOADDONE: + HAL_DBG_TRACE_INFO( "###### ===== UPLOAD DONE EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Upload status: %s\n", smtc_modem_event_uploaddone_status_to_str( + current_event.event_data.uploaddone.status ) ); + if( apps_modem_event_callback->upload_done != NULL ) + { + apps_modem_event_callback->upload_done( current_event.event_data.uploaddone.status ); + } + break; + case SMTC_MODEM_EVENT_SETCONF: + HAL_DBG_TRACE_INFO( "###### ===== SET CONF EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Tag: %s", + smtc_modem_event_setconf_tag_to_str( current_event.event_data.setconf.tag ) ); + if( apps_modem_event_callback->set_conf != NULL ) + { + apps_modem_event_callback->set_conf( current_event.event_data.setconf.tag ); + } + break; + case SMTC_MODEM_EVENT_MUTE: + HAL_DBG_TRACE_INFO( "###### ===== MUTE EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Mute: %s\n", + smtc_modem_event_mute_status_to_str( current_event.event_data.mute.status ) ); + if( apps_modem_event_callback->mute != NULL ) + { + apps_modem_event_callback->mute( current_event.event_data.mute.status ); + } + break; + case SMTC_MODEM_EVENT_STREAMDONE: + HAL_DBG_TRACE_INFO( "###### ===== STREAM DONE EVENT ==== ######\n" ); + if( apps_modem_event_callback->stream_done != NULL ) + { + apps_modem_event_callback->stream_done( ); + } + break; + case SMTC_MODEM_EVENT_TIME: + HAL_DBG_TRACE_INFO( "###### ===== TIME EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Time: %s\n", + smtc_modem_event_time_status_to_str( current_event.event_data.time.status ) ); + if( apps_modem_event_callback->time_updated_alc_sync != NULL ) + { + apps_modem_event_callback->time_updated_alc_sync( current_event.event_data.time.status ); + } + break; + case SMTC_MODEM_EVENT_TIMEOUT_ADR_CHANGED: + HAL_DBG_TRACE_INFO( "###### ===== ADR CHANGED EVENT ==== ######\n" ); + if( apps_modem_event_callback->adr_mobile_to_static != NULL ) + { + apps_modem_event_callback->adr_mobile_to_static( ); + } + break; + case SMTC_MODEM_EVENT_NEW_LINK_ADR: + HAL_DBG_TRACE_INFO( "###### ===== NEW LINK ADR EVENT ==== ######\n" ); + if( apps_modem_event_callback->new_link_adr != NULL ) + { + apps_modem_event_callback->new_link_adr( ); + } + break; + case SMTC_MODEM_EVENT_LINK_CHECK: + HAL_DBG_TRACE_INFO( "###### ===== LINK CHECK EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Link status: %s\n", smtc_modem_event_link_check_status_to_str( + current_event.event_data.link_check.status ) ); + HAL_DBG_TRACE_PRINTF( "Margin: %d dB\n", current_event.event_data.link_check.margin ); + HAL_DBG_TRACE_PRINTF( "Number of gateways: %d\n", current_event.event_data.link_check.gw_cnt ); + if( apps_modem_event_callback->link_status != NULL ) + { + apps_modem_event_callback->link_status( current_event.event_data.link_check.status, + current_event.event_data.link_check.margin, + current_event.event_data.link_check.gw_cnt ); + } + break; + case SMTC_MODEM_EVENT_ALMANAC_UPDATE: + HAL_DBG_TRACE_INFO( "###### ===== ALMANAC UPDATE EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Almanac update status: %s\n", + smtc_modem_event_almanac_update_status_to_str( + current_event.event_data.almanac_update.status ) ); + if( apps_modem_event_callback->almanac_update != NULL ) + { + apps_modem_event_callback->almanac_update( current_event.event_data.almanac_update.status ); + } + break; + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS: + HAL_DBG_TRACE_INFO( "###### ===== USER RADIO ACCESS EVENT ==== ######\n" ); + if( apps_modem_event_callback->user_radio_access != NULL ) + { + apps_modem_event_callback->user_radio_access( + current_event.event_data.user_radio_access.timestamp_ms, + current_event.event_data.user_radio_access.status ); + } + break; + case SMTC_MODEM_EVENT_CLASS_B_PING_SLOT_INFO: + HAL_DBG_TRACE_INFO( "###### ===== CLASS B PING SLOT INFO EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( "Class B ping slot status: %s\n", + smtc_modem_event_class_b_ping_slot_status_to_str( + current_event.event_data.class_b_ping_slot_info.status ) ); + if( apps_modem_event_callback->class_b_ping_slot_info != NULL ) + { + apps_modem_event_callback->class_b_ping_slot_info( + current_event.event_data.class_b_ping_slot_info.status ); + } + break; + case SMTC_MODEM_EVENT_CLASS_B_STATUS: + HAL_DBG_TRACE_INFO( "###### ===== CLASS B STATUS EVENT ==== ######\n" ); + HAL_DBG_TRACE_PRINTF( + "Class B status: %s\n", + smtc_modem_event_class_b_status_to_str( current_event.event_data.class_b_status.status ) ); + if( apps_modem_event_callback->class_b_status != NULL ) + { + apps_modem_event_callback->class_b_status( current_event.event_data.class_b_status.status ); + } + break; + case SMTC_MODEM_EVENT_MIDDLEWARE_1: + HAL_DBG_TRACE_INFO( "###### ===== MIDDLEWARE_1 EVENT ==== ######\n" ); + if( apps_modem_event_callback->middleware_1 != NULL ) + { + apps_modem_event_callback->middleware_1( + current_event.event_data.middleware_event_status.status ); + } + break; + case SMTC_MODEM_EVENT_MIDDLEWARE_2: + HAL_DBG_TRACE_INFO( "###### ===== MIDDLEWARE_2 EVENT ==== ######\n" ); + if( apps_modem_event_callback->middleware_2 != NULL ) + { + apps_modem_event_callback->middleware_2( + current_event.event_data.middleware_event_status.status ); + } + break; + case SMTC_MODEM_EVENT_MIDDLEWARE_3: + HAL_DBG_TRACE_INFO( "###### ===== MIDDLEWARE_3 EVENT ==== ######\n" ); + if( apps_modem_event_callback->middleware_3 != NULL ) + { + apps_modem_event_callback->middleware_3( + current_event.event_data.middleware_event_status.status ); + } + break; + case SMTC_MODEM_EVENT_NONE: + break; + default: + HAL_DBG_TRACE_INFO( "###### ===== UNKNOWN EVENT %u ==== ######\n", current_event.event_type ); + break; + } + } + else + { + HAL_DBG_TRACE_ERROR( "lora_basics_modem_event_callback not defined\n" ); + } + } + else + { + HAL_DBG_TRACE_ERROR( "smtc_modem_get_event != SMTC_MODEM_RC_OK\n" ); + } + } while( ( return_code == SMTC_MODEM_RC_OK ) && ( current_event.event_type != SMTC_MODEM_EVENT_NONE ) ); +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/common/apps_modem_event.h b/lr1110/lr1110/seeed/apps/common/apps_modem_event.h new file mode 100644 index 000000000..605cb4757 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/apps_modem_event.h @@ -0,0 +1,222 @@ +/*! + * @file apps_modem_event.h + * + * @brief LoRa Basics Modem event manager definition + * + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 APPS_MODEM_EVENT_H +#define APPS_MODEM_EVENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include "smtc_modem_api.h" +#include "smtc_modem_middleware_advanced_api.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/*! + * @brief Lora Basics Modem callback functions + */ +typedef struct +{ + /*! + * @brief Reset callback prototype. + * + * @param [in] reset_count + */ + void ( *reset )( uint16_t reset_count ); + /*! + * @brief Alarm timer expired callback prototype. + */ + void ( *alarm )( void ); + /*! + * @brief Attemp to join network failed callback prototype. + */ + void ( *joined )( void ); + /*! + * @brief Joined callback prototype. + */ + void ( *join_fail )( void ); + /*! + * @brief Tx done callback prototype. + * + * @param [in] status + */ + void ( *tx_done )( smtc_modem_event_txdone_status_t status ); + /*! + * @brief Downlink data received callback prototype. + * + * @param [in] rssi rssi in signed value in dBm + 64 + * @param [in] snr snr signed value in 0.25 dB steps + * @param [in] flags rx flags \see smtc_modem_event_downdata_window_t + * @param [in] port LoRaWAN port + * @param [in] payload Received buffer pointer + * @param [in] size Received buffer size + */ + void ( *down_data )( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ); + /*! + * @brief File upload completed callback prototype. + * + * @param [in] status \see smtc_modem_event_uploaddone_status_t + */ + void ( *upload_done )( smtc_modem_event_uploaddone_status_t status ); + /*! + * @brief Set conf changed by DM callback prototype. + * + * @param [in] tag \see smtc_modem_event_setconf_tag_t + */ + void ( *set_conf )( smtc_modem_event_setconf_tag_t tag ); + /*! + * @brief Mute callback prototype. + * + * @param [in] status \see smtc_modem_event_mute_status_t + */ + void ( *mute )( smtc_modem_event_mute_status_t status ); + /*! + * @brief Data stream fragments sent callback prototype. + */ + void ( *stream_done )( void ); + /*! + * @brief Gnss Done Done callback prototype. + * + * @param [in] nav_message + * @param [in] size + */ + void ( *time_updated_alc_sync )( smtc_modem_event_time_status_t status ); + /*! + * @brief Automatic switch from mobile to static ADR when connection timeout occurs callback prototype. + */ + void ( *adr_mobile_to_static )( void ); + /*! + * @brief New link ADR request callback prototype. + */ + void ( *new_link_adr )( void ); + /*! + * @brief Link Status request callback prototype. + * + * @param [in] status \see smtc_modem_event_link_check_status_t + * @param [in] margin The demodulation margin in dB + * @param [in] gw_cnt number of gateways that received the most recent LinkCheckReq command + */ + void ( *link_status )( smtc_modem_event_link_check_status_t status, uint8_t margin, uint8_t gw_cnt ); + /*! + * @brief Almanac update callback prototype. + * + * @param [in] status \see smtc_modem_event_almanac_update_status_t + */ + void ( *almanac_update )( smtc_modem_event_almanac_update_status_t status ); + /*! + * @brief User radio access callback prototype. + * + * @param [in] timestamp_ms timestamp in ms of the radio irq + * @param [in] status Interrupt status + */ + void ( *user_radio_access )( uint32_t timestamp_ms, smtc_modem_event_user_radio_access_status_t status ); + /*! + * @brief Class B ping slot status callback prototype + * + * @param [in] status Class B ping slot status + */ + void ( *class_b_ping_slot_info )( smtc_modem_event_class_b_ping_slot_status_t status ); + /*! + * @brief Class B status callback prototype + * + * @param [in] status Class B status + */ + void ( *class_b_status )( smtc_modem_event_class_b_status_t status ); + /*! + * @brief Middleware 1 callback prototype. + * + * @param [in] status Interrupt status + */ + void ( *middleware_1 )( uint8_t status ); + /*! + * @brief Middleware 2 callback prototype. + * + * @param [in] status Interrupt status + */ + void ( *middleware_2 )( uint8_t status ); + /*! + * @brief Middleware 3 callback prototype. + * + * @param [in] status Interrupt status + */ + void ( *middleware_3 )( uint8_t status ); +} apps_modem_event_callback_t; + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/*! + * @brief Init the Lora Basics Modem event callbacks + * + * @param [in] event_callback Lora basics modem event callback \ref apps_modem_event_callback_t + */ +void apps_modem_event_init( apps_modem_event_callback_t* event_callback ); + +/*! + * @brief Process the analysis of lora basics modem event and calls callback functions + * depending on event. This callback is called every time an event ( see modem_event_t ) appears in the modem. + * Several events may have to be read from the modem when this callback is called. + */ +void apps_modem_event_process( void ); + +#ifdef __cplusplus +} +#endif + +#endif // APPS_MODEM_EVENT_H + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/common/apps_utilities.c b/lr1110/lr1110/seeed/apps/common/apps_utilities.c new file mode 100644 index 000000000..9854b11c1 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/apps_utilities.c @@ -0,0 +1,173 @@ +/*! + * @file apps_utilities.c + * + * @brief Common Application Helper function implementations + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include + +#include "smtc_hal.h" +#include "apps_utilities.h" +#include "smtc_modem_api_str.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC VARIABLES -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +void modem_status_to_string( smtc_modem_status_mask_t modem_status ) +{ + HAL_DBG_TRACE_MSG( "Modem status: " ); + + if( ( modem_status & SMTC_MODEM_STATUS_BROWNOUT ) == SMTC_MODEM_STATUS_BROWNOUT ) + { + HAL_DBG_TRACE_PRINTF( "BROWNOUT " ); + } + if( ( modem_status & SMTC_MODEM_STATUS_CRASH ) == SMTC_MODEM_STATUS_CRASH ) + { + HAL_DBG_TRACE_PRINTF( "CRASH " ); + } + if( ( modem_status & SMTC_MODEM_STATUS_MUTE ) == SMTC_MODEM_STATUS_MUTE ) + { + HAL_DBG_TRACE_PRINTF( "MUTE " ); + } + if( ( modem_status & SMTC_MODEM_STATUS_JOINED ) == SMTC_MODEM_STATUS_JOINED ) + { + HAL_DBG_TRACE_PRINTF( "JOINED " ); + } + if( ( modem_status & SMTC_MODEM_STATUS_SUSPEND ) == SMTC_MODEM_STATUS_SUSPEND ) + { + HAL_DBG_TRACE_PRINTF( "SUSPEND " ); + } + if( ( modem_status & SMTC_MODEM_STATUS_UPLOAD ) == SMTC_MODEM_STATUS_UPLOAD ) + { + HAL_DBG_TRACE_PRINTF( "UPLOAD " ); + } + if( ( modem_status & SMTC_MODEM_STATUS_JOINING ) == SMTC_MODEM_STATUS_JOINING ) + { + HAL_DBG_TRACE_PRINTF( "JOINING " ); + } + if( ( modem_status & SMTC_MODEM_STATUS_STREAM ) == SMTC_MODEM_STATUS_STREAM ) + { + HAL_DBG_TRACE_PRINTF( "STREAM " ); + } + + HAL_DBG_TRACE_PRINTF( "\r\n\r\n" ); +} + +void modem_class_to_string( smtc_modem_class_t lorawan_class ) +{ + switch( lorawan_class ) + { + case SMTC_MODEM_CLASS_A: + case SMTC_MODEM_CLASS_B: + case SMTC_MODEM_CLASS_C: + { + HAL_DBG_TRACE_PRINTF( "Class: %s\n", smtc_modem_class_to_str( lorawan_class ) ); + break; + } + default: + { + HAL_DBG_TRACE_WARNING( "Unknown Class\n\n" ); + } + } +} + +void modem_region_to_string( smtc_modem_region_t region ) +{ + switch( region ) + { + case SMTC_MODEM_REGION_EU_868: + case SMTC_MODEM_REGION_AS_923_GRP1: + case SMTC_MODEM_REGION_US_915: + case SMTC_MODEM_REGION_AU_915: + case SMTC_MODEM_REGION_CN_470: + case SMTC_MODEM_REGION_WW2G4: + case SMTC_MODEM_REGION_AS_923_GRP2: + case SMTC_MODEM_REGION_AS_923_GRP3: + case SMTC_MODEM_REGION_IN_865: + case SMTC_MODEM_REGION_KR_920: + case SMTC_MODEM_REGION_RU_864: + case SMTC_MODEM_REGION_CN_470_RP_1_0: + { + HAL_DBG_TRACE_PRINTF( "Region: %s\n\n", smtc_modem_region_to_str( region ) ); + break; + } + default: + { + HAL_DBG_TRACE_WARNING( "Unknown Region\n\n" ); + } + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/common/apps_utilities.h b/lr1110/lr1110/seeed/apps/common/apps_utilities.h new file mode 100644 index 000000000..98d7b418e --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/apps_utilities.h @@ -0,0 +1,145 @@ +/*! + * @file apps_utilities.h + * + * @brief Common Application Helper functions + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 APPS_UTILITIES_H +#define APPS_UTILITIES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include "smtc_modem_api.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/*! + * @brief Stringify constants + */ +#define xstr( a ) str( a ) +#define str( a ) #a + +/*! + * @brief Helper macro that returned a human-friendly message if a command does not return SMTC_MODEM_RC_OK + * + * @remark The macro is implemented to be used with functions returning a @ref smtc_modem_return_code_t + * + * @param[in] rc Return code + */ +#define ASSERT_SMTC_MODEM_RC( rc ) \ + \ + if( rc != SMTC_MODEM_RC_OK ) \ + { \ + if( rc == SMTC_MODEM_RC_NOT_INIT ) \ + { \ + HAL_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_NOT_INIT ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_INVALID ) \ + { \ + HAL_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_INVALID ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_BUSY ) \ + { \ + HAL_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_BUSY ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_FAIL ) \ + { \ + HAL_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_FAIL ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_BAD_SIZE ) \ + { \ + HAL_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_BAD_SIZE ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_NO_TIME ) \ + { \ + HAL_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_NO_TIME ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_INVALID_STACK_ID ) \ + { \ + HAL_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_INVALID_STACK_ID ) ); \ + } \ + } + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/*! + * @brief convert modem status to string + */ +void modem_status_to_string( smtc_modem_status_mask_t modem_status ); + +/*! + * @brief convert lorawan class status to string + */ +void modem_class_to_string( smtc_modem_class_t lorawan_class ); + +/*! + * @brief convert lorawan region status to string + */ +void modem_region_to_string( smtc_modem_region_t region ); + +#ifdef __cplusplus +} +#endif + +#endif // APPS_UTILITIES_H + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/common/lorawan_key_config.h b/lr1110/lr1110/seeed/apps/common/lorawan_key_config.h new file mode 100644 index 000000000..48d397d74 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/lorawan_key_config.h @@ -0,0 +1,110 @@ +/*! + * @file lorawan_key_config.h + * + * @brief End device lorawan key configuration + * + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 LORAWAN_KEY_CONFIG_H +#define LORAWAN_KEY_CONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/*! + ****************************************************************************** + ********************************** WARNING *********************************** + ****************************************************************************** + The crypto-element implementation supports both 1.0.x and 1.1.x LoRaWAN + versions of the specification. + Thus it has been decided to use the 1.1.x keys and EUI name definitions. + The below table shows the names equivalence between versions: + +---------------------+-------------------------+ + | 1.0.x | 1.1.x | + +=====================+=========================+ + | LORAWAN_DEVICE_EUI | LORAWAN_DEVICE_EUI | + +---------------------+-------------------------+ + | LORAWAN_APP_EUI | LORAWAN_JOIN_EUI | + +---------------------+-------------------------+ + | LORAWAN_APP_KEY | LORAWAN_NWK_KEY | + +---------------------+-------------------------+ + + ****************************************************************************** + ****************************************************************************** + ****************************************************************************** + */ + +/*! + * @brief LoRaWAN class - see @ref smtc_modem_class_t + */ +#define LORAWAN_REGION SMTC_MODEM_REGION_EU_868 +#define LORAWAN_CLASS SMTC_MODEM_CLASS_A +// #define LORAWAN_DEVICE_EUI "70B3D57ED0050417" +// #define LORAWAN_JOIN_EUI "0000000000000000" +// #define LORAWAN_APP_KEY "EC6B1047B76B2C3C92CF9A128A5CCFEB" +#define LORAWAN_DEVICE_EUI "3CF7F1000000007A" +#define LORAWAN_JOIN_EUI "0000000000000000" +#define LORAWAN_APP_KEY "7A8400FD6A4639A5C2244488F6DC41C6" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +#ifdef __cplusplus +} +#endif + +#endif // LORAWAN_KEY_CONFIG_H + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/common/smtc_modem_api_str.c b/lr1110/lr1110/seeed/apps/common/smtc_modem_api_str.c new file mode 100644 index 000000000..1de42bd87 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/smtc_modem_api_str.c @@ -0,0 +1,854 @@ +/*! + * @file smtc_modem_api_str.c + * + * @brief String helper functions for SMTC modem API + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 "smtc_modem_api_str.h" + +const char *smtc_modem_return_code_to_str(const smtc_modem_return_code_t value) +{ + switch (value) + { + case SMTC_MODEM_RC_OK: + { + return (const char *) "SMTC_MODEM_RC_OK"; + } + + case SMTC_MODEM_RC_NOT_INIT: + { + return (const char *) "SMTC_MODEM_RC_NOT_INIT"; + } + + case SMTC_MODEM_RC_INVALID: + { + return (const char *) "SMTC_MODEM_RC_INVALID"; + } + + case SMTC_MODEM_RC_BUSY: + { + return (const char *) "SMTC_MODEM_RC_BUSY"; + } + + case SMTC_MODEM_RC_FAIL: + { + return (const char *) "SMTC_MODEM_RC_FAIL"; + } + + case SMTC_MODEM_RC_BAD_SIZE: + { + return (const char *) "SMTC_MODEM_RC_BAD_SIZE"; + } + + case SMTC_MODEM_RC_MODEM_E_FRAME_ERROR: + { + return (const char *) "SMTC_MODEM_RC_MODEM_E_FRAME_ERROR"; + } + + case SMTC_MODEM_RC_NO_TIME: + { + return (const char *) "SMTC_MODEM_RC_NO_TIME"; + } + + case SMTC_MODEM_RC_INVALID_STACK_ID: + { + return (const char *) "SMTC_MODEM_RC_INVALID_STACK_ID"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_adr_profile_to_str(const smtc_modem_adr_profile_t value) +{ + switch (value) + { + case SMTC_MODEM_ADR_PROFILE_NETWORK_CONTROLLED: + { + return (const char *) "SMTC_MODEM_ADR_PROFILE_NETWORK_CONTROLLED"; + } + + case SMTC_MODEM_ADR_PROFILE_MOBILE_LONG_RANGE: + { + return (const char *) "SMTC_MODEM_ADR_PROFILE_MOBILE_LONG_RANGE"; + } + + case SMTC_MODEM_ADR_PROFILE_MOBILE_LOW_POWER: + { + return (const char *) "SMTC_MODEM_ADR_PROFILE_MOBILE_LOW_POWER"; + } + + case SMTC_MODEM_ADR_PROFILE_CUSTOM: + { + return (const char *) "SMTC_MODEM_ADR_PROFILE_CUSTOM"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_class_to_str(const smtc_modem_class_t value) +{ + switch (value) + { + case SMTC_MODEM_CLASS_A: + { + return (const char *) "SMTC_MODEM_CLASS_A"; + } + + case SMTC_MODEM_CLASS_B: + { + return (const char *) "SMTC_MODEM_CLASS_B"; + } + + case SMTC_MODEM_CLASS_C: + { + return (const char *) "SMTC_MODEM_CLASS_C"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_file_upload_cipher_mode_to_str(const smtc_modem_file_upload_cipher_mode_t value) +{ + switch (value) + { + case SMTC_MODEM_FILE_UPLOAD_NO_CIPHER: + { + return (const char *) "SMTC_MODEM_FILE_UPLOAD_NO_CIPHER"; + } + + case SMTC_MODEM_FILE_UPLOAD_AES_WITH_APPSKEY: + { + return (const char *) "SMTC_MODEM_FILE_UPLOAD_AES_WITH_APPSKEY"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_stream_cipher_mode_to_str(const smtc_modem_stream_cipher_mode_t value) +{ + switch (value) + { + case SMTC_MODEM_STREAM_NO_CIPHER: + { + return (const char *) "SMTC_MODEM_STREAM_NO_CIPHER"; + } + + case SMTC_MODEM_STREAM_AES_WITH_APPSKEY: + { + return (const char *) "SMTC_MODEM_STREAM_AES_WITH_APPSKEY"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_dm_info_interval_format_to_str(const smtc_modem_dm_info_interval_format_t value) +{ + switch (value) + { + case SMTC_MODEM_DM_INFO_INTERVAL_IN_SECOND: + { + return (const char *) "SMTC_MODEM_DM_INFO_INTERVAL_IN_SECOND"; + } + + case SMTC_MODEM_DM_INFO_INTERVAL_IN_DAY: + { + return (const char *) "SMTC_MODEM_DM_INFO_INTERVAL_IN_DAY"; + } + + case SMTC_MODEM_DM_INFO_INTERVAL_IN_HOUR: + { + return (const char *) "SMTC_MODEM_DM_INFO_INTERVAL_IN_HOUR"; + } + + case SMTC_MODEM_DM_INFO_INTERVAL_IN_MINUTE: + { + return (const char *) "SMTC_MODEM_DM_INFO_INTERVAL_IN_MINUTE"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_region_to_str(const smtc_modem_region_t value) +{ + switch (value) + { + case SMTC_MODEM_REGION_EU_868: + { + return (const char *) "SMTC_MODEM_REGION_EU_868"; + } + + case SMTC_MODEM_REGION_AS_923_GRP1: + { + return (const char *) "SMTC_MODEM_REGION_AS_923_GRP1"; + } + + case SMTC_MODEM_REGION_US_915: + { + return (const char *) "SMTC_MODEM_REGION_US_915"; + } + + case SMTC_MODEM_REGION_AU_915: + { + return (const char *) "SMTC_MODEM_REGION_AU_915"; + } + + case SMTC_MODEM_REGION_CN_470: + { + return (const char *) "SMTC_MODEM_REGION_CN_470"; + } + + case SMTC_MODEM_REGION_WW2G4: + { + return (const char *) "SMTC_MODEM_REGION_WW2G4"; + } + + case SMTC_MODEM_REGION_AS_923_GRP2: + { + return (const char *) "SMTC_MODEM_REGION_AS_923_GRP2"; + } + + case SMTC_MODEM_REGION_AS_923_GRP3: + { + return (const char *) "SMTC_MODEM_REGION_AS_923_GRP3"; + } + + case SMTC_MODEM_REGION_IN_865: + { + return (const char *) "SMTC_MODEM_REGION_IN_865"; + } + + case SMTC_MODEM_REGION_KR_920: + { + return (const char *) "SMTC_MODEM_REGION_KR_920"; + } + + case SMTC_MODEM_REGION_RU_864: + { + return (const char *) "SMTC_MODEM_REGION_RU_864"; + } + + case SMTC_MODEM_REGION_CN_470_RP_1_0: + { + return (const char *) "SMTC_MODEM_REGION_CN_470_RP_1_0"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_mc_grp_id_to_str(const smtc_modem_mc_grp_id_t value) +{ + switch (value) + { + case SMTC_MODEM_MC_GRP_0: + { + return (const char *) "SMTC_MODEM_MC_GRP_0"; + } + + case SMTC_MODEM_MC_GRP_1: + { + return (const char *) "SMTC_MODEM_MC_GRP_1"; + } + + case SMTC_MODEM_MC_GRP_2: + { + return (const char *) "SMTC_MODEM_MC_GRP_2"; + } + + case SMTC_MODEM_MC_GRP_3: + { + return (const char *) "SMTC_MODEM_MC_GRP_3"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_stack_state_to_str(const smtc_modem_stack_state_t value) +{ + switch (value) + { + case SMTC_MODEM_STACK_STATE_IDLE: + { + return (const char *) "SMTC_MODEM_STACK_STATE_IDLE"; + } + + case SMTC_MODEM_STACK_STATE_BUSY: + { + return (const char *) "SMTC_MODEM_STACK_STATE_BUSY"; + } + + case SMTC_MODEM_STACK_STATE_TX_WAIT: + { + return (const char *) "SMTC_MODEM_STACK_STATE_TX_WAIT"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_downdata_window_to_str(const smtc_modem_event_downdata_window_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX1: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX1"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX2: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX2"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC_MC_GRP0: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC_MC_GRP0"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC_MC_GRP1: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC_MC_GRP1"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC_MC_GRP2: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC_MC_GRP2"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC_MC_GRP3: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC_MC_GRP3"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB_MC_GRP0: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB_MC_GRP0"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB_MC_GRP1: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB_MC_GRP1"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB_MC_GRP2: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB_MC_GRP2"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB_MC_GRP3: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXB_MC_GRP3"; + } + + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXBEACON: + { + return (const char *) "SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXBEACON"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_time_sync_service_to_str(const smtc_modem_time_sync_service_t value) +{ + switch (value) + { + case SMTC_MODEM_TIME_MAC_SYNC: + { + return (const char *) "SMTC_MODEM_TIME_MAC_SYNC"; + } + + case SMTC_MODEM_TIME_ALC_SYNC: + { + return (const char *) "SMTC_MODEM_TIME_ALC_SYNC"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_time_status_to_str(const smtc_modem_event_time_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_TIME_NOT_VALID: + { + return (const char *) "SMTC_MODEM_EVENT_TIME_NOT_VALID"; + } + + case SMTC_MODEM_EVENT_TIME_VALID: + { + return (const char *) "SMTC_MODEM_EVENT_TIME_VALID"; + } + + case SMTC_MODEM_EVENT_TIME_VALID_BUT_NOT_SYNC: + { + return (const char *) "SMTC_MODEM_EVENT_TIME_VALID_BUT_NOT_SYNC"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_link_check_status_to_str(const smtc_modem_event_link_check_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_LINK_CHECK_NOT_RECEIVED: + { + return (const char *) "SMTC_MODEM_EVENT_LINK_CHECK_NOT_RECEIVED"; + } + + case SMTC_MODEM_EVENT_LINK_CHECK_RECEIVED: + { + return (const char *) "SMTC_MODEM_EVENT_LINK_CHECK_RECEIVED"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_txdone_status_to_str(const smtc_modem_event_txdone_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_TXDONE_NOT_SENT: + { + return (const char *) "SMTC_MODEM_EVENT_TXDONE_NOT_SENT"; + } + + case SMTC_MODEM_EVENT_TXDONE_SENT: + { + return (const char *) "SMTC_MODEM_EVENT_TXDONE_SENT"; + } + + case SMTC_MODEM_EVENT_TXDONE_CONFIRMED: + { + return (const char *) "SMTC_MODEM_EVENT_TXDONE_CONFIRMED"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_mute_status_to_str(const smtc_modem_event_mute_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_MUTE_OFF: + { + return (const char *) "SMTC_MODEM_EVENT_MUTE_OFF"; + } + + case SMTC_MODEM_EVENT_MUTE_ON: + { + return (const char *) "SMTC_MODEM_EVENT_MUTE_ON"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_uploaddone_status_to_str(const smtc_modem_event_uploaddone_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_UPLOADDONE_ABORTED: + { + return (const char *) "SMTC_MODEM_EVENT_UPLOADDONE_ABORTED"; + } + + case SMTC_MODEM_EVENT_UPLOADDONE_SUCCESSFUL: + { + return (const char *) "SMTC_MODEM_EVENT_UPLOADDONE_SUCCESSFUL"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_setconf_tag_to_str(const smtc_modem_event_setconf_tag_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_SETCONF_ADR_MODE_UPDATED: + { + return (const char *) "SMTC_MODEM_EVENT_SETCONF_ADR_MODE_UPDATED"; + } + + case SMTC_MODEM_EVENT_SETCONF_JOIN_EUI_UPDATED: + { + return (const char *) "SMTC_MODEM_EVENT_SETCONF_JOIN_EUI_UPDATED"; + } + + case SMTC_MODEM_EVENT_SETCONF_DM_INTERVAL_UPDATED: + { + return (const char *) "SMTC_MODEM_EVENT_SETCONF_DM_INTERVAL_UPDATED"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_almanac_update_status_to_str(const smtc_modem_event_almanac_update_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_ALMANAC_UPDATE_COMPLETED: + { + return (const char *) "SMTC_MODEM_EVENT_ALMANAC_UPDATE_COMPLETED"; + } + + case SMTC_MODEM_EVENT_ALMANAC_UPDATE_STATUS_REQUESTED: + { + return (const char *) "SMTC_MODEM_EVENT_ALMANAC_UPDATE_STATUS_REQUESTED"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_class_b_status_to_str(const smtc_modem_event_class_b_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_CLASS_B_NOT_READY: + { + return (const char *) "SMTC_MODEM_EVENT_CLASS_B_NOT_READY"; + } + + case SMTC_MODEM_EVENT_CLASS_B_READY: + { + return (const char *) "SMTC_MODEM_EVENT_CLASS_B_READY"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_class_b_ping_slot_status_to_str(const smtc_modem_event_class_b_ping_slot_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_CLASS_B_PING_SLOT_NOT_ANSWERED: + { + return (const char *) "SMTC_MODEM_EVENT_CLASS_B_PING_SLOT_NOT_ANSWERED"; + } + + case SMTC_MODEM_EVENT_CLASS_B_PING_SLOT_ANSWERED: + { + return (const char *) "SMTC_MODEM_EVENT_CLASS_B_PING_SLOT_ANSWERED"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_event_user_radio_access_status_to_str(const smtc_modem_event_user_radio_access_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_RX_ERROR: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_RX_ERROR"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_CAD_OK: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_CAD_OK"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_CAD_DONE: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_CAD_DONE"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_TX_DONE: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_TX_DONE"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_RX_DONE: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_RX_DONE"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_RX_TIMEOUT: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_RX_TIMEOUT"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_WIFI_SCAN_DONE: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_WIFI_SCAN_DONE"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_GNSS_SCAN_DONE: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_GNSS_SCAN_DONE"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_ABORTED: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_ABORTED"; + } + + case SMTC_MODEM_EVENT_USER_RADIO_ACCESS_UNKNOWN: + { + return (const char *) "SMTC_MODEM_EVENT_USER_RADIO_ACCESS_UNKNOWN"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_class_b_ping_slot_periodicity_to_str(const smtc_modem_class_b_ping_slot_periodicity_t value) +{ + switch (value) + { + case SMTC_MODEM_CLASS_B_PINGSLOT_1_S: + { + return (const char *) "SMTC_MODEM_CLASS_B_PINGSLOT_1_S"; + } + + case SMTC_MODEM_CLASS_B_PINGSLOT_2_S: + { + return (const char *) "SMTC_MODEM_CLASS_B_PINGSLOT_2_S"; + } + + case SMTC_MODEM_CLASS_B_PINGSLOT_4_S: + { + return (const char *) "SMTC_MODEM_CLASS_B_PINGSLOT_4_S"; + } + + case SMTC_MODEM_CLASS_B_PINGSLOT_8_S: + { + return (const char *) "SMTC_MODEM_CLASS_B_PINGSLOT_8_S"; + } + + case SMTC_MODEM_CLASS_B_PINGSLOT_16_S: + { + return (const char *) "SMTC_MODEM_CLASS_B_PINGSLOT_16_S"; + } + + case SMTC_MODEM_CLASS_B_PINGSLOT_32_S: + { + return (const char *) "SMTC_MODEM_CLASS_B_PINGSLOT_32_S"; + } + + case SMTC_MODEM_CLASS_B_PINGSLOT_64_S: + { + return (const char *) "SMTC_MODEM_CLASS_B_PINGSLOT_64_S"; + } + + case SMTC_MODEM_CLASS_B_PINGSLOT_128_S: + { + return (const char *) "SMTC_MODEM_CLASS_B_PINGSLOT_128_S"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_frame_pending_bit_status_to_str(const smtc_modem_frame_pending_bit_status_t value) +{ + switch (value) + { + case SMTC_MODEM_NO_DATA_ARE_PENDING: + { + return (const char *) "SMTC_MODEM_NO_DATA_ARE_PENDING"; + } + + case SMTC_MODEM_DATA_ARE_PENDING: + { + return (const char *) "SMTC_MODEM_DATA_ARE_PENDING"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + +const char *smtc_modem_d2d_class_b_tx_done_status_to_str(const smtc_modem_d2d_class_b_tx_done_status_t value) +{ + switch (value) + { + case SMTC_MODEM_EVENT_D2D_CLASS_B_TX_DONE_NOT_SENT: + { + return (const char *) "SMTC_MODEM_EVENT_D2D_CLASS_B_TX_DONE_NOT_SENT"; + } + + case SMTC_MODEM_EVENT_D2D_CLASS_B_TX_DONE_SENT: + { + return (const char *) "SMTC_MODEM_EVENT_D2D_CLASS_B_TX_DONE_SENT"; + } + + default: + { + return (const char *) "Unknown"; + } + + } + +} + diff --git a/lr1110/lr1110/seeed/apps/common/smtc_modem_api_str.h b/lr1110/lr1110/seeed/apps/common/smtc_modem_api_str.h new file mode 100644 index 000000000..cfd4773a7 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/common/smtc_modem_api_str.h @@ -0,0 +1,69 @@ +/*! + * @file smtc_modem_api_str.h + * + * @brief String helper functions for SMTC modem API + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 SMTC_MODEM_API_STR_H +#define SMTC_MODEM_API_STR_H +#include "smtc_modem_api.h" +#ifdef __cplusplus +extern "C" { +#endif +const char *smtc_modem_return_code_to_str(const smtc_modem_return_code_t value); +const char *smtc_modem_adr_profile_to_str(const smtc_modem_adr_profile_t value); +const char *smtc_modem_class_to_str(const smtc_modem_class_t value); +const char *smtc_modem_file_upload_cipher_mode_to_str(const smtc_modem_file_upload_cipher_mode_t value); +const char *smtc_modem_stream_cipher_mode_to_str(const smtc_modem_stream_cipher_mode_t value); +const char *smtc_modem_dm_info_interval_format_to_str(const smtc_modem_dm_info_interval_format_t value); +const char *smtc_modem_region_to_str(const smtc_modem_region_t value); +const char *smtc_modem_mc_grp_id_to_str(const smtc_modem_mc_grp_id_t value); +const char *smtc_modem_stack_state_to_str(const smtc_modem_stack_state_t value); +const char *smtc_modem_event_downdata_window_to_str(const smtc_modem_event_downdata_window_t value); +const char *smtc_modem_time_sync_service_to_str(const smtc_modem_time_sync_service_t value); +const char *smtc_modem_event_time_status_to_str(const smtc_modem_event_time_status_t value); +const char *smtc_modem_event_link_check_status_to_str(const smtc_modem_event_link_check_status_t value); +const char *smtc_modem_event_txdone_status_to_str(const smtc_modem_event_txdone_status_t value); +const char *smtc_modem_event_mute_status_to_str(const smtc_modem_event_mute_status_t value); +const char *smtc_modem_event_uploaddone_status_to_str(const smtc_modem_event_uploaddone_status_t value); +const char *smtc_modem_event_setconf_tag_to_str(const smtc_modem_event_setconf_tag_t value); +const char *smtc_modem_event_almanac_update_status_to_str(const smtc_modem_event_almanac_update_status_t value); +const char *smtc_modem_event_class_b_status_to_str(const smtc_modem_event_class_b_status_t value); +const char *smtc_modem_event_class_b_ping_slot_status_to_str(const smtc_modem_event_class_b_ping_slot_status_t value); +const char *smtc_modem_event_user_radio_access_status_to_str(const smtc_modem_event_user_radio_access_status_t value); +const char *smtc_modem_class_b_ping_slot_periodicity_to_str(const smtc_modem_class_b_ping_slot_periodicity_t value); +const char *smtc_modem_frame_pending_bit_status_to_str(const smtc_modem_frame_pending_bit_status_t value); +const char *smtc_modem_d2d_class_b_tx_done_status_to_str(const smtc_modem_d2d_class_b_tx_done_status_t value); +#ifdef __cplusplus +} +#endif +#endif // SMTC_MODEM_API_STR_H diff --git a/lr1110/lr1110/seeed/apps/examples/accelerometer/main_accelerometer.c b/lr1110/lr1110/seeed/apps/examples/accelerometer/main_accelerometer.c new file mode 100644 index 000000000..1dca01684 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/accelerometer/main_accelerometer.c @@ -0,0 +1,21 @@ + +#include "smtc_hal.h" + +int main(void) +{ + hal_debug_init( ); + hal_i2c_master_init( ); + hal_gpio_init_out( SENSOR_POWER, HAL_GPIO_SET ); + hal_mcu_wait_ms( 10 ); // wait power on + LIS3dInit( ); + + PRINTF( "Accelerometer test\r\n" ); + + while( 1 ) + { + float ax = 0, ay = 0, az = 0; + LIS3dGetAcceleration( &ax, &ay, &az ); + PRINTF( "%.3f, %.3f, %.3f\r\n", ax, ay, az ); + hal_mcu_wait_ms( 1000 ); + } +} diff --git a/lr1110/lr1110/seeed/apps/examples/air_th_sensor/main_air_th_sensor.c b/lr1110/lr1110/seeed/apps/examples/air_th_sensor/main_air_th_sensor.c new file mode 100644 index 000000000..0f80ad18f --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/air_th_sensor/main_air_th_sensor.c @@ -0,0 +1,20 @@ + +#include "smtc_hal.h" + +int main(void) +{ + hal_debug_init( ); + hal_i2c_master_init( ); + hal_gpio_init_out( SENSOR_POWER, HAL_GPIO_SET ); + hal_mcu_wait_ms( 10 ); // wait power on + SHT41Init( ); + PRINTF( "SHT41 test\r\n" ); + + while( 1 ) + { + float temp = 0, humi = 0; + SHT41GetTempAndHumi( &temp, &humi ); + PRINTF( "temp = %.1f, humi = %.1f\r\n", temp, humi ); + hal_mcu_wait_ms( 1000 ); + } +} diff --git a/lr1110/lr1110/seeed/apps/examples/blinky/main_blinky.c b/lr1110/lr1110/seeed/apps/examples/blinky/main_blinky.c new file mode 100644 index 000000000..67064f49c --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/blinky/main_blinky.c @@ -0,0 +1,18 @@ + +#include "smtc_hal.h" + +int main(void) +{ + hal_gpio_init_out( USER_LED_G, HAL_GPIO_RESET ); + hal_gpio_init_out( USER_LED_R, HAL_GPIO_RESET ); + + while( 1 ) + { + hal_gpio_init_out( USER_LED_G, HAL_GPIO_SET ); + hal_gpio_init_out( USER_LED_R, HAL_GPIO_RESET ); + hal_mcu_wait_ms( 500 ); + hal_gpio_init_out( USER_LED_G, HAL_GPIO_RESET ); + hal_gpio_init_out( USER_LED_R, HAL_GPIO_SET ); + hal_mcu_wait_ms( 500 ); + } +} diff --git a/lr1110/lr1110/seeed/apps/examples/full_almanac_update/README.md b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/README.md new file mode 100644 index 000000000..9bb60d595 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/README.md @@ -0,0 +1,64 @@ +# LR11xx full almanac update example + +## Description + +This application executes a full almanac update by using the LR11xx API. +It does not involve LoRaWAN communication to update the almanac. +For the almanac update through LoRaWAN, refer to the example *almanac_update*. + +This example also provides a simple python script *get_full_almanac.py* that fetches almanac content from LoRa Cloud and generate a C header file that is compiled with the embedded binary. + +**NOTE**: This example is only applicable to LR1110 / LR1120 chips. + +## Usage + +The full almanac update with this example is executed in two steps: + +1. Generate an almanac C header file with the python script *get_full_almanac.py*; +2. Build the example code and flash the binary to the Nucleo board. + +### Generation of almanac C header file + +The python script usage to generate the almanac C header file can be obtained with: + +```bash +$ python ./get_full_almanac.py --help +usage: get_full_almanac.py [-h] [-f OUTPUT_FILE] mgs_token + +Companion software that generates almanac header file to be compiled for LR1110/LR1120 embedded full almanac update. + +positional arguments: + mgs_token MGS LoRa Cloud token to use to fetch the almanac + +optional arguments: + -h, --help show this help message and exit + -f OUTPUT_FILE, --output_file OUTPUT_FILE + file that will contain the results +``` + +### Compile and flash the binary code + +The example code expects the almanac C header file produced by *get_full_almanac.py* python script to be named `almanac.h`. + +The binary can be produced as the other examples (refer to [README.md](../../../README.md) for the details) + +## Expected Behavior + +Here follow the steps that has to be seen in the logs to indicate the expected behavior of the application. + +### Device starts + +``` +INFO: Modem Initialization + +INFO: ===== LoRa Basics Modem full almanac update example ===== +``` + +### Full update of the almanac + +The successful completion of the full almanac update is indicated by: + +``` +INFO: Local almanac doesn't match LR11XX almanac -> start update +INFO: Almanac update succeeded +``` diff --git a/lr1110/lr1110/seeed/apps/examples/full_almanac_update/almanac.h b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/almanac.h new file mode 100644 index 000000000..e05b25eff --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/almanac.h @@ -0,0 +1,5 @@ +/* This file has been auto-generated by the get_full_almanac.py script */ + +#include "lr11xx_gnss.h" + +static const uint8_t full_almanac[( LR11XX_GNSS_FULL_UPDATE_N_ALMANACS * LR11XX_GNSS_SINGLE_ALMANAC_WRITE_SIZE ) + 20] = { 0x80, 0x86, 0x05, 0x97, 0x32, 0xB9, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x43, 0x28, 0x2B, 0x03, 0x50, 0x28, 0x8F, 0x40, 0x3B, 0x26, 0xE7, 0xA3, 0xA6, 0xFF, 0xDF, 0x00, 0x01, 0x00, 0x01, 0x42, 0x43, 0x28, 0x1F, 0x05, 0x68, 0x27, 0xF9, 0x17, 0x02, 0xCA, 0xF5, 0x9F, 0xA0, 0xFF, 0x6F, 0x00, 0x01, 0x00, 0x02, 0x58, 0x43, 0x28, 0x25, 0x01, 0xD2, 0x27, 0xB4, 0x11, 0x66, 0x29, 0xF2, 0xCD, 0xA5, 0xFF, 0x37, 0x00, 0x01, 0x00, 0x03, 0x59, 0x43, 0x28, 0x98, 0x00, 0x3B, 0x27, 0xD0, 0x88, 0x58, 0x84, 0xFF, 0xF9, 0xAC, 0xFF, 0x1B, 0x00, 0x01, 0x00, 0x04, 0x21, 0x43, 0x28, 0x7A, 0x01, 0x4B, 0x27, 0xB0, 0xA9, 0x28, 0x2E, 0x1A, 0xCC, 0xA4, 0xFF, 0xA4, 0x01, 0x01, 0x00, 0x05, 0x21, 0x43, 0x28, 0xBF, 0x00, 0x48, 0x28, 0x32, 0x4A, 0x26, 0xDE, 0x90, 0xA3, 0xA7, 0xFF, 0xD2, 0x00, 0x01, 0x00, 0x06, 0x0D, 0x43, 0x28, 0x4A, 0x04, 0xB9, 0x26, 0x7F, 0x28, 0x5F, 0xA5, 0x5E, 0x23, 0xA3, 0xFF, 0xA6, 0x01, 0x01, 0x00, 0x07, 0x3A, 0x43, 0x28, 0x23, 0x02, 0x21, 0x27, 0x65, 0x9B, 0xD1, 0x08, 0x8E, 0x77, 0xAC, 0xFF, 0xD3, 0x00, 0x01, 0x00, 0x08, 0x43, 0x43, 0x28, 0xA2, 0x00, 0xF5, 0x26, 0xB7, 0xA8, 0x58, 0x50, 0xB8, 0xF7, 0xAB, 0xFF, 0x69, 0x00, 0x01, 0x00, 0x09, 0x45, 0x43, 0x28, 0x2B, 0x02, 0xD0, 0x27, 0x4D, 0xEB, 0x8C, 0x9C, 0xD6, 0xCD, 0xA3, 0xFF, 0xBB, 0x00, 0x01, 0x00, 0x0A, 0x4D, 0x43, 0x28, 0x32, 0x00, 0x4F, 0x27, 0xCA, 0x7C, 0x2C, 0x92, 0x57, 0xA5, 0xA4, 0xFF, 0x5D, 0x00, 0x01, 0x00, 0x0B, 0x2E, 0x43, 0x28, 0x49, 0x02, 0x5C, 0x27, 0x9E, 0x07, 0x70, 0x36, 0xCC, 0x50, 0xA6, 0xFF, 0x17, 0x00, 0x01, 0x00, 0x0C, 0x44, 0x43, 0x28, 0xBB, 0x01, 0x86, 0x27, 0x7F, 0x88, 0x04, 0x26, 0x2F, 0xFE, 0xAF, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0D, 0x33, 0x43, 0x28, 0xAF, 0x00, 0xAE, 0x26, 0xEF, 0x09, 0x6F, 0x82, 0x48, 0x4F, 0xA5, 0xFF, 0x05, 0x00, 0x01, 0x00, 0x0E, 0x45, 0x43, 0x28, 0xC6, 0x03, 0xFC, 0x25, 0x98, 0x73, 0x0C, 0x30, 0x01, 0xF3, 0xAB, 0xFF, 0x02, 0x00, 0x01, 0x00, 0x0F, 0xFA, 0x42, 0x28, 0x6F, 0x03, 0x59, 0x27, 0x0A, 0xBC, 0x6D, 0x1F, 0x8B, 0x51, 0xA9, 0xFF, 0x01, 0x00, 0x01, 0x00, 0x10, 0x5C, 0x43, 0x28, 0x7F, 0x03, 0xC3, 0x27, 0x8D, 0x99, 0x2E, 0xC6, 0x1E, 0x7B, 0xB0, 0xFF, 0x91, 0x01, 0x01, 0x00, 0x11, 0x48, 0x43, 0x28, 0xC3, 0x00, 0xA8, 0x27, 0x90, 0x45, 0x24, 0x81, 0x00, 0xA4, 0xA4, 0xFF, 0xC8, 0x00, 0x01, 0x00, 0x12, 0x20, 0x43, 0x28, 0x55, 0x02, 0xB9, 0x27, 0xD2, 0xF0, 0x68, 0x5B, 0xEF, 0x7C, 0xAB, 0xFF, 0x64, 0x00, 0x01, 0x00, 0x13, 0x63, 0x43, 0x28, 0x23, 0x01, 0x93, 0x26, 0x7C, 0x64, 0x55, 0x89, 0x2E, 0xC7, 0xA1, 0xFF, 0x32, 0x00, 0x01, 0x00, 0x14, 0x36, 0x43, 0x28, 0x5E, 0x06, 0x2D, 0x27, 0xE0, 0x98, 0xE3, 0xDE, 0xCA, 0x9F, 0xA9, 0xFF, 0x19, 0x00, 0x01, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x16, 0x31, 0x43, 0x28, 0xC5, 0x00, 0x9C, 0x27, 0x9F, 0x1B, 0xB9, 0x81, 0xC0, 0xCC, 0xA3, 0xFF, 0xCC, 0x01, 0x01, 0x00, 0x17, 0x2E, 0x43, 0x28, 0x83, 0x03, 0x0E, 0x26, 0xEF, 0x4B, 0x15, 0x24, 0xBE, 0x1F, 0xA4, 0xFF, 0x39, 0x00, 0x01, 0x00, 0x18, 0x67, 0x43, 0x28, 0xDC, 0x02, 0xDA, 0x26, 0xC2, 0x03, 0x08, 0x2A, 0x7D, 0x4D, 0xA4, 0xFF, 0x1C, 0x00, 0x01, 0x00, 0x19, 0x20, 0x43, 0x28, 0x04, 0x02, 0x12, 0x26, 0xE6, 0xE3, 0x60, 0x12, 0x4B, 0x4B, 0xA2, 0xFF, 0x0E, 0x00, 0x01, 0x00, 0x1A, 0x3F, 0x43, 0x28, 0xE8, 0x02, 0x7D, 0x27, 0xAD, 0x9B, 0x79, 0x1C, 0x78, 0x78, 0xAE, 0xFF, 0x07, 0x00, 0x01, 0x00, 0x1B, 0x21, 0x43, 0x28, 0x42, 0x00, 0x2E, 0x27, 0x6C, 0xE7, 0xC3, 0x44, 0xCD, 0x21, 0xA8, 0xFF, 0x03, 0x00, 0x01, 0x00, 0x1C, 0x15, 0x43, 0x28, 0x90, 0x00, 0xDA, 0x27, 0xC8, 0x9C, 0xA7, 0x61, 0xAE, 0x7B, 0xAD, 0xFF, 0xA8, 0x01, 0x01, 0x00, 0x1D, 0x21, 0x43, 0x28, 0x93, 0x01, 0x1E, 0x26, 0x56, 0x22, 0xF8, 0x93, 0xAD, 0x23, 0xA3, 0xFF, 0xD4, 0x00, 0x01, 0x00, 0x1E, 0x2F, 0x43, 0x28, 0xC2, 0x02, 0xE4, 0x26, 0x25, 0x10, 0x74, 0x14, 0x2A, 0x24, 0xA6, 0xFF, 0x6A, 0x00, 0x01, 0x00, 0x1F, 0x58, 0x43, 0x28, 0xB3, 0x01, 0x1B, 0x27, 0x09, 0xB7, 0x2B, 0xA4, 0x3D, 0xF8, 0xAD, 0xFF, 0x35, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 0x55, 0xBB, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE8, 0x66, 0x00, 0x00, 0x87, 0x01, 0x02, 0x01, 0x41, 0x49, 0xC3, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9E, 0x3B, 0x00, 0x00, 0x39, 0x06, 0x02, 0x01, 0x42, 0x13, 0xC2, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x4E, 0x00, 0x00, 0xE6, 0x01, 0x02, 0x01, 0x43, 0xD6, 0xBB, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0x71, 0x00, 0x00, 0x09, 0x06, 0x02, 0x01, 0x44, 0x13, 0xC6, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAC, 0x29, 0x00, 0x00, 0x05, 0x06, 0x02, 0x01, 0x45, 0x73, 0xBA, 0x32, 0xE7, 0x00, 0x93, 0x26, 0x66, 0xB8, 0xB1, 0x82, 0x07, 0x12, 0xEF, 0xFF, 0xF8, 0x01, 0x02, 0x01, 0x46, 0xF2, 0xB9, 0x32, 0x94, 0x00, 0x62, 0x23, 0x0E, 0x5B, 0x25, 0x8F, 0x39, 0x62, 0xEA, 0xFF, 0x06, 0x06, 0x02, 0x01, 0x47, 0x08, 0xBA, 0x32, 0x9D, 0x00, 0x19, 0x2B, 0xCF, 0x0C, 0x94, 0x83, 0x2A, 0xBB, 0xDD, 0xFF, 0xF9, 0x01, 0x02, 0x01, 0x48, 0xC6, 0xBA, 0x32, 0xA7, 0x02, 0xC5, 0x26, 0xAB, 0x8E, 0x84, 0xA1, 0xD0, 0x13, 0xEF, 0xFF, 0x04, 0x07, 0x02, 0x01, 0x49, 0x7C, 0xBA, 0x32, 0x2A, 0x02, 0x78, 0x23, 0x93, 0x46, 0xE3, 0x9B, 0xFA, 0x61, 0xE9, 0xFF, 0xBE, 0x07, 0x02, 0x01, 0x4A, 0x35, 0x45, 0x29, 0x88, 0x00, 0x44, 0x28, 0xC3, 0x04, 0x74, 0xBA, 0xEF, 0x8B, 0xB8, 0xFF, 0x61, 0x00, 0x02, 0x01, 0x4B, 0x24, 0x45, 0x29, 0x4E, 0x00, 0x34, 0x28, 0x81, 0x1F, 0x4F, 0xBF, 0x71, 0x8B, 0xBA, 0xFF, 0x8E, 0x07, 0x02, 0x01, 0x4C, 0x82, 0xBA, 0x32, 0x39, 0x01, 0x8A, 0x29, 0xC8, 0xEA, 0xA5, 0x9F, 0x12, 0xBA, 0xDC, 0xFF, 0x82, 0x07, 0x02, 0x01, 0x4D, 0x32, 0x45, 0x29, 0x51, 0x00, 0x83, 0x27, 0xA6, 0x7F, 0xE6, 0xE9, 0xDC, 0xDF, 0xB5, 0xFF, 0x7F, 0x00, 0x02, 0x01, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x4F, 0xFE, 0xB9, 0x32, 0x6F, 0x01, 0x35, 0x27, 0xB3, 0x9B, 0x7E, 0xA1, 0xBC, 0x11, 0xF0, 0xFF, 0x7E, 0x00, 0x02, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x52, 0x2C, 0x45, 0x29, 0x3D, 0x00, 0x9E, 0x27, 0x2D, 0x09, 0xA1, 0xCF, 0xE2, 0xDF, 0xB6, 0xFF, 0x3C, 0x00, 0x02, 0x01, 0x53, 0x2F, 0x45, 0x29, 0x2F, 0x00, 0x9E, 0x27, 0x42, 0x16, 0xF1, 0xE0, 0xE8, 0xDF, 0xB6, 0xFF, 0xC1, 0x07, 0x02, 0x01, 0x54, 0x2C, 0x45, 0x29, 0x2F, 0x00, 0x97, 0x27, 0x89, 0xC0, 0x24, 0xD8, 0xF6, 0xDF, 0xB5, 0xFF, 0x3F, 0x00, 0x02, 0x01, 0x55, 0x31, 0x45, 0x29, 0x1F, 0x00, 0x97, 0x27, 0xB0, 0xC2, 0xC8, 0xF5, 0xF5, 0xDF, 0xB6, 0xFF, 0xC0, 0x07, 0x02, 0x01, 0x56, 0x17, 0x45, 0x29, 0x0E, 0x00, 0x88, 0x26, 0xC3, 0xC4, 0x2C, 0x21, 0x5C, 0x35, 0xB1, 0xFF, 0xEF, 0x07, 0x02, 0x01, 0x57, 0x21, 0x45, 0x29, 0x35, 0x00, 0x88, 0x26, 0xDB, 0x05, 0x3B, 0x21, 0x5A, 0x35, 0xB1, 0xFF, 0xE3, 0x07, 0x02, 0x01, 0x58, 0x18, 0x45, 0x29, 0x27, 0x00, 0x98, 0x26, 0x15, 0xE7, 0x93, 0x1D, 0x5C, 0x34, 0xB2, 0xFF, 0x1E, 0x00, 0x02, 0x01, 0x59, 0x16, 0x45, 0x29, 0x3B, 0x00, 0x98, 0x26, 0xE7, 0x29, 0x85, 0x1A, 0x62, 0x34, 0xB1, 0xFF, 0xE0, 0x07, 0x02, 0x01, 0x5A, 0x2B, 0x45, 0x29, 0x2B, 0x00, 0x48, 0x27, 0x65, 0x44, 0x4C, 0x29, 0x1B, 0x8B, 0xB7, 0xFF, 0x1F, 0x00, 0x02, 0x01, 0x5B, 0x2E, 0x45, 0x29, 0x09, 0x00, 0x46, 0x27, 0x2A, 0xBC, 0xC1, 0xCF, 0x19, 0x8B, 0xB7, 0xFF, 0x0C, 0x00, 0x02, 0x01, 0x5C, 0x27, 0x45, 0x29, 0x0D, 0x00, 0x36, 0x27, 0x65, 0x0C, 0xBD, 0x21, 0x82, 0x89, 0xB7, 0xFF, 0xF1, 0x07, 0x02, 0x01, 0x5D, 0x29, 0x45, 0x29, 0x24, 0x00, 0x35, 0x27, 0xC6, 0x41, 0x97, 0x0D, 0x87, 0x89, 0xB7, 0xFF, 0x0F, 0x00, 0x02, 0x01, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x5F, 0x31, 0x45, 0x29, 0x27, 0x00, 0x91, 0x27, 0x24, 0x4D, 0xAA, 0xCB, 0xA3, 0xDF, 0xB5, 0xFF, 0xFD, 0x07, 0x02, 0x01, 0x60, 0x30, 0x45, 0x29, 0x19, 0x00, 0x91, 0x27, 0x88, 0x78, 0x72, 0xE0, 0xA1, 0xDF, 0xB6, 0xFF, 0x03, 0x00, 0x02, 0x01, 0x61, 0x2B, 0x45, 0x29, 0x28, 0x00, 0x22, 0x27, 0xCB, 0xB7, 0x66, 0x15, 0x9C, 0x89, 0xB6, 0xFF, 0xFC, 0x07, 0x02, 0x01, 0x62, 0x45, 0x45, 0x29, 0x3B, 0x00, 0x23, 0x27, 0x97, 0x01, 0x61, 0x0C, 0x9B, 0x89, 0xB7, 0xFF, 0xFE, 0x07, 0x02, 0x01, 0x63, 0x16, 0x45, 0x29, 0x1E, 0x00, 0x97, 0x26, 0xD7, 0xB3, 0x02, 0xD2, 0x4B, 0x35, 0xB1, 0xFF, 0x01, 0x00, 0x02, 0x01, 0x64, 0x16, 0x45, 0x29, 0x23, 0x00, 0x98, 0x26, 0xF2, 0xCF, 0xF2, 0xF6, 0x49, 0x35, 0xB0, 0xFF, 0xFF, 0x07, 0x02, 0x01, 0x65, 0x0B, 0xBA, 0x32, 0x83, 0x00, 0x73, 0x28, 0xB0, 0x08, 0xAA, 0x91, 0x1B, 0xBA, 0xDB, 0xFF, 0x57, 0x04, 0x02, 0x01, 0x66, 0x66, 0xBA, 0x32, 0xC4, 0x00, 0x3E, 0x27, 0x19, 0xBD, 0xE5, 0x88, 0xDF, 0x0E, 0xF0, 0xFF, 0xED, 0x04, 0x02, 0x01, 0x67, 0x21, 0xBA, 0x32, 0xBD, 0x00, 0x71, 0x28, 0xE0, 0x6F, 0x23, 0x7D, 0x49, 0x67, 0xF1, 0xFF, 0xDD, 0x04, 0x02, 0x01, 0x68, 0x2C, 0x45, 0x29, 0x6E, 0x00, 0x85, 0x27, 0x6A, 0x78, 0x25, 0xC1, 0xC6, 0xDF, 0xB6, 0xFF, 0xD1, 0x04, 0x02, 0x01, 0x69, 0x2E, 0x45, 0x29, 0x60, 0x00, 0x86, 0x27, 0x50, 0xAE, 0x20, 0xCB, 0xCC, 0xDF, 0xB5, 0xFF, 0xD2, 0x04, 0x02, 0x01, 0x6A, 0x29, 0x45, 0x29, 0x26, 0x00, 0x03, 0x27, 0x0E, 0x9E, 0x7A, 0x11, 0x97, 0x89, 0xB6, 0xFF, 0x2D, 0x03, 0x02, 0x01, 0x6B, 0x2B, 0x45, 0x29, 0x34, 0x00, 0x07, 0x27, 0x52, 0xDC, 0xB7, 0x11, 0x99, 0x89, 0xB6, 0xFF, 0x8C, 0x04, 0x02, 0x01, 0x6C, 0x17, 0x45, 0x29, 0x25, 0x00, 0xC0, 0x26, 0x89, 0x52, 0xAB, 0x12, 0x68, 0x35, 0xB2, 0xFF, 0x92, 0x04, 0x02, 0x01, 0x6D, 0x1B, 0x45, 0x29, 0x39, 0x00, 0xC1, 0x26, 0x99, 0x96, 0x75, 0x0D, 0x6D, 0x35, 0xB1, 0xFF, 0xBC, 0x04, 0x02, 0x01, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7A, 0x0C, 0xBD, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0x63, 0x00, 0x00, 0x3B, 0x05, 0x02, 0x01, 0x7B, 0x4F, 0xBD, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD3, 0x38, 0x00, 0x00, 0x37, 0x05, 0x02, 0x01, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; diff --git a/lr1110/lr1110/seeed/apps/examples/full_almanac_update/almanac_old.h b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/almanac_old.h new file mode 100644 index 000000000..4818d883b --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/almanac_old.h @@ -0,0 +1,6 @@ +/* This file has been auto-generated by the get_full_almanac.py script */ + +#include "lr11xx_gnss.h" + +/* Almanac image for GPS time 1301529618 */ +static const uint8_t full_almanac[( LR11XX_GNSS_FULL_UPDATE_N_ALMANACS * LR11XX_GNSS_SINGLE_ALMANAC_WRITE_SIZE ) + 20] = { 0x80, 0xD8, 0x02, 0x5A, 0x97, 0xB6, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x43, 0x28, 0xB9, 0x02, 0x16, 0x28, 0x46, 0x5D, 0xAE, 0x21, 0xBE, 0x97, 0xAA, 0xFF, 0xDF, 0x00, 0x01, 0x00, 0x01, 0x33, 0x43, 0x28, 0x31, 0x05, 0x39, 0x27, 0xC3, 0x67, 0xBB, 0xC1, 0x60, 0x94, 0xAA, 0xFF, 0x6F, 0x00, 0x01, 0x00, 0x02, 0x27, 0x43, 0x28, 0xDB, 0x00, 0x75, 0x27, 0xDB, 0x30, 0xA8, 0x21, 0x0B, 0xC2, 0xAA, 0xFF, 0x37, 0x00, 0x01, 0x00, 0x03, 0x68, 0x43, 0x28, 0x4C, 0x00, 0x24, 0x27, 0x6C, 0xA1, 0xD8, 0x86, 0x11, 0xEE, 0xAA, 0xFF, 0x1B, 0x00, 0x01, 0x00, 0x04, 0x96, 0x43, 0x28, 0x89, 0x01, 0xED, 0x26, 0x07, 0xCE, 0xDB, 0x23, 0x92, 0xC0, 0xA8, 0xFF, 0xA4, 0x01, 0x01, 0x00, 0x05, 0x62, 0x43, 0x28, 0x97, 0x00, 0x11, 0x28, 0x1D, 0x68, 0xD9, 0xD6, 0x68, 0x97, 0xA9, 0xFF, 0xD2, 0x00, 0x01, 0x00, 0x06, 0x10, 0x43, 0x28, 0xC2, 0x03, 0xCB, 0x26, 0x2F, 0x45, 0x84, 0xA0, 0xB5, 0x17, 0xA7, 0xFF, 0xA6, 0x01, 0x01, 0x00, 0x07, 0x0E, 0x43, 0x28, 0x9F, 0x01, 0x70, 0x27, 0xAC, 0xB7, 0xC9, 0x00, 0x02, 0x6C, 0xA5, 0xFF, 0xD3, 0x00, 0x01, 0x00, 0x08, 0x80, 0x43, 0x28, 0x7F, 0x00, 0xD9, 0x26, 0xE5, 0xC9, 0xBB, 0x4A, 0x04, 0xEC, 0xA8, 0xFF, 0x69, 0x00, 0x01, 0x00, 0x09, 0x3F, 0x43, 0x28, 0xAF, 0x01, 0x74, 0x27, 0x84, 0x09, 0x7C, 0x97, 0xED, 0xC1, 0xA9, 0xFF, 0xBB, 0x00, 0x01, 0x00, 0x0A, 0x0A, 0x44, 0x28, 0xEB, 0x03, 0x44, 0x25, 0xEA, 0x3D, 0x29, 0x55, 0x97, 0x83, 0xA0, 0xFF, 0x5D, 0x00, 0x01, 0x00, 0x0B, 0x71, 0x43, 0x28, 0x2C, 0x02, 0xAF, 0x27, 0x69, 0x27, 0x9E, 0x30, 0xF2, 0x44, 0xA8, 0xFF, 0x17, 0x00, 0x01, 0x00, 0x0C, 0x3F, 0x43, 0x28, 0x42, 0x01, 0x74, 0x27, 0xD1, 0x9D, 0xAF, 0x28, 0x09, 0xF2, 0xAB, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0D, 0x65, 0x43, 0x28, 0x22, 0x00, 0x03, 0x27, 0x44, 0x40, 0x3D, 0x62, 0xDF, 0x43, 0xA8, 0xFF, 0x05, 0x00, 0x01, 0x00, 0x0E, 0x0C, 0x43, 0x28, 0x59, 0x03, 0xDA, 0x25, 0x2C, 0x90, 0xB7, 0x27, 0x0A, 0xE8, 0xA7, 0xFF, 0x02, 0x00, 0x01, 0x00, 0x0F, 0x42, 0x43, 0x28, 0x24, 0x03, 0xB0, 0x27, 0xAD, 0xDB, 0x3D, 0x1B, 0xB5, 0x45, 0xA9, 0xFF, 0x01, 0x00, 0x01, 0x00, 0x10, 0x0F, 0x43, 0x28, 0x6C, 0x03, 0x09, 0x28, 0x08, 0xB5, 0x05, 0xC0, 0x26, 0x6F, 0xAA, 0xFF, 0x91, 0x01, 0x01, 0x00, 0x11, 0x3F, 0x43, 0x28, 0x53, 0x00, 0x6C, 0x27, 0x62, 0x65, 0x46, 0x79, 0x41, 0x98, 0xA7, 0xFF, 0xC8, 0x00, 0x01, 0x00, 0x12, 0x28, 0x43, 0x28, 0x58, 0x02, 0xFA, 0x27, 0xCF, 0x1B, 0x6D, 0x49, 0xFE, 0x70, 0xA6, 0xFF, 0x64, 0x00, 0x01, 0x00, 0x13, 0x48, 0x3F, 0x28, 0x8B, 0x01, 0x30, 0x26, 0xDE, 0x4B, 0xFD, 0x7A, 0x2C, 0xBC, 0xA7, 0xFF, 0x32, 0x00, 0x01, 0x00, 0x14, 0x4D, 0x43, 0x28, 0x32, 0x06, 0xFC, 0x26, 0xB5, 0xBD, 0xB1, 0xD0, 0x64, 0x94, 0xA4, 0xFF, 0x19, 0x00, 0x01, 0x00, 0x15, 0x09, 0x43, 0x28, 0xCF, 0x01, 0x0D, 0x26, 0xEB, 0x8D, 0xFB, 0xD6, 0x11, 0xBE, 0xA8, 0xFF, 0x0C, 0x00, 0x01, 0x00, 0x16, 0x34, 0x43, 0x28, 0x47, 0x00, 0x40, 0x27, 0x4D, 0x4D, 0x26, 0x68, 0xFB, 0xC0, 0xA9, 0xFF, 0xCC, 0x01, 0x01, 0x00, 0x17, 0x5C, 0x43, 0x28, 0xD3, 0x02, 0x1E, 0x26, 0xD5, 0x6B, 0x0D, 0x1E, 0x8E, 0x14, 0xA8, 0xFF, 0x39, 0x00, 0x01, 0x00, 0x18, 0x31, 0x43, 0x28, 0x8B, 0x02, 0x2B, 0x27, 0x2C, 0x1A, 0x04, 0x27, 0xFA, 0x41, 0xA7, 0xFF, 0x1C, 0x00, 0x01, 0x00, 0x19, 0x76, 0x43, 0x28, 0x7C, 0x01, 0x64, 0x26, 0x9D, 0xFE, 0x27, 0x0C, 0x4D, 0x40, 0xA6, 0xFF, 0x0E, 0x00, 0x01, 0x00, 0x1A, 0x42, 0x43, 0x28, 0x64, 0x02, 0xC9, 0x27, 0xB8, 0xB5, 0x6F, 0x18, 0xB1, 0x6C, 0xA7, 0xFF, 0x07, 0x00, 0x01, 0x00, 0x1B, 0x3D, 0x43, 0x28, 0x8C, 0x04, 0xA7, 0x27, 0x68, 0xD4, 0x2F, 0xC9, 0xDB, 0x45, 0xA7, 0xFF, 0x03, 0x00, 0x01, 0x00, 0x1C, 0x39, 0x43, 0x28, 0x6E, 0x00, 0x1D, 0x28, 0x29, 0xBD, 0xD9, 0x55, 0xA7, 0x6F, 0xA8, 0xFF, 0xA8, 0x01, 0x01, 0x00, 0x1D, 0x54, 0x43, 0x28, 0x4A, 0x01, 0x30, 0x26, 0x3A, 0x40, 0x1A, 0x8D, 0x70, 0x18, 0xA6, 0xFF, 0xD4, 0x00, 0x01, 0x00, 0x1E, 0x69, 0x43, 0x28, 0x8C, 0x02, 0xF8, 0x26, 0x14, 0x2F, 0x71, 0x0A, 0x64, 0x18, 0xA9, 0xFF, 0x6A, 0x00, 0x01, 0x00, 0x1F, 0x2F, 0x43, 0x28, 0x3B, 0x01, 0x03, 0x27, 0x12, 0xD4, 0x46, 0x9E, 0x6A, 0xEC, 0xA8, 0xFF, 0x35, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x23, 0x91, 0xBC, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x16, 0x00, 0x00, 0x9A, 0x00, 0x01, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x25, 0x48, 0xBC, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xF4, 0x00, 0x00, 0x3E, 0x02, 0x01, 0x00, 0x26, 0xF2, 0xBB, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x2D, 0x00, 0x00, 0xF4, 0x03, 0x01, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x28, 0x28, 0xBC, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3B, 0x00, 0x00, 0x5A, 0x03, 0x01, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x2A, 0x80, 0xBB, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x91, 0x63, 0x00, 0x00, 0xE1, 0x00, 0x01, 0x00, 0x2B, 0x55, 0xBC, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xAC, 0x00, 0x00, 0x69, 0x01, 0x01, 0x00, 0x2C, 0x48, 0xBD, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x42, 0x00, 0x00, 0x50, 0x01, 0x01, 0x00, 0x2D, 0xDF, 0xBB, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA4, 0x00, 0x00, 0xD9, 0x03, 0x01, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x2F, 0x2F, 0xBB, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xA7, 0x00, 0x00, 0x8E, 0x02, 0x01, 0x00, 0x30, 0x48, 0xBC, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x01, 0x00, 0x31, 0x97, 0xBB, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x5A, 0x00, 0x00, 0x07, 0x02, 0x01, 0x00, 0x32, 0x15, 0xBB, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7, 0xB3, 0x00, 0x00, 0x28, 0x01, 0x01, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 0x0B, 0xBF, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDB, 0x66, 0x00, 0x00, 0x87, 0x01, 0x02, 0x01, 0x41, 0x55, 0xBC, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x3B, 0x00, 0x00, 0x39, 0x06, 0x02, 0x01, 0x42, 0x72, 0xC1, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x4E, 0x00, 0x00, 0xE6, 0x01, 0x02, 0x01, 0x43, 0x3E, 0xBD, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB5, 0x71, 0x00, 0x00, 0x09, 0x06, 0x02, 0x01, 0x44, 0x82, 0xC0, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x29, 0x00, 0x00, 0x05, 0x06, 0x02, 0x01, 0x45, 0x21, 0xBA, 0x32, 0xCB, 0x02, 0x8E, 0x26, 0x60, 0xB6, 0x9D, 0xA6, 0x59, 0xF7, 0xEB, 0xFF, 0xF8, 0x01, 0x02, 0x01, 0x46, 0x97, 0xBA, 0x32, 0x16, 0x02, 0x3C, 0x24, 0x60, 0x73, 0xEC, 0x98, 0xD7, 0x48, 0xEA, 0xFF, 0x06, 0x06, 0x02, 0x01, 0x47, 0xCC, 0xBA, 0x32, 0x59, 0x01, 0x6F, 0x2A, 0x18, 0x22, 0x4C, 0x92, 0xD3, 0xA0, 0xE9, 0xFF, 0xF9, 0x01, 0x02, 0x01, 0x48, 0x4E, 0xBA, 0x32, 0x0A, 0x02, 0xC2, 0x26, 0x3D, 0xAB, 0x94, 0x9F, 0x13, 0xF9, 0xEC, 0xFF, 0x04, 0x07, 0x02, 0x01, 0x49, 0xFA, 0xBA, 0x32, 0xC3, 0x01, 0x4F, 0x24, 0xF8, 0x63, 0xAF, 0x97, 0x91, 0x48, 0xE9, 0xFF, 0xBE, 0x07, 0x02, 0x01, 0x4A, 0x21, 0x45, 0x29, 0x7E, 0x00, 0x5F, 0x28, 0x49, 0x76, 0xA9, 0xB1, 0xE1, 0x7C, 0xB6, 0xFF, 0x61, 0x00, 0x02, 0x01, 0x4B, 0x22, 0x45, 0x29, 0x43, 0x00, 0x51, 0x28, 0x24, 0x91, 0x66, 0xB6, 0x6C, 0x7C, 0xB5, 0xFF, 0x8E, 0x07, 0x02, 0x01, 0x4C, 0x52, 0xBA, 0x32, 0xFC, 0x00, 0xDB, 0x28, 0x13, 0x0C, 0x7E, 0x98, 0xF3, 0x9F, 0xE8, 0xFF, 0x82, 0x07, 0x02, 0x01, 0x4D, 0x22, 0x45, 0x29, 0x5F, 0x00, 0x3A, 0x27, 0x7D, 0xEF, 0x62, 0xE4, 0x18, 0xD1, 0xB7, 0xFF, 0x7F, 0x00, 0x02, 0x01, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x4F, 0xD3, 0xB9, 0x32, 0xED, 0x00, 0x2E, 0x27, 0x5F, 0xBD, 0x3F, 0x9B, 0xE0, 0xF6, 0xEC, 0xFF, 0x7E, 0x00, 0x02, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x52, 0x27, 0x45, 0x29, 0x42, 0x00, 0x56, 0x27, 0x59, 0x74, 0x9D, 0xCE, 0x0D, 0xD1, 0xB8, 0xFF, 0x3C, 0x00, 0x02, 0x01, 0x53, 0x29, 0x45, 0x29, 0x39, 0x00, 0x56, 0x27, 0xB8, 0x83, 0x3B, 0xDE, 0x14, 0xD1, 0xB8, 0xFF, 0xC1, 0x07, 0x02, 0x01, 0x54, 0x28, 0x45, 0x29, 0x36, 0x00, 0x4E, 0x27, 0xC5, 0x2B, 0x26, 0xD7, 0x26, 0xD1, 0xB7, 0xFF, 0x3F, 0x00, 0x02, 0x01, 0x55, 0x26, 0x45, 0x29, 0x2A, 0x00, 0x4E, 0x27, 0xE8, 0x34, 0xD6, 0xED, 0x25, 0xD1, 0xB7, 0xFF, 0xC0, 0x07, 0x02, 0x01, 0x56, 0x22, 0x45, 0x29, 0x04, 0x00, 0xB5, 0x26, 0xCA, 0x46, 0x5C, 0x07, 0xE2, 0x26, 0xB6, 0xFF, 0xEF, 0x07, 0x02, 0x01, 0x57, 0x23, 0x45, 0x29, 0x28, 0x00, 0xB5, 0x26, 0x3D, 0x77, 0xD4, 0x17, 0xE0, 0x26, 0xB5, 0xFF, 0xE3, 0x07, 0x02, 0x01, 0x58, 0x26, 0x45, 0x29, 0x1E, 0x00, 0xC2, 0x26, 0x89, 0x5B, 0xF8, 0x11, 0xD7, 0x25, 0xB6, 0xFF, 0x1E, 0x00, 0x02, 0x01, 0x59, 0x24, 0x45, 0x29, 0x31, 0x00, 0xC3, 0x26, 0xF2, 0x9D, 0x38, 0x0F, 0xDD, 0x25, 0xB6, 0xFF, 0xE0, 0x07, 0x02, 0x01, 0x5A, 0x24, 0x45, 0x29, 0x2E, 0x00, 0x65, 0x27, 0x28, 0xB3, 0xC6, 0x24, 0x98, 0x7C, 0xB2, 0xFF, 0x1F, 0x00, 0x02, 0x01, 0x5B, 0x23, 0x45, 0x29, 0x03, 0x00, 0x64, 0x27, 0x97, 0x46, 0x77, 0xB1, 0x97, 0x7C, 0xB2, 0xFF, 0x0C, 0x00, 0x02, 0x01, 0x5C, 0x24, 0x45, 0x29, 0x12, 0x00, 0x59, 0x27, 0x0B, 0x70, 0xFF, 0x27, 0x07, 0x7B, 0xB2, 0xFF, 0xF1, 0x07, 0x02, 0x01, 0x5D, 0x21, 0x45, 0x29, 0x24, 0x00, 0x58, 0x27, 0x48, 0xAD, 0x8E, 0x0B, 0x0D, 0x7B, 0xB1, 0xFF, 0x0F, 0x00, 0x02, 0x01, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x5F, 0x25, 0x45, 0x29, 0x2B, 0x00, 0x48, 0x27, 0xF1, 0xB5, 0xFB, 0xCC, 0xD7, 0xD0, 0xB7, 0xFF, 0xFD, 0x07, 0x02, 0x01, 0x60, 0x25, 0x45, 0x29, 0x21, 0x00, 0x48, 0x27, 0xE4, 0xE2, 0x34, 0xE0, 0xD5, 0xD0, 0xB7, 0xFF, 0x03, 0x00, 0x02, 0x01, 0x61, 0x27, 0x45, 0x29, 0x29, 0x00, 0x45, 0x27, 0x41, 0x25, 0x5A, 0x12, 0x2C, 0x7B, 0xB2, 0xFF, 0xFC, 0x07, 0x02, 0x01, 0x62, 0x23, 0x45, 0x29, 0x34, 0x00, 0x45, 0x27, 0x14, 0x70, 0x89, 0x08, 0x2B, 0x7B, 0xB3, 0xFF, 0xFE, 0x07, 0x02, 0x01, 0x63, 0x23, 0x45, 0x29, 0x26, 0x00, 0xC4, 0x26, 0x41, 0x2B, 0xDC, 0xC2, 0xC7, 0x26, 0xB6, 0xFF, 0x01, 0x00, 0x02, 0x01, 0x64, 0x23, 0x45, 0x29, 0x23, 0x00, 0xC4, 0x26, 0x0D, 0x4E, 0x9E, 0xE0, 0xC6, 0x26, 0xB7, 0xFF, 0xFF, 0x07, 0x02, 0x01, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; diff --git a/lr1110/lr1110/seeed/apps/examples/full_almanac_update/get_full_almanac.py b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/get_full_almanac.py new file mode 100644 index 000000000..75789f9be --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/get_full_almanac.py @@ -0,0 +1,92 @@ +""" +The Clear BSD License +Copyright Semtech Corporation 2021. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted (subject to the limitations in the disclaimer +below) provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the Semtech corporation nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY +THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "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 SEMTECH CORPORATION 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. +""" + +import base64 +import requests + +import sys +from argparse import ArgumentParser + + +def main(): + + # The file in which the almanac will be written to + filename_default = "almanac.h" + parser = ArgumentParser( + description="Companion software that generates almanac header file to be compiled for LR1110/LR1120 embedded full almanac update." + ) + parser.add_argument( + "mgs_token", help="MGS LoRa Cloud token to use to fetch the almanac" + ) + parser.add_argument( + "-f", + "--output_file", + help="file that will contain the results", + default=filename_default, + ) + args = parser.parse_args() + + mgs_token = args.mgs_token + filename = args.output_file + + # Build request URL + url = "https://mgs.loracloud.com/api/v1/almanac/full" + print("Requesting latest full almanac image available...") + + # HTTP request to MGS + my_header = {"Authorization": mgs_token} + res = requests.get(url, headers=my_header) + + if res.status_code != 200: + print("ERROR: failed to get almanac - " + str(res)) + sys.exit(2) + else: + print("Success") + + raw_bytes = bytes(base64.b64decode(res.json()["result"]["almanac_image"])) + + # Build the byte array containing the almanac to be written to LR11xx + my_almanac_in_hex = "static const uint8_t full_almanac[( LR11XX_GNSS_FULL_UPDATE_N_ALMANACS * LR11XX_GNSS_SINGLE_ALMANAC_WRITE_SIZE ) + 20] = { " + my_almanac_in_hex += ", ".join("0x{:02X}".format(byt) for byt in raw_bytes) + my_almanac_in_hex += " };" + + # Write C file to be included to the full_almanac_update application for writting to LR11xx + file_header = ( + "/* This file has been auto-generated by the get_full_almanac.py script */\n\n" + ) + file_header += '#include "lr11xx_gnss.h"\n\n' + with open(filename, "w") as f: + f.write(file_header + my_almanac_in_hex + "\n") + + print("Almanac image written to " + filename + " file") + + +if __name__ == "__main__": + main() diff --git a/lr1110/lr1110/seeed/apps/examples/full_almanac_update/main_full_almanac_update.c b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/main_full_almanac_update.c new file mode 100644 index 000000000..a48e2dfb6 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/full_almanac_update/main_full_almanac_update.c @@ -0,0 +1,257 @@ +/*! + * @file main_full_almanac_update.c + * + * @brief LR11xx full almanac update example + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include + +#include "smtc_board.h" +#include "smtc_hal.h" +#include "apps_modem_common.h" +#include "apps_modem_event.h" +#include "smtc_board_ralf.h" +#include "smtc_modem_utilities.h" + +#include "lr11xx_gnss_types.h" + +#include "almanac.h" /* Almanac image file, generated by the get_full_almanac.py python script provided */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +#define xstr( a ) str( a ) +#define str( a ) #a + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +#define OFFSET_BETWEEN_GPS_EPOCH_AND_UNIX_EPOCH 315964800 + +#define TIME_BUFFER_SIZE 80 + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief write full almanac image to LR11xx + */ +static bool almanac_update( const void* ral_context ); + +/*! + * @brief Reset event callback + * + * @param [in] reset_count reset counter from the modem + */ +static void on_modem_reset( uint16_t reset_count ); + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +/** + * @brief Main application entry point. + */ +int main( void ) +{ + static apps_modem_event_callback_t smtc_event_callback = { + .adr_mobile_to_static = NULL, + .alarm = NULL, + .almanac_update = NULL, + .down_data = NULL, + .join_fail = NULL, + .joined = NULL, + .link_status = NULL, + .mute = NULL, + .new_link_adr = NULL, + .reset = on_modem_reset, + .set_conf = NULL, + .stream_done = NULL, + .time_updated_alc_sync = NULL, + .tx_done = NULL, + .upload_done = NULL, + .user_radio_access = NULL, + .middleware_1 = NULL, + }; + + ralf_t* modem_radio = smtc_board_initialise_and_get_ralf( ); + + hal_mcu_disable_irq( ); + + hal_mcu_init( ); + smtc_board_init_periph( ); + + /* Init the Lora Basics Modem event callbacks */ + apps_modem_event_init( &smtc_event_callback ); + + smtc_modem_init( modem_radio, &apps_modem_event_process ); + + hal_mcu_enable_irq( ); + + HAL_DBG_TRACE_INFO( "===== LoRa Basics Modem full almanac update example =====\n\n" ); + apps_modem_common_display_lbm_version( ); + + /* Convert raw almanac date to epoch time */ + uint16_t almanac_date_raw = ( uint16_t )( ( full_almanac[2] << 8 ) | full_almanac[1] ); + time_t almanac_date = ( OFFSET_BETWEEN_GPS_EPOCH_AND_UNIX_EPOCH + 24 * 3600 * ( 2048 * 7 + almanac_date_raw ) ); + + /* Convert epoch time to human readbale format */ + char buf[TIME_BUFFER_SIZE]; + const struct tm* time = localtime( &almanac_date ); + strftime( buf, TIME_BUFFER_SIZE, "%a %Y-%m-%d %H:%M:%S %Z", time ); + HAL_DBG_TRACE_PRINTF( "Source almanac date: %s\n\n", buf ); + + /* Update full almanac */ + almanac_update( modem_radio->ral.context ); + + while( 1 ) + { + /* Execute modem runtime, this function must be called again in sleep_time_ms milliseconds or sooner. */ + uint32_t sleep_time_ms = smtc_modem_run_engine( ); + + hal_mcu_set_sleep_for_ms( sleep_time_ms ); + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/*! + * @brief LoRa Basics Modem event callbacks called by smtc_event_process function + */ + +static void on_modem_reset( uint16_t reset_count ) +{ + +} + +static bool get_almanac_crc( const void* ral_context, uint32_t* almanac_crc ) +{ + lr11xx_status_t err; + lr11xx_gnss_context_status_bytestream_t context_status_bytestream; + lr11xx_gnss_context_status_t context_status; + + err = lr11xx_gnss_get_context_status( ral_context, context_status_bytestream ); + if( err != LR11XX_STATUS_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to get gnss context status\n" ); + return false; + } + + err = lr11xx_gnss_parse_context_status_buffer( context_status_bytestream, &context_status ); + if( err != LR11XX_STATUS_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to parse gnss context status to get almanac status\n" ); + return false; + } + + *almanac_crc = context_status.global_almanac_crc; + + return true; +} + +static bool almanac_update( const void* ral_context ) +{ + uint32_t global_almanac_crc, local_almanac_crc; + local_almanac_crc = + ( full_almanac[6] << 24 ) + ( full_almanac[5] << 16 ) + ( full_almanac[4] << 8 ) + ( full_almanac[3] ); + + if( get_almanac_crc( ral_context, &global_almanac_crc ) == false ) + { + HAL_DBG_TRACE_ERROR( "Failed to get almanac CRC before update\n" ); + return false; + } + if( global_almanac_crc != local_almanac_crc ) + { + HAL_DBG_TRACE_INFO( "Local almanac doesn't match LR11XX almanac -> start update\n" ); + + /* Load almanac in flash */ + uint16_t almanac_idx = 0; + while( almanac_idx < sizeof( full_almanac ) ) + { + if( lr11xx_gnss_almanac_update( ral_context, full_almanac + almanac_idx, 1 ) != LR11XX_STATUS_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to update almanac\n" ); + return false; + } + almanac_idx += LR11XX_GNSS_SINGLE_ALMANAC_WRITE_SIZE; + } + + /* Check CRC again to confirm proper update */ + if( get_almanac_crc( ral_context, &global_almanac_crc ) == false ) + { + HAL_DBG_TRACE_ERROR( "Failed to get almanac CRC after update\n" ); + return false; + } + if( global_almanac_crc != local_almanac_crc ) + { + HAL_DBG_TRACE_ERROR( "Local almanac doesn't match LR11XX almanac -> update failed\n" ); + return false; + } + else + { + HAL_DBG_TRACE_INFO( "Almanac update succeeded\n" ); + } + } + else + { + HAL_DBG_TRACE_INFO( "Local almanac matches LR11XX almanac -> no update\n" ); + } + + return true; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/README.md b/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/README.md new file mode 100644 index 000000000..6cc22873f --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/README.md @@ -0,0 +1,96 @@ +# Geo-location Application Server + +This folder contains the files describing the node-red application server code. + +## Install & Configure + +This Application Server needs the following to be installed first: + +- Node-red +- semtech-wsp-apps/node-red-contrib-loracloud-utils + - and the dependencies of this package + +The install procedure for these is described [here](https://lora-developers.semtech.com/build/software/lora-basics/lora-basics-for-end-nodes/developer-walk-through/?url=application_server.html#setup). Be careful to **NOT** import the examples for End Nodes Demo flow. + +Instead of importing the End Nodes Demo flow, import the files *modem.json* and *geolocation.json* available in the *geolocation_application_server/* directory. + +The web page also indicates how to [configure the flow](https://lora-developers.semtech.com/build/software/lora-basics/lora-basics-for-end-nodes/developer-walk-through/?url=application_server.html#configure-the-flow) concerning the MQTT connections with LoRaWAN Network Servers and LoRa Cloud. +Follow the same procedure but for the flow distributed in this folder. + +This application server needs to use *LoRa Cloud Modem & Geolocation Services*. +Obtaining both *LoRa Cloud Modem & Geolocation Services* token and URL is explained in the web page here-before. + +### Maps + +The Application Server generates and display interactive maps that are made available at the following URLs: + +- `:/worldmap_wifi` : for Wi-Fi results +- `:/worldmap_gnss_single` : for GNSS Single-frame solving +- `:/worldmap_gnss_multiframe_grouping` : for GNSS Multi-frame solving with grouping accumulation + +The fields `` and `` are typically the one used to connect to the Node-red interface. On a classical local installation these are respectively `http://127.0.0.1` and `1880`. + +The meaning concerning the different maps is provided here-after. + +## Important note + +When executing the Application Server instance, it is important to ensure that there is no other Application Server is monitoring the same devices. This can be the case if the examples from *semtech-wsp-apps/node-red-contrib-loracloud-utils* have been installed previously. + +If this is the case, the flows corresponding to the examples from *semtech-wsp-apps/node-red-contrib-loracloud-utils* must be disabled (refer to the [doc](https://nodered.org/docs/user-guide/editor/workspace/flows#enabling-or-disabling-a-flow) for the procedure). + +## Usage + +Once deployed, the Application Server runs alone and does not need intervention. When the LoRa Network Server it is connected to receives an uplink, the Application Server receives a MQTT message. + +This reception of the MQTT message triggers a http request to the *LoRa Cloud Modem & Geolocation Services*. The *LoRa Cloud Modem & Geolocation Services* may respond with a downlink request that is propagated automatically by the Application Server to the LoRaWAN Network Server. + +The reception of the MQTT message also executes the Geolocation flow. If LoRaWAN port of the uplink corresponds to the geolocation application port (194 or 196) then the payload is interpreted as a geolocation scan result: + +- on port 196: Wi-Fi scan result; or +- on port 194: GNSS scan result; or +- unknown result. + +If the payload cannot be interpreted as Wi-Fi result of GNSS result, it is discarded. + +If the payload is interpreted successfully as a Wi-Fi or GNSS result, then these results are solved to obtain a location estimation of the device that is displayed on different maps. The details of the result obtaining is provided hereafter. + +### Wi-Fi result + +If the geolocation payload corresponds to a Wi-Fi result, then the *LoRa Cloud Modem & Geolocation Services* is contacted to solve the location of the device based on these Wi-Fi result. If the *LoRa Cloud Modem & Geolocation Services* responds with a valid location, then this location is displayed on the `/worldmap_wifi` map. + +When clicking on each location points, the MAC addresses scanned are provided. + +### GNSS result + +Every GNSS payload reception triggers the two following pipelines: + +- execution of a Single frame solving; and +- appending to the grouping queue. + +The GNSS single frame solving is executed by a call to the *LoRa Cloud Modem & Geolocation Services*. If the solve is successful, then the location is displayed on the map `/worldmap_gnss_single`. + +The grouping queue allows to store together scans that belongs to the same scan group (refer to readme of the GNSS geolocation demo for details). When a GNSS payload frame is appended to the grouping queue, the Application Server determines if it belongs to the same scan group as the GNSS payload already present in the queue. + +If the new GNSS payload is from a scan group different from the payload already in the queue, then the following is executed: + +1. a multi frame solving with the content of the grouping queue; +1. flushing of the grouping queue; and +1. appending of the new GNSS payload. + +In case of successful solve, the result is displayed on the map `/worldmap_gnss_multiframe_grouping`. + +### Outliers filtering + +The Application Server features a filter of outliers based on accuracy estimation returned by Wi-Fi or GNSS solvers. +The filter is a basic rejection that compares the accuracy estimation from solver to a configurable threshold. If the estimation is below the threshold, then the point is displayed on the corresponding map. Otherwise it is not displayed on the map and discarded . + +The thresholds are configurable through the node `Geolocation accuracy thresholds`. +The default values are (in meter): + +```json +{ + "wifi":100, + "gnss":100, + "multiframe":40 +} +``` diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/geolocation.json b/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/geolocation.json new file mode 100644 index 000000000..a7986915f --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/geolocation.json @@ -0,0 +1,1408 @@ +[ + { + "id": "a6d3ba07.ce0cc8", + "type": "tab", + "label": "Geolocation", + "disabled": false, + "info": "" + }, + { + "id": "b5df30f7.7e3348", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "9def56e2.ac2f4" + ], + "x": 315, + "y": 520, + "wires": [ + [ + "15b850cf.47cae7" + ] + ] + }, + { + "id": "15b850cf.47cae7", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Parse TLV output", + "func": "function pad(b) {\n const h = b.toString(16);\n return (h + \"\").length < 2 ? \"0\" + h : h;\n}\n\nfunction parse_wifi_legacy(devEui, bytes) {\n const mgs_query = {\n \"deveui\":devEui.match( /.{1,2}/g ).join( '-' ),\n \"uplink\":{\n \"msgtype\":\"wifi\",\n \"payload\": `01${bytes}`,\n }\n };\n\n bytes = Buffer.from(bytes, \"hex\");\n var addresses = [];\n for (var i = 0; i < bytes.length; i += 7) {\n var rssi_raw = bytes[i + 0];\n var bssid_slice = bytes.slice(i + 1, i + 7);\n var bssid = [];\n bssid_slice.forEach(byte => {\n bssid.push(pad(byte));\n });\n addresses.push({\n \"macAddress\": bssid.join(\":\"),\n \"signalStrength\": (rssi_raw > 127) ? rssi_raw - 256 : rssi_raw,\n });\n }\n \n const lorawan = [{\n \"gatewayId\": \"fake\",\n \"antennaId\": 0,\n \"rssi\": 0,\n \"snr\":0,\n \"antennaLocation\": {\n \"latitude\": 0.0,\n \"longitude\": 0.0,\n \"altitude\": 0.0\n }\n }];\n \n var gls_query = {\n \"lorawan\": lorawan,\n \"wifiAccessPoints\": addresses,\n };\n gls_query.wifiAccessPoints.forEach(ap => {\n delete ap.type;\n delete ap.channel;\n });\n \n \n return {\n \"type\": \"Wi-Fi_Legacy\",\n \"addresses\": addresses,\n \"raw\": bytes,\n \"gls_query\": gls_query,\n \"mgs_query\": mgs_query,\n };\n}\n\nfunction parse_wifi(devEui, bytes) {\n const mgs_query = {\n \"deveui\":devEui.match( /.{1,2}/g ).join( '-' ),\n \"uplink\":{\n \"msgtype\":\"wifi\",\n \"payload\": `01${bytes.slice(10)}`,\n }\n };\n\n const version = parseInt(bytes.slice(0, 2), 16);\n const timestamp = parseInt(bytes.slice(2, 10), 16);\n \n bytes = Buffer.from(bytes + 10, \"hex\");\n var addresses = [];\n for (var i = 5; i < bytes.length; i += 7) {\n var rssi_raw = bytes[i + 0];\n var bssid_slice = bytes.slice(i + 1, i + 7);\n var bssid = [];\n bssid_slice.forEach(byte => {\n bssid.push(pad(byte));\n });\n addresses.push({\n \"macAddress\": bssid.join(\":\"),\n \"signalStrength\": (rssi_raw > 127) ? rssi_raw - 256 : rssi_raw,\n });\n }\n \n const lorawan = [{\n \"gatewayId\": \"fake\",\n \"antennaId\": 0,\n \"rssi\": 0,\n \"snr\":0,\n \"antennaLocation\": {\n \"latitude\": 0.0,\n \"longitude\": 0.0,\n \"altitude\": 0.0\n }\n }];\n \n var gls_query = {\n \"lorawan\": lorawan,\n \"wifiAccessPoints\": addresses,\n };\n gls_query.wifiAccessPoints.forEach(ap => {\n delete ap.type;\n delete ap.channel;\n });\n \n return {\n \"type\": \"Wi-Fi\",\n \"addresses\": addresses,\n \"timestamp\": timestamp,\n \"version\": version,\n \"raw\": bytes,\n \"gls_query\": gls_query,\n \"mgs_query\": mgs_query,\n };\n}\n\nfunction parse_gnss(antenna, devEui, data) {\n const bit_mask_has_location = 1;\n const has_assisted_location = (parseInt(data.slice(6, 8), 16) & bit_mask_has_location) !== 0;\n var mgs_query = {\n \"deveui\":devEui.match( /.{1,2}/g ).join( \"-\" ),\n \"uplink\":{\n \"msgtype\":\"gnss\",\n \"payload\": data,\n }\n };\n if (has_assisted_location === false){\n mgs_query.uplink.gnss_assist_position = global.get(\"device_assistance_coordinates\")\n }\n \n return {\n \"type\": `GNSS - ${antenna} antenna`,\n \"data\": data,\n \"mgs_query\": mgs_query,\n };\n}\n\nfunction parse_signed_2_bytes_int(value) {\n var value_int = parseInt(value, 16);\n if ((value_int & 0x8000) > 0) {\n value_int = value_int - 0x10000;\n }\n return value_int;\n}\n\nfunction parse_acc(data) {\n const move_history = parseInt(data.slice(0, 2), 16);\n const acc = {\n 'x': parse_signed_2_bytes_int(data.slice(2, 6), 16),\n 'y': parse_signed_2_bytes_int(data.slice(6, 10), 16),\n 'z': parse_signed_2_bytes_int(data.slice(10, 14), 16),\n };\n const temperature = parse_signed_2_bytes_int(data.slice(14, 18), 16)/100;\n \n return {\n \"type\": \"acc\",\n \"move_history\": move_history,\n \"acc_mg\": acc,\n \"temperature_C\": temperature\n };\n}\n\nfunction parse_charge(data) {\n return {\n \"type\": \"charge\",\n \"charge_mAh\": parseInt(data, 16),\n };\n}\n\nfunction parse_voltage(data) {\n return {\n \"type\": \"voltage\",\n \"voltage_V\": parseInt(data, 16)/1000,\n };\n}\n\nfunction parse_counter(data) {\n \n const host_reset = parseInt(data.slice(0, 4), 16);\n const modem_reset = parseInt(data.slice(4, 8), 16);\n \n return {\n \"type\": \"counter\",\n \"host_reset\": host_reset,\n \"modem_reset\": modem_reset,\n };\n}\n\nfunction parse_sensors_full(data) {\n const version = parseInt(data.slice(0, 1), 16);\n const move_history = parseInt(data.slice(1, 2), 16);\n const temperature = parse_signed_2_bytes_int(data.slice(2, 6), 16)/100;\n const acc_charge = parse_signed_2_bytes_int(data.slice(6, 10), 16);\n const voltage = parse_signed_2_bytes_int(data.slice(10, 14), 16);\n \n return {\n \"type\": \"sensor_full\",\n\t\t\"version\": version,\n \"move_history\": move_history,\n \"temperature_C\": temperature,\n \"accumulated_charge\": acc_charge,\n \"voltage\": voltage\n };\n}\n\nfunction parse_sensors_basic(data) {\n const version = parseInt(data.slice(0, 1), 16);\n const move_history = parseInt(data.slice(1, 2), 16);\n \n return {\n \"type\": \"sensor_basic\",\n \"version\": version,\n \"move_history\": move_history,\n };\n}\n\nfunction parse_gnss_multiframe(antenna, devEui, data) {\n const mgs_query = {\n \"deveui\":devEui.match( /.{1,2}/g ).join( \"-\" ),\n \"uplink\":{\n \"msgtype\":\"gnss\",\n \"payload\": data.slice(4,data.length),\n }\n };\n\n bytes = Buffer.from(data.slice(0,5), \"hex\");\n\n const token = bytes[0];\n const gnss_scan_idx = bytes[1]>>3;\n const nb_satellites = bytes[1]-(gnss_scan_idx<<3);\n return {\n \"type\": `GNSS ${antenna}`,\n \"data\": data.slice(4,data.length),\n \"mgs_query\": mgs_query,\n \"nb_of_satellite\":nb_satellites,\n \"token\":token,\n \"gnss_scan_idx\": gnss_scan_idx,\n };\n}\n\nfunction create_message(payload, uplink, topic) {\n return {\n \"payload\": payload,\n \"uplink\": uplink,\n \"topic\": topic,\n };\n}\n\nmsg.payload.forEach( stream_array => {\n var index = 0;\n var stream = stream_array[1];\n \n while(index < stream.length) {\n var tag = parseInt(stream.slice(index, index + 2), 16);\n index += 2;\n var length = parseInt(stream.slice(index, index + 2), 16);\n index += 2;\n var value = stream.slice(index, index + length*2);\n index += length * 2;\n \n \n switch (tag) {\n case 5: // GNSS - No specific antenna\n node.send(\n create_message(\n parse_gnss(\"\", msg.uplink.devEui, value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 6:\n node.send(\n create_message(\n parse_gnss(\"PCB\", msg.uplink.devEui, value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 7:\n node.send(\n create_message(\n parse_gnss(\"Patch\", msg.uplink.devEui, value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 8:\n node.send(\n create_message(\n parse_wifi_legacy(msg.uplink.devEui, value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 9:\n node.send(\n create_message(\n parse_acc(value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 0x0A:\n node.send(\n create_message(\n parse_charge(value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 0x0B:\n node.send(\n create_message(\n parse_voltage(value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 0x0C:\n node.send(\n create_message(\n parse_counter(value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 0x0D:\n if (length == 7){\n message_data = parse_sensors_full(value)\n }else if (length == 1){\n message_data = parse_sensors_basic(value)\n }\n else{\n break;\n }\n \n node.send(\n create_message(\n message_data,\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 0x0E:\n node.send(\n create_message(\n parse_wifi(msg.uplink.devEui, value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n case 0x0F:\n // There are 2 node.send here: one to send a multi-frame geolocation request, and another one to\n // send a single frame request\n node.send(\n create_message(\n parse_gnss_multiframe(\"multi-frame\", msg.uplink.devEui, value),\n msg.uplink,\n msg.topic\n )\n );\n \n node.send(\n create_message(\n parse_gnss_multiframe(\"PCB\", msg.uplink.devEui, value),\n msg.uplink,\n msg.topic\n )\n );\n break;\n \n default:\n continue;\n }\n }\n \n});\n\n\nreturn null;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 790, + "y": 520, + "wires": [ + [ + "db465188.2f5bc8" + ] + ] + }, + { + "id": "db465188.2f5bc8", + "type": "switch", + "z": "a6d3ba07.ce0cc8", + "name": "Geolocation type switch", + "property": "payload.type", + "propertyType": "msg", + "rules": [ + { + "t": "cont", + "v": "Wi-Fi", + "vt": "str" + }, + { + "t": "cont", + "v": "multi-frame", + "vt": "str" + }, + { + "t": "cont", + "v": "GNSS", + "vt": "str" + }, + { + "t": "else" + } + ], + "checkall": "false", + "repair": false, + "outputs": 4, + "x": 1010, + "y": 520, + "wires": [ + [ + "7c34d551.6e1534" + ], + [ + "34ae840.3d3ac7c" + ], + [ + "7008ce0b.1ea08" + ], + [ + "e8d36585.c10dc" + ] + ], + "outputLabels": [ + "Wi-Fi", + "", + "GNSS", + "Tracker" + ], + "info": "Route data regarding their content:\n* Wi-Fi\n* GNSS\n* Tracker data (accelerometer/battery)" + }, + { + "id": "1c42eb9a.782f54", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Prepare Wi-Fi request", + "func": "const api = \"api/v1/device/send\";\n\nmsg.url = `${global.get('mgs_url')}/${api}`;\nmsg.method = \"POST\";\nmsg.headers = {\n 'Authorization': global.get('mgs_token'),\n 'Content-Type': 'application/json',\n};\n\nmsg.data = msg.payload;\nmsg.data.result_information = {\"addresses\": msg.payload.addresses};\nmsg.payload = msg.payload.mgs_query;\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 380, + "y": 880, + "wires": [ + [ + "bd05f0ca.793218" + ] + ] + }, + { + "id": "7c34d551.6e1534", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "938c6031.895b4", + "314f7c36.4d0234" + ], + "x": 1175, + "y": 460, + "wires": [] + }, + { + "id": "938c6031.895b4", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "To prepare Wi-Fi request", + "links": [ + "7c34d551.6e1534", + "bff742ee.2e8c6" + ], + "x": 235, + "y": 880, + "wires": [ + [ + "1c42eb9a.782f54" + ] + ] + }, + { + "id": "bdff813e.6ab108", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "To worldmap", + "links": [ + "c773054.b46e378", + "e9090994.7e4388", + "cbe1e11e.8196c8" + ], + "x": 1055, + "y": 1640, + "wires": [ + [ + "75a295f1.a72e14" + ] + ] + }, + { + "id": "ffae88d.7662478", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "To prepare GNSS request", + "links": [ + "7008ce0b.1ea08", + "b0fc727.554491" + ], + "x": 235, + "y": 1044, + "wires": [ + [ + "968ce3e5.62731" + ] + ] + }, + { + "id": "968ce3e5.62731", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Prepare GNSS request", + "func": "const api = \"api/v1/device/send\";\n\nmsg.url = `${global.get('mgs_url')}/${api}`;\nmsg.method = \"POST\";\nmsg.headers = {\n 'Authorization': global.get('mgs_token'),\n 'Content-Type': 'application/json',\n};\n\nmsg.data = msg.payload;\nmsg.data.result_information = {\"nav_message\": msg.payload.data};\nmsg.payload = msg.payload.mgs_query;\n\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 390, + "y": 1044, + "wires": [ + [ + "c256f252.b2388" + ] + ] + }, + { + "id": "c256f252.b2388", + "type": "http request", + "z": "a6d3ba07.ce0cc8", + "name": "", + "method": "use", + "ret": "txt", + "paytoqs": false, + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "authType": "", + "x": 530, + "y": 1004, + "wires": [ + [ + "1ce5be50.b2fa02" + ] + ] + }, + { + "id": "1ce5be50.b2fa02", + "type": "json", + "z": "a6d3ba07.ce0cc8", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 610, + "y": 1044, + "wires": [ + [ + "2941dfc3.725e7", + "f1248cd2.b8cd8" + ] + ] + }, + { + "id": "7008ce0b.1ea08", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "92c3efdc.34b2d8", + "ffae88d.7662478" + ], + "x": 1175, + "y": 540, + "wires": [] + }, + { + "id": "e8d36585.c10dc", + "type": "debug", + "z": "a6d3ba07.ce0cc8", + "name": "LR11XX Tracker environmental data", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "x": 1310, + "y": 580, + "wires": [] + }, + { + "id": "7cb4d562.f8b394", + "type": "comment", + "z": "a6d3ba07.ce0cc8", + "name": "License", + "info": "Revised BSD License\nCopyright Semtech Corporation 2020. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n* Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n* Neither the name of the Semtech corporation nor the\n names of its contributors may be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY DIRECT,\nINDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n", + "x": 1070, + "y": 120, + "wires": [] + }, + { + "id": "d1ca7a3d.1d2fd", + "type": "comment", + "z": "a6d3ba07.ce0cc8", + "name": "_________________________ CONFIG _________________________", + "info": "Demo AS code Modem-E v1.2.0\n\n---\n\nv1.2.0\n\n- Add GNSS multi-frame support and new worldmap\n\nv1.1.0\n\n- Modify TLV parser to add sensors and new Wi-Fi format\n\nv1.0.2\n\n- Rename LoRa Cloud Devices & Application Services\n\nv1.0.1\n\n- Fix bug with Wi-Fi http request output that is not converted to json\n- Rename the *Force port to downlink GNSS assisted position*\n", + "x": 600, + "y": 120, + "wires": [] + }, + { + "id": "30f1c7e.5bc97b8", + "type": "inject", + "z": "a6d3ba07.ce0cc8", + "name": "Assistance coordinates for GNSS autonomous scans", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "", + "topic": "", + "payload": "[0,0]", + "payloadType": "json", + "x": 440, + "y": 180, + "wires": [ + [ + "fed8dee.a5364a" + ] + ] + }, + { + "id": "fed8dee.a5364a", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Assistance coordinates", + "func": "global.set('device_assistance_coordinates', msg.payload);", + "outputs": 1, + "noerr": 0, + "x": 790, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "cc27d4fc.5a225", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "baa45081.65a3b" + ], + "x": 1255, + "y": 920, + "wires": [] + }, + { + "id": "75a295f1.a72e14", + "type": "worldmap", + "z": "a6d3ba07.ce0cc8", + "name": "", + "lat": "", + "lon": "", + "zoom": "", + "layer": "OSM", + "cluster": "", + "maxage": "", + "usermenu": "show", + "layers": "show", + "panit": "true", + "panlock": "false", + "zoomlock": "false", + "hiderightclick": "false", + "coords": "none", + "showgrid": "false", + "path": "/worldmap_gnss_single", + "mapname": "", + "mapurl": "", + "mapopt": "", + "mapwms": false, + "x": 1200, + "y": 1640, + "wires": [] + }, + { + "id": "2941dfc3.725e7", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Force port to downlink GNSS assistance position", + "func": "// This block exists because the LoRa Cloud Device & Application Services is returning value 0\n// for the dnlink.port field.\n// This is the trigger that this downlink has to be transported on application level\n\nfor (const eui in msg.payload.result){\n if ((msg.payload.result.dnlink !== null) && (msg.payload.result.dnlink.port === 0)){\n msg.payload.result.dnlink.port = global.get('port_gnss_push_solver_message_port');\n }\n}\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 890, + "y": 1044, + "wires": [ + [ + "ad44f212.7e2808" + ] + ] + }, + { + "id": "ad44f212.7e2808", + "type": "json", + "z": "a6d3ba07.ce0cc8", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 1150, + "y": 1044, + "wires": [ + [ + "cc27d4fc.5a225" + ] + ] + }, + { + "id": "263ba803.2ec99", + "type": "inject", + "z": "a6d3ba07.ce0cc8", + "name": "Port GNSS push solver message port", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "", + "topic": "", + "payload": "150", + "payloadType": "json", + "x": 490, + "y": 220, + "wires": [ + [ + "7ec5605b.ff7808" + ] + ] + }, + { + "id": "7ec5605b.ff7808", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Push GNSS solver message port", + "func": "global.set('port_gnss_push_solver_message_port', msg.payload);", + "outputs": 1, + "noerr": 0, + "x": 820, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "594ee252.63e204", + "type": "comment", + "z": "a6d3ba07.ce0cc8", + "name": "Link it to LoRa Cloud Device & Application Services parser input", + "info": "", + "x": 1130, + "y": 880, + "wires": [] + }, + { + "id": "539d298.46045d8", + "type": "comment", + "z": "a6d3ba07.ce0cc8", + "name": "Link it from stream of LoRa Cloud Device & Application Services parser", + "info": "", + "x": 270, + "y": 560, + "wires": [] + }, + { + "id": "b78fd0c5.473ab8", + "type": "debug", + "z": "a6d3ba07.ce0cc8", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 950, + "y": 1840, + "wires": [] + }, + { + "id": "f39f8dfd.8c82", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Clear FiFo of deveui", + "func": "var fifo_per_deveui = flow.get('multi_frame_fifos') || {};\n\nconst deveui = msg.payload.deveui;\n\n// 1. Clear the fifo corresponding to the deveui\nfifos_per_deveui[deveui] = {};\n\n// 2. Store it back\nflow.set('multi_frame_fifos', fifos_per_deveui);\n\nmsg.fifos_per_deveui = fifos_per_deveui;\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 600, + "y": 1840, + "wires": [ + [ + "b78fd0c5.473ab8" + ] + ] + }, + { + "id": "dd165b71.304e3", + "type": "inject", + "z": "a6d3ba07.ce0cc8", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"deveui\":\"\"}", + "payloadType": "json", + "x": 390, + "y": 1840, + "wires": [ + [ + "f39f8dfd.8c82" + ] + ] + }, + { + "id": "ec4015fc.a6665", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Clear all FiFo", + "func": "// 1. Clear all the fifo\nconst fifo_per_deveui = {};\n\n// 2. Store it back\nflow.set('multi_frame_fifos', fifo_per_deveui);\n\nmsg.fifo_per_deveui = fifo_per_deveui;\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 580, + "y": 1800, + "wires": [ + [ + "b78fd0c5.473ab8" + ] + ] + }, + { + "id": "cc0fc0e8.e2a3a8", + "type": "inject", + "z": "a6d3ba07.ce0cc8", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"deveui\":\"toto2\",\"nav_message\":\"titi2\"}", + "payloadType": "json", + "x": 410, + "y": 1800, + "wires": [ + [ + "ec4015fc.a6665" + ] + ] + }, + { + "id": "e06cba87.e529c8", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Prepare GNSS multi-frame request", + "func": "const api = \"api/v1/solve/gnss_lr1110_multiframe\";\n\nmsg.url = `${global.get('mgs_url')}/${api}`;\nmsg.method = \"POST\";\nmsg.headers = {\n 'Authorization': global.get('mgs_token'),\n 'Content-Type': 'application/json',\n};\n\nfifo = msg.payload;\nmsg.data.result_information = {\"nav_messages\": fifo.reduce((prev, cur)=>prev + '\\n' + cur) };\nconst captures = fifo.map(nav => ({\"payload\": nav}));\nmsg.payload = {\"captures\": captures};\nmsg.fifo = fifo;\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 420, + "y": 1360, + "wires": [ + [ + "ce56fcef.0a0108" + ] + ] + }, + { + "id": "9fb36d29.17598", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Extract deveui & nav message", + "func": "const deveui = msg.payload.mgs_query.deveui;\nconst nav_message = msg.payload.mgs_query.uplink.payload;\n\nmsg.data = msg.payload;\nmsg.payload = {\"deveui\": deveui, \"nav_message\": nav_message};\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 410, + "y": 1180, + "wires": [ + [ + "ba1af6b9.3f5d18" + ] + ] + }, + { + "id": "fa58b434.956dd8", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "To prepare GNSS multi-frame request", + "links": [ + "2f9abd97.246f1a", + "b0fc727.554491", + "34ae840.3d3ac7c" + ], + "x": 235, + "y": 1180, + "wires": [ + [ + "9fb36d29.17598" + ] + ] + }, + { + "id": "ce56fcef.0a0108", + "type": "http request", + "z": "a6d3ba07.ce0cc8", + "name": "", + "method": "use", + "ret": "txt", + "paytoqs": false, + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "authType": "", + "x": 670, + "y": 1360, + "wires": [ + [ + "d8bd6131.a9e2f8" + ] + ] + }, + { + "id": "d8bd6131.a9e2f8", + "type": "json", + "z": "a6d3ba07.ce0cc8", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 810, + "y": 1360, + "wires": [ + [ + "32cad74d.8540d8" + ] + ] + }, + { + "id": "6060854e.71fd5c", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Create worldmap object", + "func": "/*\nmsg.payload = {\"result\":{\n \"llh\": [45.2, 5.7, 200],\n},\"warnings\":[],\"errors\":[\"GNSS solver error [1]: Not enough viewable satellites\"]}\n*/\n\nif (msg.payload.result === null) return;\n\nvar counters = context.get(\"counters\") || {};\n\nconst device_name = msg.topic.split('/')[2];\nconst devEui = msg.uplink.devEui.match( /.{1,2}/g ).join( '-' );\nconst location_result = msg.location_result;\nconst nav_messages = msg.fifo;\nconst type = msg.data.type;\nvar counters_for_deveui = counters[devEui] || {};\nvar counter = counters_for_deveui[type] || 0;\n\nmsg.payload = {\n \"name\": `${devEui} (${counter})`,\n \"lat\": location_result[0],\n \"lon\": location_result[1],\n \"data_type\": msg.data.type\n}\n\n// Get optional information that may have been set and add it to the information to be drawn on the map\nconst information = msg.data.result_information || {};\nfor (var info_key in information){\n msg.payload[info_key] = information[info_key];\n}\n\ncounters_for_deveui[type] = counter + 1;\ncounters[devEui] = counters_for_deveui;\ncontext.set(\"counters\", counters);\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 390, + "y": 1604, + "wires": [ + [ + "e77c5112.9011e" + ] + ] + }, + { + "id": "e77c5112.9011e", + "type": "switch", + "z": "a6d3ba07.ce0cc8", + "name": "Switch Type", + "property": "data.type", + "propertyType": "msg", + "rules": [ + { + "t": "cont", + "v": "grouping", + "vt": "str" + }, + { + "t": "eq", + "v": "Wi-Fi", + "vt": "str" + }, + { + "t": "eq", + "v": "Wi-Fi_Legacy", + "vt": "str" + }, + { + "t": "cont", + "v": "GNSS", + "vt": "str" + }, + { + "t": "else" + } + ], + "checkall": "false", + "repair": false, + "outputs": 5, + "x": 610, + "y": 1604, + "wires": [ + [ + "ba205aa5.f51ff" + ], + [ + "ea35e7c4.1bb8d" + ], + [ + "ea35e7c4.1bb8d" + ], + [ + "cbe1e11e.8196c8" + ], + [ + "715fc868.d50768" + ] + ] + }, + { + "id": "715fc868.d50768", + "type": "debug", + "z": "a6d3ba07.ce0cc8", + "name": "Unknown data type", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 810, + "y": 1680, + "wires": [] + }, + { + "id": "f1248cd2.b8cd8", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Extract location result Wi-Fi & GNSS", + "func": "try{\n msg.location_result = msg.payload.result.position_solution.llh\n // return [msg, null];\n} catch(err) {\n msg.payload = msg.payload.result.log_messages || msg.payload.errors;\n return [null, msg];\n}\n\nconst algorithm_type = msg.payload.result.position_solution.algorithm_type || \"gnss\";\nconst accuracy = msg.payload.result.position_solution.accuracy;\nconst accuracy_threshold = flow.get(\"accuracy_thresholds\")[algorithm_type];\n\nif(accuracy <= accuracy_threshold){\n return [msg, null];\n}", + "outputs": 2, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 830, + "y": 964, + "wires": [ + [ + "4d6c2fc2.dd2af8" + ], + [ + "72ce3969.2ecc8" + ] + ] + }, + { + "id": "32cad74d.8540d8", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Extract location result GNSS multi-frame", + "func": "try{\n msg.location_result = msg.payload.result.llh\n} catch(err) {\n msg.payload = msg.payload.result.errors || msg.payload.result.warnings;\n return [null, msg];\n}\n\n// Compare accuracy versus threshold\nconst accuracy = msg.payload.result.accuracy;\nconst accuracy_threshold = flow.get(\"accuracy_thresholds\")[\"multiframe\"];\nif(accuracy <= accuracy_threshold){\n return [msg, null];\n}", + "outputs": 2, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 1040, + "y": 1360, + "wires": [ + [ + "dd2c37b3.db764" + ], + [ + "fc0b7618.9b71f" + ] + ] + }, + { + "id": "cbe1e11e.8196c8", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "bdff813e.6ab108" + ], + "x": 735, + "y": 1640, + "wires": [] + }, + { + "id": "e6d22150.c56f6", + "type": "debug", + "z": "a6d3ba07.ce0cc8", + "name": "Prepare map error", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 1190, + "y": 1680, + "wires": [] + }, + { + "id": "1fc9d25.038dbae", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "To prepare GNSS request", + "links": [ + "72ce3969.2ecc8", + "fc0b7618.9b71f" + ], + "x": 1055, + "y": 1680, + "wires": [ + [ + "e6d22150.c56f6" + ] + ] + }, + { + "id": "72ce3969.2ecc8", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "1fc9d25.038dbae" + ], + "x": 1015, + "y": 984, + "wires": [] + }, + { + "id": "fc0b7618.9b71f", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "1fc9d25.038dbae" + ], + "x": 1235, + "y": 1380, + "wires": [] + }, + { + "id": "dd2c37b3.db764", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "16a44c08.f1b344" + ], + "x": 1235, + "y": 1340, + "wires": [] + }, + { + "id": "16a44c08.f1b344", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "To worldmap", + "links": [ + "c5e3b03d.b79e5", + "1bd9f271.c3bebe", + "dd2c37b3.db764", + "4d6c2fc2.dd2af8" + ], + "x": 235, + "y": 1604, + "wires": [ + [ + "6060854e.71fd5c" + ] + ] + }, + { + "id": "4d6c2fc2.dd2af8", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "16a44c08.f1b344" + ], + "x": 1015, + "y": 944, + "wires": [] + }, + { + "id": "384539b7.56d456", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "Geolocation Payload Handler", + "links": [ + "eb8810a.3bed6f" + ], + "x": 235, + "y": 680, + "wires": [ + [ + "30030e88.ad0e0a" + ] + ] + }, + { + "id": "89454543.cd94c8", + "type": "comment", + "z": "a6d3ba07.ce0cc8", + "name": "Geolocation Payload Handler", + "info": "", + "x": 200, + "y": 740, + "wires": [] + }, + { + "id": "30030e88.ad0e0a", + "type": "switch", + "z": "a6d3ba07.ce0cc8", + "name": "Switch uplink port", + "property": "uplink.port", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "193", + "vt": "str" + }, + { + "t": "eq", + "v": "192", + "vt": "str" + }, + { + "t": "eq", + "v": "197", + "vt": "str" + }, + { + "t": "eq", + "v": "199", + "vt": "str" + }, + { + "t": "else" + } + ], + "checkall": "true", + "repair": false, + "outputs": 5, + "x": 360, + "y": 680, + "wires": [ + [ + "1c57d7f5.cc30e8" + ], + [ + "f0a0bb0d.4f45b8" + ], + [ + "d4b76f1.e402c1" + ], + [ + "38b6e792.e65cd" + ], + [ + "158e5cf.103cca3" + ] + ] + }, + { + "id": "d4b76f1.e402c1", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Direct Wi-Fi", + "func": "function pad(b) {\n const h = b.toString(16);\n return (h + \"\").length < 2 ? \"0\" + h : h;\n}\n\nfunction parse_wifi(devEui, bytes) {\n const mgs_query = {\n \"deveui\":devEui.match( /.{1,2}/g ).join( '-' ),\n \"uplink\":{\n \"msgtype\":\"wifi\",\n \"payload\": `${bytes}`,\n }\n };\n \n bytes = Buffer.from(bytes, \"hex\");\n var addresses = [];\n for (var i = 0; i < bytes.length; i += 6) {\n var bssid_slice = bytes.slice(i + 0, i + 6);\n var bssid = [];\n bssid_slice.forEach(byte => {\n bssid.push(pad(byte));\n });\n addresses.push({\n \"macAddress\": bssid.join(\":\")\n });\n }\n \n const lorawan = [{\n \"gatewayId\": \"fake\",\n \"antennaId\": 0,\n \"rssi\": 0,\n \"snr\":0,\n \"antennaLocation\": {\n \"latitude\": 0.0,\n \"longitude\": 0.0,\n \"altitude\": 0.0\n }\n }];\n \n return {\n \"type\": \"Wi-Fi\",\n \"addresses\": addresses,\n \"raw\": bytes,\n \"mgs_query\": mgs_query,\n };\n}\n\nfunction create_message(payload, uplink, topic) {\n return {\n \"payload\": payload,\n \"uplink\": uplink,\n \"topic\": topic,\n };\n}\n\n\nnode.send(\n create_message(\n parse_wifi(msg.uplink.devEui, msg.payload),\n msg.uplink,\n msg.topic\n )\n);", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 610, + "y": 680, + "wires": [ + [ + "db465188.2f5bc8" + ] + ] + }, + { + "id": "1c57d7f5.cc30e8", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Direct Sensors", + "func": "function parse_sensors_full(data) {\n const temperature = parse_signed_2_bytes_int(data.slice(0, 4), 16)/100;\n const acc_charge = parse_signed_2_bytes_int(data.slice(4, 8), 16);\n const voltage = parse_signed_2_bytes_int(data.slice(8, 12), 16);\n \n return {\n \"type\": \"sensor_full\",\n \"temperature_C\": temperature,\n \"accumulated_charge\": acc_charge,\n \"voltage\": voltage\n };\n}\n\nfunction parse_signed_2_bytes_int(value) {\n var value_int = parseInt(value, 16);\n if ((value_int & 0x8000) > 0) {\n value_int = value_int - 0x10000;\n }\n return value_int;\n}\n\nfunction create_message(payload, uplink, topic) {\n return {\n \"payload\": payload,\n \"uplink\": uplink,\n \"topic\": topic,\n };\n}\n\n\nnode.send(\n create_message(\n parse_sensors_full(msg.payload),\n msg.uplink,\n msg.topic\n )\n );", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 620, + "y": 600, + "wires": [ + [ + "db465188.2f5bc8" + ] + ] + }, + { + "id": "f0a0bb0d.4f45b8", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Direct GNSS", + "func": "function parse_gnss_multiframe(antenna, devEui, data) {\n const mgs_query = {\n \"deveui\":devEui.match( /.{1,2}/g ).join( \"-\" ),\n \"uplink\":{\n \"msgtype\":\"gnss\",\n \"payload\": data.slice(2,data.length),\n }\n };\n\n bytes = Buffer.from(data.slice(0,2), \"hex\");\n\n const token = bytes[0] & 0x7F;\n const is_last = ((bytes[0] & 0x80 ) == 0x80)\n return {\n \"type\": `GNSS ${antenna}`,\n \"data\": data.slice(2,data.length),\n \"mgs_query\": mgs_query,\n \"token\":token,\n \"is_last\": is_last,\n };\n}\n\nfunction create_message(payload, uplink, topic) {\n return {\n \"payload\": payload,\n \"uplink\": uplink,\n \"topic\": topic,\n };\n}\n\n\n// There are 2 node.send here: one to send a multi-frame geolocation request, and another one to\n// send a single frame request\nnode.send(\n create_message(\n parse_gnss_multiframe(\"multi-frame\", msg.uplink.devEui, msg.payload),\n msg.uplink,\n msg.topic\n )\n );\n \nnode.send(\n create_message(\n parse_gnss_multiframe(\"PCB\", msg.uplink.devEui, msg.payload),\n msg.uplink,\n msg.topic\n )\n );", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 610, + "y": 640, + "wires": [ + [ + "db465188.2f5bc8" + ] + ] + }, + { + "id": "158e5cf.103cca3", + "type": "debug", + "z": "a6d3ba07.ce0cc8", + "name": "Unknown port", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 600, + "y": 760, + "wires": [] + }, + { + "id": "8a60686c.13e7a8", + "type": "inject", + "z": "a6d3ba07.ce0cc8", + "name": "Geolocation accuracy thresholds", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"wifi\":100,\"gnss\":100,\"multiframe\":40}", + "payloadType": "json", + "x": 500, + "y": 260, + "wires": [ + [ + "b2563636.2b0c58" + ] + ] + }, + { + "id": "b2563636.2b0c58", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Store geolocation accuracy thresholds", + "func": "flow.set(\"accuracy_thresholds\", msg.payload);\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 830, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "cdc53586.923428", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "To worldmap", + "links": [ + "c773054.b46e378", + "e9090994.7e4388", + "ea35e7c4.1bb8d" + ], + "x": 1055, + "y": 1600, + "wires": [ + [ + "7dd12f31.0d33e8" + ] + ] + }, + { + "id": "7dd12f31.0d33e8", + "type": "worldmap", + "z": "a6d3ba07.ce0cc8", + "name": "", + "lat": "", + "lon": "", + "zoom": "", + "layer": "OSM", + "cluster": "", + "maxage": "", + "usermenu": "show", + "layers": "show", + "panit": "true", + "panlock": "false", + "zoomlock": "false", + "hiderightclick": "false", + "coords": "none", + "showgrid": "false", + "path": "/worldmap_wifi", + "mapname": "", + "mapurl": "", + "mapopt": "", + "mapwms": false, + "x": 1180, + "y": 1600, + "wires": [] + }, + { + "id": "ea35e7c4.1bb8d", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "cdc53586.923428" + ], + "x": 735, + "y": 1600, + "wires": [] + }, + { + "id": "34ae840.3d3ac7c", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "fa58b434.956dd8" + ], + "x": 1175, + "y": 500, + "wires": [] + }, + { + "id": "38b6e792.e65cd", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Fake stream output", + "func": "// The TLV parser was initialy built on top of the stream feature.\n// However the TLV parsing can now also be used on top of direct uplinks.\n// So this node is here to make the payload of an uplink msg look like\n// a stream msg.\n\n// A stream msg payload is a list of tuple, where the first element is the stream index\n// and the second element is the corresponding payload.\n\nmsg.payload = [[0, msg.payload]]\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 610, + "y": 720, + "wires": [ + [ + "15b850cf.47cae7" + ] + ] + }, + { + "id": "97b6fb36.e73d38", + "type": "comment", + "z": "a6d3ba07.ce0cc8", + "name": "Grouping Multi-Frame", + "info": "This is an implementation of a block behaving multiframe. Here the token scan group of the GNSS requests is taken into account so that only the GNSS request of the same group are used per multi-frame soving request.", + "x": 680, + "y": 1140, + "wires": [] + }, + { + "id": "ba1af6b9.3f5d18", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Push to grouping multi-frame FIFO, return content", + "func": "var fifos_per_deveui = flow.get('multi_frame_fifos') || {};\n\nconst deveui = msg.payload.deveui;\nconst nav_message = msg.payload.nav_message;\nconst actual_token = msg.data.token;\n\n// 1. Get the fifos corresponding to the deveui\nvar fifos = fifos_per_deveui[deveui] || {};\nvar grouping_fifo = fifos.grouping || {'token': actual_token, 'fifo': []};\nconst stored_token = grouping_fifo.token;\n\n// Cases to handle:\n// 1. Token still the same, not last NAV\n// -> Just store new NAV\n// 2. Token has changed, not the last NAV\n// -> Send the previous group, start new one with NAV just received\n// 3. Token the same, last NAV\n// -> Store the just received NAV, send the current group\n// 4. Token has changed, last NAV\n// -> Send the previous group\n// -> Create a new group with NAV just received and send it (will\n// generate a single-multi-capture group which may be considered\n// an error by loracloud)\n\nconst is_token_the_same = stored_token == actual_token;\nconst is_last = msg.data.is_last;\n\nif(is_token_the_same === true){\n // Token is still the same: the nav message is just pushed to storage\n grouping_fifo.fifo.push(nav_message);\n}\nelse{\n // The token just changed: the request for solve is prepared, and the new nav and token\n // are stored to prepare the next group\n msg.payload = grouping_fifo.fifo;\n msg.data.type = \"GNSS grouping multi-frame\";\n grouping_fifo.token = actual_token;\n grouping_fifo.fifo = [nav_message];\n node.send(msg);\n}\n\nif(is_last === true){\n msg.payload = grouping_fifo.fifo;\n msg.data.type = \"GNSS grouping multi-frame\";\n delete fifos.grouping;\n fifos_per_deveui[deveui] = fifos;\n node.send(msg);\n}\nelse{\n fifos.grouping = grouping_fifo;\n fifos_per_deveui[deveui] = fifos;\n}\n\nflow.set('multi_frame_fifos', fifos_per_deveui)", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 770, + "y": 1180, + "wires": [ + [ + "ba95ca19.b0933" + ] + ] + }, + { + "id": "72b1fff4.74e67", + "type": "worldmap", + "z": "a6d3ba07.ce0cc8", + "name": "", + "lat": "", + "lon": "", + "zoom": "", + "layer": "OSM", + "cluster": "", + "maxage": "", + "usermenu": "show", + "layers": "show", + "panit": "true", + "panlock": "false", + "zoomlock": "false", + "hiderightclick": "false", + "coords": "none", + "showgrid": "false", + "path": "/worldmap_gnss_multiframe_grouping", + "mapname": "", + "mapurl": "", + "mapopt": "", + "mapwms": false, + "x": 1250, + "y": 1560, + "wires": [] + }, + { + "id": "25a44871.82969", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "To worldmap", + "links": [ + "ba205aa5.f51ff" + ], + "x": 1055, + "y": 1560, + "wires": [ + [ + "72b1fff4.74e67" + ] + ] + }, + { + "id": "ba205aa5.f51ff", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "25a44871.82969" + ], + "x": 735, + "y": 1560, + "wires": [] + }, + { + "id": "cbce0613.afff5", + "type": "function", + "z": "a6d3ba07.ce0cc8", + "name": "Display all fifos", + "func": "const fifos_per_deveui = flow.get('multi_frame_fifos') || {};\nmsg.payload = fifos_per_deveui;\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 580, + "y": 1960, + "wires": [ + [ + "9e0de6a8.bcf608" + ] + ] + }, + { + "id": "65f06a4b.dcb53c", + "type": "inject", + "z": "a6d3ba07.ce0cc8", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 400, + "y": 1960, + "wires": [ + [ + "cbce0613.afff5" + ] + ] + }, + { + "id": "9e0de6a8.bcf608", + "type": "debug", + "z": "a6d3ba07.ce0cc8", + "name": "All FiFos", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 740, + "y": 1960, + "wires": [] + }, + { + "id": "4e0d6df9.8e5474", + "type": "link in", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "ba95ca19.b0933" + ], + "x": 235, + "y": 1360, + "wires": [ + [ + "e06cba87.e529c8" + ] + ] + }, + { + "id": "ba95ca19.b0933", + "type": "link out", + "z": "a6d3ba07.ce0cc8", + "name": "", + "links": [ + "4e0d6df9.8e5474" + ], + "x": 995, + "y": 1180, + "wires": [] + }, + { + "id": "66210cb6.52ad5c", + "type": "json", + "z": "a6d3ba07.ce0cc8", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 630, + "y": 880, + "wires": [ + [ + "f1248cd2.b8cd8" + ] + ] + }, + { + "id": "bd05f0ca.793218", + "type": "http request", + "z": "a6d3ba07.ce0cc8", + "name": "", + "method": "use", + "ret": "txt", + "paytoqs": "ignore", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "authType": "", + "x": 530, + "y": 920, + "wires": [ + [ + "66210cb6.52ad5c", + "cc27d4fc.5a225" + ] + ] + } +] diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/modem.json b/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/modem.json new file mode 100644 index 000000000..f10e61697 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_application_server/modem.json @@ -0,0 +1,1224 @@ +[ + { + "id": "348ffbdd.1c23a4", + "type": "tab", + "label": "Modem", + "disabled": false, + "info": "" + }, + { + "id": "4204a1dc.b5cfe", + "type": "http request", + "z": "348ffbdd.1c23a4", + "name": "Query LoRa Cloud Device & Application Services", + "method": "use", + "ret": "txt", + "paytoqs": false, + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "authType": "", + "x": 750, + "y": 1100, + "wires": [ + [ + "f939b303.5b69e" + ] + ] + }, + { + "id": "f939b303.5b69e", + "type": "link out", + "z": "348ffbdd.1c23a4", + "name": "", + "links": [ + "baa45081.65a3b", + "1caa2da1.b4e51a" + ], + "x": 975, + "y": 1100, + "wires": [] + }, + { + "id": "baa45081.65a3b", + "type": "link in", + "z": "348ffbdd.1c23a4", + "name": "To LoRa Cloud Device & Application Services parser", + "links": [ + "f939b303.5b69e", + "ed4fc504.88425" + ], + "x": 235, + "y": 1260, + "wires": [ + [ + "95f885b1.0f631" + ] + ] + }, + { + "id": "415ce353.9b39cc", + "type": "comment", + "z": "348ffbdd.1c23a4", + "name": "_________________________ Device & Application Services _________________________", + "info": "", + "x": 750, + "y": 1040, + "wires": [] + }, + { + "id": "4ce62231.050b74", + "type": "comment", + "z": "348ffbdd.1c23a4", + "name": "_________________________ Network Server Connexion _________________________", + "info": "", + "x": 700, + "y": 420, + "wires": [] + }, + { + "id": "f9439f8b.fc42b8", + "type": "comment", + "z": "348ffbdd.1c23a4", + "name": "_________________________ CONFIG _________________________", + "info": "Demo AS code v1.4.0\n\n---\n\nv1.4.0\nAdd LoRa Cloud Geolocation token and URL parameters\n\nv1.3.0\nAdd Network Server storage\nFix network server naming\n\nRename LoRa Cloud Devices & Application Services\n\nv1.2.0\nAdd Downlink Generator block\nChange DAS API usage from /uplink/send to /device/send\n\nv1.1.0\nUse Wi-Fi DAS\n\nv1.0.1\nUpdate license\n\nv1.0.0\nInital release", + "x": 660, + "y": 100, + "wires": [] + }, + { + "id": "d5a89476.fef33", + "type": "comment", + "z": "348ffbdd.1c23a4", + "name": "License", + "info": "Revised BSD License\nCopyright Semtech Corporation 2020. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n* Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n* Neither the name of the Semtech corporation nor the\n names of its contributors may be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY DIRECT,\nINDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n", + "x": 1170, + "y": 120, + "wires": [] + }, + { + "id": "4a3245a6.fb9a04", + "type": "mqtt in", + "z": "348ffbdd.1c23a4", + "name": "TTN v2 - Uplinks", + "topic": "+/devices/+/up", + "qos": "2", + "datatype": "auto", + "broker": "", + "x": 360, + "y": 620, + "wires": [ + [ + "2fa445da.30cd7a" + ] + ] + }, + { + "id": "2e0871f.4d0780e", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "LoRa Cloud Device & Application Services - Error", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "x": 910, + "y": 1340, + "wires": [] + }, + { + "id": "ebc39354.f6b378", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "LoRa Cloud Device & Application Services - Data", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "x": 910, + "y": 1180, + "wires": [] + }, + { + "id": "786390a0.66b1b", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "LoRa Cloud Device & Application Services - Stream", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "x": 920, + "y": 1260, + "wires": [] + }, + { + "id": "f5c2dfd.d5c5ca", + "type": "mqtt out", + "z": "348ffbdd.1c23a4", + "name": "", + "topic": "", + "qos": "", + "retain": "", + "broker": "", + "x": 830, + "y": 1760, + "wires": [] + }, + { + "id": "5440c72.95d3438", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Get devices information from LoRa Cloud Device & Application Services", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 320, + "y": 2420, + "wires": [ + [ + "daf9378e.7b287" + ] + ] + }, + { + "id": "daf9378e.7b287", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "/api/v1/device/info", + "func": "var api = 'api/v1/device/info';\nmsg.method = 'post';\n\nmsg.url = `${global.get('mgs_url')}/${api}`;\nmsg.headers = {'Authorization': global.get('mgs_token')};\n\nmsg.payload = {\n 'deveuis': global.get('deveuis'),\n}\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 690, + "y": 2420, + "wires": [ + [ + "9a5da7e1.0d1a7" + ] + ] + }, + { + "id": "af7b981e.495a68", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "device/info", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "x": 1170, + "y": 2420, + "wires": [] + }, + { + "id": "9a5da7e1.0d1a7", + "type": "http request", + "z": "348ffbdd.1c23a4", + "name": "", + "method": "use", + "ret": "txt", + "paytoqs": false, + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "authType": "", + "x": 890, + "y": 2420, + "wires": [ + [ + "f22b15f0.c2687" + ] + ] + }, + { + "id": "f22b15f0.c2687", + "type": "json", + "z": "348ffbdd.1c23a4", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 1030, + "y": 2420, + "wires": [ + [ + "af7b981e.495a68" + ] + ] + }, + { + "id": "4b766149.e23ab8", + "type": "comment", + "z": "348ffbdd.1c23a4", + "name": "_________________________ Direct API calls _________________________", + "info": "", + "x": 700, + "y": 2360, + "wires": [] + }, + { + "id": "92be7a1e.ee12b", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Send reset request", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "[{\"type\":\"RESET\",\"param\":3}]", + "payloadType": "json", + "x": 350, + "y": 2480, + "wires": [ + [ + "a69dcdc5.c107a" + ] + ] + }, + { + "id": "a69dcdc5.c107a", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "/api/v1/requests/set", + "func": "var api = 'api/v1/requests/set';\nmsg.method = 'post';\n\nmsg.url = `${global.get('mgs_url')}/${api}`;\nmsg.headers = {'Authorization': global.get('mgs_token')};\n\nmsg.payload = {\n 'deveuis': global.get('deveuis'),\n 'requests': msg.payload,\n 'upcount': 1,\n 'updelay': 20,\n};\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 700, + "y": 2540, + "wires": [ + [ + "430c9a53.a632ec" + ] + ] + }, + { + "id": "c3f6f67c.c2aa6", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "requests/add", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload.result", + "targetType": "msg", + "x": 1170, + "y": 2540, + "wires": [] + }, + { + "id": "430c9a53.a632ec", + "type": "http request", + "z": "348ffbdd.1c23a4", + "name": "", + "method": "use", + "ret": "txt", + "paytoqs": false, + "url": "", + "tls": "", + "proxy": "", + "authType": "basic", + "x": 890, + "y": 2540, + "wires": [ + [ + "c22e4dc4.b50e48" + ] + ] + }, + { + "id": "c22e4dc4.b50e48", + "type": "json", + "z": "348ffbdd.1c23a4", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 1030, + "y": 2540, + "wires": [ + [ + "c3f6f67c.c2aa6" + ] + ] + }, + { + "id": "d4de71dd.89ef", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Send rejoin request", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "[{\"type\":\"REJOIN\"}]", + "payloadType": "json", + "x": 350, + "y": 2520, + "wires": [ + [ + "a69dcdc5.c107a" + ] + ] + }, + { + "id": "31dd0d31.75c0aa", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Update all info from modem", + "props": [ + { + "p": "payload", + "v": "[{\"type\":\"GETINFO\",\"param\":[\"appstatus\",\"uptime\",\"signal\",\"session\",\"voltage\",\"interval\",\"rxtime\",\"firmware\",\"region\",\"temp\",\"joineui\",\"chipeui\",\"adrmode\",\"charge\",\"status\",\"rstcount\",\"deveui\"]}]", + "vt": "json" + }, + { + "p": "topic", + "v": "", + "vt": "string" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "[{\"type\":\"GETINFO\",\"param\":[\"appstatus\",\"uptime\",\"signal\",\"session\",\"voltage\",\"interval\",\"rxtime\",\"firmware\",\"region\",\"temp\",\"joineui\",\"chipeui\",\"adrmode\",\"charge\",\"status\",\"rstcount\",\"deveui\"]}]", + "payloadType": "json", + "x": 380, + "y": 2560, + "wires": [ + [ + "a69dcdc5.c107a" + ] + ] + }, + { + "id": "4dce8eaf.09cc38", + "type": "link out", + "z": "348ffbdd.1c23a4", + "name": "", + "links": [ + "184c96a.c70ca69" + ], + "x": 675, + "y": 640, + "wires": [] + }, + { + "id": "184c96a.c70ca69", + "type": "link in", + "z": "348ffbdd.1c23a4", + "name": "To LoRa Cloud Device & Application Services - Joining", + "links": [ + "4dce8eaf.09cc38", + "7892faaf.208c44" + ], + "x": 295, + "y": 1100, + "wires": [ + [ + "66d405e2.55873c" + ] + ] + }, + { + "id": "66d405e2.55873c", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "Set url and credentials", + "func": "var api = 'api/v1/device/send';\nmsg.method = 'post';\n\nmsg.url = `${global.get('mgs_url')}/${api}`;\nmsg.headers = {'Authorization': global.get('mgs_token')};\n\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 440, + "y": 1100, + "wires": [ + [ + "4204a1dc.b5cfe" + ] + ] + }, + { + "id": "2fa445da.30cd7a", + "type": "loracloud-utils-connectors-ttn-v2-in", + "z": "348ffbdd.1c23a4", + "name": "", + "port": "199", + "x": 570, + "y": 620, + "wires": [ + [ + "eb8810a.3bed6f" + ], + [ + "4dce8eaf.09cc38" + ] + ] + }, + { + "id": "b79a9551.41b3f", + "type": "mqtt in", + "z": "348ffbdd.1c23a4", + "name": "TTN v2 - joinReq", + "topic": "+/devices/+/events/activations", + "qos": "2", + "datatype": "auto", + "broker": "", + "x": 360, + "y": 680, + "wires": [ + [ + "2fa445da.30cd7a" + ] + ] + }, + { + "id": "9d3574cc.338f48", + "type": "mqtt in", + "z": "348ffbdd.1c23a4", + "name": "TTN v3 - Uplinks", + "topic": "v3/+/devices/+/up", + "qos": "2", + "datatype": "auto", + "broker": "", + "x": 360, + "y": 820, + "wires": [ + [ + "acc42664.34b9a8" + ] + ] + }, + { + "id": "acc42664.34b9a8", + "type": "loracloud-utils-connectors-ttn-v3-in", + "z": "348ffbdd.1c23a4", + "name": "", + "port": "199", + "x": 570, + "y": 820, + "wires": [ + [ + "eb8810a.3bed6f" + ], + [ + "7892faaf.208c44" + ] + ] + }, + { + "id": "7892faaf.208c44", + "type": "link out", + "z": "348ffbdd.1c23a4", + "name": "", + "links": [ + "184c96a.c70ca69" + ], + "x": 675, + "y": 840, + "wires": [] + }, + { + "id": "90a718e3.cf6888", + "type": "mqtt in", + "z": "348ffbdd.1c23a4", + "name": "TTN v3 - joinReq", + "topic": "v3/+/devices/+/join", + "qos": "2", + "datatype": "auto", + "broker": "", + "x": 360, + "y": 880, + "wires": [ + [ + "acc42664.34b9a8" + ] + ] + }, + { + "id": "28846cf0.90a9fc", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "devEuis", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "", + "topic": "", + "payload": "[\"00-00-00-00-00-00-00-00\"]", + "payloadType": "json", + "x": 580, + "y": 320, + "wires": [ + [ + "29519ec0.d040ba" + ] + ] + }, + { + "id": "29519ec0.d040ba", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "Set deveuis", + "func": "global.set('deveuis', msg.payload);", + "outputs": 1, + "noerr": 0, + "x": 730, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "79dcac57.8f5194", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Clear requests", + "props": [ + { + "p": "payload", + "v": "", + "vt": "date" + }, + { + "p": "topic", + "v": "", + "vt": "string" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 340, + "y": 2660, + "wires": [ + [ + "ef92cc8a.b5e67" + ] + ] + }, + { + "id": "ef92cc8a.b5e67", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "/api/v1/requests/clear", + "func": "var api = 'api/v1/requests/clear';\nmsg.method = 'post';\n\nmsg.url = `${global.get('mgs_url')}/${api}`;\nmsg.headers = {'Authorization': global.get('mgs_token')};\n\nmsg.payload = {\n 'deveuis': global.get('deveuis'),\n}\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 700, + "y": 2660, + "wires": [ + [ + "69b3db14.9385a4" + ] + ] + }, + { + "id": "69b3db14.9385a4", + "type": "http request", + "z": "348ffbdd.1c23a4", + "name": "", + "method": "use", + "ret": "txt", + "paytoqs": false, + "url": "", + "tls": "", + "proxy": "", + "authType": "basic", + "x": 890, + "y": 2660, + "wires": [ + [ + "1f7fe688.450069" + ] + ] + }, + { + "id": "1f7fe688.450069", + "type": "json", + "z": "348ffbdd.1c23a4", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 1030, + "y": 2660, + "wires": [ + [ + "2c18c52d.0238ca" + ] + ] + }, + { + "id": "2c18c52d.0238ca", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "requests/add", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload.result", + "targetType": "msg", + "x": 1170, + "y": 2660, + "wires": [] + }, + { + "id": "73d55bf.4627324", + "type": "link out", + "z": "348ffbdd.1c23a4", + "name": "File from LoRa Cloud Device & Application Services", + "links": [ + "a9fbdb98.a0f53" + ], + "x": 735, + "y": 1220, + "wires": [] + }, + { + "id": "c78d2902.4704c", + "type": "link out", + "z": "348ffbdd.1c23a4", + "name": "Downlink from LoRa Cloud Device & Application Services", + "links": [ + "2f019093.3918f8" + ], + "x": 735, + "y": 1300, + "wires": [] + }, + { + "id": "2f019093.3918f8", + "type": "link in", + "z": "348ffbdd.1c23a4", + "name": "To network server downlink", + "links": [ + "c78d2902.4704c", + "c475068b.5bb018" + ], + "x": 455, + "y": 1760, + "wires": [ + [ + "c68ccce1.0f2218", + "6a9aa87.da1ffd8" + ] + ] + }, + { + "id": "e7a6b3ed.5b8958", + "type": "comment", + "z": "348ffbdd.1c23a4", + "name": "_________________________ Downlink management _________________________", + "info": "", + "x": 760, + "y": 1520, + "wires": [] + }, + { + "id": "9def56e2.ac2f4", + "type": "link out", + "z": "348ffbdd.1c23a4", + "name": "Stream from LoRa Cloud Device & Application Services", + "links": [ + "b5df30f7.7e3348" + ], + "x": 1175, + "y": 1260, + "wires": [] + }, + { + "id": "95f885b1.0f631", + "type": "loracloud-utils-connectors-mgs-parser", + "z": "348ffbdd.1c23a4", + "name": "", + "x": 470, + "y": 1260, + "wires": [ + [ + "ebc39354.f6b378" + ], + [ + "73d55bf.4627324" + ], + [ + "786390a0.66b1b", + "9def56e2.ac2f4" + ], + [ + "c78d2902.4704c" + ], + [ + "2e0871f.4d0780e" + ] + ] + }, + { + "id": "c68ccce1.0f2218", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "LoRa Cloud Device & Application Services - Downlink", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "x": 720, + "y": 1800, + "wires": [] + }, + { + "id": "6a9aa87.da1ffd8", + "type": "loracloud-utils-connectors-downlink-generator", + "z": "348ffbdd.1c23a4", + "name": "", + "x": 660, + "y": 1760, + "wires": [ + [ + "f5c2dfd.d5c5ca", + "adedaa18.7cf38" + ] + ] + }, + { + "id": "adedaa18.7cf38", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "Downlink Generator output", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 900, + "y": 1720, + "wires": [] + }, + { + "id": "e0d93494.f8baa", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "Update network server configuration storage", + "func": "lns_configurations = global.get('lns_configurations') || {};\n\ndeveui = msg.uplink.devEui;\nlns_config = {\n \"uplink\":msg.uplink,\n \"topic\": msg.topic,\n};\nlns_configurations[deveui] = lns_config;\n\nglobal.set('lns_configurations', lns_configurations);\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 740, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "1caa2da1.b4e51a", + "type": "link in", + "z": "348ffbdd.1c23a4", + "name": "To network server storage", + "links": [ + "f939b303.5b69e" + ], + "x": 535, + "y": 2800, + "wires": [ + [ + "e0d93494.f8baa" + ] + ] + }, + { + "id": "71bef9d8.2a6fa8", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Read network server configuration", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{}", + "payloadType": "json", + "x": 400, + "y": 2840, + "wires": [ + [ + "a3151779.3cb638" + ] + ] + }, + { + "id": "a3151779.3cb638", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "Read network server configuration storage", + "func": "msg.payload = global.get('lns_configurations') || {};\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 730, + "y": 2840, + "wires": [ + [ + "ba96f77.6cb2988" + ] + ] + }, + { + "id": "ba96f77.6cb2988", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "network server configurations", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 1190, + "y": 2840, + "wires": [] + }, + { + "id": "a5394965.6b09e8", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Clear network server storage", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{}", + "payloadType": "json", + "x": 410, + "y": 2880, + "wires": [ + [ + "65c83954.4dca3" + ] + ] + }, + { + "id": "65c83954.4dca3", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "Clear network server configuration storage", + "func": "global.set('lns_configurations', {});\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 730, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "135883ff.35aab4", + "type": "comment", + "z": "348ffbdd.1c23a4", + "name": "_________________________ Network Server context _________________________", + "info": "", + "x": 690, + "y": 2740, + "wires": [] + }, + { + "id": "eb8c93ad.54f86", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "Generate downlink message", + "func": "deveuis = global.get('deveuis').map(raw => raw.replace(/-/g, \"\"));\nlns_configurations = global.get('lns_configurations') || {};\n\nconst all_out_msg = deveuis.map(deveui =>{\n return {\n \"payload\":{\n \"payload\":msg.payload.payload,\n \"port\": msg.payload.port,\n },\n \"uplink\":lns_configurations[deveui].uplink,\n \"topic\":lns_configurations[deveui].topic,\n }\n }\n);\n\nreturn [all_out_msg];", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 820, + "y": 1600, + "wires": [ + [ + "d9471a64.abb3a", + "c475068b.5bb018" + ] + ] + }, + { + "id": "d9471a64.abb3a", + "type": "debug", + "z": "348ffbdd.1c23a4", + "name": "Generated downlink message", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 1190, + "y": 1600, + "wires": [] + }, + { + "id": "c475068b.5bb018", + "type": "link out", + "z": "348ffbdd.1c23a4", + "name": "", + "links": [ + "2f019093.3918f8" + ], + "x": 995, + "y": 1600, + "wires": [] + }, + { + "id": "41e7d6ee.88e738", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Example of message to downlink", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"payload\":\"001122\",\"port\":2}", + "payloadType": "json", + "x": 350, + "y": 1600, + "wires": [ + [ + "eb8c93ad.54f86" + ] + ] + }, + { + "id": "b7873f1c.e350f", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Turn On LED", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"payload\":\"4F0101\",\"port\":151}", + "payloadType": "json", + "x": 410, + "y": 1640, + "wires": [ + [ + "eb8c93ad.54f86" + ] + ] + }, + { + "id": "ffadc893.4e1938", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Turn Off LED", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"payload\":\"4F0100\",\"port\":151}", + "payloadType": "json", + "x": 410, + "y": 1680, + "wires": [ + [ + "eb8c93ad.54f86" + ] + ] + }, + { + "id": "98186df7.c883e8", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Modem & Geolocation Services url", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 500, + "y": 280, + "wires": [ + [ + "83e37e26.bc9148" + ] + ] + }, + { + "id": "83e37e26.bc9148", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "Modem & Geolocation Services url", + "func": "global.set('mgs_url', msg.payload);", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 800, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "e53839c7.d2746", + "type": "inject", + "z": "348ffbdd.1c23a4", + "name": "Modem & Geolocation Services server token", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 470, + "y": 240, + "wires": [ + [ + "7d232e05.2c821" + ] + ] + }, + { + "id": "7d232e05.2c821", + "type": "function", + "z": "348ffbdd.1c23a4", + "name": "Modem & Geolocation Services server token", + "func": "global.set('mgs_token', msg.payload);", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 830, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "eb8810a.3bed6f", + "type": "link out", + "z": "348ffbdd.1c23a4", + "name": "Other Ports", + "links": [ + "384539b7.56d456" + ], + "x": 855, + "y": 620, + "wires": [] + }, + { + "id": "ca037929.3d8c78", + "type": "comment", + "z": "348ffbdd.1c23a4", + "name": "To Geolocation Payload Handler", + "info": "", + "x": 930, + "y": 660, + "wires": [] + } +] diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/README.md b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/README.md new file mode 100644 index 000000000..737b0741e --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/README.md @@ -0,0 +1,332 @@ +# Geolocation GNSS example application + +## Description + +This example illustrates the GNSS scan procedure: + +- configuration of the LoRa Basics Modem library; and +- execution of GNSS *scan & send* feature using the *GNSS geolocation middleware*. + +## GNSS middleware + +The GNSS middleware is a software layer used to simplify the integration of a +GNSS scan & send operation in a user application. + +For more details about the middleware, please refer to the documentation here: + +[Geolocation middleware documentation](<../../../geolocation_middleware/readme.md>) + +## LoRa Basics Modem configuration + +Once the modem has reset, the application will set the ALMANAC_STATUS info field +in order to allow automatic OTA update of the almanac written in the LR11xx +memory. + +Once the modem has joined the LoRaWAN network, the application will start the +time synchronization service. + +Once the clock synchronization is done, the application will: +- configure the ADR custom profile for the selected region; +- disable the modem auto-switch to network controlled ADR; +- initialize the GNSS middleware; and +- initiate the first GNSS scan & send. + +## GNSS middleware events + +After the initial scan has been initiated, the application relies on the events +sent by the GNSS middleware to progress in the scan sequence. + +For this, the application defines a callback function `on_middleware_gnss_event()` +that will be called when the GNSS middleware sends an event. + +In this callback function, the application will process the pending events, and +for any event except the `GNSS_MW_EVENT_SCAN_DONE` event, it will schedule the +next scan with the delay defined by `GNSS_SCAN_GROUP_PERIOD`. + +After all pending events have been processed, the function has to clear the +middleware pending events by calling the API function `gnss_mw_clear_pending_events()`. + +## Assistance position + +The application proposes 2 different modes for the assistance position: + +* auto: when `MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO` is set to `true`, the +GNSS middleware will execute autonomous scans until the solver sends a +applicative downlink with an assistance position. The application will receive +the downlink on port `GNSS_DAS_DOWNLINK_PORT` and push the received payload to +the GNSS middleware (see `on_modem_down_data()`). After this, the middleware +will automatically switch to assisted scan. +*WARNING*: this mode is to be used when the object has a clear sky view, as the +sensitivity for autonomous scan is not as good as for assisted scan. +* user defined: when `MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO` is set to false, +the application will use the values defined in `MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT` +and `MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG` to configure the assistance +position to be used. In this mode, the GNSS middleware will only use assisted +scan. + +## Limitations + +It is expected that the almanac written in the LR11xx flash memory is up-to-date +enough. It is recommended to perform a full update before using this application. +No particular action is taken in case the GNSS middleware detects that the +almanac is too old to perform a successful scan, except sending an error event +`GNSS_MW_EVENT_ERROR_ALMANAC_UPDATE` and programming the next scan. + +## Configuration + +### LoRaWAN related configuration + +The `apps/common/lorawan_key_config.h` header file defines several constants to +configure the LoRaWAN parameters (profile, region, keys). + +| Constant | Comments | Possible values | Default Value | +| ---------------- | -------------------------------- | -------------------- | -------------------------- | +| `LORAWAN_REGION` | Selects the regulatory region | See here-below | `SMTC_MODEM_REGION_EU_868` | +| `LORAWAN_CLASS` | Selects the LoRaWAN class to use | `SMTC_MODEM_CLASS_A` | `SMTC_MODEM_CLASS_A` | + +Supported values for `LORAWAN_REGION`: + +* `SMTC_MODEM_REGION_EU_868 (default)` +* `SMTC_MODEM_REGION_US_915` +* `SMTC_MODEM_REGION_CN_470_RP_1_0` + +Supported values for `LORAWAN_CLASS`: + +* `SMTC_MODEM_CLASS_A` + +When compiling with *arm-none-eabi-gcc* toolchain, all these constant are configurable through command line with the `EXTRAFLAGS`. +See [README.md](../../../README.md#command-line-configuration). + +### GNSS demonstration related configuration + +The `main_geolocation_gnss.h` header file defines several constants to configure +geolocation parameters. + +| Constant | Comments | Possible values | Default Value | +| ---------------------------------------- | --------------------------------------------------------------------------------------- | ---------------- | ------------- | +| `MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO` | If set to `true`: configures the application to autonomously get an assistance position | {`true`,`false`} | `true` | + +If manual mode is selected for assistance position, the following constants +define the position to be used. + +| Constant | Comments | Possible values | Default Value | +| ---------------------------------------- | ------------------------------------------------------------------------------- | -------------------------- | ------------------ | +| `MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT` | The latitude to use for GNSS Assisted scan (decimal degree) | Any `float` in [-90, 90] | 45.181454 | +| `MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG` | The longitude to use for GNSS Assisted scan (decimal degree) | Any `float` in [-180, 180] | 5.720893 | +| `MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT` | A text representation of the assistance position, for information printing only | Any constant c-string | "Grenoble, FRANCE" | + +*WARNING*: The pre-defined assistance position must be within a 150 km range of +the actual location. + +| Constant | Comments | Possible values | Default Value | +| ------------------------ | --------------------------------------------------------------------------------------------- | ------------------------- | --------------------- | +| `GNSS_SCAN_GROUP_PERIOD` | Defines the duration between the end of a scan & send sequence and the start of next sequence | `uint32_t` | 30 | +| `GNSS_SCAN_MODE` | Defines the GNSS scan mode (static or mobile) to be used for scan & send sequences. | Value in `gnss_mw_mode_t` | `GNSS_MW_MODE_STATIC` | + +The GNSS scan mode selected by default is `GNSS_MW_MODE_STATIC`, meaning that +this application example targets non-mobile objects. + +When compiling with *arm-none-eabi-gcc* toolchain, all these constant are configurable through command line with the `EXTRAFLAGS`. +See [README.md](../../../README.md#command-line-configuration). + +## Build + +The demo can be built through GNU make command by doing the following: + +```shell +# Navigate to the build folder +$ cd apps/geolocation_gnss/makefile + +# Execute the make call +$ make -j +``` + +By default, the demonstration is compiled to use the EUIs and Application key +defined in the file *apps/common/lorawan_key_config.h*. + +## Usage + +### Serial console + +The application requires no user intervention after the static configuration +option have been set. + +Information messages are displayed on the serial console, starting with the +DevEUI, AppEUI/JoinEUI and PIN that you might need in order to register your +device with the LoRa Cloud Device Join service. + +### LoRaWAN Network Server / Application Server + +This application needs an Application Server to run in order to perform the GNSS +solving. +Two possibilities : +* A Node-Red application server is provided in folder *apps/geolocation_application_server*. +Refer to the readme in this folder for details about setup and usage of the +application server. +* Use the LoRaCloud Locator https://atk.loracloud.com/ which embed a complete integration of an application server and an associated dashboard. + +### ADR configuration + +The Adaptive Data Rate (ADR) is configured in *Custom ADR profile* with datarate +distribution and number of repetition defined per regions. + +For more details about ADR configuration for geolocation middleware usage, +please refer to this [Geolocation middleware documentation](<../../../geolocation_middleware/doc/geolocationMiddleware.rst>), section "LoRaWAN datarate considerations". + +The actual datarate and number of retransmission values are defined in the +`main_geolocation_gnss.h` header file. + +| Constant | Comments | +| ----------------------- | -------------------------------------------------------- | +| `CUSTOM_NB_TRANS_EU868` | The number of retransmission to be used for EU868 region | +| `ADR_CUSTOM_LIST_EU868` | The custom ADR list to be used for EU868 region | +| `CUSTOM_NB_TRANS_US915` | The number of retransmission to be used for US915 region | +| `ADR_CUSTOM_LIST_US915` | The custom ADR list to be used for US915 region | +| `CUSTOM_NB_TRANS_CN470` | The number of retransmission to be used for CN470 region | +| `ADR_CUSTOM_LIST_CN470` | The custom ADR list to be used for CN470 region | + +The values must be carefully set to match with duty cycle constraints, power +consumption targets etc... + +## Expected Behavior + +Here follow the steps that shall be seen in the logs to indicate the expected behavior of the application. + +### Device starts and resets + +``` +INFO: Modem Initialization + +INFO: ===== LoRa Basics Modem GNSS Geolocation example (for static objects) ===== +``` + +Following this print you shall find application and parameter prints + +### Joined the network + +At first run no time is supposed to be available + +``` +INFO: ###### ===== JOINED EVENT ==== ###### +``` + +### Obtain time synchronization + +``` +INFO: ###### ===== TIME EVENT ==== ###### +Time: SMTC_MODEM_EVENT_TIME_VALID +``` + +### Execute and send first autonomous GNSS scan + +``` +INFO: Set ADR custom profile for EU868 +INFO: Initializing GNSS middleware v0.1.0 +New scan group for autonomous scan +INFO: RP_TASK_GNSS - new scan group - task queued at 79363 + 0 +---- internal scan start ---- + +INFO: ###### ===== MIDDLEWARE_1 EVENT ==== ###### +INFO: GNSS middleware event - SCAN DONE +SCAN_DONE info: +-- token: 0x00 +-- is_valid: 1 +-- number of valid scans: 1 +-- scan[0][1335796447] (6 SV): 222DEA586B20F0E0236AEB27B4371A419B68151226FA61D29F7CBC9706 + SV_ID 26: 45dB + SV_ID 15: 45dB + SV_ID 7: 43dB + SV_ID 20: 43dB + SV_ID 6: 42dB + SV_ID 9: 40dB +-- power consumption: 7 uah +-- mode: 0 +-- assisted: 0 +-- almanac CRC: 0X6A820509 + +---- internal TX DONE ---- +INFO: ###### ===== MIDDLEWARE_1 EVENT ==== ###### +INFO: GNSS middleware event - TERMINATED +TERMINATED info: +-- number of scans sent: 1 +``` + +### Receiving Assistance position + +``` +INFO: Received D-GNSSLOC-AIDP solver message +``` + +### Execute and send the assisted multiframe GNSS scan + +``` +New scan group for assisted scan +INFO: RP_TASK_GNSS - new scan group - task queued at 461083 + 30000 +---- internal scan start ---- +---- internal scan start ---- +---- internal scan start ---- +---- internal scan start ---- +INFO: ###### ===== MIDDLEWARE_1 EVENT ==== ###### +INFO: GNSS middleware event - SCAN DONE +SCAN_DONE info: +-- token: 0x07 +-- is_valid: 1 +-- number of valid scans: 4 +-- scan[0][1335796859] (10 SV): 8247EA010A01567378AFF1C58A85C9DAAC54199BD49A4C2245D6E833409C1A89B0020B00 + SV_ID 26: 46dB + SV_ID 7: 45dB + SV_ID 15: 43dB + SV_ID 6: 42dB + SV_ID 9: 42dB + SV_ID 89: 42dB + SV_ID 20: 40dB + SV_ID 99: 39dB + SV_ID 92: 38dB + SV_ID 108: 37dB +-- scan[1][1335796883] (10 SV): 8249EA010A01567338BBF07532486A9F4CD31D5CD41D3D2205571672D2625627B2CDCD0D + SV_ID 26: 46dB + SV_ID 7: 46dB + SV_ID 15: 44dB + SV_ID 92: 43dB + SV_ID 9: 42dB + SV_ID 89: 42dB + SV_ID 98: 41dB + SV_ID 6: 41dB + SV_ID 108: 41dB + SV_ID 20: 40dB +-- scan[2][1335796906] (10 SV): 824AEA010A01F6A071BEEEF559A82940EA143919D475192305076B32E7992F4C8B923308 + SV_ID 7: 46dB + SV_ID 26: 46dB + SV_ID 92: 45dB + SV_ID 15: 42dB + SV_ID 6: 42dB + SV_ID 9: 42dB + SV_ID 108: 40dB + SV_ID 89: 40dB + SV_ID 20: 40dB + SV_ID 98: 40dB +-- scan[3][1335796930] (10 SV): 824CEA010A015473FACFEE65888DC97DB04CC19115EB34DB90F1A3E326897EC9D7095F0F + SV_ID 26: 45dB + SV_ID 92: 45dB + SV_ID 7: 44dB + SV_ID 15: 43dB + SV_ID 6: 41dB + SV_ID 89: 41dB + SV_ID 108: 41dB + SV_ID 99: 40dB + SV_ID 98: 39dB + SV_ID 93: 38dB +-- power consumption: 32 uah +-- mode: 0 +-- assisted: 1 +-- aiding position: (45.131836, 5.888672) +-- almanac CRC: 0X6A820509 + +# (...Several TX...) + +---- internal TX DONE ---- +INFO: ###### ===== MIDDLEWARE_1 EVENT ==== ###### +INFO: GNSS middleware event - TERMINATED +TERMINATED info: +-- number of scans sent: 4 +``` \ No newline at end of file diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/main_geolocation_gnss.c b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/main_geolocation_gnss.c new file mode 100644 index 000000000..99fd77aec --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/main_geolocation_gnss.c @@ -0,0 +1,562 @@ +/*! + * @ingroup apps_geolocation + * @file main_geolocation_gnss.c + * + * @brief LoRa Basics Modem LR11XX Geolocation GNSS example with *scan group" for static geolocation. + * + * @copyright + * @parblock + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + * @endparblock + */ + +/*! + * @addtogroup apps_geolocation + * LoRa Basics Modem LR11XX Geolocation GNSS example for static objects + * @{ + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "main_geolocation_gnss.h" +#include "smtc_board.h" +#include "smtc_hal.h" +#include "apps_utilities.h" +#include "apps_modem_common.h" +#include "apps_modem_event.h" + +#include "gnss_middleware.h" +#include "lr11xx_system.h" + +#include "smtc_modem_utilities.h" +#include "smtc_board_ralf.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ +/** + * @brief LR11XX radio firmware + */ +#define LR1110_FW_VERSION 0x0307 +#define LR1110_FW_TYPE 0x01 +#define LR1120_FW_VERSION 0x0101 +#define LR1120_FW_TYPE 0x02 + +/** + * @brief Duration in second after last ALC sync response received to consider the local clock time invalid + * + * Set time valid for 1 day (to be fine tuned depending on board properties) + */ +#define APP_ALC_TIMING_INVALID ( 3600 * 24 ) + +/** + * @brief Interval in second between two consecutive ALC sync requests + * + * 3 time sync requests per day + */ +#define APP_ALC_TIMING_INTERVAL ( APP_ALC_TIMING_INVALID / 3 ) + +/** + * @brief Duration in second after Join to start the application keep alive sequence + */ +#define APP_TIMER_START ( 10 ) + +/** + * @brief Duration in second for keep alive alarm sequence + */ +#define APP_TIMER_KEEP_ALIVE ( 30 ) + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/*! + * @brief Stack identifier + */ +static uint8_t stack_id = 0; + +/*! + * @brief Modem radio + */ +static ralf_t* modem_radio; + +/*! + * @brief First time sync status for application startup + */ +static bool is_first_time_sync = true; + +/*! + * @brief ADR custom list and retransmission definition for EU868 / IN865 / RU864 / AU915 / CN470 /AS923 / KR920 regions + */ +static const uint8_t adr_custom_list_dr5_dr3[16] = ADR_CUSTOM_LIST_DR5_DR3; +static const uint8_t custom_nb_trans_dr5_dr3 = CUSTOM_NB_TRANS_DR5_DR3; + +/*! + * @brief ADR custom list and retransmission definition for US915 region + */ +static const uint8_t adr_custom_list_us915[16] = ADR_CUSTOM_LIST_US915; +static const uint8_t custom_nb_trans_us915 = CUSTOM_NB_TRANS_US915; + +/*! + * @brief ADR custom list and retransmission definition for WW2G4 region + */ +static const uint8_t adr_custom_list_ww2g4[16] = ADR_CUSTOM_LIST_WW2G4; +static const uint8_t custom_nb_trans_ww2g4 = CUSTOM_NB_TRANS_WW2G4; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Helper function that configure the custom ADR configuration for geolocation scan & send, based on the region + * already configured in the stack. + * + * Prior using this function, the region must have been set already in the stack. + */ +static void configure_adr( void ); + +/*! + * @addtogroup basics_modem_evt_callback + * LoRa Basics Modem event callbacks + * @{ + */ + +/*! + * @brief Reset event callback + * + * @param [in] reset_count reset counter from the modem + */ +static void on_modem_reset( uint16_t reset_count ); + +/*! + * @brief Network Joined event callback + */ +static void on_modem_network_joined( void ); + +/*! + * @brief Alarm event callback + */ +static void on_modem_alarm( void ); + +/*! + * @brief Clock synchronisation event callback + */ +static void on_modem_clk_synch( smtc_modem_event_time_status_t time_status ); + +/*! + * @brief Downlink data event callback. + * + * @param [in] rssi RSSI in signed value in dBm + 64 + * @param [in] snr SNR signed value in 0.25 dB steps + * @param [in] rx_window RX window + * @param [in] port LoRaWAN port + * @param [in] payload Received buffer pointer + * @param [in] size Received buffer size + */ +static void on_modem_down_data( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ); + +/*! + * @brief GNSS Almanac update event callback + */ +static void on_modem_almanac_update( smtc_modem_event_almanac_update_status_t status ); + +/*! + * @brief GNSS middleware event callback + */ +static void on_middleware_gnss_event( uint8_t pending_events ); + +/*! + * @} + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +/** + * @brief Main application entry point. + */ +int main( void ) +{ + lr11xx_system_version_t lr11xx_fw_version; + lr11xx_status_t status; + static apps_modem_event_callback_t smtc_event_callback = { + .adr_mobile_to_static = NULL, + .alarm = on_modem_alarm, + .almanac_update = on_modem_almanac_update, + .down_data = on_modem_down_data, + .join_fail = NULL, + .joined = on_modem_network_joined, + .link_status = NULL, + .mute = NULL, + .new_link_adr = NULL, + .reset = on_modem_reset, + .set_conf = NULL, + .stream_done = NULL, + .time_updated_alc_sync = on_modem_clk_synch, + .tx_done = NULL, + .upload_done = NULL, + .user_radio_access = NULL, + .middleware_1 = on_middleware_gnss_event, + .middleware_2 = NULL, + }; + + /* Initialise the ralf_t object corresponding to the board */ + modem_radio = smtc_board_initialise_and_get_ralf( ); + + /* Disable IRQ to avoid unwanted behaviour during init */ + hal_mcu_disable_irq( ); + + /* Init board and peripherals */ + hal_mcu_init( ); + smtc_board_init_periph( ); + + /* Init the Lora Basics Modem event callbacks */ + apps_modem_event_init( &smtc_event_callback ); + + /* Init the modem and use apps_modem_event_process as event callback, please note that the callback will be called + * immediately after the first call to modem_run_engine because of the reset detection */ + smtc_modem_init( modem_radio, &apps_modem_event_process ); + + /* Re-enable IRQ */ + hal_mcu_enable_irq( ); + + HAL_DBG_TRACE_MSG( "\n" ); + HAL_DBG_TRACE_INFO( + "###### ===== LoRa Basics Modem GNSS Geolocation example (for static objects) ==== ######\n\n" ); + + apps_modem_common_display_lbm_version( ); + + /* Check LR11XX Firmware version */ + ASSERT_SMTC_MODEM_RC( smtc_modem_suspend_before_user_radio_access( ) ); /* protect from radio access conflicts */ + status = lr11xx_system_get_version( modem_radio->ral.context, &lr11xx_fw_version ); + ASSERT_SMTC_MODEM_RC( smtc_modem_resume_after_user_radio_access( ) ); + if( status != LR11XX_STATUS_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to get LR11XX firmware version\n" ); + mcu_panic( ); + } + if( ( ( lr11xx_fw_version.fw != LR1110_FW_VERSION ) && ( lr11xx_fw_version.type = LR1110_FW_TYPE ) ) && + ( ( lr11xx_fw_version.fw != LR1120_FW_VERSION ) && ( lr11xx_fw_version.type = LR1120_FW_TYPE ) ) ) + { + HAL_DBG_TRACE_ERROR( "Wrong LR11XX firmware version, expected 0x%04X, got 0x%04X\n", LR1110_FW_VERSION, + lr11xx_fw_version.fw ); + mcu_panic( ); + } + HAL_DBG_TRACE_INFO( "LR11XX FW : 0x%04X\n", lr11xx_fw_version.fw ); + + while( 1 ) + { + /* Execute modem runtime, this function must be called again in sleep_time_ms milliseconds or sooner. */ + uint32_t sleep_time_ms = smtc_modem_run_engine( ); + /* go in low power */ + hal_mcu_set_sleep_for_ms( sleep_time_ms ); + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/*! + * @brief LoRa Basics Modem event callbacks called by smtc_event_process function + */ + +static void on_modem_reset( uint16_t reset_count ) +{ + apps_modem_common_configure_lorawan_params( stack_id ); + + /* Configure modem DM status for regular almanac status update */ + smtc_modem_dm_info_interval_format_t format = SMTC_MODEM_DM_INFO_INTERVAL_IN_DAY; + uint8_t interval = 1; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_set_info_interval( format, interval ) ); + + /* Active almanac update OTA - WARNING: will remove all other DM message */ + uint8_t info_field = SMTC_MODEM_DM_FIELD_ALMANAC_STATUS; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_set_info_fields( &info_field, 1 ) ); + + /* Start the Join process */ + ASSERT_SMTC_MODEM_RC( smtc_modem_join_network( stack_id ) ); + + HAL_DBG_TRACE_INFO( "###### ===== JOINING ==== ######\n\n" ); +} + +static void on_modem_network_joined( void ) +{ + /* Start time sync (ALC sync), necessary for GNSS scan: + The interval_s indicates how often the LBM will request a time sync from the DAS. + If no time sync downlink has been received from the DAS after the invalid_delay_s is elapsed, + the LBM will report SMTC_MODEM_RC_NO_TIME on smtc_modem_get_time() call. */ + /* -- */ + ASSERT_SMTC_MODEM_RC( smtc_modem_time_set_sync_interval_s( APP_ALC_TIMING_INTERVAL ) ); /* keep call order */ + ASSERT_SMTC_MODEM_RC( smtc_modem_time_set_sync_invalid_delay_s( APP_ALC_TIMING_INVALID ) ); /* keep call order */ + /* Start the service */ + ASSERT_SMTC_MODEM_RC( smtc_modem_time_start_sync_service( stack_id, SMTC_MODEM_TIME_ALC_SYNC ) ); +} + +static void on_modem_alarm( void ) +{ + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( GNSS_SCAN_GROUP_PERIOD_DEFAULT ) ); + HAL_DBG_TRACE_PRINTF( "smtc_modem_alarm_start_timer: %d s\n\n", GNSS_SCAN_GROUP_PERIOD_DEFAULT ); + + // mw_return_code_t gnss_rc; + // gnss_rc = gnss_mw_scan_start( GNSS_SCAN_MODE, 0 ); /* start ASAP */ + // if( gnss_rc != MW_RC_OK ) + // { + // HAL_DBG_TRACE_ERROR( "Failed to start GNSS scan\n" ); + // } +} + +static void on_modem_clk_synch( smtc_modem_event_time_status_t time_status ) +{ + mw_return_code_t gnss_rc; + mw_version_t mw_version; + + if( time_status != SMTC_MODEM_EVENT_TIME_NOT_VALID ) + { + if( is_first_time_sync == true ) + { + /* Set the custom ADR profile for geolocation scan & send */ + configure_adr( ); + + /* Initialize GNSS middleware */ + gnss_mw_get_version( &mw_version ); + HAL_DBG_TRACE_INFO( "Initializing GNSS middleware v%d.%d.%d\n", mw_version.major, mw_version.minor, + mw_version.patch ); + gnss_mw_init( modem_radio, stack_id ); + gnss_mw_set_constellations( GNSS_MW_CONSTELLATION_GPS_BEIDOU ); + + /* Set user defined assistance position */ +#if MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO == false + const char* assistance_position_text = MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT; + gnss_mw_set_user_aiding_position( MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT, + MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG ); + HAL_DBG_TRACE_WARNING( "User defined assistance position has been set in %s\n", assistance_position_text ); +#endif + + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( 5 ) ); + + /* Start the GNSS scan group sequence */ + gnss_rc = gnss_mw_scan_start( GNSS_SCAN_MODE, 0 ); /* start ASAP */ + if( gnss_rc != MW_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to start GNSS scan\n" ); + } + } + + is_first_time_sync = false; + } +} + +static void on_modem_down_data( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ) +{ + HAL_DBG_TRACE_INFO( "Downlink received:\n" ); + HAL_DBG_TRACE_INFO( " - LoRaWAN Fport = %d\n", port ); + HAL_DBG_TRACE_INFO( " - Payload size = %d\n", size ); + HAL_DBG_TRACE_INFO( " - RSSI = %d dBm\n", rssi - 64 ); + HAL_DBG_TRACE_INFO( " - SNR = %d dB\n", snr >> 2 ); + + if( size != 0 ) + { + HAL_DBG_TRACE_ARRAY( "Payload", payload, size ); + + /* Forward downlink to GNSS middleware to handle it if necessary */ + gnss_mw_handle_downlink( port, payload, size ); + } +} + +static void on_modem_almanac_update( smtc_modem_event_almanac_update_status_t status ) +{ + if( status == SMTC_MODEM_EVENT_ALMANAC_UPDATE_STATUS_REQUESTED ) + { + HAL_DBG_TRACE_INFO( "Almanac update is not completed: sending new request\n" ); +#if 0 + uint8_t dm_almanac_status = SMTC_MODEM_DM_FIELD_ALMANAC_STATUS; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_request_single_uplink( &dm_almanac_status, 1 ) ); +#endif + } + else + { + HAL_DBG_TRACE_INFO( "Almanac update is completed\n" ); + } +} + +static void on_middleware_gnss_event( uint8_t pending_events ) +{ + mw_return_code_t gnss_rc; + + /* Parse events */ + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_SCAN_DONE ) ) + { + gnss_mw_event_data_scan_done_t event_data; + + HAL_DBG_TRACE_INFO( "GNSS middleware event - SCAN DONE\n" ); + gnss_mw_get_event_data_scan_done( &event_data ); + gnss_mw_display_results( &event_data ); + + if( event_data.context.almanac_update_required ) + { + uint8_t dm_almanac_status = SMTC_MODEM_DM_FIELD_ALMANAC_STATUS; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_request_single_uplink( &dm_almanac_status, 1 ) ); + } + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_TERMINATED ) ) + { + gnss_mw_event_data_terminated_t event_data; + + HAL_DBG_TRACE_INFO( "GNSS middleware event - TERMINATED\n" ); + gnss_mw_get_event_data_terminated( &event_data ); + HAL_DBG_TRACE_PRINTF( "TERMINATED info:\n" ); + HAL_DBG_TRACE_PRINTF( "-- number of scans sent: %u\n", event_data.nb_scans_sent ); + HAL_DBG_TRACE_PRINTF( "-- aiding position check sent: %d\n", event_data.aiding_position_check_sent ); + HAL_DBG_TRACE_PRINTF( "-- indoor detected: %d\n", event_data.indoor_detected ); + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_SCAN_CANCELLED ) ) + { + HAL_DBG_TRACE_INFO( "GNSS middleware event - SCAN CANCELLED\n" ); + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_NO_TIME ) ) + { + HAL_DBG_TRACE_ERROR( "GNSS middleware event - ERROR NO TIME\n" ); + ASSERT_SMTC_MODEM_RC( smtc_modem_time_trigger_sync_request( stack_id ) ); + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_ALMANAC_UPDATE ) ) + { + HAL_DBG_TRACE_ERROR( "GNSS middleware event - ALMANAC UPDATE REQUIRED\n" ); + uint8_t dm_almanac_status = SMTC_MODEM_DM_FIELD_ALMANAC_STATUS; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_request_single_uplink( &dm_almanac_status, 1 ) ); + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_NO_AIDING_POSITION ) ) + { + HAL_DBG_TRACE_ERROR( "GNSS middleware event - ERROR NO AIDING POSITION set\n" ); + +/* Set user defined assistance position */ +#if MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO == false + const char* assistance_position_text = MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT; + gnss_mw_set_user_aiding_position( MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT, + MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG ); + HAL_DBG_TRACE_WARNING( "User defined assistance position has been set in %s\n", assistance_position_text ); +#endif + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_UNKNOWN ) ) + { + HAL_DBG_TRACE_ERROR( "GNSS middleware event - UNEXPECTED ERROR\n" ); + } + + /* Program the next GNSS scan group if event != SCAN_DONE */ + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_TERMINATED ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_NO_TIME ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_ALMANAC_UPDATE ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_NO_AIDING_POSITION ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_UNKNOWN ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_SCAN_CANCELLED ) ) + { + gnss_rc = gnss_mw_scan_start( GNSS_SCAN_MODE, GNSS_SCAN_GROUP_PERIOD ); + if( gnss_rc != MW_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to start GNSS scan\n" ); + } + } + + gnss_mw_clear_pending_events( ); +} + +void configure_adr( void ) +{ + smtc_modem_region_t region; + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_region( stack_id, ®ion ) ); + + /* Set the ADR profile once joined */ + switch( region ) + { + case SMTC_MODEM_REGION_EU_868: + case SMTC_MODEM_REGION_IN_865: + case SMTC_MODEM_REGION_RU_864: + case SMTC_MODEM_REGION_AU_915: + case SMTC_MODEM_REGION_AS_923_GRP1: + case SMTC_MODEM_REGION_AS_923_GRP2: + case SMTC_MODEM_REGION_AS_923_GRP3: + case SMTC_MODEM_REGION_CN_470: + case SMTC_MODEM_REGION_CN_470_RP_1_0: + case SMTC_MODEM_REGION_KR_920: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_dr5_dr3 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_dr5_dr3 ) ); + break; + case SMTC_MODEM_REGION_US_915: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_us915 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_us915 ) ); + break; + case SMTC_MODEM_REGION_WW2G4: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_ww2g4 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_ww2g4 ) ); + break; + default: + HAL_DBG_TRACE_ERROR( "Region not supported in this example, could not set custom ADR profile\n" ); + break; + } + + /* Disable auto switch to network controlled after a certain amount of TX without RX */ + ASSERT_SMTC_MODEM_RC( smtc_modem_connection_timeout_set_thresholds( stack_id, 0, 0 ) ); +} + +/*! + * @} + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/main_geolocation_gnss.h b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/main_geolocation_gnss.h new file mode 100644 index 000000000..ba389657d --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss/main_geolocation_gnss.h @@ -0,0 +1,154 @@ +/*! + * @ingroup apps_geolocation + * @file main_geolocation_gnss.h + * + * @brief LoRa Basics Modem LR11XX Geolocation GNSS example with *scan group" for static geolocation. + * + * @copyright + * @parblock + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + * @endparblock + */ + +/*! + * @addtogroup apps_geolocation + * LoRa Basics Modem LR11XX Geolocation example + * @{ + */ + +#ifndef MAIN_GEOLOCATION_GNSS_H +#define MAIN_GEOLOCATION_GNSS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * ----------------------------------------------------------------------------- + * --- Application Configuration ----------------------------------------------- + */ + +/** + * @brief Let the application autonomously set the assistance position for GNSS geolocation (default, autonomous mode) + */ +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO_DEFAULT false + +/** + * @brief User defined assistance position for GNSS geolocation (not used by default, autonomous mode) + */ +// #define MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT_DEFAULT ( 45.181454 ) +// #define MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG_DEFAULT ( 5.720893 ) +// #define MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT_DEFAULT "Grenoble, FRANCE" +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT_DEFAULT ( 22.576814 ) +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG_DEFAULT ( 113.922068 ) +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT_DEFAULT "Shenzhen, CHINA" + +/* + * ----------------------------------------------------------------------------- + * --- Geolocation scan group Configuration ------------------------------------ + */ + +/*! + * @brief Defines the delay before starting a new GNSS scan group, value in [s]. + */ +#define GNSS_SCAN_GROUP_PERIOD_DEFAULT 60 + +/*! + * @brief Defines the scan mode used for GNSS (STATIC or MOBILE). + */ +#define GNSS_SCAN_MODE_DEFAULT GNSS_MW_MODE_MOBILE + +#ifndef MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO_DEFAULT +#endif // MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO + +#ifndef MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT_DEFAULT +#endif // MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT + +#ifndef MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG_DEFAULT +#endif // MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG + +#ifndef MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT_DEFAULT +#endif // MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT + +#ifndef GNSS_SCAN_GROUP_PERIOD +#define GNSS_SCAN_GROUP_PERIOD GNSS_SCAN_GROUP_PERIOD_DEFAULT +#endif // GNSS_SCAN_GROUP_PERIOD + +#ifndef GNSS_SCAN_MODE +#define GNSS_SCAN_MODE GNSS_SCAN_MODE_DEFAULT +#endif // GNSS_SCAN_MODE +/* + * ----------------------------------------------------------------------------- + * --- LoRaWAN Configuration --------------------------------------------------- + */ + +/*! + * @brief ADR custom list and retransmission parameters for EU868 / IN865 / RU864 / AU915 / CN470 /AS923 / KR920 regions + */ +#define CUSTOM_NB_TRANS_DR5_DR3 1 +#define ADR_CUSTOM_LIST_DR5_DR3 \ + { \ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3 \ + } /* 125kHz - SF7, SF8, SF9 */ + +/*! + * @brief ADR custom list and retransmission parameters for US915 region + */ +#define CUSTOM_NB_TRANS_US915 1 +#define ADR_CUSTOM_LIST_US915 \ + { \ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1 \ + } /* 125kHz - SF7, SF8, SF9 */ + +/*! + * @brief ADR custom list and retransmission parameters for WW2G4 region + */ +#define CUSTOM_NB_TRANS_WW2G4 1 +#define ADR_CUSTOM_LIST_WW2G4 \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ + } /* SF12 */ + +#ifdef __cplusplus +} +#endif + +#endif // MAIN_GEOLOCATION_GNSS_H + +/*! + * @} + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/README.md b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/README.md new file mode 100644 index 000000000..89faa479f --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/README.md @@ -0,0 +1,268 @@ +# Geolocation GNSS and Wi-Fi example application + +## Description + +This example illustrates the combination of GNSS and Wi-Fi scan procedures: + +- configuration of the LoRa Basics Modem library; and +- concurrent execution of GNSS and Wi-Fi *scan & send* features using the +*GNSS geolocation middleware* and the *Wi-Fi geolocation middleware*. + +## GNSS & Wi-Fi middlewares + +The geolocation middlewares belongs to a software layer used to simplify the +integration of a GNSS and Wi-Fi scan & send operation in a user application. + +For more details about the middleware, please refer to the documentation here: + +[Geolocation middleware documentation](<../../../geolocation_middleware/readme.md>) + +## LoRa Basics Modem configuration + +Same as [geolocation_gnss](<../geolocation_gnss/README.md>) + +## Geolocation scan sequence + +This application starts with a GNSS *scan & send* sequence, and as soon as it is +completed, it performs a Wi-Fi *scan & send* sequence (with no delay). + +Once the Wi-Fi sequence is completed, it schedules the next GNSS sequence with +`GNSS_SCAN_GROUP_PERIOD` delay. + +It uses the events sent by the geolocation middlewares to handle the concurrent +execution of GNSS and Wi-Fi sequences. + +Please refer to [geolocation_gnss](<../geolocation_gnss/README.md>) and +[geolocation_wifi](<../geolocation_wifi/README.md>) for more details. + +## GNSS Assistance position + +Same as [geolocation_gnss](<../geolocation_gnss/README.md>) + +## Limitations + +Same as [geolocation_gnss](<../geolocation_gnss/README.md>) + +## Configuration + +### LoRaWAN related configuration + +The `apps/common/lorawan_key_config.h` header file defines several constants to +configure the LoRaWAN parameters (profile, region, keys). + +| Constant | Comments | Possible values | Default Value | +| ---------------- | -------------------------------- | -------------------- | -------------------------- | +| `LORAWAN_REGION` | Selects the regulatory region | See here-below | `SMTC_MODEM_REGION_EU_868` | +| `LORAWAN_CLASS` | Selects the LoRaWAN class to use | `SMTC_MODEM_CLASS_A` | `SMTC_MODEM_CLASS_A` | + +Supported values for `LORAWAN_REGION`: + +* `SMTC_MODEM_REGION_EU_868 (default)` +* `SMTC_MODEM_REGION_US_915` +* `SMTC_MODEM_REGION_CN_470_RP_1_0` + +Supported values for `LORAWAN_CLASS`: + +* `SMTC_MODEM_CLASS_A` + +When compiling with *arm-none-eabi-gcc* toolchain, all these constant are configurable through command line with the `EXTRAFLAGS`. +See [README.md](../../../README.md#command-line-configuration). + +### Geolocation demonstration related configuration + +The `main_geolocation_gnss_wifi.h` header file defines several constants to +configure geolocation parameters. + +| Constant | Comments | Possible values | Default Value | +| ---------------------------------------- | --------------------------------------------------------------------------------------- | ---------------- | ------------- | +| `MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO` | If set to `true`: configures the application to autonomously get an assistance position | {`true`,`false`} | `false` | + +If manual mode is selected for assistance position, the following constants +define the position to be used. + +| Constant | Comments | Possible values | Default Value | +| ---------------------------------------- | ------------------------------------------------------------------------------- | -------------------------- | ------------------ | +| `MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT` | The latitude to use for GNSS Assisted scan (decimal degree) | Any `float` in [-90, 90] | 45.181454 | +| `MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG` | The longitude to use for GNSS Assisted scan (decimal degree) | Any `float` in [-180, 180] | 5.720893 | +| `MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT` | A text representation of the assistance position, for information printing only | Any constant c-string | "Grenoble, FRANCE" | + +*WARNING*: The pre-defined assistance position must be within a 150 km range of +the actual location. + +| Constant | Comments | Possible values | Default Value | +| ------------------------ | --------------------------------------------------------------------------------------------- | ------------------------- | --------------------- | +| `GNSS_SCAN_GROUP_PERIOD` | Defines the duration between the end of a scan & send sequence and the start of next sequence | `uint32_t` | 30 | +| `GNSS_SCAN_MODE` | Defines the GNSS scan mode (static or mobile) to be used for scan & send sequences. | Value in `gnss_mw_mode_t` | `GNSS_MW_MODE_MOBILE` | + +The GNSS scan mode selected by default is `GNSS_MW_MODE_MOBILE`, meaning that +this application example targets mobile objects. + +When compiling with *arm-none-eabi-gcc* toolchain, all these constant are configurable through command line with the `EXTRAFLAGS`. +See [README.md](../../../README.md#command-line-configuration). + +## Build + +The demo can be built through GNU make command by doing the following: + +```shell +# Navigate to the build folder +$ cd apps/geolocation_gnss_wifi/makefile + +# Execute the make call +$ make -j +``` + +By default, the demonstration is compiled to use the EUIs and Application key +defined in the file *apps/common/lorawan_key_config.h*. + +## Usage + +### Serial console + +The application requires no user intervention after the static configuration +option have been set. + +Information messages are displayed on the serial console, starting with the +DevEUI, AppEUI/JoinEUI and PIN that you might need in order to register your +device with the LoRa Cloud Device Join service. + +### LoRaWAN Network Server / Application Server + +This application needs an Application Server to run in order to perform the GNSS and Wi-Fi +solving. +Two possibilities : +* A Node-Red application server is provided in folder *apps/geolocation_application_server*. +Refer to the readme in this folder for details about setup and usage of the +application server. +* Use the LoRaCloud Locator https://atk.loracloud.com/ which embed a complete integration of an application server and an associated dashboard. + +### ADR configuration + +The Adaptive Data Rate (ADR) is configured in *Custom ADR profile* with datarate +distribution and number of repetition defined per regions. + +For more details about ADR configuration for geolocation middleware usage, +please refer to this [Geolocation middleware documentation](<../../../geolocation_middleware/doc/geolocationMiddleware.rst>), section "LoRaWAN datarate considerations". + +The actual datarate and number of retransmission values are defined in the +`main_geolocation_gnss_wifi.h` header file. + +| Constant | Comments | +| ----------------------- | -------------------------------------------------------- | +| `CUSTOM_NB_TRANS_EU868` | The number of retransmission to be used for EU868 region | +| `ADR_CUSTOM_LIST_EU868` | The custom ADR list to be used for EU868 region | +| `CUSTOM_NB_TRANS_US915` | The number of retransmission to be used for US915 region | +| `ADR_CUSTOM_LIST_US915` | The custom ADR list to be used for US915 region | +| `CUSTOM_NB_TRANS_CN470` | The number of retransmission to be used for CN470 region | +| `ADR_CUSTOM_LIST_CN470` | The custom ADR list to be used for CN470 region | + +The values must be carefully set to match with duty cycle constraints, power +consumption targets etc... + +## Expected Behavior + +Here follow the steps that shall be seen in the logs to indicate the expected behavior of the application. + +### Device starts and resets + +``` +INFO: Modem Initialization + +INFO: ===== LoRa Basics Modem GNSS/Wi-Fi Geolocation example ===== +``` + +Following this print you shall find application and parameter prints + +### Joined the network + +At first run no time is supposed to be available + +``` +INFO: ###### ===== JOINED EVENT ==== ###### +``` + +### Obtain time synchronization + +``` +INFO: ###### ===== TIME EVENT ==== ###### +Time: SMTC_MODEM_EVENT_TIME_VALID +``` + +### Execute and send first assisted GNSS scan + +``` +INFO: Set ADR custom profile for EU868 +INFO: Initializing GNSS middleware v0.1.0 +INFO: Initializing Wi-Fi middleware v0.1.0 +WARN: User defined assistance position has been set in Grenoble, FRANCE +New scan group for assisted scan +INFO: RP_TASK_GNSS - new scan group - task queued at 67363 + 0 +---- internal scan start ---- +---- internal scan start ---- + +INFO: ###### ===== MIDDLEWARE_1 EVENT ==== ###### +INFO: GNSS middleware event - SCAN DONE +SCAN_DONE info: +-- token: 0x00 +-- is_valid: 1 +-- number of valid scans: 2 +-- scan[0][1335859412] (10 SV): 828DF902020115F3419EA946F82DCFD0541F212495C26A6A6404B25231D4ECCDFBB2D50A + SV_ID 24: 45dB + SV_ID 31: 45dB + SV_ID 21: 44dB + SV_ID 86: 43dB + SV_ID 28: 43dB + SV_ID 83: 43dB + SV_ID 30: 42dB + SV_ID 100: 42dB + SV_ID 106: 41dB + SV_ID 95: 41dB +-- scan[1][1335859420] (10 SV): 828DF902020115F36BD0A956D42DCF5753DF0E219582C40A326F35FDC4A6CE45FFFA9808 + SV_ID 24: 46dB + SV_ID 100: 45dB + SV_ID 31: 44dB + SV_ID 21: 44dB + SV_ID 86: 44dB + SV_ID 28: 43dB + SV_ID 106: 42dB + SV_ID 83: 42dB + SV_ID 30: 42dB + SV_ID 95: 41dB +-- power consumption: 14 uah +-- mode: 1 +-- assisted: 1 +-- aiding position: (45.175781, 5.712891) +-- almanac CRC: 0X6A820509 + +---- internal TX DONE ---- +INFO: ###### ===== MIDDLEWARE_1 EVENT ==== ###### +INFO: GNSS middleware event - TERMINATED +TERMINATED info: +-- number of scans sent: 2 +``` + +### Execute and send first Wi-Fi scan + +``` +INFO: RP_TASK_WIFI - new scan - task queued at 358709 + 0 +---- internal Wi-Fi scan start ---- +INFO: start Wi-Fi scan + +INFO: ###### ===== MIDDLEWARE_2 EVENT ==== ###### +INFO: Wi-Fi middleware event - SCAN DONE +SCAN_DONE info: +-- number of results: 3 +-- power consumption: 0 uah +-- Timestamp: 1335859699 +64 70 02 D9 94 55 -- Channel: 1 -- Type: 1 -- RSSI: -76 +A4 3E 51 11 BC 15 -- Channel: 1 -- Type: 1 -- RSSI: -93 +30 7C B2 C8 52 26 -- Channel: 6 -- Type: 1 -- RSSI: -93 + +---- internal TX DONE ---- +INFO: ###### ===== MIDDLEWARE_2 EVENT ==== ###### +INFO: Wi-Fi middleware event - TERMINATED +TERMINATED info: +-- number of scans sent: 1 +New scan group for assisted scan +INFO: RP_TASK_GNSS - new scan group - task queued at 369925 + 30000 +``` \ No newline at end of file diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/main_geolocation_gnss_wifi.c b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/main_geolocation_gnss_wifi.c new file mode 100644 index 000000000..3cf653184 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/main_geolocation_gnss_wifi.c @@ -0,0 +1,631 @@ +/*! + * @ingroup apps_geolocation + * @file main_geolocation_gnss_wifi.c + * + * @brief LoRa Basics Modem LR11XX Geolocation GNSS and Wi-Fi example. + * + * @copyright + * @parblock + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + * @endparblock + */ + +/*! + * @addtogroup apps_geolocation + * LoRa Basics Modem LR11XX Geolocation example + * @{ + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "main_geolocation_gnss_wifi.h" +#include "lorawan_key_config.h" +#include "smtc_board.h" +#include "smtc_hal.h" +#include "apps_utilities.h" +#include "apps_modem_common.h" +#include "apps_modem_event.h" + +#include "gnss_middleware.h" +#include "wifi_middleware.h" +#include "lr11xx_system.h" + +#include "smtc_modem_api.h" +#include "smtc_modem_test_api.h" +#include "smtc_modem_utilities.h" +#include "smtc_board_ralf.h" + +#include +#include + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ +/** + * @brief LR11XX radio firmware + */ +#define LR1110_FW_VERSION 0x0307 +#define LR1110_FW_TYPE 0x01 +#define LR1120_FW_VERSION 0x0101 +#define LR1120_FW_TYPE 0x02 + +/** + * @brief Duration in second after last ALC sync response received to consider the local clock time invalid + * + * Set time valid for 1 day (to be fine tuned depending on board properties) + */ +#define APP_ALC_TIMING_INVALID ( 3600 * 24 ) + +/** + * @brief Interval in second between two consecutive ALC sync requests + * + * 3 time sync requests per day + */ +#define APP_ALC_TIMING_INTERVAL ( APP_ALC_TIMING_INVALID / 3 ) + +/** + * @brief Duration in second after Join to start the application keep alive sequence + */ +#define APP_TIMER_START ( 10 ) + +/** + * @brief Duration in second for keep alive alarm sequence + */ +#define APP_TIMER_KEEP_ALIVE ( 30 ) + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/*! + * @brief Stack identifier + */ +static uint8_t stack_id = 0; + +/*! + * @brief Modem radio + */ +static ralf_t* modem_radio; + +/*! + * @brief Wi-Fi output results + */ +static wifi_mw_event_data_scan_done_t wifi_results; + +/*! + * @brief First time sync status for application startup + */ +static bool is_first_time_sync = true; + +/*! + * @brief ADR custom list and retransmission definition for EU868 / IN865 / RU864 / AU915 / CN470 /AS923 / KR920 regions + */ +static const uint8_t adr_custom_list_dr5_dr3[16] = ADR_CUSTOM_LIST_DR5_DR3; +static const uint8_t custom_nb_trans_dr5_dr3 = CUSTOM_NB_TRANS_DR5_DR3; + +/*! + * @brief ADR custom list and retransmission definition for US915 region + */ +static const uint8_t adr_custom_list_us915[16] = ADR_CUSTOM_LIST_US915; +static const uint8_t custom_nb_trans_us915 = CUSTOM_NB_TRANS_US915; + +/*! + * @brief ADR custom list and retransmission definition for WW2G4 region + */ +static const uint8_t adr_custom_list_ww2g4[16] = ADR_CUSTOM_LIST_WW2G4; +static const uint8_t custom_nb_trans_ww2g4 = CUSTOM_NB_TRANS_WW2G4; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Helper function that configure the custom ADR configuration for geolocation scan & send, based on the region + * already configured in the stack. + * + * Prior using this function, the region must have been set already in the stack. + */ +static void configure_adr( void ); + +/*! + * @addtogroup basics_modem_evt_callback + * LoRa Basics Modem event callbacks + * @{ + */ + +/*! + * @brief Reset event callback + * + * @param [in] reset_count reset counter from the modem + */ +static void on_modem_reset( uint16_t reset_count ); + +/*! + * @brief Network Joined event callback + */ +static void on_modem_network_joined( void ); + +/*! + * @brief Alarm event callback + */ +static void on_modem_alarm( void ); + +/*! + * @brief Clock synchronisation event callback + */ +static void on_modem_clk_synch( smtc_modem_event_time_status_t time_status ); + +/*! + * @brief Downlink data event callback. + * + * @param [in] rssi RSSI in signed value in dBm + 64 + * @param [in] snr SNR signed value in 0.25 dB steps + * @param [in] rx_window RX window + * @param [in] port LoRaWAN port + * @param [in] payload Received buffer pointer + * @param [in] size Received buffer size + */ +static void on_modem_down_data( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ); + +/*! + * @brief GNSS Almanac update event callback + */ +static void on_modem_almanac_update( smtc_modem_event_almanac_update_status_t status ); + +/*! + * @brief GNSS middleware event callback + */ +static void on_middleware_gnss_event( uint8_t pending_events ); + +/*! + * @brief Wi-Fi middleware event callback + */ +static void on_middleware_wifi_event( uint8_t pending_events ); + +/*! + * @} + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +/** + * @brief Main application entry point. + */ +int main( void ) +{ + lr11xx_system_version_t lr11xx_fw_version; + lr11xx_status_t status; + static apps_modem_event_callback_t smtc_event_callback = { + .adr_mobile_to_static = NULL, + .alarm = on_modem_alarm, + .almanac_update = on_modem_almanac_update, + .down_data = on_modem_down_data, + .join_fail = NULL, + .joined = on_modem_network_joined, + .link_status = NULL, + .mute = NULL, + .new_link_adr = NULL, + .reset = on_modem_reset, + .set_conf = NULL, + .stream_done = NULL, + .time_updated_alc_sync = on_modem_clk_synch, + .tx_done = NULL, + .upload_done = NULL, + .user_radio_access = NULL, + .middleware_1 = on_middleware_gnss_event, + .middleware_2 = on_middleware_wifi_event, + }; + + /* Initialise the ralf_t object corresponding to the board */ + modem_radio = smtc_board_initialise_and_get_ralf( ); + + /* Disable IRQ to avoid unwanted behaviour during init */ + hal_mcu_disable_irq( ); + + /* Init board and peripherals */ + hal_mcu_init( ); + smtc_board_init_periph( ); + + /* Init the Lora Basics Modem event callbacks */ + apps_modem_event_init( &smtc_event_callback ); + + /* Init the modem and use apps_modem_event_process as event callback, please note that the callback will be called + * immediately after the first call to modem_run_engine because of the reset detection */ + smtc_modem_init( modem_radio, &apps_modem_event_process ); + + /* Re-enable IRQ */ + hal_mcu_enable_irq( ); + + HAL_DBG_TRACE_MSG( "\n" ); + HAL_DBG_TRACE_INFO( "###### ===== LoRa Basics Modem GNSS/Wi-Fi Geolocation example ==== ######\n\n" ); + apps_modem_common_display_lbm_version( ); + + /* Check LR11XX Firmware version */ + ASSERT_SMTC_MODEM_RC( smtc_modem_suspend_before_user_radio_access( ) ); /* protect from radio access conflicts */ + status = lr11xx_system_get_version( modem_radio->ral.context, &lr11xx_fw_version ); + ASSERT_SMTC_MODEM_RC( smtc_modem_resume_after_user_radio_access( ) ); + if( status != LR11XX_STATUS_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to get LR11XX firmware version\n" ); + mcu_panic( ); + } + if( ( ( lr11xx_fw_version.fw != LR1110_FW_VERSION ) && ( lr11xx_fw_version.type = LR1110_FW_TYPE ) ) && + ( ( lr11xx_fw_version.fw != LR1120_FW_VERSION ) && ( lr11xx_fw_version.type = LR1120_FW_TYPE ) ) ) + { + HAL_DBG_TRACE_ERROR( "Wrong LR11XX firmware version, expected 0x%04X, got 0x%04X\n", LR1110_FW_VERSION, + lr11xx_fw_version.fw ); + mcu_panic( ); + } + HAL_DBG_TRACE_INFO( "LR11XX FW : 0x%04X\n", lr11xx_fw_version.fw ); + + while( 1 ) + { + /* Execute modem runtime, this function must be called again in sleep_time_ms milliseconds or sooner. */ + uint32_t sleep_time_ms = smtc_modem_run_engine( ); + + /* go in low power */ + hal_mcu_set_sleep_for_ms( sleep_time_ms ); + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/*! + * @brief LoRa Basics Modem event callbacks called by smtc_event_process function + */ + +static void on_modem_reset( uint16_t reset_count ) +{ + apps_modem_common_configure_lorawan_params( stack_id ); + + /* Configure modem DM status for regular almanac status update */ + smtc_modem_dm_info_interval_format_t format = SMTC_MODEM_DM_INFO_INTERVAL_IN_DAY; + uint8_t interval = 1; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_set_info_interval( format, interval ) ); + + /* Active almanac update OTA - WARNING: will remove all other DM message */ + uint8_t info_field = SMTC_MODEM_DM_FIELD_ALMANAC_STATUS; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_set_info_fields( &info_field, 1 ) ); + + /* Start the Join process */ + ASSERT_SMTC_MODEM_RC( smtc_modem_join_network( stack_id ) ); + + HAL_DBG_TRACE_INFO( "###### ===== JOINING ==== ######\n\n" ); +} + +static void on_modem_network_joined( void ) +{ + /* Start time sync (ALC sync), necessary for GNSS scan: + The interval_s indicates how often the LBM will request a time sync from the DAS. + If no time sync downlink has been received from the DAS after the invalid_delay_s is elapsed, + the LBM will report SMTC_MODEM_RC_NO_TIME on smtc_modem_get_time() call. */ + /* -- */ + ASSERT_SMTC_MODEM_RC( smtc_modem_time_set_sync_interval_s( APP_ALC_TIMING_INTERVAL ) ); /* keep call order */ + ASSERT_SMTC_MODEM_RC( smtc_modem_time_set_sync_invalid_delay_s( APP_ALC_TIMING_INVALID ) ); /* keep call order */ + /* Start the service */ + ASSERT_SMTC_MODEM_RC( smtc_modem_time_start_sync_service( stack_id, SMTC_MODEM_TIME_ALC_SYNC ) ); +} + +static void on_modem_alarm( void ) +{ + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( GEOLOCATION_SCAN_PERIOD_DEFAULT ) ); + HAL_DBG_TRACE_PRINTF( "smtc_modem_alarm_start_timer: %d s\n\n", GEOLOCATION_SCAN_PERIOD_DEFAULT ); + + // mw_return_code_t gnss_rc; + // gnss_rc = gnss_mw_scan_start( GNSS_SCAN_MODE, 0 ); /* start ASAP */ + // if( gnss_rc != MW_RC_OK ) + // { + // HAL_DBG_TRACE_ERROR( "Failed to start GNSS scan\n" ); + // } +} + +static void on_modem_clk_synch( smtc_modem_event_time_status_t time_status ) +{ + mw_return_code_t gnss_rc; + mw_version_t mw_version; + + if( time_status != SMTC_MODEM_EVENT_TIME_NOT_VALID ) + { + if( is_first_time_sync == true ) + { + /* Set the custom ADR profile for geolocation scan & send */ + configure_adr( ); + + /* Initialize GNSS middleware */ + gnss_mw_get_version( &mw_version ); + HAL_DBG_TRACE_INFO( "Initializing GNSS middleware v%d.%d.%d\n", mw_version.major, mw_version.minor, + mw_version.patch ); + gnss_mw_init( modem_radio, stack_id ); + gnss_mw_set_constellations( GNSS_MW_CONSTELLATION_GPS_BEIDOU ); + + /* Initialize WIFI middleware */ + wifi_mw_get_version( &mw_version ); + HAL_DBG_TRACE_INFO( "Initializing Wi-Fi middleware v%d.%d.%d\n", mw_version.major, mw_version.minor, + mw_version.patch ); + wifi_mw_init( modem_radio, stack_id ); + + /* Set user defined assistance position */ +#if MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO == false + const char* assistance_position_text = MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT; + gnss_mw_set_user_aiding_position( MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT, + MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG ); + HAL_DBG_TRACE_WARNING( "User defined assistance position has been set in %s\n", assistance_position_text ); +#endif + + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( 5 ) ); + + /* Start the GNSS scan group sequence */ + gnss_rc = gnss_mw_scan_start( GNSS_SCAN_MODE, 0 ); /* start ASAP */ + if( gnss_rc != MW_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to start GNSS scan\n" ); + } + } + + is_first_time_sync = false; + } +} + +static void on_modem_down_data( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ) +{ + HAL_DBG_TRACE_INFO( "Downlink received:\n" ); + HAL_DBG_TRACE_INFO( " - LoRaWAN Fport = %d\n", port ); + HAL_DBG_TRACE_INFO( " - Payload size = %d\n", size ); + HAL_DBG_TRACE_INFO( " - RSSI = %d dBm\n", rssi - 64 ); + HAL_DBG_TRACE_INFO( " - SNR = %d dB\n", snr >> 2 ); + + if( size != 0 ) + { + HAL_DBG_TRACE_ARRAY( "Payload", payload, size ); + + /* Forward downlink to GNSS middleware to handle it if necessary */ + gnss_mw_handle_downlink( port, payload, size ); + } +} + +static void on_modem_almanac_update( smtc_modem_event_almanac_update_status_t status ) +{ + if( status == SMTC_MODEM_EVENT_ALMANAC_UPDATE_STATUS_REQUESTED ) + { + HAL_DBG_TRACE_INFO( "Almanac update is not completed: sending new request\n" ); +#if 0 + uint8_t dm_almanac_status = SMTC_MODEM_DM_FIELD_ALMANAC_STATUS; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_request_single_uplink( &dm_almanac_status, 1 ) ); +#endif + } + else + { + HAL_DBG_TRACE_INFO( "Almanac update is completed\n" ); + } +} + +static void on_middleware_gnss_event( uint8_t pending_events ) +{ + mw_return_code_t wifi_rc; + + /* Parse events */ + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_SCAN_DONE ) ) + { + gnss_mw_event_data_scan_done_t event_data; + + HAL_DBG_TRACE_INFO( "GNSS middleware event - SCAN DONE\n" ); + gnss_mw_get_event_data_scan_done( &event_data ); + gnss_mw_display_results( &event_data ); + + if( event_data.context.almanac_update_required ) + { + uint8_t dm_almanac_status = SMTC_MODEM_DM_FIELD_ALMANAC_STATUS; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_request_single_uplink( &dm_almanac_status, 1 ) ); + } + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_TERMINATED ) ) + { + gnss_mw_event_data_terminated_t event_data; + + HAL_DBG_TRACE_INFO( "GNSS middleware event - TERMINATED\n" ); + gnss_mw_get_event_data_terminated( &event_data ); + HAL_DBG_TRACE_PRINTF( "TERMINATED info:\n" ); + HAL_DBG_TRACE_PRINTF( "-- number of scans sent: %u\n", event_data.nb_scans_sent ); + HAL_DBG_TRACE_PRINTF( "-- aiding position check sent: %d\n", event_data.aiding_position_check_sent ); + HAL_DBG_TRACE_PRINTF( "-- indoor detected: %d\n", event_data.indoor_detected ); + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_SCAN_CANCELLED ) ) + { + HAL_DBG_TRACE_INFO( "GNSS middleware event - SCAN CANCELLED\n" ); + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_NO_TIME ) ) + { + HAL_DBG_TRACE_ERROR( "GNSS middleware event - ERROR NO TIME\n" ); + ASSERT_SMTC_MODEM_RC( smtc_modem_time_trigger_sync_request( stack_id ) ); + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_ALMANAC_UPDATE ) ) + { + HAL_DBG_TRACE_ERROR( "GNSS middleware event - ALMANAC UPDATE REQUIRED\n" ); + uint8_t dm_almanac_status = SMTC_MODEM_DM_FIELD_ALMANAC_STATUS; + ASSERT_SMTC_MODEM_RC( smtc_modem_dm_request_single_uplink( &dm_almanac_status, 1 ) ); + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_NO_AIDING_POSITION ) ) + { + HAL_DBG_TRACE_ERROR( "GNSS middleware event - ERROR NO AIDING POSITION set\n" ); + +/* Set user defined assistance position */ +#if MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO == false + const char* assistance_position_text = MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT; + gnss_mw_set_user_aiding_position( MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT, + MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG ); + HAL_DBG_TRACE_WARNING( "User defined assistance position has been set in %s\n", assistance_position_text ); +#endif + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_UNKNOWN ) ) + { + HAL_DBG_TRACE_ERROR( "GNSS middleware event - UNEXPECTED ERROR\n" ); + } + + /* Start Wifi scan */ + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_TERMINATED ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_NO_TIME ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_ALMANAC_UPDATE ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_NO_AIDING_POSITION ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_ERROR_UNKNOWN ) || + gnss_mw_has_event( pending_events, GNSS_MW_EVENT_SCAN_CANCELLED ) ) + { + wifi_rc = wifi_mw_scan_start( 0 ); + if( wifi_rc != MW_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to start WiFi scan\n" ); + } + } + + gnss_mw_clear_pending_events( ); +} + +static void on_middleware_wifi_event( uint8_t pending_events ) +{ + mw_return_code_t gnss_rc; + + /* Parse events */ + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_SCAN_DONE ) ) + { + HAL_DBG_TRACE_INFO( "Wi-Fi middleware event - SCAN DONE\n" ); + wifi_mw_get_event_data_scan_done( &wifi_results ); + wifi_mw_display_results( &wifi_results ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_TERMINATED ) ) + { + wifi_mw_event_data_terminated_t event_data; + + HAL_DBG_TRACE_INFO( "Wi-Fi middleware event - TERMINATED\n" ); + wifi_mw_get_event_data_terminated( &event_data ); + HAL_DBG_TRACE_PRINTF( "TERMINATED info:\n" ); + HAL_DBG_TRACE_PRINTF( "-- number of scans sent: %u\n", event_data.nb_scans_sent ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_SCAN_CANCELLED ) ) + { + HAL_DBG_TRACE_INFO( "Wi-Fi middleware event - SCAN CANCELLED\n" ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_ERROR_UNKNOWN ) ) + { + HAL_DBG_TRACE_INFO( "Wi-Fi middleware event - UNEXPECTED ERROR\n" ); + } + + /* Program next GNSS scan */ + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_TERMINATED ) || + wifi_mw_has_event( pending_events, WIFI_MW_EVENT_ERROR_UNKNOWN ) || + wifi_mw_has_event( pending_events, WIFI_MW_EVENT_SCAN_CANCELLED ) ) + { + gnss_rc = gnss_mw_scan_start( GNSS_SCAN_MODE, GEOLOCATION_SCAN_PERIOD ); + if( gnss_rc != MW_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to start GNSS scan\n" ); + } + } + + wifi_mw_clear_pending_events( ); +} + +void configure_adr( void ) +{ + smtc_modem_region_t region; + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_region( stack_id, ®ion ) ); + + /* Set the ADR profile once joined */ + switch( region ) + { + case SMTC_MODEM_REGION_EU_868: + case SMTC_MODEM_REGION_IN_865: + case SMTC_MODEM_REGION_RU_864: + case SMTC_MODEM_REGION_AU_915: + case SMTC_MODEM_REGION_AS_923_GRP1: + case SMTC_MODEM_REGION_AS_923_GRP2: + case SMTC_MODEM_REGION_AS_923_GRP3: + case SMTC_MODEM_REGION_CN_470: + case SMTC_MODEM_REGION_CN_470_RP_1_0: + case SMTC_MODEM_REGION_KR_920: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_dr5_dr3 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_dr5_dr3 ) ); + break; + case SMTC_MODEM_REGION_US_915: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_us915 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_us915 ) ); + break; + case SMTC_MODEM_REGION_WW2G4: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_ww2g4 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_ww2g4 ) ); + break; + default: + HAL_DBG_TRACE_ERROR( "Region not supported in this example, could not set custom ADR profile\n" ); + break; + } + + /* Disable auto switch to network controlled after a certain amount of TX without RX */ + ASSERT_SMTC_MODEM_RC( smtc_modem_connection_timeout_set_thresholds( stack_id, 0, 0 ) ); +} + +/*! + * @} + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/main_geolocation_gnss_wifi.h b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/main_geolocation_gnss_wifi.h new file mode 100644 index 000000000..0f70b137e --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_gnss_wifi/main_geolocation_gnss_wifi.h @@ -0,0 +1,155 @@ +/*! + * @ingroup apps_geolocation + * @file main_geolocation_gnss_wifi.h + * + * @brief LoRa Basics Modem LR11XX Geolocation GNSS and Wi-Fi example. + * + * @copyright + * @parblock + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + * @endparblock + */ + +/*! + * @addtogroup apps_geolocation + * LoRa Basics Modem LR11XX Geolocation example + * @{ + */ + +#ifndef MAIN_GEOLOCATION_GNSS_WIFI_H +#define MAIN_GEOLOCATION_GNSS_WIFI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * ----------------------------------------------------------------------------- + * --- Application Configuration ----------------------------------------------- + */ + +/** + * @brief Let the application autonomously set the assistance position for GNSS geolocation (default, autonomous mode) + */ +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO_DEFAULT false + +/** + * @brief User defined assistance position for GNSS geolocation (not used by default, autonomous mode) + */ +// #define MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT_DEFAULT ( 45.181454 ) +// #define MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG_DEFAULT ( 5.720893 ) +// #define MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT_DEFAULT "Grenoble, FRANCE" +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT_DEFAULT ( 22.576814 ) +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG_DEFAULT ( 113.922068 ) +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT_DEFAULT "Shenzhen, CHINA" + +/* + * ----------------------------------------------------------------------------- + * --- Geolocation scan group Configuration ------------------------------------ + */ + +/*! + * @brief Defines the delay before starting the next scan sequence, value in [s]. + */ +#define GEOLOCATION_SCAN_PERIOD_DEFAULT 60 + +/*! + * @brief Defines the scan mode used for GNSS (STATIC or MOBILE). + */ +#define GNSS_SCAN_MODE_DEFAULT GNSS_MW_MODE_MOBILE + +#ifndef MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO_DEFAULT +#endif // MODEM_EXAMPLE_ASSISTANCE_POSITION_AUTO + +#ifndef MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT_DEFAULT +#endif // MODEM_EXAMPLE_ASSISTANCE_POSITION_LAT + +#ifndef MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG_DEFAULT +#endif // MODEM_EXAMPLE_ASSISTANCE_POSITION_LONG + +#ifndef MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT +#define MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT_DEFAULT +#endif // MODEM_EXAMPLE_ASSISTANCE_POSITION_TEXT + +#ifndef GEOLOCATION_SCAN_PERIOD +#define GEOLOCATION_SCAN_PERIOD GEOLOCATION_SCAN_PERIOD_DEFAULT +#endif // GEOLOCATION_SCAN_PERIOD + +#ifndef GNSS_SCAN_MODE +#define GNSS_SCAN_MODE GNSS_SCAN_MODE_DEFAULT +#endif // GNSS_SCAN_MODE + +/* + * ----------------------------------------------------------------------------- + * --- LoRaWAN Configuration --------------------------------------------------- + */ + +/*! + * @brief ADR custom list and retransmission parameters for EU868 / IN865 / RU864 / AU915 / CN470 /AS923 / KR920 regions + */ +#define CUSTOM_NB_TRANS_DR5_DR3 2 +#define ADR_CUSTOM_LIST_DR5_DR3 \ + { \ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3 \ + } /* 125kHz - SF7, SF8, SF9 */ + +/*! + * @brief ADR custom list and retransmission parameters for US915 region + */ +#define CUSTOM_NB_TRANS_US915 2 +#define ADR_CUSTOM_LIST_US915 \ + { \ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1 \ + } /* 125kHz - SF7, SF8, SF9 */ + +/*! + * @brief ADR custom list and retransmission parameters for WW2G4 region + */ +#define CUSTOM_NB_TRANS_WW2G4 2 +#define ADR_CUSTOM_LIST_WW2G4 \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ + } /* SF12 */ + +#ifdef __cplusplus +} +#endif + +#endif // MAIN_GEOLOCATION_GNSS_WIFI_H + +/*! + * @} + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/README.md b/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/README.md new file mode 100644 index 000000000..831c8c2e6 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/README.md @@ -0,0 +1,179 @@ +# Geolocation Wi-Fi example application + +## Description + +This application demonstrates the usage of the Wi-Fi geolocation middleware and +how the LoRa Basics Modem should be configured to meet the prerequisites for +This example illustrates the Wi-Fi scan procedure: + +- configuration of the LoRa Basics Modem library; and +- execution of Wi-Fi *scan & send* feature using the *Wi-Fi geolocation middleware*. + +## Wi-Fi middleware + +The Wi-Fi middleware is a software layer used to simplify the integration of a +Wi-Fi scan & send operation in a user application. + +For more details about the middleware, please refer to the documentation here: + +[Geolocation middleware documentation](<../../../geolocation_middleware/readme.md>) + +## LoRa Basics Modem configuration + +There is no particular modem configuration prerequisite for Wi-Fi scanning. + +Once the modem has joined the network, the application will configure the ADR +custom profile for the selected region, disable the modem auto-switch to network +controlled ADR and finally initialize the Wi-Fi middleware, and immediately +initiate the first Wi-Fi scan & send. + +## Wi-Fi middleware events + +After the initial scan has been programmed, the application relies on the events +sent by the Wi-Fi middleware to progress in the scan sequence. + +For this, the application defines a callback function `on_middleware_wifi_event()` +that will be called when the Wi-Fi middleware sends an event. + +In this callback function, the application will process the pending events, and +for any event except the `WIFI_MW_EVENT_SCAN_DONE` event, it will program the +next scan with the delay defined by `WIFI_SCAN_GROUP_PERIOD`. + +## Configuration + +When compiling with *arm-none-eabi-gcc* toolchain, all these constant are configurable through command line with the `EXTRAFLAGS`. +See [README.md](../../../README.md#command-line-configuration). + +### LoRaWAN related configuration + +The `apps/common/lorawan_key_config.h` header file defines several constants to +configure the LoRaWAN parameters (profile, region, keys). + +| Constant | Comments | Possible values | Default Value | +| ---------------- | -------------------------------- | -------------------- | -------------------------- | +| `LORAWAN_REGION` | Selects the regulatory region | See here-below | `SMTC_MODEM_REGION_EU_868` | +| `LORAWAN_CLASS` | Selects the LoRaWAN class to use | `SMTC_MODEM_CLASS_A` | `SMTC_MODEM_CLASS_A` | + +Supported values for `LORAWAN_REGION`: + +* `SMTC_MODEM_REGION_EU_868 (default)` +* `SMTC_MODEM_REGION_US_915` +* `SMTC_MODEM_REGION_CN_470_RP_1_0` + +Supported values for `LORAWAN_CLASS`: + +* `SMTC_MODEM_CLASS_A` + +### Wi-Fi demonstration related configuration + +The `main_geolocation_wifi.h` header file defines several constants which can be +set to define the configurable parameters of the application. + +| Constant | Comments | Possible values | Default Value | +| ------------------ | --------------------------------------------------------------------------------------------- | --------------- | ------------- | +| `WIFI_SCAN_PERIOD` | Defines the duration between the end of a scan & send sequence and the start of next sequence | `uint32_t` | 30 | +## Build + +The demo can be built through GNU make command by doing the following: + +```shell +# Navigate to the build folder +$ cd apps/geolocation_wifi/makefile + +# Execute the make call +$ make -j +``` + +By default, the demonstration is compiled to use the EUIs and Application key +defined in the file *apps/common/lorawan_key_config.h*. + +## Usage + +### Serial console + +The application requires no user intervention after the static configuration +option have been set. + +Information messages are displayed on the serial console, starting with the +DevEUI, AppEUI/JoinEUI and PIN that you might need in order to register your +device with the LoRa Cloud Device Join service. + +### LoRaWAN Network Server / Application Server + +This application needs an Application Server to run in order to perform the Wi-Fi +solving. +Two possibilities : +* A Node-Red application server is provided in folder *apps/geolocation_application_server*. +Refer to the readme in this folder for details about setup and usage of the +application server. +* Use the LoRaCloud Locator https://atk.loracloud.com/ which embed a complete integration of an application server and an associated dashboard. + +### ADR configuration + +The Adaptive Data Rate (ADR) is configured in *Custom ADR profile* with datarate +distribution and number of repetition defined per regions. + +For more details about ADR configuration for geolocation middleware usage, +please refer to this [Geolocation middleware documentation](<../../../geolocation_middleware/doc/geolocationMiddleware.rst>), section "LoRaWAN datarate considerations". + +The actual datarate and number of retransmission values are defined in the +`main_geolocation_wifi.h` header file. + +| Constant | Comments | Default value | +| ----------------------- | -------------------------------------------------------- | ------------------------------------ | +| `CUSTOM_NB_TRANS_EU868` | The number of retransmission to be used for EU868 region | 1 | +| `ADR_CUSTOM_LIST_EU868` | The custom ADR list to be used for EU868 region | '5' repeated 9 times, '4' x5, '3' x2 | +| `CUSTOM_NB_TRANS_US915` | The number of retransmission to be used for US915 region | 2 | +| `ADR_CUSTOM_LIST_US915` | The custom ADR list to be used for US915 region | '5' repeated 9 times, '4' x5, '3' x2 | +| `CUSTOM_NB_TRANS_CN470` | The number of retransmission to be used for CN470 region | 2 | +| `ADR_CUSTOM_LIST_CN470` | The custom ADR list to be used for CN470 region | '3' repeated 9 times, '2' x5, '1' x2 | + +The values must be carefully set to match with duty cycle constraints, power +consumption targets etc... + +## Expected Behavior + +Here follow the steps that shall be seen in the logs to indicate the expected behavior of the application. + +### Device starts and resets + +``` +INFO: Modem Initialization + +INFO: ===== LoRa Basics Modem Geolocation Wi-Fi example ===== +``` + +Following this print you shall find application and parameter prints + +### Joined the network + +At first run no time is supposed to be available + +``` +INFO: ###### ===== JOINED EVENT ==== ###### +``` + +### Execute and send the Wi-Fi scan + +``` +INFO: RP_TASK_WIFI - new scan - task queued at 40097 + 30000 +---- internal Wi-Fi scan start ---- +INFO: start Wi-Fi scan +WARN: No time available. + +INFO: ###### ===== MIDDLEWARE_2 EVENT ==== ###### +INFO: Wi-Fi middleware event - SCAN DONE +SCAN_DONE info: +-- number of results: 3 +-- power consumption: 0 uah +-- Timestamp: 0 +64 70 02 D9 94 55 -- Channel: 1 -- Type: 1 -- RSSI: -78 +3C 17 10 B7 CD 90 -- Channel: 6 -- Type: 1 -- RSSI: -88 +74 B6 B6 42 B4 EB -- Channel: 1 -- Type: 2 -- RSSI: -87 + +---- internal TX DONE ---- +INFO: ###### ===== MIDDLEWARE_2 EVENT ==== ###### +INFO: Wi-Fi middleware event - TERMINATED +TERMINATED info: +-- number of scans sent: 1 +``` \ No newline at end of file diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/main_geolocation_wifi.c b/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/main_geolocation_wifi.c new file mode 100644 index 000000000..e17f55264 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/main_geolocation_wifi.c @@ -0,0 +1,409 @@ +/*! + * @ingroup apps_geolocation + * @file main_geolocation_wifi.c + * + * @brief LoRa Basics Modem LR11XX Geolocation Wi-Fi example + * + * @copyright + * @parblock + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + * @endparblock + + */ + +/*! + * @addtogroup apps_geolocation + * LoRa Basics Modem LR11XX Geolocation Wi-Fi example + * @{ + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "main_geolocation_wifi.h" +#include "smtc_board.h" +#include "smtc_hal.h" +#include "apps_utilities.h" +#include "apps_modem_common.h" +#include "apps_modem_event.h" + +#include "wifi_middleware.h" +#include "lr11xx_system.h" + +#include "smtc_modem_utilities.h" +#include "smtc_board_ralf.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ +/** + * @brief LR11XX radio firmware + */ +#define LR1110_FW_VERSION 0x0307 +#define LR1110_FW_TYPE 0x01 +#define LR1120_FW_VERSION 0x0101 +#define LR1120_FW_TYPE 0x02 + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/*! + * @brief Stack identifier + */ +static uint8_t stack_id = 0; + +/*! + * @brief Modem radio + */ +static ralf_t* modem_radio; + +/*! + * @brief Wi-Fi output results + */ +static wifi_mw_event_data_scan_done_t wifi_results; + +/*! + * @brief ADR custom list and retransmission definition for EU868 / IN865 / RU864 / AU915 / CN470 /AS923 / KR920 regions + */ +static const uint8_t adr_custom_list_dr5_dr3[16] = ADR_CUSTOM_LIST_DR5_DR3; +static const uint8_t custom_nb_trans_dr5_dr3 = CUSTOM_NB_TRANS_DR5_DR3; + +/*! + * @brief ADR custom list and retransmission definition for US915 region + */ +static const uint8_t adr_custom_list_us915[16] = ADR_CUSTOM_LIST_US915; +static const uint8_t custom_nb_trans_us915 = CUSTOM_NB_TRANS_US915; + +/*! + * @brief ADR custom list and retransmission definition for WW2G4 region + */ +static const uint8_t adr_custom_list_ww2g4[16] = ADR_CUSTOM_LIST_WW2G4; +static const uint8_t custom_nb_trans_ww2g4 = CUSTOM_NB_TRANS_WW2G4; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Helper function that configure the custom ADR configuration for geolocation scan & send, based on the region + * already configured in the stack. + * + * Prior using this function, the region must have been set already in the stack. + */ +static void configure_adr( void ); + +/*! + * @addtogroup basics_modem_evt_callback + * LoRa Basics Modem event callbacks + * @{ + */ + +/*! + * @brief Reset event callback + * + * @param [in] reset_count reset counter from the modem + */ +static void on_modem_reset( uint16_t reset_count ); + +/*! + * @brief Network Joined event callback + */ +static void on_modem_network_joined( void ); + +/*! + * @brief Alarm event callback + */ +static void on_modem_alarm( void ); + +/*! + * @brief Wi-Fi middleware event callback + */ +static void on_middleware_wifi_event( uint8_t pending_events ); + +/*! + * @} + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +/** + * @brief Main application entry point. + */ +int main( void ) +{ + lr11xx_system_version_t lr11xx_fw_version; + lr11xx_status_t status; + static apps_modem_event_callback_t smtc_event_callback = { + .adr_mobile_to_static = NULL, + .alarm = on_modem_alarm, + .almanac_update = NULL, + .down_data = NULL, + .join_fail = NULL, + .joined = on_modem_network_joined, + .link_status = NULL, + .mute = NULL, + .new_link_adr = NULL, + .reset = on_modem_reset, + .set_conf = NULL, + .stream_done = NULL, + .time_updated_alc_sync = NULL, + .tx_done = NULL, + .upload_done = NULL, + .user_radio_access = NULL, + .middleware_1 = NULL, + .middleware_2 = on_middleware_wifi_event, + }; + + /* Initialise the ralf_t object corresponding to the board */ + modem_radio = smtc_board_initialise_and_get_ralf( ); + + /* Disable IRQ to avoid unwanted behaviour during init */ + hal_mcu_disable_irq( ); + + /* Init board and peripherals */ + hal_mcu_init( ); + smtc_board_init_periph( ); + + /* Init the Lora Basics Modem event callbacks */ + apps_modem_event_init( &smtc_event_callback ); + + /* Init the modem and use smtc_event_process as event callback, please note that the callback will be called + * immediately after the first call to modem_run_engine because of the reset detection */ + smtc_modem_init( modem_radio, &apps_modem_event_process ); + + /* Re-enable IRQ */ + hal_mcu_enable_irq( ); + + HAL_DBG_TRACE_MSG( "\n" ); + HAL_DBG_TRACE_INFO( "###### ===== LoRa Basics Modem Geolocation Wi-Fi example ==== ######\n\n" ); + apps_modem_common_display_lbm_version( ); + + /* Check LR11XX Firmware version */ + ASSERT_SMTC_MODEM_RC( smtc_modem_suspend_before_user_radio_access( ) ); /* protect from radio access conflicts */ + status = lr11xx_system_get_version( modem_radio->ral.context, &lr11xx_fw_version ); + ASSERT_SMTC_MODEM_RC( smtc_modem_resume_after_user_radio_access( ) ); + + if( status != LR11XX_STATUS_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to get LR11XX firmware version\n" ); + mcu_panic( ); + } + if( ( ( lr11xx_fw_version.fw != LR1110_FW_VERSION ) && ( lr11xx_fw_version.type = LR1110_FW_TYPE ) ) && + ( ( lr11xx_fw_version.fw != LR1120_FW_VERSION ) && ( lr11xx_fw_version.type = LR1120_FW_TYPE ) ) ) + { + HAL_DBG_TRACE_ERROR( "Wrong LR11XX firmware version, expected 0x%04X, got 0x%04X\n", LR1110_FW_VERSION, + lr11xx_fw_version.fw ); + mcu_panic( ); + } + HAL_DBG_TRACE_INFO( "LR11XX FW : 0x%04X\n", lr11xx_fw_version.fw ); + + while( 1 ) + { + /* Execute modem runtime, this function must be called again in sleep_time_ms milliseconds or sooner. */ + uint32_t sleep_time_ms = smtc_modem_run_engine( ); + + /* go in low power */ + hal_mcu_set_sleep_for_ms( sleep_time_ms ); + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/*! + * @brief LoRa Basics Modem event callbacks called by smtc_event_process function + */ + +static void on_modem_reset( uint16_t reset_count ) +{ + /* Basic LoRaWAN configuration */ + apps_modem_common_configure_lorawan_params( stack_id ); + + /* Start the Join process */ + ASSERT_SMTC_MODEM_RC( smtc_modem_join_network( stack_id ) ); + + HAL_DBG_TRACE_INFO( "###### ===== JOINING ==== ######\n\n" ); +} + +static void on_modem_network_joined( void ) +{ + mw_return_code_t wifi_rc; + mw_version_t mw_version; + + /* Set the custom ADR profile for geolocation scan & send */ + configure_adr( ); + + /* Initialize Wi-Fi middleware */ + wifi_mw_get_version( &mw_version ); + HAL_DBG_TRACE_INFO( "Initializing Wi-Fi middleware v%d.%d.%d\n", mw_version.major, mw_version.minor, + mw_version.patch ); + wifi_mw_init( modem_radio, stack_id ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( 5 ) ); + + /* Start the Wi-Fi scan sequence */ + wifi_rc = wifi_mw_scan_start( 0 ); + if( wifi_rc != MW_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to start WiFi scan\n" ); + } +} + +static void on_modem_alarm( void ) +{ + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( WIFI_SCAN_PERIOD ) ); + HAL_DBG_TRACE_PRINTF( "smtc_modem_alarm_start_timer: %d s\n\n", WIFI_SCAN_PERIOD ); + + // mw_return_code_t wifi_rc; + // wifi_rc = wifi_mw_scan_start( 0 ); + // if( wifi_rc != MW_RC_OK ) + // { + // HAL_DBG_TRACE_ERROR( "Failed to start WiFi scan\n" ); + // } +} + +/*! + * @brief User private function + */ + +static void on_middleware_wifi_event( uint8_t pending_events ) +{ + mw_return_code_t wifi_rc; + + /* Parse events */ + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_SCAN_DONE ) ) + { + HAL_DBG_TRACE_INFO( "Wi-Fi middleware event - SCAN DONE\n" ); + wifi_mw_get_event_data_scan_done( &wifi_results ); + wifi_mw_display_results( &wifi_results ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_TERMINATED ) ) + { + wifi_mw_event_data_terminated_t event_data; + + HAL_DBG_TRACE_INFO( "Wi-Fi middleware event - TERMINATED\n" ); + wifi_mw_get_event_data_terminated( &event_data ); + HAL_DBG_TRACE_PRINTF( "TERMINATED info:\n" ); + HAL_DBG_TRACE_PRINTF( "-- number of scans sent: %u\n", event_data.nb_scans_sent ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_SCAN_CANCELLED ) ) + { + HAL_DBG_TRACE_INFO( "Wi-Fi middleware event - SCAN CANCELLED\n" ); + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_ERROR_UNKNOWN ) ) + { + HAL_DBG_TRACE_INFO( "Wi-Fi middleware event - UNEXPECTED ERROR\n" ); + } + + /* Program next scan */ + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_ERROR_UNKNOWN ) || + wifi_mw_has_event( pending_events, WIFI_MW_EVENT_TERMINATED ) ) + { + /* Program the next Wi-Fi group */ + wifi_rc = wifi_mw_scan_start( WIFI_SCAN_PERIOD ); + if( wifi_rc != MW_RC_OK ) + { + HAL_DBG_TRACE_ERROR( "Failed to start WiFi scan\n" ); + } + } + + wifi_mw_clear_pending_events( ); +} + +void configure_adr( void ) +{ + smtc_modem_region_t region; + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_region( stack_id, ®ion ) ); + + /* Set the ADR profile once joined */ + switch( region ) + { + case SMTC_MODEM_REGION_EU_868: + case SMTC_MODEM_REGION_IN_865: + case SMTC_MODEM_REGION_RU_864: + case SMTC_MODEM_REGION_AU_915: + case SMTC_MODEM_REGION_AS_923_GRP1: + case SMTC_MODEM_REGION_AS_923_GRP2: + case SMTC_MODEM_REGION_AS_923_GRP3: + case SMTC_MODEM_REGION_CN_470: + case SMTC_MODEM_REGION_CN_470_RP_1_0: + case SMTC_MODEM_REGION_KR_920: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_dr5_dr3 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_dr5_dr3 ) ); + break; + case SMTC_MODEM_REGION_US_915: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_us915 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_us915 ) ); + break; + case SMTC_MODEM_REGION_WW2G4: + ASSERT_SMTC_MODEM_RC( + smtc_modem_adr_set_profile( stack_id, SMTC_MODEM_ADR_PROFILE_CUSTOM, adr_custom_list_ww2g4 ) ); + ASSERT_SMTC_MODEM_RC( smtc_modem_set_nb_trans( stack_id, custom_nb_trans_ww2g4 ) ); + break; + default: + HAL_DBG_TRACE_ERROR( "Region not supported in this example, could not set custom ADR profile\n" ); + break; + } + + /* Disable auto switch to network controlled after a certain amount of TX without RX */ + ASSERT_SMTC_MODEM_RC( smtc_modem_connection_timeout_set_thresholds( stack_id, 0, 0 ) ); +} + +/*! + * @} + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/main_geolocation_wifi.h b/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/main_geolocation_wifi.h new file mode 100644 index 000000000..10bf0b139 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/geolocation_wifi/main_geolocation_wifi.h @@ -0,0 +1,110 @@ +/*! + * @ingroup apps_geolocation + * @file main_geolocation_wifi.h + * + * @brief LoRa Basics Modem LR11XX Geolocation Wi-Fi example + * + * @copyright + * @parblock + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + * @endparblock + */ + +/*! + * @addtogroup apps_geolocation + * LoRa Basics Modem LR11XX Geolocation example + * @{ + */ + +#ifndef MAIN_GEOLOCATION_WIFI_H +#define MAIN_GEOLOCATION_WIFI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * ----------------------------------------------------------------------------- + * --- Application Configuration ----------------------------------------------- + */ + +/*! + * @brief Defines the delay before starting a new Wi-Fi scan, value in [s]. + */ +#define WIFI_SCAN_PERIOD_DEFAULT 60 + +/* + * ----------------------------------------------------------------------------- + * --- LoRaWAN Configuration --------------------------------------------------- + */ + +/*! + * @brief ADR custom list and retransmission parameters for EU868 / IN865 / RU864 / AU915 / CN470 /AS923 / KR920 regions + */ +#define CUSTOM_NB_TRANS_DR5_DR3 1 +#define ADR_CUSTOM_LIST_DR5_DR3 \ + { \ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3 \ + } /* 125kHz - SF7, SF8, SF9 */ + +/*! + * @brief ADR custom list and retransmission parameters for US915 region + */ +#define CUSTOM_NB_TRANS_US915 1 +#define ADR_CUSTOM_LIST_US915 \ + { \ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1 \ + } /* 125kHz - SF7, SF8, SF9 */ + +/*! + * @brief ADR custom list and retransmission parameters for WW2G4 region + */ +#define CUSTOM_NB_TRANS_WW2G4 1 +#define ADR_CUSTOM_LIST_WW2G4 \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ + } /* SF12 */ + +#ifndef WIFI_SCAN_PERIOD +#define WIFI_SCAN_PERIOD WIFI_SCAN_PERIOD_DEFAULT +#endif // WIFI_SCAN_PERIOD + +#ifdef __cplusplus +} +#endif + +#endif // MAIN_GEOLOCATION_WIFI_H + +/*! + * @} + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/lorawan/README.md b/lr1110/lr1110/seeed/apps/examples/lorawan/README.md new file mode 100644 index 000000000..e03c847e8 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/lorawan/README.md @@ -0,0 +1,21 @@ +# LoRa Basics Modem LoRaWAN Class A/C example + +## Description + +The application will automatically starts a procedure to join a LoRaWAN network (see [configuration](../../apps/common/lorawan_key_config.h)). + +Once a network is joined (i.e. when the corresponding event is triggered), uplinks are sent periodically. This periodic action is based on the LoRa Basics Modem alarm functionality. Each time the alarm-related event is triggered, the application requests an uplink. + +The content of the uplink is the value read out from the charge counter by calling `smtc_modem_get_charge()`. + +The application is also capable of displaying data and meta-data of a received downlink. + +## Configuration + +Several parameters can be updated in `main_lorawan.h` header file: + +| Constant | Description | +| -------------------------- | ----------------------------------------------------------------------------- | +| `LORAWAN_APP_PORT` | LoRaWAN FPort used for the uplink messages | +| `LORAWAN_CONFIRMED_MSG_ON` | Request a confirmation from the LNS that the uplink message has been received | +| `APP_TX_DUTYCYCLE` | Delay in second between two uplinks | diff --git a/lr1110/lr1110/seeed/apps/examples/lorawan/main_lorawan.c b/lr1110/lr1110/seeed/apps/examples/lorawan/main_lorawan.c new file mode 100644 index 000000000..99cbfdf05 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/lorawan/main_lorawan.c @@ -0,0 +1,333 @@ +/*! + * @file main_lorawan.c + * + * @brief LoRa Basics Modem Class A/C device implementation + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "main_lorawan.h" +#include "lorawan_key_config.h" +#include "smtc_board.h" +#include "smtc_hal.h" +#include "apps_modem_common.h" +#include "apps_modem_event.h" +#include "smtc_modem_api.h" +#include "device_management_defs.h" +#include "smtc_board_ralf.h" +#include "apps_utilities.h" +#include "smtc_modem_utilities.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/*! + * @brief Stringify constants + */ +#define xstr( a ) str( a ) +#define str( a ) #a + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/*! + * @brief Stack identifier + */ +static uint8_t stack_id = 0; + +/*! + * @brief User application data + */ +static uint8_t app_data_buffer[LORAWAN_APP_DATA_MAX_SIZE]; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Send an application frame on LoRaWAN port defined by LORAWAN_APP_PORT + * + * @param [in] buffer Buffer containing the LoRaWAN buffer + * @param [in] length Payload length + * @param [in] confirmed Send a confirmed or unconfirmed uplink [false : unconfirmed / true : confirmed] + */ +static void send_frame( const uint8_t* buffer, const uint8_t length, const bool confirmed ); + +/*! + * @brief Parse the received downlink + * + * @remark Demonstrates how a TLV-encoded command sequence received by downlink can control the state of an LED. It can + * easily be extended to handle other commands received on the same port or another port. + * + * @param [in] port LoRaWAN port + * @param [in] payload Payload Buffer + * @param [in] size Payload size + */ +static void parse_downlink_frame( uint8_t port, const uint8_t* payload, uint8_t size ); + +/*! + * @brief Reset event callback + * + * @param [in] reset_count reset counter from the modem + */ +static void on_modem_reset( uint16_t reset_count ); + +/*! + * @brief Network Joined event callback + */ +static void on_modem_network_joined( void ); + +/*! + * @brief Alarm event callback + */ +static void on_modem_alarm( void ); + +/*! + * @brief Tx done event callback + * + * @param [in] status tx done status @ref smtc_modem_event_txdone_status_t + */ +static void on_modem_tx_done( smtc_modem_event_txdone_status_t status ); + +/*! + * @brief Downlink data event callback. + * + * @param [in] rssi RSSI in signed value in dBm + 64 + * @param [in] snr SNR signed value in 0.25 dB steps + * @param [in] rx_window RX window + * @param [in] port LoRaWAN port + * @param [in] payload Received buffer pointer + * @param [in] size Received buffer size + */ +static void on_modem_down_data( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ); + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +/** + * @brief Main application entry point. + */ +int main( void ) +{ + static apps_modem_event_callback_t smtc_event_callback = { + .adr_mobile_to_static = NULL, + .alarm = on_modem_alarm, + .almanac_update = NULL, + .down_data = on_modem_down_data, + .join_fail = NULL, + .joined = on_modem_network_joined, + .link_status = NULL, + .mute = NULL, + .new_link_adr = NULL, + .reset = on_modem_reset, + .set_conf = NULL, + .stream_done = NULL, + .time_updated_alc_sync = NULL, + .tx_done = on_modem_tx_done, + .upload_done = NULL, + }; + + /* Initialise the ralf_t object corresponding to the board */ + ralf_t* modem_radio = smtc_board_initialise_and_get_ralf( ); + + /* Disable IRQ to avoid unwanted behaviour during init */ + hal_mcu_disable_irq( ); + + /* Init board and peripherals */ + hal_mcu_init( ); + smtc_board_init_periph( ); + + /* Init the Lora Basics Modem event callbacks */ + apps_modem_event_init( &smtc_event_callback ); + + /* Init the modem and use apps_modem_event_process as event callback, please note that the callback will be called + * immediately after the first call to modem_run_engine because of the reset detection */ + smtc_modem_init( modem_radio, &apps_modem_event_process ); + + /* Re-enable IRQ */ + hal_mcu_enable_irq( ); + + HAL_DBG_TRACE_MSG( "\n" ); + HAL_DBG_TRACE_INFO( "###### ===== LoRa Basics Modem LoRaWAN Class A/C demo application ==== ######\n\n" ); + + /* LoRa Basics Modem Version */ + apps_modem_common_display_lbm_version( ); + + /* Configure the partial low power mode */ + hal_mcu_partial_sleep_enable( APP_PARTIAL_SLEEP ); + + while( 1 ) + { + /* Execute modem runtime, this function must be called again in sleep_time_ms milliseconds or sooner. */ + uint32_t sleep_time_ms = smtc_modem_run_engine( ); + + /* go in low power */ + hal_mcu_set_sleep_for_ms( sleep_time_ms ); + } +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +static void on_modem_reset( uint16_t reset_count ) +{ + HAL_DBG_TRACE_INFO( "Application parameters:\n" ); + HAL_DBG_TRACE_INFO( " - LoRaWAN uplink Fport = %d\n", LORAWAN_APP_PORT ); + HAL_DBG_TRACE_INFO( " - DM report interval = %d\n", APP_TX_DUTYCYCLE ); + HAL_DBG_TRACE_INFO( " - Confirmed uplink = %s\n", ( LORAWAN_CONFIRMED_MSG_ON == true ) ? "Yes" : "No" ); + + apps_modem_common_configure_lorawan_params( stack_id ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_join_network( stack_id ) ); +} + +static void on_modem_network_joined( void ) +{ + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( APP_TX_DUTYCYCLE ) ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_adr_set_profile( stack_id, LORAWAN_DEFAULT_DATARATE, adr_custom_list ) ); +} + +static void on_modem_alarm( void ) +{ + smtc_modem_status_mask_t modem_status; + uint32_t charge = 0; + uint8_t app_data_size = 0; + + /* Schedule next packet transmission */ + ASSERT_SMTC_MODEM_RC( smtc_modem_alarm_start_timer( APP_TX_DUTYCYCLE ) ); + HAL_DBG_TRACE_PRINTF( "smtc_modem_alarm_start_timer: %d s\n\n", APP_TX_DUTYCYCLE ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_status( stack_id, &modem_status ) ); + modem_status_to_string( modem_status ); + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_charge( &charge ) ); + + app_data_buffer[app_data_size++] = ( uint8_t ) ( charge ); + app_data_buffer[app_data_size++] = ( uint8_t ) ( charge >> 8 ); + app_data_buffer[app_data_size++] = ( uint8_t ) ( charge >> 16 ); + app_data_buffer[app_data_size++] = ( uint8_t ) ( charge >> 24 ); + + send_frame( app_data_buffer, app_data_size, LORAWAN_CONFIRMED_MSG_ON ); +} + +static void on_modem_tx_done( smtc_modem_event_txdone_status_t status ) +{ + static uint32_t uplink_count = 0; + + HAL_DBG_TRACE_INFO( "Uplink count: %d\n", ++uplink_count ); +} + +static void on_modem_down_data( int8_t rssi, int8_t snr, smtc_modem_event_downdata_window_t rx_window, uint8_t port, + const uint8_t* payload, uint8_t size ) +{ + HAL_DBG_TRACE_INFO( "Downlink received:\n" ); + HAL_DBG_TRACE_INFO( " - LoRaWAN Fport = %d\n", port ); + HAL_DBG_TRACE_INFO( " - Payload size = %d\n", size ); + HAL_DBG_TRACE_INFO( " - RSSI = %d dBm\n", rssi - 64 ); + HAL_DBG_TRACE_INFO( " - SNR = %d dB\n", snr >> 2 ); + + switch( rx_window ) + { + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX1: + { + HAL_DBG_TRACE_INFO( " - Rx window = %s\n", xstr( SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX1 ) ); + break; + } + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX2: + { + HAL_DBG_TRACE_INFO( " - Rx window = %s\n", xstr( SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RX2 ) ); + break; + } + case SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC: + { + HAL_DBG_TRACE_INFO( " - Rx window = %s\n", xstr( SMTC_MODEM_EVENT_DOWNDATA_WINDOW_RXC ) ); + break; + } + } + + if( size != 0 ) + { + HAL_DBG_TRACE_ARRAY( "Payload", payload, size ); + } +} + +static void send_frame( const uint8_t* buffer, const uint8_t length, bool tx_confirmed ) +{ + uint8_t tx_max_payload; + int32_t duty_cycle; + + /* Check if duty cycle is available */ + ASSERT_SMTC_MODEM_RC( smtc_modem_get_duty_cycle_status( &duty_cycle ) ); + if( duty_cycle < 0 ) + { + HAL_DBG_TRACE_WARNING( "Duty-cycle limitation - next possible uplink in %d ms \n\n", duty_cycle ); + return; + } + + ASSERT_SMTC_MODEM_RC( smtc_modem_get_next_tx_max_payload( stack_id, &tx_max_payload ) ); + if( length > tx_max_payload ) + { + HAL_DBG_TRACE_WARNING( "Not enough space in buffer - send empty uplink to flush MAC commands \n" ); + ASSERT_SMTC_MODEM_RC( smtc_modem_request_empty_uplink( stack_id, true, LORAWAN_APP_PORT, tx_confirmed ) ); + } + else + { + HAL_DBG_TRACE_INFO( "Request uplink\n" ); + ASSERT_SMTC_MODEM_RC( smtc_modem_request_uplink( stack_id, LORAWAN_APP_PORT, tx_confirmed, buffer, length ) ); + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/lorawan/main_lorawan.h b/lr1110/lr1110/seeed/apps/examples/lorawan/main_lorawan.h new file mode 100644 index 000000000..1ea0d6cce --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/lorawan/main_lorawan.h @@ -0,0 +1,120 @@ +/*! + * @file main_lorawan.h + * + * @brief LoRa Basics Modem Class A/C device application configuration + * + * @copyright + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 MAIN_LORAWAN_H +#define MAIN_LORAWAN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/*! + * @brief Defines the application data transmission duty cycle. 60s, value in [s]. + */ +#define APP_TX_DUTYCYCLE 60 + +/*! + * @brief LoRaWAN application port + */ +#define LORAWAN_APP_PORT 2 + +/*! + * @brief User application data buffer size + */ +#define LORAWAN_APP_DATA_MAX_SIZE 242 + +/*! + * @brief If true, then the system will not power down all peripherals when going to low power mode. This is necessary + * to keep the LEDs active in low power mode. + */ +#define APP_PARTIAL_SLEEP true + +/* + * ----------------------------------------------------------------------------- + * --- LoRaWAN Configuration --------------------------------------------------- + */ + +/*! + * @brief LoRaWAN confirmed messages + */ +#define LORAWAN_CONFIRMED_MSG_ON false + +/*! + * @brief Default datarate + * + * @remark See @ref smtc_modem_adr_profile_t + */ +#define LORAWAN_DEFAULT_DATARATE SMTC_MODEM_ADR_PROFILE_NETWORK_CONTROLLED + +/*! + * @brief ADR custom list when LORAWAN_DEFAULT_DATARATE is set to SMTC_MODEM_ADR_PROFILE_CUSTOM + */ +uint8_t adr_custom_list[16] = { 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x03, 0x03, + 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00 }; + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +#ifdef __cplusplus +} +#endif + +#endif // MAIN_LORAWAN_H + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/apps/examples/low_power/main_low_power.c b/lr1110/lr1110/seeed/apps/examples/low_power/main_low_power.c new file mode 100644 index 000000000..87f0a8ff9 --- /dev/null +++ b/lr1110/lr1110/seeed/apps/examples/low_power/main_low_power.c @@ -0,0 +1,57 @@ + +#include "smtc_hal.h" +#include "nrf_pwr_mgmt.h" +#include "nrf_gpio.h" +#include "lr11xx_hal_context.h" +#include "lr11xx_system.h" + +lr11xx_hal_context_t radio_context = { + .nss = LR1110_SPI_NSS_PIN, + .busy = LR1110_BUSY_PIN, + .reset = LR1110_NRESER_PIN, + .spi_id = 3, +}; + +void lr1110_sleep_enter( uint32_t tick ) +{ + lr11xx_system_version_t version; + + hal_spi_init( ); + + lr11xx_system_clear_errors( &radio_context ); + lr11xx_system_set_tcxo_mode( &radio_context , LR11XX_SYSTEM_TCXO_CTRL_3_3V, 50 ); + lr11xx_system_cfg_lfclk( &radio_context , LR11XX_SYSTEM_LFCLK_XTAL, 1 ); + + lr11xx_system_sleep_cfg_t radio_sleep_cfg; + radio_sleep_cfg.is_warm_start = 1; + radio_sleep_cfg.is_rtc_timeout = 1; + + lr11xx_system_drive_dio_in_sleep_mode( &radio_context , true ); + lr11xx_system_set_sleep( &radio_context , radio_sleep_cfg, tick ); + + hal_spi_deinit( ); +} + +int main(void) +{ + hal_pwr_init( ); + + hal_gpio_init_out( LR1110_SPI_NSS_PIN, HAL_GPIO_SET ); + hal_gpio_init_in( LR1110_BUSY_PIN, HAL_GPIO_PULL_MODE_NONE, HAL_GPIO_IRQ_MODE_OFF, NULL ); + hal_gpio_init_in( LR1110_IRQ_PIN, HAL_GPIO_PULL_MODE_DOWN, HAL_GPIO_IRQ_MODE_RISING, NULL ); + hal_gpio_init_out( LR1110_NRESER_PIN, HAL_GPIO_SET ); + + lr1110_sleep_enter( 0 ); + + hal_debug_init( ); + PRINTF( "\r\nWX1110 low power\r\n" ); + hal_debug_deinit( ); + + hal_mcu_wait_ms( 5000 ); + + while( 1 ) + { + nrf_pwr_mgmt_run( ); + } +} + \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/bsp/mw_bsp.h b/lr1110/lr1110/seeed/geolocation_middleware/bsp/mw_bsp.h new file mode 100644 index 000000000..c71884d4e --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/bsp/mw_bsp.h @@ -0,0 +1,108 @@ +/** + * @file mw_bsp.h + * + * @brief Board Support Package for the Middleware. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 MW_BSP_H__ +#define MW_BSP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include "lr11xx_system_types.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/** + * @brief Board specific actions that need to be taken before starting a GNSS scan + */ +void mw_bsp_gnss_prescan_actions( void ); + +/** + * @brief Board specific actions that need to be taken after the end of a GNSS scan + */ +void mw_bsp_gnss_postscan_actions( void ); + +/** + * @brief Board specific actions that need to be taken before starting a WIFI scan + */ +void mw_bsp_wifi_prescan_actions( void ); + +/** + * @brief Board specific actions that need to be taken after the end of a WIFI scan + */ +void mw_bsp_wifi_postscan_actions( void ); + +/** + * @brief Get the lr11xx current Low Frequency clock configuration + * + * @return lr11xx_system_lfclk_cfg_t + */ +lr11xx_system_lfclk_cfg_t mw_bsp_get_lr11xx_lf_clock_cfg( void ); + +/** + * @brief Get the lr11xx current regulator mode + */ +void mw_bsp_get_lr11xx_reg_mode( const void* context, lr11xx_system_reg_mode_t* reg_mode ); + +#ifdef __cplusplus +} +#endif + +#endif // MW_BSP_H__ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/common/mw_assert.h b/lr1110/lr1110/seeed/geolocation_middleware/common/mw_assert.h new file mode 100644 index 000000000..ec7a8d7b8 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/common/mw_assert.h @@ -0,0 +1,134 @@ +/*! + * @file mw_assert.h + * + * @brief Middleware assert definition. + * + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 MW_ASSERT_H +#define MW_ASSERT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ +#include // C99 types +#include // bool type + +#include "smtc_modem_api.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/*! + * @brief Stringify constants + */ +#ifndef xstr +#define xstr( a ) str( a ) +#endif +#ifndef str +#define str( a ) #a +#endif + +/*! + * @brief Helper macro that returned a human-friendly message if a command does not return SMTC_MODEM_RC_OK + * + * @remark The macro is implemented to be used with functions returning a @ref smtc_modem_return_code_t + * + * @param[in] rc Return code + */ +#define MW_ASSERT_SMTC_MODEM_RC( rc ) \ + \ + if( rc != SMTC_MODEM_RC_OK ) \ + { \ + if( rc == SMTC_MODEM_RC_NOT_INIT ) \ + { \ + MW_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_NOT_INIT ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_INVALID ) \ + { \ + MW_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_INVALID ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_BUSY ) \ + { \ + MW_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_BUSY ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_FAIL ) \ + { \ + MW_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_FAIL ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_BAD_SIZE ) \ + { \ + MW_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_BAD_SIZE ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_NO_TIME ) \ + { \ + MW_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_NO_TIME ) ); \ + } \ + else if( rc == SMTC_MODEM_RC_INVALID_STACK_ID ) \ + { \ + MW_DBG_TRACE_ERROR( "In %s - %s (line %d): %s\n", __FILE__, __func__, __LINE__, \ + xstr( SMTC_MODEM_RC_INVALID_STACK_ID ) ); \ + } \ + } + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +#ifdef __cplusplus +} +#endif + +#endif // MW_ASSERT_H + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/common/mw_common.c b/lr1110/lr1110/seeed/geolocation_middleware/common/mw_common.c new file mode 100644 index 000000000..d14054717 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/common/mw_common.c @@ -0,0 +1,159 @@ +/*! + * \file mw_common.c + * + * \brief Middleware common functions implementation. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include // C99 types +#include // bool type + +#include "mw_common.h" +#include "mw_bsp.h" +#include "mw_dbg_trace.h" + +#include "lr11xx_system.h" + +#include "smtc_modem_api.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +bool mw_radio_configure_for_scan( const void* radio_context ) +{ + lr11xx_status_t status; + lr11xx_system_errors_t errors; + lr11xx_system_lfclk_cfg_t lf_clock_cfg = mw_bsp_get_lr11xx_lf_clock_cfg( ); + + // Clear potential old errors + status = lr11xx_system_clear_errors( radio_context ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Fail to clear error\n" ); + return false; + } + + // Configure lf clock + status = lr11xx_system_cfg_lfclk( radio_context, lf_clock_cfg, true ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Fail to config lfclk\n" ); + return false; + } + + // Get errors + status = lr11xx_system_get_errors( radio_context, &errors ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Fail to get lr11xx error\n" ); + return false; + } + + // In case clock config is XTAL check if there is no LF_XOSC_START error + if( ( lf_clock_cfg == LR11XX_SYSTEM_LFCLK_XTAL ) && + ( ( errors & LR11XX_SYSTEM_ERRORS_LF_XOSC_START_MASK ) == LR11XX_SYSTEM_ERRORS_LF_XOSC_START_MASK ) ) + { + // lr11xx specification is telling to reset the radio to fix this error + return false; + } + + return true; +} + +void mw_radio_set_sleep( const void* radio_context ) +{ + lr11xx_system_sleep_cfg_t radio_sleep_cfg; + + radio_sleep_cfg.is_warm_start = true; + radio_sleep_cfg.is_rtc_timeout = true; + + if( lr11xx_system_cfg_lfclk( radio_context, LR11XX_SYSTEM_LFCLK_XTAL, true ) != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to set LF clock\n" ); + } + if( lr11xx_system_set_sleep( radio_context, radio_sleep_cfg, 0 ) != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to set the radio to sleep\n" ); + } +} + +uint32_t mw_get_gps_time( void ) +{ + uint32_t gps_time_s = 0; + uint32_t gps_fractional_s = 0; + + const smtc_modem_return_code_t status = smtc_modem_get_time( &gps_time_s, &gps_fractional_s ); + + switch( status ) + { + case SMTC_MODEM_RC_OK: + return gps_time_s; + case SMTC_MODEM_RC_NO_TIME: + MW_DBG_TRACE_WARNING( "No time available.\n" ); + return 0; + default: + MW_DBG_TRACE_ERROR( "Failed to get time from modem\n" ); + return 0; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/common/mw_common.h b/lr1110/lr1110/seeed/geolocation_middleware/common/mw_common.h new file mode 100644 index 000000000..af3e1c73a --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/common/mw_common.h @@ -0,0 +1,118 @@ +/** + * @file mw_common.h + * + * @brief Middleware common functions definition. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 MW_COMMON_H__ +#define MW_COMMON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/** + * @brief Middleware return code definition + */ +typedef enum mw_return_code_e +{ + MW_RC_OK, //!< No error + MW_RC_BUSY, //!< A middleware task is already running + MW_RC_FAILED //!< Failed to execute the requested task +} mw_return_code_t; + +/** + * @brief Middleware version descriptor definition + */ +typedef struct mw_version_s +{ + uint8_t major; //!< Major value + uint8_t minor; //!< Minor value + uint8_t patch; //!< Patch value +} mw_version_t; + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/** + * @brief Configure the lr11xx radio for scan + * + * @param [in] radio_context Chip implementation context + * + * @return a boolean set to true for success, false otherwise + */ +bool mw_radio_configure_for_scan( const void* radio_context ); + +/*! + * @brief Set the lr11xx radio to sleep. To be called by middlewares at the end of the RP task_done handler. + * + * @param [in] radio_context Chip implementation context + */ +void mw_radio_set_sleep( const void* radio_context ); + +/** + * @brief Get the current GPS time from the modem + * + * @return the current GPS time, or 0 if no valid time is available + */ +uint32_t mw_get_gps_time( void ); + +#ifdef __cplusplus +} +#endif + +#endif // MW_COMMON_H__ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/common/mw_dbg_trace.h b/lr1110/lr1110/seeed/geolocation_middleware/common/mw_dbg_trace.h new file mode 100644 index 000000000..de54632e0 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/common/mw_dbg_trace.h @@ -0,0 +1,158 @@ +/*! + * @file mw_dbg_trace.h + * + * @brief Middleware debug trace definition. + * + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 MW_DBG_TRACE_H +#define MW_DBG_TRACE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ +#include // C99 types +#include // bool type + +#include "smtc_modem_hal.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +#ifndef MW_DBG_TRACE +#define MW_DBG_TRACE 1 +#endif + +#ifndef MW_DBG_TRACE_COLOR +#define MW_DBG_TRACE_COLOR 1 +#endif + +#if( MW_DBG_TRACE_COLOR == 1 ) +#define MW_DBG_TRACE_COLOR_RED "\x1B[0;31m" +#define MW_DBG_TRACE_COLOR_GREEN "\x1B[0;32m" +#define MW_DBG_TRACE_COLOR_YELLOW "\x1B[0;33m" +#define MW_DBG_TRACE_COLOR_DEFAULT "\x1B[0m" +#else +#define MW_DBG_TRACE_COLOR_RED "" +#define MW_DBG_TRACE_COLOR_GREEN "" +#define MW_DBG_TRACE_COLOR_YELLOW "" +#define MW_DBG_TRACE_COLOR_DEFAULT "" +#endif + +#if( MW_DBG_TRACE == 1 ) + +#define MW_DBG_TRACE_PRINTF( ... ) smtc_modem_hal_print_trace( __VA_ARGS__ ) + +#define MW_DBG_TRACE_MSG( msg ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( MW_DBG_TRACE_COLOR_DEFAULT ); \ + MW_DBG_TRACE_PRINTF( msg ); \ + } while( 0 ) + +#define MW_DBG_TRACE_INFO( ... ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( MW_DBG_TRACE_COLOR_GREEN ); \ + MW_DBG_TRACE_PRINTF( "INFO: " ); \ + MW_DBG_TRACE_PRINTF( __VA_ARGS__ ); \ + MW_DBG_TRACE_PRINTF( MW_DBG_TRACE_COLOR_DEFAULT ); \ + } while( 0 ) + +#define MW_DBG_TRACE_WARNING( ... ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( MW_DBG_TRACE_COLOR_YELLOW ); \ + MW_DBG_TRACE_PRINTF( "WARN: " ); \ + MW_DBG_TRACE_PRINTF( __VA_ARGS__ ); \ + MW_DBG_TRACE_PRINTF( MW_DBG_TRACE_COLOR_DEFAULT ); \ + } while( 0 ) + +#define MW_DBG_TRACE_ERROR( ... ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( MW_DBG_TRACE_COLOR_RED ); \ + MW_DBG_TRACE_PRINTF( "ERROR: " ); \ + MW_DBG_TRACE_PRINTF( __VA_ARGS__ ); \ + MW_DBG_TRACE_PRINTF( MW_DBG_TRACE_COLOR_DEFAULT ); \ + } while( 0 ) + +#define MW_DBG_TRACE_ARRAY( msg, array, len ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( "%s - (%lu bytes):\n", msg, ( uint32_t ) len ); \ + for( uint32_t i = 0; i < ( uint32_t ) len; i++ ) \ + { \ + if( ( ( i % 16 ) == 0 ) && ( i > 0 ) ) \ + { \ + MW_DBG_TRACE_PRINTF( "\n" ); \ + } \ + MW_DBG_TRACE_PRINTF( " %02X", array[i] ); \ + } \ + MW_DBG_TRACE_PRINTF( "\n" ); \ + } while( 0 ) + +#else +#define MW_DBG_TRACE_PRINTF( ... ) +#define MW_DBG_TRACE_MSG( msg ) +#define MW_DBG_TRACE_INFO( ... ) +#define MW_DBG_TRACE_WARNING( ... ) +#define MW_DBG_TRACE_ERROR( ... ) +#define MW_DBG_TRACE_ARRAY( msg, array, len ) +#endif + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +#ifdef __cplusplus +} +#endif + +#endif // MW_DBG_TRACE_H + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/doc/applicationServer.rst b/lr1110/lr1110/seeed/geolocation_middleware/doc/applicationServer.rst new file mode 100644 index 000000000..e0f418d06 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/doc/applicationServer.rst @@ -0,0 +1,111 @@ +Application server for Geolocation +================================== + +.. _Application Server Introduction: + +Introduction +------------ + +The geolocation middleware described in this project relies on an application server to provide the final location. + +As the end-device using the LR11xx radio does not provide the location on itself, it requires some assistance. But it does not require the same level of assistance if it uses GNSS or Wi-Fi scanning. + +.. _Requirements on the Application Server for GNSS geolocation: + +Requirements on the Application Server for GNSS geolocation +----------------------------------------------------------- + +In order to be able to detect Space Vehicles (SVs) and generate a NAV message to be sent to the GNSS solver, using GNSS assisted scan, the LR11xx radio needs the following: + +* the current time/date +* an almanac up-to-date +* an assistance position close enough to the actual position (~150 km) + +.. _time and almanac: + +Time & Almanac update ++++++++++++++++++++++ + +The middleware itself does not handle time synchronization and almanac update. It is up to the user application to use the services provided by the LoRa Basics Modem and its connection to LoRaCloud "Modem and Geolocation Services". + +Once the end-device has configured the LoRa Basics Modem services, it is up to the Application Server to forward the Device Management messages received on port 199 to the LoRaCloud Modem Services, and transmit back to the end-device the downlink responses if any. + +.. _assistance position: + +Assistance Position update +++++++++++++++++++++++++++ + +When the GNSS solver detects that the assistance position used by the LR11xx radio is not accurate anymore compared to the solved position it provides a new assistance position to be sent to the LR11xx radio. +It is the responsibility of the Application Server to get this assistance position from LoRaCloud Geolocation Service and forward it to the end-device as an applicative downlink. + +The user application on the end-device must forward the downlink received from the Application Server to the middleware, using the ``gnss_mw_set_solver_aiding_position()`` API function. + +.. _fig_docApplicationServerAssistancePosition: + +.. figure:: geoloc_docApplicationServerAssistancePosition.png + :align: center + :alt: application server assistance position + + Application Server assistance position + +It is to be noted that the payload sent by LoRaCloud can be forwarded "as is" to the middleware. + +In order to get an update of the assistance position from the solver, the NAV message has to be sent to the "Modem Services" API (``mgs.loracloud.com/api/v1/device/send``), with ``msgtype`` set to ``gnss``. + +A description of the API can be found here: https://www.loracloud.com/documentation/modem_services?url=mdmsvc.html#uplink-message + + +(The multiframe solver API from the "Geolocation Services" (``mgs.loracloud.com/api/v1/solve/gnss_lr1110_multiframe``) won't provide an update of the assistance position.) + +.. _GNSS multiframe solving: + +GNSS Multiframe solving ++++++++++++++++++++++++ + +In order to improve the accuracy of GNSS solving, it is recommended to use the multiframe solving of the LoRaCloud "Geolocation Services". + +The GNSS middleware helps for gathering NAV messages that should be used together for a multiframe solving by adding "scan group" information with the NAV message. + +A *scan group* is a collection of successive GNSS scan results. Each element of a scan group contains: + +- NAV message; +- **group token** (7 bits); and +- **last NAV message** (1 bit). + +All items belonging to the same group have the same **group token** value. The **last NAV message** bit of an element is set when it is the last element of the group being send. + +To take into account the fact that any uplink can be lost over-the-air, the Application Server shall implement the following process to handle multiframe solving: + +- On reception of a new scan group item, store it and: + + - if the last item received has **last NAV message** bit set: the current scan group is terminated and a multiframe request can be prepared. + - if the last item received has a **group token** value that is different from the one before: the last item of the previous scan group has been lost over-the-air, and it is terminated. A multiframe request of the previous scan group can then be prepared. + - the last item received has a **group token** value that is different from the one before and it has **last NAV message** bit set: the previous scan group has terminated and the one being received also. A multiframe request for the previous scan group can be prepared, and a second GNSS solving request can be prepared for the last item received. Here the second request will contain only one NAV message (single frame request). + +The preparation of multiframe request is described here: https://www.loracloud.com/documentation/modem_services?url=gls.html#apiv1solvegnssmulti. The multiframe request shall contain NAV messages of all items belonging to the same scan group. + +.. _Requirements on the Application Server for Wi-Fi geolocation: + +Requirements on the Application Server for Wi-Fi geolocation +------------------------------------------------------------ + +The end-device do not need valid time nor assistance position to perform Wi-Fi solving. Therefore it can start Wi-Fi scanning as soon as it has joined the network. + +The Application Server just has to build the POST request for LoRaCloud "Modem Services" with the MAC addresses received in the uplink, using the ``msgtype`` seto to ``wifi``. + +A description of the API can be found here: https://www.loracloud.com/documentation/modem_services?url=mdmsvc.html#uplink-message + +As the middleware does not add the RSSI of the MAC addresses detected, this format can be used for solving: https://www.loracloud.com/documentation/modem_services?url=mdmsvc.html#record-wifilocmac + +Application Server block diagram +-------------------------------- + +This diagram is a simplified view an implementation of the application server, aiming to show the different LoRaCloud APIs to be used. + +.. _fig_docApplicationServerBlockDiagram: + +.. figure:: geoloc_docApplicationServerBlockDiagram.png + :align: center + :alt: application server block diagram + + Application Server Block Diagram diff --git a/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docApplicationServerAssistancePosition.png b/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docApplicationServerAssistancePosition.png new file mode 100644 index 0000000000000000000000000000000000000000..7eba93e50ff9fcca3be206d24df133cb99243884 GIT binary patch literal 29889 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 _@} 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 literal 0 HcmV?d00001 diff --git a/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docApplicationServerBlockDiagram.png b/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docApplicationServerBlockDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..a652fa567c836719bc9b0054ba04f7e40b499a59 GIT binary patch literal 103757 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�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 TlmuI1eoaDD2ATc*jr;!xjZRYW literal 0 HcmV?d00001 diff --git a/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docMiddlewareSequenceOverview.png b/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_docMiddlewareSequenceOverview.png new file mode 100644 index 0000000000000000000000000000000000000000..333c64f20a491c0ad00ba3c9751b956b369e501a GIT binary patch literal 36728 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 AvH$=8 literal 0 HcmV?d00001 diff --git a/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_mobileScanScheme.png b/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_mobileScanScheme.png new file mode 100644 index 0000000000000000000000000000000000000000..443bd0eb52beaa91f96dbe266e5956b81cdca0c3 GIT binary patch literal 28762 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 literal 0 HcmV?d00001 diff --git a/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_staticScanScheme.png b/lr1110/lr1110/seeed/geolocation_middleware/doc/geoloc_staticScanScheme.png new file mode 100644 index 0000000000000000000000000000000000000000..a93eec930fe3831466e854807bc12499e30189ae GIT binary patch literal 34938 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 literal 0 HcmV?d00001 diff --git a/lr1110/lr1110/seeed/geolocation_middleware/doc/geolocationMiddleware.rst b/lr1110/lr1110/seeed/geolocation_middleware/doc/geolocationMiddleware.rst new file mode 100644 index 000000000..55cb5a298 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/doc/geolocationMiddleware.rst @@ -0,0 +1,404 @@ +Geolocation middlewares +======================= + +.. _Middleware Introduction: + +Introduction +------------ + +The geolocation middlewares aim to provide a high level API to run "scan & send" sequences, as simply as possible. + +The middlewares provide an abstraction layer: + +* on top of the LR11xx radio for geolocation features (GNSS & Wi-Fi scanning) +* based upon the LoRa Basics Modem for scheduling operations and sending results. + +The goal is to expose as few parameters as possible to the user application, to accomplish pre-defined use cases with the best performances. + +The user application just has to program when the next scan needs to be launched. The middleware will notify when the operation has completed with events. + +.. _fig_docMiddlewareSequenceOverview: + +.. figure:: geoloc_docMiddlewareSequenceOverview.png + :scale: 80% + :align: center + :alt: middleware scan sequence overview + + middleware scan & send sequence overview + +.. _GNSS Middleware: + +GNSS middleware +--------------- + +.. _GNSS scan group: + +Scan group +++++++++++ + +In order to get the best accuracy from GNSS scan results, it is recommended to use the **multiframe** feature of the solver. + +In this context, a scan group is a group of scan results (NAV messages) to be used together to solve a position. + +A scan group also has an identifier (a token) used to identify the NAV messages which have to be grouped for multiframe solving by the Application Server. + +A scan group can be *valid* if all scans within the group meets the expected criteria (minimum number of Space Vehicles detected...). +So it can also be *invalid*, in this case the results will not be sent over the air by the middleware. + +As soon as a scan with the group doesn't meet the expected criteria, the scan group is aborted. + +.. _GNSS scanning modes: + +Scanning modes +++++++++++++++ + +Currently there are 2 modes supported, but this could be extended in the future. + +* **STATIC mode**: to be used for non-moving objects +* **MOBILE mode**: to be used for mobile objects + +Selecting a particular mode will indicate to the middleware how operations must be sequenced to get the best performances. + + **STATIC mode** + + When this mode is selected, with ``GNSS_MW_MODE_STATIC``, the GNSS middleware will run 4 GNSS scans, with a delay of 15 seconds between the end of a scan and the start of the next one. + Once the last scan is completed, it will send each scan result with a LoRaWAN uplink, one after the other. The full scheme is shown on figure `fig_geoloc_staticScanScheme`_ + + **MOBILE mode** + + When this mode is selected, with ``GNSS_MW_MODE_MOBILE``, the GNSS middleware will run 2 GNSS scans, with no delay between the end of a scan and the start of the next one. + Once the last scan is completed, it will send each scan result with a LoRaWAN uplink, one after the other. The full scheme is shown on figure `fig_geoloc_mobileScanScheme`_ + + +.. _fig_geoloc_staticScanScheme: + +.. figure:: geoloc_staticScanScheme.png + :align: center + :alt: Scan scheme for a static mode + + Illustration of *STATIC mode* showing the scan sequence and transmission over LoRaWAN + + +.. _fig_geoloc_mobileScanScheme: + +.. figure:: geoloc_mobileScanScheme.png + :align: center + :alt: Scan scheme for a mobile mode + + Illustration of *MOBILE mode* showing the scan sequence and transmission over LoRaWAN + + +.. _GNSS events notification: + +Events notification ++++++++++++++++++++ + +In order to inform the user application about the "scan & send" sequence status, it will send several events to indicate what happened and allow the user application to take actions. + +* **SCAN_DONE**: This event is always sent, at the end of the scan sequence (before sending results over the air). It is also sent if the scan group has been aborted, and set to invalid. +* **TERMINATED**: This event is always sent, at the end of the send sequence (even if nothing has been sent). +* **CANCELLED**: Sent when the middleware acknowledges a user cancel request. +* **ERROR_ALMANAC**: Sent when a scan could not be done due to an ALMANAC being too old. +* **ERROR_NO_TIME**: Sent when a scan could not be done due to no valid time available (clock sync). +* **ERROR_NO_AIDING_POSITION**: Sent when a scan could not be done due to no assistance position configured. +* **ERROR_UNKNOWN**: An unknown error occurred during the sequence. + +When data associated with an event are available, a dedicated API function to retrieve those data is provided. It is the case for SCAN DONE event and TERMINATED event. + +.. _GNSS internal choices: + +Internal choices +++++++++++++++++ + +In order to reach an acceptable trade-off for performances, power consumption and simplicity, some parameters have been set in the middleware, and are not configurable from the API. + +* A maximum of 10 Space Vehicles detected per NAV message: allow good accuracy while still being able to transmit a complete NAV message in 1 uplink (49 bytes when dopplers are enabled). +* LR1110 scan parameters: dopplers are enabled for autonomous scan only so that the doppler solver can be used to get an assistance position back. +* There are 2 scan group modes to choose the strategy for determining when a scan group is valid or not: + + * `GNSS_SCAN_GROUP_MODE_DEFAULT`: a scan group is valid if the number of valid scans is equal to the group size. As soon as a scan fails (not enough SV detected), the scan group is aborted and not sent over the air (for power consumption saving). + * `GNSS_SCAN_GROUP_MODE_SENSITIVITY`: a scan group is valid as soon as there is a valid scan in the group (with a valid NAV message). This mode optimizes the chances to get a position even if the environment is not ideal. The position accuracy can be lower than with GNSS_SCAN_GROUP_MODE_DEFAULT mode because the position solving could be done with less data. + +Some clarification about what is a valid scan group, a valid scan or a valid NAV message: + +* *scan group*: a scan group is valid if the result of the function `gnss_scan_group_queue_is_valid()` is true, which depends on the scan group mode selected. +* *scan*: a scan is valid if the LR11xx radio returned a minimum number of detected space vehicles (the minimum number is defined by the scanning mode). +* *NAV message*: a NAV message is valid if the result of the function `smtc_gnss_is_nav_message_valid()` is true, which depends on the number of SV detected per constellation. + +For example, in `GNSS_SCAN_GROUP_MODE_SENSITIVITY` scan group mode, a scan group is: + +* *not valid* if there was only one valid scan with an invalid NAV message. +* *valid* if there were 2 valid scans, even if the individual NAV messages would be invalid (no check on individual NAV validity for multiframe solving). + +.. _GNSS default options: + +Default options ++++++++++++++++ + +We have made the choice to keep configuration parameters as low as possible for a standard usage of the middleware. + +By default: + +* The GNSS constellations used are: **GPS & BEIDOU** +* Each scan results is sent as a dedicated LoRaWAN uplink on **port 194**. +* The scan group token is incremented by 1 for each valid scan group. +* The scan group mode selected is `GNSS_SCAN_GROUP_MODE_SENSITIVITY`. + +.. _GNSS advanced options: + +Advanced options +++++++++++++++++ + +Some default parameters can be overruled for specific use cases: + +* The constellations to be used: use GPS only, BEIDOU only +* The port on which the LoRaWAN uplink is sent +* The sequence can be set as "send_bypass" mode, meaning that the scan results won't be automatically sent by the middleware. It can be useful if the user application wants to send the result in a specific manner (using modem streaming feature...). +* Several scan groups can be aggregated together by keeping the same token. It can be useful for non-mobile objects for multiframe solving with a sliding window. + +.. _GNSS assistance Position: + +Assistance/Aiding Position +++++++++++++++++++++++++++ + +The best performances for GNSS geolocation is achieved by using the "assisted scan" feature of the LR1110 radio. In order to use this feature, the middleware needs to provide an assistance position to the radio. + +There are 2 ways to provide this assistance position: + +* an assistance position is given by the user at application startup. +* no assistance position is given by the user, so the middleware starts with an "autonomous scan" and rely on the solver and the application server to return an assistance position with an applicative downlink based on the autonomous can result. + +Note: When using autonomous scan, the sensitivity is not optimal. A better sky view is required to detect Space Vehicles compared to assisted scan. +So it is recommended, if possible, to set an assistance position (as accurate as possible) at startup. + +.. _Internals of the GNSS middleware: + +Internals of the GNSS middleware +++++++++++++++++++++++++++++++++ + +The main role of the middleware is to ease the usage of the LR11xx radio and avoid conflicts between the radio usage for GNSS scanning and other concurrent tasks in an application (transmitting packets...). + +For this, the middleware heavily relies on LoRa Basics Modem (LBM) and in particular its Radio Planner. + +In the LBM, the Radio Planner is responsible for arbitrating the radio usage and ensure that only one user tries to access it at a time. + +* So, when the user calls the ``gnss_mw_scan_start()`` function to start a GNSS scan in the specified delay, it basically schedules a new task in the Radio Planner. The task is scheduled with the ASAP mode, this means that if the radio is already busy at the requested time, the task will be shifted and executed As Soon As Possible. +* When the Radio Planner is ready to launch the programmed task, the ``gnss_mw_scan_rp_task_launch()`` function is called, and the LR11xx radio is ready to be configured to perform the first scan of the scan group. **It is important to note that the code is executed under interrupt, so it needs to be as quick to execute as possible.** +* Once the LR11xx radio has completed the scan, the Radio Planner will call the ``gnss_mw_scan_rp_task_done()`` function of the middleware. **Again, this function is executed under interrupt context, so needs to be fast.** This function will get the scan results and store it in the scan group queue. It will also send a ``GNSS_MW_EVENT_SCAN_DONE`` event to the application. The user application can retrieve scan results and statistics by calling the ``gnss_mw_get_event_data_scan_done()`` function. +* Then, either it is the last scan of the group and it will trigger the first transmission, or it is not the last and it will program the next scan of the queue. +* For sending results over the air, the middleware uses an extended internal API of the LBM which does not copy the buffer to be sent, so the middleware must ensure that the buffer to be sent keeps consistent until it is sent. The LBM will call the ``gnss_mw_tx_done_callback()`` for each completed transmission, and based on this, the middleware will pop all results to be sent over the air. +* Once the last scan result of the scan group has been sent, the ``GNSS_MW_EVENT_TERMINATED`` event is sent to the application, and the scan sequence is over. + +.. _Prerequisites for a GNSS scan: + +Prerequisites for a GNSS scan ++++++++++++++++++++++++++++++ + +There are some prerequisites necessary to have a functional GNSS scan, and to get the best performances. It is the responsability of the user application to ensure that those requirements are met. + +* **time**: a valid time must be provided (ALC Sync, network clock sync...). +* **almanac**: the Almanac written in the LR11xx flash memory must be as up-to-date as possible. It can either be be fully updated at once, or incrementally updated through LoRaCloud Modem & Geolocation Services. +* **assistance position**: an assistance position must be provided to the middleware, either as a user defined assistance position, or by forwarding the downlink coming from the solver. + +.. _GNSS scan results payload format: + +Scan results payload format ++++++++++++++++++++++++++++ + +As the middleware automatically sends the scan results for location solving, it has control over the format used for the uplink. + +The format is the following: + +.. _table-gnss-payload: + +.. table:: GNSS results payload format. + + +---------------------+------------------+--------------------+ + | scan group last NAV | scan group token | NAV message | + +=====================+==================+====================+ + | 1 bit | 7 bits | 36 or 49 bytes max | + +---------------------+------------------+--------------------+ + +* scan group last NAV: indicates to the Application Server if this message is the last of a scan group. It can be used to trigger a multiframe solving request by the Application Server. +* scan group token: it is the identifier of the current scan group. It is used by the Application Server to group the NAV message which should be used as a multiframe solving request. +* NAV message: it is the GNSS scan result returned by the LR11xx radio. The actual size depends on the number of Space Vehicle detected by the scan, and if dopplers are enabled or not. For assisted scans, the maximum size is 49 bytes if dopplers are enabled, and 36 bytes otherwise. + +The maximum size of the complete payload has been kept under 51 bytes to match with the maximum payload size allowed by the LoRaWAN Regional Parameters for most regions (there are few exceptions like DR0 of the US915 region which therefore cannot be used). + +.. _LoRaWAN datarate considerations for GNSS: + +LoRaWAN datarate considerations ++++++++++++++++++++++++++++++++ + +As seen in the section `GNSS scan results payload format`_ , due to the maximum length of the scan results payload, some LoRaWAN datarates cannot be used to transmit the results. + +Also, depending on the region of operation and how often it is required to get a position for the final application, much care should be taken of the datarates used. + +It is **mandatory** to disable the "Network Controlled" mode for Adaptative Datarate (ADR) and rather used custom profiles. +In this custom profiles, it is generally more efficient to use fast datarates, and increase the number of retransmission. + +It is to be noted that the same ADR configuration will be used for sending geolocation scan results and application specific payloads. + +.. _Cancelling a GNSS scan: + +Cancelling a GNSS scan +++++++++++++++++++++++ + +The middleware API provides a function ``gnss_mw_scan_cancel()`` which can be used by the user application to cancel a programmed scan operation. + +It is important to note that a scan can be cancelled only if the corresponding task has not yet been launched. A scan task which has been launched cannot be aborted and will complete (both scan and send). + +A scan task is considered "launched" when the delay to start the scan has elapsed and the Radio Planner has granted access to the radio. + +.. _GNSS API: + +API ++++ + +Refer to the ``gnss/src/gnss_middleware.h`` file. + +.. _Wi-Fi Middleware: + +Wi-Fi middleware +---------------- + +Contrary to the GNSS middleware, there is no scan group concept in the Wi-Fi middleware, and no multiframe solving. +A Wi-Fi scan will simply return the list of Access Points MAC address that have been detected, and will be sent to the solver within one uplink message. + +.. _Wi-Fi events notification: + +Events notification ++++++++++++++++++++ + +In order to inform the user application about the "scan & send" sequence status, it will send several events to indicate what happened and allow the user application to take actions. + +* **SCAN_DONE**: This event is always sent, at the end of the scan sequence (before sending results over the air). It is also sent if the scan group has been aborted, and set to invalid. +* **TERMINATED**: This event is always sent, at the end of the send sequence (even if nothing has been sent). +* **CANCELLED**: Sent when the middleware acknowledges a user cancel request. +* **ERROR_UNKNOWN**: An unknown error occurred during the sequence. + +When data associated with an event are available, a dedicated API function to retrieve those data is provided. It is the case for SCAN DONE event and TERMINATED event. + +.. _Wi-Fi internal choices: + +Internal choices +++++++++++++++++ + +The following parameters are set by the middleware, and are not configurable from the API. + +* A Minimum of 3 Access Points must be detected to get a valid scan. +* The scan will stop when a maximum of 5 Access Points have been detected. +* All channels are enabled to be scanned. +* A scan will look for Beacons of type B, G and N. +* The maximum time spent scanning a channel is set to 300ms +* The maximum time spent for preamble detection for each single scan is set to 90ms + +*Note*: The current implementation is very basic, and does not provide the best performances possible in terms of accuracy and power consumption. It will be improved in further version. + +.. _Wi-Fi default options: + +Default options ++++++++++++++++ + +We have made the choice to keep configuration parameters as low as possible for a standard usage of the middleware. + +By default: + +* Each scan results is sent as a dedicated LoRaWAN uplink on **port 196**. + +.. _Wi-Fi advanced options: + +Advanced options +++++++++++++++++ + +Some default parameters can be overruled for specific use cases: + +* The port on which the LoRaWAN uplink is sent +* The sequence can be set as "send_bypass" mode, meaning that the scan results won't be automatically sent by the middleware. It can be useful if the user application wants to send the result in a specific manner (using modem streaming feature...). + +.. _Internals of the Wi-Fi middleware: + +Internals of the Wi-Fi middleware ++++++++++++++++++++++++++++++++++ + +The main role of the middleware is to ease the usage of the LR11xx radio and avoid conflicts between the radio usage for GNSS scanning and other concurrent use for other tasks in an application (transmitting packets...). + +For this, the middleware heavily relies on LoRa Basics Modem (LBM) and in particular its Radio Planner. + +In the LBM, the Radio Planner is responsible for arbitrating the radio usage and ensure that only one user tries to access it at a time. + +* So, when the user calls the ``wifi_mw_scan_start()`` function to start a Wi-Fi scan in the specified delay, it basically schedules a new task in the Radio Planner. The task is scheduled with the ASAP mode, this means that if the radio is already busy at the requested time, the task will be shifted and executed As Soon As Possible. +* When the Radio Planner is ready to launch the programmed task, the ``wifi_mw_scan_rp_task_launch()`` function is called, and the LR11xx radio is ready to be configured to perform the scan. **It is important to note that the code is executed under interrupt, so it needs to be as quick to execute as possible.** +* Once the LR11xx radio has completed the scan, the Radio Planner calls the ``wifi_mw_scan_rp_task_done()`` function of the middleware. **Again, this function is executed under interrupt context, so needs to be fast.** This function gets the scan results and store it in the middleware context. It also sends a ``WIFI_MW_EVENT_SCAN_DONE`` event to the application. The user application can retrieve scan results and statistics by calling the ``wifi_mw_get_event_data_scan_done()`` function. +* Then, the middleware sends the results over the air. For this, it uses an extended internal API of the LBM which does not copy the buffer to be sent, so the middleware must ensure that the buffer to be sent is kept consistent until it is sent. The LBM calls the ``wifi_mw_tx_done_callback()`` when the transmission is completed. +* The middleware sends the ``WIFI_MW_EVENT_TERMINATED`` event to the application, and the scan sequence is over. + +.. _Wi-Fi scan results payload format: + +Scan results payload format ++++++++++++++++++++++++++++ + +As the middleware automatically sends the scan results for location solving, it has control over the format used for the uplink. + +There are 2 formats possible, that the user can choose depending on the solver used: + +* `WIFI_MW_PAYLOAD_MAC`: contains only the MAC addresses of the detected Access Points +* `WIFI_MW_PAYLOAD_MAC_RSSI`: contains the MAC addresses of the detected Access Points and the strength of the signal at which it has been detected. + + +.. _table-wifi-payload-mac: + +.. table:: Wi-Fi results payload format with MAC addresses only. + + +-----------------+-----------------+-----+-----------------+ + | AP1 MAC address | AP2 MAC address | ... | APn MAC address | + +=================+=================+=====+=================+ + | 6 bytes | 6 bytes | ... | 6 bytes | + +-----------------+-----------------+-----+-----------------+ + + +.. _table-wifi-payload-mac-rssi: + +.. table:: Wi-Fi results payload format with MAC addresses and RSSI. + + +----------+-----------------+----------+-----------------+-----+----------+-----------------+ + | AP1 RSSI | AP1 MAC address | AP2 RSSI | AP2 MAC address | ... | APn RSSI | APn MAC address | + +==========+=================+==========+=================+=====+==========+=================+ + | 1 byte | 6 bytes | 1 byte | 6 bytes | ... | 1 byte | 6 bytes | + +----------+-----------------+----------+-----------------+-----+----------+-----------------+ + + +The maximum size of the complete payload has been kept under 51 bytes to match with the maximum payload size allowed by the LoRaWAN Regional Parameters for most regions (there are few exceptions like DR0 of the US915 region which therefore cannot be used). + +.. _LoRaWAN datarate considerations for Wi-Fi: + +LoRaWAN datarate considerations ++++++++++++++++++++++++++++++++ + +As seen in the section `Wi-Fi scan results payload format`_ , due to the maximum length of the scan results payload, some LoRaWAN datarates cannot be used to transmit the results. + +Also, depending on the region of operation and how often it is required to get a position for the final application, much care should be taken of the datarates used. + +It is **mandatory** to disable the "Network Controlled" mode for Adaptative Datarate (ADR) and rather used custom profiles. +In this custom profiles, it is generally more efficient to use fast datarates, and increase the number of retransmission. + +It is to be noted that the same ADR configuration will be used for sending geolocation scan results and application specific payloads. + +.. _Cancelling a Wi-Fi scan: + +Cancelling a Wi-Fi scan ++++++++++++++++++++++++ + +The middleware API provides a function ``wifi_mw_scan_cancel()`` which can be used by the user application to cancel a programmed scan & send operation. + +It is important to note that a scan can be cancelled only if the corresponding task has not yet been launched. A scan task which has been launched cannot be aborted and will complete (both scan and send). + +A scan task is considered "launched" when the delay to start the scan has elapsed and the Radio Planner has granted access to the radio. + + +.. _Wi-Fi API: + +API ++++ + +Refer to the ``wifi/src/wifi_middleware.h`` file. diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers.c b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers.c new file mode 100644 index 000000000..e6e03598b --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers.c @@ -0,0 +1,938 @@ +/** + * @file gnss_helpers.c + * + * @brief Interface between the GNSS middleware and the LR11xx radio driver. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ +#include + +#include "gnss_helpers.h" +#include "gnss_helpers_defs.h" + +#include "mw_assert.h" +#include "mw_dbg_trace.h" +#include "mw_bsp.h" +#include "mw_common.h" + +#include "smtc_modem_api.h" + +#include "lr11xx_system.h" +#include "lr11xx_gnss.h" + +#include "lr11xx_driver_extension.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +#define GNSS_HELPERS_FEATURE_OFF 0 +#define GNSS_HELPERS_FEATURE_ON !GNSS_HELPERS_FEATURE_OFF + +#ifndef GNSS_HELPERS_DBG_TRACE +#define GNSS_HELPERS_DBG_TRACE GNSS_HELPERS_FEATURE_OFF /* Enable/Disable traces here */ +#endif + +#if( GNSS_HELPERS_DBG_TRACE == GNSS_HELPERS_FEATURE_ON ) +#define GNSS_HELPERS_TRACE_MSG( msg ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( msg ); \ + } while( 0 ) + +#define GNSS_HELPERS_TRACE_PRINTF( ... ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( __VA_ARGS__ ); \ + } while( 0 ) + +#else +#define GNSS_HELPERS_TRACE_MSG( msg ) +#define GNSS_HELPERS_TRACE_PRINTF( ... ) +#endif + +#define ABS( N ) ( ( N < 0 ) ? ( -N ) : ( N ) ) + +#define GETBIT( var, bit ) ( ( ( var ) >> ( bit ) ) & 1 ) + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Configure and start scan operation (autonomous or assisted) + * + * @param[in] radio_context Chip implementation context + * @param[in] date Current date + * @param[in] params Pointer to scan parameters + + * @return a boolean: true for success, false otherwise + */ +static bool gnss_scan( const void* radio_context, lr11xx_gnss_date_t date, const smtc_gnss_scan_params_t* params ); + +/*! + * @brief Parse a result buffer to determine if it is a NAV message to be sent to the solver + * + * @param[in] buffer Buffer containing the result to be parsed + * @param[in] buffer_length Length of the given result buffer + * + * @return a boolean: true for success, false otherwise + */ +static inline bool gnss_is_result_to_solver( const uint8_t* buffer, uint8_t buffer_length ); + +/*! + * @brief Parse a result buffer to determine if it is a message for the host (error message) + * + * @param[in] buffer Buffer containing the result to be parsed + * @param[in] buffer_length Length of the given result buffer + * + * @return a boolean: true for success, false otherwise + */ +static inline bool gnss_is_result_to_host( const uint8_t* buffer, uint8_t buffer_length ); + +/*! + * @brief Compute the median value of the given values array + * + * @param[in] n Size of the input array + * @param[in] x Array containing values + * + * @return an integer: the median value + */ +static int median( int n, int x[] ); + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +bool smtc_gnss_set_assistance_position( const void* radio_context, + const lr11xx_gnss_solver_assistance_position_t* assistance_position ) +{ + if( lr11xx_gnss_set_assistance_position( radio_context, assistance_position ) != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to set assistance position\n" ); + return false; + } + + return true; +} + +bool smtc_gnss_push_solver_msg( const void* radio_context, const uint8_t* payload, const uint16_t payload_size ) +{ + if( lr11xx_gnss_push_solver_msg( radio_context, payload, payload_size ) != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to push solver msg\n" ); + return false; + } + + return true; +} + +bool smtc_gnss_get_almanac_crc( const void* radio_context, uint32_t* almanac_crc ) +{ + lr11xx_status_t err; + lr11xx_gnss_context_status_bytestream_t context_status_bytestream; + lr11xx_gnss_context_status_t context_status; + + err = lr11xx_gnss_get_context_status( radio_context, context_status_bytestream ); + if( err != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get gnss context status\n" ); + return false; + } + + err = lr11xx_gnss_parse_context_status_buffer( context_status_bytestream, &context_status ); + if( err != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to parse gnss context status to get almanac status\n" ); + return false; + } + + *almanac_crc = context_status.global_almanac_crc; + + return true; +} + +bool smtc_gnss_scan( const void* radio_context, uint32_t date, const smtc_gnss_scan_params_t* params ) +{ + bool status; + + status = mw_radio_configure_for_scan( radio_context ); + if( status == true ) + { + /* Enable LNA for GNSS with LR11XX Evaluation board with passive antenna */ + mw_bsp_gnss_prescan_actions( ); + + /* Start scan */ + status = gnss_scan( radio_context, ( lr11xx_gnss_date_t ) date, params ); + if( status == false ) + { + MW_DBG_TRACE_ERROR( "gnss_scan() failed\n" ); + mw_bsp_gnss_postscan_actions( ); + return false; + } + } + else + { + MW_DBG_TRACE_ERROR( "Failed to configure LR11XX for GNSS scan\n" ); + return false; + } + + return true; +} + +void smtc_gnss_scan_ended( void ) +{ + /* Disable LNA which was enabled during GNSS scan */ + mw_bsp_gnss_postscan_actions( ); +} + +bool smtc_gnss_get_scan_context( const void* radio_context, lr11xx_gnss_solver_assistance_position_t* aiding_position, + uint32_t* almanac_crc ) +{ + lr11xx_status_t status; + bool err; + + status = lr11xx_gnss_read_assistance_position( radio_context, aiding_position ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to read assistance position from LR11xx\n" ); + return false; + } + + err = smtc_gnss_get_almanac_crc( radio_context, almanac_crc ); + if( err != true ) + { + MW_DBG_TRACE_ERROR( "Failed to read almanac CRC from LR11xx\n" ); + return false; + } + + return true; +} + +smtc_gnss_get_results_return_code_t smtc_gnss_get_results( const void* radio_context, const uint8_t results_max_size, + uint8_t* res_sz, uint8_t* results, bool* no_sv_detected ) +{ + lr11xx_status_t status; + uint16_t result_size; + + /* Initialize output value, in case this function returns with an error */ + *res_sz = 0; + *no_sv_detected = false; + + /* Use read result API to fetch the result buffer */ + status = lr11xx_gnss_get_result_size( radio_context, &result_size ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get GNSS scan result size\n" ); + return SMTC_GNSS_GET_RESULTS_ERROR_UNKNOWN; + } + + if( result_size > results_max_size ) + { + MW_DBG_TRACE_ERROR( "GNSS scan result size exceeds %d (%u)\n", results_max_size, result_size ); + return SMTC_GNSS_GET_RESULTS_ERROR_BUFFER_SIZE; + } + + status = lr11xx_gnss_read_results( radio_context, results, result_size ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get result\n" ); + return SMTC_GNSS_GET_RESULTS_ERROR_UNKNOWN; + } + + /* Check if the message read from read result API is a NAV message or not. If it is not, print the + appropriate error message */ + if( gnss_is_result_to_solver( results, result_size ) == false ) + { + /* The result read is for the solver, it is probably an error message. */ + if( gnss_is_result_to_host( results, result_size ) == true ) + { + const lr11xx_gnss_message_host_status_t status_code_raw = ( lr11xx_gnss_message_host_status_t ) results[1]; + switch( status_code_raw ) + { + case LR11XX_GNSS_HOST_NO_TIME: + { + MW_DBG_TRACE_ERROR( "GNSS error: NO TIME\n" ); + return SMTC_GNSS_GET_RESULTS_ERROR_NO_TIME; + } + case LR11XX_GNSS_HOST_NO_SATELLITE_DETECTED: + { + MW_DBG_TRACE_INFO( "GNSS error: NO SATELLITE\n" ); + *no_sv_detected = true; + return SMTC_GNSS_GET_RESULTS_NO_ERROR; /* not an error */ + } + case LR11XX_GNSS_HOST_ALMANAC_IN_FLASH_TOO_OLD: + { + MW_DBG_TRACE_ERROR( "GNSS error: ALMANAC TOO OLD\n" ); + return SMTC_GNSS_GET_RESULTS_ERROR_ALMANAC; + } + case LR11XX_GNSS_HOST_NOT_ENOUGH_SV_DETECTED_TO_BUILD_A_NAV_MESSAGE: + { + MW_DBG_TRACE_INFO( "GNSS error: NOT ENOUGH SVs TO BUILD A NAV MESSAGE\n" ); + return SMTC_GNSS_GET_RESULTS_NO_ERROR; /* not an error */ + } + default: + { + MW_DBG_TRACE_ERROR( "GNSS error: UNKNOWN ERROR CODE: 0x%02X\n", status_code_raw ); + return SMTC_GNSS_GET_RESULTS_ERROR_UNKNOWN; + } + } + } + else + { + MW_DBG_TRACE_ERROR( + "GNSS error: NAV message is neither for host nor for solver. Destination byte: 0x%02x\n", results[0] ); + return SMTC_GNSS_GET_RESULTS_ERROR_UNKNOWN; + } + } + else + { + /* The result read is for the solver, check if there is an error status. */ + const uint8_t status_code_raw = results[1]; + if( status_code_raw == 0x00 ) + { + MW_DBG_TRACE_ERROR( "GNSS error: NO ASSISTANCE POSITION\n" ); + MW_DBG_TRACE_ARRAY( "results", results, result_size ); + return SMTC_GNSS_GET_RESULTS_ERROR_AIDING_POS; + } + } + + /* Remove the destination byte (first byte) which does not need to be sent over the air */ + memmove( results, results + 1, result_size - 1 ); + + /* Set the returned buffer size without the destination byte */ + *res_sz = result_size - 1; + + return SMTC_GNSS_GET_RESULTS_NO_ERROR; +} + +bool smtc_gnss_get_sv_info( const void* radio_context, const uint8_t sv_info_max_size, uint8_t* nb_detected_sv, + lr11xx_gnss_detected_satellite_t* sv_info ) +{ + lr11xx_status_t status; + + /* Initialize output values, in case this function returns with an error */ + *nb_detected_sv = 0; + + /* Fetch the detected SVs */ + status = lr11xx_gnss_get_nb_detected_satellites( radio_context, nb_detected_sv ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get number of satellites detected\n" ); + return false; + } + + if( *nb_detected_sv > sv_info_max_size ) + { + MW_DBG_TRACE_ERROR( "Cannot store info of all detected SVs (%u: max:%u)\n", nb_detected_sv, sv_info_max_size ); + return false; + } + + /* Get details about detected SVs */ + status = lr11xx_gnss_get_detected_satellites( radio_context, *nb_detected_sv, sv_info ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get detected satellites\n" ); + return false; + } + + return true; +} + +bool smtc_gnss_get_almanac_update_status( const void* radio_context, const uint32_t date, + const lr11xx_gnss_solver_assistance_position_t* assistance_position, + const lr11xx_gnss_constellation_mask_t constellations, + bool* almanacs_update_required ) +{ + lr11xx_status_t status; + uint8_t nb_visible_gps_satellites, nb_visible_beidou_satellites; + lr11xx_gnss_visible_satellite_t visible_gps[12]; + lr11xx_gnss_visible_satellite_t visible_beidou[12]; + uint8_t nb_almanac_old = 0; + + /* Initialize output status */ + *almanacs_update_required = false; + + /* Get visible satellites for GPS constellation */ + if( constellations & LR11XX_GNSS_GPS_MASK ) + { + status = lr11xx_gnss_get_nb_visible_satellites( radio_context, ( lr11xx_gnss_date_t ) date, assistance_position, + LR11XX_GNSS_GPS_MASK, &nb_visible_gps_satellites ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get number of visible GPS satellites\n" ); + return false; + } + + status = lr11xx_gnss_get_visible_satellites( radio_context, nb_visible_gps_satellites, visible_gps ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get visible GPS satellites info\n" ); + return false; + } + + GNSS_HELPERS_TRACE_PRINTF( "\nVisible GPS satellites: %u\n", nb_visible_gps_satellites ); + GNSS_HELPERS_TRACE_PRINTF( "id | doppler | almanac_age \n" ); + GNSS_HELPERS_TRACE_PRINTF( "----|------------|--------------\n" ); + for( int i = 0; i < nb_visible_gps_satellites; i++ ) + { + GNSS_HELPERS_TRACE_PRINTF( "%-3u | %-10d | %-10d\n", visible_gps[i].satellite_id, visible_gps[i].doppler, + visible_gps[i].doppler_error ); + } + + /* Get the ratio of visible GPS SVs which have almanacs up-to-date */ + for( int i = 0; i < nb_visible_gps_satellites; i++ ) + { + if( visible_gps[i].doppler_error > 250 ) /* almanac age more than 6 months old (125Hz = 3months) */ + { + nb_almanac_old += 1; + } + } + } + + /* Get visible satellites for BEIDOU constellation */ + if( constellations & LR11XX_GNSS_BEIDOU_MASK ) + { + status = lr11xx_gnss_get_nb_visible_satellites( radio_context, ( lr11xx_gnss_date_t ) date, assistance_position, + LR11XX_GNSS_BEIDOU_MASK, &nb_visible_beidou_satellites ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get number of visible BEIDOU satellites\n" ); + return false; + } + + status = lr11xx_gnss_get_visible_satellites( radio_context, nb_visible_beidou_satellites, visible_beidou ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get visible BEIDOU satellites info\n" ); + return false; + } + + GNSS_HELPERS_TRACE_PRINTF( "\nVisible BEIDOU satellites: %u\n", nb_visible_beidou_satellites ); + GNSS_HELPERS_TRACE_PRINTF( "id | doppler | almanac_age \n" ); + GNSS_HELPERS_TRACE_PRINTF( "----|------------|--------------\n" ); + for( int i = 0; i < nb_visible_beidou_satellites; i++ ) + { + GNSS_HELPERS_TRACE_PRINTF( "%-3u | %-10d | %-10d\n", visible_beidou[i].satellite_id, + visible_beidou[i].doppler, visible_beidou[i].doppler_error ); + } + + /* Get the ratio of visible BEIDOU SVs which have almanacs up-to-date */ + for( int i = 0; i < nb_visible_beidou_satellites; i++ ) + { + if( visible_beidou[i].doppler_error > 250 ) /* almanac age more than 8 months old (125Hz = 4months) */ + { + nb_almanac_old += 1; + } + } + } + + /* Percentage of almanac updated among visible SVs */ + uint16_t almanacs_updated_status = + 100 - ( nb_almanac_old * 100 / ( nb_visible_gps_satellites + nb_visible_beidou_satellites ) ); + + /* at least 70% of visible SVs with up-to-date almanacs are necessary to trust doppler error detection */ + if( almanacs_updated_status < 70 ) + { + *almanacs_update_required = true; + } + else + { + *almanacs_update_required = false; + } + + GNSS_HELPERS_TRACE_PRINTF( "Almanac update status: %u%% (update_required:%d)\n", almanacs_updated_status, + *almanacs_update_required ); + + return true; +} + +bool smtc_gnss_get_doppler_error_from_nav( const uint8_t* nav ) +{ + uint8_t bit = GETBIT( nav[0], 4 ); + return ( bool ) bit; +} + +bool smtc_gnss_get_doppler_error( const void* radio_context, const uint32_t date, + const lr11xx_gnss_solver_assistance_position_t* assistance_position, + const lr11xx_gnss_constellation_mask_t constellations, const uint8_t nb_detected_sv, + const lr11xx_gnss_detected_satellite_t* detected_sv_info, bool* doppler_error ) +{ + lr11xx_status_t status; + uint8_t nb_visible_gps_satellites, nb_visible_beidou_satellites; + lr11xx_gnss_visible_satellite_t visible_gps[12]; + lr11xx_gnss_visible_satellite_t visible_beidou[12]; + + typedef struct doppler_offset_s + { + lr11xx_gnss_satellite_id_t id; + int16_t doppler_offset; + int16_t almanac_age; + } doppler_offset_t; + doppler_offset_t doppler_offsets[GNSS_NB_SVS_MAX] = { 0 }; /* doppler offsets of detected SVs */ + + int doppler_offsets_for_median_size = 0; + int doppler_offsets_for_median[GNSS_NB_SVS_MAX] = { 0 }; + + /* Initialize output status */ + *doppler_error = false; + + GNSS_HELPERS_TRACE_PRINTF( "date:%u (%.6f, %.6f)\n", date, assistance_position->latitude, + assistance_position->longitude ); + + /* Get visible satellites for GPS constellation */ + if( constellations & LR11XX_GNSS_GPS_MASK ) + { + status = lr11xx_gnss_get_nb_visible_satellites( radio_context, ( lr11xx_gnss_date_t ) date, assistance_position, + LR11XX_GNSS_GPS_MASK, &nb_visible_gps_satellites ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get number of visible GPS satellites\n" ); + return false; + } + + status = lr11xx_gnss_get_visible_satellites( radio_context, nb_visible_gps_satellites, visible_gps ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get visible GPS satellites info\n" ); + return false; + } + } + + /* Get visible satellites for BEIDOU constellation */ + if( constellations & LR11XX_GNSS_BEIDOU_MASK ) + { + status = lr11xx_gnss_get_nb_visible_satellites( radio_context, ( lr11xx_gnss_date_t ) date, assistance_position, + LR11XX_GNSS_BEIDOU_MASK, &nb_visible_beidou_satellites ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get number of visible BEIDOU satellites\n" ); + return false; + } + + status = lr11xx_gnss_get_visible_satellites( radio_context, nb_visible_beidou_satellites, visible_beidou ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get visible BEIDOU satellites info\n" ); + return false; + } + } + + /* Compute the offset between the detected doppler and the theoretical doppler of visible SVs */ + for( int j = 0; j < nb_detected_sv; j++ ) + { + /* GPS satellites */ + if( constellations & LR11XX_GNSS_GPS_MASK ) + { + for( int k = 0; k < nb_visible_gps_satellites; k++ ) + { + if( detected_sv_info[j].satellite_id == visible_gps[k].satellite_id ) + { + doppler_offsets[j].id = detected_sv_info[j].satellite_id; + doppler_offsets[j].doppler_offset = detected_sv_info[j].doppler - visible_gps[k].doppler; + doppler_offsets[j].almanac_age = visible_gps[k].doppler_error; + + /* Add doppler_offset to median calculation, if almanac is up-to-date */ + if( doppler_offsets[j].almanac_age == 0 ) + { + doppler_offsets_for_median[doppler_offsets_for_median_size] = + ( int ) doppler_offsets[j].doppler_offset; + doppler_offsets_for_median_size += 1; + } + } + } + } + /* BEIDOU satellites */ + if( constellations & LR11XX_GNSS_BEIDOU_MASK ) + { + for( int k = 0; k < nb_visible_beidou_satellites; k++ ) + { + if( detected_sv_info[j].satellite_id == visible_beidou[k].satellite_id ) + { + doppler_offsets[j].id = detected_sv_info[j].satellite_id; + doppler_offsets[j].doppler_offset = detected_sv_info[j].doppler - visible_beidou[k].doppler; + doppler_offsets[j].almanac_age = visible_beidou[k].doppler_error; + + /* Add doppler_offset to median calculation, if almanac is up-to-date */ + if( doppler_offsets[j].almanac_age == 0 ) + { + doppler_offsets_for_median[doppler_offsets_for_median_size] = + ( int ) doppler_offsets[j].doppler_offset; + doppler_offsets_for_median_size += 1; + } + } + } + } + } + + /* Compute the median doppler offset of SVs with updated almanacs */ + int median_offset = median( doppler_offsets_for_median_size, doppler_offsets_for_median ); + + /* Debug prints: TODO: add flag to disable/enable */ + GNSS_HELPERS_TRACE_PRINTF( "\nDetected satellites: %u\n", nb_detected_sv ); + GNSS_HELPERS_TRACE_PRINTF( "id | doppler | cnr\n" ); + GNSS_HELPERS_TRACE_PRINTF( "----|------------|--------------\n" ); + for( int i = 0; i < nb_detected_sv; i++ ) + { + GNSS_HELPERS_TRACE_PRINTF( "%-3u | %-10d | %-10d\n", detected_sv_info[i].satellite_id, + detected_sv_info[i].doppler, detected_sv_info[i].cnr ); + } + + if( constellations & LR11XX_GNSS_GPS_MASK ) + { + GNSS_HELPERS_TRACE_PRINTF( "\nVisible GPS satellites: %u\n", nb_visible_gps_satellites ); + GNSS_HELPERS_TRACE_PRINTF( "id | doppler | almanac_age \n" ); + GNSS_HELPERS_TRACE_PRINTF( "----|------------|--------------\n" ); + for( int i = 0; i < nb_visible_gps_satellites; i++ ) + { + GNSS_HELPERS_TRACE_PRINTF( "%-3u | %-10d | %-10d\n", visible_gps[i].satellite_id, visible_gps[i].doppler, + visible_gps[i].doppler_error ); + } + } + + if( constellations & LR11XX_GNSS_BEIDOU_MASK ) + { + GNSS_HELPERS_TRACE_PRINTF( "\nVisible BEIDOU satellites: %u\n", nb_visible_beidou_satellites ); + GNSS_HELPERS_TRACE_PRINTF( "id | doppler | almanac_age \n" ); + GNSS_HELPERS_TRACE_PRINTF( "----|------------|--------------\n" ); + for( int i = 0; i < nb_visible_beidou_satellites; i++ ) + { + GNSS_HELPERS_TRACE_PRINTF( "%-3u | %-10d | %-10d\n", visible_beidou[i].satellite_id, + visible_beidou[i].doppler, visible_beidou[i].doppler_error ); + } + } + + /* Get the current frequency search space configuration to determine threshold for acceptable doppler offset */ + lr11xx_gnss_freq_search_space_t freq_search_space; + int16_t offset_threshold; + status = lr11xx_gnss_read_freq_search_space( radio_context, &freq_search_space ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to read current freq search space\n" ); + return false; + } + switch( freq_search_space ) + { + case LR11XX_GNSS_FREQUENCY_SEARCH_SPACE_250_HZ: + offset_threshold = 250; + break; + case LR11XX_GNSS_FREQUENCY_SEARCH_SPACE_500_HZ: + offset_threshold = 500; + break; + case LR11XX_GNSS_FREQUENCY_SEARCH_SPACE_1_KHZ: + offset_threshold = 1000; + break; + case LR11XX_GNSS_FREQUENCY_SEARCH_SPACE_2_KHZ: + offset_threshold = 2000; + break; + default: + MW_DBG_TRACE_ERROR( "Unknown freq search space - %d\n", freq_search_space ); + return false; + } + + /* Check if there is at least 1 SV with doppler error (with almanac up-to-date) */ + int16_t offset_from_median; + bool doppler_error_sv; + bool doppler_error_ignored; + GNSS_HELPERS_TRACE_PRINTF( "\nDoppler offsets (median=%d):\n", median_offset ); + GNSS_HELPERS_TRACE_PRINTF( "id | offset | off-median | doppler_err\n" ); + GNSS_HELPERS_TRACE_PRINTF( "----|------------|------------|------------\n" ); + for( int i = 0; i < nb_detected_sv; i++ ) + { + offset_from_median = ABS( ( int16_t )( doppler_offsets[i].doppler_offset - median_offset ) ); + doppler_error_sv = ( offset_from_median > offset_threshold ) ? true : false; + doppler_error_ignored = ( doppler_offsets[i].almanac_age > 250 ) + ? true + : false; /* If almanac is too old for this SV, ignore doppler detected */ + if( doppler_error_ignored == true ) + { + doppler_error_sv = false; + } + GNSS_HELPERS_TRACE_PRINTF( "%-3u | %-10d | %-10d | %d %s\n", doppler_offsets[i].id, + doppler_offsets[i].doppler_offset, offset_from_median, doppler_error_sv, + ( doppler_error_ignored == true ) ? "(IGNORED)" : "" ); + + if( doppler_error_sv == true ) + { + /* Return doppler error status */ + *doppler_error = true; + /* TODO: break here if don't want to have details for all SVs */ + } + } + + if( *doppler_error == true ) + { + MW_DBG_TRACE_WARNING( "Doppler error detected\n" ); + } + + return true; +} + +bool smtc_gnss_get_power_consumption( const void* radio_context, uint32_t* power_consumption_uah ) +{ + lr11xx_status_t status; + lr11xx_gnss_timings_t timings; + lr11xx_gnss_constellation_mask_t constellation_used; + lr11xx_system_reg_mode_t reg_mode; + + /* Initialize output values, in case this function returns with an error */ + *power_consumption_uah = 0; + + status = lr11xx_gnss_get_timings( radio_context, &timings ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get gnss timings\n" ); + return false; + } + + status = lr11xx_gnss_read_used_constellations( radio_context, &constellation_used ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get gnss constellation used\n" ); + return false; + } + + mw_bsp_get_lr11xx_reg_mode( radio_context, ®_mode ); + *power_consumption_uah = lr11xx_gnss_get_consumption( reg_mode, timings, constellation_used ); + + return true; +} + +bool smtc_gnss_is_nav_message_valid( const lr11xx_gnss_constellation_mask_t constellations, + uint8_t nb_detected_satellites, + lr11xx_gnss_detected_satellite_t* detected_satellites ) +{ + bool is_valid_nav_message = false; + + /* A NAV message is considered valid if: + - Dual constellations: + - at least 2 SVs per constellation (BEIDOU and GNSS only) and 6 SVs in total. + - 5 SVs in a same constellation. + - Single constellation: at least 5 detected SVs (BEIDOU and GNSS only). + + GPS satellites ID [0 31] + SBAS satellites ID [32 63] but not used + BEIDOU satellites ID [64 127]. + */ + if( nb_detected_satellites >= 5 ) + { + uint8_t gps_sv_cnt = 0; + uint8_t beidou_sv_cnt = 0; + + for( uint8_t i = 0; i < nb_detected_satellites; i++ ) + { + /* Check if it's a GPS satellite */ + if( detected_satellites[i].satellite_id <= 31 ) + { + gps_sv_cnt++; + } + /* Ignore SBAS */ + /* Check if it's a BEIDOU satellite */ + if( ( detected_satellites[i].satellite_id >= 64 ) && ( detected_satellites[i].satellite_id <= 127 ) ) + { + beidou_sv_cnt++; + } + } + + /* Check if the NAV message is valid */ + if( constellations != ( LR11XX_GNSS_GPS_MASK | LR11XX_GNSS_BEIDOU_MASK ) ) + { + /* Single constellation */ + if( ( gps_sv_cnt >= 5 ) || ( beidou_sv_cnt >= 5 ) ) + { + is_valid_nav_message = true; + } + else + { + is_valid_nav_message = false; + } + } + else + { + /* Dual constellations */ + if( ( ( gps_sv_cnt >= 2 ) && ( beidou_sv_cnt >= 2 ) && ( ( gps_sv_cnt + beidou_sv_cnt ) >= 6 ) ) || + ( ( gps_sv_cnt >= 5 ) || ( beidou_sv_cnt >= 5 ) ) ) + { + is_valid_nav_message = true; + } + else + { + is_valid_nav_message = false; + } + } + } + else + { + is_valid_nav_message = false; + } + + return is_valid_nav_message; +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +static bool gnss_scan( const void* radio_context, lr11xx_gnss_date_t date, const smtc_gnss_scan_params_t* params ) +{ + lr11xx_status_t status = LR11XX_STATUS_ERROR; + + status = + lr11xx_system_set_dio_irq_params( radio_context, LR11XX_SYSTEM_IRQ_GNSS_SCAN_DONE, LR11XX_SYSTEM_IRQ_NONE ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to set GNSS scan done IRQ params\n" ); + return false; + } + + status = lr11xx_gnss_set_scan_mode( radio_context, LR11XX_GNSS_SCAN_MODE_3_SINGLE_SCAN_AND_5_FAST_SCANS ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to set GNSS scan mode\n" ); + return false; + } + + status = lr11xx_gnss_set_constellations_to_use( radio_context, params->constellations ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to set constellations\n" ); + return false; + } + + if( params->assisted == true ) + { + status = lr11xx_gnss_set_freq_search_space( radio_context, params->freq_search_space ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to set freq search space\n" ); + return false; + } + + status = lr11xx_gnss_scan_assisted( radio_context, date, LR11XX_GNSS_OPTION_BEST_EFFORT, + params->input_parameters, params->nb_svs_max ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "[GNSS] Failed to start assisted scan\n" ); + return false; + } + } + else + { + status = lr11xx_gnss_scan_autonomous( radio_context, date, LR11XX_GNSS_OPTION_BEST_EFFORT, + params->input_parameters, params->nb_svs_max ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "[GNSS] Failed to start autonomous scan\n" ); + return false; + } + } + + return true; +} + +static inline bool gnss_is_result_to_solver( const uint8_t* buffer, uint8_t buffer_length ) +{ + if( buffer_length >= 2 ) + { + return buffer[0] == LR11XX_GNSS_DESTINATION_SOLVER; + } + else + { + return false; + } +} + +static inline bool gnss_is_result_to_host( const uint8_t* buffer, uint8_t buffer_length ) +{ + if( buffer_length >= 2 ) + { + return buffer[0] == LR11XX_GNSS_DESTINATION_HOST; + } + else + { + return false; + } +} + +static int median( int n, int x[] ) +{ + int temp; + int i, j; + // the following two loops sort the array x in ascending order + for( i = 0; i < n - 1; i++ ) + { + for( j = i + 1; j < n; j++ ) + { + if( x[j] < x[i] ) + { + // swap elements + temp = x[i]; + x[i] = x[j]; + x[j] = temp; + } + } + } + + if( n % 2 == 0 ) + { + // if there is an even number of elements, return mean of the two elements in the middle + return ( ( x[n / 2] + x[n / 2 - 1] ) / 2 ); + } + else + { + // else return the element in the middle + return x[n / 2]; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers.h b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers.h new file mode 100644 index 000000000..6f2fb5d02 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers.h @@ -0,0 +1,231 @@ +/** + * @file gnss_helpers.h + * + * @brief Interface between the GNSS middleware and the LR11xx radio driver. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 __GNSS_HELPERS_H__ +#define __GNSS_HELPERS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "lr11xx_gnss_types.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/** + * @brief Return codes for GNSS get results + */ +typedef enum +{ + SMTC_GNSS_GET_RESULTS_NO_ERROR, //!< No error + SMTC_GNSS_GET_RESULTS_ERROR_ALMANAC, //!< Almanac update required + SMTC_GNSS_GET_RESULTS_ERROR_AIDING_POS, //!< No Aiding Position configured + SMTC_GNSS_GET_RESULTS_ERROR_BUFFER_SIZE, //!< Results cannot be stored in given buffer + SMTC_GNSS_GET_RESULTS_ERROR_NO_TIME, //!< No valid time available + SMTC_GNSS_GET_RESULTS_ERROR_UNKNOWN //!< Unkown error +} smtc_gnss_get_results_return_code_t; + +/** + * @brief GNSS scan configuration parameters + */ +typedef struct +{ + bool assisted; //!< assisted or autonomous scan + lr11xx_gnss_constellation_mask_t constellations; //!< constellations to be used + uint8_t input_parameters; //!< scan in put parameters + lr11xx_gnss_freq_search_space_t freq_search_space; //!< frequency search space + uint8_t nb_svs_max; //!< maximum number of Space Vehicles to be detected +} smtc_gnss_scan_params_t; + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/*! + * @brief Manually sets an assistance position for assisted scan + * + * @param [in] radio_context Chip implementation context + * @param [in] assistance_position Assistance position to use for next assisted GNSS scan + * + * @return a boolean: true for success, false otherwise + */ +bool smtc_gnss_set_assistance_position( const void* radio_context, + const lr11xx_gnss_solver_assistance_position_t* assistance_position ); + +/*! + * @brief Push a message received from the geolocation solver to the LR11XX + * + * @param [in] radio_context Chip implementation context + * @param [in] payload Message received from the solver to be pushed to the LR11XX + * @param [in] payload_size Message payload size + * + * @return a boolean: true for success, false otherwise + */ +bool smtc_gnss_push_solver_msg( const void* radio_context, const uint8_t* payload, const uint16_t payload_size ); + +/*! + * @brief Get current almanac CRC from LR11xx radio + * + * @param [in] radio_context Chip implementation context + * @param [out] almanac_crc Current almanac CRC stored in LR11XX + * + * @return a boolean: true for success, false otherwise + */ +bool smtc_gnss_get_almanac_crc( const void* radio_context, uint32_t* almanac_crc ); + +/*! + * @brief Start a GNSS scan + * + * @param [in] radio_context Chip implementation context + * @param [in] date Date to use for GNSS scan operation + * @param [in] params Pointer to the scan parameters to be used + + * @return a boolean: true for success, false otherwise + */ +bool smtc_gnss_scan( const void* radio_context, uint32_t date, const smtc_gnss_scan_params_t* params ); + +/*! + * @brief Execute tear down actions when GNSS scan operation is terminated + */ +void smtc_gnss_scan_ended( void ); + +/*! + * @brief Fetch the result of last GNSS scan operation + * + * @param [in] radio_context Chip implementation context + * @param [in] results_max_size Size of the results array given to store scan results + * @param [out] res_sz Length of the raw result buffer returned by the radio + * @param [out] results Pointer to a buffer that will be filled with raw result buffer + * + * @return get results return code as defined in @ref smtc_gnss_get_results_return_code_t + */ +smtc_gnss_get_results_return_code_t smtc_gnss_get_results( const void* radio_context, const uint8_t results_max_size, + uint8_t* res_sz, uint8_t* results, bool* no_sv_detected ); + +/*! + * @brief Get detailed info of detected SVs + * + * @param [in] radio_context Chip implementation context + * @param [in] sv_info_max_size Size of the sv_info array to fill with detected space vehicle information + * @param [out] nb_detected_sv Number of Space Vehicles detected in last scan + * @param [out] sv_info Pointer to an array of structures of size big enough to contain + * nb_detected_sv elements. Information about Space Vehicles detected in last scan + * + * @return a boolean: true for success, false otherwise + */ +bool smtc_gnss_get_sv_info( const void* radio_context, const uint8_t sv_info_max_size, uint8_t* nb_detected_sv, + lr11xx_gnss_detected_satellite_t* sv_info ); + +/*! + * @brief TODO + */ +bool smtc_gnss_get_almanac_update_status( const void* radio_context, const uint32_t date, + const lr11xx_gnss_solver_assistance_position_t* assistance_position, + const lr11xx_gnss_constellation_mask_t constellations, + bool* almanacs_update_required ); + +/*! + * @brief TODO + */ +bool smtc_gnss_get_doppler_error_from_nav( const uint8_t* nav ); + +/*! + * @brief TODO + */ +bool smtc_gnss_get_doppler_error( const void* radio_context, const uint32_t date, + const lr11xx_gnss_solver_assistance_position_t* assistance_position, + const lr11xx_gnss_constellation_mask_t constellations, const uint8_t nb_detected_sv, + const lr11xx_gnss_detected_satellite_t* detected_sv_info, bool* doppler_error ); + +/*! + * @brief Get the power consumption of the last scan + * + * @param [in] radio_context Chip implementation context + * @param [out] power_consumption_uah Power consumption of the last scan in uAh + * + * @return a boolean: true for success, false otherwise + */ +bool smtc_gnss_get_power_consumption( const void* radio_context, uint32_t* power_consumption_uah ); + +/*! + * @brief Read the current scan context (almanac CRC, aiding position....) + * + * @param [in] radio_context Chip implementation context + * @param [out] aiding_position Aiding position which is currently written in the LR11xx radio + * @param [out] almanac_crc Almanac CRC of the current almanac written in the LR11xx radio + * + * @return a boolean: true for success, false otherwise. + */ +bool smtc_gnss_get_scan_context( const void* radio_context, lr11xx_gnss_solver_assistance_position_t* aiding_position, + uint32_t* almanac_crc ); + +/*! + * @brief Check if the given NAV message is valid (would allow the solver to return a position) + * + * @param [in] constellations Constellations used + * @param [in] nb_detected_satellites Number of detected space vehicles + * @param [in] detected_satellites Space vehicles detected + * + * @return a boolean: true if the NAV message is valid, false otherwise + */ +bool smtc_gnss_is_nav_message_valid( const lr11xx_gnss_constellation_mask_t constellations, + uint8_t nb_detected_satellites, + lr11xx_gnss_detected_satellite_t* detected_satellites ); + +#ifdef __cplusplus +} +#endif + +#endif // __GNSS_HELPERS_H__ + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers_defs.h b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers_defs.h new file mode 100644 index 000000000..70d1f1c01 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_helpers_defs.h @@ -0,0 +1,73 @@ +/** + * @file gnss_helpers_defs.h + * + * @brief Types and constants definitions of GNSS helpers. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 __GNSS_HELPERS_DEFS_H__ +#define __GNSS_HELPERS_DEFS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/*! + * @brief Maximal number of Space Vehicles to search during a GNSS scan + */ +#define GNSS_NB_SVS_MAX ( 10 ) + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +#ifdef __cplusplus +} +#endif + +#endif // __GNSS_HELPERS_DEFS_H__ + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_middleware.c b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_middleware.c new file mode 100644 index 000000000..7aca01397 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_middleware.c @@ -0,0 +1,1298 @@ +/** + * @file gnss_middleware.c + * + * @brief GNSS geolocation middleware implementing scan & send sequence. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include + +#include "gnss_version.h" +#include "gnss_middleware.h" +#include "gnss_helpers.h" +#include "gnss_queue.h" + +#include "mw_assert.h" +#include "mw_dbg_trace.h" + +#include "lr11xx_system.h" + +#include "smtc_modem_middleware_advanced_api.h" +#include "smtc_modem_hal.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/** + * @brief Define flag to enable/disable traces during time critical sections + */ +#define GNSS_MW_FEATURE_OFF 0 +#define GNSS_MW_FEATURE_ON !GNSS_MW_FEATURE_OFF + +/** + * @brief Enable/disable traces during time critical sections + */ +#ifndef GNSS_MW_DBG_TRACE_TIME_CRITICAL +#define GNSS_MW_DBG_TRACE_TIME_CRITICAL GNSS_MW_FEATURE_OFF /* Set to GNSS_MW_FEATURE_ON to enable those traces */ +#endif + +/** + * @brief Print macros to be used in time critical sections + */ +#if( GNSS_MW_DBG_TRACE_TIME_CRITICAL == GNSS_MW_FEATURE_ON ) +#define GNSS_MW_TIME_CRITICAL_TRACE_MSG( msg ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( msg ); \ + } while( 0 ) + +#define GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( ... ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( __VA_ARGS__ ); \ + } while( 0 ) + +#else +#define GNSS_MW_TIME_CRITICAL_TRACE_MSG( msg ) +#define GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( ... ) +#endif + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/** + * @brief Radio planner task ID for GNSS middleware + */ +#define RP_TASK_GNSS SMTC_MODEM_RP_TASK_ID1 + +/** + * @brief LoRaWAN port used for uplinks of the GNSS scan results + */ +#define GNSS_DEFAULT_UPLINK_PORT 192 + +/** + * @brief Solver aiding position buffer size (1byte for TAG and 3 for position) + */ +#define SOLVER_AIDING_POSITION_SIZE 4 + +/** + * @brief The LoRa Basics Modem extended uplink ID to be used for GNSS uplinks (TASK_EXTENDED_1) + */ +#define SMTC_MODEM_EXTENDED_UPLINK_ID_GNSS 1 + +/** + * @brief TODO + */ +#define LR11XX_GNSS_SCALING_LATITUDE 90 + +/** + * @brief TODO + */ +#define LR11XX_GNSS_SCALING_LONGITUDE 180 + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/** + * @brief The list of possible internal pending errors + */ +typedef enum +{ + GNSS_MW_ERROR_NONE, //!< No error + GNSS_MW_ERROR_SCAN_FAILED, //!< Scan failed due to LR11xx error + GNSS_MW_ERROR_NO_TIME, //!< Scan could not be performed because no time available + GNSS_MW_ERROR_UNKNOWN, //!< An unknown error occurred +} gnss_mw_internal_error_t; + +/** + * @brief Description of a scan mode + */ +typedef struct +{ + uint32_t scan_group_delay; //!< The delay between the end of a scan and the start of the next one, in seconds + uint8_t scan_group_size; //!< The number of scans in the scan group + uint8_t scan_group_stop; //!< The number of SVs in a scans necessary to stop the scan group +} gnss_mw_mode_desc_t; + +/** + * @brief TODO + */ +typedef enum +{ + GNSS_MW_SCAN_TYPE_AUTONOMOUS, + GNSS_MW_SCAN_TYPE_ASSISTED, + GNSS_MW_SCAN_TYPE_ASSISTED_FOR_AIDING_POSITION, +} gnss_mw_scan_type_t; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/** + * @brief The modem/radio context given by the application when middleware is initialized. Used to access LBM and radio + * functions. + */ +static ralf_t* modem_radio_ctx = NULL; + +/** + * @brief The modem stack ID to be used. + */ +static uint8_t modem_stack_id = 0; + +/** + * @brief The current scan group queue to store scan results + */ +static gnss_scan_group_queue_t gnss_scan_group_queue; + +/*! + * @brief Indicates if a user update of the current assistance position is ready to be written to the LR11xx on the next + * scan + */ +static bool user_aiding_position_update_received = false; + +/*! + * @brief The assistance position user update to be written to the LR11xx for the next scan + */ +static lr11xx_gnss_solver_assistance_position_t user_aiding_position_update; + +/*! + * @brief Indicates if a solver update of the current assistance position is ready to be written to the LR11xx on the + * next scan + */ +static bool solver_aiding_position_update_received = false; + +/*! + * @brief The assistance position solver update to be written to the LR11xx for the next scan + */ +static uint8_t solver_aiding_position_update[SOLVER_AIDING_POSITION_SIZE]; + +/*! + * @brief TODO + */ +static gnss_mw_scan_type_t current_scan_type = GNSS_MW_SCAN_TYPE_AUTONOMOUS; + +/*! + * @brief Pre-defined scan modes to be selected by the user depending on the use case (STATIC, MOBILE...) + */ +static gnss_mw_mode_desc_t modes[__GNSS_MW_MODE__SIZE] = { + { .scan_group_delay = 15, .scan_group_size = 4, .scan_group_stop = 0 }, /* GNSS_MW_MODE_STATIC */ + { .scan_group_delay = 0, .scan_group_size = 2, .scan_group_stop = 0 }, /* GNSS_MW_MODE_MOBILE */ +}; + +static gnss_mw_mode_desc_t aiding_position_request_mode = { + .scan_group_delay = 0, .scan_group_size = 4, .scan_group_stop = 8 +}; /* If at least 8 SVs detected on a scan, the scan group can be stopped and sent for solving */ + +/*! + * @brief The index of the modes[] array to get configuration for the current scan sequence + */ +static gnss_mw_mode_t current_mode_index; + +/*! + * @brief The current pending errors (reset when a new scan sequence starts) + */ +static gnss_mw_internal_error_t pending_error = GNSS_MW_ERROR_NONE; + +/*! + * @brief The current pending events (reset by the user or when a new scan sequence starts) + */ +static uint8_t pending_events = 0; + +/*! + * @brief The selected constellations to be used + */ +static lr11xx_gnss_constellation_mask_t current_constellations = LR11XX_GNSS_GPS_MASK + LR11XX_GNSS_BEIDOU_MASK; + +/*! + * @brief The LoRaWAN port on which results uplinks are sent + */ +static uint8_t lorawan_port = GNSS_DEFAULT_UPLINK_PORT; + +/*! + * @brief Indicates if the next scan will use the same token as the previous one + */ +static bool scan_aggregate = false; + +/*! + * @brief Indicates sequence to "scan & send" or "scan only" mode + */ +static bool send_bypass = false; + +/*! + * @brief User has requested to cancel the scan that was scheduled + */ +static bool task_cancelled_by_user = false; + +/*! + * @brief The scan sequence has started + * Set to true when the first scan of the sequence actually started (radio) + * Set back to false when the complete sequence is terminated (all results sent) + */ +static bool task_running = false; + +/*! + * @brief LR11xx current configuration context (almanac CRC, aiding position....) + */ +static gnss_mw_scan_context_t lr11xx_scan_context; + +/*! + * @brief TODO + */ +static uint8_t aid_pos_check_buffer[5 + GNSS_RESULT_SIZE_MAX_MODE3] = { 0 }; /* | TAG | 0x00 | LAT/LON | NAV | */ + +/*! + * @brief TODO + */ +static uint8_t stat_nb_scans_sent_within_current_scan_group = 0; +static bool stat_aiding_position_check_sent = false; +static bool stat_indoor_detected = false; + +/*! + * @brief TODO + */ +static bool autonomous_scan_for_indoor_check = false; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Program the next scan of the scan group, with the specified delay + * + * @param [in] delay_s Delay in seconds to start the scan + * + * @return the error code as returned by the modem / radio planner + */ +static smtc_modem_return_code_t gnss_mw_scan_next( uint32_t delay_s ); + +/*! + * @brief TODO + */ +static uint32_t gnss_mw_get_next_scan_delay( void ); + +/*! + * @brief Interrupt handler signaled by the Radio Planner when the radio is available and it is time to start the scan + * (WARNING: running under interrupt context) + * + * @param [in] context Pointer to context given by RP (not used) + */ +static void gnss_mw_scan_rp_task_launch( void* context ); + +/*! + * @brief Interrupt handler signaled by the Radio Planner when the scan is completed (WARNING: running under interrupt + * context) + * + * @param [in] status IRQ status from RP + */ +static void gnss_mw_scan_rp_task_done( smtc_modem_rp_status_t* status ); + +/*! + * @brief Send an uplink with the current aiding position to LoRaCloud, for checking if possible and send a downlink + * update if necessary. + * + * @param [in] current_assistance_position The current aiding position configured + * + * @return a boolean set to true for success, false otherwise + */ +static bool gnss_mw_send_aiding_position_check_request( + const lr11xx_gnss_solver_assistance_position_t* current_assistance_position, gnss_scan_t* scan ); + +/*! + * @brief Callback called by the LBM when the uplink has been sent. Pop the next result ot be sent until the scan group + * queue is empty. + */ +static void gnss_mw_tx_done_callback( void ); + +/*! + * @brief Request an uplink to LBM through the extended API (no buffer copy) + * + * @param [in] tx_frame_buffer A pointer to the buffer payload to be sent over the air. + * @param [in] tx_frame_buffer_size The size of the buffer to be sent. + * + * @return a boolean set to true for success, false otherwise. + */ +static bool gnss_mw_send_frame( const uint8_t* tx_frame_buffer, const uint8_t tx_frame_buffer_size, uint8_t port ); + +/*! + * @brief Add an event to the pending event bitfield, and send all pending events to the application + * + * @param [in] event_type The event to be added to the pending events bitfield. + */ +static void gnss_mw_send_event( gnss_mw_event_type_t event_type ); + +/** + * @brief Forward the aiding position received from the solver (through a downlink) to the LR11xx + * + * @param [in] payload Aiding position payload as transmitted by the solver + * @param [in] size Size of the aiding position payload + */ +static void gnss_mw_set_solver_aiding_position( const uint8_t* payload, uint8_t size ); + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +mw_return_code_t gnss_mw_get_version( mw_version_t* version ) +{ + if( version == NULL ) + { + MW_DBG_TRACE_ERROR( "Failed to get GNSS middleware version\n" ); + return MW_RC_FAILED; + } + + version->major = GNSS_MW_VERSION_MAJOR; + version->minor = GNSS_MW_VERSION_MINOR; + version->patch = GNSS_MW_VERSION_PATCH; + + return MW_RC_OK; +} + +mw_return_code_t gnss_mw_init( ralf_t* modem_radio, uint8_t stack_id ) +{ + if( modem_radio == NULL ) + { + MW_DBG_TRACE_ERROR( "Failed to init GNSS middleware: modem radio not set\n" ); + return MW_RC_FAILED; + } + + /* Initialize GNSS scan group queue */ + memset( &gnss_scan_group_queue, 0, sizeof( gnss_scan_group_queue_t ) ); + gnss_scan_group_queue_reset_token( &gnss_scan_group_queue ); + + /* Set radio context */ + modem_radio_ctx = modem_radio; + + /* Set modem stack ID */ + modem_stack_id = stack_id; + + return MW_RC_OK; +} + +mw_return_code_t gnss_mw_scan_start( gnss_mw_mode_t mode, uint32_t start_delay ) +{ + smtc_modem_return_code_t modem_rc; + bool scan_group_err; + + if( modem_radio_ctx == NULL ) + { + MW_DBG_TRACE_ERROR( "GNSS middleware not ready, cannot start scan\n" ); + return MW_RC_FAILED; + } + + if( mode >= __GNSS_MW_MODE__SIZE ) + { + MW_DBG_TRACE_ERROR( "Wrong parameter, mode %d is not supported\n", mode ); + return MW_RC_FAILED; + } + + /* Check a sequence sequence is already running (meaning the RP task has been launched) */ + if( task_running == true ) + { + MW_DBG_TRACE_ERROR( "GNSS scan on-going, cannot start a new scan yet\n" ); + return MW_RC_BUSY; + } + + /* Set selected mode */ + current_mode_index = mode; + + /* Reset pending internal error */ + pending_error = GNSS_MW_ERROR_NONE; + + /* Reset pending events */ + pending_events = 0; + + /* Reset any pending cancel request which has not been completed (error case) */ + task_cancelled_by_user = false; + + /* Reset scan context */ + memset( &lr11xx_scan_context, 0, sizeof lr11xx_scan_context ); + + /* Reset stats */ + stat_nb_scans_sent_within_current_scan_group = 0; + stat_aiding_position_check_sent = false; + stat_indoor_detected = false; + + /* Switch back to assisted if previous scan was for aiding position request */ + if( current_scan_type == GNSS_MW_SCAN_TYPE_ASSISTED_FOR_AIDING_POSITION ) + { + current_scan_type = GNSS_MW_SCAN_TYPE_ASSISTED; + } + + /* Initialize new scan group */ + MW_DBG_TRACE_INFO( "New scan group - type:%d - start_delay:%us\n", current_scan_type, start_delay ); + if( current_scan_type != GNSS_MW_SCAN_TYPE_ASSISTED ) + { + scan_group_err = + gnss_scan_group_queue_new( &gnss_scan_group_queue, aiding_position_request_mode.scan_group_size, + aiding_position_request_mode.scan_group_stop ); + } + else + { + scan_group_err = gnss_scan_group_queue_new( &gnss_scan_group_queue, modes[current_mode_index].scan_group_size, + modes[current_mode_index].scan_group_stop ); + } + if( scan_group_err != true ) + { + MW_DBG_TRACE_ERROR( "Failed to create scan group queue\n" ); + return MW_RC_FAILED; + } + + /* Prepare the task for next scan */ + modem_rc = gnss_mw_scan_next( start_delay ); + if( modem_rc != SMTC_MODEM_RC_OK ) + { + return MW_RC_FAILED; + } + + return MW_RC_OK; +} + +mw_return_code_t gnss_mw_scan_cancel( void ) +{ + smtc_modem_return_code_t modem_rc; + + if( modem_radio_ctx == NULL ) + { + MW_DBG_TRACE_ERROR( "GNSS middleware not ready, no scan to cancel\n" ); + return MW_RC_FAILED; + } + + /* The GNSS scan sequence will be in running state from the moment the task + has been started by the RP, until all the packets have been sent over the + air. This is handled this way for more simplicity: + - as we cannot abort a running scan, it would requires to check RP state + to know if we can abort the scheduled task or not. + - aborting anywhere in the complete sequence will require lot of logic + through all the code, we want to keep as simple as possible + So a scan can be cancelled only if requested before the first scan as + actually started. Once it is started, it will complete the sequence */ + if( task_running == true ) + { + MW_DBG_TRACE_ERROR( "GNSS scan sequence started, too late to cancel\n" ); + return MW_RC_BUSY; + } + + task_cancelled_by_user = true; + + MW_DBG_TRACE_INFO( "RP_TASK_GNSS - Request cancel of scheduled scan\n" ); + modem_rc = smtc_modem_rp_abort_user_radio_access_task( RP_TASK_GNSS ); + if( modem_rc != SMTC_MODEM_RC_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to abort GNSS scan task\n" ); + } + + return MW_RC_OK; +} + +mw_return_code_t gnss_mw_set_user_aiding_position( float latitude, float longitude ) +{ + /* Store the user assistance position to be written to the LR11xx on the next scan */ + user_aiding_position_update.latitude = latitude; + user_aiding_position_update.longitude = longitude; + user_aiding_position_update_received = true; + + /* We can switch to assisted scan for the next scan */ + current_scan_type = GNSS_MW_SCAN_TYPE_ASSISTED; + + return MW_RC_OK; +} + +bool gnss_mw_has_event( uint8_t pending_events, gnss_mw_event_type_t event ) +{ + if( ( pending_events & ( 1 << event ) ) == ( 1 << event ) ) + { + return true; + } + + return false; +} + +mw_return_code_t gnss_mw_get_event_data_scan_done( gnss_mw_event_data_scan_done_t* data ) +{ + if( data == NULL ) + { + MW_DBG_TRACE_ERROR( "Provided pointer is NULL\n" ); + return MW_RC_FAILED; + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_SCAN_DONE ) ) + { + data->is_valid = gnss_scan_group_queue_is_valid( &gnss_scan_group_queue ); + data->token = gnss_scan_group_queue.token; + data->nb_scans_valid = gnss_scan_group_queue.nb_scans_valid; + /* Note: nb_scans_valid is <= GNSS_SCAN_GROUP_SIZE_MAX */ + for( uint8_t i = 0; i < gnss_scan_group_queue.nb_scans_valid; i++ ) + { + data->scans[i].nav = &gnss_scan_group_queue.scans[i].results_buffer[GNSS_SCAN_METADATA_SIZE]; + data->scans[i].nav_size = gnss_scan_group_queue.scans[i].results_size; + data->scans[i].nav_valid = gnss_scan_group_queue.scans[i].nav_valid; + data->scans[i].timestamp = gnss_scan_group_queue.scans[i].timestamp; + data->scans[i].nb_svs = gnss_scan_group_queue.scans[i].detected_svs; + /* Note: detected_sv is <= GNSS_NB_SVS_MAX */ + for( uint8_t j = 0; j < gnss_scan_group_queue.scans[i].detected_svs; j++ ) + { + data->scans[i].info_svs[j].sv_id = gnss_scan_group_queue.scans[i].info_svs[j].satellite_id; + data->scans[i].info_svs[j].cnr = gnss_scan_group_queue.scans[i].info_svs[j].cnr; + } + } + data->power_consumption_uah = gnss_scan_group_queue.power_consumption_uah; + data->context.mode = lr11xx_scan_context.mode; + data->context.assisted = lr11xx_scan_context.assisted; + data->context.aiding_position_latitude = lr11xx_scan_context.aiding_position_latitude; + data->context.aiding_position_longitude = lr11xx_scan_context.aiding_position_longitude; + data->context.almanac_crc = lr11xx_scan_context.almanac_crc; + data->context.almanac_update_required = lr11xx_scan_context.almanac_update_required; + + return MW_RC_OK; + } + else + { + MW_DBG_TRACE_ERROR( "Data are not ready\n" ); + return MW_RC_FAILED; + } +} + +void gnss_mw_set_constellations( gnss_mw_constellation_t constellations ) +{ + if( constellations == GNSS_MW_CONSTELLATION_GPS ) + { + current_constellations = LR11XX_GNSS_GPS_MASK; + } + else if( constellations == GNSS_MW_CONSTELLATION_BEIDOU ) + { + current_constellations = LR11XX_GNSS_BEIDOU_MASK; + } + else + { + current_constellations = LR11XX_GNSS_GPS_MASK + LR11XX_GNSS_BEIDOU_MASK; + } +} + +void gnss_mw_set_port( uint8_t port ) { lorawan_port = port; } + +void gnss_mw_scan_aggregate( bool aggregate ) +{ + MW_DBG_TRACE_INFO( "GNSS scan: set aggregate mode to %s\n", aggregate ? "TRUE" : "FALSE" ); + + /* Set scan aggregation current mode */ + scan_aggregate = aggregate; +} + +void gnss_mw_send_bypass( bool no_send ) +{ + MW_DBG_TRACE_INFO( "GNSS scan: set scan only mode to %s (bypass send)\n", no_send ? "TRUE" : "FALSE" ); + + /* Set scan only current mode */ + send_bypass = no_send; +} + +void gnss_mw_display_results( const gnss_mw_event_data_scan_done_t* data ) +{ + uint8_t i, j; + + if( data != NULL ) + { + MW_DBG_TRACE_PRINTF( "SCAN_DONE info:\n" ); + MW_DBG_TRACE_PRINTF( "-- token: 0x%02X\n", data->token ); + MW_DBG_TRACE_PRINTF( "-- is_valid: %d\n", data->is_valid ); + MW_DBG_TRACE_PRINTF( "-- number of valid scans: %u\n", data->nb_scans_valid ); + for( i = 0; i < data->nb_scans_valid; i++ ) + { + MW_DBG_TRACE_PRINTF( "-- scan[%d][%u] (%u SV - %d): ", i, data->scans[i].timestamp, data->scans[i].nb_svs, + data->scans[i].nav_valid ); + for( j = 0; j < data->scans[i].nav_size; j++ ) + { + MW_DBG_TRACE_PRINTF( "%02X", data->scans[i].nav[j] ); + } + MW_DBG_TRACE_PRINTF( "\n" ); + for( j = 0; j < data->scans[i].nb_svs; j++ ) + { + MW_DBG_TRACE_PRINTF( " SV_ID %u:\t%ddB\n", data->scans[i].info_svs[j].sv_id, + data->scans[i].info_svs[j].cnr ); + } + } + MW_DBG_TRACE_PRINTF( "-- power consumption: %u uah\n", data->power_consumption_uah ); + MW_DBG_TRACE_PRINTF( "-- mode: %d\n", data->context.mode ); + MW_DBG_TRACE_PRINTF( "-- assisted: %d\n", data->context.assisted ); + if( data->context.assisted == true ) + { + MW_DBG_TRACE_PRINTF( "-- aiding position: (%.6f, %.6f)\n", data->context.aiding_position_latitude, + data->context.aiding_position_longitude ); + } + MW_DBG_TRACE_PRINTF( "-- almanac CRC: 0X%08X\n", data->context.almanac_crc ); + MW_DBG_TRACE_PRINTF( "-- almanac update required: %d\n", data->context.almanac_update_required ); + } +} + +mw_return_code_t gnss_mw_get_event_data_terminated( gnss_mw_event_data_terminated_t* data ) +{ + if( data == NULL ) + { + MW_DBG_TRACE_ERROR( "Provided pointer is NULL\n" ); + return MW_RC_FAILED; + } + + if( gnss_mw_has_event( pending_events, GNSS_MW_EVENT_TERMINATED ) ) + { + data->nb_scans_sent = stat_nb_scans_sent_within_current_scan_group; + data->aiding_position_check_sent = stat_aiding_position_check_sent; + data->indoor_detected = stat_indoor_detected; + return MW_RC_OK; + } + else + { + MW_DBG_TRACE_ERROR( "Scan is not terminated\n" ); + return MW_RC_FAILED; + } +} + +void gnss_mw_clear_pending_events( void ) { pending_events = 0; } + +mw_return_code_t gnss_mw_handle_downlink( uint8_t port, const uint8_t* payload, uint8_t size ) +{ + /* Check if this downlink is for the GNSS middleware, otherwise, just ignore it */ + if( port != lorawan_port ) + { + return MW_RC_OK; + } + + /* Check input parameters */ + if( payload == NULL ) + { + MW_DBG_TRACE_ERROR( "Downlink payload pointer is NULL\n" ); + return MW_RC_FAILED; + } + if( size < SOLVER_AIDING_POSITION_SIZE ) + { + MW_DBG_TRACE_ERROR( "Downlink payload size not supported\n" ); + return MW_RC_FAILED; + } + + /* Check if the payload contains an D-GNSSLOC-AIDP: Aiding Position as defined by LoRaCloud */ + if( payload[0] == 0x00 ) + { + MW_DBG_TRACE_INFO( "Received D-GNSSLOC-AIDP solver message\n" ); + gnss_mw_set_solver_aiding_position( payload, SOLVER_AIDING_POSITION_SIZE ); + } + else + { + MW_DBG_TRACE_ERROR( "Unknown downlink type for GNSS: 0x%02X\n", payload[0] ); + return MW_RC_FAILED; + } + + /* Note: The remaining bytes (if any) will be ignored. In the future, we could support several messages in the + * payload */ + + return MW_RC_OK; +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +static smtc_modem_return_code_t gnss_mw_scan_next( uint32_t delay_s ) +{ + smtc_modem_rp_task_t rp_task = { 0 }; + smtc_modem_return_code_t modem_rc; + uint32_t time_ms, delay_ms; + + time_ms = smtc_modem_hal_get_time_in_ms( ) + 300; /* 300ms for scheduling delay */ + delay_ms = delay_s * 1000; + + rp_task.type = SMTC_MODEM_RP_TASK_STATE_ASAP; + rp_task.start_time_ms = time_ms + delay_ms; + rp_task.duration_time_ms = 10 * 1000; + rp_task.id = RP_TASK_GNSS; + rp_task.launch_task_callback = gnss_mw_scan_rp_task_launch; + rp_task.end_task_callback = gnss_mw_scan_rp_task_done; + modem_rc = smtc_modem_rp_add_user_radio_access_task( &rp_task ); + if( modem_rc == SMTC_MODEM_RC_OK ) + { + GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( "RP_TASK_GNSS - scan task queued at %u + %u\n", time_ms, delay_ms ); + } + else + { + MW_DBG_TRACE_ERROR( "RP_TASK_GNSS - failed to queue scan task (0x%02X)\n", modem_rc ); + } + + return modem_rc; +} + +static uint32_t gnss_mw_get_next_scan_delay( void ) +{ + if( current_scan_type == GNSS_MW_SCAN_TYPE_ASSISTED ) + { + return modes[current_mode_index].scan_group_delay; + } + else + { + return aiding_position_request_mode.scan_group_delay; + } +} + +static void gnss_mw_scan_rp_task_launch( void* context ) +{ + smtc_modem_return_code_t err; + uint32_t gps_time = 0; + uint32_t fractional_seconds = 0; + lr11xx_gnss_solver_assistance_position_t lr11xx_aiding_position; + smtc_gnss_scan_params_t scan_params; + gnss_mw_scan_type_t scan_type = + ( ( autonomous_scan_for_indoor_check == true ) ? GNSS_MW_SCAN_TYPE_AUTONOMOUS : current_scan_type ); + + /* From now, the scan sequence can not be cancelled */ + task_running = true; + + MW_DBG_TRACE_PRINTF( "---- internal scan start (%d) ----\n", scan_type ); + + err = smtc_modem_get_time( &gps_time, &fractional_seconds ); + if( err == SMTC_MODEM_RC_OK ) + { + /* Set assistance position if an update is pending */ + if( user_aiding_position_update_received == true ) + { + if( smtc_gnss_set_assistance_position( modem_radio_ctx->ral.context, &user_aiding_position_update ) == + true ) + { + GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( "User assistance position set to (%.6f, %.6f)\n", + user_aiding_position_update.latitude, + user_aiding_position_update.longitude ); + user_aiding_position_update_received = false; + } + } + if( solver_aiding_position_update_received == true ) + { + if( smtc_gnss_push_solver_msg( modem_radio_ctx->ral.context, solver_aiding_position_update, + SOLVER_AIDING_POSITION_SIZE ) == true ) + { + GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( "Solver assistance position set to " ); + for( int i = 0; i < SOLVER_AIDING_POSITION_SIZE; i++ ) + { + GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( "%02X ", solver_aiding_position_update[i] ); + } + GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( "\n" ); + solver_aiding_position_update_received = false; + } + } + + /* Get context used for scan */ + smtc_gnss_get_scan_context( modem_radio_ctx->ral.context, &lr11xx_aiding_position, + &lr11xx_scan_context.almanac_crc ); + lr11xx_scan_context.aiding_position_latitude = lr11xx_aiding_position.latitude; + lr11xx_scan_context.aiding_position_longitude = lr11xx_aiding_position.longitude; + lr11xx_scan_context.assisted = ( scan_type == GNSS_MW_SCAN_TYPE_AUTONOMOUS ) ? false : true; + lr11xx_scan_context.mode = current_mode_index; + lr11xx_scan_context.gps_time = gps_time; + + /* Set GNSS scan parameters */ + scan_params.constellations = current_constellations; + switch( scan_type ) + { + case GNSS_MW_SCAN_TYPE_AUTONOMOUS: + scan_params.assisted = false; + scan_params.freq_search_space = LR11XX_GNSS_FREQUENCY_SEARCH_SPACE_250_HZ; /* not used */ + scan_params.input_parameters = LR11XX_GNSS_RESULTS_DOPPLER_MASK + + LR11XX_GNSS_RESULTS_DOPPLER_ENABLE_MASK; /* 14 dopplers max, no bit change */ + scan_params.nb_svs_max = + 9; /* NAV2: 9 SV + 9 dopplers => 44 bytes (reduce nb SV to keep space for APC message)*/ + break; + case GNSS_MW_SCAN_TYPE_ASSISTED: + scan_params.assisted = true; + scan_params.freq_search_space = LR11XX_GNSS_FREQUENCY_SEARCH_SPACE_250_HZ; + scan_params.input_parameters = 0; /* no dopplers, no bit change */ + scan_params.nb_svs_max = GNSS_NB_SVS_MAX; + break; + case GNSS_MW_SCAN_TYPE_ASSISTED_FOR_AIDING_POSITION: + scan_params.assisted = true; + scan_params.freq_search_space = LR11XX_GNSS_FREQUENCY_SEARCH_SPACE_2_KHZ; + scan_params.input_parameters = LR11XX_GNSS_RESULTS_DOPPLER_MASK + + LR11XX_GNSS_RESULTS_DOPPLER_ENABLE_MASK; /* 14 dopplers max, no bit change */ + scan_params.nb_svs_max = + 9; /* NAV2: 9 SV + 9 dopplers => 47 bytes (reduce nb SV to keep payload under 50 bytes) */ + break; + default: + break; + } + + /* Start GNSS scan */ + if( smtc_gnss_scan( modem_radio_ctx->ral.context, gps_time, &scan_params ) != true ) + { + pending_error = GNSS_MW_ERROR_SCAN_FAILED; + + MW_DBG_TRACE_ERROR( "RP_TASK_GNSS - failed to start scan, abort task\n" ); + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_rp_abort_user_radio_access_task( RP_TASK_GNSS ) ); + + /* + When aborting the task, the RP will call the end_task_callback() with SMTC_RP_RADIO_ABORTED status. + ERROR event will be sent from there to the application + */ + } + } + else if( err == SMTC_MODEM_RC_NO_TIME ) + { + pending_error = GNSS_MW_ERROR_NO_TIME; + + MW_DBG_TRACE_ERROR( "RP_TASK_GNSS - time sync is not valid, abort task\n" ); + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_rp_abort_user_radio_access_task( RP_TASK_GNSS ) ); + + /* + When aborting the task, the RP will call the end_task_callback() with SMTC_RP_RADIO_ABORTED status. + ERROR event will be sent from there to the application + */ + } + else + { + pending_error = GNSS_MW_ERROR_UNKNOWN; + + MW_DBG_TRACE_ERROR( "RP_TASK_GNSS - failed to get time, abort task\n" ); + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_rp_abort_user_radio_access_task( RP_TASK_GNSS ) ); + + /* + When aborting the task, the RP will call the end_task_callback() with SMTC_RP_RADIO_ABORTED status. + ERROR event will be sent from there to the application + */ + } +} + +void gnss_mw_scan_rp_task_done( smtc_modem_rp_status_t* status ) +{ + smtc_modem_rp_radio_status_t irq_status = status->status; + uint32_t time_ms; + uint32_t meas_time; + uint32_t power_consumption_uah; + + /* ------------------------------------------------------------------------- + WARNING: put the radio back to sleep before exiting this function. + ---------------------------------------------------------------------- */ + + /* ------------------------------------------------------------------------- + WARNING: this callback function is called by the radio planner under + interrupt context. Exit as fast as possible. + ---------------------------------------------------------------------- */ + + time_ms = smtc_modem_hal_get_time_in_ms( ); + GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( "GNSS task done at %d (irq_status=%d)\n", time_ms, irq_status ); + + /* GNSS task completed or aborted - first thing to be done */ + smtc_gnss_scan_ended( ); + + if( irq_status == SMTC_RP_RADIO_ABORTED ) /* by RP or by user */ + { + if( pending_error == GNSS_MW_ERROR_NONE ) + { + if( task_cancelled_by_user == false ) + { + MW_DBG_TRACE_WARNING( "RP_TASK_GNSS(%d) - task aborted by RP\n", __LINE__ ); + /* Program next GNSS scan */ + MW_ASSERT_SMTC_MODEM_RC( gnss_mw_scan_next( gnss_mw_get_next_scan_delay( ) ) ); + } + else + { + MW_DBG_TRACE_WARNING( "RP_TASK_GNSS(%d) - task cancelled by user\n", __LINE__ ); + + /* We handle here the fact that when the user requests to abort a radio planner task + with smtc_modem_rp_abort_user_radio_access_task(), the RP will call this task_done callback + with status set to SMTC_RP_RADIO_ABORTED */ + + /* reset cancel request status */ + task_cancelled_by_user = false; + + /* Send an event to application to notify for error */ + gnss_mw_send_event( GNSS_MW_EVENT_SCAN_CANCELLED ); + } + } + else if( pending_error == GNSS_MW_ERROR_NO_TIME ) + { + MW_DBG_TRACE_WARNING( "RP_TASK_GNSS(%d) - task aborted NO_TIME\n", __LINE__ ); + + /* Send an event to application to notify for error */ + gnss_mw_send_event( GNSS_MW_EVENT_ERROR_NO_TIME ); + } + else + { + MW_DBG_TRACE_WARNING( "RP_TASK_GNSS(%d) - task aborted for UNKNOWN reason\n", __LINE__ ); + + /* Send an event to application to notify for error */ + gnss_mw_send_event( GNSS_MW_EVENT_ERROR_UNKNOWN ); + } + } + else if( irq_status == SMTC_RP_RADIO_GNSS_SCAN_DONE ) + { + gnss_scan_t scan_results = { 0 }; + smtc_gnss_get_results_return_code_t scan_results_rc; + bool scan_results_no_sv = false; + bool doppler_error = false; + bool almanac_update_required = false; + lr11xx_gnss_solver_assistance_position_t current_assistance_position; + + current_assistance_position.latitude = lr11xx_scan_context.aiding_position_latitude; + current_assistance_position.longitude = lr11xx_scan_context.aiding_position_longitude; + + /* Get scan results from LR1110 */ + scan_results.timestamp = mw_get_gps_time( ); + scan_results_rc = + smtc_gnss_get_results( modem_radio_ctx->ral.context, GNSS_RESULT_SIZE_MAX_MODE3, &scan_results.results_size, + &scan_results.results_buffer[GNSS_SCAN_METADATA_SIZE], &scan_results_no_sv ); + + /* Get scan power consumption and aggregate it to the scan group power consumption */ + smtc_gnss_get_power_consumption( modem_radio_ctx->ral.context, &power_consumption_uah ); + GNSS_MW_TIME_CRITICAL_TRACE_PRINTF( "Scan power consumption: %u uah\n", power_consumption_uah ); + gnss_scan_group_queue.power_consumption_uah += power_consumption_uah; + + if( scan_results_rc == SMTC_GNSS_GET_RESULTS_NO_ERROR ) + { + /* Get detailed info about the scan */ + smtc_gnss_get_sv_info( modem_radio_ctx->ral.context, GNSS_NB_SVS_MAX, &scan_results.detected_svs, + scan_results.info_svs ); + + if( autonomous_scan_for_indoor_check == true ) + { + /* The scan which just completed was for an indoor / aiding position check */ + autonomous_scan_for_indoor_check = false; + + if( scan_results_no_sv == true ) + { + /* If NO SATELLITES has been detected, we consider the device is indoor */ + stat_indoor_detected = true; + gnss_mw_send_event( GNSS_MW_EVENT_TERMINATED ); + } + else + { + /* The autonomous scan detected some SVs but not enough to generate a NAV, so send an aiding + * position check uplink */ + stat_aiding_position_check_sent = true; + gnss_mw_send_aiding_position_check_request( ¤t_assistance_position, &scan_results ); + } + } + else + { + /* Get almanac update status (once per scan group) */ + if( lr11xx_scan_context.almanac_update_checked == false ) + { + smtc_gnss_get_almanac_update_status( modem_radio_ctx->ral.context, lr11xx_scan_context.gps_time, + ¤t_assistance_position, current_constellations, + &almanac_update_required ); + /* Update scan context */ + lr11xx_scan_context.almanac_update_checked = true; + lr11xx_scan_context.almanac_update_required = almanac_update_required; + } + + /* Check if there is doppler error for detected satellites (if assisted scan) */ + if( ( scan_results.detected_svs > 0 ) && ( current_scan_type != GNSS_MW_SCAN_TYPE_AUTONOMOUS ) && + ( lr11xx_scan_context.almanac_update_required == false ) ) + { + smtc_gnss_get_doppler_error( + modem_radio_ctx->ral.context, lr11xx_scan_context.gps_time, ¤t_assistance_position, + current_constellations, scan_results.detected_svs, scan_results.info_svs, + &doppler_error ); /* TODO: need to add constellations to scan context */ + } + + if( ( doppler_error == true ) && + ( current_scan_type != GNSS_MW_SCAN_TYPE_ASSISTED_FOR_AIDING_POSITION ) ) + { + current_scan_type = GNSS_MW_SCAN_TYPE_ASSISTED_FOR_AIDING_POSITION; + + /* Save power consumption */ + uint32_t power_consumption_uah_save = gnss_scan_group_queue.power_consumption_uah; + + /* Clear current queue and reconfigure */ + gnss_scan_group_queue_new( &gnss_scan_group_queue, aiding_position_request_mode.scan_group_size, + aiding_position_request_mode.scan_group_stop ); + + /* Restore power consumption */ + gnss_scan_group_queue.power_consumption_uah = power_consumption_uah_save; + + /* Program next GNSS scan */ + MW_ASSERT_SMTC_MODEM_RC( gnss_mw_scan_next( 0 ) ); + } + else + { + /* Check if the NAV message is valid (the solver can use this single NAV to get a position) */ + scan_results.nav_valid = smtc_gnss_is_nav_message_valid( + current_constellations, scan_results.detected_svs, scan_results.info_svs ); + + /* Push scan to the scan group */ + gnss_scan_group_queue_push( &gnss_scan_group_queue, &scan_results ); + + /* Trigger next GNSS scan or send first scan results, if scan group completed */ + if( gnss_scan_group_queue_is_full( &gnss_scan_group_queue ) == false ) + { + /* Program next GNSS scan */ + MW_ASSERT_SMTC_MODEM_RC( gnss_mw_scan_next( gnss_mw_get_next_scan_delay( ) ) ); + } + else + { + /* All scans in the group have been completed, send an event to application */ + gnss_mw_send_event( GNSS_MW_EVENT_SCAN_DONE ); + + /* Send results */ + /* static variables because there is no copy done by LBM for extended send API */ + static uint8_t* buffer_to_send; + static uint8_t buffer_to_send_size; + + /* Is there any scan to be sent ? */ + if( gnss_scan_group_queue_pop( &gnss_scan_group_queue, &buffer_to_send, + &buffer_to_send_size ) == false ) + { + /* No SV detected for this scan group, program an autonomous scan for indoor check / aiding + * position check */ + autonomous_scan_for_indoor_check = true; + MW_ASSERT_SMTC_MODEM_RC( gnss_mw_scan_next( 0 ) ); + /* TODO: if current scan was an autonomous one, we could avoid this last one for indoor + * check */ + } + else + { + /* Check if "no send "mode" is configured */ + if( send_bypass == true ) + { + /* Send an event to application to notify for completion */ + /* The application needs to know that it can proceed with the next scan */ + gnss_mw_send_event( GNSS_MW_EVENT_TERMINATED ); + } + else + { + /* Send uplink */ + if( gnss_mw_send_frame( buffer_to_send, buffer_to_send_size, lorawan_port ) == false ) + { + MW_DBG_TRACE_ERROR( "Failed to send uplink frame\n" ); + /* TODO: send error event ? */ + } + else + { + stat_nb_scans_sent_within_current_scan_group += 1; + } + } + } + } + } + } + } + else if( scan_results_rc == SMTC_GNSS_GET_RESULTS_ERROR_ALMANAC ) + { + MW_DBG_TRACE_ERROR( "RP_TASK_GNSS - almanac update required\n" ); + /* Send an event to application to notify for error */ + gnss_mw_send_event( GNSS_MW_EVENT_ERROR_ALMANAC_UPDATE ); + } + else if( scan_results_rc == SMTC_GNSS_GET_RESULTS_ERROR_AIDING_POS ) + { + MW_DBG_TRACE_ERROR( "RP_TASK_GNSS - no assistance position configured\n" ); + /* Send an event to application to notify for error */ + gnss_mw_send_event( GNSS_MW_EVENT_ERROR_NO_AIDING_POSITION ); + } + else if( scan_results_rc == SMTC_GNSS_GET_RESULTS_ERROR_NO_TIME ) + { + MW_DBG_TRACE_ERROR( "RP_TASK_GNSS - no valid time available\n" ); + /* Send an event to application to notify for error */ + gnss_mw_send_event( GNSS_MW_EVENT_ERROR_NO_TIME ); + } + else + { + MW_DBG_TRACE_ERROR( "RP_TASK_GNSS - unkown error on get results\n" ); + /* Send an event to application to notify for error */ + gnss_mw_send_event( GNSS_MW_EVENT_ERROR_UNKNOWN ); + } + } + else + { + MW_DBG_TRACE_ERROR( "GNSS RP task - Unknown status %d at %d\n", irq_status, time_ms ); + + /* Send an event to application to notify for error */ + gnss_mw_send_event( GNSS_MW_EVENT_ERROR_UNKNOWN ); + } + + /* Monitor callback exec duration (should be kept as low as possible) */ + meas_time = smtc_modem_hal_get_time_in_ms( ); + MW_DBG_TRACE_WARNING( "GNSS RP task - done callback duration %u ms\n", meas_time - time_ms ); + + /* Set the radio back to sleep */ + mw_radio_set_sleep( modem_radio_ctx->ral.context ); +} + +static bool gnss_mw_send_aiding_position_check_request( + const lr11xx_gnss_solver_assistance_position_t* current_assistance_position, gnss_scan_t* scan ) +{ + bool success = false; + const int16_t latitude = ( ( current_assistance_position->latitude * 2048 ) / LR11XX_GNSS_SCALING_LATITUDE ); + const int16_t longitude = ( ( current_assistance_position->longitude * 2048 ) / LR11XX_GNSS_SCALING_LONGITUDE ); + + /* Prepare buffer for request */ + /* | TAG (8b) | 0x00 | LatLSB (8b) | LonLSB (4b) LatMSB (4b) | LonMSB (8b) | */ + aid_pos_check_buffer[0] = ( scan->detected_svs > 0 ) ? 0x01 : 0x00; /* TAG */ + aid_pos_check_buffer[1] = 0x00; /* Extension marker */ + + /* Current aiding position */ + aid_pos_check_buffer[2] = latitude & 0xFF; + aid_pos_check_buffer[3] = ( ( longitude & 0x00F ) << 4 ) | ( ( latitude & 0xF00 ) >> 8 ); + aid_pos_check_buffer[4] = ( longitude & 0xFF0 ) >> 4; + + /* Append NAV if available */ + if( scan->detected_svs > 0 ) + { + /* The number of SV max has been limited for autonomous scan in order to keep the complete buffer below 51 bytes + */ + memcpy( &aid_pos_check_buffer[5], &( scan->results_buffer[GNSS_SCAN_METADATA_SIZE] ), scan->results_size ); + } + + /* Send uplink */ + if( gnss_mw_send_frame( aid_pos_check_buffer, 5 + scan->results_size, lorawan_port ) == true ) + { + success = true; + } + else + { + MW_DBG_TRACE_ERROR( "Failed to send aiding position request uplink frame\n" ); + } + + return success; +} + +static void gnss_mw_tx_done_callback( void ) +{ + MW_DBG_TRACE_MSG( "---- internal TX DONE ----\n" ); + + /* static variables because there is no copy done by LBM for extended send API */ + static uint8_t* buffer_to_send; + static uint8_t buffer_to_send_size; + + /* Get the next scan to be sent from the scan group queue */ + if( gnss_scan_group_queue_pop( &gnss_scan_group_queue, &buffer_to_send, &buffer_to_send_size ) == true ) + { + /* Send uplink */ + if( gnss_mw_send_frame( buffer_to_send, buffer_to_send_size, lorawan_port ) == false ) + { + MW_DBG_TRACE_ERROR( "Failed to send uplink frame\n" ); + /* TODO: send error event ? */ + } + else + { + stat_nb_scans_sent_within_current_scan_group += 1; + } + } + else + { + /* Send an event to application to notify for completion */ + gnss_mw_send_event( GNSS_MW_EVENT_TERMINATED ); + } +} + +static bool gnss_mw_send_frame( const uint8_t* tx_frame_buffer, const uint8_t tx_frame_buffer_size, uint8_t port ) +{ + smtc_modem_return_code_t modem_response_code = SMTC_MODEM_RC_OK; + uint8_t tx_max_payload; + int32_t duty_cycle; + + /* Sanity check: + We expect the application parameters to be properly set to avoid: + - exceed duty cycle + - exceed maximum payload + The below checks are only for developer information */ + + /* Inform if duty cycle is not available */ + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_get_duty_cycle_status( &duty_cycle ) ); + if( duty_cycle < 0 ) + { + MW_DBG_TRACE_ERROR( "Duty Cycle: available for next uplink in %d milliseconds\n", duty_cycle ); + } + + /* Get the next tx payload size */ + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_get_next_tx_max_payload( modem_stack_id, &tx_max_payload ) ); + if( tx_frame_buffer_size > tx_max_payload ) + { + MW_DBG_TRACE_ERROR( "payload size: exceed max payload allowed for next uplink (%d > %d bytes)\n", + tx_frame_buffer_size, tx_max_payload ); + } + + /* Send uplink */ + modem_response_code = + smtc_modem_request_extended_uplink( modem_stack_id, port, false, tx_frame_buffer, tx_frame_buffer_size, + SMTC_MODEM_EXTENDED_UPLINK_ID_GNSS, &gnss_mw_tx_done_callback ); + if( modem_response_code == SMTC_MODEM_RC_OK ) + { + MW_DBG_TRACE_INFO( "Request uplink:\n" ); + MW_DBG_TRACE_ARRAY( "Payload", tx_frame_buffer, tx_frame_buffer_size ); + return true; + } + else + { + MW_DBG_TRACE_ERROR( "Request uplink failed with modem_response_code : %d \n", modem_response_code ); + return false; + } +} + +static void gnss_mw_send_event( gnss_mw_event_type_t event_type ) +{ + /* The scan sequence ends when an event is sent to the application, except if SCAN_DONE */ + if( event_type != GNSS_MW_EVENT_SCAN_DONE ) + { + task_running = false; + } + + /* Increment the token on SCAN_DONE if the scan group is valid (and no aggregate) */ + if( event_type == GNSS_MW_EVENT_SCAN_DONE ) + { + if( ( scan_aggregate == false ) && ( gnss_scan_group_queue_is_valid( &gnss_scan_group_queue ) ) ) + { + gnss_scan_group_queue_increment_token( &gnss_scan_group_queue ); + } + } + + /* Send the event to the application */ + pending_events = pending_events | ( 1 << event_type ); + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_increment_event_middleware( SMTC_MODEM_EVENT_MIDDLEWARE_1, pending_events ) ); +} + +static void gnss_mw_set_solver_aiding_position( const uint8_t* payload, uint8_t size ) +{ + /* Store the solver assistance position to be written to the LR11xx on the next scan */ + memcpy( solver_aiding_position_update, payload, SOLVER_AIDING_POSITION_SIZE ); + solver_aiding_position_update_received = true; + + /* We can switch to assisted scan for the next scan */ + current_scan_type = GNSS_MW_SCAN_TYPE_ASSISTED; +} + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_middleware.h b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_middleware.h new file mode 100644 index 000000000..4a5593a7b --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_middleware.h @@ -0,0 +1,334 @@ +/** + * @file gnss_middleware.h + * + * @brief GNSS geolocation middleware implementing scan & send sequence. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 __GNSS_MIDDLEWARE_H__ +#define __GNSS_MIDDLEWARE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include + +#include "ralf.h" + +#include "mw_common.h" + +#include "gnss_helpers_defs.h" +#include "gnss_queue_defs.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/** + * @brief GNSS scanning modes. Configures the scanning sequence according to the use case. + */ +typedef enum +{ + GNSS_MW_MODE_STATIC, //!< Scanning mode for non moving objects + GNSS_MW_MODE_MOBILE, //!< Scanning mode for moving objects + __GNSS_MW_MODE__SIZE //!< Number of modes available +} gnss_mw_mode_t; + +/** + * @brief GNSS constellations to be scanned (GPS, BEIDOU or both) + */ +typedef enum +{ + GNSS_MW_CONSTELLATION_GPS, //!< Use GPS only constellation + GNSS_MW_CONSTELLATION_BEIDOU, //!< Use BEIDOU only constellation + GNSS_MW_CONSTELLATION_GPS_BEIDOU //!< Use both GPS and BEIDOU constellations +} gnss_mw_constellation_t; + +/** + * @brief GNSS event status sent from the middleware to the application. + */ +typedef enum +{ + GNSS_MW_EVENT_SCAN_CANCELLED = 0, //!< Scan operation has been cancelled + GNSS_MW_EVENT_SCAN_DONE = 1, //!< Scan operation has been completed + GNSS_MW_EVENT_TERMINATED = 2, //!< Scan & send sequence has been completed + GNSS_MW_EVENT_ERROR_NO_TIME = 3, //!< Scan operation has failed because no time is available + GNSS_MW_EVENT_ERROR_ALMANAC_UPDATE = 4, //!< Scan operation has failed because the almanac needs to be updated + GNSS_MW_EVENT_ERROR_NO_AIDING_POSITION = + 5, //!< Scan operation has failed because the assistance position is not configured + GNSS_MW_EVENT_ERROR_UNKNOWN = 6, //!< Scan operation has failed for an unknown reason + /* 8 event types max */ +} gnss_mw_event_type_t; + +/** + * @brief The configuration context in which a scan has been performed. + */ +typedef struct +{ + gnss_mw_mode_t mode; //!< Scan mode that has been used (STATIC, MOBILE...) + bool assisted; //!< Was it an an autonomous scan or an assisted scan ? + float aiding_position_latitude; //!< Aiding position latitude used for the assisted scan + float aiding_position_longitude; //!< Aiding position longitude used for the assisted scan + uint32_t almanac_crc; //!< Almanac CRC when scan was performed + uint32_t gps_time; //!< GPS time when the scan was launched + bool almanac_update_checked; //!< Has the almanac update status been already checked for this scan group ? + bool almanac_update_required; //!< Does the almanac requires to be updated ? +} gnss_mw_scan_context_t; + +/** + * @brief Information about detected satellite (Space Vehicle). + */ +typedef struct +{ + uint8_t sv_id; //!< ID of the space vehicle detected while scanning + int8_t cnr; //!< Carrier to Noise Ratio at which the Space Vehicle has been detected +} gnss_mw_sv_info_t; + +/** + * @brief Description of a scan result. + */ +typedef struct +{ + uint32_t timestamp; //!< Scan timestamp + uint8_t* nav; //!< NAV message result for this scan + uint8_t nav_size; //!< NAV message size + bool nav_valid; //!< is the NAV message valid (can be used by the solver for a single frame solve) + uint8_t nb_svs; //!< Number of Space Vehicles detected by this scan + gnss_mw_sv_info_t info_svs[GNSS_NB_SVS_MAX]; //!< Information about the SVs detected +} gnss_mw_event_data_scan_desc_t; + +/** + * @brief The data that can be retrieved when a GNSS_MW_EVENT_SCAN_DONE event occurs. + */ +typedef struct +{ + bool is_valid; //!< Is the scan group valid ? (enough SV detected...) + uint8_t token; //!< Scan group identifier + uint8_t nb_scans_valid; //!< Number of valid scans in that scan group + gnss_mw_event_data_scan_desc_t scans[GNSS_SCAN_GROUP_SIZE_MAX]; //!< Descriptions of all scan results + uint32_t power_consumption_uah; //!< Power consumption induced by this scan group + gnss_mw_scan_context_t context; //!< Configuration context used for this scan +} gnss_mw_event_data_scan_done_t; + +/** + * @brief The data that can be retrieved when a GNSS_MW_EVENT_TERMINATED event occurs. + */ +typedef struct +{ + uint8_t nb_scans_sent; //!< Number of scans which have been sent over the air + bool aiding_position_check_sent; //!< Indicates if an aiding position check uplink has been sent + bool indoor_detected; //!< Indicates if an indoor detection occurred (in case aiding position check was enabled) +} gnss_mw_event_data_terminated_t; + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/** + * @brief Get version of the GNSS middleware + * + * @param [out] version Middleware version + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED version is not initialized + */ +mw_return_code_t gnss_mw_get_version( mw_version_t* version ); + +/** + * @brief Initialize the GNSS middleware + * + * @param [in] modem_radio Interface to access the radio + * @param [in] stack_id Modem stack ID + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED The modem/radio interface is not initialized + */ +mw_return_code_t gnss_mw_init( ralf_t* modem_radio, uint8_t stack_id ); + +/** + * @brief Set the aiding position (assistance position) to be used for assisted scan + * + * @param [in] latitude Latitude of the aiding position + * @param [in] longitude Longitude of the aiding position + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED The modem/radio interface is not initialized + */ +mw_return_code_t gnss_mw_set_user_aiding_position( float latitude, float longitude ); + +/** + * @brief Program a GNSS scan & send sequence to start in a given delay + * + * @param [in] mode Scanning mode to be used (STATIC, MOBILE...) + * @param [in] start_delay Delay in seconds before starting the scan sequence + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_BUSY A scan sequence is already on-going + * @retval MW_RC_FAILED An error occurred while starting the scanning sequence + */ +mw_return_code_t gnss_mw_scan_start( gnss_mw_mode_t mode, uint32_t start_delay ); + +/** + * @brief Cancel the current programmed GNSS scan & send sequence (if not already started) + * After calling this function, in case of success, the user should wait for the GNSS_MW_EVENT_SCAN_CANCELLED event + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_BUSY The scan sequence has already started (cannot be cancelled) + */ +mw_return_code_t gnss_mw_scan_cancel( void ); + +/** + * @brief Check if there is a particular event in the "pending events" bitfield + * + * @param [in] pending_events Pending events bitfield given when an event occurs + * @param [in] event Event to search in the pending events bitfield + * + * @return a boolean to indicate if the given event is set in the pending events bitfield + */ +bool gnss_mw_has_event( uint8_t pending_events, gnss_mw_event_type_t event ); + +/** + * @brief Retrieve the data associated with the GNSS_MW_EVENT_SCAN_DONE event + * + * @param [out] data Description of the scan group results + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED If the given pointer is NULL or if there is no SCAN_DONE event pending + */ +mw_return_code_t gnss_mw_get_event_data_scan_done( gnss_mw_event_data_scan_done_t* data ); + +/** + * @brief Retrieve the data associated with the GNSS_MW_EVENT_TERMINATED event + * + * @param [out] data Status of the end of the scan & send sequence + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED If the given pointer is NULL or if there is no TERMINATED event pending + */ +mw_return_code_t gnss_mw_get_event_data_terminated( gnss_mw_event_data_terminated_t* data ); + +/** + * @brief Indicates to the middleware that all pending events have been handled and can be cleared + */ +void gnss_mw_clear_pending_events( void ); + +/** + * @brief Set the GNSS constellations to be used for scanning for all subsequent scans (optional) + * + * @param [in] constellations Constellation(s) to be used for the scans + * + * By default it is configured for using both GPS and BEIDOU constellations + */ +void gnss_mw_set_constellations( gnss_mw_constellation_t constellations ); + +/** + * @brief Set the LoRaWAN port on which to send the scan results uplinks + * + * @param [in] port LoRaWAN port + * + * By default it is set to 192 (GNSS_DEFAULT_UPLINK_PORT) + */ +void gnss_mw_set_port( uint8_t port ); + +/** + * @brief Indicates if the current scan group identifier (token) has to be kept unchanged for all subsequent scan & send + * sequences. It can be used for non-mobile objects to aggregate multiple scan results and use more frames + * in a multiframe solve to get more accurate position overtime. + * + * @param [in] aggregate Boolean to aggregate or not + * + * By default it is set to false, meaning that the token will change for each call to gnss_mw_scan_start() if the result + * was valid + */ +void gnss_mw_scan_aggregate( bool aggregate ); + +/** + * @brief Bypass the "send" part of the "scan & send" sequence. Basically it is a "scan only" mode. + * It can be used if the application wants to control how the scan results are sent over the air. + * + * @param [in] no_send Boolean to bypass send or not + * + * By default it is set to false + */ +void gnss_mw_send_bypass( bool no_send ); + +/** + * @brief Print the results of the GNSS_MW_EVENT_SCAN_DONE event + * + * @param [in] data Scan results to be printed on the console + */ +void gnss_mw_display_results( const gnss_mw_event_data_scan_done_t* data ); + +/** + * @brief Parse downlink message, and handle it if it targets the GNSS middleware. + * + * @param [in] port Port on which the downlink has been received + * @param [in] payload Payload of the downlink has received + * @param [in] size Size of the downlink payload + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED If the given pointer is NULL or if the size is not compatible with supported messages + */ +mw_return_code_t gnss_mw_handle_downlink( uint8_t port, const uint8_t* payload, uint8_t size ); + +#ifdef __cplusplus +} +#endif + +#endif // __GNSS_MIDDLEWARE_H__ + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue.c b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue.c new file mode 100644 index 000000000..baf5cd59f --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue.c @@ -0,0 +1,267 @@ +/** + * @file gnss_queue.c + * + * @brief Implementation of the GNSS scan group queue. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include + +#include "mw_dbg_trace.h" +#include "gnss_queue.h" +#include "smtc_modem_hal.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +#define GNSS_QUEUE_FEATURE_OFF 0 +#define GNSS_QUEUE_FEATURE_ON !GNSS_QUEUE_FEATURE_OFF + +#ifndef GNSS_QUEUE_DBG_TRACE +#define GNSS_QUEUE_DBG_TRACE GNSS_QUEUE_FEATURE_OFF /* Enable/Disable traces here */ +#endif + +#if( GNSS_QUEUE_DBG_TRACE == GNSS_QUEUE_FEATURE_ON ) +#define GNSS_QUEUE_TRACE_MSG( msg ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( msg ); \ + } while( 0 ) + +#define GNSS_QUEUE_TRACE_PRINTF( ... ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( __VA_ARGS__ ); \ + } while( 0 ) + +#define GNSS_QUEUE_PRINT( queue ) \ + { \ + GNSS_QUEUE_TRACE_PRINTF( "****************************************\n" ); \ + GNSS_QUEUE_TRACE_PRINTF( "token: 0x%02X\n", queue->token ); \ + GNSS_QUEUE_TRACE_PRINTF( "group_size: %d\n", queue->scan_group_size ); \ + GNSS_QUEUE_TRACE_PRINTF( "scan_valid: %d\n", queue->nb_scans_valid ); \ + GNSS_QUEUE_TRACE_PRINTF( "scan_total: %d\n", queue->nb_scans_total ); \ + GNSS_QUEUE_TRACE_PRINTF( "next_send_index: %d\n", queue->next_send_index ); \ + GNSS_QUEUE_TRACE_PRINTF( "power_cons: %d uah\n", queue->power_consumption_uah ); \ + for( uint8_t i = 0; i < queue->nb_scans_valid; i++ ) \ + { \ + GNSS_QUEUE_TRACE_PRINTF( "scans[%d]: %02d %02d %02d - ", i, queue->scans[i].detected_svs, \ + queue->scans[i].results_size, queue->scans[i].nav_valid ); \ + for( uint8_t j = 0; j < ( GNSS_SCAN_METADATA_SIZE + queue->scans[i].results_size ); j++ ) \ + { \ + GNSS_QUEUE_TRACE_PRINTF( "%02X ", queue->scans[i].results_buffer[j] ); \ + } \ + GNSS_QUEUE_TRACE_PRINTF( "\n" ); \ + } \ + GNSS_QUEUE_TRACE_PRINTF( "****************************************\n" ); \ + } + +#else +#define GNSS_QUEUE_TRACE_MSG( msg ) +#define GNSS_QUEUE_TRACE_PRINTF( ... ) +#define GNSS_QUEUE_PRINT( queue ) +#endif + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +void gnss_scan_group_queue_reset_token( gnss_scan_group_queue_t* queue ) +{ + if( queue != NULL ) + { + queue->token = + ( uint8_t ) smtc_modem_hal_get_random_nb_in_range( 2, 0x1F ); /* 5-bits token with 0x00 and 0x01 excluded */ + } +} + +void gnss_scan_group_queue_increment_token( gnss_scan_group_queue_t* queue ) +{ + if( queue != NULL ) + { + queue->token = ( queue->token + 1 ) % 0x20; /* roll-over on 5-bits */ + + /* Exclude 0x00 and 0x01 values */ + if( queue->token == 0 ) + { + queue->token = 2; + } + } +} + +bool gnss_scan_group_queue_new( gnss_scan_group_queue_t* queue, uint8_t scan_group_size, uint8_t scan_group_stop ) +{ + if( ( queue != NULL ) && ( scan_group_size <= GNSS_SCAN_GROUP_SIZE_MAX ) ) + { + /* queue params */ + queue->scan_group_size = scan_group_size; + queue->scan_group_stop = scan_group_stop; + + /* reset queue current status */ + queue->nb_scans_valid = 0; + queue->nb_scans_total = 0; + queue->next_send_index = 0; + queue->power_consumption_uah = 0; + queue->stop = false; + + /* reset queue buffers */ + memset( queue->scans, 0, sizeof queue->scans ); + + GNSS_QUEUE_TRACE_PRINTF( "%s:\n", __FUNCTION__ ); + GNSS_QUEUE_PRINT( queue ); + + return true; + } + + return false; +} + +bool gnss_scan_group_queue_is_full( gnss_scan_group_queue_t* queue ) +{ + if( queue != NULL ) + { + /* the number of scan done reached group size */ + return ( ( queue->nb_scans_total == queue->scan_group_size ) || ( queue->stop == true ) ); + } + + return false; +} + +bool gnss_scan_group_queue_is_valid( gnss_scan_group_queue_t* queue ) +{ + if( queue != NULL ) + { + if( ( ( queue->nb_scans_valid == 1 ) && ( queue->scans[0].nav_valid == true ) ) || + ( queue->nb_scans_valid > 1 ) ) + { + return true; + } + } + + return false; +} + +void gnss_scan_group_queue_push( gnss_scan_group_queue_t* queue, gnss_scan_t* scan ) +{ + if( ( queue != NULL ) && ( scan != NULL ) ) + { + /* Add the scan to the queue if valid */ + if( scan->detected_svs > 0 ) + { + memcpy( &( queue->scans[queue->nb_scans_valid] ), scan, sizeof( gnss_scan_t ) ); + queue->nb_scans_valid += 1; + } + + /* If a stop limit has been set and the current scan has enough detected SVs, the current scan group can be + * stopped and sent */ + if( ( queue->scan_group_stop != 0 ) && ( scan->detected_svs >= queue->scan_group_stop ) ) + { + queue->stop = true; + } + + queue->nb_scans_total += 1; + + GNSS_QUEUE_TRACE_PRINTF( "%s:\n", __FUNCTION__ ); + GNSS_QUEUE_PRINT( queue ); + } +} + +bool gnss_scan_group_queue_pop( gnss_scan_group_queue_t* queue, uint8_t** buffer, uint8_t* buffer_size ) +{ + if( ( queue != NULL ) && gnss_scan_group_queue_is_valid( queue ) && + ( queue->next_send_index < queue->nb_scans_valid ) ) + { + const uint8_t index = queue->next_send_index; + const uint8_t is_last = ( queue->next_send_index == ( queue->nb_scans_valid - 1 ) ); + + /* Set scan group metadata + | last NAV (1bit) | RFU (2bits) | token (5bits) | + - token: scan group identifier + - last NAV: indicates if this is the last NAV message of a scan group + */ + queue->scans[index].results_buffer[0] = 0; + queue->scans[index].results_buffer[0] = ( is_last << 7 ) | ( queue->token & 0x1F ); + + /* Update queue info */ + queue->next_send_index += 1; + + /* Return a pointer to the buffer to be sent, and its size */ + *buffer = queue->scans[index].results_buffer; + *buffer_size = GNSS_SCAN_METADATA_SIZE + queue->scans[index].results_size; + + GNSS_QUEUE_TRACE_PRINTF( "%s:\n", __FUNCTION__ ); + GNSS_QUEUE_PRINT( queue ); + + return true; + } + + /* scan results to be sent */ + *buffer = NULL; + *buffer_size = 0; + GNSS_QUEUE_TRACE_PRINTF( "%s: no scan result left in queue\n", __FUNCTION__ ); + + return false; +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue.h b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue.h new file mode 100644 index 000000000..78d82f4c0 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue.h @@ -0,0 +1,202 @@ +/** + * @file gnss_queue.h + * + * @brief Implementation of the GNSS scan group queue. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 GNSS_QUEUE_H +#define GNSS_QUEUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include + +#include "gnss_helpers.h" +#include "gnss_helpers_defs.h" +#include "gnss_queue_defs.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/** + * @brief Number of bytes of the metadata field concerning a GNSS scan + */ +#define GNSS_SCAN_METADATA_SIZE 1 + +/** + * @brief Maximum size for a MODE3 scan NAV message (10 sv, doppler, no bit change) + */ +#define GNSS_RESULT_SIZE_MAX_MODE3 ( 49 + 1 ) /* 1 byte for destination byte */ + +/** + * @brief The minimum number of SV necessary for single NAV position solving + */ +#define GNSS_SCAN_SINGLE_NAV_MIN_SV 6 + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/** + * @brief Description of a scan node in the scan group + */ +typedef struct +{ + uint32_t timestamp; //!< GPS time at which the scan has completed + uint8_t detected_svs; //!< Number of Space Vehicles detected during the scan + uint8_t results_size; //!< Size of the result (NAV) + uint8_t results_buffer[GNSS_SCAN_METADATA_SIZE + GNSS_RESULT_SIZE_MAX_MODE3]; + lr11xx_gnss_detected_satellite_t info_svs[GNSS_NB_SVS_MAX]; //!< Information about each SV detected (ID, CNR...) + bool nav_valid; //!< Indicates if a single NAV can be used by the solver to get a position */ +} gnss_scan_t; + +/** + * @brief Description of a scan group + */ +typedef struct +{ + uint8_t token; //!< Scan group identifier (7-bits roll-over) + gnss_scan_t scans[GNSS_SCAN_GROUP_SIZE_MAX]; //!< Description of all scans of the group + uint8_t scan_group_size; //!< Size of the scan group + uint8_t scan_group_stop; //!< The number of SVs necessary on a single NAV to close the scan group (0 means no stop) + uint8_t nb_scans_valid; //!< Number of valid scans in the group + uint8_t nb_scans_total; //!< Total number of scans completed (valid or not) + uint8_t next_send_index; //!< Scan index to be sent next + uint32_t power_consumption_uah; //!< Power consumption of the complete scan group + bool stop; //!< Current scan group can be stopped and sent +} gnss_scan_group_queue_t; + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/*! + * @brief Reset the scan group token + * + * @param[in] queue Queue for which the token needs to be reset + */ +void gnss_scan_group_queue_reset_token( gnss_scan_group_queue_t* queue ); + +/*! + * @brief Increment the scan group token value (7-bits roll-over) + * + * @param[in] queue Queue for which the token needs to be incremented + */ +void gnss_scan_group_queue_increment_token( gnss_scan_group_queue_t* queue ); + +/*! + * @brief Reset the scan group queue, except token value + * + * @param[in] queue Queue to be initialized and configured + * @param[in] scan_group_size Size of the scan group + * @param[in] scan_group_stop Number of SVs to reach to stop current scan group (0: no stop) + * + * @return a boolean set to true if the queue could be configured, false otherwise + */ +bool gnss_scan_group_queue_new( gnss_scan_group_queue_t* queue, uint8_t scan_group_size, uint8_t scan_group_stop ); + +/*! + * @brief Check if a queue is full + * + * A queue is considered full if one of the following confition is met: + * - The number of valid GNSS scan result has reached GNSS_SCAN_GROUP_SIZE; or + * - The scan group has been aborted due to invalid scan + * + * @param[in] queue Queue to check + * + * @return a boolean set to true if the queue is full, false otherwise + */ +bool gnss_scan_group_queue_is_full( gnss_scan_group_queue_t* queue ); + +/*! + * @brief Check if a scan group is valid + * + * Depending on the current scan group mode, a scan group is considered valid if: + * - DEFAULT mode: the number of valid GNSS scan result is equal to the group size; or + * - SENSITIVITY mode: there is at least 1 valid scan. If there only 1 valid scan, it needs to be a valid NAV message + * (that the solver can use to get a position) + * + * valid scan group: as defined above + * valid scan : a successful scan which detected at least 1 SV + * valid NAV : a NAV message that can be used by the solver to get a position with this single NAV + * + * @param[in] queue Queue to check + * + * @return a boolean set to true if the queue is valid, false otherwise + */ +bool gnss_scan_group_queue_is_valid( gnss_scan_group_queue_t* queue ); + +/*! + * @brief Add a new scan result to a queue + * After a call to this function, the user must check if the queue is full with gnss_scan_group_queue_is_full() + * to avoid overflow. + * + * @param[in] queue Queue to update + * @param[in] scan Scan result to push to the queue + */ +void gnss_scan_group_queue_push( gnss_scan_group_queue_t* queue, gnss_scan_t* scan ); + +/*! + * @brief Prepare the scan result payload to be sent over the air, with associated metadata. + * The format of the payload is: | last NAV (1bit) | RFU (2bits) | token (5bits) | NAV | + * + * @param[in] queue Queue from which the result is popped + * @param[out] buffer Pointer to the prepared buffer ready to be sent over the air + * @param[out] buffer_size Size of the buffer to be sent + * + * @return a boolean set to true is a scan result is ready to be sent, false if there is no result to be sent + */ +bool gnss_scan_group_queue_pop( gnss_scan_group_queue_t* queue, uint8_t** buffer, uint8_t* buffer_size ); + +#ifdef __cplusplus +} +#endif + +#endif // GNSS_QUEUE_H + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue_defs.h b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue_defs.h new file mode 100644 index 000000000..995b9121b --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_queue_defs.h @@ -0,0 +1,73 @@ +/** + * @file gnss_queue_defs.h + * + * @brief Types and constants definitions of GNSS queue. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 __GNSS_QUEUE_DEFS_H__ +#define __GNSS_QUEUE_DEFS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/** + * @brief Maximum number of GNSS scan in a scan group [1..32] + */ +#define GNSS_SCAN_GROUP_SIZE_MAX ( 4 ) + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +#ifdef __cplusplus +} +#endif + +#endif // __GNSS_QUEUE_DEFS_H__ + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_version.h b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_version.h new file mode 100644 index 000000000..61021adca --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/gnss_version.h @@ -0,0 +1,73 @@ +/*! + * \file gnss_version.h + * + * \brief Defines the GNSS middleware version + * + * Revised BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation 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 AND CONTRIBUTORS "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 SEMTECH CORPORATION 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 GNSS_MW_VERSION_H__ +#define GNSS_MW_VERSION_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ +#define GNSS_MW_VERSION_MAJOR 2 +#define GNSS_MW_VERSION_MINOR 0 +#define GNSS_MW_VERSION_PATCH 0 + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +#ifdef __cplusplus +} +#endif + +#endif // GNSS_MW_VERSION_H__ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/lr11xx_driver_extension.c b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/lr11xx_driver_extension.c new file mode 100644 index 000000000..baa768975 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/lr11xx_driver_extension.c @@ -0,0 +1,179 @@ +/*! + * @file lr11xx_driver_extension.c + * + * @brief driver extension implementation for LR11XX + * + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "lr11xx_hal.h" +#include "lr11xx_driver_extension.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +#define LR11XX_GNSS_SET_FREQ_SEARCH_SPACE_CMD_LENGTH ( 2 + 1 ) +#define LR11XX_GNSS_READ_FREQ_SEARCH_SPACE_CMD_LENGTH ( 2 ) +#define LR11XX_GNSS_GET_SV_VISIBLE_CMD_LENGTH ( 2 + 9 ) +#define LR11XX_GNSS_GET_SV_VISIBLE_DOPPLER_CMD_LENGTH ( 2 ) + +#define LR11XX_GNSS_SCALING_LATITUDE 90 +#define LR11XX_GNSS_SCALING_LONGITUDE 180 + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/*! + * @brief Operating codes for GNSS-related operations + */ +enum +{ + LR11XX_GNSS_SET_FREQ_SEARCH_SPACE_OC = 0x0404, //!< Set the frequency search space + LR11XX_GNSS_READ_FREQ_SEARCH_SPACE_OC = 0x0405, //!< Read the frequency search space + LR11XX_GNSS_GET_SV_VISIBLE_OC = 0x041F, //!< Get the number of visible SV from a date and a position + LR11XX_GNSS_GET_SV_VISIBLE_DOPPLER_OC = 0x0420, //!< Get visible SV ID and corresponding doppler value +}; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +lr11xx_status_t lr11xx_gnss_set_freq_search_space( const void* radio, + const lr11xx_gnss_freq_search_space_t freq_search_space ) +{ + const uint8_t cbuffer[LR11XX_GNSS_SET_FREQ_SEARCH_SPACE_CMD_LENGTH] = { + ( uint8_t )( LR11XX_GNSS_SET_FREQ_SEARCH_SPACE_OC >> 8 ), + ( uint8_t )( LR11XX_GNSS_SET_FREQ_SEARCH_SPACE_OC >> 0 ), + ( uint8_t ) freq_search_space, + }; + + return ( lr11xx_status_t ) lr11xx_hal_write( radio, cbuffer, LR11XX_GNSS_SET_FREQ_SEARCH_SPACE_CMD_LENGTH, 0, 0 ); +} + +lr11xx_status_t lr11xx_gnss_read_freq_search_space( const void* radio, + lr11xx_gnss_freq_search_space_t* freq_search_space ) +{ + const uint8_t cbuffer[LR11XX_GNSS_READ_FREQ_SEARCH_SPACE_CMD_LENGTH] = { + ( uint8_t )( LR11XX_GNSS_READ_FREQ_SEARCH_SPACE_OC >> 8 ), + ( uint8_t )( LR11XX_GNSS_READ_FREQ_SEARCH_SPACE_OC >> 0 ), + }; + + return ( lr11xx_status_t ) lr11xx_hal_read( radio, cbuffer, LR11XX_GNSS_READ_FREQ_SEARCH_SPACE_CMD_LENGTH, + ( uint8_t* ) freq_search_space, sizeof( uint8_t ) ); +} + +lr11xx_status_t lr11xx_gnss_get_nb_visible_satellites( + const void* context, const lr11xx_gnss_date_t date, + const lr11xx_gnss_solver_assistance_position_t* assistance_position, + const lr11xx_gnss_constellation_t constellation, uint8_t* nb_visible_sv ) +{ + const int16_t latitude = ( ( assistance_position->latitude * 2048 ) / LR11XX_GNSS_SCALING_LATITUDE ); + const int16_t longitude = ( ( assistance_position->longitude * 2048 ) / LR11XX_GNSS_SCALING_LONGITUDE ); + const uint8_t cbuffer[LR11XX_GNSS_GET_SV_VISIBLE_CMD_LENGTH] = { + ( uint8_t )( LR11XX_GNSS_GET_SV_VISIBLE_OC >> 8 ), + ( uint8_t )( LR11XX_GNSS_GET_SV_VISIBLE_OC >> 0 ), + ( uint8_t )( date >> 24 ), + ( uint8_t )( date >> 16 ), + ( uint8_t )( date >> 8 ), + ( uint8_t )( date >> 0 ), + ( uint8_t )( latitude >> 8 ), + ( uint8_t )( latitude >> 0 ), + ( uint8_t )( longitude >> 8 ), + ( uint8_t )( longitude >> 0 ), + ( uint8_t )( constellation - 1 ), + }; + + return ( lr11xx_status_t ) lr11xx_hal_read( context, cbuffer, LR11XX_GNSS_GET_SV_VISIBLE_CMD_LENGTH, nb_visible_sv, + 1 ); +} + +lr11xx_status_t lr11xx_gnss_get_visible_satellites( const void* context, const uint8_t nb_visible_satellites, + lr11xx_gnss_visible_satellite_t* visible_satellite_id_doppler ) +{ + uint8_t result_buffer[12 * 5] = { 0 }; + const uint16_t read_size = nb_visible_satellites * 5; + + const uint8_t cbuffer[LR11XX_GNSS_GET_SV_VISIBLE_DOPPLER_CMD_LENGTH] = { + ( uint8_t )( LR11XX_GNSS_GET_SV_VISIBLE_DOPPLER_OC >> 8 ), + ( uint8_t )( LR11XX_GNSS_GET_SV_VISIBLE_DOPPLER_OC >> 0 ), + }; + + const lr11xx_hal_status_t hal_status = + lr11xx_hal_read( context, cbuffer, LR11XX_GNSS_GET_SV_VISIBLE_DOPPLER_CMD_LENGTH, result_buffer, read_size ); + + if( hal_status == LR11XX_HAL_STATUS_OK ) + { + for( uint8_t index_satellite = 0; index_satellite < nb_visible_satellites; index_satellite++ ) + { + const uint16_t local_result_buffer_index = index_satellite * 5; + lr11xx_gnss_visible_satellite_t* local_satellite_result = &visible_satellite_id_doppler[index_satellite]; + + local_satellite_result->satellite_id = result_buffer[local_result_buffer_index]; + local_satellite_result->doppler = ( int16_t )( ( result_buffer[local_result_buffer_index + 1] << 8 ) + + ( result_buffer[local_result_buffer_index + 2] << 0 ) ); + local_satellite_result->doppler_error = + ( int16_t )( ( result_buffer[local_result_buffer_index + 3] << 8 ) + + ( result_buffer[local_result_buffer_index + 4] << 0 ) ); + } + } + + return ( lr11xx_status_t ) hal_status; +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/lr11xx_driver_extension.h b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/lr11xx_driver_extension.h new file mode 100644 index 000000000..c81b114eb --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/gnss/src/lr11xx_driver_extension.h @@ -0,0 +1,140 @@ +/*! + * @file lr11xx_driver_extension.h + * + * @brief driver extension for LR11XX + * + * The Clear BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 LR11XX_DRIVER_EXTENSION_H +#define LR11XX_DRIVER_EXTENSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "lr11xx_types.h" +#include "lr11xx_gnss_types.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/*! + * @brief Structure for information about visible SV + */ +typedef struct lr11xx_gnss_visible_satellite_s +{ + lr11xx_gnss_satellite_id_t satellite_id; //!< SV ID + int16_t doppler; //!< SV doppler in Hz + int16_t doppler_error; //!< SV doppler error - step of 125Hz +} lr11xx_gnss_visible_satellite_t; + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/*! + * @brief Function to read the frequency search space around the Doppler frequency + * + * @param [in] radio Radio abstraction + * @param [out] freq_search_space Frequency search space configuration read from the chip + * + * @returns Operation status + */ +lr11xx_status_t lr11xx_gnss_read_freq_search_space( const void* radio, + lr11xx_gnss_freq_search_space_t* freq_search_space ); + +/*! + * @brief Function to set the frequency search space around the Doppler frequency + * + * @param [in] radio Radio abstraction + * @param [in] freq_search_space Frequency search space configuration to be applied + * + * @returns Operation status + */ +lr11xx_status_t lr11xx_gnss_set_freq_search_space( const void* radio, + const lr11xx_gnss_freq_search_space_t freq_search_space ); + +/** + * @brief Return the theoretical number of visible satellites based on the given parameters. + * + * @param [in] context Chip implementation context + * @param [in] date The actual date of scan. Its format is the number of seconds elapsed since January the 6th 1980 + * 00:00:00 with leap seconds included. + * @param [in] assistance_position, latitude 12 bits and longitude 12 bits + * @param [in] constellation Bit mask of the constellations to use. See @ref lr11xx_gnss_constellation_t for + * the possible values + * @param [out] nb_visible_sv thoeretical number of visible satellites + * + * @returns Operation status + */ +lr11xx_status_t lr11xx_gnss_get_nb_visible_satellites( + const void* context, const lr11xx_gnss_date_t date, + const lr11xx_gnss_solver_assistance_position_t* assistance_position, + const lr11xx_gnss_constellation_t constellation, uint8_t* nb_visible_sv ); + +/** + * @brief Return the doppler information of theoretical visible satellites, this function shall be called after + * lr11xx_gnss_get_nb_visible_satellites function. + * + * @param [in] context Chip implementation context + * @param [in] nb_visible_satellites number of visible satellites returned by lr11xx_gnss_get_nb_visible_satellites + * function, + * @param [out] visible_satellite_id_doppler Doppler information of each satellite. + * + * @returns Operation status + */ +lr11xx_status_t lr11xx_gnss_get_visible_satellites( const void* context, const uint8_t nb_visible_satellites, + lr11xx_gnss_visible_satellite_t* visible_satellite_id_doppler ); + +#ifdef __cplusplus +} +#endif + +#endif // LR11XX_DRIVER_EXTENSION_H + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/readme.md b/lr1110/lr1110/seeed/geolocation_middleware/readme.md new file mode 100644 index 000000000..6bbbff563 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/readme.md @@ -0,0 +1,13 @@ +LoRa Basics Modem - Geolocation middlewares +=========================================== + +The documentation below gives an overview about the goals and design choices for the geolocation middleware implemented in this project. + +[Geolocation middleware documentation](<./doc/geolocationMiddleware.rst>) + +Application server for Geolocation +================================== + +The documentation below gives some insights about what an Application Server should provide and implement to work with the geolocation middleware. + +[Application server requirements for geolocation middleware](<./doc/applicationServer.rst>) \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers.c b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers.c new file mode 100644 index 000000000..ac03443c3 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers.c @@ -0,0 +1,237 @@ +/** + * @file wifi_helpers.c + * + * @brief Interface between the WI-Fi middleware and the LR11xx radio driver. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include + +#include "mw_assert.h" +#include "mw_dbg_trace.h" + +#include "lr11xx_wifi.h" +#include "lr11xx_system.h" + +#include "wifi_helpers.h" + +#include "mw_bsp.h" +#include "mw_common.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +#define TIMESTAMP_AP_PHONE_FILTERING 18000 + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +static wifi_settings_t settings = { 0 }; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +void smtc_wifi_settings_init( const wifi_settings_t* wifi_settings ) +{ + /* Set current context Wi-Fi settings */ + memcpy( &settings, wifi_settings, sizeof settings ); +} + +bool smtc_wifi_start_scan( const void* radio_context ) +{ + lr11xx_status_t status; + + if( mw_radio_configure_for_scan( radio_context ) == true ) + { + status = + lr11xx_system_set_dio_irq_params( radio_context, LR11XX_SYSTEM_IRQ_WIFI_SCAN_DONE, LR11XX_SYSTEM_IRQ_NONE ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to set Wi-Fi IRQ params\n" ); + return false; + } + + status = lr11xx_wifi_cfg_timestamp_ap_phone( radio_context, TIMESTAMP_AP_PHONE_FILTERING ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to configure timestamp ap phone\n" ); + return false; + } + + /* Enable Wi-Fi path */ + mw_bsp_wifi_prescan_actions( ); + + status = lr11xx_wifi_scan_time_limit( radio_context, settings.types, settings.channels, + LR11XX_WIFI_SCAN_MODE_BEACON_AND_PKT, settings.max_results, + settings.timeout_per_channel, settings.timeout_per_scan ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to start Wi-Fi scan\n" ); + mw_bsp_wifi_postscan_actions( ); + return false; + } + } + else + { + MW_DBG_TRACE_ERROR( "Failed to configure LR11XX for Wi-Fi scan\n" ); + return false; + } + + return true; +} + +void smtc_wifi_scan_ended( void ) +{ + /* Disable the Wi-Fi path */ + mw_bsp_wifi_postscan_actions( ); +} + +bool smtc_wifi_get_results( const void* radio_context, wifi_scan_all_result_t* wifi_results ) +{ + lr11xx_wifi_basic_complete_result_t wifi_results_mac_addr[WIFI_MAX_RESULTS] = { 0 }; + uint8_t nb_results; + uint8_t max_nb_results; + uint8_t result_index = 0; + lr11xx_status_t status = LR11XX_STATUS_OK; + + status = lr11xx_wifi_get_nb_results( radio_context, &nb_results ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get Wi-Fi scan number of results\n" ); + return false; + } + + /* check if the array is big enough to hold all results */ + max_nb_results = sizeof( wifi_results_mac_addr ) / sizeof( wifi_results_mac_addr[0] ); + if( nb_results > max_nb_results ) + { + MW_DBG_TRACE_ERROR( "Wi-Fi scan result size exceeds %u (%u)\n", max_nb_results, nb_results ); + return false; + } + + status = lr11xx_wifi_read_basic_complete_results( radio_context, 0, nb_results, wifi_results_mac_addr ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to read Wi-Fi scan results\n" ); + return false; + } + + /* add scan to results */ + for( uint8_t index = 0; index < nb_results; index++ ) + { + const lr11xx_wifi_basic_complete_result_t* local_basic_result = &wifi_results_mac_addr[index]; + lr11xx_wifi_channel_t channel; + bool rssi_validity; + lr11xx_wifi_mac_origin_t mac_origin_estimation; + + lr11xx_wifi_parse_channel_info( local_basic_result->channel_info_byte, &channel, &rssi_validity, + &mac_origin_estimation ); + + if( mac_origin_estimation != LR11XX_WIFI_ORIGIN_BEACON_MOBILE_AP ) + { + wifi_results->results[result_index].channel = channel; + + wifi_results->results[result_index].type = + lr11xx_wifi_extract_signal_type_from_data_rate_info( local_basic_result->data_rate_info_byte ); + + memcpy( wifi_results->results[result_index].mac_address, local_basic_result->mac_address, + LR11XX_WIFI_MAC_ADDRESS_LENGTH ); + + wifi_results->results[result_index].rssi = local_basic_result->rssi; + wifi_results->nbr_results++; + result_index++; + } + } + + return true; +} + +bool smtc_wifi_get_power_consumption( const void* radio_context, uint32_t* power_consumption_uah ) +{ + lr11xx_status_t status; + lr11xx_wifi_cumulative_timings_t timing; + lr11xx_system_reg_mode_t reg_mode; + + status = lr11xx_wifi_read_cumulative_timing( radio_context, &timing ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to get wifi timings\n" ); + return false; + } + + mw_bsp_get_lr11xx_reg_mode( radio_context, ®_mode ); + *power_consumption_uah = ( uint32_t ) lr11xx_wifi_get_consumption( reg_mode, timing ); + + /* Accumulate timings until there is a significant amount of energy consumed */ + if( *power_consumption_uah > 0 ) + { + status = lr11xx_wifi_reset_cumulative_timing( radio_context ); + if( status != LR11XX_STATUS_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to reset wifi timings\n" ); + return false; + } + } + + return true; +} + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers.h b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers.h new file mode 100644 index 000000000..1e95746a1 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers.h @@ -0,0 +1,120 @@ +/** + * @file wifi_helpers.h + * + * @brief Interface between the WI-Fi middleware and the LR11xx radio driver. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 __WIFI_HELPERS_H__ +#define __WIFI_HELPERS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include + +#include "wifi_helpers_defs.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +/*! + * @brief Initialise the settings for Wi-Fi scan + * + * @param [in] wifi_settings Wi-Fi settings \ref wifi_settings_t + */ +void smtc_wifi_settings_init( const wifi_settings_t* wifi_settings ); + +/*! + * @brief Start a Wi-Fi scan + * + * @param [in] radio_context Chip implementation context + * + * @return a boolean: true for success, false otherwise + */ +bool smtc_wifi_start_scan( const void* radio_context ); + +/*! + * @brief Fetch the results obtained during previous Wi-Fi scan + * + * @param [in] radio_context Chip implementation context + * @param [out] result Scan results \ref wifi_scan_all_result_t + * + * @return a boolean: true for success, false otherwise + */ +bool smtc_wifi_get_results( const void* radio_context, wifi_scan_all_result_t* result ); + +/*! + * @brief Tear down function for Wi-Fi scan termination actions + * + * This function is typically to be called when during the handling of the event of user radio access + */ +void smtc_wifi_scan_ended( void ); + +/*! + * @brief Get the power consumption of the last scan + * + * @param [in] radio_context Chip implementation context + * @param [out] power_consumption_uah Power consumption of the last scan in uAh + * + * @return a boolean: true for success, false otherwise + */ +bool smtc_wifi_get_power_consumption( const void* radio_context, uint32_t* power_consumption_uah ); + +#ifdef __cplusplus +} +#endif + +#endif // __WIFI_HELPERS_H__ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers_defs.h b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers_defs.h new file mode 100644 index 000000000..93dc9ec5e --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_helpers_defs.h @@ -0,0 +1,119 @@ +/** + * @file wifi_helpers_defs.h + * + * @brief Types and constants definitions of Wi-Fi helpers. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 __WIFI_HELPERS_DEFS_H__ +#define __WIFI_HELPERS_DEFS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include "lr11xx_wifi.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/*! + * @brief The maximal time to spend in preamble detection for each single scan, in ms + */ +#define WIFI_TIMEOUT_PER_SCAN_DEFAULT ( 90 ) + +/*! + * @brief The time to spend scanning one channel, in ms + */ +#define WIFI_TIMEOUT_PER_CHANNEL_DEFAULT ( 300 ) + +/*! + * @brief The maximal number of results to gather. Maximum value is 32 + */ +#define WIFI_MAX_RESULTS ( 5 ) + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/*! + * @brief Structure representing the configuration of Wi-Fi scan + */ +typedef struct +{ + lr11xx_wifi_channel_mask_t channels; //!< A mask of the channels to be scanned + lr11xx_wifi_signal_type_scan_t types; //!< Wi-Fi types to be scanned + uint8_t max_results; //!< Maximum number of results expected for a scan + uint32_t timeout_per_channel; //!< Time to spend scanning one channel, in ms + uint32_t timeout_per_scan; //!< Maximal time to spend in preamble detection for each single scan, in ms +} wifi_settings_t; + +/*! + * @brief Structure representing a single scan result + */ +typedef struct +{ + lr11xx_wifi_mac_address_t mac_address; //!< MAC address of the Wi-Fi access point which has been detected + lr11xx_wifi_channel_t channel; //!< Channel on which the access point has been detected + lr11xx_wifi_signal_type_result_t type; //!< Type of Wi-Fi which has been detected + int8_t rssi; //!< Strength of the detected signal +} wifi_scan_single_result_t; + +/*! + * @brief Structure representing a collection of scan results + */ +typedef struct +{ + uint8_t nbr_results; //!< Number of results + uint32_t power_consumption_uah; //!< Power consumption to acquire this set of results + uint32_t timestamp; //!< Timestamp at which the data set has been completed + wifi_scan_single_result_t results[WIFI_MAX_RESULTS]; //!< Buffer containing the results +} wifi_scan_all_result_t; + +#ifdef __cplusplus +} +#endif + +#endif // __WIFI_HELPERS_DEFS_H__ + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_middleware.c b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_middleware.c new file mode 100644 index 000000000..7f5d72bf1 --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_middleware.c @@ -0,0 +1,757 @@ +/** + * @file wifi_middleware.c + * + * @brief Wi-Fi geolocation middleware implementing scan & send sequence. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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. + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include + +#include "wifi_version.h" +#include "wifi_middleware.h" +#include "wifi_helpers.h" + +#include "mw_assert.h" +#include "mw_dbg_trace.h" + +#include "lr11xx_system.h" + +#include "smtc_modem_middleware_advanced_api.h" +#include "smtc_modem_hal.h" + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE MACROS----------------------------------------------------------- + */ + +#define WIFI_MW_FEATURE_OFF 0 +#define WIFI_MW_FEATURE_ON !WIFI_MW_FEATURE_OFF + +#ifndef WIFI_MW_DBG_TRACE_TIME_CRITICAL +#define WIFI_MW_DBG_TRACE_TIME_CRITICAL WIFI_MW_FEATURE_OFF /* Enable/Disable traces here */ +#endif + +#if( WIFI_MW_DBG_TRACE_TIME_CRITICAL == WIFI_MW_FEATURE_ON ) +#define WIFI_MW_TIME_CRITICAL_TRACE_MSG( msg ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( msg ); \ + } while( 0 ) + +#define WIFI_MW_TIME_CRITICAL_TRACE_PRINTF( ... ) \ + do \ + { \ + MW_DBG_TRACE_PRINTF( __VA_ARGS__ ); \ + } while( 0 ) + +#else +#define WIFI_MW_TIME_CRITICAL_TRACE_MSG( msg ) +#define WIFI_MW_TIME_CRITICAL_TRACE_PRINTF( ... ) +#endif + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE CONSTANTS ------------------------------------------------------- + */ + +/** + * @brief Radio planner task ID for WiFi middleware + */ +#define RP_TASK_WIFI SMTC_MODEM_RP_TASK_ID2 + +/** + * @brief LoRaWAN port used for uplinks of the WIFI scan results + */ +#define WIFI_DEFAULT_UPLINK_PORT 197 + +/** + * @brief Minimal number of detected access point in a scan result to consider the scan valid + */ +#define WIFI_SCAN_NB_AP_MIN ( 3 ) + +/** + * @brief Size in bytes of a WiFi Access-Point address + */ +#define WIFI_AP_ADDRESS_SIZE ( 6 ) + +/** + * @brief Size in bytes to store the RSSI of a detected WiFi Access-Point + */ +#define WIFI_AP_RSSI_SIZE ( 1 ) + +/** + * @brief Size in bytes of the payload tag to indicate frame format (as defined by LR1110 WiFi positioning protocol of + * LoRaCloud) + */ +#define WIFI_TAG_SIZE ( 1 ) + +/** + * @brief The LoRa Basics Modem extended uplink ID to be used for Wi-Fi uplinks (TASK_EXTENDED_2) + */ +#define SMTC_MODEM_EXTENDED_UPLINK_ID_WIFI ( 2 ) +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE TYPES ----------------------------------------------------------- + */ + +/** + * @brief The list of possible internal pending errors + */ +typedef enum +{ + WIFI_MW_ERROR_NONE, //!< No error + WIFI_MW_ERROR_SCAN_FAILED, //!< Scan failed due to LR11xx error + WIFI_MW_ERROR_UNKNOWN, //!< An unknown error occurred +} wifi_mw_internal_error_t; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE VARIABLES ------------------------------------------------------- + */ + +/** + * @brief The modem/radio context given by the application when middleware is initialized. Used to access LBM and radio + * functions. + */ +static ralf_t* modem_radio_ctx = NULL; + +/** + * @brief The modem stack ID to be used. + */ +static uint8_t modem_stack_id = 0; + +/*! + * @brief The current pending errors (reset when a new scan sequence starts) + */ +static wifi_mw_internal_error_t pending_error = WIFI_MW_ERROR_NONE; + +/*! + * @brief The current pending events (reset by the user or when a new scan sequence starts) + */ +static uint8_t pending_events = 0; + +/*! + * @brief Results of the current Wi-Fi scan + */ +static wifi_scan_all_result_t wifi_results; + +/*! + * @brief The buffer containing results to be sent over the air + */ +static uint8_t wifi_result_buffer[WIFI_TAG_SIZE + ( ( WIFI_AP_RSSI_SIZE + WIFI_AP_ADDRESS_SIZE ) * WIFI_MAX_RESULTS )]; + +/*! + * @brief User has requested to cancel the scan that was scheduled + */ +static bool task_cancelled_by_user = false; + +/*! + * @brief The scan sequence has started + * + * Set to true when the first scan of the sequence actually started (radio) + * Set back to false when the complete sequence is terminated (all results sent) + */ +static bool task_running = false; + +/*! + * @brief Indicates sequence to "scan & send" or "scan only" mode + */ +static bool send_bypass = false; + +/*! + * @brief The LoRaWAN port on which WiFi scan results are sent + */ +static uint8_t lorawan_port = WIFI_DEFAULT_UPLINK_PORT; + +/*! + * @brief The format of the Wi-Fi scan results to be used. + */ +static wifi_mw_payload_format_t payload_format = WIFI_MW_PAYLOAD_MAC; + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DECLARATION ------------------------------------------- + */ + +/*! + * @brief Interrupt handler signaled by the Radio Planner when the radio is available and it is time to start the scan + * (WARNING: running under interrupt context) + * + * @param [in] context Pointer to context given by RP (not used) + */ +static void wifi_mw_scan_rp_task_launch( void* context ); + +/*! + * @brief Interrupt handler signaled by the Radio Planner when the scan is completed (WARNING: running under interrupt + * context) + * + * @param [in] status IRQ status from RP + */ +static void wifi_mw_scan_rp_task_done( smtc_modem_rp_status_t* status ); + +/*! + * @brief Send the scan results over the air (uses extended API from LBM to send uplink) + * + * @return a boolean set to true if a frame has been sent, set to false is there is nothing to be sent (bypass mode, + * invalid scan, send error...). + */ +static bool wifi_mw_send_results( void ); + +/*! + * @brief Clear the results structure + */ +static void wifi_mw_reset_results( void ); + +/*! + * @brief Callback called by the LBM when the uplink has been sent. + */ +static void wifi_mw_tx_done_callback( void ); + +/*! + * @brief Send an uplink request to LBM through the extended API (no buffer copy) + * + * @param [in] tx_frame_buffer A pointer to the buffer payload to be sent over the air. + * @param [in] tx_frame_buffer_size The size of the buffer to be sent. + * + * @return a boolean set to true for success, false otherwise. + */ +static bool wifi_mw_send_frame( const uint8_t* tx_frame_buffer, const uint8_t tx_frame_buffer_size ); + +/*! + * @brief Add an event to the pending event bitfield, and send all pending events to the application + * + * @param [in] event_type The event to be added to the pending events bitfield. + */ +static void wifi_mw_send_event( wifi_mw_event_type_t event_type ); + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + +mw_return_code_t wifi_mw_get_version( mw_version_t* version ) +{ + if( version == NULL ) + { + MW_DBG_TRACE_ERROR( "Failed to get Wi-Fi middleware version\n" ); + return MW_RC_FAILED; + } + + version->major = WIFI_MW_VERSION_MAJOR; + version->minor = WIFI_MW_VERSION_MINOR; + version->patch = WIFI_MW_VERSION_PATCH; + + return MW_RC_OK; +} + +mw_return_code_t wifi_mw_init( ralf_t* modem_radio, uint8_t stack_id ) +{ + if( modem_radio == NULL ) + { + MW_DBG_TRACE_ERROR( "Failed to init Wi-Fi middleware: modem radio not set\n" ); + return MW_RC_FAILED; + } + + /* Set radio context */ + modem_radio_ctx = modem_radio; + + /* Set the stack id */ + modem_stack_id = stack_id; + + return MW_RC_OK; +} + +mw_return_code_t wifi_mw_scan_start( uint32_t start_delay ) +{ + smtc_modem_return_code_t modem_rc; + smtc_modem_rp_task_t rp_task = { 0 }; + uint32_t time_ms; + wifi_settings_t wifi_settings = { 0 }; + + if( modem_radio_ctx == NULL ) + { + MW_DBG_TRACE_ERROR( "Wi-Fi middleware not ready, cannot start scan\n" ); + return MW_RC_FAILED; + } + + if( task_running == true ) + { + MW_DBG_TRACE_ERROR( "Wi-Fi scan on-going. Cancel it before starting a new one\n" ); + return MW_RC_BUSY; + } + + /* Reset pending internal error */ + pending_error = WIFI_MW_ERROR_NONE; + + /* Reset pending events */ + pending_events = 0; + + /* Reset any pending cancel request which has not been completed (error case) */ + task_cancelled_by_user = false; + + /* Init settings */ + wifi_settings.channels = 0x3FFF; /* all channels enabled */ + wifi_settings.types = LR11XX_WIFI_TYPE_SCAN_B_G_N; + wifi_settings.max_results = WIFI_MAX_RESULTS; + wifi_settings.timeout_per_channel = WIFI_TIMEOUT_PER_CHANNEL_DEFAULT; + wifi_settings.timeout_per_scan = WIFI_TIMEOUT_PER_SCAN_DEFAULT; + smtc_wifi_settings_init( &wifi_settings ); + + /* Prepare the task for next scan, add 300ms to avoid schedule a task in the past */ + time_ms = smtc_modem_hal_get_time_in_ms( ) + 300; + + rp_task.type = SMTC_MODEM_RP_TASK_STATE_ASAP; + rp_task.start_time_ms = time_ms + ( start_delay * 1000 ); + rp_task.duration_time_ms = 10 * 1000; + rp_task.id = RP_TASK_WIFI; + rp_task.launch_task_callback = wifi_mw_scan_rp_task_launch; + rp_task.end_task_callback = wifi_mw_scan_rp_task_done; + modem_rc = smtc_modem_rp_add_user_radio_access_task( &rp_task ); + + if( modem_rc == SMTC_MODEM_RC_OK ) + { + MW_DBG_TRACE_INFO( "RP_TASK_WIFI - new scan - task queued at %u + %u\n", time_ms, start_delay * 1000 ); + } + else + { + MW_DBG_TRACE_ERROR( "Failed to queue Wi-Fi scan task (0x%02X)\n", modem_rc ); + return MW_RC_FAILED; + } + + return MW_RC_OK; +} + +mw_return_code_t wifi_mw_scan_cancel( void ) +{ + smtc_modem_return_code_t modem_rc; + + if( modem_radio_ctx == NULL ) + { + MW_DBG_TRACE_ERROR( "Wi-Fi middleware not ready, no scan to cancel\n" ); + return MW_RC_FAILED; + } + + /* The Wi-Fi scan sequence will be in running state from the moment the task + has been started by the RP, until all the packets have been sent over the + air. This is handled this way for more simplicity: + - as we cannot abort a running scan, it would requires to check RP state + to know if we can abort the scheduled task or not. + - aborting anywhere in the complete sequence will require lot of logic + through all the code, we want to keep as simple as possible + So a scan can be cancelled only if requested before the first scan as + actually started. Once it is started, it will complete the sequence */ + if( task_running == true ) + { + MW_DBG_TRACE_ERROR( "Wi-Fi scan sequence started, too late to cancel\n" ); + return MW_RC_BUSY; + } + + task_cancelled_by_user = true; + + MW_DBG_TRACE_INFO( "RP_TASK_WIFI - Request cancel of scheduled scan\n" ); + modem_rc = smtc_modem_rp_abort_user_radio_access_task( RP_TASK_WIFI ); + if( modem_rc != SMTC_MODEM_RC_OK ) + { + MW_DBG_TRACE_ERROR( "Failed to abort Wi-Fi scan task\n" ); + } + + return MW_RC_OK; +} + +bool wifi_mw_has_event( uint8_t pending_events, wifi_mw_event_type_t event ) +{ + if( ( pending_events & ( 1 << event ) ) == ( 1 << event ) ) + { + return true; + } + + return false; +} + +mw_return_code_t wifi_mw_get_event_data_scan_done( wifi_mw_event_data_scan_done_t* data ) +{ + if( data == NULL ) + { + MW_DBG_TRACE_ERROR( "Provided pointer is NULL\n" ); + return MW_RC_FAILED; + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_SCAN_DONE ) ) + { + data->nbr_results = wifi_results.nbr_results; + data->power_consumption_uah = wifi_results.power_consumption_uah; + data->timestamp = wifi_results.timestamp; + for( uint8_t i = 0; i < wifi_results.nbr_results; i++ ) + { + data->results[i].rssi = wifi_results.results[i].rssi; + data->results[i].channel = wifi_results.results[i].channel; + data->results[i].type = wifi_results.results[i].type; + memcpy( data->results[i].mac_address, wifi_results.results[i].mac_address, WIFI_AP_ADDRESS_SIZE ); + } + + return MW_RC_OK; + } + else + { + MW_DBG_TRACE_ERROR( "Data are not ready\n" ); + return MW_RC_FAILED; + } +} + +void wifi_mw_set_port( uint8_t port ) { lorawan_port = port; } + +void wifi_mw_send_bypass( bool no_send ) +{ + MW_DBG_TRACE_INFO( "Wi-Fi scan: set scan only mode to %s (bypass send)\n", no_send ? "TRUE" : "FALSE" ); + + /* Set scan only current mode */ + send_bypass = no_send; +} + +void wifi_mw_set_payload_format( wifi_mw_payload_format_t format ) { payload_format = format; } + +void wifi_mw_display_results( const wifi_mw_event_data_scan_done_t* data ) +{ + if( data != NULL ) + { + MW_DBG_TRACE_PRINTF( "SCAN_DONE info:\n" ); + MW_DBG_TRACE_PRINTF( "-- number of results: %u\n", data->nbr_results ); + MW_DBG_TRACE_PRINTF( "-- power consumption: %u uah\n", data->power_consumption_uah ); + MW_DBG_TRACE_PRINTF( "-- timestamp: %u\n", data->timestamp ); + + for( uint8_t i = 0; i < data->nbr_results; i++ ) + { + for( uint8_t j = 0; j < WIFI_AP_ADDRESS_SIZE; j++ ) + { + MW_DBG_TRACE_PRINTF( "%02X ", data->results[i].mac_address[j] ); + } + MW_DBG_TRACE_PRINTF( " -- Channel: %d", data->results[i].channel ); + MW_DBG_TRACE_PRINTF( " -- Type: %d", data->results[i].type ); + MW_DBG_TRACE_PRINTF( " -- RSSI: %d\n", data->results[i].rssi ); + } + } +} + +mw_return_code_t wifi_mw_get_event_data_terminated( wifi_mw_event_data_terminated_t* data ) +{ + if( data == NULL ) + { + MW_DBG_TRACE_ERROR( "Provided pointer is NULL\n" ); + return MW_RC_FAILED; + } + + if( wifi_mw_has_event( pending_events, WIFI_MW_EVENT_TERMINATED ) ) + { + /* Result are sent only if enough results */ + if( send_bypass == false ) + { + data->nb_scans_sent = ( wifi_results.nbr_results >= WIFI_SCAN_NB_AP_MIN ) ? 1 : 0; + } + else + { + /* assume that the "no send" mode was configured before starting the scan, so no packet sent */ + data->nb_scans_sent = 0; + } + + return MW_RC_OK; + } + else + { + MW_DBG_TRACE_ERROR( "Scan is not terminated\n" ); + return MW_RC_FAILED; + } +} + +void wifi_mw_clear_pending_events( void ) { pending_events = 0; } + +/* + * ----------------------------------------------------------------------------- + * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- + */ + +static void wifi_mw_scan_rp_task_launch( void* context ) +{ + MW_DBG_TRACE_MSG( "---- internal Wi-Fi scan start ----\n" ); + + /* From now, the scan sequence can not be cancelled */ + task_running = true; + + /* Reset previous results */ + wifi_mw_reset_results( ); + + /* Start WIFI scan */ + if( smtc_wifi_start_scan( modem_radio_ctx->ral.context ) != true ) + { + pending_error = WIFI_MW_ERROR_SCAN_FAILED; + + MW_DBG_TRACE_ERROR( "RP_TASK_WIFI - failed to start scan, abort task\n" ); + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_rp_abort_user_radio_access_task( RP_TASK_WIFI ) ); + + /* + When aborting the task, the RP will call the end_task_callback() with SMTC_RP_RADIO_ABORTED status. + ERROR event will be sent from there to the application + */ + } +} + +void wifi_mw_scan_rp_task_done( smtc_modem_rp_status_t* status ) +{ + uint32_t time_ms; + smtc_modem_rp_radio_status_t irq_status = status->status; + uint32_t meas_time; + + /* ----------------------------------------------------------------- */ + /* WARNING: put the radio back to sleep before exiting this function */ + /* ----------------------------------------------------------------- */ + + /* Save current time to restart task */ + time_ms = smtc_modem_hal_get_time_in_ms( ); + WIFI_MW_TIME_CRITICAL_TRACE_PRINTF( "WIFI scan done at %d (irq_status=%d)\n", time_ms, irq_status ); + + /* WIFI scan completed or aborted - first thing to be done */ + smtc_wifi_scan_ended( ); + + if( irq_status == SMTC_RP_RADIO_ABORTED ) + { + if( pending_error == WIFI_MW_ERROR_NONE ) + { + if( task_cancelled_by_user == false ) + { + MW_DBG_TRACE_WARNING( "RP_TASK_WIFI(%d) - task aborted by RP\n", __LINE__ ); + + /* Notify the application that the operation has been terminated */ + wifi_mw_send_event( WIFI_MW_EVENT_SCAN_DONE ); + wifi_mw_send_event( WIFI_MW_EVENT_TERMINATED ); + } + else + { + MW_DBG_TRACE_WARNING( "RP_TASK_WIFI(%d) - task cancelled by user\n", __LINE__ ); + + /* We handle here the fact that when the user requests to abort a radio planner task + with smtc_modem_rp_abort_user_radio_access_task(), the RP will call this task_done callback + with status set to SMTC_RP_RADIO_ABORTED */ + + /* reset cancel request status */ + task_cancelled_by_user = false; + + /* Send an event to application to notify for error */ + wifi_mw_send_event( WIFI_MW_EVENT_SCAN_CANCELLED ); + } + } + else + { + MW_DBG_TRACE_WARNING( "RP_TASK_WIFI(%d) - task aborted for UNKNOWN reason\n", __LINE__ ); + + /* Send an event to application to notify for error */ + wifi_mw_send_event( WIFI_MW_EVENT_ERROR_UNKNOWN ); + } + } + else if( irq_status == SMTC_RP_RADIO_WIFI_SCAN_DONE ) + { + bool scan_results_rc; + + memset( &wifi_results, 0, sizeof wifi_results ); + + /* Timestamp this scan */ + wifi_results.timestamp = mw_get_gps_time( ); + + /* Wi-Fi scan completed, get and display the results */ + scan_results_rc = smtc_wifi_get_results( modem_radio_ctx->ral.context, &wifi_results ); + + /* Get scan power consumption */ + smtc_wifi_get_power_consumption( modem_radio_ctx->ral.context, &wifi_results.power_consumption_uah ); + + if( scan_results_rc == true ) + { + /* Scan has been completed, send an event to application */ + wifi_mw_send_event( WIFI_MW_EVENT_SCAN_DONE ); + + /* Send scan uplink if any, or send event to application */ + if( wifi_mw_send_results( ) == false ) + { + /* No more scan result to be sent, or failed to send packet */ + WIFI_MW_TIME_CRITICAL_TRACE_PRINTF( "RP_TASK_WIFI: no scan result to be sent\n" ); + + /* Send an event to application to notify for completion */ + /* The application needs to know that it can proceed with the next scan */ + wifi_mw_send_event( WIFI_MW_EVENT_TERMINATED ); + } + } + else + { + MW_DBG_TRACE_ERROR( "RP_TASK_WIFI - unkown error on get results\n" ); + /* Send an event to application to notify for error */ + wifi_mw_send_event( WIFI_MW_EVENT_ERROR_UNKNOWN ); + } + } + else + { + MW_DBG_TRACE_ERROR( "WIFI RP task - Unknown status %d at %d\n", irq_status, time_ms ); + + /* Send an event to application to notify for error */ + wifi_mw_send_event( WIFI_MW_EVENT_ERROR_UNKNOWN ); + } + + /* Monitor callback exec duration (should be kept as low as possible) */ + meas_time = smtc_modem_hal_get_time_in_ms( ); + MW_DBG_TRACE_WARNING( "WIFI RP task - done callback duration %u ms\n", meas_time - time_ms ); + + /* Set the radio back to sleep */ + mw_radio_set_sleep( modem_radio_ctx->ral.context ); +} + +/*! + * @brief User private function + */ + +static bool wifi_mw_send_results( void ) +{ + uint8_t wifi_buffer_size = 0; + + /* Check if "no send "mode" is configured */ + if( send_bypass == true ) + { + /* Bypass send */ + return false; + } + + /* Check if there are results to be sent */ + if( wifi_results.nbr_results < WIFI_SCAN_NB_AP_MIN ) + { + return false; + } + + /* Add the payload format tag */ + wifi_result_buffer[wifi_buffer_size] = payload_format; + wifi_buffer_size += WIFI_TAG_SIZE; + + /* Concatenate all results in send buffer */ + for( uint8_t i = 0; i < wifi_results.nbr_results; i++ ) + { + /* Copy Access Point RSSI address in result buffer (if requested) */ + if( payload_format == WIFI_MW_PAYLOAD_MAC_RSSI ) + { + wifi_result_buffer[wifi_buffer_size] = wifi_results.results[i].rssi; + wifi_buffer_size += WIFI_AP_RSSI_SIZE; + } + /* Copy Access Point MAC address in result buffer */ + memcpy( &wifi_result_buffer[wifi_buffer_size], wifi_results.results[i].mac_address, WIFI_AP_ADDRESS_SIZE ); + wifi_buffer_size += WIFI_AP_ADDRESS_SIZE; + } + + /* Send buffer */ + if( wifi_mw_send_frame( wifi_result_buffer, wifi_buffer_size ) != true ) + { + MW_DBG_TRACE_ERROR( "SEND FRAME ERROR\n" ); + return false; + } + + return true; +} + +static void wifi_mw_reset_results( void ) { memset( &wifi_results, 0, sizeof wifi_results ); } + +static void wifi_mw_tx_done_callback( void ) +{ + MW_DBG_TRACE_MSG( "---- internal TX DONE ----\n" ); + + /* Send an event to application to notify for completion */ + wifi_mw_send_event( WIFI_MW_EVENT_TERMINATED ); +} + +static bool wifi_mw_send_frame( const uint8_t* tx_frame_buffer, const uint8_t tx_frame_buffer_size ) +{ + smtc_modem_return_code_t modem_response_code = SMTC_MODEM_RC_OK; + uint8_t tx_max_payload; + int32_t duty_cycle; + + /* Sanity check: + We expect the application parameters to be properly set to avoid: + - exceed duty cycle + - exceed maximum payload + The below checks are only for developer information */ + + /* Inform if duty cycle is not available */ + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_get_duty_cycle_status( &duty_cycle ) ); + if( duty_cycle < 0 ) + { + MW_DBG_TRACE_ERROR( "Duty Cycle: available for next uplink in %d milliseconds\n", duty_cycle ); + } + + /* Get the next tx payload size */ + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_get_next_tx_max_payload( modem_stack_id, &tx_max_payload ) ); + if( tx_frame_buffer_size > tx_max_payload ) + { + MW_DBG_TRACE_ERROR( "payload size: exceed max payload allowed for next uplink (%d > %d bytes)\n", + tx_frame_buffer_size, tx_max_payload ); + } + + /* Send uplink */ + modem_response_code = + smtc_modem_request_extended_uplink( modem_stack_id, lorawan_port, false, tx_frame_buffer, tx_frame_buffer_size, + SMTC_MODEM_EXTENDED_UPLINK_ID_WIFI, &wifi_mw_tx_done_callback ); + if( modem_response_code == SMTC_MODEM_RC_OK ) + { + MW_DBG_TRACE_INFO( "Request uplink:\n" ); + MW_DBG_TRACE_ARRAY( "Payload", tx_frame_buffer, tx_frame_buffer_size ); + return true; + } + else + { + MW_DBG_TRACE_ERROR( "Request uplink failed with modem_response_code : %d \n", modem_response_code ); + return false; + } +} + +static void wifi_mw_send_event( wifi_mw_event_type_t event_type ) +{ + /* The scan sequence ends when an event is sent to the application, except if SCAN_DONE */ + if( event_type != WIFI_MW_EVENT_SCAN_DONE ) + { + task_running = false; + } + + /* Send the event to the application */ + pending_events = pending_events | ( 1 << event_type ); + MW_ASSERT_SMTC_MODEM_RC( smtc_modem_increment_event_middleware( SMTC_MODEM_EVENT_MIDDLEWARE_2, pending_events ) ); +} + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_middleware.h b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_middleware.h new file mode 100644 index 000000000..8c31122ac --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_middleware.h @@ -0,0 +1,234 @@ +/** + * @file wifi_middleware.h + * + * @brief Wi-Fi geolocation middleware implementing scan & send sequence. + * + * The Clear BSD License + * Copyright Semtech Corporation 2022. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the disclaimer + * below) provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 SEMTECH CORPORATION 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 __WIFI_MIDDLEWARE_H__ +#define __WIFI_MIDDLEWARE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +#include +#include + +#include "ralf.h" + +#include "mw_common.h" + +#include "wifi_helpers_defs.h" + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ + +/** + * @brief Wi-Fi event status sent from the middleware to the application + */ +typedef enum wifi_mw_event_mask_e +{ + WIFI_MW_EVENT_SCAN_CANCELLED = 0, //!< Scan operation has been cancelled + WIFI_MW_EVENT_SCAN_DONE = 1, //!< Scan operation has been completed + WIFI_MW_EVENT_TERMINATED = 2, //!< Scan & send sequence has been completed + WIFI_MW_EVENT_ERROR_UNKNOWN = 3, //!< Scan operation has failed for an unknown reason + /* 8 event types max */ +} wifi_mw_event_type_t; + +/** + * @brief Wi-Fi payload format (as defined by LR1110 WiFi positioning protocol of LoRaCloud). + */ +typedef enum wifi_mw_payload_format_e +{ + WIFI_MW_PAYLOAD_MAC = 0x00, //!< Only the MAC addresses of the detected Access Points are sent + WIFI_MW_PAYLOAD_MAC_RSSI = 0x01, //!< Both MAC address and RSSI of detected Access Points are sent +} wifi_mw_payload_format_t; + +/** + * @brief The data that can be retrieved when a WIFI_MW_EVENT_SCAN_DONE event occurs + */ +typedef wifi_scan_all_result_t wifi_mw_event_data_scan_done_t; + +/** + * @brief The data that can be retrieved when a WIFI_MW_EVENT_TERMINATED event occurs. + */ +typedef struct +{ + uint8_t nb_scans_sent; //!< Number of scans that have been sent over the air +} wifi_mw_event_data_terminated_t; + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ +/** + * @brief Get version of the Wi-Fi middleware + * + * @param [out] version Middleware version + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED version is not initialized + */ +mw_return_code_t wifi_mw_get_version( mw_version_t* version ); + +/** + * @brief Initialize the Wi-Fi middleware context + * Must be called only once at startup, before any other call to the middleware. + * + * @param [in] modem_radio Interface to access the radio + * @param [in] stack_id Modem stack ID + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED The modem/radio interface is not initialized + */ +mw_return_code_t wifi_mw_init( ralf_t* modem_radio, uint8_t stack_id ); + +/** + * @brief Program a Wi-Fi scan & send sequence to start in a given delay + * + * @param [in] start_delay Delay in seconds before starting the scan sequence + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_BUSY A scan sequence is already on-going + * @retval MW_RC_FAILED An error occurred while starting the scanning sequence + */ +mw_return_code_t wifi_mw_scan_start( uint32_t start_delay ); + +/** + * @brief Cancel the currently programmed Wi-Fi scan & send sequence (if not actually started) + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_BUSY The scan sequence has already started (cannot be cancelled) + */ +mw_return_code_t wifi_mw_scan_cancel( void ); + +/** + * @brief Check if there is a particular event in the "pending events" bitfield + * + * @param [in] pending_events Pending events bitfield given when an event occurs + * @param [in] event Event to search in the pending events bitfield + * + * @return a boolean to indicate if the given event is set in the pending events bitfield + */ +bool wifi_mw_has_event( uint8_t pending_events, wifi_mw_event_type_t event ); + +/** + * @brief Retrieve the data associated with the WIFI_MW_EVENT_SCAN_DONE event + * + * @param [out] data Description of the scan group results + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED If the given pointer is NULL or if there is no SCAN_DONE event pending + */ +mw_return_code_t wifi_mw_get_event_data_scan_done( wifi_mw_event_data_scan_done_t* data ); + +/** + * @brief Retrieve the data associated with the WIFI_MW_EVENT_TERMINATED event + * + * @param [out] data Status of the end of the scan & send sequence + * + * @return Middleware return code as defined in @ref mw_return_code_t + * @retval MW_RC_OK Command executed without errors + * @retval MW_RC_FAILED If the given pointer is NULL or if there is no TERMINATED event pending + */ +mw_return_code_t wifi_mw_get_event_data_terminated( wifi_mw_event_data_terminated_t* data ); + +/** + * @brief Indicates to the middleware that all pending events have been handled and can be cleared + * + * Needs to be called when pending events have been processed, to avoid multiple notification of the same event. + */ +void wifi_mw_clear_pending_events( void ); + +/** + * @brief Set the LoRaWAN port on which to send the scan results uplinks + * + * @param [in] port LoRaWAN port + * + * By default it is set to 197 (WIFI_DEFAULT_UPLINK_PORT) + */ +void wifi_mw_set_port( uint8_t port ); + +/** + * @brief Bypass the "send" part of the "scan & send" sequence. Basically it is a "scan only" mode. + * It can be used if the application wants to control how the scan results are sent over the air. + * + * @param [in] no_send Boolean to bypass send or not + * + * By default it is set to false + */ +void wifi_mw_send_bypass( bool no_send ); + +/** + * @brief Indicates the format of the payload to be sent: MAC address only or MAC address with RSSI + * + * @param [in] format Payload format to be used + * + * By default it is set to MAC address only + */ +void wifi_mw_set_payload_format( wifi_mw_payload_format_t format ); + +/** + * @brief Print the data received with the WIFI_MW_EVENT_SCAN_DONE event + * + * @param [in] data Scan results to be printed on the console + */ +void wifi_mw_display_results( const wifi_mw_event_data_scan_done_t* data ); + +#ifdef __cplusplus +} +#endif + +#endif // __WIFI_MIDDLEWARE_H__ + +/* --- EOF ------------------------------------------------------------------ */ \ No newline at end of file diff --git a/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_version.h b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_version.h new file mode 100644 index 000000000..929a1112c --- /dev/null +++ b/lr1110/lr1110/seeed/geolocation_middleware/wifi/src/wifi_version.h @@ -0,0 +1,73 @@ +/*! + * \file wifi_version.h + * + * \brief Defines the Wi-Fi middleware version + * + * Revised BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation 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 AND CONTRIBUTORS "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 SEMTECH CORPORATION 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 WIFI_MW_VERSION_H__ +#define WIFI_MW_VERSION_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ +#define WIFI_MW_VERSION_MAJOR 2 +#define WIFI_MW_VERSION_MINOR 0 +#define WIFI_MW_VERSION_PATCH 0 + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +#ifdef __cplusplus +} +#endif + +#endif // WIFI_MW_VERSION_H__ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/lora_basics_modem/.gitlab-ci.yml b/lr1110/lr1110/seeed/lora_basics_modem/.gitlab-ci.yml new file mode 100644 index 000000000..72ac5082a --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/.gitlab-ci.yml @@ -0,0 +1,124 @@ +image: gitlab-radio-lib-runner-image-update + +before_script: + - apt-get update + - gem update --system + +variables: + GIT_SUBMODULE_STRATEGY: recursive + +stages: + - build + +#-- Common job --------------------------------------------------------------- +.build: + stage: build + + variables: + TARGET: "sx1261" + TRACE: "yes" + CRYPTO: "SOFT" + OPT: "" + REGION: "" + RP: "" + MCU_FLAGS: "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard" + + before_script: + - git submodule sync --recursive + - git submodule update --init --recursive + + script: + - arm-none-eabi-gcc --version + - make basic_modem_$TARGET MODEM_TRACE=$TRACE CRYPTO=$CRYPTO $REGION RP_VERSION=$RP MCU_FLAGS="$MCU_FLAGS" VERBOSE=yes + +#-- SX1261 jobs -------------------------------------------------------------- +job-build-sx1261-rp2-1-0-1: + extends: .build + variables: + TARGET: "sx1261" + RP: "RP2_101" + +job-build-sx1261-rp2-1-0-3: + extends: .build + variables: + TARGET: "sx1261" + RP: "RP2_103" + +#-- SX1262 jobs -------------------------------------------------------------- +job-build-sx1262-rp2-1-0-1: + extends: .build + variables: + TARGET: "sx1262" + RP: "RP2_101" + +job-build-sx1262-rp2-1-0-3: + extends: .build + variables: + TARGET: "sx1262" + RP: "RP2_103" + +#-- SX1268 jobs -------------------------------------------------------------- +job-build-sx1268-rp2-1-0-1: + extends: .build + variables: + TARGET: "sx1262" + RP: "RP2_101" + +job-build-sx1268-rp2-1-0-3: + extends: .build + variables: + TARGET: "sx1262" + RP: "RP2_103" + +#-- SX128x jobs -------------------------------------------------------------- +job-build-sx128x: + extends: .build + variables: + TARGET: "sx128x" + RP: "RP2_101" + +#-- LR1110 jobs -------------------------------------------------------------- +job-build-lr1110-rp2-1-0-1: + extends: .build + variables: + TARGET: "lr1110" + RP: "RP2_101" + +job-build-lr1110-rp2-1-0-3: + extends: .build + variables: + TARGET: "lr1110" + RP: "RP2_103" + +job-build-lr1110-crypto: + extends: .build + variables: + TARGET: "lr1110" + CRYPTO: "LR11XX" + RP: "RP2_101" + +job-build-lr1110-crypto-cred: + extends: .build + variables: + TARGET: "lr1110" + CRYPTO: "LR11XX_WITH_CREDENTIALS" + RP: "RP2_101" + +job-build-lr1110-no-trace: + extends: .build + variables: + TARGET: "lr1110" + TRACE: "no" + +#-- LR1120 jobs -------------------------------------------------------------- +job-build-lr1120-rp2-1-0-1: + extends: .build + variables: + TARGET: "lr1120" + RP: "RP2_101" + +job-build-lr1120-rp2-1-0-3: + extends: .build + variables: + TARGET: "lr1120" + RP: "RP2_103" diff --git a/lr1110/lr1110/seeed/lora_basics_modem/CHANGELOG.md b/lr1110/lr1110/seeed/lora_basics_modem/CHANGELOG.md new file mode 100644 index 000000000..c6fb7f469 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/CHANGELOG.md @@ -0,0 +1,90 @@ +# LoRa Basics Modem Library changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v3.2.4] 2022-08-23 + +### Added + +* AS923 group 4 Regional Parameters +* WW2G4 Regional Parameters for LoRaWAN protocol emulation +* Support of sx128x radio + +### Changed + +* [lr11xx_driver] Update to version `v2.1.1` +* [sx126x_driver] Update to version `v2.1.0` +* [makefile] remove ARM Cortex option from makefile to make LoRa Basics Modem completely agnostic from the MCU. Makefile shall be called with a new MCU_FLAGS option containing all core options +* [makefile] Align built target directory with crypto compilation options +* [utility/example] Update PA configuration process in `ral_lr11xx_bsp_get_tx_cfg` function. +* [utility/example] Update `stm32l476rgtx_flash.ld` files to fix stack start and stop address +* [utility/example] Remove `ral_lr11xx_bsp_get_rssi_calibration_table` workaround as the lr11xx driver was fixed +* [utility/example] Fix `hal_rtc_get_time_ms` so that it returns a full range value +* Clock Sync Service with ALC Sync source can generate events: + * SMTC_MODEM_EVENT_TIME_VALID_BUT_NOT_SYNC +* Clock Sync Service with DeviceTimeReq source can generate events: + * SMTC_MODEM_EVENT_TIME_VALID_BUT_NOT_SYNC + * SMTC_MODEM_EVENT_TIME_NOT_VALID + +### Fixed + +* [LFU] LoRa Basics Modem now rejects properly files with a size between 8181 and 8192 bytes +* [LFU] Fix issue regarding encryption of files with size higher than 4080 bytes +* [RP] Fix issue on radio interruption timestamp +* [LBT] On lr11xx targets, correct outdated LBT pre-hook issue +* [LBT] Remove log print when uplinking on fsk to avoid adding delay on scheduled tasks +* [LBT] Moved log print after enqueued the sniffing task in Radio Planer to avoid to add a delays +* [ADR] In case a MAC command `link_adr_req` with a new channel mask is received, it is now accepted if the custom datarate profile is enabled and configured with the highest datarate of the corresponding region +* [LFU/Stream] In case of reception of rejoin request from DAS, reset LFU and stream services properly +*[ClockSyncService/MAC] Fixed an issue where the Clock Synchronization Service was not reloaded when DeviceTimeAns was not received +*[DeviceTimeReq/MAC] Fixed an issue where the GPS epoch time became invalid if DeviceTimeAns not received + +## [v3.1.7] 2022-04-22 + +### Added + +* AS923 (3 sub regions included), IN865, KR920, RU864, AU915 Regional Parameters +* Class B support +* Class B Multicast support (up to 4 sessions) +* Class C Multicast support (up to 4 sessions) +* LR-FHSS Support (enabled with compilation option: `RP_VERSION=RP2_103`) +* Support of SX1261 and SX1262 radios +* Added commands: + * New connectivity check function: smtc_modem_lorawan_get_lost_connection_counter + * Makefile: add Regional Parameters option to choose to compile the code for RP2_101 or RP2_103 + * [smtc_modem_hal]: + * `smtc_modem_hal_assert(expr)` macro + * `smtc_modem_hal_assert_fail()` function + * `smtc_modem_hal_get_time_in_100us()` function + * `smtc_modem_hal_get_radio_irq_timestamp_in_100us()` function + * In `SMTC_MODEM_EVENT_DOWNDATA` event status: added new class B reception windows, fpending bit status, reception frequency and datarate + * Middleware API for geolocation +* Add basic example to provide an easy start point on Nucleo L476 board + +### Changed + +* `smtc_modem_set_crystal_error` renamed to `smtc_modem_set_crystal_error_ppm` and now takes real ppm (previously was ppt) +* `smtc_modem_get_stack_state`: Added a new stack state `SMTC_MODEM_STACK_STATE_TX_WAIT` when stack is between retransmissions +* `smtc_modem_time_trigger_sync_request` function does not take `sync_service` parameter anymore, now it will use the current enabled time synchronization service +* [smtc_modem_hal]: + * `smtc_modem_hal_irq_is_radio_irq_pending()` function has been replaced with `smtc_modem_hal_radio_irq_clear_pending()`. Now modem only asks to clear radio pending irq +* LR1110 driver was renamed to LR11xx driver and now also supports LR1120 radio +* Updated to latest version of SX126x and LR11xx driver +* An `ALMANAC_UPDATE` event is generated if "Almanac force update" is received. +* File upload size can be now up to 8k +* Remove -2dB default tx power offset (now it is 0) and manage EIRP to ERP conversion in LoRaWAN stack +* `smtc_modem_connection_timeout_get_thresholds`: Default internal value of `nb_of_uplinks_before_network_controlled` is now 0 (before was 255). Result: the mobile to static automatic switching service is now deactivated by default. + +### Fixed + +* Corrected `Fcnt_down` msb management +* `smtc_modem_derive_keys` now takes user defined EUIs into account +* AU915: when dwell time was on, the returned max payload sizes were incorrect. This has been corrected +* Corrected bug in `smtc_modem_reset_charge` +* Internal join nonce value is now initialized to FFFFFF to avoid dropping the first join accept message + +## [v2.1.0] 2021-11-03 + +Initial release diff --git a/lr1110/lr1110/seeed/lora_basics_modem/LICENSE.txt b/lr1110/lr1110/seeed/lora_basics_modem/LICENSE.txt new file mode 100644 index 000000000..7ef65c5ec --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/LICENSE.txt @@ -0,0 +1,27 @@ +The Clear BSD License +Copyright Semtech Corporation 2022. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted (subject to the limitations in the disclaimer +below) provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the Semtech corporation nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY +THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "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 SEMTECH CORPORATION 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. diff --git a/lr1110/lr1110/seeed/lora_basics_modem/LICENSES.txt b/lr1110/lr1110/seeed/lora_basics_modem/LICENSES.txt new file mode 100644 index 000000000..ef0aefc5a --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/LICENSES.txt @@ -0,0 +1,136 @@ +Semtech's software made available with this LICENSES.txt file +includes or is provided with certain third-party components that +are subject to separate terms and conditions specified by +applicable third-party licenses (“Third-Party Components”). +These Third-Party Components and applicable licenses are set +forth in this LICENSES.txt file. + +Your access and use of all Third-Party Components are at all times +governed by the applicable third-party licenses. + + +Semtech Corporation +------------------- + +The Clear BSD License +Copyright Semtech Corporation 2022. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted (subject to the limitations in the disclaimer +below) provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the Semtech corporation nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY +THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "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 SEMTECH CORPORATION 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. + + +STMicroelectronics (STM32L4 HAL) +------------------------------------------ + +This software component is licensed by ST under BSD 3-Clause license, +the "License"; You may not use this file except in compliance with the +License. You may obtain a copy of the License at: + opensource.org/licenses/BSD-3-Clause + + +STMicroelectronics (CMSIS Device) +------------------------------ + +This software component is licensed by ST under Apache License, Version 2.0, +the "License"; You may not use this file except in compliance with the +License. You may obtain a copy of the License at: + opensource.org/licenses/Apache-2.0 + + +Arm Limited (CMSIS) +------------------- + +Licensed under the Apache License, Version 2.0 (the License); you may +not use this file except in compliance with the License. +You may obtain a copy of the License at + +www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an AS IS BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +AES - Issue: 09/09/2006 +----------------------- + +Copyright (c) 1998-2008, Brian Gladman, Worcester, UK. All rights reserved. + +LICENSE TERMS + +The redistribution and use of this software (with or without changes) +is allowed without the payment of fees or royalties provided that: + + 1. source code distributions include the above copyright notice, this + list of conditions and the following disclaimer; + + 2. binary distributions include the above copyright notice, this list + of conditions and the following disclaimer in their documentation; + + 3. the name of the copyright holder is not used to endorse products + built using this software without specific written permission. + +DISCLAIMER + +This software is provided 'as is' with no explicit or implied warranties +in respect of its properties, including, but not limited to, correctness +and/or fitness for purpose. + + +CMAC +---- + +Copyright (C) 2009 Lander Casado, Philippas Tsigas + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files +(the "Software"), to deal with 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: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimers. Redistributions in +binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimers in the documentation and/or +other materials provided with the distribution. + +In no event shall the authors or copyright holders be liable for any special, +incidental, indirect or consequential damages of any kind, or any damages +whatsoever resulting from loss of use, data or profits, whether or not +advised of the possibility of damage, and on any theory of liability, +arising out of or in connection with the use or performance of this 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 +CONTRIBUTORS 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 WITH THE SOFTWARE diff --git a/lr1110/lr1110/seeed/lora_basics_modem/Makefile b/lr1110/lr1110/seeed/lora_basics_modem/Makefile new file mode 100644 index 000000000..37f8bec03 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/Makefile @@ -0,0 +1,196 @@ +############################################################################## +# Main makefile for basic_modem +############################################################################## + +-include makefiles/printing.mk + +#----------------------------------------------------------------------------- +# Global configuration options +#----------------------------------------------------------------------------- +# Prefix for all build directories +BUILD_ROOT = build + +# Prefix for all binaries names +TARGET_ROOT = basic_modem + +BYPASS=no + +PERF_TEST?=no + +# Compile with coverage analysis support +COVERAGE ?= no + +# Use multithreaded build (make -j) +MULTITHREAD ?= yes + +# Print each object file size +SIZE ?= no + +# Save memory usage to log file +LOG_MEM ?= yes + +# Tranceiver +RADIO ?= nc + +#MCU - Must be provided by user +MCU_FLAGS =? nc + +#----------------------------------------------------------------------------- +# Internal LBM features management +#----------------------------------------------------------------------------- + +# Middleware advanced access +MIDDLEWARE?= no + +# Crypto management +CRYPTO ?= SOFT + +# D2D feature +ADD_D2D ?= no + +# Multicast feature +ADD_MULTICAST ?= yes + +# Stream feature +ADD_SMTC_STREAM ?= yes + +# File Upload feature +ADD_SMTC_FILE_UPLOAD ?= yes + +# ALCSYNC feature +ADD_SMTC_ALC_SYNC ?= yes + +# Trace prints +MODEM_TRACE ?= yes +MODEM_DEEP_TRACE ?= no + +# GNSS +USE_GNSS ?= yes + + +#----------------------------------------------------------------------------- +# default action: print help +#----------------------------------------------------------------------------- +help: + $(call echo_help_b, "Available TARGETs: sx128x lr1110 lr1120 sx1261 sx1262 sx1268") + $(call echo_help, "") + $(call echo_help_b, "-------------------------------- Clean -------------------------------------") + $(call echo_help, " * make clean_ : clean basic_modem for a given target") + $(call echo_help, " * make clean_all : clean all") + $(call echo_help, "") + $(call echo_help_b, "----------------------------- Compilation ----------------------------------") + $(call echo_help, " * make basic_modem_ MCU_FLAGS=xxx : build basic_modem on a given target with chosen mcu flags") + $(call echo_help, " * MCU_FLAGS are mandatory. Ex for stm32l4:") + $(call echo_help, " * MCU_FLAGS=\"-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard\"") + $(call echo_help, "") + $(call echo_help_b, "---------------------- Optional build parameters ---------------------------") + $(call echo_help, " * REGION=xxx : choose which region should be compiled (default: all)") + $(call echo_help, " * Combinations also work (i.e. REGION=EU_868,US_915 )") + $(call echo_help, " * - AS_923") + $(call echo_help, " * - AU_915") + $(call echo_help, " * - CN_470") + $(call echo_help, " * - CN_470_RP_1_0") + $(call echo_help, " * - EU_868") + $(call echo_help, " * - IN_865") + $(call echo_help, " * - KR_920") + $(call echo_help, " * - RU_864") + $(call echo_help, " * - US_915") + $(call echo_help, " * - WW_2G4 (to be used only for lr1120 and sx128x targets)") + $(call echo_help, " * RP_VERSION=xxx : choose wich regional paramerter version should be compiled (default: RP2_101) ") + $(call echo_help, " * - RP2_101") + $(call echo_help, " * - RP2_103 (LR-FHSS support)") + $(call echo_help, " * CRYPTO=xxx : choose which crypto should be compiled (default: SOFT)") + $(call echo_help, " * - SOFT") + $(call echo_help, " * - LR11XX (only for lr1110 and lr1120 targets)") + $(call echo_help, " * - LR11XX_WITH_CREDENTIALS (only for lr1110 and lr1120 targets)") + $(call echo_help, " * MODEM_TRACE=yes/no : choose to enable or disable modem trace print (default: yes)") + $(call echo_help, " * USE_GNSS=yes/no : only for lr1110 and lr1120 targets: choose to enable or disable use of gnss (default: yes)") + $(call echo_help, " * MIDDLEWARE=yes/no : build target for middleware advanced access (default: no)") + $(call echo_help_b, "-------------------- Optional makefile parameters --------------------------") + $(call echo_help, " * DEBUG=yes/no : Change opt to O0 and add -g* options for debugging (default: no)") + $(call echo_help, " * MULTITHREAD=yes/no : Multithreaded build (default: yes)") + $(call echo_help, " * VERBOSE=yes/no : Increase build verbosity (default: no)") + $(call echo_help, " * SIZE=yes/no : Display size for all objects (default: no)") + + + +#----------------------------------------------------------------------------- +# Makefile include selection +#----------------------------------------------------------------------------- +ifeq ($(RADIO),lr1110) +-include makefiles/lr11xx.mk +endif + +ifeq ($(RADIO),lr1120) +-include makefiles/lr11xx.mk +endif + +ifeq ($(RADIO),sx1261) +-include makefiles/sx126x.mk +endif + +ifeq ($(RADIO),sx1262) +-include makefiles/sx126x.mk +endif + +ifeq ($(RADIO),sx1268) +-include makefiles/sx126x.mk +endif + +ifeq ($(RADIO),sx128x) +-include makefiles/sx128x.mk +endif + +#----------------------------------------------------------------------------- +-include makefiles/common.mk + +.PHONY: clean_all all help +.PHONY: FORCE +FORCE: + +all: basic_modem_sx128x basic_modem_lr1110 basic_modem_lr1120 basic_modem_sx1261 basic_modem_sx1262 + +#----------------------------------------------------------------------------- +# Clean +#----------------------------------------------------------------------------- +clean_all: + -rm -rf $(BUILD_ROOT) + +clean_sx128x: + $(MAKE) clean_target RADIO=sx128x + +clean_lr1110: + $(MAKE) clean_target RADIO=lr1110 + +clean_lr1120: + $(MAKE) clean_target RADIO=lr1120 + +clean_sx1261: + $(MAKE) clean_target RADIO=sx1261 + +clean_sx1262: + $(MAKE) clean_target RADIO=sx1262 + +clean_sx1268: + $(MAKE) clean_target RADIO=sx1268 + +#----------------------------------------------------------------------------- +# Compilation +#----------------------------------------------------------------------------- +basic_modem_sx128x: + $(MAKE) basic_modem RADIO=sx128x $(MTHREAD_FLAG) + +basic_modem_lr1110: + $(MAKE) basic_modem RADIO=lr1110 $(MTHREAD_FLAG) + +basic_modem_lr1120: + $(MAKE) basic_modem RADIO=lr1120 $(MTHREAD_FLAG) + +basic_modem_sx1261: + $(MAKE) basic_modem RADIO=sx1261 $(MTHREAD_FLAG) + +basic_modem_sx1262: + $(MAKE) basic_modem RADIO=sx1262 $(MTHREAD_FLAG) + +basic_modem_sx1268: + $(MAKE) basic_modem RADIO=sx1268 $(MTHREAD_FLAG) diff --git a/lr1110/lr1110/seeed/lora_basics_modem/README.md b/lr1110/lr1110/seeed/lora_basics_modem/README.md new file mode 100644 index 000000000..2ce435857 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/README.md @@ -0,0 +1,85 @@ +# LoRa Basics Modem + +## LoRaWAN parameters + +### LoRaWAN version + +The LoRaWAN version that is currently implemented in LoRa Basics Modem is v1.0.4. + +### LoRaWAN region + +LoRa Basics Modem supports the following LoRaWAN regions: + +* AS_923 (AS923-1, AS923-2, AS923-3) +* AU_915 +* CN_470 +* CN_470_RP_1_0 +* EU_868 +* IN_865 +* KR_920 +* RU_864 +* US_915 + +LoRa Basics Modem supports an emulation of LoRaWAN protocol for the 2.4GHz global ISM band (WW2G4) + +### LoRaWAN regional parameters + +Default regional parameters version supported by LoRa Basics Modem is rp2-1.0.1. It is possible to switch to rp2-1.0.3 at compile time. + +### LoRaWAN class + +LoRa Basics Modem supports the following LoRaWAN classes: + +* Class A +* Class B (with up to 4 multicast sessions) +* Class C (with up to 4 multicast sessions) + +## LoRa Basics Modem services + +LoRa Basics Modem supports the following services: + +* Large files upload +* ROSE Streaming +* Application-Layer Clock synchronization +* Almanac Update + +## LoRa Basics Modem API + +The Application Programming Interface of LoRa Basics Modem is defined in `smtc_modem_api/smtc_modem_api.h` header file. + +## LoRa Basics Modem engine + +LoRa Basics Modem has to be initialized first by calling `smtc_modem_init()`. Then, calling periodically `smtc_modem_run_engine()` is required to make the state machine move forward. + +These functions can be found in `smtc_modem_api/smtc_modem_utilities.h` + +## LoRa Basics Modem HAL + +The Hardware Abstraction Layer of LoRa Basics Modem is defined in the `smtc_modem_hal/smtc_modem_hal.h` header file. Porting LoRa Basics Modem to a new architecture requires one to implement the functions described by the prototypes in it. + +## Transceiver + +LoRa Basics Modem supports the following transceivers: + +* LR1110 with firmware 0x0307. +* LR1120 with firmware 0x0101 +* SX1261 +* SX1262 +* SX1280 +* SX1281 + +## Known Limitations + +* [LFU] In case LoRa Basics Modem is acting in US915 region with datarate DR0, files smaller than 13 bytes are not properly sent and cannot be reconstructed on LoRa Cloud side +* [charge] Values returned by `smtc_modem_get_charge()` for regions CN470 and CN470_RP1 are not accurate +* [charge] Values returned by `smtc_modem_get_charge()` for the LR-FHSS based datarate are not accurate + +## Disclaimer + +This software has been extensively tested when targeting LR1110 / LR1120 / SX1261 / SX1262 / SX1280 / SX1281 for LoRaWAN regions mentioned in [this paragraph](#lorawan-region). For all other combinations of features this software shall be considered an Engineering Sample. + +Modem trace prints can only be used for debug purpose and shall be deactivated for production release. + +### Disclaimer for Engineering Samples + +Information relating to this product and the application or design described herein is believed to be reliable, however such information is provided as a guide only and Semtech assumes no liability for any errors related to the product, documentation, or for the application or design described herein. Semtech reserves the right to make changes to the product or this document at any time without notice. diff --git a/lr1110/lr1110/seeed/lora_basics_modem/lora_basics_modem_version.h b/lr1110/lr1110/seeed/lora_basics_modem/lora_basics_modem_version.h new file mode 100644 index 000000000..7fd50ff23 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/lora_basics_modem_version.h @@ -0,0 +1,73 @@ +/*! + * \file lora_basics_modem_version.h + * + * \brief Defines the Lora Basics Modem firmware version + * + * Revised BSD License + * Copyright Semtech Corporation 2021. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Semtech corporation 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 AND CONTRIBUTORS "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 SEMTECH CORPORATION 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 LORA_BASICS_MODEM_VERSION_H__ +#define LORA_BASICS_MODEM_VERSION_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ----------------------------------------------------------------------------- + * --- DEPENDENCIES ------------------------------------------------------------ + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC MACROS ----------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC CONSTANTS -------------------------------------------------------- + */ + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC TYPES ------------------------------------------------------------ + */ +#define LORA_BASICS_MODEM_FW_VERSION_MAJOR 3 +#define LORA_BASICS_MODEM_FW_VERSION_MINOR 2 +#define LORA_BASICS_MODEM_FW_VERSION_PATCH 4 + +/* + * ----------------------------------------------------------------------------- + * --- PUBLIC FUNCTIONS PROTOTYPES --------------------------------------------- + */ + +#ifdef __cplusplus +} +#endif + +#endif // LORA_BASICS_MODEM_VERSION_H__ + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/lr1110/lr1110/seeed/lora_basics_modem/makefiles/common.mk b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/common.mk new file mode 100644 index 000000000..15bb2c57e --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/common.mk @@ -0,0 +1,551 @@ +############################################################################## +# Common rules and definitions +############################################################################## + +#----------------------------------------------------------------------------- +# Build system binaries +#----------------------------------------------------------------------------- +PREFIX = arm-none-eabi- +# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx) +# either it can be added to the PATH environment variable. +ifdef GCC_PATH +AR = $(GCC_PATH)/$(PREFIX)ar +CC = $(GCC_PATH)/$(PREFIX)gcc +CPP = $(GCC_PATH)/$(PREFIX)g++ +AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp +CP = $(GCC_PATH)/$(PREFIX)objcopy +SZ = $(GCC_PATH)/$(PREFIX)size +GCOV = $(GCC_PATH)/$(PREFIX)gcov +else +AR = $(PREFIX)ar +CC = $(PREFIX)gcc +CPP = $(PREFIX)g++ +AS = $(PREFIX)g++ -x assembler-with-cpp +CP = $(PREFIX)objcopy +SZ = $(PREFIX)size +GCOV = $(PREFIX)gcov +endif +HEX = $(CP) -O ihex +BIN = $(CP) -O binary -S +GCOVR = gcovr + + +#----------------------------------------------------------------------------- +# Board selection +#----------------------------------------------------------------------------- + +-include makefiles/cortex_m4.mk + + +#----------------------------------------------------------------------------- +# Define target build directory +#----------------------------------------------------------------------------- +TARGET_MODEM = $(TARGET_ROOT)_$(TARGET) +BUILD_DIR_MODEM = $(BUILD_ROOT)/$(TARGET) + +#----------------------------------------------------------------------------- +# Multithread is disabled if verbose build, for readable logs +#----------------------------------------------------------------------------- +ifeq ($(VERBOSE),yes) +MULTITHREAD = no +endif +ifeq ($(SIZE),yes) +MULTITHREAD = no +endif + +ifeq ($(MULTITHREAD),no) +MTHREAD_FLAG = +else +MTHREAD_FLAG = -j +endif + +-include makefiles/regions.mk + +#----------------------------------------------------------------------------- +# Update target name wrt. compilation options +#----------------------------------------------------------------------------- +ifdef REGION +TARGET_MODEM := $(TARGET_MODEM)_$(REGION) +endif + +ifeq ($(COVERAGE), RADIO) +BUILD_DIR_MODEM = $(BUILD_ROOT)/$(TARGET)/cov_radio +TARGET_MODEM := $(TARGET_MODEM)_cov_radio +endif +ifeq ($(COVERAGE), MODEM) +BUILD_DIR_MODEM = $(BUILD_ROOT)/$(TARGET)/cov_modem +TARGET_MODEM := $(TARGET_MODEM)_cov_modem +endif + +ifeq ($(RADIO),lr1110) +ifeq ($(CRYPTO),LR11XX) +TARGET_MODEM := $(TARGET_MODEM)_lr11xx_crypto +BUILD_DIR_MODEM := $(BUILD_DIR_MODEM)_lr11xx_crypto +endif # LR11XX +ifeq ($(CRYPTO),LR11XX_WITH_CREDENTIALS) +TARGET_MODEM := $(TARGET_MODEM)_lr11xx_crypto_with_cred +BUILD_DIR_MODEM := $(BUILD_DIR_MODEM)_lr11xx_crypto_with_cred +endif # LR11XX_WITH_CREDENTIALS +endif # lr1110 + +ifeq ($(RADIO),lr1120) +ifeq ($(CRYPTO),LR11XX) +TARGET_MODEM := $(TARGET_MODEM)_lr11xx_crypto +BUILD_DIR_MODEM := $(BUILD_DIR_MODEM)_lr11xx_crypto +endif # LR11XX +ifeq ($(CRYPTO),LR11XX_WITH_CREDENTIALS) +TARGET_MODEM := $(TARGET_MODEM)_lr11xx_crypto_with_cred +BUILD_DIR_MODEM := $(BUILD_DIR_MODEM)_lr11xx_crypto_with_cred +endif # LR11XX_WITH_CREDENTIALS +endif # lr1120 + +ifeq ($(MIDDLEWARE),yes) +TARGET_MODEM := $(TARGET_MODEM)_middleware +BUILD_DIR_MODEM := $(BUILD_DIR_MODEM)_middleware +endif + +ifeq ($(MODEM_TRACE), yes) +TARGET_MODEM := $(TARGET_MODEM)_trace +BUILD_DIR_MODEM := $(BUILD_DIR_MODEM)/trace +else +TARGET_MODEM := $(TARGET_MODEM)_notrace +BUILD_DIR_MODEM := $(BUILD_DIR_MODEM)/notrace +endif + +# Clean up commas +COMMA := , +TARGET_MODEM := $(subst $(COMMA),_,$(TARGET_MODEM)) + +#----------------------------------------------------------------------------- +# Coverage +#----------------------------------------------------------------------------- +COVERAGE_OUTPUT_DIR = $(BUILD_DIR_MODEM)/gcov + +COVERAGE_ARCHIVE = $(BUILD_ROOT)/$(TARGET_MODEM)_gcov.tar.gz + +#COVERAGE_PREFIX_STRIP = $(words $(subst /, ,$(realpath $(shell pwd)))) +COVERAGE_PREFIX_STRIP = $(words $(subst /, ,$(realpath $(COVERAGE_OUTPUT_DIR)))) + +# Compilation flags needed for coverage support +COVERAGE_CFLAGS = -fprofile-arcs -ftest-coverage + +# When compiling with coverage we should disable optimizations +ifneq ($(COVERAGE),no) +#OPT = -O0 -g +DEBUG = yes +COVERAGE_LDFLAGS = -fprofile-arcs +LIBS += -lgcov +else +#OPT = -Os -g +COVERAGE_LDFLAGS = +endif + +#----------------------------------------------------------------------------- +# Debug +#----------------------------------------------------------------------------- +ifeq ($(DEBUG),yes) +OPT = -O0 -ggdb3 -gdwarf +else +OPT = -Os +endif + +#----------------------------------------------------------------------------- +# Dump memory usage to a log file +#----------------------------------------------------------------------------- +ifeq ($(LOG_MEM), yes) +MEMLOG_FILE := $(BUILD_DIR_MODEM)/mem_usage.log +MEMLOG = | tee $(MEMLOG_FILE) +else +MEMLOG = +endif + +#----------------------------------------------------------------------------- +# Bypass LoRaWAN network and use testbench to uplink/downlink frames +#----------------------------------------------------------------------------- +ifeq ($(BYPASS),yes) +BYPASS_FLAGS := '-DLORAWAN_BYPASS_ENABLED' +else +BYPASS_FLAGS := +endif + +#----------------------------------------------------------------------------- +# Compilation flags +#----------------------------------------------------------------------------- +# Basic compilation flags +WFLAG += \ + -Wall \ + -Wextra \ + -Wno-unused-parameter \ + -Wpedantic \ + -fomit-frame-pointer \ + -mabi=aapcs \ + -fno-unroll-loops \ + -ffast-math \ + -ftree-vectorize \ + $(BYPASS_FLAGS) + +# Allow linker to not link unused functions +WFLAG += \ + -ffunction-sections \ + -fdata-sections + +# Generate .su files for stack use analysis +WFLAG += -fstack-usage + +# Change symbols path to please debug tools +CURRENT_DIR := $(shell basename $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))) +WFLAG += -ffile-prefix-map==$(CURRENT_DIR)/ + +#Link-time optimization +#WFLAG += --lto + +# AS defines +AS_DEFS = + +# Assembly flags +ASFLAGS += -fno-builtin $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) $(WFLAG) + +COMMON_C_DEFS += \ + -DGIT_VERSION=\"$(GIT_VERSION)\" \ + -DGIT_COMMIT=\"$(GIT_COMMIT)\" \ + -DGIT_DATE=\"$(GIT_DATE)\" \ + -DBUILD_DATE=\"$(BUILD_DATE)\" + +ifeq ($(MODEM_TRACE),yes) +COMMON_C_DEFS += \ + -DMODEM_HAL_DBG_TRACE=1 +ifeq ($(MODEM_DEEP_TRACE),yes) +COMMON_C_DEFS += \ + -DMODEM_HAL_DEEP_DBG_TRACE=1 +endif +ifeq ($(MODEM_DEEP_TRACE),no) +COMMON_C_DEFS += \ + -DMODEM_HAL_DEEP_DBG_TRACE=0 +endif +endif + +ifeq ($(MODEM_TRACE),no) +COMMON_C_DEFS += \ + -DMODEM_HAL_DBG_TRACE=0 +endif + +ifeq ($(PERF_TEST),yes) +COMMON_C_DEFS += \ + -DPERF_TEST_ENABLED +endif + +ifeq ($(MIDDLEWARE),yes) +COMMON_C_DEFS += \ + -DTASK_EXTENDED_1 \ + -DTASK_EXTENDED_2 \ + -DENABLE_FAST_CLOCK_SYNC +endif + +ifeq ($(ADD_D2D),yes) +COMMON_C_DEFS += \ + -DSMTC_D2D +endif + +ifeq ($(ADD_MULTICAST),yes) +COMMON_C_DEFS += \ + -DSMTC_MULTICAST +endif + +ifeq ($(ADD_SMTC_STREAM),yes) +COMMON_C_DEFS += \ + -DADD_SMTC_STREAM +endif + +ifeq ($(ADD_SMTC_FILE_UPLOAD),yes) +COMMON_C_DEFS += \ + -DADD_SMTC_FILE_UPLOAD +endif + +ifeq ($(ADD_SMTC_ALC_SYNC),yes) +COMMON_C_DEFS += \ + -DADD_SMTC_ALC_SYNC +endif + + +CFLAGS += -fno-builtin $(MCU_FLAGS) $(BOARD_C_DEFS) $(COMMON_C_DEFS) $(MODEM_C_DEFS) $(BOARD_C_INCLUDES) $(COMMON_C_INCLUDES) $(MODEM_C_INCLUDES) $(OPT) $(WFLAG) -MMD -MP -MF"$(@:%.o=%.d)" +CFLAGS += -falign-functions=4 +CFLAGS += -std=c17 + +ifneq ($(COVERAGE),no) +CFLAGS += -DCOVERAGE_ENABLED +endif +#----------------------------------------------------------------------------- +# Link flags +#----------------------------------------------------------------------------- +# libraries +LIBS += -lstdc++ -lsupc++ -lm -lc -lnosys + +#----------------------------------------------------------------------------- +# Common sources +#----------------------------------------------------------------------------- +SMTC_MODEM_CORE_C_SOURCES += \ + smtc_modem_core/lorawan_api/lorawan_api.c \ + smtc_modem_core/device_management/dm_downlink.c \ + smtc_modem_core/device_management/modem_context.c\ + smtc_modem_core/modem_core/smtc_modem.c\ + smtc_modem_core/modem_core/smtc_modem_test.c\ + smtc_modem_core/modem_services/fifo_ctrl.c\ + smtc_modem_core/modem_services/modem_utilities.c \ + smtc_modem_core/modem_services/smtc_modem_services_hal.c\ + smtc_modem_core/modem_services/lorawan_certification.c\ + smtc_modem_core/modem_supervisor/modem_supervisor.c + +ifeq ($(ADD_SMTC_ALC_SYNC),yes) +SMTC_MODEM_CORE_C_SOURCES += \ + smtc_modem_core/modem_services/smtc_clock_sync.c +endif + +ifeq ($(ADD_SMTC_STREAM),yes) +SMTC_MODEM_SERVICES_C_SOURCES += \ + smtc_modem_core/smtc_modem_services/src/stream/stream.c\ + smtc_modem_core/smtc_modem_services/src/stream/rose.c +endif + +ifeq ($(ADD_SMTC_FILE_UPLOAD),yes) +SMTC_MODEM_SERVICES_C_SOURCES += \ + smtc_modem_core/smtc_modem_services/src/file_upload/file_upload.c +endif + +ifeq ($(ADD_SMTC_ALC_SYNC),yes) +SMTC_MODEM_SERVICES_C_SOURCES += \ + smtc_modem_core/smtc_modem_services/src/alc_sync/alc_sync.c +endif + + +LR1MAC_C_SOURCES += \ + smtc_modem_core/lr1mac/src/lr1_stack_mac_layer.c\ + smtc_modem_core/lr1mac/src/lr1mac_core.c\ + smtc_modem_core/lr1mac/src/lr1mac_utilities.c\ + smtc_modem_core/lr1mac/src/smtc_real/src/smtc_real.c\ + smtc_modem_core/lr1mac/src/services/smtc_duty_cycle.c\ + smtc_modem_core/lr1mac/src/services/smtc_lbt.c\ + smtc_modem_core/lr1mac/src/lr1mac_class_c/lr1mac_class_c.c\ + smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_beacon_sniff.c\ + smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_ping_slot.c + +ifeq ($(ADD_D2D),yes) +LR1MAC_C_SOURCES += \ + smtc_modem_core/lr1mac/src/lr1mac_class_b/smtc_d2d.c +endif + +ifeq ($(ADD_MULTICAST),yes) +LR1MAC_C_SOURCES += \ + smtc_modem_core/lr1mac/src/services/smtc_multicast.c +endif + +SMTC_MODEM_CRYPTO_C_SOURCES += \ + smtc_modem_core/smtc_modem_crypto/smtc_modem_crypto.c + +RADIO_PLANNER_C_SOURCES += \ + smtc_modem_core/radio_planner/src/radio_planner.c\ + smtc_modem_core/radio_planner/src/radio_planner_hal.c + +COMMON_C_INCLUDES += \ + -I.\ + -Ismtc_modem_api\ + -Ismtc_modem_core\ + -Ismtc_modem_core/modem_config\ + -Ismtc_modem_core/modem_core\ + -Ismtc_modem_core/modem_supervisor\ + -Ismtc_modem_core/device_management\ + -Ismtc_modem_core/modem_services\ + -Ismtc_modem_core/lorawan_api\ + -Ismtc_modem_core/smtc_modem_services/headers\ + -Ismtc_modem_core/smtc_modem_services/src\ + -Ismtc_modem_core/smtc_modem_services/src/stream\ + -Ismtc_modem_core/smtc_modem_services/src/file_upload\ + -Ismtc_modem_core/smtc_modem_services/src/alc_sync\ + -Ismtc_modem_core/smtc_modem_services\ + -Ismtc_modem_core/smtc_ral/src\ + -Ismtc_modem_core/smtc_ralf/src\ + -Ismtc_modem_core/lr1mac\ + -Ismtc_modem_core/lr1mac/src\ + -Ismtc_modem_core/lr1mac/src/services\ + -Ismtc_modem_core/lr1mac/src/lr1mac_class_c\ + -Ismtc_modem_core/lr1mac/src/lr1mac_class_b\ + -Ismtc_modem_core/radio_planner/src\ + -Ismtc_modem_core/smtc_modem_crypto\ + -Ismtc_modem_core/smtc_modem_crypto/smtc_secure_element\ + -Ismtc_modem_core/lorawan_api\ + -Ismtc_modem_core/lr1mac/src/smtc_real/src\ + -Ismtc_modem_hal + + + +#----------------------------------------------------------------------------- +# Gather everything +#----------------------------------------------------------------------------- +ifeq ($(COVERAGE),RADIO) +C_SOURCES_COVERAGE = \ + $(RADIO_DRIVER_C_SOURCES) \ + $(SMTC_RAL_C_SOURCES) \ + $(SMTC_RALF_C_SOURCES) \ + $(LR1MAC_C_SOURCES) \ + $(RADIO_PLANNER_C_SOURCES) + +C_SOURCES = \ + $(SMTC_MODEM_CORE_C_SOURCES) \ + $(SMTC_MODEM_SERVICES_C_SOURCES) \ + $(SMTC_MODEM_CRYPTO_C_SOURCES) +endif +ifeq ($(COVERAGE),MODEM) +C_SOURCES_COVERAGE = \ + $(SMTC_MODEM_CORE_C_SOURCES) \ + $(SMTC_MODEM_SERVICES_C_SOURCES) \ + $(SMTC_MODEM_CRYPTO_C_SOURCES) + +C_SOURCES = \ + $(RADIO_DRIVER_C_SOURCES) \ + $(SMTC_RAL_C_SOURCES) \ + $(SMTC_RALF_C_SOURCES) \ + $(LR1MAC_C_SOURCES) \ + $(RADIO_PLANNER_C_SOURCES) +endif + +ifeq ($(COVERAGE),no) +C_SOURCES_COVERAGE = +C_SOURCES = \ + $(RADIO_DRIVER_C_SOURCES) \ + $(SMTC_RAL_C_SOURCES) \ + $(SMTC_RALF_C_SOURCES) \ + $(RADIO_PLANNER_C_SOURCES) \ + $(SMTC_MODEM_CORE_C_SOURCES) \ + $(SMTC_MODEM_SERVICES_C_SOURCES) \ + $(SMTC_MODEM_CRYPTO_C_SOURCES) \ + $(LR1MAC_C_SOURCES) +endif + +ASM_SOURCES = $(BOARD_ASM_SOURCES) + +vpath %.c $(sort $(dir $(C_SOURCES_COVERAGE))) +vpath %.c $(sort $(dir $(C_SOURCES))) +vpath %.cpp $(sort $(dir $(CPP_SOURCES))) + +#----------------------------------------------------------------------------- +basic_modem: +ifeq ($(RADIO),nc) + $(call echo_error,"No radio selected! Please specified the target radio using RADIO=radio_name option") +else + $(MAKE) basic_modem_build +endif + +.PHONY: basic_modem_build + +basic_modem_build: $(BUILD_ROOT)/$(TARGET_MODEM).a + $(SILENT) cp $< $(BUILD_ROOT)/$(TARGET_ROOT).a +ifneq ($(COVERAGE),no) + $(MAKE) $(COVERAGE_ARCHIVE) +endif + $(SILENT) rm -rf $(BUILD_ROOT)/latest + $(SILENT) ln -s $(realpath $(BUILD_DIR_MODEM)) $(BUILD_ROOT)/latest + $(call success,$@ $<) + + +#----------------------------------------------------------------------------- +# list of C objects +COVERAGE_OBJECTS = $(addprefix $(COVERAGE_OUTPUT_DIR)/, $(notdir $(C_SOURCES_COVERAGE:.c=.o))) +vpath %.c $(sort $(dir $(C_SOURCES_COVERAGE))) + +OBJECTS = $(addprefix $(BUILD_DIR_MODEM)/,$(notdir $(C_SOURCES:.c=.o))) +vpath %.c $(sort $(dir $(C_SOURCES))) + +# list of ASM program objects +OBJECTS += $(addprefix $(BUILD_DIR_MODEM)/,$(notdir $(ASM_SOURCES:.s=.o))) +vpath %.s $(sort $(dir $(ASM_SOURCES))) + +# Mark .o intermediate files as secondary target (or precious) +# Without this, $(MAKE) will remove intermediate files and slow down rebuilds +.SECONDARY: $(COVERAGE_OBJECTS) +.SECONDARY: $(OBJECTS) + +$(COVERAGE_OUTPUT_DIR)/%.o: %.c Makefile | $(COVERAGE_OUTPUT_DIR) + $(call build,'CC-GCOV',$<) + $(SILENT)$(CC) -c $(CFLAGS) $(COVERAGE_CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR_MODEM)/$(notdir $(<:.c=.lst)) $< -o $@ +ifeq ($(SIZE),yes) + $(SZ) $@ +endif + +$(BUILD_DIR_MODEM)/%.o: %.c Makefile | $(BUILD_DIR_MODEM) + $(call build,'CC',$<) + $(SILENT)$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR_MODEM)/$(notdir $(<:.c=.lst)) $< -o $@ +ifeq ($(SIZE),yes) + $(SZ) $@ +endif + +$(BUILD_DIR_MODEM)/%.o: %.s Makefile | $(BUILD_DIR_MODEM) + $(call build,'AS',$<) + $(SILENT)$(AS) -c $(ASFLAGS) $< -o $@ +ifeq ($(SIZE),yes) + $(SZ) $@ +endif + + +$(BUILD_DIR_MODEM)/%.a: $(OBJECTS) $(COVERAGE_OBJECTS) Makefile | $(BUILD_DIR_MODEM) + $(call build,'LIB',$@) + $(SILENT)$(AR) rcs $@ $(OBJECTS) $(COVERAGE_OBJECTS) + $(SZ) -t $@ + +$(BUILD_ROOT)/$(TARGET_MODEM).a: $(BUILD_DIR_MODEM)/$(TARGET_MODEM).a + $(call build,'LIB',$@) + $(SILENT) cp $< $@ + +$(BUILD_DIR_MODEM): + $(SILENT)mkdir -p $@ + +$(COVERAGE_ARCHIVE): $(COVERAGE_OBJECTS) + $(call build,'TAR',$@) + $(SILENT) tar czf $@ -C $(BUILD_DIR_MODEM) gcov + +$(COVERAGE_OUTPUT_DIR): + $(SILENT)mkdir -p $@ + +#----------------------------------------------------------------------------- +# Debug print rules +#----------------------------------------------------------------------------- +debug_region: + $(call echo,"Region $(REGION)") + $(call echo," REGION_AS_923 $(REGION_AS_923)") + $(call echo," REGION_AU_915 $(REGION_AU_915)") + $(call echo," REGION_CN_470 $(REGION_CN_470)") + $(call echo," REGION_CN_470_RP_1_0 $(REGION_CN_470_RP_1_0)") + $(call echo," REGION_EU_868 $(REGION_EU_868)") + $(call echo," REGION_IN_865 $(REGION_IN_865)") + $(call echo," REGION_KR_920 $(REGION_KR_920)") + $(call echo," REGION_RU_864 $(REGION_RU_864)") + $(call echo," REGION_US_915 $(REGION_US_915)") + +debug_target: + $(call echo,"Target $(TARGET)") + $(call echo,"Build directory $(BUILD_DIR_MODEM)") + $(call echo,"Binary $(TARGET_MODEM)") + +debug_sources: + $(call echo,"RADIO_DRIVER_C_SOURCES $(RADIO_DRIVER_C_SOURCES)") + $(call echo,"SMTC_RAL_C_SOURCES $(SMTC_RAL_C_SOURCES)") + $(call echo,"SMTC_RALF_C_SOURCES $(SMTC_RALF_C_SOURCES)") + $(call echo,"RADIO_HAL_C_SOURCES $(RADIO_HAL_C_SOURCES)") + $(call echo,"RADIO_PLANNER_C_SOURCES $(RADIO_PLANNER_C_SOURCES)") + $(call echo,"SMTC_MODEM_CORE_C_SOURCES $(SMTC_MODEM_CORE_C_SOURCES)") + $(call echo,"SMTC_MODEM_SERVICES_C_SOURCES $(SMTC_MODEM_SERVICES_C_SOURCES)") + $(call echo,"SMTC_MODEM_CRYPTO_C_SOURCES $(SMTC_MODEM_CRYPTO_C_SOURCES)") + $(call echo,"LR1MAC_C_SOURCES $(LR1MAC_C_SOURCES)") + $(call echo,"COMMON_C_INCLUDES $(COMMON_C_INCLUDES)") + +debug_flags: + $(call echo,"MODEM_C_DEFS $(MODEM_C_DEFS)") + +debug: debug_target debug_region debug_sources debug_flags + +#----------------------------------------------------------------------------- +# Clean +#----------------------------------------------------------------------------- +clean_target: + -rm -fR $(BUILD_DIR_MODEM) + -rm -fR $(BUILD_ROOT)/$(TARGET_MODEM).a + -rm -fR $(BUILD_ROOT)/$(TARGET_ROOT).a + -rm -fR *_gcov.tar.gz diff --git a/lr1110/lr1110/seeed/lora_basics_modem/makefiles/lr11xx.mk b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/lr11xx.mk new file mode 100644 index 000000000..23a702f04 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/lr11xx.mk @@ -0,0 +1,107 @@ +############################################################################## +# Definitions for the LR11XX tranceiver +############################################################################## +ifeq ($(RADIO),lr1110) +TARGET = lr1110 +endif +ifeq ($(RADIO),lr1120) +TARGET = lr1120 +endif + + +#----------------------------------------------------------------------------- +# Common sources +#----------------------------------------------------------------------------- + +ifeq ($(USE_GNSS),yes) +SMTC_MODEM_SERVICES_C_SOURCES += \ + smtc_modem_core/smtc_modem_services/src/almanac_update/almanac_update.c +endif + +RADIO_DRIVER_C_SOURCES += \ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_bootloader.c\ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_crypto_engine.c\ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_driver_version.c\ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_radio.c\ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_regmem.c\ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_system.c\ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_wifi.c\ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_lr_fhss.c +ifeq ($(USE_GNSS),yes) +RADIO_DRIVER_C_SOURCES += \ + smtc_modem_core/radio_drivers/lr11xx_driver/src/lr11xx_gnss.c +endif + +SMTC_RAL_C_SOURCES += \ + smtc_modem_core/smtc_ral/src/ral_lr11xx.c + +SMTC_RALF_C_SOURCES += \ + smtc_modem_core/smtc_ralf/src/ralf_lr11xx.c + + +ifeq ($(CRYPTO),LR11XX) +SMTC_MODEM_CRYPTO_C_SOURCES += \ + smtc_modem_core/smtc_modem_crypto/lr11xx_crypto_engine/lr11xx_ce.c +endif # LR11XX + +ifeq ($(CRYPTO),LR11XX_WITH_CREDENTIALS) +SMTC_MODEM_CRYPTO_C_SOURCES += \ + smtc_modem_core/smtc_modem_crypto/lr11xx_crypto_engine/lr11xx_ce.c +endif # LR11XX_WITH_CREDENTIALS + +ifeq ($(CRYPTO),SOFT) +SMTC_MODEM_CRYPTO_C_SOURCES += \ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/aes.c\ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/cmac.c\ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/soft_se.c +endif # soft_crypto + +#----------------------------------------------------------------------------- +# Includes +#----------------------------------------------------------------------------- +MODEM_C_INCLUDES = \ + -Ismtc_modem_core/radio_drivers/lr11xx_driver/src + +ifeq ($(CRYPTO),LR11XX) +MODEM_C_INCLUDES += \ + -Ismtc_modem_core/smtc_modem_crypto/lr11xx_crypto_engine +endif # LR11XX + +ifeq ($(CRYPTO),LR11XX_WITH_CREDENTIALS) +MODEM_C_INCLUDES += \ + -Ismtc_modem_core/smtc_modem_crypto/lr11xx_crypto_engine +endif # LR11XX_WITH_CREDENTIALS + +ifeq ($(CRYPTO),SOFT) +MODEM_C_INCLUDES += \ + -Ismtc_modem_core/smtc_modem_crypto/soft_secure_element +endif # soft_crypto + +#----------------------------------------------------------------------------- +# Radio specific compilation flags +#----------------------------------------------------------------------------- +MODEM_C_DEFS += \ + -DLR11XX\ + -DLR11XX_TRANSCEIVER + +ifeq ($(RADIO),lr1120) +MODEM_C_DEFS += \ + -DLR1120 +endif + +ifeq ($(CRYPTO),LR11XX) +MODEM_C_DEFS += \ + -DUSE_LR11XX_CE +endif # LR11XX + +ifeq ($(CRYPTO),LR11XX_WITH_CREDENTIALS) +MODEM_C_DEFS += \ + -DUSE_LR11XX_CE \ + -DUSE_PRE_PROVISIONED_FEATURES +endif # LR11XX_WITH_CREDENTIALS + +# GNSS USE +ifeq ($(USE_GNSS),yes) +MODEM_C_DEFS += \ + -DENABLE_MODEM_GNSS_FEATURE +endif diff --git a/lr1110/lr1110/seeed/lora_basics_modem/makefiles/printing.mk b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/printing.mk new file mode 100644 index 000000000..770a49c8c --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/printing.mk @@ -0,0 +1,58 @@ +###################################### +# Some pretty colors and printing +###################################### + +###################################### +# Verbose build +###################################### +ifdef VERBOSE +SILENT = +else +SILENT = @ +endif + +# Shell colors +BOLD_RED := '\033[1;31m' +BOLD_CYAN := '\033[1;36m' +BOLD_GREEN := '\033[1;32m' +BOLD_PURPLE := '\033[1;35m' +BOLD_YELLOW:= '\033[1;33m' +IPURPLE:= '\033[0;95m' +BIPURPLE:= '\033[1;95m' + +NC := '\033[0m' # no color +ECHO = @echo -e $(LIGHT_CYAN) +ECHO_OK = @echo -e $(LIGHT_GREEN) +ECHO_CMD = /bin/echo + +define echo + @$(ECHO_CMD) -e $(BOLD_CYAN)$(1)$(NC) +endef + +define echo_error + @$(ECHO_CMD) -e $(BOLD_RED)'! '$(1)$(NC) +endef + +define build + @echo -e $(BOLD_CYAN)'['$(1)'] Building '$(2)$(NC) +endef + +define success + @echo -e $(BOLD_GREEN)'+ '$(1)' [SUCCESS]'$(NC) +endef + +define warn + @$(ECHO_CMD) -e $(BOLD_PURPLE)'! '$(1)$(NC) +endef + +define echo_help + @$(ECHO_CMD) -e $(IPURPLE)'| '$(1)$(NC) +endef + +define echo_help_b + @$(ECHO_CMD) -e $(BIPURPLE)' '$(1)$(NC) +endef + +define echo_build + @$(ECHO_CMD) -e $(BOLD_YELLOW)$(1)$(NC) +endef diff --git a/lr1110/lr1110/seeed/lora_basics_modem/makefiles/regions.mk b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/regions.mk new file mode 100644 index 000000000..b4db64eac --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/regions.mk @@ -0,0 +1,137 @@ + +#----------------------------------------------------------------------------- +# Region selection. They are all enabled by default, unless one is selected via the REGION flag +#----------------------------------------------------------------------------- +REGION_AS_923 = no +REGION_AU_915 = no +REGION_CN_470 = no +REGION_CN_470_RP_1_0 = no +REGION_EU_868 = no +REGION_IN_865 = no +REGION_KR_920 = no +REGION_RU_864 = no +REGION_US_915 = no +REGION_WW_2G4 = no + +ifndef REGION +ifneq ($(RADIO),sx128x) +REGION_AS_923 = yes +REGION_AU_915 = yes +REGION_CN_470 = yes +REGION_CN_470_RP_1_0 = yes +REGION_EU_868 = yes +REGION_IN_865 = yes +REGION_KR_920 = yes +REGION_RU_864 = yes +REGION_US_915 = yes +else +REGION_WW_2G4 = yes +endif +ifeq ($(RADIO),lr1120) +REGION_WW_2G4 = yes +endif +endif # REGION + +#----------------------------------------------------------------------------- +# Regional Parameter Version +#----------------------------------------------------------------------------- + +ifndef RP_VERSION +MODEM_C_DEFS += -DRP2_101 +endif + +ifeq ($(RP_VERSION),RP2_103) +MODEM_C_DEFS += -DRP2_103 +endif + +ifeq ($(RP_VERSION),RP2_101) +MODEM_C_DEFS += -DRP2_101 +endif + +#----------------------------------------------------------------------------- +# Extract all comma-separated regions into a list +#----------------------------------------------------------------------------- +COMMA := , +REGION_LIST = $(subst $(COMMA), ,$(REGION)) + +#----------------------------------------------------------------------------- +# Manual selection of one region +#----------------------------------------------------------------------------- +ifneq ($(filter AS_923,$(REGION_LIST)),) +REGION_AS_923 = yes +endif +ifneq ($(filter AU_915,$(REGION_LIST)),) +REGION_AU_915 = yes +endif +ifneq ($(filter CN_470,$(REGION_LIST)),) +REGION_CN_470 = yes +endif +ifneq ($(filter CN_470_RP_1_0,$(REGION_LIST)),) +REGION_CN_470_RP_1_0 = yes +endif +ifneq ($(filter EU_868,$(REGION_LIST)),) +REGION_EU_868 = yes +endif +ifneq ($(filter IN_865,$(REGION_LIST)),) +REGION_IN_865 = yes +endif +ifneq ($(filter KR_920,$(REGION_LIST)),) +REGION_KR_920 = yes +endif +ifneq ($(filter RU_864,$(REGION_LIST)),) +REGION_RU_864 = yes +endif +ifneq ($(filter US_915,$(REGION_LIST)),) +REGION_US_915 = yes +endif +ifneq ($(filter WW_2G4,$(REGION_LIST)),) +REGION_WW_2G4 = yes +endif + +#----------------------------------------------------------------------------- +# Region sources and defines +#----------------------------------------------------------------------------- +ifeq ($(REGION_AS_923), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_as_923.c +MODEM_C_DEFS += -DREGION_AS_923 +endif +ifeq ($(REGION_AU_915), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_au_915.c +MODEM_C_DEFS += -DREGION_AU_915 +endif +ifeq ($(REGION_CN_470), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470.c +MODEM_C_DEFS += -DREGION_CN_470 +endif +ifeq ($(REGION_CN_470_RP_1_0), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470_rp_1_0.c +MODEM_C_DEFS += -DREGION_CN_470_RP_1_0 +endif +ifeq ($(REGION_EU_868), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_eu_868.c +MODEM_C_DEFS += -DREGION_EU_868 +endif +ifeq ($(REGION_IN_865), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_in_865.c +MODEM_C_DEFS += -DREGION_IN_865 +endif +ifeq ($(REGION_KR_920), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_kr_920.c +MODEM_C_DEFS += -DREGION_KR_920 +endif +ifeq ($(REGION_RU_864), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_ru_864.c +MODEM_C_DEFS += -DREGION_RU_864 +endif +ifeq ($(REGION_US_915), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_us_915.c +MODEM_C_DEFS += -DREGION_US_915 +endif +ifeq ($(REGION_WW_2G4), yes) +LR1MAC_C_SOURCES += smtc_modem_core/lr1mac/src/smtc_real/src/region_ww2g4.c +MODEM_C_DEFS += \ + -DREGION_WW2G4\ + -DWW2G4_SINGLE_DATARATE +endif + + diff --git a/lr1110/lr1110/seeed/lora_basics_modem/makefiles/sx126x.mk b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/sx126x.mk new file mode 100644 index 000000000..a82cd46b7 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/sx126x.mk @@ -0,0 +1,55 @@ +############################################################################## +# Definitions for the SX128x tranceiver +############################################################################## +ifeq ($(RADIO),sx1261) +TARGET = sx1261 +endif +ifeq ($(RADIO),sx1262) +TARGET = sx1262 +endif +ifeq ($(RADIO),sx1268) +TARGET = sx1268 +endif + +RADIO_DRIVER_C_SOURCES += \ + smtc_modem_core/radio_drivers/sx126x_driver/src/sx126x.c\ + smtc_modem_core/radio_drivers/sx126x_driver/src/sx126x_lr_fhss.c\ + smtc_modem_core/radio_drivers/sx126x_driver/src/lr_fhss_mac.c + +SMTC_RAL_C_SOURCES += \ + smtc_modem_core/smtc_ral/src/ral_sx126x.c + +SMTC_RALF_C_SOURCES += \ + smtc_modem_core/smtc_ralf/src/ralf_sx126x.c + +SMTC_MODEM_CRYPTO_C_SOURCES += \ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/aes.c\ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/cmac.c\ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/soft_se.c + +#----------------------------------------------------------------------------- +# Includes +#----------------------------------------------------------------------------- +MODEM_C_INCLUDES = \ + -Ismtc_modem_core/radio_drivers/sx126x_driver/src\ + -Ismtc_modem_core/smtc_modem_crypto/soft_secure_element + +#----------------------------------------------------------------------------- +# Region +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Radio specific compilation flags +#----------------------------------------------------------------------------- +MODEM_C_DEFS += \ + -DSX126X + +ifeq ($(RADIO),sx1262) +MODEM_C_DEFS += \ + -DSX1262 +endif + +ifeq ($(RADIO),sx1268) +MODEM_C_DEFS += \ + -DSX1268 +endif diff --git a/lr1110/lr1110/seeed/lora_basics_modem/makefiles/sx128x.mk b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/sx128x.mk new file mode 100644 index 000000000..3ef5fb754 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/makefiles/sx128x.mk @@ -0,0 +1,34 @@ +############################################################################## +# Definitions for the SX128x tranceiver +############################################################################## +TARGET = sx128x + +RADIO_DRIVER_C_SOURCES += \ + smtc_modem_core/radio_drivers/sx128x_driver/src/sx128x.c + +SMTC_RAL_C_SOURCES += \ + smtc_modem_core/smtc_ral/src/ral_sx128x.c + +SMTC_RALF_C_SOURCES += \ + smtc_modem_core/smtc_ralf/src/ralf_sx128x.c + +LR1MAC_C_SOURCES += \ + smtc_modem_core/lr1mac/src/smtc_real/src/region_ww2g4.c + +SMTC_MODEM_CRYPTO_C_SOURCES += \ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/aes.c\ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/cmac.c\ + smtc_modem_core/smtc_modem_crypto/soft_secure_element/soft_se.c + +#----------------------------------------------------------------------------- +# Includes +#----------------------------------------------------------------------------- +MODEM_C_INCLUDES = \ + -Ismtc_modem_core/radio_drivers/sx128x_driver/src\ + -Ismtc_modem_core/smtc_modem_crypto/soft_secure_element + +#----------------------------------------------------------------------------- +# Radio specific compilation flags +#----------------------------------------------------------------------------- +MODEM_C_DEFS += \ + -DSX128X diff --git a/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/CHANGELOG.md b/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/CHANGELOG.md new file mode 100644 index 000000000..d70a8f42c --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/CHANGELOG.md @@ -0,0 +1,36 @@ +# Lora Basics Modem API changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v3.0.0] Unreleased + +### Added + +* [class_b] `smtc_modem_lorawan_class_b_request_ping_slot_info()` function +* [class_b] `smtc_modem_class_b_set_ping_slot_periodicity()` function +* [class_b] `smtc_modem_class_b_get_ping_slot_periodicity()` function +* [multicast] `smtc_modem_multicast_class_b_start_session()` function +* [multicast] `smtc_modem_multicast_class_b_get_session_status()` function +* [multicast] `smtc_modem_multicast_class_b_stop_all_sessions()` function +* [LoRaWAN] `smtc_modem_lorawan_get_lost_connection_counter()` function + +### Changed + +* [multicast] `smtc_modem_multicast_start_session()` function is renamed `smtc_modem_multicast_class_c_start_session()` +* [multicast] `smtc_modem_multicast_get_session_status()` function is renamed `smtc_modem_multicast_class_c_get_session_status()` +* [multicast] `smtc_modem_multicast_stop_session()` function is renamed `smtc_modem_multicast_class_c_stop_session()` +* [multicast] `smtc_modem_multicast_stop_all_sessions()` function is renamed `smtc_modem_multicast_class_c_stop_all_sessions()` +* [time_sync] `smtc_modem_time_trigger_sync_request` function does not take `sync_service` parameter anymore and will use the current enabled time synchronization service + +### Fixed + +### Removed + + +## [v2.1.0] 2021-09-24 + +### Added + +* [all] Initial version diff --git a/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/Makefile b/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/Makefile new file mode 100644 index 000000000..7d84aa516 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/Makefile @@ -0,0 +1,7 @@ +all: docs + +docs: *.h ./doxygen.config + @doxygen ./doxygen.config + +clean: + rm -rf build_doc \ No newline at end of file diff --git a/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/doxygen.config b/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/doxygen.config new file mode 100644 index 000000000..85fc6dc60 --- /dev/null +++ b/lr1110/lr1110/seeed/lora_basics_modem/smtc_modem_api/doxygen.config @@ -0,0 +1,2494 @@ +# Doxyfile 1.8.13 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "SMTC Modem API" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Generic Modem API description" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = build_doc + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ./ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /

Ou%VZ`)qf%g9;WN<=gk~=@_o3+mqKHvdZLc5_ufA5fKrc z?{3Z#6x)SiYv)P^s7lmJGiDr16x{{ztTN2qR(lA(`|ze4NKXBLpj3rmyUj;31yHAS z?M^J~oe?Tj8ePPVxQfpoU!Oj7B`Aj2(F|cUGthlG_Ve=_&XL<2NcRN*!P)s-in+0U z&*K1u!+tvm-|gz+qTv2*2nG3>=~&_Sp$1%sj=gbU<9#;rvT-_BS62_sm6b<;_|do= z0!ux4@`Pa)D25%u_^EtuvEvMC?N+=6(lmhq0bH)fG*U(e27xk%hllEkt9cF3mRYOw zx2M&B04C*i4ma6-4{V$QZx4!y7p;sSKfR)Cpzv!`z}wU{HDpGk-`cQzK0e1SR%n%? zw-?zUW?QLBK0Y~F(STCUOX7jMTugz`$bTa;=X5@e}kW>Hpi$6N|#G^kfOAhr~TU*aFBUh?S7eD#a z5H24*H66+Qa-5Km0PNTP(NSSjKx*Y_M*E{3b1L!Mc-u zW!kea3pk_09*O?LVkk2@HaPmUTT}%K%z7;F~Ql1j*d>^_qa?jN|6v3e$zeC_3LUMnB!v-e>{7o5AiEUxW+h_~%J;p0{?XB7u{}_8kKPdJ!`m)E`kQvlscCC#yYEa@lJGc+%RH@+Jb{#n58~3& z%Xm)Ag_A}%3eGLYuY^+0aE!NqQS zq7+EL$mr-^YspU0_a>2&dqOO;T{*6MRip@)PO_kvv1&fw#P|Gp)%7VRpLsKxR*o#m z6gW=L^Wt!&yP?TeALV=WN)^#V%9#y+tl&^<3r= zRq7Q+I}8+*H7MZCeZ_3;Ku2p>a}($&&S+Tkm@ge4Cr+qL8rlwI&Wd+O_}gYblxIqg&4xRx@D$4K6K24MP4vCyY~G3-LF(WzhCp zU7Y)(wT+JlP_Kjz+#(tP+{$(S*8XoaE1#&?z*oOfjDXPv1N9CwdKYJg`_JVH4+&*Kb`&n3QF>cefBFk%jVbVTjWshS+W zKZJPKM*#Gbl*C|G0A0L<+Cl>J*Qmxx_j*1wUL{uX8=0a7`50uC=ZJg5mTURAK|w*+ zH#g0lwo7%$B>wayj&`S5%WB_*Y($jGNF ze?lgF&-AI|R#n9)EUY_n~sj7YrZdW8{X0`%oCp|sA(rg?J4Gq#1 z6}VW&PA^6wibMP#epwly@r6h2`u1%B9Qwi`_2h??l8U5g1(><{+E$pTg5Ad7WM^xv zXlc^aXUiGZ+W#+ckk%UK1XaVOxScNddkqNaq)7m9nga5=t*s3LqL)%8#cRDT8M(PM zY-~ptYpGTMzR1dkmQ4YqnnxBJBMBf285Y*zVlcDkQ75?X^d}@wPEK-K&1FdxYm`$lF@@rCBaV7J_Zg8d zNNv8m*#pXNj)lDb#o;`Efoid~gd5YyPt`<~=BWYyV)V07e@Z9`4`2roF|iVz`dC_8 z8k^a-LgBDvAc3D6NPza>>fZ5Api<)Yi*TH;r)Ib0+i99(BC5*#cF8n2|)P@z@$*u zEL4@3AIMjs4^noK(FM64ujeV{*fwIe+c5tFT7Ov+UTyNcsZ%$_Cp7*ZNs04y7z3M1 z`Y{D-WB#bR2Qt;qFEV@&JhuiPDPk9q*4b~AnT<2WX1{-QtyCi`hS`7r|%NeKpwmLr$iz{)mI`k8- zj8(E*!j9L>!&4TS6cyX0N|`9dHL5LV_nPlghH&H+XjF>%$mVk52l8qGxPSNV-PycX zDlKhKz0DH56dS0%>K*DlVJF2U0{L#AiSm zxxKw*kWm2R1S!S#`^U1q{T3b)g0h4jI`pE)^Z}(|Z(_%BzUrG30M8%=JH-GgMMbo* z%}i25Gqa?`3J^PUu3JT^UbhJwTcZU|Cz~p&)@N0wYT|Z#|ls|e9rrihcV{n=Jbi(Hb-a)6NNk^ zhxH_t-uDMZ!`jeD7pWBBNzoTH9uG-fkI}1*6>2DGGcREo+*c_04HZ0SR*!v{=I42Cr&F; zkbN$IK2cOM`iX=bru%mf<$YrN>7!_AcULs-edQH0otlXXJmi~oOu3<<;p#hR*z4$|BROFcu6v-I>nG$(eWC zYmBBs-GBs26u`V#QBA!>cz9pQC>g6elI~^$^b&YoSn08@ew+{(^(7%8BkP+OR7OTd z#>dCsT%84X0l`P}i-ApNY7X{)v|V(AX$tHFVwE&CB?ULWMNsVAe#1abuaK?%0K~(A zfz)NefMCv(ooCH^`E~-&d3Zia_mq#m?{a}%@cAp-y*Q#PLT(|-Eox6zj}k%)!g5I( zu5&QE4JF5<&0Uxb2td4Tq3#d`m5S9m8Wi!k0Rn&e^K0R?zUIQvTg%zHO1HD!LUA+x zzteD+6p%7|QacPdUvwQf4aYa6)>~%hhJ2sU?Zp{>&8NnlapiFO7~XxACFdP7W%LxR@iq`3aItv>bwGU zr$y9vQqX@@quzoFIw+u?0h@Mrd((Wr_%Nu8CUoTt9iLHiq(F`NbZo{K0sW9HJ;Db$ z1sq=lsL|^m63G*p+VzJ3Om?MKY!7C30_h>%6Y$>NL=LS6XBO~05H|}83vdtdK9d(+ ztUpyg!DrG=8}OKGaFMPB@`699YXF!QdC^2xNC@Vkeay{p=%A-;5|_f3M+d`~G>`0w z7g?=Hug3a`mmZ73Mep3tUO)0TDM?A$RQ{VD zw*G-2XRT3wiSI>_Ui&R^VeDv@EX^Ulo`nH%9vKJ+odhKENxE^crl0SOco@=M24Gwk zU~T4S1^mi=L{Rt-{u-2^iwjYKXE3GQ)+~S8ygyP_)Pa9@bt3f@(0(BM^@XLS;nFvq zjc#YG1|}x79UuQ~5=hkdZ<9c@8S<#_xin*FW3xX$9e}4iuU2WkKI~59Fe|g_(QR^1 z81hFaGZfW48k8{Jpq&T~4fWiq9PhFyr3t$^OVlU=W zz>0z3tSolpffq(qk`uAE@sT^cB(a#7m^ea5Ka8oV&~zXx_>k*)%+n8NK>Q{spq`d>#%_tR(KZT)_;YLs4ob%8Rn{* z$~cB^k@5bE0*leSFRpE29oLV~-1p`Q2aJS2*7Tm#sd-?~doV{oQG@v!em3|)`+J1) zf%SbxYw7qh`s5j^C#<7XrtT;AJ-xj_7hkmoC7)jn)pve^#1DIZEFvJx)o(!u={&(e ze4vjSLBa>=e6v0dh09j3zZCk~qW1iDm){e$qNMZujOgsk&F?=C{MDcqs#yK*RJDyr zRx`&IDl~~cjV?;_@I@s|)9l|Hv`yu2DWk`(cTNk9)?}Rc-pS*=GxZrFg5rex+g#7O z`q46v*6Hb^u=y~!w8XR})OT+G3itNX7UDmgxugqVA+y)xL{LuuWhmJD|I|DFP3Q6o z7@aV`j;*Qx)A8R&r~VjA>I->u0B`yaCw{(BD1ej+Yk2`%Jz1vlJWiJS`Wb*@{O%{W z{e2GlC?5yNYM-;0axd2v%tVUE^A|)ava`U|QVI$Y&COnpj#b+7s9cWQ>Mvfru(2uC ze)YGJY#&M89|XH-}&wb;IrRR0S!CVLER3PyKRn)`X-8fp%7vA9Q+kM6S2FqDcN)g82 zjI^q~uY&e-i$XPQa5|6C=s#}th#-_xaXarX069_tsDFf{q_+S{1K42K_$aMR1jtxG ztv>1(mKygtNV4#ml5%A)F`gFMiU+;SLla=L6O~j{9^)5%N#X9vtF_i69 z>^uK`1_}_`5>eXahBDgP#PsA4`i@$jME;u&d({U=2BkSbc{ow}h6plTbSo9hfXTc9 zsG;c!lclXKA~Lenn3#@Zx6|#7iPFrlFu9*SUx(;?`D%c#EXIpTR4k{fc5ZL3Hf(uW zS%;>lKfUC3+dSrW*x6!PyK={GjnK;eb7%<&3vad0Y3M}x>!bfe-zN%>qiNAk3-9@xwP zwitX~b`0=K?^XbK6n!Vv-G z3y*mP0p(G6RFtsI`1WM^#ZGowS{gu{H|I-XICh8Ag87mvxzp|KBFF-{)3tVMV?|n= zw6q%|c}fgdEwJeFB*;B9Pcz$rGr6q*v&)$TWzzW6HI~oIC%^csi7oTxX$SnhR-pGK zpv)&x>=`alE3tcC=d>sL1Yx)wjg}z^@VUJw+}tihSrRLC!|U_S0v0@08OuNCldNoP zSUTAH`LDK>i<=~MLR zri>X9&gyK9jW3v>BIAD2d{-rC&ufS;%*&QI7S8r2lcNDg{QvaF=}2P56eYoH@|-Uy0*3! zu-PkI6h(FQN5sTEL+(h6jb@3Ftgn=2f7n{o7YKpfqama3e)n!iyGCVD={l_GQ(ayC zs51rR5EP1soI)$(MeA{SY@(XQ!7li_TfPI1(_O6Att|r-lm4rX1A6Vu_i?&4)<43Y ztB-Ye2g}3@^C{gi$l&EnGzt5oRVYpoCx$q?#g6Sl#^dj^K9k>EB_@;;nhyC9v-J>V z<)NZ(7oZZ*9zAR6?`LLZwFT5ZNK&Pu4!0c0jj(X`$dCbo zMs{`%5gf@?@ZA6lswnJ)p9YDMM5i%G9id@kVU3mO$pt!WjU5<0cF2CxC?WnGNM(Rj z9t0?SEVh+2dyPU;Y0=d{sOvruRdi~`KIHDEM9}7)W~&G=FoXz5YFY+!Pcqq1$)zxG zMAXq$&xWD?-^mNt2=V7FP+DMdh^X+P`L1Z>gR4P)eF$&i9;Y|Z-F+6^w2V&F*GyC zidQn_4eHO{5De`eJjuGj&&iqsQqKQs=BuNk+`G035h;rj5e%feK~hpWhZsufF6kIr zMHoO~=x&G7p_EqX?$kj^2N;l!A-)^%9M3t=^PTms_5SnDAFKt;J#){FYhTy??L8L( z`EfhL5|AJM6oC9d%Y8t1b^mI{=NLhwqBz*t<`iEt7APluV&#exi?D`WK0O-nW5cV- zex{|au3qhEly3CFmAqlbnvwXPy?GFKBY$!Wbud_hC0 zG>a>;xHZENq|-n7g#A1pkoCTsjJlNqGVZ&3R|k(Hv^3GDaPeJYt6!Q;y5@_wt zYVn7NC^Pv(L{Qi%7OG}eRK!Vj78Wv@m`tmtJ#d(+f}*rEHE;4+XOe@tv_*33eO5m# z;y9=W2N3A2*zRx4^V<%O*ZT^NS*u6~;6$d0cvd-nO^zJQmL*z}p|TDyqk){Z^T{hb z^tx8zUgJnzWqzAXm)X{ElSevVua113l~%%Vswz|pme8xSb>@gq>djM5Ry$48Qd1j1 zFpgg15Q!?_PWV12m_U-EH~`T?FQ>1cM7ES6{L~UiRI;AIhOp33;#)#Ln}Tqqh^XF3 zzXIY04uGQPM5adb?BU-Oi_IqEFTrwl3J`-l(e_(kdShXrv_T}B#{x!Ty{GxJ(cE8m z@CN6x2J4gNWH$wsgf0Yy9!;&s7{TY%u0N)2im)q|&UPb=A@j4{?2s$RzRlf5nC;^= zvu0|nYOLQpwazb8wj?x)pYgv8&oH&3R}n63>KiH>aA;+%?nLgvE3P^>ckomRK4`YQ@~yb53%}%QA~2^IC7&Kd z^5OLa6Ply1>EciqH0b6Aaw3LRIK8i}clz7J{^!e|C036P7cPpys;%6C$m0CHHV|re z=k#QQBn+`E_8@k}UQEcWscTDj@oXalD-d-$u+qd~d-v)i{;X%1B=GbAMH267$T$gq?F#C_ z!iN0wImNRI5Y_y9rC|h{&{S}=CgGl58uis7;LjT;|ID3%e{Vx|bss^1hcYZoARD8YJ=8!h) z<8k%rVy}v_a$My|U}$LQY0MD?dTUQsoz)BbxRF3N=AXCMId;L@5f(1o&Ro&zCuD$VpQt^ zK=S7+5{Dg(8JFBGAY!g!IJmeOUNhlq4VWlO3kwVS`}c?6Jq!mlQ8J}bBgDTmj``KA zSK{L0e4UvOLwI<2yqH18)s8fMIFC7CJLsm7zsO6oXhnlz$M#&%LnRY`=P#FqO0DF% z{x1m^eXQ=h!3LW5H~(^YwW5JyOm9hi0)Xeoj~~<1)8XXzWsU&f=oagJEI&CIYixy5 z7r^KA30__;gyhIUnKtwh=HWs`!^6V@4wHKa2d!;w0H5PN`sX4kO&-fIJ5P5ED|VU(Kon*^IB|w4evaHjm_od$VUSll4Xqg;jLYOvYPwH(~A4;btmz9U*k4w zB^JLu`Fwe-EF$k7*|eODOgk7$Is5O%jN$D0io4pB1%V2T>{}7*~b9;0R+X# zGZJd1Q)b%rOtT~9k#dRv``x?WgP}kTj3~c(z9t}R2Vn4m=TvZTFksoO*=PyPA1!Oo z-mrkO98XtNkr&Sz{@a@T@#<)OJ4bloj^sybD5O_zcl9f?hpCSM2Oe~Dv2RiAJ|m-A zGGF+0W@ctF-yKo^1HP6DGQ0YppNQ&Ua6H*URa4V@VQGAe|0#st+QQ365ZA)I0VIEH za}i{A@+tFaP;}|>*2R+P}iAef_Q-!LaXy#2}egq`VY9c z)CpUdyB4LpsDL^rC--dW>DvnT3S0!mInD?^Y~b@GYEHZ>D7d@x>14?QMxqda8q9n9 z_ARqke!q$0G82g4%9TYy+-O=cmR=T4&geX5y%}ujf9jAyCN}@M_G=Ic6);&Je5aUS z5Txy!3EH$Vx3Ewc$|SKF$Zk=SIFhkBQ1WdjCL%DevZ>Rz=ibM{rMZ3FtdDgRi$MKOW0 zAYNaj2yUh=2Nan*36*r72Epu-8r|KyPmT_@fw5X@5rfu}`Hv-}(OTJHA?MR#mk+u_B96*D2lN+X06z@j4}P z*ye6-Zcm=v)zuUgw&)}?4$4~5mnm}l(Vx}1XvW-6&Yuc=%PCyFTY&g zR+@un4XTM2wG!Wx+X9Le5a%a*v(e1;W^GY`dYYLr1<^a*TVEJ`X-I#K}sgb2MGFrfKx!) z*d^DBpI(7RL?|gK+1~-4d?SWzYPNval=d!#h+_8B8=Eg8tQnAHedejb3^((i8?o`AKOl1FstzW(^-W?$hXqdaw&-m;{yMYQbD*ohC=;5KC zjtsW5q-0>Pgf-TCX%_TUP|;t`qm<@Yno4B>3a`dK1@>Eq8-DXqmw7l4T+C0N=G996 z)=s5>Zo(Q_=M5w*X=f49IcEp>)B6TNtcyXv357HrO+@-s-EM%{iIq~0v95S;T$f=u zkeu_tV_{)uWnxn;;2_q}p5=uZt2}7(5|;YvCnT0O*U`3XhIp`wWZO&5zBEoRyvwr_{A zMA6yLU7CYU#js+3yZq&YAQlJCZ=&g)#U{S)=eUWyGI_ss-9eP$pAq+zlWAm< zqyi|_*Kho}th~oHb9p&)`7mtqyLSj5u&fq_i@O>MCzEbw?-^gf(?*)9cT3;SM0B)A z-Xmlwv?r`WMBc~|ejv|7U>PZ-L3QVX#zB2wsUGtZ^1#u3NX*}<=fv~asuw-ET7m%` zIR%t&ne#>;H8wL|57=lgFWafH1+_(tZZ*{8La^)cSxQXQ_h%UDng6)?m~BxV&C2~F z&(f5Q!*#l`ty&LRMFps9}&GBQjX~njo;^GS)ZMt1V^>vi_79M0M7yA{q;Fl2Ss&$qIE}V7s^EUykW-J zpX49&VPjQZcpt=jCl0tBipK>>cZR*$UR-^_fF+3~=zK902#7(P6+Y5C(Re8AfeTod zzFq7MRL8-R1P8>OQ*$m|psnM4v9n^W*tTukw(Y#JZQHhO+qP}4opX8iAK3SO(N$g5 zHRr%HN;Dug>AE%}>m=jAbiq&U=n@XTOL@Z#toX3sIDN(tr@qhIuVAqKW=^fPKxx@? z((`&N0?@*a-=g1;fr#7P4_RTaQ<{)0)A7I5~1YqLFDUxPw|DE30&E3L)@j=dg2+@ITME zF|P(U1whYpVRIEQR#~@_tiY;kqg%W>$9vT8j>cH&f5bQe;D2E}n&`~=dWgk&AT zQUazN`$bz*K4D=H)DcqtcKN8x8%+Of)dN@oi_d*ejZ{Ui&wp7I)!K1Yq4OiHrMJV* zhyIv)p|=7HX&ZNLVa6&kvJhVQ>-KwX>OZ(V0+kK$`v zs4Tj3r;xRy8ha-hy(5y|VR7K|eIx!UQ#n~NlZDmy=^3THP%&Q7^ZK(xQBB(2h}V`% zt$+WRt?S_0q8#G!*t-dABWKlTBrt#2&AXV8n)+w;x7S>UgWW#rwmQ)@&pcK|@rHs8 zfkpc?Wegd{eOZz&?RD~#p=RTF^Us&*+O&&^2SD%W*^u|$PH=FX6*#aKKadu;R33dS z{FZ3ib10`ac0IcrT@3c^aP{CjJ8Ol3ZKt=tdPqei=~4Zmi2 zJoT<4N02YPM-Dt~wt|zBjj0r)rHy@&e>@W9NfCA` zd+?I)R?i2zYhrBc&qQR)T%8q@Fd| zK`}MgY6d`BeqXI#7MQY|sk>vMR>QM~UjK#>#j&O%luUlnB*5DeewZa9HP-p7BUj37 zK?DsB@XCEs7k)9QeAkb5Mgv#kMuC?j;?MZ^&9_g~lmAP3f0dQZ& zh9|Oyhj6kbWy(E@WPhhtZ~Iue&^EFsGfYqf!E?C_IdX2n|3?bjRH#vCf9jLKIZ*Rf z@UGF2-tLSOgwGrWmGMPfYgX(d&j5#31i13q%_3Zn+Cz2QPVcMU!z&17PJd^3>jP2> zdLGRiukPJ=$_U1_BaQ;z3b5?b+WgKe@=@B`|1ky1R_utqR#ck%Nr@r0vTfGtgAz4X z0-$ktWF> z>mb);cw7+&7e0fR;u$e*yQ767Xs=)qWXFCQK}0`C(8gnj-*UQ;+SP@|aXp`1^884` zD!c!bP)*Tpv!9rP-=*Iq+xe7)3gKSAnS|jP|I%3cFZWstrP>x%N)kcsqY($lR#~i6 zxi^yf-PlH}a@qT(0^ELs5yay&{cp=R-E&3Dg=qqw7=mjgH4kAVWnm+&9 z91BiI$k*6ph-By_5K(}I=_Ia(TS4`ThBP!MC`)7Lu_{?hs8?-V&D7o~Ch({u6wVns zy84=Q-smVpq9|&I$e+6sEW)+!Dp)hU%5lnYIf5!s1ELlfU6 zspf9b-u}vX!$y2<)7OhoB)9LRmj7`2N3(MQ8EI4Py@Ux6`USb0v{$IXW77KWd8cg~B-Z-il;c{QD%r;+${lrnm%YZ;oc;8>3 zifAI@&0a)Qb<~GJvCk&gbCROIEOThmD(UunUWwWrA%ww951xGt!$o>WrccM+%-kgI zR0zcSlUusSK9T_2n$vLHVa8Q@>72vITH(m9g;~*%Et)r*DRiC4?e`5z!Qm|yZDwIm zkKDgle?4vSId}wQk3XYWubQ-HmA+^Wtu*|;wM|-COD+AgqvB*sLNUT*jiukLVoqqz zzp@XCwFM=Ly2}_uf|C|84=6SSFva!tT2V^ROP>2J2TX|c4HNMptuq^tVp z1uV2rbP@gl(BiGoI7?m#ogBZ6&NP{F3d7`gG;vVD zn$dh3TkZYd?Ou1f5L2_&nIN`@R1D=>h{J36Dumv0TIq?1$_I#N9);-(K-zs9Di=E= z^B*TVoIl+MNSP*;&&_Y(h2xm1D>Sz{JCB_(<#m~Qh;4oiDlXRuvvrov5^<#Uyb3mh z@>)q##GE15U3g$}&S)-5gcsVOQ@ZjpqdZNdRqEAgexnEUD#@K`7eklMYF1u$u5M4! zsscA#k14T9_5(|$*+LF^U%a@P{$7@1D9@fPV^%KA1aKSW+QEwX-f6V*sI`35v%3zR z#jbATev@CmU6wA8Q6i0|6e zDr`g(E%!7IX&Jo(b=!(jRBiXed$@^JzfJEN9&80(U^xukn}|*BdpJw|K?B19C`GpA zLo(@jz|9hkyhfyKVGWxVu169wD&FL5hwI^YbZ$x&3~fRJ1fkkxb<+gmUX*g1^37% zcI0f=Z2G~EnETuGH392?lm9Yyvyj{-6B$wlnPWzDax9cT!sFwuIXMc=4!@taY6e=u zKgk21n7ef(+BG(AxLT~~TU1HU87LMySncG+4Y#Li*obH9j&o9Il;%y%!kY|T@$VH_ z(~G_=_uU8X6QX&-sK71RKUhCc#?nvcKyS4{+J8Gj0O=g`D;B*R+68{S#4iS$suMBR`jxLX#MRIE^%tm$QqjJ>Q8Qmc1mpHjhI zR`Px6C=~HK9%+bs#BJ~C7&){t?}2`ftC5Rog5odJM}d2ULGLZ}@n6QB=(ZB%X_7Kw zu|fR|4OSB=^H}vFPnM#2@zDZ9A7=r*L+J*8K@;=%Fh)m32_>apBCB<=OE^<=h~$yd z&h-(cE>1qzCBTvKtz%&xu&o1HaM2XkfJ|PtkuEQh0Rc+fFR^HqJLI4#c2pI5(0Jm* zRRvF3R+0Tw%XA@w;47_;ujwR__h!WA>5l5jcNIULOU(&^wg%*OCH0^Qy&t^zXP*yO zBPsXSAapd?9I^Cn3ziNsmo3;@m)FcVk}9wdG%|UZiRjt1&<|L`trz;p5vi5DG-~O= zv`XN~99$%OY8W>w1tB(o6w}WGaXe@^GTQ8pC5$*5C$T0ns@^Orm3>iOs}X;pYmqah zth;fbcS(qoAVUQZB4sITyv_w0Z{$j66mUNDubosV(9k{?e(HD%v zIgNEGn{%BxY^)SWAB+9SIdGpDwSrAQECRDT#VdtFA1*=|j4);lE`f63LI2@4;WIi6 zkjDtppH=WWfEI`GmoFTiGYcH;FoSU4-)7ii2@=tcN4m*A3NR?gDwH=37=)w#3H@9G+JT?77xhwHxj**18A zQf)Yi+)u$@U#IEBsmw{Dk5aA0CancdQ$u=~6xJu~QZ#PumDZyKAnvjXU7 zl|ys#)?>?Kc+F~kCcSulVT`3`U$PsJdwiRMnm(ezHx%0rs{s3sbg(6wA`;uo@Uq=) zVi9=fIe`RkSbrp${1;lUpT|l!y|(RsE0`xy8J#m>)rDiHx^H$ADaGfq^=SI?2jKhe zlLcDKmitD0KShUw_ma`cP!ii&6P3#3VJp1Oy_dBLRAfa#z}405OeG6Wjcyw?T+7M_Yu!2%hxD4Q;;c%_vFU$VXL#Ek>%5m`2Y zp2gu+vT30O!g~? z3KYuEQ-*vMt)DJ`pn8you*)86MIe*1%XX08^6JBatr|@yg%M*zv0ri7KQDvX$UKBS#A-3 zzTyO>@78j-a@X&Vz875fyMCAV+O+!T^$12u34{+V%VK-BZ01;oApuzoMQ`^Huk1VZuvzxkCKwI=x*C&+NvLeyS*jO>?q|T8v5%!KPwrQJXba> zKTTd-BSI81mRc6m6;~}VDr-(8CM)%0gkw-+p1fUrzS%6PR@R9qcf-&mXRwUEYeTh6 zY$Yl!b2bGPL#1{X(^vY|+WHM7^(6%Q*GbtD$$P2z3IH0|V1n@x#Q)?;&=OX-d#@ut zlL3=PhRw5^mJCM|rMJ}_6-TqqU^WaoC4)={jL7uJD!+wFV6!NMF?Uing>H!4Z8!Aq zOeMOalblTIvNs09=S*{Mbt} zzSdL@-72o1gIl__mxQ8kqvNNTgX}ZD9>HVE?|YlO>sAdcOcBD;UaGN*i6luL_@DnZ z5s%B%OS10-3?!2b3>5Kyy1FB1ZD(X9Y++#LXkhdI?d@E3xhSh@{iBNz5f}dp7x3T0 zc3x1JGaLCf0ZBA+KvSOc--T@&N|Nn>B=JyjNvEDz+gIOL%+nsXEj~ZDtoB#DOm}pv zX*+Mjn)VeK5f6%~N@BJ6xH$UaTPgCtg!$Y;f*of7Pb6QW_qYtOpHOS^za;+7F>hqm z3|v@H;m{JA2Q?K|3m-|LwyzWIXXz>f1imZLSB$PuOke(GWmWS}1k-Y`{z0mujq57kXnZgWW(D&UU9wj_x+IDTA~+Q`z;a`Zmf_*X0*wz+mH%q^!o zMZE*tjfY!lf=8=AaFFM@4kGiehqZJfcl2>gRKxK(lzR*L9}8^8y;=!l)m=}sX1Om& zv^(G5KBMwIqV+9%CPko!3HQ$mRWK;pckfsB-{37)#Q$*o{epMDa&J-ZPZw#wzPE9U z#rcl>d?`HsT>72)Cp3oggvRK{{?-3<@7x|bB z=V`%S^Ybl@ekW@QyJrrB27g>tQ}d|iYvd6lB5?oqso)JzR1S)+E>gaMfQ#19n^68n z;W9Hbg{C0DI$#wgTR<^%~7g=F56k_v^a$4RQO&S zS>=hV7;EqHlPQaPnTNVURDMsD?6KD6uSYx@c_A{dhmma{ zrD(^T1=Vb69}4uDjN-F;jlT`v@P8~IARzDv2prR%7T*Z)2(@{6S2zf<(IRmV4g7dR zp!NEAu&s7}7Z(rgJFN^XI@C<(74tNdv>{;{xH%Kt?suw$DtdZ)Lyvo6zg1{m-5vFM zfvZZj-)@mHQBE!-WTAFQNN9RYMQEvEOy<=+O?Hox62kz*ocwA$OX)2JxeaYR8J zhOiJ6ZMuj0ZD}PVcozb64+st$P(xBU5w0GGhR9JqHa}Z$%uM{)BHlbDevym5x5}*P zTK+z{)rd?J6BC7UpBXui%<{E3R^l%QzL4VWYvxk&)96qn5V!E8vTL z!7Bf2Op%F6DiIG(!4Y-P> zcy^gi{FGQK3JNr2?(CmwY<~WMr00|<0&C8BtgX_^|r}%pJYJW-t z_m1^boSH0v@PFljWBLD9!CztccZvtZi4}^`P#e-I2&Jca3zi7Iz9(2-p+ot(>%HA zZT2wUn6wr~d3t$yc|F6QqIiMh>yJ~#UIaY_qY==h-7XI!YZH>djrHyCi4ni~z5M_o z#`&)+>da$%Sq+W!x>4S40F@%~w4Qwe>w$;k9Wr*GVY_FQcx=4CfP{`t@09um`TX)A zs9aR6^vuc>9v!EDYW5PYiQyuvnOJFA{!^Q_e4TC?v{aM z&`MGwW;mj6Y(drn3=)9f0bl(!`DJiPF`JrT8iY?Jv>H5 zMXjqadAPZeI|2pg#Xi5nwi_3GkVF)I2V+tf;`((&Bn8BUN`t z_Cm)Q8Xo#0TTxZp?e)`BP;d|ptbs!bOhh#{?)vw1&WeqV&Bn%tyG^u2&OE%bgU3cz zsgfL`^aS81?@tN&GGw;k&&k5;kw)G#K4E#9E@|M>rjqf;a5*rRe6!}#r+~H z&^uN_ARp$c-QH_Z;)>Nzz9 z07uuv8)ofE@Z~D104*VVb?oGi3j}utfKv zD1s*6EQ!Yp10}HAh7vI2^_FgTm_6Hs;7t6Bba=hpZEkL+qM{<4SKK$g*G8WxC(<3E zIMvhsm~6;@Is1)wM$S7sg4k}3czTXE>Fq%?gmZiap5T{;ePjXG3B1NMHFvX^zq|;w zxAb;y?zN%r^uB#OTiMy!{To|BEU-!srdwmsJgQiR9o~sBRc21@xt*hvL;LsbZm7SfRjn6-+mR#K0+WNc(US*k5b(-_}y}`MJ%sF1*o8mFRpG>6BDqLqm5{aGLxgb zJwB0`g&^`iO!Z&SEH_IC93Lqk_GzLvz43&HcdO|i}+Z~PDpb7lE>{)G!ppFsG2 z|Go4orY6C%yIaSA=v?DId^B%lbKT~_3Y2PZlk=DBWZ(4$RDW^(j+T zR?66N&GLCSpGrCEVwWZ-VRQcxy3I&%2WQfYMp#^JI=6jmxF%LQ{Y5w_-h zGfi1k!T2{;=lf5)DlRTO5oax22|Q>*lU7$eyQGm#tMc!i>};Z7FVUyMevxX~-wtvh zuj4-t4-a2o+=Zg^Hy>>i;m(?kohd0P85tRC9ws37HW%;9@!qBSmFnm`KuoXQDkxf; zOl?)dx}Dc&?BZ*>7!-7rE_Qac`@@0wdf$dd;q;5n6*m4xX=mqw0fDDi?*(=gX^ul; z`8YY*p8UV0*Pvg(Wgg-LGYo$+u=L2Vj+u$cwx%Q-Lu&(;z_Kj33sz{gpL%;a>hjyd|DsU#A=9Fl^~ z8dg@kgOGgP&*SMV;oR)>PH7&HpY4osBhyO^=Gzm-Z?Ebd_rw>x+R52@Nz_ek#KkAh z5NHjb60||HN-I)IQc_YN9{CSsIzEI$%ephQUCayA2ZV3?_p{L6WGWr-3T?hV0|R}O znmTNEvJ?)@+=srJj-ue~glCTePa;83OK&g2d|XmekU6v07d5ANpFF%ffK&IfZ3%Z4 z6Bo_I-2A`o{;4VJ`ia@m(sUzILPFqgWi2f){c%!HSAAm}8(aaxtxU>`Mvm>YloSZ1 zfT@l3_3UleK@nHb=&iXqpj4;Gl$C$)PTyEwmITU3@#iluS2Je{@W0$_c-f(|iwj0Z zX6}vMJ0Na|o(txBF(Vo%+l%e~u9+9aA6Wj!n@u1cJ=CHVz8W(kj1N+1FKkJavRpIf zyTR3;ZRNi7Hs`*s!fqDk3oBGXNaVAFYa1J9HoJyVH&FUcYgw{KVq($_ndR;gS9UH3 zBeu!vN|ZJar~ODBCpov)iV)F?y~Kib@4k+9P}uKUniJMVCUlcK;6bR>!Lj;N?8Dp9gjey;px z8`MrLFlzw;QPsH2&W5I?9qn9I0gK6LkF`4MhXqkGVvFJpvb0VWpO(6#d z2jwMwvnLvfr-VG30|)`)*}dMZi^Yo9?Cf^Y>;1kVMI=;KaG$)AxiTGX?UIYEdtr$q zMF=(R*1-vx54sl&_v`MPR3FGD$TsDjC1=*{Gr|;ep2egs&@Cf4P5eBtHfjc5UX4DE z+wqg8G<#oP-+Emiv^04j&Y;~bE!_V8{`=UVTx;CdOdlEJ564fl5)NAgP4?olo_A3m zhEfYzS@=~Nn{(d~-kL3SdNm1&6@DFr-HU&ca}O;XXPyIp1441`^X9()T3NnnYA^Vd z%JyiZYyzR};h$e#!t8CMJGk~VH3&w<^6{~y#%ER*=ObaG1xH5nbk+fd#D}G&^)rYa z><0jT|Brag)Kb(Gga(0YmK+#=s>6xScoXkw^9ng+A@WREmHa0E;M`-Bi5PK4>CT3=}YcE)fK+E)q}uCt{Muh=_=ZiKOKvN-cByXc?JGK%hS$ zlN$kP48jxy8P*iz>Wb#QC}Cn^b*aKo0LXn(<`x~O5LVz4(X)FGiWM%(WJN<;Bi=T}?~cE7#Q<1x`*e>=a&q5^7E^|X z=7((8#s7&`=K=Hxm}+(7_iV1m$I9Ar$RLgb7T)>|4M+bhN}`3wlqG;Q=~FO3L_pA& zs!^!EJJK)1-qz}Hdny<7te~u1<+ct~faz5>hnb%$=9C#9Kk{$7wqJ#*vBPrRkPH*a zF$GaMXiEd9e+XfUECvG~V1U&vM^;i2|knY4Ds{1b*}7oyAu-YWO`>hoVgB$I)kJU z5w{LIeu+HT;tU+k$o7gKEdcLxg_rj~oB1#dGMenc>X#}a*rovllm1I9AC&NjWK9b@O9N-L z(i-7YAu$+4A*XVq)IYZ*`EE#7rHaxTI>ut3sArcy+z_}E!ap`P?GzNYe?QEq`MwK^P>0b6+rb!+ zBoKN)%+JTSmx9ARhc>u7+x2Xx*6zmeocEs&L;rUKczFI7Ga!Kt8MY&Ibla$-*lt7l zcK7pz(AQg*^rh;tTPOIfGw?`J!dYyrp%GxT>+qnpBH1?XR%Egjj7-A_cxxQqpM!u6{7t9M) zVM5jrl%(RJ_MqKZ%+lC`BM9<;3C?rLSeK%)0fu-oqBeGWEEABZFN9Q}>E!HB$5Gc2 zRJBtVb$Hvou~_r6lvg*XxBxrZXg>R&;Z+P3%-4Gc+{hWK-KJY*ADO z8-hZP{0mNvFcCcG?`qbOv7@CXVv)QR$pD+}6tFgcDgv3)3SyHVqk(NTLiaT=XhTnj z(hBksF$zUEt7ia$xi$D)aa1Z}VL@@~ZYc>hYg5 z)r!P%acZyR_Uld54b|9KKNT4gi;9F7D+ct}a$&q@filU9qe+bHKmfBNf4CT{b=#qecfX;bQKd4Q{A3e;vV=7P@T!abH5j5jqupsJqG1!F=&5NZmzDT zZ{PDiw=O0RsqO9UgoJ1HI-YPf#f{N_VunU5t(~c99><83RajUKv9RS9hd~ASUP`dC zK$imdB|-K>e&3*$713VYb=uXb>kFN!@wWmrwi&1@>*{K#U?)dcy9m*mJ?ZNDyJ%x9 zSQ9^1WgEY@`l}y7$sTRV#%AI378U%gZPR@IEPxk*a!pF_B<{ZA%*^dmU;C(^Z4`$9 z`}<$d(&kIc$|yv*2bg_=$od%yYIU9|h!H<;m>V?lAR#gW#!&!@Mu#4CU)KgjazM0p z6h%NQ%Ysq;y@ROxum2rto@VXpkw~#IGaZ6Dc)na|JicR+2R&NUg{vQdya@{nRlcL3 zpm1|@JEao&&5hDuwq@a6`;!dkw}&Ez>3cakasnLmdrsiBeQ9$0A%Kj5;sG>JaB#r| zkOE_H;=eSZ>svp}YB_YChtX5&eOSOp+S*z==V?uOxhMgmo15FOI!+K691Q+XoSB>R zW4nocU1aR$=LA-IDU+VKvhMEb5;BRCs-9qMa27N7fIx&tXS=b{h^vaMyyrBw%yhfF&MnB@ zEp8WiqWvYQkmJb0^C+swtndO?{C>XLSz^Vk=mpZ4U@-a|8^flirvA`z=IXXFb8w6o zsEJ{3Mvl>s3bkCSNm%BOTb3;^Eq&GJs{)j4Cx)(kb2%a2LQ24gGslda(9l>K`ad0- zua~se-5Hd9gUaIP7C6BQ&)U}Dm_SL^A%?&bt~jBfl>6?kh>kXm{BXU`R$}q3`#2^h zZuojZZF^~`si|pA;lB8%Y~C+`!d1!B1lM$0`_YQB6WZ9L%gX#iMiO7_ALvwARbLvo z!;jk9a7c4?1sFhNgIc($=j^ya!m+%mw>@rGk=HU??f#^ulptxd1A~dOgv8 z;o`(Q!;yddU+%Wi2>I^@|CTW%S)qc9h&;R-Pg+?8e0?1a+zc`?LA+GD7#|!7){sz8 zpq6eZ7#dgb+uAoxjs%5AN8|n8kV>Z0jEsy9Pe0xugFil3Py6YR7%h02nZaC8l9Q5> zGAJ!~Ctc6l|yDY$IA@m-Wg{0vL_ zt8p|B?ps;u{|*ferOg=v;Pc!~uiu?Dn0Nm55gjTmEf~uG?bWR`{Mq{F`!qY2phZ|M zzlwm0nsMFx@zMBr5PsVHq1_hPNR8p4@A?gsoV<+H^j}fX)5r5?;D3R%CudO5n35ST zhLGa>zuQK8N8f*KN-so1LO(CHHPqDsV0T-5Lq{AZr3|jKSOtFxzvF&fYlMABy(8Q- zV}pyL{|6&1BpqnPhZ%x|6Fako&I5?%6opFYtRx z$OUUvyVe&M=j&+}ya<1fzj;;gvH!T?Vt4kgI9Ry87;wJ2uTVmu@}iZ#ZkEy@P=O$m z8AP;?N;5?`G?XkXWar@_uQo3&S5}Qq1_ajr;@^6`Ug7!4XldcUE|sf|KG)JlSjRP6 z1TPANs;jGQ5IluaoN-v5_qy&fbH!Hef&-`eRj1`M8Uo2B;LfEHGktFl!$$04 z;Xy&fQ<&UIQB6;OW8BFbmg$zOSjjeZs?cX{zvXNEJ1?wGz9YVrD0(~zrXo>e~OM8OD$KJ=pw}eZq|I+m{}gP2W4}SRtpJ;DbRG-e-TYH>E7+1#P}K|H3!jnCx!piK zJf__e|6Oy=Ss5BSHu#+K{plB&TbN(y=;*YvvPh1p6$)}q`Is18Wn#1qb2%b6@#S^< zT0w6SUk6SUEVi|>;xZ^YgzzgWVP#}vgS5c>!@p&50{ zWIyVEx(ZNzt8WZs8>Apl3*%9@Cg>2UY6o|LYXFxAqs?+r<2WjDUVKj%s~rC(pl!7p z!f67O_u;IP2XS_E6qr!Lmdn#G*1^0|p8%KaVfnzfHull+`uWPKxgnN@l$4YJg__a~P*5n>NJ;-WnLR-1WwsmX z@7g;t5@{8D9LzYo3Mn6Y9D=175AkLQ+7Z*=vAx#oSq((E*5^T?p5I;q(6Z;@$ zbEcDoy7*ys5hni|-$?_GgPV`bijZV>UAkONFlwwl=H>}5i{9nwMm(|piFyIAJHmmi z78VrBV_~TI{Bi7}><&L7U8pLX@{&06pfY!`htZVD`I^su)T$w%0TSc|ZBtft{G%(+ z|83^5coY?LkI+GMp_pP0vYuVWwTgJVz*@`0UGXn$#Ly(`W+RB=d(h0Yw z#pIG!#I)f1r)K6c_ICHv_lJao#H*QHJG?+M@fl8T;v8Cp5-5r2d^CP@&HoY`- zlmUYWA3qn$vJ4L*<;P2YXnlQsBh%}?bOU{TUv4)d9p+p48~A@QfLp-S;0q#KUhKZJN z5ky7aBcuZ68O>spI>FlZW~U;)=+rxUeNl7k>*FUX-gse){J~!3@{)>*O7xAF$M7(O z$_kJ|5Npn_leIHJLL2-3QoV&=?y?gAlZ}g&`FaP<(cA_|lGLuuT%Vf*NLCjWp&l!% z=*k|}Zl2YQDB}MmN1Pp=t|rAUu*|tHB#U>e@jaAs0efM68;+qY*vfnCX*za-gx5sX zY!r)a5oLy5VrC|P$0auQKtuglSSagj9vgq=5AX7PzBpGRR#jCM=>Ld`f$zw@{ewfD zv}|nrASE*6WTE#(xpXcXd-Wqt$&d4KXAuzlAO(uh*T)66c?5yl{-nJ%p7xGLNAO)Nm4jtz@JZ%QG7WjLw?^OuW zt%^inyC{Tgt5VKo@(uNPYf?^zSyXWasw9*14Or^99^yRou?8g>vtpy^M0M5LseROl zDt(i(^sQJ0i@LC06%v5$p;y1XzG;D=gYkjU^@G?V1JI5YW@qElpJ1k^S7&01i0t+@ zi@g~&N{gqsH!TAYxg++Vayut3ua5Mbfn^e$gup+@{5&{MIW<;-9<5C5v;oP9zzVc{9x zp1w-@A9pHjKxwO*u4ssYIJpJ05L}QTo_NRC*49Qx$L0-0`9Pek_>pMP87Au^8>On= z!D*pO{ox<3Tz^BBvS0N?4@Yna>o9$G!&!d~(TkU_X9y4UTiXHx0wSC8augn(So{MdhJ`)dbV!0%v#kwo6vfrVzZIw`Z$Kp%#0|dEL(BpC>6_ z8eAVABtpWXJ? zGG0H|4_=S|Dk|6VI7TqIdK$bO%VmmMBC~t>;JtB`TaB)X$NV9aWBI`|7boh+GPN{$ z3Eq$X6)G}a$N-(vIdX}w(}=p2{%!l^(GJZSpxF}^2C*T@lk)sOZA|Q{56L9`KS)Ti zDazxVAmPAQinG{3+LaTv7l_V#;RNAq%t|XODqXl@(QCC*UiHij!j1o;4Qv~+qzUJG zXKMRD-!Zd49x!gSmSgjHkvjVY1v?y78v6Avt;~(hOe=r}5)#=moRL791j5>J!{e-J zU$@{KP^@|uvWIF53Y}n;C#uYUJ_gmS84^OuAXckib9t);F#}`4;2!q+1Dop)lxZW& zj;W@yvV;Ny;Q>7I(tVH9@<^qleEmnwp~!yv$)2xq(_QlgvK7Tha#OA);pwrGwmfaC zZR-a6C_tF*8=R$R49e%9zj;*YfKKXBb8%+}Kx}g>n#vuw5|>@7@>( zIZ>YtZ=Q}E>W_r{%1}r%@7Jpyj^aba87!%yT&Dp=wslrP$%zYeM1jfg?jnBuFn_?C zTpLs|7=+|@_a`-{Ciqcjl{K3-IbV8LBFbu#Fy9;(8RV!F^+Vxu{8yhB7ut+XjBm0F%Z`MC0rY|?L8M+ynT+`u`Pm5t{1XonHcb9X|@k$ZN5_uo@q zeNW9yzH5R);eX9YRYB$1*$72O<#aA)rj{M!m3T$Kzovn8ee1(G$|GdwKCZlYQ5fvO z4~ahwyM;haZB-R9F|oE_zhZLQt4f!sqgc3b>%$HpPq+A3Jol{q^mSbG%v8R|9$uSa zY~AE(_Q?C?wxmS1KPzAjz^A3Hp5{UWADA|Xf$Feiy86am;O@J7P4^$dXaTG4+tY6{ zp`~whfsu|L@wlm`iW+*;z|KYZZv6Ep4Z(l4$NOhX54JfrkB(s}ZiORsV|yNFk2o*K z^OS|N24q?9jRUEtqhkYYQnO~<`BK$&Gu}4QjN3K+bBFQ{g^>5>7be3eC+%HP0U61@ ze3qYI%)-lb=yZ2@84lC1`&Vn|?dO@wUE+hTQT&@g-jJ}id;pI&?@z5PMS8}9f)qOo z>+#VM4o({<*DY{P770dsTN}U>6nVyT4YH%IyZV~-@81Im`k}T1hB2xkTLQvXn>N)T zQ$!!;pJSLEsa9Vb8=E0lNu=Lw9xylO`Z}4#IB@<^0Bzhq9V(E3RYVt^Gxfc;-{8a> z7hMvAWBe(uWxi=J-mC&cLiLFVE&EX@r}z?J+=`R^<|vFa^NTf3wD!^fwlaB$I*iH=y` z>YlLkrBT22;5p;5t~Z)VLN)7x(QxR7$`t5-wS`rYhToB5y&fE(G-Xv)SF{X>s92MQ zj(RV(g+BhIlountT~jR#ib{b*2C*-@-PZ3z$d>*Nfl>P!=(>;feMQPMR-1W~Hrm0$ z`g#WW;4_uM-%MW=p*h2<1c!_XK98sIFYf4i8%&p6WvG%S3+E}a2@`o^l3O-t_&Y#| zKd+>k2FS76MtKg`9ym-(MTUlh15>i(4?hUK(N_LHz7~c2#bO;oQh_M_2@-ZDPz zVTpJi92oTS^6c`ZvjPWe3P{kquPWRu5V)GS=Y9jmLW;&J1FF{mu?J4}w6cPsUAsQo zVKVbH5TIZ-+$i z26|H!MNnI}3(EH0AT?rtywZWB;{O`Eji;a|9Gsj1(xHfqS`CC!M%P(5H(AQC0DQhR za|ALnG-m_O(YB~447gDlO>o-#Ryfq|PS$*Km9v5iv&=$Inea3Yrf1H(W(zhx51 zataFGj4LA+FbyyE4-F-Pz>ViL^PgCP_H+F)3urY3fDA$>ARqvQIK;=))RcvVCByD= zSUkU@DU`pxy}cj*^1piq3yaPll&Sdp57t=oX&3Ot>_-v^rz?|_^`aT2o~(aXyg+Hn!6!z|qYS$tyR(8M`n?+D`loV{l5FC|<-4vF`Vyl@GbYxEi6Qx|*t z{{H?rE{j1p$acCxOG#-!vsXJ5(5bA9gD#rkkkT##h-Y(sJt;OeJ1=jd+RlJ5M6ye* z{o}`evWjQVTA*9M&CR`k|NgB)Dof1);pF7x z;IRFgQR4tl#su|FR5|TooP%hpAp#R)4ZK%|bE99rRFapM*F}s;5Hih&i=^JE1qDpJ zyww>QH@LaERaAzyx3>+v4;NbeP%yotqUPCtFSxt{Vg$v>Cm^tG7x+Wv9RUFW6nlo* z|7ky5LIWO6Aw=DtF9~43RW6_i2rUZ(5GFwX=;w?N8e&~aY=d0D2@0pHqhq$ZpL<+l z?4}Xhb(kG7F|iZ9!orb}k;9!#3#_^pxBiR~{$-3FK>7PF zAIq-s!R3AYC;^HwG7_78CkC$bKKkgs8W-UEO3A8OC%0f5lZ&>&n6&EL6E_n;QDZW0my+Fzs4`)hR# z3L3LXTSG%C->j~t=9koQ-(S~KQ@g42NK4!JLr2Gbr-V22^2q|PV`F&%nEd_yp#p)A zB&4L;+uPA&;@;#g!MCV3q$DKTdU`fSMl=;vgCJ*OSo9KJy~4-A**e%VN=TS&^r(JF zFh=Qf!p_4}B^5ydx}^EZroN)0qP4ZPtZXQFJ@Z3BY1#j_H;(Br^Cxt;L9N3AOq+&= z1`ih(8H-F)S-I@jUNxRVt@7tHwvQeu>FcxX?$%XYxbF}{OR4gGNhK62lSQkHmWO8_ zeEy3UFUCtAU7|MLS?oFKMvrLMDK(T-QhEtZYHhs^Y=O8}zrQ{X#L25kd@t%lMOD?= zLac%L?bUXe!h+91vlY0U(6BJ5xJHMC2QUXPZH9((z;PkL!A-~C>lan^?67-KUgTG?E&HfvFP@68)bEM{dyN0Ev@+GW*@8e z?H7k&0pn#C6S%FkO`jl^PcE1BA<|BzZ)?k9LCY(FTI;-~{`hfeOblUj9Fp8UgE}Ya z=UxNOF(gbLxA_xzPZksHE<5rH2naYX^)C3V&CkEd&5eKcik7b#R2m?%SVx7$kbhf6 zW#!)f{@Z9?AX;9Vi8sIunwkgeD7kXbYvI>Coix z%KvPT%Tl;0V+c$F1j?Mu4VG3;^n;X?lpr4;lWvV-PBPt!F;-e90t$lpQVrwc;|q0b zqf#)D{n-CrxAV@U|JSxU>D=Gj>TK<~YZuSg|8wu0nW3Q=DKz4fjR_NL>!g$vmfa7Q zBHA0&w{A%_eYl#gfjeM!ZsAbXl7NfY3OzG8W0iidCB?V-z_$z6fiEeXTyXG8&-%s& zcnsI2Uh*U5=x7BUL>b4j=$)LLK;{M6rEdXL{?@C_9BOtX)uprB4ki68B+g&+?6 zEo=#*l{yf&8U|iBk2*xN^70-KJ*s!zngicq@hxAYm_o!eCq2ETxUEa7t7|(qHSw$DyI>WuN^2GLBNTk#6$?w;|EsZ}IX zNZ!4BS57W$Ryz9jQ@XyWd86CQo;;?179SVw`A(dx&EF&L)J{xH_)eL2$8GKOiH5Kk z-b*+w&s2+m%4Mz)B8u(=NPhr-C?o{0;Kr2a?voP$TL&_g;k3JcWo2b4!X9+0#~C1? z{3C~lRnyNvGNo0w9GG+OfNHtA2i9kIaxgsyz}VcvqS1Bh20uSPH@7pKcNZC1lvmEN z29t2kTo3RV_?m>LdymvQnu($Mj58k13MCsdup&0Zo%t=xWMpBG(BRiX z{f4yW6&3NYv0+<}_4g|(DdmF1E-08^O3T9hr}Bf&em*Q8_m2eO2@!YMm>B5X%a}56 zU0PqaPTz+zH&^<$yX$l&j4Ptu<0ryJCAHh^1%dYrBtTFR&}?L6iE$@xiwpQ9z`#RL zcol5C%~O4S3g!dl#m#3&OQ}9sXlR|?-S{W0M7X%yt0RnHNZAYvELsX2p4Dq1tk!Ajy=8`q;<%?^!@u(HVzcL@3PS=`H)2L7#J9e z^&2g%)eL@Zd{fb6BJ7bJ81EN@vAVXlx4Vmcje_a3{1{ig=V4hzeWr>C05G#rjX$;+ z1_lO;UfnKYFgBJ$;f?;@`6nqU34n%)hUWI$GJuhFUhF0oyYJ=I=>M&MyFTM(2*pdq zmnOs)&9|APMcz*KWxHl9I9co`oot4qo>sTy)t{tzIvkDz;NggY0%UoSMv$}6grO%b zjb?H30>$mIlM}5!u$@NE)bzA>M_*7V z4)>Lnapf%gQbi5S7x{Yn265?6bR9t(1E)kqN0*nC)jipiUsA8W3(6e_2M0hI1&a(@ z?|IRiH}g<<;3*;UHm`r&VTq)*u=?`^YZ`&m>M#Q@Z|q{We}6__ZEdaB(Jqb`HzOk> zJNv+=PY<@ew6wL4{@!>-ARHN7TDk@7rlh8}w>*#u9tWgoMFp3FLLXFR%LEosY$m@5 zh?M2!WnbTm;^H5mD?3W0w0mL}F&A#o(}M~f92iitXOP@(mUx7&D1j6eF@CZ$J{#rb z%UN~>C|KT{;K3QbsQ_>4*_VgOR|h(K9r&G{B0z$Hwv6)3UrhTMFQKx!Z32vGWQ0db zdOX{5sl`JB1Nj^H2O6pH2A7S5l$4Z!07+@-&gSzo!N+qS2M3kCmGDB{P;1P zhli6hKQ}jBQV5FH=Wu~2U3jp8GopR%bLlt0oy%W9J4h#{N(g~v2nq^<3g4Wn*U-{( zgOWOlWznqx?}B(jRdawEDe}UfM_Vf{s;H=lbwJ(tPYewWfzv{zP9_!0o_SEuk^UcB z%T#x0G*nbb0UD~Se^y$Jt*@_JT3H3e_#qx_`t{f%Ju`Dw0N>=~r12M`WU2V1q{)*K z25q~$_WU9b!t2csk!UH)`In;Q+rg0$e&X{vIbdgG%x{sCIQJNuBXxIouegfI+(SK4 zSXQQ7LHArWTi*PLpR!SF`^VQ0$OdYcwa@*}niz+xXS`)l)63sq+1s2s2Sfz7G0@+C zUtZpn?YiKT@6cKJ!jg^ddkCVX}h@W&$jqoBjdxy!MVGsk4nO4{}3D? z8sa{ZZ@$*{l0;2^cnsk>slJwHj$715k#*HN_YkD|#zZ9}wf|_rEII~;wKav)ojXqz(Hqgek3=u(pCc9ijSWkN9+a-O)6qJq}xHtr(Gv4??WgtCk7^_LW&TB_SbAEj9UsR zOi=DcMe=fTdeyec;J(1<09E<_u&}b)gen8=4aO2|_0P?jpP)oatc9aHnHU)0L+R=1 zz8f_6LT9&Jg(!+!;unGP;A=4xVN~i17cK}PUAn1s6+xFBi0(2LT*0^M!mDmep56RGlNs- z$>wxV>KkxPFwp_cPG5-h`gnO@*8;AXtI#yiR_9lHRaI4(iIgA)DNpzN_ZfM4tAUi> zvu5%}Mn+J!?~FN2L}bNK^~jWW!$bbHIWB*f=n+_`ilfWUv+pkG=zJkyR7VY`a-*Ss z1E?%4tf!(-5V{za0!dHy|QKMLwXC3|Ae?RDpRQL{@(BL2k z1H(#Z^ew>V3U)d=I#JQoPGbQDU;Eh3JdXD_1f7-@!QkrX$e<34j#ibH;^09WY{O~d zWlcAy8wFj~H37S~7i6GDEG!OtcxOM9`&eEc7-4Oud=+x3WN9sp9INxsxP&WuAmC-h&p-B~%#y^6eXG15{Ntnn?NGaVfrJ-w;6 zstlDknVFeaLU7?S33=b-EeM;pFY*AchS<((NI+dE5#l!3-(6XV;@!i z^{=f@{f0P(^o)$D{Enu*N&I?sPMsYcu23%Eut1mvQ2fv!le|nd5{H}8hYSpk*ykG_ z9!^3+0=-sOSBKcuQDviJbP6FkJw3(5#5_O!IdhFt1cO{)7v!JAlP5%ojb`FvVsB;R z2O&#}zAic4f%#Z!LW;H3BaTh|8>^j!<8sqCSu_Fm%i%f=}s2hKJ)45)e-g zXcX%glB2`f|2&PH#{Jv1L?(i)hpK@Vi)A&q&A?EoU72UHZ}sru)Ao^cIgo3o03AAl zSCO7pUHtPJkjMx~?8o-@{G6QY#Kg=7O`iQ}cMHi8FHflu`SW&=J0ig1{)ZiRP^G1% zEf8<>NW*MN@JIT4q|wKrVD_v3eo4xoAKvNx-`LFV`1s5%Ejg}-0{&FG@Lk2h$N%_{4H{fq@bB}OqkSqQM?yLgg-Qbm01K1<{COKF zJZ(X;Kc9KJD27FnR!GmlAT2MCcx3~SoVFn0-)B6$!;JN`Dx;(%5<4R+YeX|}dwZJ@ z1Lf~)$AW7&$Ad=hZ}#yK>wu%LW84FvMWwEPvI#;ef>PAQ(Q#;CU^6Zc2L}g%FQJtXkGRRiFoEB*V#65cA;P;xJr6jz zzrPQ-E9}1ex#qDESd)JFnMM!r4hXkgWi=MwV`?{5r&glC8G-ueGPQ{;A0t(uAyxqR z9p;nx9Y>4w*5>Cw8kQcg%Ehrgny9dV;$dN6p!T1ipa1;%^YG{>i%wMmfT&q-Qaq1M zLTc)a?-lfe#G2Y#=aoSd5c{R2r6|`ZhWqb*k_Q2wL0Put<4AvF8>RxIxVT zCiNxptLW(^?}MFA=br>7553NAxYU~rGur$4GrJfV`RMTQMd#-Sm9+=1RW@JL_4Uu@ zJMn;X#1OBD2@4Iq{Ie1gFjj1Ei;iv(%V~3ZdOBadkb{#mnwj?R5))Jmxs+Cz$ZZuf zTdh%~TUuV8iDn=cl#nn{X3_=F|9!f#YqQ*Xoa1clx2TheWg)NzXg)aDGBWabW$C!J zrCaOJIxz4u)nIgRaC>(be{xLp{N&s0Y(JFH>iJ`5=bQfad}Q(B7ys03)g^xOr&ZrW zSes^jK$WN{D!x-#Q3ieSYtNH6I`b{L0ZFU?Z7|QR1@`oZi*w%)hL?%yjdRiA<>tb| z!k$l`uA-x>;PscADTsA^skSpg|0m@5!*fXHYoL)24TjvwF_6Ve{LE>hJMl>*Dgtar zs5*IxC>8vQuV!$@f*xov?IB5z-QC;!*I$67iQHr&%!lr@u(0qA+tq1tYq6oF;O4Fb zyM6*%#<%#Ycb8!)z~#%t#4gr_zwscI7*2oiQ8te4*@v&RH*XHuSy@_QkqZFlaT|UN z;hd3; zoxh|7MH@-<>xWjje@f{;-^TQ71pjsmCqC;_PjvC5Nl|{>N~hZmCn(6##AyvCCs+^0 zT+4mpBHZT?T&&knp1L8#c-y59>qeSy+KphnJ4$zgcc&d*J^)@c$17O2!9iGO0UjT)JcHDx~kM zaRI4`y$0*zQ)~ON3npI^E+VO28_V1zC}Q{_d{^{e=P)_5bG1dD5fGxj{PYuFYTz0b z(*3TNA@x|1m&fpta!6_@PEoOo{p`r`ip53$$He2m9{4K1{vW(T5^ZQ`@LjjQp*%Er zgQdF8ibf&mKVB}U8aO{c7ed4-B+TjS=ZJ@^+1e^}Bc-hD-o}K$=xEy|H^l~1 zl?6kMo1aIvVjKKAian) z(;OGKySHXEHHopk!!L0|bj34+3lnpQK_xUPIaj?fuP9xJ&4a_R|HtWpi&4Mq*B@RV z_^ar|8<3E0U!6I;OQx%Wly>{8`>vOG!*`FiZv)eftTC*`ucI?*CHD<8B=n}#b*lO+ zEOra8yo?hjcKi>VR(+w6ic2RO6vtaEFOQWNbgMN2S7{?O^a-aaZC~77Ny(8ZB&~JiOWq8Dnw7MYd;M_ySiWU^QxB^-XmZJa6f6B5p^ zXD8SRbN=%C?GbjdDJdycRXjN032xHTy8as7aqQpTJ-g&P?%?QnOD^G~KgDP3suwS$ zm6Vk3BSr@p-lXJYuyZtVGwtnWAA@kl$Hyh!6cyQ8TPMAKO@N1ocl~;e*YW=RyfOG~ zoPCY;8XmH?RILzPH~gd|u2HH(^iNBOkB^Uz zX7fjSg7`g;meJ`GUSZ+k`}_Ov44MlH3me>ar+}9LI)#p&UQ%2f(C_6rIi~xwHG_#P zYnvl|eXlsp!lw&uzuWr>LMdEFd}j<2S)G11X!B*#?o&iv9Pyjal|3z7`Hon1O)- z&;xc$tu6DVRAPw1g*OjekA8Z~$(eb72;i1A@_e81UU@;j=)r%0;U`IQax$5Kdx>~B zF&Mp);ole@KDf~20Z8$d=4NylO-M+HZUG9l1ui?_?))Xb9lR28u|FqwQ)4p?C@fc3 z*ZGA7J6qc;L~>f%ND3!Cx)x0_VB%8dpFSO3E(O#9QYwU1ul|iLRe~Vj;4TSO2$nU# z(5P8hy0LRC%23^q1arAwv!oSaFX>y0E!=5?{MD6W`g z1n#~pi|(zl_|g~3Dj1QvmX;KlgoLD$g*7S81yPRwF?r^!Ous2ME{;gw>w#!PYpeI_ zul@$GVkmZ-LC)tbEald#Tvr*{{^>fMRO(0nwI**sLM}A81c#(@S#{*hw*_Q{=+syP zs6=C52#9Kq;}AgU;*A0L0Jn8FHiiH?qOQ&ZZRct&dE=un&LRhMwIw+*&3Bx9+YM3E z2itMA0(>VjvSzGR1wWS$wI}=!t@j%O6kYp@S0Nwm9(6WDe{2dH06md5(QI@SvFmKx z5%p^=dS#)P->Lho@|k;^1bFHqF!$Z)?vY5qNa}{)RaW1oqHI`ruAWQ~5kts24#1aScK95Bl=kJi7E%r^ z$PEhxb-&A%iN9PGSFZzW{KtQO;0_57PkZlVtfln|KZ2AuQu;?|C>5k9f>c&Z3BdEN zu5Tg6CMKZ;(s6WiwRR>|@YdE=02l!AKQ%Q4U{r%M0(&C#FnG1z7}ol`z|TlI_*YzH zSKis(wgm{g8R_tc53qrR;_96BDCIy0N&!d?gu`@YwJq~|1MMbR$dxAt%YD>m$w&Sv zrwqKC=lX_TwJX8wW~Y(YlJjSBHVJpr`RVD?sMZ=1-ok~+k2eo%&d%oGIIgY+Ey&gL zOu~Grst{Wy6+A3baI3XivCU5;5(!9DK>C9O1@%-_T~NqtP}SYdZaK5^)kKTN$vShn z4Om-Fa|^vse62TX6+0&T1YwVN)ov!89--9nH-dp6c&(4PDr@?~e|w2YyUJW19ekcD zEiNxNtg~OJvKS#4JKLTJ#w8yx?HeAZV=%Zg=P)%hV-EoEgYE6xzW#n`P0hOBzuDfu z_grfA>kPuNnJgFskm$usW~#jG((L8$UX0$(8l&7FrGRS>diFXt zE9-D|6_gzvreTnZ^H8L6wOP=~Xx6-4iEG2 z@YL4TwK=YIvB#VoS zd;f{tKnDk9*-HyS4Johlr{CYtE{^wq7U&22cwI(P+`1FVPIsquoAVydW`x8FgR$}? zgPZy5oQ4J#^#knHEJ`AGs&9JIf&>Il8yKpu^3%!8|IDw12RJM=mG`Mn+StQAc76=h zfdsg_V+K<{L%(m0W@kIIY=O2`nRqWY8 zz^k|W%*<%$1kW2Q8Use8^Z4WBs;8fAov$X=DJfv79%199 z&N(nXG5)HNTvw!ENzo0bce`k)lFj9{dBEn?SlAT!V{w&AY!r(wS6f84r(a~;Uml6y z;WwgpWwuMqbBtF4f98IENHV%mwcZtY?U*z-E7v6dF70&4DtTE61u@5k4E#@g;=m7Ul8?Tx@Alo3i9NaxMiuY2I4#a)@ zv`F@cXmQxmhz-eqVKWTv$j~)HnjsKKR~OjB<1&U?65G5$w~iIRCGLH{4}cEF#vqZZ zHvk>7vf{Iwtp?^wtN-0B6k1YU?TQ#kII)+6Nu%&;s;gk#L#)Ru4mP|MqviUW`bWQh zg}*GJoK!SbDrcq}E=IZ4iB8i#9dXdRU4cD~Oia`soMI;4?U#>=5PR~s{MZF_j+2*D zOj_G(xx@=L7gkBI*rq$1dzY~$DS|NNwYBLsu2L*DsPH;ti|u{Z8 zQgWJ=Q2BCf&CDi#IV?FI+YW}@+dB{3M`d!ebS)_+bQVrjYkPle-b4w>^6PegYc)Sn zt3+LEYwu#7&)uHJzpV39<0CuOu#*W3&+_s)NV|^vRHBiZ8e-$7A#+k$HZ|>WC>0u7 zW;51oq{~Dcem-y*IX7TNWLNTYnRHt7yb1^FXo8)G4&o6NSQ|;|by=PEDU)$Aco4m| zR`5^T+uQTGJm9vUzd{!^rdNoUT~WzQC-RF}wwcHqDp3A~{q$*y6W_Xu*Mt7ERPH!D z86Nz$pA}cRGNIKhttpx@{}F7TQz5cRtl+Z~#2~! zfiL8byqY6_<-HkyEUg?P&p)$=6FNh1<+?dBD)NwGJ7o$RtKP@=de!;mJ62}jZJ+QE zsBQo(rDF3NLmDzrfyV6qoOy4=C+fMMl%CglUS1}u|Nf5Ro;5v-Wv^WbROA4cnN!+3 zGgKc9tMn9H1XzG69c}GC?{e0q6RG{u96r)}Uw&!y7A2Q~P#9q8@$lXsSdrBv^Q5d> z>tE>Ao^aJBFOj>u&lI{# zSR(KAKA-klTA-w;>KEDib6bD6!u~5``ZxSuoHM5+v`=Gcrj4GKuF>tVH5fv`BjQ3)j*L`rayniLxMyJ<0X{#ZKr)H~_>x%JV#79J ze0Kevb={Go1(HI;axGq2r|4TVYF^`?9X3N~IOM@j6RL+z`}UiLaylz1G^b zkLjM1p4-(mNxyy|pd&jko84xK3Yd?^9}Pv$81>f-pA3WV|*9UB{4<^Sd7<3kuzSZLKis0EW7X%-6Yn>7lFNon}x9V{vq zF+>{wf~ck0Mb;93I=7AASvyiM5pXfuNf(NO3#7F*I$r1IrF0!tVMH67mCWB8vW=GcsBvWKET^Y;_G0j+8~JOqssX%cu}91Q!-z6|6xN>pKu~k$S3K=%cc8EG7&|4i5pG_#oAaE zU=TU#q%6gWW{X6V-VrGK1h+qNYb81OT$*FbX-k9O(z>hLe}k#t0tWA6zMr&P{P>Y6 zryQ7Sq*JAm;4ew6FZ^z3%GL3d@3SnhP=!5+)H-`Ha43FO)4ax@u%5DPBJ+yl`|Rv& zfB}|23ujtgXYS6{*;B~iS65Vw3K90b z`VG)^A)gDpF^G8h&BdLd_+o?0CP3P`O?rN;bOzU1j(znK6QQCqx;$7A@Hn7>6sqQH z7}_w>3L{zYVKzn#@x#UyJKS}0VPVKutb)G-gUTp0b<;vBCsafb>v%Cnew^7~-iK^; z+fka6eXe90QjkU5508z5KzZp!w&D%Rt;KFEsZVo(8M-rF7WaaP!N#l`J+)v~bb59C z@@e?WC&2}fuMdg1E2= z&1IMBYpp^1U!O(-*4BnkOt^G>!_q9%yWX!`N@UfyHa1pKQ|tPHD$L0_L+^l_`TZr! zP+FRSj?Nrbq+YC1y6X5oC@4rlpMvj6GzpV?hnO$xdqoY+b@a@d<4d$?G)2epK|^V8XCpNd@{<00Mmm#sM6@l?q4xInW=(>FO4rW3e?NfFRgrQniw|r* z*woTdDK~ItD6;-<2!%U5FK+`enAFnJ5=AaRjE5JUoLo>+GGC%y)#`UUoXTA+9$sz$ zHLQ0u0`yp!VVggoHh3Kt|4ng7PD`tESX%7wx4Jr7fx*HC)-0{@WBOX_QbI$|aLte$4JS^i=j(c(k)V)fADaH)#Ypk;=u!|3#m z9?JHHNauSzBS07;0AjC@DR{ly9tg00yfD$t4RlM?rx6m$xE6P!-^A9Zrs3q|1e}Ks zd@R?jz_)?)@n=- zeDsK^5nXEI=6qMvo`*Obov?&U&R?S6RQnO(u`?Ok8;8ua;Wl~V6p7`<5hR&baa*EKa%N6isi7an6bR~)cJGXkD_k)-uFE8g> z4@w9iRBx+^cJy#SjL?z$_&DmB;CM})R6K@x{3dtRr=T!DLdJL$1AYEp!QY^}Q&x=| zI;pnqtJRK=-#6HP_>f9~CC-OvT)z$v4m@kwG_3OquRQ(WPPyxPzvP12REW^pAagw6 zit4SZrDVRQ`t}&4sRrn`y6Y6VXvY!XWVz28bPNV&49Y?=Qn#w&V{x5pj5mLwCC0 z$mkT?xB@=J??DSfS+W3nhpWhw^2z7akFv$*9p5nsEJ&CyHwl%ma(ej0uaPQ*s;l>r zS~61J{g{bFB6Vu4wRLn7LxY z25Ql2(&D+Ne@lY+TV}62i(jUC-^6ypV?*`OOgWHezcI>E-2$!4Q4e8eHb2z(7E06UUJ?f;GU(3($4QO7jj1+MXJLATO?*KCXak>D@RXR2M zYRgjL8hG+|X@YG;f&|fF=YuyjvBo#iAL<@%|Ma=Zx|EG-0Yycjf0B!BuctYw|kE2OWx+wA%T@e#db)OnA(Y>T0mG^lqJWfKB?`*ayPdX0~d zj-L1Q^tkOzqH7PL_v~CAmgnTq$e(MJ8)U@Asg5OKwo#GcS^!Un6SJTc2p<8w7oN2n&s@WXE8E8IgvycU~LOM)m=QKz(k|;-Hn;Flhb8|!f8}}C^S7T z(|y0(PIQ`;UR<29xL~Tz7H+ZgBa@I$_U%2YZS#Okgyb4&WiQbf8RO(PsKL`I!M0St z+IJLf7vFSWys~;>MS;&)ai!n zXSmoP4xS4%gxT0LWRn2KInU?MwO;11(_0Vl1z@x{O6RoZp4XC##E4J|sUIQVQtm{SXjT!fo_%LWh%fYTi7e)=uZjESMNehW3yY=~5#Xsh%) zP1sKZFmESShtH}|90LQKlj|^ADajWlm}}czfVlVZL>NyFZdNfv znhB-4bsOvJ&@2%{L&GA61JE(G`NhS>!2!sg`*5LYx^!++cZihN{@JTHs~S~+Qv;|N z2%=eTpsf?u3R^D^$u23ewY0P}Gvl_Kt;VNjbgN@FaHsQ=$_?So>VOa|=H<{O#rvjE ze3pb4W?`SAcI$saASLAvp9)NL@-@h*J9VBgn~-yiQkigb&@tYpU!+7T1OxMwMH@6% zFq#$8ktBsK8>YpU>7P)xV{F z_$FIdgPv1nrl*(u`ZZII-nZcDs5&6 zc(U(yMd^P?+D|}DJ%ziML3Z~gMW)9UFm*5ht?l6A?7Og6H_J0_^VtD6e9!OcZar!0L>_pYTz$g{2!m>eUIRYo(%uzf_0jdIANkV78jI{rb+Zy154oosO zCF54LL_bJS_#XYE}_*jxwZ7TH*DSXf93`VbuNg+}dWbMxA#@mMpva58n;-$29D z^oL6g7fj~6Dl?e=qEQ+(%YDOJ_hFJt1JEE~Q&aQSYyTsl(*QpE;*v*BTDu`8p=Q4x z%bn9)ny|y<)nwt^gjMCpSl~UXqJ7r?cWl^zb(AguDZaoA23)6|*srok6LQUxjZ0x- z2&7E-a+&=We(FFeK(Kn4MDj8%Ar^VP64!}0=R`Q*S43mR^lTTlB_E|1GbypGO?9=;Ld?aG$x@jDNnGQsEVNY@;89Y&6XfGi2ZmKVxj{ z4k-wppsCU*eP?$^eF7(>h?<7@MB&S7RBzkR8?hK&K5`zP@K#8M8i)HEHfSf=10*y# z%{1nbcKiz>ESx|LTgBSF(%tij&ytb%DjHFwsT;uGmW=B6>P>YxTc{%{^!4 zk(F16^^Iy74(8bJdwb9Jc*_LMnwoydW6Y6Mqm$$J;n`mt3Xe?8zKAhTh2x%g)v@@C z*z`l6=vsezrDM--X-*SNA>{45`bS1j zW|$9!ZXq&s1LWOKL|iJRG1IBzal$Md2x!cujVMMZ73LcVDm zFH0(Q_b$f|sXMVI#BPfC$jZui%MoR4W9#y$}f__ z*KazBvM~Mx`6e)RY6hV0Zt_DEYxIC+%;f||R2ErF7hER7nJM9kcLVxzg&5?q48I(#cZZ&CkV3tFvjX z2o04d9JUp;cTc1!>_1xSO1}yTY&WjxvNMpNX0pLLk8wta5}ApMCw|939s3cCbtuq% zaZbs+#lV@fPhz|-FkkE!YkW@27HD6xA{bxU_d_HtQv$k5_XG^o8=i|roJ0gYWoBkx zZ1!@wIorl~+S&*M{uT5%NN;ULmn3qo+|nIFueT&7?(FQCo0`@)Gyo1Slx4$<6u(rb zCQRgqsIl=XL_8dzscuKhqL`S$fLzx2J%xesB1z6ccr=*TseCQYI6N$j={Hw+^)pzN zD(b0Nco!4G??w%ekd#o2eMGE{-w?GE%7etQyliuZX8&gLZ;jNTe@l)seCB+&cYut(R~NUA$m? zoMyAxeP2gMCp9uMvZ+ZxaHsybjmUyLh$V+++PY@Y^s)`QeR(AdZHL#s=ok&;s>x;R%leTF5Eu zu1r|i<5Q7lXZEcf+Fv*3q=)l$wUkgOXBi}K>ZiOw(>5m&{LNJl@_p|oBmdT=Lc$`l z$odNM#waJ_3N3##?{49X`oy%DMYG9Q{p7nyg{(J_<6S+j^RtOHk=FWsRi2Umxr{EC z9E;Anq-NT+61cGSKl+v>rIJNysx(2O+Y5oWj`nQ}T=E>wW&o5lbUS`X!27*C2t+w8 z2Ae@!Jar=9WeP`&MqD1WUpr!OpTimo^}`H(yXXJ;+g2Nd{qC?i3i%Ul-y*|i7)4M( zTMZNy+1*I(Fx}ae7D!yP>*6ehIUrJ3|Jisk;O^B5bhNkkO)|TQTKAGQhipTA{nqYo z0P1_wey`t{Smkti%V0w(V2`ci_|`JZBihGR zPp$jm=raIbi;A61eRV5xa=3VTc{%t&M-BH0q!y8SvrVTRMd~ljOX;HOaH<*k3C@~$ zZW@FJvamr^+iN1qvZ0lx#Z_PXsGhVKbv_{uuW{Se0O>Tjv$(8t*<5(VGoik@c^od* z)Yb8FZ~!BK)Mufl?u5hP`$ELrH9V}K0J%U$zld%l?d>bD zVokXxA|h?5yW8E}T^nobi^HY+?(ml!y!q&3UA)f+1_q3EXhcm-Y4B@o;guY}v^>4N z^YZiOW@oLftg!O(b8>!bG0V#jRffCnPRTox#{Riza9WcX$rN&Pb*-+c@j2f??C-P4 zlMf6__q}6dV;fyuG%z&G8br*_%%Hag47l$vwHcx}ndr`qj6`M4G`LuZiFJPd{MjjDQqFbE!Z2 zNk$hUFiz+7N`S*PAiVox1_$J1XdqstsqdY&p^Bq5g9#2&Gj|U_95%2Z%FpIA&t8{7 z$}%%jPsT43KQ-QFW`Mb7y&~epzW#Zh%NW&O=|W(hOGBw!Yw1rKetbhl|LB1EY1Mh) zjXu0p+p;~3BFZ7~p?(ltv9R>e^pC#1&FN9tQOb2norS%GcX(NCdU-jsZ;;!r_Uy$E zPZH>l?*85%uu(DNx>1j)z}1eA>XWdhh}TPI>z1GsAuqYNBl+p+#R7E%V@KU>bq6dr zX5-#NEXC$;7gMLSSRRS7Ji?DE5Wt}n2V)tU-^8Xs_e%#9YsZpaCZ;$&DH2yr8`gOe z1kfB6WB+&`g6Qy~=8`|pEvHX;^Et7*^jq7yiC}L<%!{+>ioeeV}Dd!YCGERx{{52CAX z5-0<7At50lJbZAj&VhxQd1tcl6u1(Mr^IKw)9*AVKU*fT>hCNxU3LaPkBEqHadrlX zkoVaZK0ZGB>;z%I8$e8uynape;)SP|7kqY><1b@)iD6r22)a!60nWpqvNBeDbQz*@ zf~B9netw|^CcTjyNq;s0AF{lRM@&4`=w@SQX9s960fDAK%;y7o%jdh(z$14^WbzmP zuV24PM3QZ9XrP`cv$C>QnGcOmPq(>jDnwBTJ-&x!i(!HYp2^)OzqoYkzWlbEw}R{B z&;f|p4NI%G*4-xm-x(ug(sw zJhwY$zqA<6UuYm4C+qbLtgK3VD@tCHSmUBC?e$9&LokH zMFsrkKk-Cl*ZhyO-?x7;^E7ov8;vvzdB~A8K_v5W#BK~7wKXFac7JXZe~lLX-;yA{ z!Wq2EteX}VPdB_a_EXYQvU5uG+}G2skL9@% z;Z!hgu%J)bTllPQLWJMt`;d@ooQP+;VdVW~*lZ>qL09)%Xv7ZzKYH-Fx$ii2yxMA_ zKaQT0(>dZ5yYb1(^W6Z{hQU}pnI9I{Q}^!*^77|LN0#&TPMgEV4(Jm=6~KW{aK^{R zQrL_h;_J8i^4;$0%J(OKfN<&NWeu#d+7YoWq(1Bz)n++2&?QZjdHI=@)_bnp{Y!4y z86p5nES%y^!A?Kb6Cux|Pnw#V<^zL9ok0za=w?4#EzLHv+8vJG!WSD$Ku(^KnK`_d zwALFH_2Wl%MMY>xi093jm8Ip@FUcrtSdrW1!2$rwN&4tjNgn(8~1L2B%+ps-$lFvo!(tsD_3H^nR*uwvRAzO{?Ic zp`p0AxWF{RFwFBVi1xWWz*oM#Ks+zeC`(|{EYqq0v9b;?Es4<4nFFkRvEFs*zqtTl zE{}tGUWdg#jpb&qQ;ia>nAB7fV&cS<6h4!lXCR$MS1akz8Bs@1!A0N(Gcq!EVZ|@; z@RVxx<(|zh8Gcz4seB=}(J@$BOhwrs9z(%6F^7aBq_ixx+3gtJ67i>4l9L*M!vVO0O@L}quWChuk zGA=#FA=d}V+`ghmuW%$^cVWz(prqNNdO1sU4?FVCNrQ2l16FLWeg%*CH4ocs3ANQ= zOyo8OMem~iCQ>>GhWu1S_Mn6q*vCqJE`?qs{Jw6FJFk0q@m-w!$B(SW^LOpGL_!qL z|7VJb>w7mB106<3b6g+Cs1TaxaJbdgTXo#td#JSkefaIm;lvk2=db7s4IQ0~R$5Li zwg%rn8RGZg=5bJpPP$xP^=h}3!vGY^I1g8Bbwtqs@7T;A*_R;&W+RIU4Bx24e2Vib zn#ae_zIiSYmfc#6e5!!Q-OsbykCzsW^))`i|Gro}DI|TKx%5*fzDY#QJnG?tX!-NZ zSEA+!|Gb2Gc3V3;eSQ5DkIQ@Hsp9mzk`mznmvT$J$PkGLlI|5pg-qn3#_1lsN?cr= zfM4__F>xlJOHk{xQS`~*PjE=q5r_>t|_PRv>wQ0T`{mP5`OV)=_9g zY=ZW9*H1!NJi!K6vE}u#v=gQ(>YaXZ5ScfAL~oJ@ywZ!L{QsL3D;EWPo}G=&E}zSWUW2pvyvsP8@xrDl>Du*MKO1?A$g}Y>H4dH8uBx`NHyYN=nN6 zvu)LwYe1jo$*0xY&G9^a8vgn71K@9^S8^gp=@W6wnXq<%Fe-y62Q{8JO<9kw4m#a4jQBO7ib(qC)J+1=3^Q`fh2vAM)AtQqZlUH%)F` zPAC8TFgdt+Y}gh@vFB9ECXyB6%rSl)`WovP98Y|Id{eeRg)8PRj3bnx{G0a3D%`%d z;q`V}?=wjU%K}ztXRNTQ#ov+kC3cpkB0j?RimxMk|4oT8TKuS_qMTA`mY8c48?(?(Kn>kJfxxVqk zT~X3x3n@ArQt7uvj4)(R(ADjA27kM8Rq#7)gB0gi9Mzg*5pGLW6ETA%rG;lMowGgx zDLPGs-9@{LwFGqmM4uvKa<&R~N}oLN4VaI08Qwg0l^>uiCw%aeq#rMd8U)w|0G{HK zlBb4_7B}xXThhcE{qMZ3t&0F**K)V1D^K@|1>s2ML*ofZ zaeNBXhee|;#hQk9x6=%)?;hxb&)j`Gja9YjR&JAD`7Db?Y~kpOvu|YuptRq1PfWyx z1U-9gY;Vt+EbO{93K%*%3%$`z))GIDl9h7eSFLShO$|4`AM)yWB43e1vtBG1S1jmR z|Hz1klT%Ez-Ac!IQBhF^1%-c)gKBSY2P}(}$5!i?LsG0OxGIvr)=C$u4PNtU+K}=029yO;+yDj+>95k+| zsGXfe9%WB^q#hj?E7{#%l*8(!rIp7bLrn-4+e`>L4=~A+@4bSVU%a~8Ku5Y0UJ#}5 zST|ptwYf2;G87l3*4Z~9Ax(*2jNGjUh5|0s%@DNz4@{=NHhpt6{vCAuk{Ylvlt_S^ zxyG9FkZv6nq$^~^Vq-j(V1rM-fjVa{GD`~Vp$Le>E02|xC<>n4d03997*;r2HguSp z?D}*T9+~k0oPCLmgP=$shQIzJsy`Y31eQz3%X2L@$wMz`V6}ZjJ4mXnJ*XG?GprV{ zF@&WRDU5fbTtC>p`Cc>t>yy z|Mm9}BoeunY57*f41kzo&2oSfB+ylTMHi$9!6keDru^aHog7i)TpwPN z$=|rW_!JZsmTJ0Ir|6kJ48T}q%TB8;#V7z!i zkrZYqWm$3OXZ*uTEOxPbEe67LJrq9-WN5s76Btzi!<;JBeVW_scbgcJtek^Up~XyB zvG+GY1Su~uQdLYWuyZ9|etK-o$lb;%a9YX=_)ccx-R1VcSo%?$rPh|TG+k@!9pHv= z$@#gw&n(^C&Sz$3fC=U0M_j)R6xtJqZ-{JU|wF zt`qY9Y=^s!1`@T>5uB+lyRxR4JtHI2mifLo*8%Suxs9BXx8QYP`z2a>f4*-Joi+%C z=ayX?O4_P(yKN+W;oM|oU^<LdfO!5#`tD={Cfw|mre`bX%+ho z2`^+&wD%rOqNw~aE6+lrt$pe#Xg(nDz5w?7IAcvxKWL8WhAmz$p_2;I@s;u}^YJK>eZ+%=G;VFlZ31A^M(`E9qvT=a9LDTur<+ILr zVAv5>YH)7xpn9^-;o;%Q$+M2{Pp_}9>zKIyz=&Q+wC2 zBAHm;a&mIA{?h~m=74ctTtoryUFi9-gj~i# zC*HZr-}_W3iz!B3=SF@{UiLW)E-K=l1gi%;J3-^x*yWd+&gier^PgSvpR=S_jTUvU zM%q1Y;u%DEno3L}#x^Aio!`ru$~#Vy!~mANAQ3eDmkkIt zdUSlVcIh)LvyN|e7Qni!#k1&!f`LJ4S-|VvQ6kU=MDh-8KsA8wjg5`X^1mr;WIBOI zk_*hw&*zZUR)0)k0ubQGYfd_0VS}45KWGasY{H&7@dd@kj@LV_RaRD(={2lwY~HU8yiEKnr{1IsB`V%nopel4@WD* zZ3ksKKz7s(-?It#cfhcXuyD};OGpNHfjwfHfRNJVt+_P!MNnC7;iqINGh<_8cmR87 ztXPV=2_QWHlYqh0*S82)xol?~yTgdUXIIB-&X_^w4|~lh5$yETc}dlHPJbaPAL$+O zI>0R}=h2nFXJ)Pd{DMAsv&zQ8LdtrNuQr6B;neoz?UTA`CM~eBB*2$c4G-an1Cx3G zlPi{{Tfu@vN;v)u9uy{chsi5O{mLnY)vFc=E~%{?c}$u6d+sw$sj2gyXD*mMH-`^=65B3TVSfUypj*a@fbW5n}W!RX8a zck8fc=bF)}Q!{|TKF7U-h#A!E!F~Eu!jYnE>*6;T8h7-?c9xUPvL2u?PXfIZXGR7< zpo4{)Fro<{BM*)iK6zI;zfZ@fmQeH1x8~2|JpA$qb6wZAMo*QIFv$ee2e_;7A3w;r zETc0sXLRMwM>n5S3jc=NwqL(`$*lDOe7M-sFMcr&~R>U?&`zU?CcH)FHW)A zw3L*7vy$iisk{#Uw-Oa*=#mZ4Su%RCO;thTF=3`8hTaN0CEM zbFyKvv??eJ3*Tw0JLr4#2emrAgsj}Uj}QaFa2yK0CzIrs^ zW_nN{zq6;#-%AD7d`|{d)|^@FeE9jx7mu&h=lmaqVPhjlGEGsmy=%%!0CI$cFl5iC zpX*l3g@)#x^yG!$;DmaT$btEL=*$8p>-9hTBr4&nI>PA?UyxiL>?> zhrvDbjYvI>`82lnuCBfiJB3IQ7`G_}>N?Yr7Sf;B2s~%4XM3*p!rwMCGjn@;ySBF0 z%p~%^8Br@e5u`#s7n5USV^dQHu;Qe;IuE!r2M0%bMg|KDOM_lnZLNE6B)P}_ta`15 zfwS`oL_8c2G4IK6lLomtId`Yamo|pdFiw-x(y9O=k>|8EN<|#r4HgH~TgP)_gI3=L z+l{cP;H5u*{(SK9@$vKXV@yIw6_1%Ka@iaX3JUt`ufJj??N~I`4OF}QT*~G2 z02buVqFZaq^Yz=8_a6bN78n@X2Y$htB&#warzL$ckJ#6p>1)wSM0SjBlSeFaPcV=M zM(&x$J5am-(S6-T2c`_wm!TO2|R-_RG3R zZNCV`G%)n2qp5x9&L_0Mu5F}ARG{e#_-y9R`Xu;myKU4$Ih};W+ronC9oqfuOD~!s z#p{IPN;Mfk4O4jWhy?*;bS=LsA>KA9f(9~I7L0M4lo)@N!LI-w=f!N5JNFJ}hf7}VFRmkVp$?WYWctofY0!cVEQc6rN~y*C2& z3l7;rwQVM2z51WYf`9%=7Gx6qsY?rs?(rY!hqJl#)>N*`^ zcuqmzs1NE4;U(>66d6Oj5b#fQn8Zz6`CG9-UFKYUkeBSTWL1HmtR!pr>l6RpoQg<2 z;?pOY^F-^2($}L*N8=b~+%?1tKGi!g$#RYTol=6&<>l}2-S%db^`2;IYGPsr|Ni|O zV6QedHn%r7vv4>T&M+wvkr|+3R##6h_H_ZV%Bymqs$z{v0H?0~t9yF&U6*)s0CVk(7y0G2&BJ9~3| z&Hl~~69XfHjBk8sNZz!coZs~gNJK;g%O*)8L0>hQ{S$yrZ4H_ntaYBptHORaGjnsC zh>)(Xq`7LVxHsqsc*qt&an(63WKVTj(_agY&Tcy<>^^aRK%)RuQbr3 zVNk45>g(%!c76`@UxVkdarV9WKNaeAG&QGtBv6W3)<3PU!w8PKuFgm{W8XEk*R<6N zHEd^>6z#BGp4xx1nwx;ACdIC4tDO_@-+;k94e~jVGG?#eSa7iFOkdv#Dvj`@7e(q_}lOt{hiyY&AjdGlyQ{MZv; zG4T04zlW7Yag>9&qB95%%#PrIjNL}Hvp9u*>yIW+PfvLx$j&ZS9-V5~p*$ACf0dU0 z?1_D`ukLDMc)hsD<3FidPI|4aEhnQPQ#lgzsQ3m>?02J9>_k}n7cPS5VlN0*m6mdN zWA&AcF!U`iCpFz!O;DtL9Ii;05LRFJCApuu-a^AUvZrSJ$Hrroo%g1mmlX<>gHPsA(%Sy%sz30>FBSX@sp zy{#=kP7YdYHy2_FjPqBQsR&9DVGfS*o*p_7XmxucKRY`+ibAMJy?AP&$pa(skS}84M0_sErzc@Oe<7v+#_Pz0K;C?{ zbz1)LL8*9L=cV<+d)*qDid^g64{Btky03h4y}%otADpq~Ywf9|zlMPEi0 zTajqIGMZVsT%%lHpLfaf!6EZ>K1mP6e9obZi#nT7#1tvGdo)GR)9L8qqTA~01@KZi zIXQd#gCs+LX|QY(YiU720Y67vATBO$8Dh`PdYG@fuP-FTeu3bAuqDQ;;WelE)$J|% zD1_P|Lv*FaC^Ei}-0$KkIL&{Ag~{04mz9@02$dHaX;CL6UUC#4waad%I-~@>F_}pBR_3dnpP0C?~wU@Uy>U`QzJxOq| zuaAa~&ikLn`1Y-(l@$VL8yg%W{ugiyz|p$}nHm`x+1QYhka*vt{76Yj$MQ8wb=)?f zDZNpYySux<6C0bG`FVN6St9qqh{6&FwcGsfo{J!t*Var-O}mhI^ys6`0irPVT7s~$ zw)W!kl9I!;4}f1jvf(`Wv~P+Tf?kynNCym7^V&KPX(4~16JQE^j=J@8Z6e8~@8TR} zv9N=~$JVowzTAGHHps&D$ET>nk@eSRP;Kvo4jbJBdHe42`LZMWBX%)cJ=##c z!bB`~pw#ABU0YcuFu$h|V6!|~wy=7EXZ`(JFqM{-oEiK(3-5@fQ zlrVG((%l^fDcuc%ba#i6!qDB_-5t+4|G2N~UhDn#etOsYY*^}YV()##FZS#?538E8 zbKKq#*G(&o_sGZwtEp)Lq3-e>`iSt9NthXTn1jAOEAOrbRBqw>^9O>dV`Y zLteka)O4QLbBvz<%#k(3nZ259sv)@ATJ#va+C({9$?_VjuQtcSyAFWn?Y_?b@sZE* zrdQ}3iI|7|;|+vN5B7&a$j&5ny5SeOpL_M~PW`UYV*`Vt2wP!gs^*F)|0oW2Mk1tV z;$b)It@XJ5w`aF<%G#c5{nG?qCcR#z=gi5W&rvDTqiR$vV@4TFCtUAy>nyKBm-S$m z2Dki<*5p5oqIAw8&gX^3ae=yr9wH>(jRrCX{x?{S{o+un7#e&t2{Q4l zt5>JndwYA=XS>5=V^m~TR#t|7Z*$jHZ*Fhrn>^^r$=M9vpEjFww4`xAeHAb|IW6U`qe8jeY$7<{lZMN zNU&IDE>6ynfB;Ep=?ZY-rJwwoo13G~2Iv_WdJ1Iau_>$g$4!n^!Gd2+c&tL(y=+SZBLf3pKgzjj*P%>W`i$a zJsod$vz1?*Ae3>B+L>Df>(?4%W0MQ|&cO7C%gI{Py^8p>p{>YQC)nAfj+mb?N!WdC-eCXI=Y3b$Y|e#<)#TNTdQ9e0Y}dp zR$@!}4Y2(rpzKl;~u&s(wH_MD(3ir2Zlvfq~vi^g`_w*>9f;c?wRRW=OUk1_7c z&ut=Ue=~pDgE8LmshdL;4NnGK$(INTXSAUB0euX(JX@ZojC+d;2rGML5G zReQn-!NKX=EnjG&3|%yOr9?PQw;CbD}P4< z0ayE2CanwiyI=yYI288gnr7D#mEsNff` zD*xW`8aeXIRYwdy(F?^2_S*MHglc)~oa&D|=niA{xtxQx3)N;WJw}^a3KY6jL|`S8 zzIjuveDTa>)(OLv^KUI$&yLtX%Zf(li!`83=_xK3-SBoZ#*na!|tebJAZD3P-SIvxsd48=bgPd>!5lrbfyB8 zknuVkh~e-Qq+?<-tnzg`UJJJFDtQ@c@e~cs==q1Ijh?r4zzjox@gQSnhOfm9P^?hB z(sXyKQja`{kmqQrJ+LJkxc|vUxAUbXJqHH|BO@a>w`<{VPvYa_1BeVhehdo>drAM1 zO8DP<0f>kgZ$4HR6~X5bo0~I0)zZ)UB05_&KaT2VOUK*pOq9-h-8XJUw84kB;&XJj zwPkEo>B^v9^5&u^!88ca?V7)cm#fkpJ>RYA-o=WVwuyGYiW=3gWiSjceS2_bA#1~u z3SZ_*=YyK6w)zKcuJ^ZTvjn9x25-$69@H)t;CzJ>~&FlU=lipMv>tc zVll8$HkQcsL)LjJ(I<1Rw!=E?NVl9+uA>8AF5~flswfh0!(Ce;R6A`w9+*y-N}nQd zuuI@vKFh_tiHPw0sf8649q$N^&7mGYo{w7)`FV}q+CC}U`gbwyq*z?fJBGk^+aykr z3W0@#-#_zX^NqJRG#upuL)~z&4j5=Y&fR-n>yI@L&9q-$3ds?+?%3qbHGPZG&URwf zx!Ap>Jn88gRk5^ZEjJ#To_WiF1Do+&(3)rK|N4-SO2z;`_3`63I{v#sEs7d>CgsrE zy)oHg!v9MA@hzR3|5E*vNFdw%An{c92@7A`I*L6u%%wz%XgNTu0$ z$YuJEA8)m_1%-rkDoRb|vJlbn@bG+meSxS3zMlxbQhwduc<+n@8SbSpL{S`5Vo6?Z{N=_}wmSto65FTn)spx`ykjQ;G>QFUQ|_Ec1EKTOVnW+& zraidy4Ukk6A+I9;RC-+r1Q{uDacWXh()_hsiQ%E4lhgZCijKsMaJ!Vtye6j93lq_3 zCH`6We!yaP85gZB<>kvm{naVjoDCWpw!q}d73@h2X0iJ)-#$hroO#u~ROr)3whVYE zYL8JSP2aOodZ>S(>L;G-y17l?JQ`{_Dr+cWjStR)RiA(vt7S&`@i6Fv%fH0NhhU*bp ztaf_l!j6t|Z7ZC7n>Z|S9%^AYxNAD{CxX`EDJVJpTFopygtyy+(=KhVLJfMxyYUl1 zWgXcPpG@3OeLln#XS}=fsLvXUsw5Z>xmXz-rEfu;Z*-Q0h!}d%syj-GN?n&1-%n1_ zvo5S{i=!!4RLr6gqVKW8EnQD)_5FT+JcihWS5J*llhz6Uj;!qt@}S;cH7e2T^xsA0 z?NmRCY`j;l+1yE?mbu!`n@?=J*BA@WKVlexz&TbM`RzayXB z5R)9z+-)w4j~Dsx5q?mT*S54&=XjQt_Z>O!(oG{Mw=ns@43XZftPDr6;?)KO(*G6F zn-Tnhr8Mf>7w@E(Y;>}_Fqi5nOM)ZLmYfJ-Bz?eM2p@_^K>-mJ<>BE`nkMDtRWHCn zg8IJG(b3V?PVonxObpVWB7n*F;q^=KEcEbzfPe`oKXHE-d@~u%Qv$_taD2>XyYdIP zE3@YjN~v{qDE{KMJx?25m73MiD`kRCd59(iRH=td~GNLO#X25A5%atn!fI=iZp#^#pxyjLyalc zzv6@!U4CK`>LC^sQK-vz^7K9*GUB!4Tv}+k+aY&iqw{MgRE1iNX#Zeg6dkHc6colk3YMk~rhT$>otj=};YxJm=X6 zn|f}8P+^FQin`vHjEshMf3;nlzguF|Pi(l)opyh_?*&=e+3DfGIT@Doy1QIIUhjvC zXiG$A;0wo#bzt4yV+uy!dJU&1CoeBADXFQ)F*3e>eTa@Nbq396IUJ$o=TGt{Bs%Q9 zv-udRq#;n%pI;UMyEly~c;ZByN|1UixJj7WE7cvHpMyo@tr8KhEq_+L%VogB;PAM~ z_hD++`)Q~5&=^eSwhZIK&LzcT$=5NTqx4t6?{n7g$NKt8ig~G5^zScS_J?fev(_^W z74PVO@KR7U=^=TJ_t_+__>Caj4v>8N`t@rJ>I3yTolR#vgo@9)cI#+~e9f6Z5fDCe z7Bnt4zvucxPuERL6NQZ%7;V3hn`67O(hci=|NVJ<(D4Az@y*#SGoX8Fu3I-$J5HjH zRy@`pI=y1eBtkrdAy>-xQ(a;H`n5A>TEUoPsS>YN!qn_;!AlPQq;=&t#$(`BqmsNP zyJn_hYbS#^6ezH!f8v6Yy_fS}kB#Z>hGELLlxIVtu!xi#cpL${L84Cg3cX7u2?E<$ z%k65M24iVENj3M)&4*P|Is>mh5G2qJ=q@gd=zV#}K60?wD!|YG?%g}nt%HT%-+;?5 zP%HOsU3~iJD-Z-)P3~&4vX;Q^OCwQJXFouEGE-{@AA?LSmohRuOrGwCM#3t&%VW2; zv9-l2FgrVYeZDVbyCR|cY)z$Dn~2xme@k7>Twfmu6Q}JlMtb^u-eQR(=Pi0_>h;NT zBcOtBuTIS-o(|ACL@#@)E8AwC$ws%mr&R>)$l|hIW3KHOE^T3!pvF*C=P2o@E;=b> z7IwM%wZdIiUC>YBi$5`Ce$A-|ZH#ZsusjtRj=ZGh195FxG3}5EhSCaWQ&ZwV(eCaU zv~0cT6|6TtDafpLTTZ8B7#MnzrY6S!PKQwsYDiC4MUGrqRkkn^{C>As{9(38444kN zg43XyJ=~*nmaG>QO$O(9LKfxtC!PU1lDZGjX=#*>1GIduqC_}0=2}a{i^tK?NakzC zKIPaMh>9)@wnEtdR3;Qclq%a{8F`x(m>sPvKjItRb%b0GuEL#3WyMV7!&eq16WiZX z>!M!Wc6>uj*4HFPm;qT865Swm}V0qa%39JU)r`zKorRTfT*@cBNkh!_J zv$Hdf)7UpDSG+Dq`l6yf`T6uv??-}UVz!#uWf2I2SrP9ixoW>3@-);CYQN)TCKXSE zxOdrV!7||q3{k~bSF}x;*{c_`ylopE=owheytydJ26B;{nm+$EbN;VrrLfr5Zkw8; zvgb!uunX5@b~Va&>8;v~A0}(P-rlOpVSatT3T0RmX#UOJ6a^yvwgI(=RN!~*IkeE{ zs0|)hkasJQ!A#_yO~)Vldy@QXzQ0!#5nfrksmrGb#OJr`+ipj*A`+bVlykgHFkTip zzZoA-FnLlq#l|HdV;QJlV+g?W`7b7n~>0bsL-AB;nHJhI1f4b z&zL)m4ycZJ2q9^Dann`aZ}PYFhLoMy-jw`-utmz1gQYSTS{K{s2<+7ynPig<_uAP- zvH26XObYrYzqIs_I&Hb(3~`p5Ay!Wlyj6d>nNo%!#prdLMDBV5lKZc!#Js37NxowL z&Tv8jUW?%gui6WH{QKoRluzOi&2wZy8~w6zS2De-m(tF3+IHV`)Dv##lnYAF7@L91 z5ja^sKW9p!@98Qs;d<9G9cQeow0+LVtmiKQgzk=C^f%WvoJHKe#u@^;OM~yUj7)Uh zu;>gUwE)OteKXyU28B6!F-KblHeGVKgG3ax?%cW^Y4}Z-zK)|mCIP{`qS{O z-i~Pt2;#{?ocfcQE}c?V&$^bCxG41L_{O73S95dDNZ3Mj)Lk9sQ?6tCVAkA-)N=V% z>!b5${?$9TB6Q2H z5c+SdTyZ@7@;1(lvuu;Te^~(exw8%he*2HK-iYYv*$NY=tZZKh5uZA5%oq#?3v699 zpQ$O$M0`N2J}cf-?K0fl`Ae^~W{a2AmLtYmvM+MhquPwLIYIR5JMUJqYXa-OhcZ2R zc12D`gULhe@K^U_r*jzTTgPiCpZ}fUIBmD!b{^Io8bB2b)oFgt@9OXW!mVL`3N~a? zq8qARyJ^B_B!gXqA5x2HYrR>`3LpxRwf?o(lexUjAQ8hp%Jk-Ow?io@H=fWBw;xv3 zdVmZLaVgB9VWuYg^43~+eaugV*=}V;j6t1YXSlsCyEP*>8XH@KDm3s#4Xruu(?`YR z#Kfv)+(6Wt`daezM63hlE~($hCN5i~AvAqKjlOAxG2$@Imf!baiUt=qmQkZBCpWj! z{&k_Eq@-lAb|Wew_eb-fvV9UZoNyJA_9l1m&G(4%!-@%F|4ziNqCT6Og}FI)S##Hkb#!mxUrws+r*uAj^f2-c(YMF7xe_<9 z{mSrOC*K#f!E5tI#g~XzLlt{e5^vC~!rImx+wzgaeC#v~rf3LI;i4r$T4B z#wM79E2DE+h<wB+PtJ7y@kW`q8F3tV(Wchacu2u~UOb%_Ni)JiaGO`-ff;(1{A3uB`Ct4Zk(C;-x zl$0`8CrlDCg7A+E8gR9#)s&O#j|q90&_%~~S^0JEd@n$y%#YhkX2 zYXS1%?T;_lP($eZb%;2fnpC-5h^>GfA{LiBy*sfZ`|fr_!*`inq&vQ-b_bog9J;cxvHJyIY+bmuF1wO*UrzMlV-A@+kPR*`*rK*}J7FFh2`Dw6x4U}2g zpP!@(d_9TrQqjx4=aeSW!!JFTpL+i|)DU=BL6h4Qk*IqRDSAPC!!5J0Xk>2Yw=ay4 z!+c}zmrxPSBlWnWb&-c4t>=;C%(Z5uT(a#*Oz)$e!&5No%^~(H%u7e^2d7P^z ziQb<-n;w>yNj1NcpB9i~rh+L`pGr`b5br|yiHQB5g&|>$QtQZec2fj+_Kpr1!j#J=;tx6HEYD|)L`MsKl})qL##_DH z4M*(@qLf9NT~GOs_Y~5v-ZNCW;$R(2r@KH__^osseR=C0 zpu&zZnai&Q*p8_}FaQlHsSk=wrpxV9k;Js?*>TvLN{Os@@8(gWSXfLwG}&11A8w7o zI3#f7KBM@1s>p@$YF6fM3Y*7#da-@Iz0%;Iq@2H8u$v475DIs{4n@~f$w7I+z~(*~ zDUWM@-*!Ve?Q-F6Qc#-}IQ;1-YpmL=9wa_KT)Tgy@#`q)3nO%7dQr$+6X~n3EyqPY zbm+RjXM053bDaF4*gUioHaN{x&!`a9oe9J(C?lzOVo8z5;--yR@Xth<3u9ot2sifz zOA}2TP2bx~d=OTSiBE2AxWBF1$zawH6N^CqesrAv6%o3uy{+MRe>bsd>2WQ^X9yr= z?3+x*khiO_bFrwg&!0-EN!|HN zs7WhiC#W+{ThCu3jNp1!iO9UkMtE!2;(lTy(p02GORj$WV?k11F*1}Pr+}fW$cBR* zeKR+PY3o`+zAC}nR`|W49^}R74Zg_@jt>J)K6JgPPJmFNk~v(vRqXZ3kHt?zV|D`v zHm&^=V=pcr!ppZ8jpKLHLxkRE$Ag_Hmvv)VQO3KCU#aC8BYhpWufMBg(jcL)X6sW) zlAl3A*m=qqJ78?6)%Foh#m7~0DNu{>3{mG%awW+`DadZtM#-y&N2J0SWo#_HvLjL> zQ&V-+)M%&Jgm~C!$+BXO$5MR9Eaubftf+@JMD!K+rSg!E-&-#2qW@=hkl_0xh}jig zF?JmPcNZo}F9U4s$UdT=Af*>d5~(>N9(|dz8GWQ|GGsD%WGLXrE_~fC;0XMb(e)@y zRqwyT#!^>QRNS4$!Q;tPH>F7MjS1H!a41*YW%;asjbSoQUF7@rzs6O*B_m9)yj2uX z%L+|zk0maj*g4~!qMALaHw_PG-NdAoWO=DXrnE+nhq|c|-h_=FjhP8`;a$yETr*l* z&u)@>-v@gyDJ9k0!$?Uc<3P!l?@9PcBVtpgEn3h1-k0z{gL>Wy6zt}g<)v%%BtmXc zd8xFf9Y)iOY)9gAGOKTvURF}ex+(1UDN}>JAumEjA!-m;M(qDgwL}2RcPZ1#CcP{z<{dr*f(ebg_rZq*G)4n!NAT|lfyg3r5`7gWm zzEahKl9G}HRNU=>mBoLe{j1aa+eluU^-l!#aFgMzMwes5^}gi42Q(Y8rQY_$voYXo z1M8d~{HZg*ve7GVyCh%z&%8o*YT%6211l=n$rPZ_{9F-F z&n95+P96@7jd8NDJbU&Gy^7!Kj$c5)1K0~L%bAy)Sy@?4?w7WAH^gIj)BRE$9eqNXRgsLmET2y8`VzY-MbbLN?<&}HZwa56ef_|M!gA{ zz`W+=U7e2W9ADm;wpv?T`-g<|{rU5e_z4mcJslmBdIb#+&*f;oN{iT4iE%=PGFsgO zTr$oD5bxxqBrGf}Ui*y_+tu!)BimBw$fU5epn)34K=#-R{AoTHxiD0LBIXN!~ICsF|D=};C z2sLIw{~gg);i-y*GC;?mR;Iu3>kFG#!Zys6VB*tBW)} zBZERiW4f%26{MDtv5;Im#2q{;C@2WBh)v9@+wzErC|N0A*~iy+Z>myCO^xUE>$h53 z9;@Bay1Kf+gqX!LX~i(8DR8_%Lh_sUy1xJxLFv9d+nxR?9!ixS78V9ytaNWyLtdVR zf+E9gyhxfnVC4s8R7Awl(W=yPzJ69~c0Y?FZh)1H9|+Z%Dnpy_J=fh=_>U((&&gz=E1{@^NvUA1-%F z%kTgA@tS~ufH+hff{u<3qHAYk17B}YU405ZWGX3%jEpQOI9ODU`YKf$mP=Xd_ivpt zBJ&u3R{q`A26_GRWjM%tWMt$96uwGPnL*$|T5PO}n3$NcvGM)gZGiDyg9|tojWYeN zC|YP712sCx4BNAAtLEFWxjD~PUlhn3=_+WH^}k1bBj!{b&qnRj&ip(spp*sp!W0mP<2qnb(RuiL~74Gkgec5Bj5=&-B9 z%W@D~E~`0y=Y!9i{cUS|dv~|z^BUm%rl*mfJYn4x78XW)^1?qb5c&D@EdUQ{YHF&g z6I}D|t^9n>2Mcj=uW$m}`QcwsjE|q(o2_@+ouYK*9lSrul!_g*HI=j^|F^4<|H21Z zd%w42b_?{D??n+iu?z?q;we)Qc$*4Irt$ zFPRUynTe56Mn;B+&#}N53G_VK_rO5RsyC|{cs*2pdW@^9tCyFTd3kwCa_b8V)&LXJ z)6)SQ0!-4-&>$--FF!szla-V6I@ypH6&01?F&0d{Kikzs7d15f}m^A5g!J+SqLf0fihSPzjcUu?iPGh* z(fmyj6I4DvzAP^nm&<;kd-scj#fgatKq6VCrefL`soLwg|3bPt4;*XxTwGih$E6!l zbt(N4@D23!+n(f3wGb*rfBV)7ngm3P00TqM!U8A@2$m6N)c>ptP*qh0T8V-JJ_uRS zjsaB3w`46OfM~8=t^#Y;Ojnqrlh2Vj2yVIupk~v zY+_rLRqjL`+yv484_0%*XY)cC4Ra|`i@_0QxI2~1H^}jh#nN|NP*hQAcE3Ct&XyDHtSB$9ci2{T zcCKk`^pK(V2k_qMKQK6G5)p>~vCSXd)6=u1!q(Dq4^;8isBlkP0A_1zt6VZKAQ14T5fTgalu2At<+j@ItpwQz#;pB2D{P5-3o%yvRgZlMKcXoD|v>PNK zdORDAZs!WUKHTcjfC7?v>^eXmXJ?cD!f0bNv+Rrvi8(8X@TI#_v(vsPzn?N0XjIBi${++LrXnVL>bOhClM_U4{|mf_zZZe2_g^tb{j zB;a~-aC$mhWs#_A6MdC}pV0k?cug1q!5BXd3XVVbwGI*qQq0@ezw+?}sW>bu$0d5I z1EQWFeU9;Qd5M7kr1{lbCGgB2m%#n1>dJlzeTFQQhd}Y=KN|Ec;zzl6|521-I4l1> zv{U_m&2%^UUo+h^{?|K`&D@djK-=nyPzB<4!2SFgkI?a}wFMo*n`uc*`AS|p?Ah2WhTqC5vxqqk8 zd863L`~TIT{J)E4n_j6p@1ro@FK-8iNdJAT0-@l4z0Gf4yel2S1Vu$fKus(jI@8h7;o#ux?d<`| z8FiGEmewrO4*-H6Se01V?o4fYVPPTAkE^g);D!8YsHy!Z@yN-^O=cehA(OsE8y z`y#xZ{;E#+*AWM&g9A!We+L1wj+T}-mRSc_(xX@%&&z46<^V@}1_pZv2LlBJbIy9L zdPjFbAv*YG3M@Xoa>2pT5ytL5*p`+eQd8w1noKI<;?&H{%q%P{3=HPeRTe-QsjI64 z&W1vvCpkw)M>8{;WEf`S0|Q~*?T84!nge8?17R}`|5D~|LUHjvFj&2YD&;Rf$jgVr z3bWi(K?o1bBn`dQTXIY+^Y+^)OkpvM$hb`Uvhwv;Pnf$<9(?9R+Wsf%?Zd-tf`4s{ zjEt0&2HjD#kd@`-b2C?zKs8XD?kXP2Loqgrm* zTV??BzO}QprIJmGOHH*hGQ!2dQP$MFS?f&%wyLJ4281S4rC3{<3mZE+$m{s{_*2Sz zNlA1IG&D4;4vmKh2lJG?wEFt`?x&+F5PKk{fqqn)o}ZrP`^gI*S<5TI4bM=e9n^!G zN}iRK73gCSsr+M0_SYA@R!3E8O8^&NRG#j5&V%f;sbUo`zRysW{=nS~r^0KlFB7OvS!hf2| z_&p)2(ZCO32o?bWtOA(+zt$V^yjVm64m;1UrnVLZj}_QTVCh0k0s;fc$)u#DfDSSI zP5`>^bFU#EH}^|4gkI)cE9{kfp391GL zl<1|Z3~OnrsXDTD_q@$Hh?pQ@x65NO2viuAb(^=~BjoYl5|Y&Baa3hC(RkcK7m zIr;zT?tZVKQJ?h_Bs;F*t+6o;y2sUKHt-7^91cLhju-1NGcsC)45U2)whW*t5Qe6? z2vl+@=ElaaUcH)7cpy>Kz8}4kuwGP^MnI-yvB5F3(%s!{Ia4DH0gB3a;;9TPxqq(= zN?csq#N;Gs;f9hD>)|Y!Ayi@n!H55o`)aFs*;GLRDk@)p@a{eAR8&+Lz%a=(BGJ*) z|20@RA|k^7&%}iD_E;e}zSplmPL$|^=H%q$1pRSxdTKB>T&d2)!~~A=`h4H2Wzah) zIQaTxlMT3i;6R^0`q~jnf`_*Wyur@S4EP@)Cr@{$ojQW>;A2Sp`>k#kK8!B5`k|np z0CP_vM8U&zxzZI$uTn(FW1CT0TAG%&SUvAaAB^eo=q^!ZHUS$KSIGThpuL@vlT%qz zax>AYDYt4GJO!2)VjVi!(f?FwJ?n|Iv=WO{8oc-$ChO_UWCh}8Mbs))5&q5lfcdLd{~El5f$rA| zSOX*=9-M8B&9XO9+zC`yS9f>$4UwXvqBkS3HFw*^&7e|#{`}e6(gMfVyPM05j0~1f z9h$Ya2B1_FYE#BKdwVU;b|!nqCnqO0s?2#DcL3v^+q905L=0lSO?|%0P*Rg}p@eD+h#5;&me*SN^F~6_lFUSOIYOWEd_o zOf)n(1qB67`S$BQ@!)BmPEO~*_)FF#)aX*1Zo?ac*TvPfqZ7WZudmNHc})NLvjJQ@ zNVrwRZ^~43)6_(K9+qcn8XBP_LQMq)LAkI&#ovg(I+6a7ESvamJ|dtCcwDgq2+I;mrnq7)qPpn*)^NodC+H@y_%zOec@9`=3*E%e9=fO;iIt(I1GE@z9FFG#*ij*gC_DJ=k9 zhB73Bo4@bun2U&rNJ+u+I%VVD{2mxM00g183nT-mZ1^JS@$r)bX(I4-3A&I!jKq37 z1^F{DG6Lin9v((~_z*zY3^4KZ^!c>VEN^db|I-2kWWg+jHtJOOqpMRh@aDktg09G$ z1-$kG1;uG+VsK#_BSm6Dp}1aMUf$+trK>BF`g~_{xUH=XR6h(B*wx0eh3!?zm64vl z1Y9l%=l*PcbwEHsW8*CdZ$8su5nF_M5idPG3{=#gFj7!upncBH&cIXWtF3feWl7y1 zz`xQDdH{41I~NxT0E*Fkm3*VNR>*Fx2Uz|@$AgZZr!w3ug=*!# zy@n+G&L!GzR-Wv`cnA_t|IL-HqocBt5+gJ7{pq-l*;cvPI1`zzy*(i7LJ#(}*1n#h zs;uDhNeW8JV1T1QN=Y-xsi>TTvjd=&Ea>4{DcP78XQuMu!%GWI!0Z6|KI!RUVPnh4 z$fz+WzkT})G-G>}G4hKSJup~gcz8U^Co!0|sM51%&wh#p^EzxP>guNX_&iSRoojG8 zR?+bor672tOu0ye$qVwI#AB!3=xShUnpaRjh=a4XwdHzqVI$e6q^9PwJ;tb{q(neK z0P+EHwY0RfJzgB>Ku=3s>$r=L9*obCr;AJJN|u$W?NnJ(GB7&o0Pqj=$f|8EfEXDm zDL(s+fj{BopFVwx7Yq(+{_*1%curRYC7`pI*jO$$HlVYfzIdToXP;|aq#Pk(TDJw7 z2f*I$eDnQmy_1-j80bC*9H2aRvQs_t_S?VLxg4*x1z-ZPmY~slUGnjFt=&50 zn*G?UM&=C%hgO9#I(ngMX*!G2PX6cuUfMHiDmJ!qP;2ndZnWyFv{~FA!IOmgZ;}+0 zl?~r(09IyUXRk2z1AD%{zK)dGoZzyW8>wIll62eL+8V2kRm_K9VF4uvu>R5G$65`} zhgYZDO-)U_yu6Exi*SK`Y%Q4D&p^V-#f57DUtJg+85ueG?d@%Us*o2rYN7iZsEo|+ zda74QXeh3u+rh#E^!S7XQ)A`98^15$nc?Z(?Wc(dEpS;KZ7C>l_SVjg0Chdk-`1r1uN2^F^#6cuNg5>0%2Gd1b z9_f8rx?l7oc_$(Q5^25E7QpAUm&E79#Lj*K8XmY62Auq+a7DLKpb%Wo_mY#7wVa(V z0Aa|c@Z%8@x}Wc9$;tIU(JTNxGY%*gu;#}8{{2c9=34o~L{1BgtiHs1ju~YJwK9mX zFexuD5Xi$Q+?x1o?+~f7)C;s56IvHRS5Qz;`itCUIch}AYe?atO{|@fHqev)q8x5cdu4%SlQ;l z{sqL?4~?Yz&!3&C%6yOOGwGkv(b(mm_dtnNy1!}2$e>_lg?}h_czDQ9Y_--CpWYt= z1clrAUOFSteDj{y7J%uEi{4OD`Un;@n}z4+bb<0ACnE!r7#tYY%!2}*W>0WT3Umgq zLDQ90RGb_fd@M_1z%lDf;*ph=br%F|a_f*1C~5dCA3KzgM?p-Cikq98o?fbA`to>v zQ^dDgw@p^Dxc+LX9a%?52OArkgoI>~j}RQuaz`-0F+l!bzI+J|5&~3wv_wV2HNP;f>v)K@G zaQ{*186mBU7nhg8!~%7o*;`vhfP4m)2eikNCr{vpr+gAv#R;H#N}7X$r1kXDv$M0K zqoX4t6xREaM@L4cs;!!JTYZ7D>KDA0HW&Cr&=N&(w-PDW##xKcYi|tH9dHNxGqvQ7 z{?*mhX@70CwU;^aKY?I1Ffnlf!i`C*&i|BwmiF-Y82AVnh)68_015(z*Y{OL$j*U9+Uv2XF{t+c*CZKk$X3yJxq5Bulo=M8ctXgwtWn}@T?fUu{xvl#> zgLVUawLPGo0ou9W-M9de22l9R7hwQbAl|${EyvV-?+64YE*%6ppt4+*0h|SZ!8{)# zMJ1)^s3?CLKE6cS%9+wn;#8NETL2xTi9Y91V;AlJM4L3C!kkS~k*8jJ2@0pwkvKFv zKR+=pZerl083pKwJbVJE4H+$MZb5;xtZY#hW~0k76({G|*qE}t+TO*nOfYTS=WauH|ue!-L-5(ZK{Tq(BIoGZhLw6i|~qvl^}Y+`~QUV`9y-0VC367CGy z3@Qq`RaJN8C>E>JP7J+WWuz;I$H!CIjATc*hESe1^$|Qout2vR_$%Tl ziVn#ilYLQ%cmebA)|aXl2ztOl&8Rnl;}jphC{CgKe(~3@53j=NFk0D_Wdwjx?oAO$ zyu1PjNK8zuQmlQuSHBO5Bm!#cc&(S7jxK@6E(^pSuuOV7Sz<3L3Q97sgQ<&4Z3%Vx z)^TqlSFby^gC44`qgy_27R6;fbJGPGBcn_Od`zkyAJ=n9&ra{W4yp!3A0MB}l^<5X zdy++yz3S`h%U3RRy*SX-)6)ZK-VkwF?Z8^faZPuf9R+@h?0XP=D{#C4)>5pazrohq zfzDK+SRzlmvZ?6~L=dnz@vB$Pi!F~07YlQ9;VUL0y?8b78T!VVKSdQ8$`83<%RVe# ztSUN_gj>Vw-RgOYeP%_V}zTPWN(lkIdz`s-bMEUw2A385mkXHc2Wf-Xl02q5f3V`XTsHx}c zokC%&tUj;tv$g`Z98}`_U@?q|Tvpo2>!zwVJ@kHE_hW)JD}X26dV6}{Q2YZgZ4Bep~>a2OSSjO+g{OxLC;bWCMsBTkiYr7k0HXK$f|WHi+G1k9nVG9mv`}^4uq^-@C)?*rz$xNld%Qk*pp=*n6&z|7Be`CG@_ZJnL}9~1jpCQ?gGu1 zf^0JV72BS3%*@5Sc5D3WJZ!y3JnNT)-+- z{#bR7+;QEV962~RlGM;^u7Hjcyl3w7$$zv(F1D~k(AW5yn!587y1jwHDkwwXzq!x9 zv`P6M3VH&D2AmI6RH5u>@9A6jU^>)3Dhi4Z{Cf0Pq2C_zKZ&1DIe4Y*{qo!ISA32I zk&(m8ouNSG6X4_f&r)3qsL@T`5k5h%N42urhkqV;f7i$s>d*Yh1ny|z_eJgQ27fC7 zaFd&t_g+Gx1}IfPe(FiBt*mTpAf<)H@NK>-r21L&vO5qCKo-r_*pTz^#4q7eKKVx4 z`-M%~Yj1|f&FvbOS^MeJr!QW;Spen;sJpVt%GDXpUjUNT)u({j>I`{Uh{$4$!22>- zYs2kyohPv{Ut&;c4cREM59z{B8pY6< zebZt#UYpSRJP?CYYu0W`t!+mpnd80@97vBcU%3?EacjL|5YN|Erj^^S9iB=bmHcs_lMoL>;0+6Db-nXn8d9x z*|%@sii;zWLiEiR&-HURR@UDjG#7bIt6=jcYinzJ9xk=xm??u2}{$KWn~;4P~yi1y7D6cm7{1kx0gK@yu0YIHq#ho;3h z<`Zwuc!3fIiaBf0I3*>eSiA8#GBPqMYD{FL+wB#{decvUQf!7jG<0+T9^~tjU%h*( zH-KQzjvqV*o!1IU;%(Yqm{aJ!es|+>*<#x{>ZQ{ZcEA*F<}2K?ULb8zUV0rubaTKW zDyFXfA?%Ko({+=Cen)xwSk_uiQ=!ac9Y?V5!HIqHB@(wmcT_x^QJGcch>v>GS$5Nl zFCRX9c&oor?{>~~Ed$iU(=>RjYTU0u)}E3A52}}#6_WT73E?2~SSUY;h)-EVBe6@L zmxl*kiaatR!dmPp@Va!@gQvUG;~gDONbT2p@HEr&^8;VjdEWA(f1szKQLQw^Hp;;z z1x5JL*mz=MqQ-u62q5kf_V1^pIDr7vR`&J;KXtsAOMI>%gv`dq<~;!PhQI0GEA_Or zv?N)C0N-3y