diff --git a/.gitignore b/.gitignore index 5acb669..399cf84 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build .vscode +__pycache__ diff --git a/CHANGELOG.md b/CHANGELOG.md index 109147a..101b3bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog -## yy.mm (Mon dd{st/nd/rd/th}, yyyy) +## 22.12 (Dec 13th, 2022) + +### Features +- Added provisioning script for `demo` examples +- Added nRF52840dk_nRF52840 with OpenThread support +- Added automatic handling of connection link state +- Added option for use of non-secure WiFi networks +- Added configuration for software-based security on nRF9160DK + +### Improvements +- Updated Zephyr to 3.2.0 +- Updated sdk-nrf to 2.1.1 +- Refactored network connection handling +- Firmware update success status can be now persisted across reboots until actual delivery + +### Bugfixes +- Fixed critical memory corruption bug in the factory provisioning app +- Fixed problem with push buttons IID ## 22.08.1 (Aug 31st, 2022) diff --git a/README.md b/README.md index 2cb36ab..991c50d 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ LwM2M Server, please register at https://eu.iot.avsystem.cloud/. Then have a look at the example configuration to configure security credentials and other necessary settings (like Wi-Fi SSID etc.). -[Guide showing basic usage of Coiote DM](https://iotdevzone.avsystem.com/docs/Coiote_DM_Device_Onboarding/Quick_start/) +[Guide showing basic usage of Coiote DM](https://iotdevzone.avsystem.com/docs/IoT_quick_start/Device_onboarding/) is available on IoT Developer Zone. > **__NOTE:__** diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index c34ebf6..9819579 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -68,9 +68,15 @@ elseif(CONFIG_ANJAY_CLIENT_BUILD_MCUBOOT_AUTOMATICALLY) # Kconfig variables, which are populated by find_package(Zephyr). # That's why we do it ourselves here... set(MERGED_HEX_NAME "${CMAKE_CURRENT_BINARY_DIR}/zephyr/merged.hex") + # In Zephyr 3.2, mergehex.py has been moved + # from scripts/mergehex.py to scripts/build/mergehex.py + set(MERGEHEX_SCRIPT "${ZEPHYR_BASE}/scripts/build/mergehex.py") + if(NOT EXISTS "${MERGEHEX_SCRIPT}") + set(MERGEHEX_SCRIPT "${ZEPHYR_BASE}/scripts/mergehex.py") + endif() add_custom_command(OUTPUT "${MERGED_HEX_NAME}" COMMAND "${PYTHON_EXECUTABLE}" - "${ZEPHYR_BASE}/scripts/mergehex.py" + "${MERGEHEX_SCRIPT}" -o "${MERGED_HEX_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/mcuboot/zephyr/zephyr.hex" "${CMAKE_CURRENT_BINARY_DIR}/zephyr/zephyr.signed.hex" @@ -110,6 +116,9 @@ else() src/status_led.h src/utils.c src/utils.h + src/network/network.c + src/network/network.h + src/network/network_internal.h src/objects/basic_sensors.c src/objects/buzzer.c src/objects/device.c @@ -147,12 +156,31 @@ else() list(APPEND app_sources src/firmware_update.c) endif() + + if(CONFIG_LTE_LINK_CONTROL) + list(APPEND app_sources + src/network/network_nrf91.c) + elseif(CONFIG_NET_L2_OPENTHREAD) + list(APPEND app_sources + src/network/network_openthread.c) + elseif(CONFIG_WIFI_ESP32) + list(APPEND app_sources + src/network/network_esp32.c) + elseif(CONFIG_WIFI_ESWIFI) + list(APPEND app_sources + src/network/network_eswifi.c) + elseif(CONFIG_WIFI) + list(APPEND app_sources + src/network/network_wifi.c) + else() + message(FATAL_ERROR "Neither CONFIG_LTE_LINK_CONTROL nor CONFIG_WIFI is enabled") + endif() endif() target_sources(app PRIVATE ${app_sources}) -if(CONF_FILE_BUILD_TYPE STREQUAL "extflash" AND CONFIG_BOOTLOADER_MCUBOOT) +if(CONF_FILE_BUILD_TYPE MATCHES ".*extflash.*" AND CONFIG_BOOTLOADER_MCUBOOT) # This is a workaround for a bug in nCS' integration with TF-M. # # When MCUboot and TF-M are both in use, the CONFIG_BOOTLOADER_MCUBOOT diff --git a/demo/Kconfig b/demo/Kconfig index 3d69a20..1b5f665 100644 --- a/demo/Kconfig +++ b/demo/Kconfig @@ -87,6 +87,12 @@ config ANJAY_CLIENT_NRF_LC_INFO_CELL_POLL_RATE range 1 2147483647 depends on ANJAY_CLIENT_NRF_LC_INFO +config ANJAY_CLIENT_NETWORK_KEEPALIVE_RATE + int "Rate of checking whether the network connection is still alive [seconds]" + default 60 + range 1 2147483647 + depends on WIFI_ESWIFI + config ANJAY_CLIENT_FOTA bool "Enable the Firmware Update object" depends on BOOTLOADER_MCUBOOT diff --git a/demo/README.md b/demo/README.md index 046a464..340c358 100644 --- a/demo/README.md +++ b/demo/README.md @@ -4,7 +4,7 @@ Project containing all implemented features, intended to be a showcase. ## Supported hardware and overview This folder contains LwM2M Client application example, which targets -[B-L475E-IOT01A Discovery kit](https://www.st.com/en/evaluation-tools/b-l475e-iot01a.html), [nRF9160 Development kit](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF9160-DK), [Nordic Thingy:91 Prototyping kit](https://www.nordicsemi.com/Products/Development-hardware/Nordic-Thingy-91) and [ESP32-DevKitC](https://www.espressif.com/en/products/devkits/esp32-devkitc). +[B-L475E-IOT01A Discovery kit](https://www.st.com/en/evaluation-tools/b-l475e-iot01a.html), [nRF9160 Development kit](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF9160-DK), [Nordic Thingy:91 Prototyping kit](https://www.nordicsemi.com/Products/Development-hardware/Nordic-Thingy-91), [ESP32-DevKitC](https://www.espressif.com/en/products/devkits/esp32-devkitc) and [nRF52840 Development kit](https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk). There's an alternative configuration for nRF9160DK, revisions 0.14.0 and up, which utilizes external flash chip to perform firmware updates. @@ -18,6 +18,7 @@ The following LwM2M Objects are supported: | B-L475E-IOT01A | **Firmware Update (/5)** (experimental)
Temperature (/3303)
Humidity (/3304)
Accelerometer (/3313)
Magnetometer (/3314)
Barometer (/3315)
Distance (/3330)
Gyrometer (/3334)
Push button (/3347) | | nRF9160DK | Connectivity Monitoring (/4)
**Firmware Update (/5)**
Location (/6, configurable in Kconfig)
On/Off switch (/3342)
Push button (/3347)
ECID-Signal Measurement Information (/10256)
Location Assistance (/50001, experimental) | | Thingy:91 | Connectivity Monitoring (/4)
**Firmware Update (/5)**
Location (/6, configurable in Kconfig)
Temperature (/3303)
Humidity (/3304)
Accelerometer (/3313)
Barometer (/3315)
Buzzer (/3338)
Push button (/3347)
LED color light (/3420)
ECID-Signal Measurement Information (/10256)
Location Assistance (/50001, experimental) | +| nRF52840DK | Push button (/3347) | ## Compilation @@ -30,7 +31,7 @@ west update You can now compile the project for B-L475E-IOT01A using `west build -b disco_l475_iot1` in `demo` directory. -### Compilation guide for nRF9160DK and Thingy:91 +### Compilation guide for nRF9160DK, Thingy:91 and nRF52840DK Because NCS uses different Zephyr version, it is necessary to change our Zephyr workspace, it is handled by using different manifest file. Set West manifest path to `Anjay-zephyr-client/demo`, and manifest file to `west-nrf.yml` and do `west update`. @@ -39,7 +40,7 @@ west config manifest.path Anjay-zephyr-client/demo west config manifest.file west-nrf.yml west update ``` -Now you can compile the project using `west build -b nrf9160dk_nrf9160_ns` or `west build -b thingy91_nrf9160_ns` in `demo` directory, respectively. +Now you can compile the project using `west build -b nrf9160dk_nrf9160_ns`, `west build -b thingy91_nrf9160_ns` or `west build -b nrf52840dk_nrf52840` in `demo` directory, respectively. The last command compiles project for use with the OpenThread network, more about this can be found in the section `Connecting to the LwM2M Server with OpenThread`. > **__NOTE:__** @@ -57,6 +58,14 @@ For nRF9160DK hardware revisions 0.14.0 and up, an alternate configuration that To compile in this configuration, use `west build -b nrf9160dk_nrf9160_ns@0.14.0 -- -DCONF_FILE=prj_extflash.conf`. +### Compiling with software-based cryptography + +On Nordic boards, security is provided using the (D)TLS sockets implemented in modem firmware and provided by nrfxlib. + +However, on nRF9160DK revisions 0.14.0 and up, it is possible to switch to software-based implementation based on Mbed TLS instead. This is not recommended due to lowered security and performance, but may be desirable if you require some specific (D)TLS features (e.g. ciphersuites) that are not supported by the modem. + +To compile in this configuration, use `west build -b nrf9160dk_nrf9160_ns@0.14.0 -- -DCONF_FILE=prj_extflash.conf -DOVERLAY_CONFIG=overlay_nrf_mbedtls.conf`. + ## Flashing the target After successful build you can flash the target using `west flash`. @@ -98,12 +107,22 @@ LwM2M Server, please register at https://eu.iot.avsystem.cloud/. Then have a look at the Configuration menu to configure security credentials and other necessary settings (like Wi-Fi SSID etc.). -[Guide showing basic usage of Coiote DM](https://iotdevzone.avsystem.com/docs/Coiote_DM_Device_Onboarding/Quick_start/) +[Guide showing basic usage of Coiote DM](https://iotdevzone.avsystem.com/docs/IoT_quick_start/Device_onboarding/) is available on IoT Developer Zone. NOTE: You may use any LwM2M Server compliant with LwM2M 1.0 TS. The server URI can be changed in the Configuration menu. +## Connecting to the LwM2M Server with OpenThread + +To use this project on the nRF52840dk board, in addition to the configuration shown in the previous paragraph, you will need to configure the OpenThread Border Router and Commissioner as described in the guides from the links below. +You can change default `CONFIG_OPENTHREAD_JOINER_PSKD` value in the `boards/nrf52840dk_nrf52840.conf`. In same file, replace `CONFIG_OPENTHREAD_FTD=y` with `CONFIG_OPENTHREAD_MTD=y` if you want your device to run as an MTD. + +Resources: +- [Introduction to OpenThread](https://openthread.io/guides) +- [Border Router guide](https://openthread.io/guides/border-router) +- [Commissioner guide](https://openthread.io/guides/commissioner) + ## Configuration menu Using serial port terminal, you can manage Anjay client using built-in Zephyr shell. Use `anjay` command to list possible options. @@ -142,5 +161,73 @@ to which the user is able to pre-provision credentials to the device using a spe tailored version of the application. This feature allows to easily pre-provision large quantities of devices in a semi-automatic manner. -To use this feature, generate a special file containing the credentials using our [Factory Provisioning Tool](https://avsystem.github.io/Anjay-doc/Tools/FactoryProvisioning.html). -Then, follow the flow described in `src/factory_provisioning/factory_flash.c` file to finish the process. +To use this feature, one can use a script `tools/provisioning-tools/ptool.py`. +It might be used in the similar manner as the script of the same name described in the documentation: +[Factory Provisioning Tool](https://avsystem.github.io/Anjay-doc/Tools/FactoryProvisioning.html). +There are a few new and important command-line arguments: + +* `--board` (`-b`) - the board for which the images should be built, +* `--image_dir` (`-i`) - directory for the cached Zephyr hex images, +* `--serial` (`-s`) - serial number of the device to be used, +* `--baudrate` (`-B`) - baudrate for the used serial port, when it is not provided the default value is 115200. + +If the image `initial.hex` exists in the given `image_dir` the initial provisioning image won't be built and the same works for +final image and `final.hex`. When `image_dir` path is provided, but some images are missing, they will be built in the given directory. +If `image_dir` is not provided then the images will be built in `$(pwd)/provisioning_builds`. + +Before using the script make sure that in the shell in which you run it the `west build` command would work and +that all of the configs passed to the script are valid - in particular, make sure that you changed `` in `lwm2m_server.json` +config file to your actual domain in EU cloud Coiote installation (or fill the whole file with some different valid server configuration). + +Currently the script is designed only for Nordic boards, and it was tested with nRF 9160DK. + +Example script invocation from the `demo` for provisioning some nRF 9160DK board directory may look like: + +```bash +../tools/provisioning-tool/ptool.py -b nrf9160dk_nrf9160_ns -s -c ../tools/provisioning-tool/configs/endpoint_cfg -t -S ../tools/provisioning-tool/configs/lwm2m_server.json +``` + +where `` should be replaced by our board's serial number and `` should be replaced by some valid authentication token for the Coiote server provided in the `lwm2m_server.json` file. + +### Using Certificate Mode with factory provisioning + +If supported by the underlying (D)TLS backend (if using Mbed TLS, make sure that +it is configured appropriately), the application can authenticate with the +server using certificate mode. + +You will need to download the server certificate first. One possible way to do +it is with `openssl`: + +```bash +openssl s_client -showcerts -connect eu.iot.avsystem.cloud:5684 | openssl x509 -outform der -out eu-cloud-cert.der +``` + +> **__NOTE:__** +> Only servers that use self-signed certificates are reliably supported by +> default. You can change this behavior by setting the Certificate Usage +> resource in the endpoint configuration file. However, this might not be +> supported by all (D)TLS backends. +> +> In particular, when `CONFIG_ANJAY_COMPAT_ZEPHYR_TLS` is enabled (which is the +> default for Nordic boards), the Certificate Usage are only approximated by +> adding the server certificate to traditional PKIX trust store if Certificate +> Usage is set to 2 or 3 (note that 3 is the default) and ignoring it otherwise. + +You should then modify the `cert_info.json` file that's located in +`tools/provisioning-tool/configs` for the desired self-signed certificate +configuration. + +Once you have the server certificate, you can now provision the board. Example +script invocation may look like: + +```bash +../tools/provisioning-tool/ptool.py -b nrf9160dk_nrf9160_ns -s -c ../tools/provisioning-tool/configs/endpoint_cfg_cert -p eu-cloud-cert.der -C ../tools/provisioning-tool/configs/cert_info.json +``` + +> **__NOTE:__** +> Coiote DM currently does not support registering devices together with +> uploading dynamically generated self-signed certificates using command-line +> tools. +> +> You will need to manually add the new device on Coiote DM via GUI and upload +> the certificate during the "Add device credentials" step. diff --git a/demo/boards/disco_l475_iot1.conf b/demo/boards/disco_l475_iot1.conf index c48e202..489f888 100644 --- a/demo/boards/disco_l475_iot1.conf +++ b/demo/boards/disco_l475_iot1.conf @@ -45,3 +45,6 @@ CONFIG_BOOTLOADER_MCUBOOT=y # Note: if relative paths are used here, it is treated as # relative to $WEST_TOPDIR (typically ~/zephyrproject) CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="bootloader/mcuboot/root-rsa-2048.pem" +# TODO: consider a deploy script, which will fill in the release version into config variable below +# Warning: MCUBoot's imgtool doesn't allow leading zeros in version string components, that may be a bug +CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS="--version 22.8.1" diff --git a/demo/boards/esp32.overlay b/demo/boards/esp32.overlay new file mode 100644 index 0000000..c4fa06f --- /dev/null +++ b/demo/boards/esp32.overlay @@ -0,0 +1,3 @@ +&wifi { + status = "okay"; +}; diff --git a/demo/boards/nrf52840dk_nrf52840.conf b/demo/boards/nrf52840dk_nrf52840.conf new file mode 100644 index 0000000..d96fe42 --- /dev/null +++ b/demo/boards/nrf52840dk_nrf52840.conf @@ -0,0 +1,66 @@ +# anjay-zephyr-client +CONFIG_ANJAY_CLIENT_DEVICE_MANUFACTURER="Nordic Semiconductor" +CONFIG_ANJAY_CLIENT_MODEL_NUMBER="nRF52840DK" + +# Anjay Settings +CONFIG_ANJAY_COMPAT_TIME=y +CONFIG_ANJAY_COMPAT_MBEDTLS=y +CONFIG_ANJAY_COMPAT_NET=y + +# General Settings +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 + +# Logging +CONFIG_LOG_BLOCK_IN_THREAD=y +CONFIG_LOG_MODE_DEFERRED=y + +# Clock synchronization +CONFIG_DATE_TIME=y +CONFIG_DATE_TIME_AUTO_UPDATE=n + +# Networking +CONFIG_NET_IPV4=n +CONFIG_NET_IPV6=y +CONFIG_NET_IPV6_NBR_CACHE=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_CONFIG_NEED_IPV4=n +CONFIG_NET_MGMT_EVENT_INFO=y +CONFIG_NET_L2_OPENTHREAD=y + +# DNS +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_SERVER_IP_ADDRESSES=y +CONFIG_DNS_SERVER1="fdaa:bb:1::2" + +# OpenThread +CONFIG_OPENTHREAD_JOINER=y +CONFIG_OPENTHREAD_JOINER_AUTOSTART=y +CONFIG_OPENTHREAD_MANUAL_START=y +CONFIG_OPENTHREAD_SLAAC=y +CONFIG_OPENTHREAD_JOINER_PSKD="J01NME" +CONFIG_OPENTHREAD_FTD=y + +# MbedTLS and security +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED=y +CONFIG_MBEDTLS_DTLS=y +CONFIG_MBEDTLS_ENTROPY_ENABLED=y + +# Shell settings +CONFIG_SHELL_MINIMAL=y +CONFIG_SHELL_WILDCARD=n +CONFIG_SHELL_VT100_COMMANDS=y +CONFIG_SHELL_VT100_COLORS=n +CONFIG_SHELL_STATS=n +CONFIG_SHELL_CMDS=n +CONFIG_SHELL_TAB=y +CONFIG_SHELL_TAB_AUTOCOMPLETION=y +CONFIG_SHELL_CMDS_RESIZE=n +CONFIG_DEVICE_SHELL=n +CONFIG_DATE_SHELL=n +CONFIG_DEVMEM_SHELL=n +CONFIG_MCUBOOT_SHELL=n +CONFIG_KERNEL_SHELL=y +CONFIG_OPENTHREAD_SHELL=y diff --git a/demo/boards/nrf52840dk_nrf52840.overlay b/demo/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000..62024b7 --- /dev/null +++ b/demo/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,9 @@ +/ { + aliases { + push-button-0 = &button0; + push-button-1 = &button1; + push-button-2 = &button2; + push-button-3 = &button3; + status-led = &led0; + }; +}; diff --git a/demo/boards/nrf9160dk_nrf9160_ns.conf b/demo/boards/nrf9160dk_nrf9160_ns.conf index 7762c4e..2934c3c 100644 --- a/demo/boards/nrf9160dk_nrf9160_ns.conf +++ b/demo/boards/nrf9160dk_nrf9160_ns.conf @@ -22,10 +22,10 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 CONFIG_LOG_RUNTIME_FILTERING=n CONFIG_LOG_CMDS=n CONFIG_LOG_MAX_LEVEL=3 -CONFIG_LOG_DEFAULT_LEVEL=2 CONFIG_HWINFO=n # Networking +CONFIG_NET_NATIVE=n CONFIG_NET_IF_MAX_IPV4_COUNT=2 CONFIG_NET_TCP_ISN_RFC6528=n CONFIG_NET_LOG=n @@ -75,3 +75,4 @@ CONFIG_DEVICE_SHELL=n CONFIG_DATE_SHELL=n CONFIG_DEVMEM_SHELL=n CONFIG_MCUBOOT_SHELL=n +CONFIG_KERNEL_SHELL=y diff --git a/demo/boards/nrf9160dk_nrf9160_ns_extflash.conf b/demo/boards/nrf9160dk_nrf9160_ns_extflash.conf index 9f23a5f..e3e971e 100644 --- a/demo/boards/nrf9160dk_nrf9160_ns_extflash.conf +++ b/demo/boards/nrf9160dk_nrf9160_ns_extflash.conf @@ -16,6 +16,7 @@ CONFIG_MAIN_STACK_SIZE=2048 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 # Networking +CONFIG_NET_NATIVE=n CONFIG_NET_IF_MAX_IPV4_COUNT=2 CONFIG_NET_TCP_ISN_RFC6528=n diff --git a/demo/boards/thingy91_nrf9160_ns.conf b/demo/boards/thingy91_nrf9160_ns.conf index 921d176..5ed558c 100644 --- a/demo/boards/thingy91_nrf9160_ns.conf +++ b/demo/boards/thingy91_nrf9160_ns.conf @@ -25,10 +25,10 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 CONFIG_LOG_RUNTIME_FILTERING=n CONFIG_LOG_CMDS=n CONFIG_LOG_MAX_LEVEL=3 -CONFIG_LOG_DEFAULT_LEVEL=2 CONFIG_HWINFO=n # Networking +CONFIG_NET_NATIVE=n CONFIG_NET_IF_MAX_IPV4_COUNT=2 CONFIG_NET_TCP_ISN_RFC6528=n CONFIG_NET_LOG=n @@ -87,3 +87,4 @@ CONFIG_DEVICE_SHELL=n CONFIG_DATE_SHELL=n CONFIG_DEVMEM_SHELL=n CONFIG_MCUBOOT_SHELL=n +CONFIG_KERNEL_SHELL=y diff --git a/demo/overlay_nrf_mbedtls.conf b/demo/overlay_nrf_mbedtls.conf new file mode 100644 index 0000000..391e47e --- /dev/null +++ b/demo/overlay_nrf_mbedtls.conf @@ -0,0 +1,15 @@ +CONFIG_ANJAY_COMPAT_ZEPHYR_TLS=n +CONFIG_ANJAY_COMPAT_MBEDTLS=y + +CONFIG_NORDIC_SECURITY_BACKEND=n +CONFIG_OBERON_BACKEND=n + +CONFIG_MBEDTLS_TLS_LIBRARY=y +CONFIG_MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG=n +CONFIG_MBEDTLS_ENTROPY_C=y +CONFIG_MBEDTLS_CIPHER_MODE_CBC=y +CONFIG_MBEDTLS_DHM_C=y +CONFIG_MBEDTLS_CTR_DRBG_C=y +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_LEGACY_CRYPTO_C=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y diff --git a/demo/src/anjay_shell.c b/demo/src/anjay_shell.c index db81c00..43ae19f 100644 --- a/demo/src/anjay_shell.c +++ b/demo/src/anjay_shell.c @@ -19,6 +19,8 @@ #include "utils.h" #include "persistence.h" +#include "network/network.h" + static int cmd_anjay_start(const struct shell *shell, size_t argc, char **argv) { ARG_UNUSED(argc); @@ -60,7 +62,7 @@ static int cmd_anjay_stop(const struct shell *shell, size_t argc, char **argv) // change the flag first to interrupt the thread if event loop is not // running yet atomic_store(&anjay_running, false); - interrupt_net_connect_wait_loop(); + network_interrupt_connect_wait_loop(); SYNCHRONIZED(global_anjay_mutex) { @@ -105,6 +107,7 @@ static int cmd_anjay_config_set(const struct shell *shell, size_t argc, char **a return config_set_option(shell, argc, argv); } + static int cmd_anjay_config_default(const struct shell *shell, size_t argc, char **argv) { ARG_UNUSED(argc); @@ -234,7 +237,8 @@ SHELL_STATIC_SUBCMD_SET_CREATE( #endif // CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING #ifdef CONFIG_WIFI SHELL_CMD(OPTION_KEY_SSID, NULL, "Wi-Fi SSID", cmd_anjay_config_set), - SHELL_CMD(OPTION_KEY_PASSWORD, NULL, "Wi-Fi password", cmd_anjay_config_set), + SHELL_CMD(OPTION_KEY_PASSWORD, NULL, "Wi-Fi password (empty for no-sec)", + cmd_anjay_config_set), #endif // CONFIG_WIFI #ifndef CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING SHELL_CMD(OPTION_KEY_URI, NULL, "Server URI", cmd_anjay_config_set), diff --git a/demo/src/common.h b/demo/src/common.h index d01f6d7..a91b702 100644 --- a/demo/src/common.h +++ b/demo/src/common.h @@ -16,14 +16,16 @@ #pragma once -#include -#include -#include #include #include +#include +#include +#include + #include +#include "network/network.h" #include "objects/objects.h" extern volatile atomic_bool device_initialized; @@ -39,6 +41,8 @@ extern volatile bool anjay_thread_running; extern struct k_mutex anjay_thread_running_mutex; extern struct k_condvar anjay_thread_running_condvar; +void sched_update_anjay_network_bearer(void); + #ifdef CONFIG_ANJAY_CLIENT_LOCATION_SERVICES_MANUAL_CELL_BASED struct cell_request_job_args { anjay_t *anjay; @@ -49,5 +53,3 @@ avs_sched_clb_t cell_request_job; #ifdef CONFIG_ANJAY_CLIENT_GPS_NRF_A_GPS avs_sched_clb_t agps_request_job; #endif // CONFIG_ANJAY_CLIENT_GPS_NRF_A_GPS - -void interrupt_net_connect_wait_loop(void); diff --git a/demo/src/config.c b/demo/src/config.c index 66d4209..f25059b 100644 --- a/demo/src/config.c +++ b/demo/src/config.c @@ -19,16 +19,16 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "config.h" #include "default_config.h" @@ -142,12 +142,15 @@ static int settings_set(const char *key, size_t len, settings_read_cb read_cb, v if (key) { for (int i = 0; i < AVS_ARRAY_SIZE(string_options); ++i) { if (strcmp(key, string_options[i].key) == 0) { - if (len != string_options[i].value_capacity) { + if (len > string_options[i].value_capacity) { return -EINVAL; } - int result = read_cb(cb_arg, string_options[i].value, - string_options[i].value_capacity); + memset(string_options[i].value, 0, + string_options[i].value_capacity); + + int result = read_cb(cb_arg, string_options[i].value, len); + return result >= 0 ? 0 : result; } } @@ -167,6 +170,10 @@ void config_print_summary(const struct shell *shell) void config_save(const struct shell *shell) { + // This is a POSIX extension provided by newlib, but only visible with + // #define _POSIX_C_SOURCE 200809L. That conflicts with some declarations in Zephyr, though. + size_t strnlen(const char *s, size_t maxlen); + char key_buf[64]; int result = 0; @@ -174,8 +181,13 @@ void config_save(const struct shell *shell) result = avs_simple_snprintf(key_buf, sizeof(key_buf), SETTINGS_ROOT_NAME "/%s", string_options[i].key); if (result >= 0) { - result = settings_save_one(key_buf, string_options[i].value, - string_options[i].value_capacity); + size_t length = + strnlen(string_options[i].value, string_options[i].value_capacity); + // Allow for storing empty strings '\0' + if (length == 0) { + length = 1; + } + result = settings_save_one(key_buf, string_options[i].value, length); } } @@ -242,32 +254,34 @@ void config_init(const struct shell *shell) int config_set_option(const struct shell *shell, size_t argc, char **argv) { - if (argc != 2) { - shell_error(shell, "Wrong number of arguments.\n"); - return -1; - } - const char *key = argv[0]; + const char *value = ""; + struct anjay_client_option *option = NULL; + if (argc == 2) { + value = argv[1]; + } else if (argc > 2 || argc == 0) { + shell_warn(shell, "Wrong argument number"); + return -1; + } for (size_t i = 0; i < AVS_ARRAY_SIZE(string_options); ++i) { if (strcmp(key, string_options[i].key) == 0) { - const char *value = argv[1]; - size_t value_len = strlen(value) + 1; - - assert(string_options[i].validator); - if (string_options[i].validator(shell, value, value_len, - &string_options[i])) { - return -1; - } + option = &string_options[i]; + break; + } + } + assert(option); - memcpy(string_options[i].value, value, value_len); + size_t value_len = strlen(value) + 1; - return 0; - } + assert(option->validator); + if (option->validator(shell, value, value_len, option)) { + return -1; } - AVS_UNREACHABLE("Invalid option key"); - return -1; + memcpy(option->value, value, value_len); + + return 0; } #endif // WITH_ANJAY_CLIENT_CONFIG @@ -278,6 +292,7 @@ static int parse_uint32(const char *value, uint32_t *out) } #endif // defined(CONFIG_ANJAY_CLIENT_GPS_NRF) || !defined(CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING) + #ifndef CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING const char *config_get_endpoint_name(void) { @@ -295,8 +310,26 @@ const char *config_get_wifi_password(void) { return app_config.password; } + +struct wifi_connect_req_params config_get_wifi_params(void) +{ + struct wifi_connect_req_params result = { 0 }; + + result.ssid = (uint8_t *)config_get_wifi_ssid(); + result.ssid_length = strlen((const char *)result.ssid); + result.psk = (uint8_t *)config_get_wifi_password(); + result.psk_length = strlen((const char *)result.psk); + if (result.psk_length) { + result.security = WIFI_SECURITY_TYPE_PSK; + } else { + result.security = WIFI_SECURITY_TYPE_NONE; + } + + return result; +} #endif // CONFIG_WIFI + #ifndef CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING const char *config_get_server_uri(void) { @@ -366,6 +399,7 @@ static int string_validate(const struct shell *shell, const char *value, size_t * !defined(CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING) */ + #ifndef CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING static int flag_validate(const struct shell *shell, const char *value, size_t value_len, const struct anjay_client_option *option) diff --git a/demo/src/config.h b/demo/src/config.h index 1eea5d1..be862b6 100644 --- a/demo/src/config.h +++ b/demo/src/config.h @@ -18,7 +18,13 @@ #include -#include +#include + +#ifdef CONFIG_WIFI +#include +#endif // CONFIG_WIFI + +#include "network/network.h" #ifdef CONFIG_WIFI #define OPTION_KEY_SSID wifi_ssid @@ -48,6 +54,7 @@ * !defined(CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING) */ + const char *config_default_ep_name(void); #ifdef WITH_ANJAY_CLIENT_CONFIG @@ -67,10 +74,11 @@ const char *config_get_endpoint_name(void); #ifdef CONFIG_WIFI const char *config_get_wifi_ssid(void); - const char *config_get_wifi_password(void); +struct wifi_connect_req_params config_get_wifi_params(void); #endif // CONFIG_WIFI + #ifndef CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING const char *config_get_server_uri(void); diff --git a/demo/src/default_config.h b/demo/src/default_config.h index 708e787..03f600a 100644 --- a/demo/src/default_config.h +++ b/demo/src/default_config.h @@ -16,7 +16,9 @@ #pragma once -#define CLIENT_VERSION "22.08-75-gd102363" +#include "config.h" + +#define CLIENT_VERSION "22.12" #ifdef CONFIG_WIFI #define WIFI_SSID "ssid" @@ -24,6 +26,7 @@ #define WIFI_PASSWORD "password" #endif // CONFIG_WIFI + #define SERVER_URI "coaps://eu.iot.avsystem.cloud:5684" #define LIFETIME "50" diff --git a/demo/src/factory_provisioning/factory_flash.c b/demo/src/factory_provisioning/factory_flash.c index 8e56257..b48a906 100644 --- a/demo/src/factory_provisioning/factory_flash.c +++ b/demo/src/factory_provisioning/factory_flash.c @@ -18,17 +18,18 @@ #include #include -#include -#include -#include +#include +#include +#include -#include +#include #include #include #include #include "factory_flash.h" +#include "../config.h" /* * THE PROCESS FOR FLASHING FACTORY PROVISIONING INFORMATION @@ -54,7 +55,7 @@ * * What happens under the hood during steps 3 and 4: * - * 1. run_anjay() in main.c calls factory_flash_input_stream_create(). This + * 1. run_anjay() in main.c calls factory_flash_input_stream_init(). This * initializes the fake "provision_fs" file system and mounts it at /factory. * 2. mcumgr starts writing the /factory/provision.cbor file. This will call: * - provision_fs_stat() - that will error out; mcumgr code only does this @@ -96,7 +97,6 @@ static enum { FACTORY_FLASH_FINISHED } factory_flash_state; static char factory_flash_result[AVS_INT_STR_BUF_SIZE(int)]; -static size_t factory_flash_result_offset; static K_MUTEX_DEFINE(factory_flash_mutex); static K_CONDVAR_DEFINE(factory_flash_condvar); @@ -104,66 +104,79 @@ static K_CONDVAR_DEFINE(factory_flash_condvar); #define PROVISION_FS_TYPE FS_TYPE_EXTERNAL_BASE #define PROVISION_FS_MOUNT_POINT "/factory" -#define PROVISION_FS_FLASH_FILE PROVISION_FS_MOUNT_POINT "/provision.cbor" -#define PROVISION_FS_RESULT_FILE PROVISION_FS_MOUNT_POINT "/result.txt" + +enum provision_fs_files { FLASH_FILE, RESULT_FILE, EP_FILE }; + +static struct provision_fs_file_info { + const char *const name; + const uint8_t *value; + size_t size; + size_t offset; +} files[] = { + [FLASH_FILE] = { .name = PROVISION_FS_MOUNT_POINT "/provision.cbor" }, + [RESULT_FILE] = { .name = PROVISION_FS_MOUNT_POINT "/result.txt" }, + [EP_FILE] = { .name = PROVISION_FS_MOUNT_POINT "/endpoint.txt" }, +}; // 15 seconds of inactivity is treated as EOF #define PROVISION_FS_UPLOAD_TIMEOUT_MS 15000 static int provision_fs_open(struct fs_file_t *filp, const char *fs_path, fs_mode_t flags) { - (void)filp; - (void)fs_path; - (void)flags; - if (strcmp(fs_path, PROVISION_FS_FLASH_FILE) == 0) { + if (strcmp(fs_path, files[FLASH_FILE].name) == 0) { if ((flags & (FS_O_READ | FS_O_WRITE)) == FS_O_WRITE && factory_flash_state == FACTORY_FLASH_INITIAL) { + filp->filep = &files[FLASH_FILE]; return 0; } - } else if (strcmp(fs_path, PROVISION_FS_RESULT_FILE) == 0) { + } else if (strcmp(fs_path, files[RESULT_FILE].name) == 0) { if ((flags & (FS_O_READ | FS_O_WRITE)) == FS_O_READ) { - if (factory_flash_state < FACTORY_FLASH_FINISHED) { - factory_flash_state = FACTORY_FLASH_EOF; - k_condvar_broadcast(&factory_flash_condvar); - } + filp->filep = &files[RESULT_FILE]; + return 0; + } + } else if (strcmp(fs_path, files[EP_FILE].name) == 0) { + if ((flags & (FS_O_READ | FS_O_WRITE)) == FS_O_READ) { + filp->filep = &files[EP_FILE]; return 0; } } + return -ENOENT; } static ssize_t provision_fs_read(struct fs_file_t *filp, void *dest, size_t nbytes) { - // Read provisioning result - (void)filp; + if (!filp->filep) { + return -EBADF; + } + ssize_t result = k_mutex_lock(&factory_flash_mutex, K_FOREVER); if (result) { return result; } - while (factory_flash_state != FACTORY_FLASH_FINISHED) { - k_condvar_wait(&factory_flash_condvar, &factory_flash_mutex, K_FOREVER); - } + if (filp->filep == &files[RESULT_FILE] || filp->filep == &files[EP_FILE]) { + struct provision_fs_file_info *file_info = filp->filep; + const uint8_t *src = file_info->value + file_info->offset; + size_t bytes_to_copy = AVS_MIN(file_info->size - file_info->offset, nbytes); - size_t bytes_to_copy = strlen(factory_flash_result) - factory_flash_result_offset; + memcpy(dest, src, bytes_to_copy); - if (nbytes < bytes_to_copy) { - bytes_to_copy = nbytes; + file_info->offset += bytes_to_copy; + result = bytes_to_copy; + } else { + result = -EBADF; } - memcpy((char *)dest, factory_flash_result + factory_flash_result_offset, bytes_to_copy); - k_mutex_unlock(&factory_flash_mutex); - return bytes_to_copy; + return result; } static ssize_t provision_fs_write(struct fs_file_t *filp, const void *src, size_t nbytes) { - // Write the actual provisioning data - (void)filp; - if (factory_flash_state != FACTORY_FLASH_INITIAL) { + if (factory_flash_state != FACTORY_FLASH_INITIAL || filp->filep != &files[FLASH_FILE]) { return -EBADF; } @@ -209,19 +222,18 @@ static ssize_t provision_fs_write(struct fs_file_t *filp, const void *src, size_ static int provision_fs_lseek(struct fs_file_t *filp, off_t off, int whence) { - (void)filp; - (void)off; - (void)whence; assert(whence == FS_SEEK_SET); if (filp->flags & FS_O_WRITE) { // The provisioning file assert(off == (size_t)received_data_total); } else { // The status file - if (off > strlen(factory_flash_result)) { + struct provision_fs_file_info *file_info = filp->filep; + + if (off > file_info->size) { return -ENXIO; } - factory_flash_result_offset = off; + file_info->offset = off; } return 0; } @@ -242,16 +254,22 @@ static int provision_fs_unlink(struct fs_mount_t *mountp, const char *name) static int provision_fs_stat(struct fs_mount_t *mountp, const char *path, struct fs_dirent *entry) { (void)mountp; - (void)path; (void)entry; - if (strcmp(path, PROVISION_FS_RESULT_FILE) != 0) { + + if (strcmp(path, files[RESULT_FILE].name) != 0 && strcmp(path, files[EP_FILE].name) != 0) { return -ENOENT; } int result = k_mutex_lock(&factory_flash_mutex, K_FOREVER); - if (!result) { - if (factory_flash_state == FACTORY_FLASH_INITIAL) { + if (result) { + return result; + } + + struct provision_fs_file_info *file_info; + + if (strcmp(path, files[RESULT_FILE].name) == 0) { + if (factory_flash_state < FACTORY_FLASH_FINISHED) { factory_flash_state = FACTORY_FLASH_EOF; k_condvar_broadcast(&factory_flash_condvar); } @@ -260,13 +278,21 @@ static int provision_fs_stat(struct fs_mount_t *mountp, const char *path, struct k_condvar_wait(&factory_flash_condvar, &factory_flash_mutex, K_FOREVER); } - entry->type = FS_DIR_ENTRY_FILE; - entry->size = strlen(factory_flash_result); - - k_mutex_unlock(&factory_flash_mutex); + file_info = &files[RESULT_FILE]; + file_info->value = factory_flash_result; + } else { + file_info = &files[EP_FILE]; + file_info->value = config_default_ep_name(); } - return result; + file_info->size = strlen(file_info->value); + + entry->size = file_info->size; + entry->type = FS_DIR_ENTRY_FILE; + + k_mutex_unlock(&factory_flash_mutex); + + return 0; } static const struct fs_file_system_t provision_fs = { .open = provision_fs_open, @@ -336,7 +362,7 @@ static struct { const avs_stream_v_table_t *const vtable; } provision_stream = { .vtable = &provision_stream_vtable }; -avs_stream_t *factory_flash_input_stream_create(void) +avs_stream_t *factory_flash_input_stream_init(void) { if (fs_register(PROVISION_FS_TYPE, &provision_fs) || fs_mount(&provision_fs_mount_point)) { return NULL; diff --git a/demo/src/factory_provisioning/factory_flash.h b/demo/src/factory_provisioning/factory_flash.h index 45a8966..b243b18 100644 --- a/demo/src/factory_provisioning/factory_flash.h +++ b/demo/src/factory_provisioning/factory_flash.h @@ -18,5 +18,5 @@ #include -avs_stream_t *factory_flash_input_stream_create(void); +avs_stream_t *factory_flash_input_stream_init(void); void factory_flash_finished(int result); diff --git a/demo/src/factory_provisioning/provisioning_app.c b/demo/src/factory_provisioning/provisioning_app.c index 0c9d832..91f5974 100644 --- a/demo/src/factory_provisioning/provisioning_app.c +++ b/demo/src/factory_provisioning/provisioning_app.c @@ -14,9 +14,9 @@ * limitations under the License. */ -#include -#include -#include +#include +#include +#include #include #include @@ -63,35 +63,24 @@ static void factory_provision(void) LOG_INF("Factory provisioning information already present. " "Please flash production firmware. Halting."); } else { + LOG_WRN("NOTE: No more log messages will be displayed. Please use " + "mcumgr to check provisioning results"); + + LOG_INF("Device ready for provisioning."); + z_shell_log_backend_disable(shell_backend_uart_get_ptr()->log_backend); - avs_stream_t *stream = factory_flash_input_stream_create(); + avs_stream_t *stream = factory_flash_input_stream_init(); assert(stream); avs_error_t err = anjay_factory_provision(anjay, stream); - avs_error_t cleanup_err = avs_stream_cleanup(&stream); + // NOTE: Not calling avs_stream_cleanup() because stream is *NOT* heap-allocated - if (avs_is_ok(err)) { - err = cleanup_err; - } if (avs_is_ok(err) && persist_factory_provisioning_info(anjay)) { err = avs_errno(AVS_EIO); } factory_flash_finished(avs_is_ok(err) ? 0 : -1); - - z_shell_log_backend_enable(shell_backend_uart_get_ptr()->log_backend, - (struct shell *)(uintptr_t)shell_backend_uart_get_ptr(), - AVS_MIN(CONFIG_SHELL_BACKEND_SERIAL_LOG_LEVEL, - CONFIG_LOG_MAX_LEVEL)); - - if (avs_is_err(err)) { - LOG_ERR("Could not perform factory provisioning. Rebooting."); - abort(); - } else { - LOG_INF("Factory provisioning finished. " - "Please flash production firmware. Halting."); - } } while (true) { diff --git a/demo/src/firmware_update.c b/demo/src/firmware_update.c index be13c0d..27a2634 100644 --- a/demo/src/firmware_update.c +++ b/demo/src/firmware_update.c @@ -17,17 +17,21 @@ #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include "firmware_update.h" #include "utils.h" LOG_MODULE_REGISTER(fw_update); +#define SETTINGS_ROOT_NAME "anjay_fw_update" +#define SETTINGS_APP_JUST_UPDATED_KEY "app_just_updated" + static bool just_updated; static bool update_requested; @@ -70,12 +74,10 @@ static int fw_stream_finish(void *user_ptr) assert(img_ctx_initialized); - if (flash_img_buffered_write(&img_ctx, NULL, 0, true)) { - return -1; - } + int result = flash_img_buffered_write(&img_ctx, NULL, 0, true) ? -1 : 0; img_ctx_initialized = false; - return 0; + return result; } static void fw_reset(void *user_ptr) @@ -126,11 +128,61 @@ int fw_update_install(anjay_t *anjay) state.result = ANJAY_FW_UPDATE_INITIAL_SUCCESS; } - return anjay_fw_update_install(anjay, &handlers, anjay, &state); + int result = anjay_fw_update_install(anjay, &handlers, anjay, &state); + + if (!result && just_updated) { + if (settings_delete(SETTINGS_ROOT_NAME "/" SETTINGS_APP_JUST_UPDATED_KEY)) { + LOG_ERR("Couldn't delete the just_updated flag"); + } + + just_updated = false; + } + + return result; +} + +static int fw_settings_set(const char *key, size_t len, settings_read_cb read_cb, void *cb_arg) +{ + if (strcmp(key, SETTINGS_APP_JUST_UPDATED_KEY) != 0) { + return -ENOENT; + } + + if (len > 1) { + return -EINVAL; + } + + char value = 0; + + int result = read_cb(cb_arg, &value, len); + + if (result < 0) { + return result; + } + + if (value != 0) { + just_updated = true; + } + + return 0; } +SETTINGS_STATIC_HANDLER_DEFINE(anjay_fw_update, SETTINGS_ROOT_NAME, NULL, fw_settings_set, NULL, + NULL); + void fw_update_apply(void) { + int settings_state = settings_subsys_init(); + + if (settings_state) { + LOG_ERR("Couldn't init settings subsystem"); + } else { + settings_load_subtree(SETTINGS_ROOT_NAME); + } + + if (just_updated) { + LOG_INF("Undelivered previous firmware update success"); + } + // Image may be unconfirmed, because: // - we've just did a FOTA of the device and new // firmware is being run @@ -144,9 +196,17 @@ void fw_update_apply(void) // // We can differentiate these two situations by taking // the retval of boot_write_img_confirmed(). - just_updated = !boot_is_img_confirmed() && !boot_write_img_confirmed(); - if (just_updated) { + if (!boot_is_img_confirmed() && !boot_write_img_confirmed()) { LOG_INF("Successfully updated firmware"); + + if (!just_updated) { + just_updated = true; + + if (settings_save_one(SETTINGS_ROOT_NAME "/" SETTINGS_APP_JUST_UPDATED_KEY, + "1", 1)) { + LOG_ERR("Couldn't save the just_updated flag"); + } + } } } diff --git a/demo/src/gps.h b/demo/src/gps.h index fc4187a..d6466fe 100644 --- a/demo/src/gps.h +++ b/demo/src/gps.h @@ -16,9 +16,11 @@ #pragma once -#include +#include #include +#include + #ifdef CONFIG_ANJAY_CLIENT_GPS struct gps_data { bool valid; @@ -49,4 +51,8 @@ int initialize_gps(void); uint32_t gps_fetch_modem_agps_request_mask(void); #endif // CONFIG_ANJAY_CLIENT_GPS_NRF_A_GPS +#ifdef CONFIG_ANJAY_CLIENT_GPS_NRF +extern volatile atomic_bool gps_prio_mode; +#endif // CONFIG_ANJAY_CLIENT_GPS_NRF + #endif // CONFIG_ANJAY_CLIENT_GPS diff --git a/demo/src/gps_impl/gps_nrf.c b/demo/src/gps_impl/gps_nrf.c index 7b89f76..346d864 100644 --- a/demo/src/gps_impl/gps_nrf.c +++ b/demo/src/gps_impl/gps_nrf.c @@ -18,15 +18,16 @@ #error "This GPS implementation is not supported by selected board" #endif // !(defined(CONFIG_BOARD_NRF9160DK_NRF9160_NS) || defined(CONFIG_BOARD_THINGY91_NRF9160_NS)) -#include -#include +#include +#include +#include + #include #include -#include -#include #include "../common.h" #include "../config.h" +#include "../network/network_internal.h" #include "../gps.h" #include "../utils.h" @@ -57,6 +58,10 @@ static const char *const init_at_commands[] = { #else // CONFIG_ANJAY_CLIENT_GPS_NRF_EXTERNAL_ANTENNA "AT%XCOEX0=1,1,1565,1586", #endif // CONFIG_ANJAY_CLIENT_GPS_NRF_EXTERNAL_ANTENNA + + // https://infocenter.nordicsemi.com/topic/ref_at_commands/REF/at_commands/mob_termination_ctrl_status/cfun_set.html + // 31 - Activates GNSS without changing LTE. + "AT+CFUN=31", }; K_MUTEX_DEFINE(gps_read_last_mtx); @@ -76,6 +81,8 @@ static void prio_mode_disable_dwork_handler(struct k_work *work); K_WORK_DEFINE(incoming_pvt_work, incoming_pvt_work_handler); K_WORK_DELAYABLE_DEFINE(prio_mode_disable_dwork, prio_mode_disable_dwork_handler); +volatile atomic_bool gps_prio_mode; + static int64_t gnss_datetime_to_timestamp(const struct nrf_modem_gnss_datetime *datetime) { struct tm broken_down = { .tm_year = datetime->year - 1900, @@ -92,12 +99,8 @@ void prio_mode_disable(void) { LOG_INF("Disabling gnss_prio_mode"); - SYNCHRONIZED(global_anjay_mutex) - { - if (global_anjay) { - anjay_transport_exit_offline(global_anjay, ANJAY_TRANSPORT_SET_IP); - } - } + atomic_store(&gps_prio_mode, false); + network_internal_connection_state_changed(); if (nrf_modem_gnss_prio_mode_disable()) { LOG_ERR("Couldn't disable gnss_prio_mode"); @@ -161,13 +164,8 @@ static void incoming_pvt_work_handler(struct k_work *work) return; } - SYNCHRONIZED(global_anjay_mutex) - { - if (global_anjay) { - anjay_transport_enter_offline(global_anjay, - ANJAY_TRANSPORT_SET_IP); - } - } + atomic_store(&gps_prio_mode, true); + network_internal_connection_state_changed(); k_work_schedule(&prio_mode_disable_dwork, K_SECONDS(gps_prio_mode_timeout)); } diff --git a/demo/src/main_app.c b/demo/src/main_app.c index 29a2afe..d2b7b04 100644 --- a/demo/src/main_app.c +++ b/demo/src/main_app.c @@ -14,17 +14,19 @@ * limitations under the License. */ -#include -#include #include -#include -#include -#include + #include -#include -#include -#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include @@ -43,23 +45,9 @@ #include "utils.h" #include "persistence.h" #include "status_led.h" -#include "objects/objects.h" -#if defined(CONFIG_WIFI) -#ifdef CONFIG_WIFI_ESWIFI -#include -#endif // CONFIG_WIFI_ESWIFI -#ifdef CONFIG_WIFI_ESP32 -#include -#include -#include -#endif // CONFIG_WIFI_ESP32 -#include -#include -#elif defined(CONFIG_LTE_LINK_CONTROL) -#include -#endif /* defined(CONFIG_WIFI) || defined(CONFIG_LTE_LINK_CONTROL) - */ +#include "network/network.h" +#include "objects/objects.h" #ifdef CONFIG_DATE_TIME #include @@ -69,6 +57,10 @@ #include "nrf_lc_info.h" #endif // CONFIG_ANJAY_CLIENT_NRF_LC_INFO +#ifdef CONFIG_NET_L2_OPENTHREAD +#define RETRY_SYNC_CLOCK_DELAY_TIME_S 1 +#endif // CONFIG_NET_L2_OPENTHREAD + LOG_MODULE_REGISTER(main_app); static const anjay_dm_object_def_t **buzzer_obj; static const anjay_dm_object_def_t **device_obj; @@ -99,13 +91,25 @@ K_MUTEX_DEFINE(anjay_thread_running_mutex); K_CONDVAR_DEFINE(anjay_thread_running_condvar); K_THREAD_STACK_DEFINE(anjay_stack, ANJAY_THREAD_STACK_SIZE); -#if defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#ifdef CONFIG_NET_L2_OPENTHREAD +static struct k_work_delayable sync_clock_work; +#endif // CONFIG_NET_L2_OPENTHREAD + +static bool anjay_online; +static enum network_bearer_t anjay_last_known_bearer; + +static K_SEM_DEFINE(synchronize_clock_sem, 0, 1); + +#if defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && \ + defined(CONFIG_MODEM_KEY_MGMT) // The only parameters needed to address a credential stored in the modem // are its type and its security tag - the type is defined already by // the proper function being called, so the query contains only a single // integer - the desired security tag. static const char *PSK_QUERY = "1"; -#endif // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#endif /* defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && + * defined(CONFIG_MODEM_KEY_MGMT) + */ #ifdef CONFIG_DATE_TIME static void set_system_time(const struct sntp_time *time) @@ -131,13 +135,31 @@ void synchronize_clock(void) struct sntp_time time; const uint32_t timeout_ms = 5000; - if (sntp_simple(NTP_SERVER, timeout_ms, &time)) { - LOG_WRN("Failed to get current time"); - } else { + if (false +#if defined(CONFIG_NET_IPV6) + || !sntp_simple_ipv6(NTP_SERVER, timeout_ms, &time) +#endif +#if defined(CONFIG_NET_IPV4) + || !sntp_simple(NTP_SERVER, timeout_ms, &time) +#endif + ) { set_system_time(&time); + k_sem_give(&synchronize_clock_sem); + } else { + LOG_WRN("Failed to get current time"); +#ifdef CONFIG_NET_L2_OPENTHREAD + k_work_schedule(&sync_clock_work, K_SECONDS(RETRY_SYNC_CLOCK_DELAY_TIME_S)); +#endif // CONFIG_NET_L2_OPENTHREAD } } +#ifdef CONFIG_NET_L2_OPENTHREAD +static void retry_synchronize_clock_work_handler(struct k_work *work) +{ + synchronize_clock(); +} +#endif // CONFIG_NET_L2_OPENTHREAD + static int register_objects(anjay_t *anjay) { device_obj = device_object_create(); @@ -302,183 +324,13 @@ static void release_objects(void) #endif // CONFIG_ANJAY_CLIENT_LOCATION_SERVICES } -static K_MUTEX_DEFINE(net_connect_mutex); -static K_CONDVAR_DEFINE(net_connect_condvar); - -#if defined(CONFIG_WIFI) -static volatile atomic_bool ip_addr_assigned; - -static void ip_addr_add_cb(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, - struct net_if *iface) -{ - (void)cb; - (void)mgmt_event; - (void)iface; - - atomic_store(&ip_addr_assigned, true); - interrupt_net_connect_wait_loop(); -} - -static int wait_for_ip_addr_interruptible(void) -{ - // Note: this mirrors the logic of net_mgmt_event_wait_on_iface(), but there is - // a nasty bug in that net_mgmt_del_event_callback() is never called by that function. - struct net_mgmt_event_callback cb; - int ret = -EAGAIN; - - memset(&cb, 0, sizeof(cb)); - net_mgmt_init_event_callback(&cb, ip_addr_add_cb, NET_EVENT_IPV4_ADDR_ADD); - atomic_store(&ip_addr_assigned, false); - net_mgmt_add_event_callback(&cb); - - SYNCHRONIZED(net_connect_mutex) - { - do { - k_condvar_wait(&net_connect_condvar, &net_connect_mutex, K_FOREVER); - if (atomic_load(&ip_addr_assigned)) { - ret = 0; - } else if (!atomic_load(&anjay_running)) { - ret = -ETIMEDOUT; - } - } while (ret == -EAGAIN); - } - - net_mgmt_del_event_callback(&cb); - - return ret; -} -#elif defined(CONFIG_LTE_LINK_CONTROL) -static void lte_connect_handler(const struct lte_lc_evt *const evt) -{ - if (evt && evt->type == LTE_LC_EVT_NW_REG_STATUS && - (evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME || - evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING)) { - interrupt_net_connect_wait_loop(); - } -} - -static int lte_interruptible_connect(void) -{ - int ret = -1; - enum lte_lc_nw_reg_status status; - - SYNCHRONIZED(net_connect_mutex) - { - // Note: this is supposed to be handled by lte_lc_connect_async(), - // but there is a nasty bug in in_progress flag handling - ret = lte_lc_nw_reg_status_get(&status); - if (!ret && status != LTE_LC_NW_REG_REGISTERED_HOME && - status != LTE_LC_NW_REG_REGISTERED_ROAMING) { - ret = lte_lc_connect_async(lte_connect_handler); - while (!ret) { - if (!atomic_load(&anjay_running)) { - ret = -ETIMEDOUT; - } else { - ret = lte_lc_nw_reg_status_get(&status); - if (!ret && (status == LTE_LC_NW_REG_REGISTERED_HOME || - status == LTE_LC_NW_REG_REGISTERED_ROAMING)) { - break; - } - k_condvar_wait(&net_connect_condvar, &net_connect_mutex, - K_FOREVER); - } - } - } - } - - return ret; -} -#endif // defined(CONFIG_WIFI) || defined(CONFIG_LTE_LINK_CONTROL) - -void interrupt_net_connect_wait_loop(void) -{ - SYNCHRONIZED(net_connect_mutex) - { - k_condvar_broadcast(&net_connect_condvar); - } -} - -static int initialize_network(void) -{ - LOG_INF("Initializing network connection..."); -#if defined(CONFIG_WIFI) - struct net_if *iface = net_if_get_default(); - -#ifdef CONFIG_WIFI_ESWIFI - struct eswifi_dev *eswifi = eswifi_by_iface_idx(0); - - assert(eswifi); - // Set regulatory domain to "World Wide (passive Ch12-14)"; eS-WiFi defaults - // to "US" which prevents connecting to networks that use channels 12-14. - if (eswifi_at_cmd(eswifi, "CN=XV\r") < 0) { - LOG_WRN("Failed to set Wi-Fi regulatory domain"); - } -#endif // CONFIG_WIFI_ESWIFI - -#ifdef CONFIG_WIFI_ESP32 - net_dhcpv4_start(iface); - - AVS_STATIC_ASSERT(!IS_ENABLED(CONFIG_ESP32_WIFI_STA_AUTO), - esp32_wifi_auto_mode_incompatible_with_project); - - wifi_config_t wifi_config = { 0 }; - - // use strncpy with the maximum length of sizeof(wifi_config.sta.{ssid|password}), - // because ESP32 Wi-Fi buffers don't have to be null-terminated - strncpy(wifi_config.sta.ssid, config_get_wifi_ssid(), sizeof(wifi_config.sta.ssid)); - strncpy(wifi_config.sta.password, config_get_wifi_password(), - sizeof(wifi_config.sta.password)); - - if (esp_wifi_set_mode(WIFI_MODE_STA) || - esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) || esp_wifi_connect()) { - LOG_ERR("connection failed"); - } -#else // CONFIG_WIFI_ESP32 - struct wifi_connect_req_params wifi_params = { .ssid = (uint8_t *)config_get_wifi_ssid(), - .ssid_length = - strlen(config_get_wifi_ssid()), - .psk = (uint8_t *)config_get_wifi_password(), - .psk_length = - strlen(config_get_wifi_password()), - .security = WIFI_SECURITY_TYPE_PSK }; - - int ret = net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &wifi_params, - sizeof(struct wifi_connect_req_params)); - - if (ret && ret != -EALREADY && ret != -EINPROGRESS) { - LOG_ERR("Failed to configure Wi-Fi"); - return -1; - } -#endif // CONFIG_WIFI_ESP32 - - if (wait_for_ip_addr_interruptible()) { - LOG_ERR("Wi-Fi link could not be established."); - net_mgmt(NET_REQUEST_WIFI_DISCONNECT, iface, NULL, 0); - return -1; - } -#elif defined(CONFIG_LTE_LINK_CONTROL) - int ret = lte_lc_init(); - - if (!ret) { - ret = lte_interruptible_connect(); - } - - if (ret < 0 && ret != -EALREADY && ret != -EINPROGRESS) { - LOG_ERR("LTE link could not be established."); - return -1; - } -#endif /* defined(CONFIG_WIFI) || defined(CONFIG_LTE_LINK_CONTROL) - */ - LOG_INF("Connected to network"); - return 0; -} - #ifndef CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING static int configure_servers_from_config(anjay_t *anjay, const anjay_configuration_t *config) { const bool bootstrap = config_is_bootstrap(); -#if defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#if defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && \ + defined(CONFIG_MODEM_KEY_MGMT) const char *psk_key = config_get_psk(); avs_crypto_psk_key_info_t psk_key_info = avs_crypto_psk_key_info_from_buffer(psk_key, strlen(psk_key)); @@ -492,23 +344,30 @@ static int configure_servers_from_config(anjay_t *anjay, const anjay_configurati LOG_ERR("Storing PSK identity failed"); return -1; } -#endif // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#endif /* defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && + * defined(CONFIG_MODEM_KEY_MGMT) + */ const anjay_security_instance_t security_instance = { .ssid = 1, .bootstrap_server = bootstrap, .server_uri = config_get_server_uri(), .security_mode = ANJAY_SECURITY_PSK, -#if defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#if defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && \ + defined(CONFIG_MODEM_KEY_MGMT) .psk_identity = avs_crypto_psk_identity_info_from_engine(PSK_QUERY), .psk_key = avs_crypto_psk_key_info_from_engine(PSK_QUERY), -#else // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#else /* defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && + * defined(CONFIG_MODEM_KEY_MGMT) + */ .public_cert_or_psk_identity = config->endpoint_name, .public_cert_or_psk_identity_size = strlen(security_instance.public_cert_or_psk_identity), .private_cert_or_psk_key = config_get_psk(), .private_cert_or_psk_key_size = strlen(security_instance.private_cert_or_psk_key) -#endif // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#endif /* defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && + * defined(CONFIG_MODEM_KEY_MGMT) + */ }; const anjay_server_instance_t server_instance = { .ssid = 1, @@ -541,17 +400,6 @@ static int configure_servers_from_config(anjay_t *anjay, const anjay_configurati static anjay_t *initialize_anjay(void) { - if (initialize_network()) { - LOG_ERR("Could not connect to the network"); - return NULL; - } - -#ifdef CONFIG_ANJAY_CLIENT_GPS - initialize_gps(); -#endif // CONFIG_ANJAY_CLIENT_GPS - - synchronize_clock(); - const anjay_configuration_t config = { #ifdef CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING .endpoint_name = config_default_ep_name(), @@ -617,72 +465,155 @@ static anjay_t *initialize_anjay(void) #endif // CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING error: -#if defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#if defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && \ + defined(CONFIG_MODEM_KEY_MGMT) if (avs_is_err(avs_crypto_psk_engine_key_rm(PSK_QUERY))) { LOG_WRN("Removing PSK key failed"); } if (avs_is_err(avs_crypto_psk_engine_identity_rm(PSK_QUERY))) { LOG_WRN("Removing PSK identity failed"); } -#endif // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#endif /* defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && + * defined(CONFIG_MODEM_KEY_MGMT) + */ anjay_delete(anjay); release_objects(); return NULL; } +static void update_anjay_network_bearer_unlocked(anjay_t *anjay, enum network_bearer_t bearer) +{ + if (anjay_online && !network_bearer_valid(bearer)) { + LOG_INF("Anjay is now offline"); + if (!anjay_transport_enter_offline(anjay, ANJAY_TRANSPORT_SET_ALL)) { + anjay_online = false; + } + } else if (network_bearer_valid(bearer) && + (!anjay_online || anjay_last_known_bearer != bearer)) { + LOG_INF("Anjay is now online on bearer %d", (int)bearer); + if (anjay_last_known_bearer != bearer) { + if (!anjay_transport_schedule_reconnect(anjay, ANJAY_TRANSPORT_SET_ALL)) { + anjay_last_known_bearer = bearer; + anjay_online = true; + } + } else if (!anjay_transport_exit_offline(anjay, ANJAY_TRANSPORT_SET_ALL)) { + anjay_online = true; + } + } +} + +static void update_anjay_network_bearer_job(avs_sched_t *sched, const void *dummy) +{ + ARG_UNUSED(dummy); + + SYNCHRONIZED(global_anjay_mutex) + { + if (global_anjay) { + update_anjay_network_bearer_unlocked(global_anjay, + network_current_bearer()); + } + } +} + +void sched_update_anjay_network_bearer(void) +{ + static avs_sched_handle_t job_handle; + + SYNCHRONIZED(global_anjay_mutex) + { + if (global_anjay) { + AVS_SCHED_NOW(anjay_get_scheduler(global_anjay), &job_handle, + update_anjay_network_bearer_job, NULL, 0); + } + } +} + void run_anjay(void *arg1, void *arg2, void *arg3) { ARG_UNUSED(arg1); ARG_UNUSED(arg2); ARG_UNUSED(arg3); + LOG_INF("Connecting to the network..."); + + if (network_connect_async()) { + LOG_ERR("Could not initiate connection"); + goto finish; + } + + if (network_wait_for_connected_interruptible()) { + LOG_ERR("Could not connect to the network"); + goto disconnect; + } + + LOG_INF("Connected to network"); + + k_sem_reset(&synchronize_clock_sem); + synchronize_clock(); + if (k_sem_take(&synchronize_clock_sem, K_SECONDS(30))) { + LOG_WRN("Could not synchronize system clock within timeout, " + "continuing without real time..."); + } + anjay_t *anjay = initialize_anjay(); - if (anjay) { - LOG_INF("Successfully created thread"); + if (!anjay) { + goto disconnect; + } - SYNCHRONIZED(global_anjay_mutex) - { - global_anjay = anjay; - } + LOG_INF("Successfully created thread"); - // anjay stop could be called immediately after anjay start - if (atomic_load(&anjay_running)) { - update_objects(anjay_get_scheduler(anjay), &anjay); - anjay_event_loop_run_with_error_handling( - anjay, avs_time_duration_from_scalar(1, AVS_TIME_S)); - } + SYNCHRONIZED(global_anjay_mutex) + { + global_anjay = anjay; - avs_sched_del(&update_objects_handle); + anjay_last_known_bearer = (enum network_bearer_t)0; + update_anjay_network_bearer_unlocked(anjay, network_current_bearer()); + } + + // anjay stop could be called immediately after anjay start + if (atomic_load(&anjay_running)) { + update_objects(anjay_get_scheduler(anjay), &anjay); + anjay_event_loop_run_with_error_handling( + anjay, avs_time_duration_from_scalar(1, AVS_TIME_S)); + } + + avs_sched_del(&update_objects_handle); #ifdef CONFIG_ANJAY_CLIENT_PERSISTENCE - if (config_is_use_persistence() && persist_anjay_if_required(anjay)) { - LOG_ERR("Couldn't persist Anjay's state!"); - } + if (config_is_use_persistence() && persist_anjay_if_required(anjay)) { + LOG_ERR("Couldn't persist Anjay's state!"); + } #endif // CONFIG_ANJAY_CLIENT_PERSISTENCE - SYNCHRONIZED(global_anjay_mutex) - { - global_anjay = NULL; - } - anjay_delete(anjay); - release_objects(); + SYNCHRONIZED(global_anjay_mutex) + { + global_anjay = NULL; + } + anjay_delete(anjay); + release_objects(); -#if defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) - if (avs_is_err(avs_crypto_psk_engine_key_rm(PSK_QUERY))) { - LOG_WRN("Removing PSK key failed"); - } - if (avs_is_err(avs_crypto_psk_engine_identity_rm(PSK_QUERY))) { - LOG_WRN("Removing PSK identity failed"); - } -#endif // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#if defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && \ + defined(CONFIG_MODEM_KEY_MGMT) + if (avs_is_err(avs_crypto_psk_engine_key_rm(PSK_QUERY))) { + LOG_WRN("Removing PSK key failed"); + } + if (avs_is_err(avs_crypto_psk_engine_identity_rm(PSK_QUERY))) { + LOG_WRN("Removing PSK identity failed"); + } +#endif /* defined(CONFIG_ANJAY_COMPAT_ZEPHYR_TLS) && defined(CONFIG_NRF_MODEM_LIB) && + * defined(CONFIG_MODEM_KEY_MGMT) + */ #ifdef CONFIG_ANJAY_CLIENT_FOTA - if (fw_update_requested()) { - fw_update_reboot(); - } -#endif // CONFIG_ANJAY_CLIENT_FOTA + if (fw_update_requested()) { + fw_update_reboot(); } +#endif // CONFIG_ANJAY_CLIENT_FOTA + +disconnect: + network_disconnect(); +finish: SYNCHRONIZED(anjay_thread_running_mutex) { anjay_thread_running = false; @@ -692,6 +623,8 @@ void run_anjay(void *arg1, void *arg2, void *arg3) void main(void) { + LOG_INF("Initializing Anjay-zephyr-client demo " CLIENT_VERSION); + #ifdef WITH_ANJAY_CLIENT_CONFIG config_init(shell_backend_uart_get_ptr()); #endif // WITH_ANJAY_CLIENT_CONFIG @@ -704,6 +637,20 @@ void main(void) status_led_init(); +#ifdef CONFIG_NET_L2_OPENTHREAD + k_work_init_delayable(&sync_clock_work, retry_synchronize_clock_work_handler); +#endif // CONFIG_NET_L2_OPENTHREAD + + if (network_initialize()) { + LOG_ERR("Cannot initialize the network"); + LOG_PANIC(); + abort(); + } + +#ifdef CONFIG_ANJAY_CLIENT_GPS + initialize_gps(); +#endif // CONFIG_ANJAY_CLIENT_GPS + #ifdef CONFIG_ANJAY_CLIENT_FOTA fw_update_apply(); #endif // CONFIG_ANJAY_CLIENT_FOTA diff --git a/demo/src/network/network.c b/demo/src/network/network.c new file mode 100644 index 0000000..d186868 --- /dev/null +++ b/demo/src/network/network.c @@ -0,0 +1,122 @@ +/* + * Copyright 2020-2022 AVSystem + * + * 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 + * + * http://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. + */ + +#include "network.h" +#include "network_internal.h" + +#include "../common.h" +#include "../utils.h" + +K_MUTEX_DEFINE(network_internal_connect_mutex); +K_CONDVAR_DEFINE(network_internal_connect_condvar); + +#ifdef CONFIG_NET_NATIVE_IPV4 +static struct net_mgmt_event_callback ipv4_mgmt_cb; +#endif // CONFIG_NET_NATIVE_IPV4 +#ifdef CONFIG_NET_NATIVE_IPV6 +static struct net_mgmt_event_callback ipv6_mgmt_cb; +#endif // CONFIG_NET_NATIVE_IPV6 + +#if defined(CONFIG_NET_NATIVE_IPV4) || defined(CONFIG_NET_NATIVE_IPV6) +static void net_mgmt_event_cb(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, + struct net_if *iface) +{ + network_internal_connection_state_changed(); +} + +bool network_internal_has_ip_address(struct net_if *iface) +{ +#ifdef CONFIG_NET_NATIVE_IPV4 + if (net_if_ipv4_get_global_addr(iface, NET_ADDR_ANY_STATE) || + net_if_ipv4_get_ll(iface, NET_ADDR_ANY_STATE)) { + return true; + } +#endif // CONFIG_NET_NATIVE_IPV4 + +#ifdef CONFIG_NET_NATIVE_IPV6 + // Link-local IPv6 addresses are useless, as every interface always has one. + // Also, NET_ADDR_ANY_STATE does not work with net_if_ipv6_get_global_addr(). + if (net_if_ipv6_get_global_addr(NET_ADDR_PREFERRED, &iface)) { + return true; + } +#endif // CONFIG_NET_NATIVE_IPV6 + + return false; +} + +__weak enum network_bearer_t network_current_bearer(void) +{ + bool connected = network_internal_has_ip_address(net_if_get_default()); + + return connected ? (enum network_bearer_t)0 : NETWORK_BEARER_LIMIT; +} +#endif // defined(CONFIG_NET_NATIVE_IPV4) || defined(CONFIG_NET_NATIVE_IPV6) + +void network_interrupt_connect_wait_loop(void) +{ + SYNCHRONIZED(network_internal_connect_mutex) + { + k_condvar_broadcast(&network_internal_connect_condvar); + } +} + +void network_internal_connection_state_changed(void) +{ + sched_update_anjay_network_bearer(); + network_interrupt_connect_wait_loop(); +} + +int network_initialize(void) +{ + int ret = network_internal_platform_initialize(); + + if (ret) { + return ret; + } + +#ifdef CONFIG_NET_NATIVE_IPV4 + net_mgmt_init_event_callback(&ipv4_mgmt_cb, net_mgmt_event_cb, + NET_EVENT_IPV4_ADDR_ADD | NET_EVENT_IPV4_ADDR_DEL); + net_mgmt_add_event_callback(&ipv4_mgmt_cb); +#endif // CONFIG_NET_NATIVE_IPV4 +#ifdef CONFIG_NET_NATIVE_IPV6 + net_mgmt_init_event_callback(&ipv6_mgmt_cb, net_mgmt_event_cb, + NET_EVENT_IPV6_ADDR_ADD | NET_EVENT_IPV6_ADDR_DEL); + net_mgmt_add_event_callback(&ipv6_mgmt_cb); +#endif // CONFIG_NET_NATIVE_IPV6 + return 0; +} + +int network_wait_for_connected_interruptible(void) +{ + int ret = -EAGAIN; + + SYNCHRONIZED(network_internal_connect_mutex) + { + do { + if (network_is_connected()) { + ret = 0; + } else if (!atomic_load(&anjay_running)) { + ret = -ETIMEDOUT; + } else { + k_condvar_wait(&network_internal_connect_condvar, + &network_internal_connect_mutex, K_FOREVER); + } + } while (ret == -EAGAIN); + } + + return ret; +} diff --git a/demo/src/network/network.h b/demo/src/network/network.h new file mode 100644 index 0000000..16ae3b4 --- /dev/null +++ b/demo/src/network/network.h @@ -0,0 +1,53 @@ +/* + * Copyright 2020-2022 AVSystem + * + * 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 + * + * http://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. + */ + +#pragma once + +#include + +#include + +enum network_bearer_t { +#ifdef CONFIG_WIFI + NETWORK_BEARER_WIFI, +#endif // CONFIG_WIFI +#if defined(CONFIG_MODEM) || defined(CONFIG_LTE_LINK_CONTROL) + NETWORK_BEARER_CELLULAR, +#endif // defined(CONFIG_MODEM) || defined(CONFIG_LTE_LINK_CONTROL) +#ifdef CONFIG_NET_L2_OPENTHREAD + NETWORK_BEARER_OPENTHREAD, +#endif // CONFIG_NET_L2_OPENTHREAD + NETWORK_BEARER_LIMIT +}; + +AVS_STATIC_ASSERT(NETWORK_BEARER_LIMIT > 0, no_network_bearers_available); + +static inline bool network_bearer_valid(enum network_bearer_t bearer) +{ + return bearer >= (enum network_bearer_t)0 && bearer < NETWORK_BEARER_LIMIT; +} + +void network_interrupt_connect_wait_loop(void); +int network_initialize(void); +int network_connect_async(void); +enum network_bearer_t network_current_bearer(void); +int network_wait_for_connected_interruptible(void); +void network_disconnect(void); + +static inline bool network_is_connected(void) +{ + return network_bearer_valid(network_current_bearer()); +} diff --git a/demo/src/network/network_esp32.c b/demo/src/network/network_esp32.c new file mode 100644 index 0000000..709288e --- /dev/null +++ b/demo/src/network/network_esp32.c @@ -0,0 +1,96 @@ +/* + * Copyright 2020-2022 AVSystem + * + * 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 + * + * http://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. + */ + +#include +#include +#include + +#include +#include +#include + +#include + +#include "network.h" +#include "network_internal.h" + +#include "../config.h" +#include "../utils.h" + +LOG_MODULE_REGISTER(network_esp32); + +static void esp32_connect_work_cb(struct k_work *work) +{ + net_dhcpv4_start(net_if_get_default()); +} + +static void esp32_disconnect_work_cb(struct k_work *work) +{ + net_dhcpv4_stop(net_if_get_default()); +} + +static K_WORK_DEFINE(esp32_connect_work, esp32_connect_work_cb); +static K_WORK_DEFINE(esp32_disconnect_work, esp32_disconnect_work_cb); + +struct net_mgmt_event_callback esp32_netif_updown_cb_obj = { 0 }; + +static void esp32_netif_updown_cb(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, + struct net_if *iface) +{ + if (mgmt_event == NET_EVENT_IF_UP) { + k_work_cancel(&esp32_disconnect_work); + k_work_submit(&esp32_connect_work); + } else if (mgmt_event == NET_EVENT_IF_DOWN) { + k_work_cancel(&esp32_connect_work); + k_work_submit(&esp32_disconnect_work); + } +} + +int network_internal_platform_initialize(void) +{ + net_mgmt_init_event_callback(&esp32_netif_updown_cb_obj, esp32_netif_updown_cb, + NET_EVENT_IF_UP | NET_EVENT_IF_DOWN); + net_mgmt_add_event_callback(&esp32_netif_updown_cb_obj); + + AVS_STATIC_ASSERT(!IS_ENABLED(CONFIG_ESP32_WIFI_STA_AUTO), + esp32_wifi_auto_mode_incompatible_with_project); + + return 0; +} + +int network_connect_async(void) +{ + wifi_config_t wifi_config = { 0 }; + + // use strncpy with the maximum length of sizeof(wifi_config.sta.{ssid|password}), + // because ESP32 Wi-Fi buffers don't have to be null-terminated + strncpy(wifi_config.sta.ssid, config_get_wifi_ssid(), sizeof(wifi_config.sta.ssid)); + strncpy(wifi_config.sta.password, config_get_wifi_password(), + sizeof(wifi_config.sta.password)); + + if (esp_wifi_set_mode(ESP32_WIFI_MODE_STA) || + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) || esp_wifi_connect()) { + LOG_ERR("connection failed"); + return -1; + } + return 0; +} + +void network_disconnect(void) +{ + esp_wifi_disconnect(); + esp_wifi_set_mode(ESP32_WIFI_MODE_NULL); +} diff --git a/demo/src/network/network_eswifi.c b/demo/src/network/network_eswifi.c new file mode 100644 index 0000000..6a6aa73 --- /dev/null +++ b/demo/src/network/network_eswifi.c @@ -0,0 +1,191 @@ +/* + * Copyright 2020-2022 AVSystem + * + * 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 + * + * http://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. + */ + +#include +#include +#include + +#include + +#include "network.h" +#include "network_internal.h" + +#include "../config.h" +#include "../utils.h" + +LOG_MODULE_REGISTER(network_wifi); + +static struct wifi_connect_req_params wifi_params; + +static void eswifi_reconnect_work_cb(struct k_work *work); +static void eswifi_keepalive_work_cb(struct k_work *work); + +static K_WORK_DELAYABLE_DEFINE(eswifi_reconnect_work, eswifi_reconnect_work_cb); +static K_WORK_DELAYABLE_DEFINE(eswifi_keepalive_work, eswifi_keepalive_work_cb); + +static void eswifi_reconnect_work_cb(struct k_work *work) +{ + net_mgmt(NET_REQUEST_WIFI_CONNECT, eswifi_by_iface_idx(0)->iface, &wifi_params, + sizeof(struct wifi_connect_req_params)); +} + +static K_SEM_DEFINE(eswifi_disconnect_sync_result_cb_sem, 0, 1); + +static void eswifi_disconnect_sync_result_cb(struct net_mgmt_event_callback *cb, + uint32_t mgmt_event, struct net_if *iface) +{ + k_sem_give(&eswifi_disconnect_sync_result_cb_sem); +} + +static void eswifi_disconnect_sync(struct net_if *iface) +{ + struct net_mgmt_event_callback cb = { 0 }; + + net_mgmt_init_event_callback(&cb, eswifi_disconnect_sync_result_cb, + NET_EVENT_WIFI_DISCONNECT_RESULT); + net_mgmt_add_event_callback(&cb); + + int ret = net_mgmt(NET_REQUEST_WIFI_DISCONNECT, iface, NULL, 0); + + if (ret >= 0 || ret == -EINPROGRESS) { + k_sem_take(&eswifi_disconnect_sync_result_cb_sem, Z_FOREVER); + } + + net_mgmt_del_event_callback(&cb); +} + +static void eswifi_keepalive_work_cb(struct k_work *work) +{ + char *result = NULL; + struct eswifi_dev *eswifi = eswifi_by_iface_idx(0); + + eswifi_lock(eswifi); + + bool connected = + (eswifi_at_cmd_rsp(eswifi, "CS\r", &result) >= 0 && result && result[0] == '1'); + + eswifi_unlock(eswifi); + + if (!connected) { + // Lost connection, let's try reconnecting + eswifi_disconnect_sync(eswifi->iface); + + // Issuing NET_REQUEST_WIFI_CONNECT locks the eswifi mutex for 30 seconds. + // This blocks e.g. poll() in the event loop - so let's call it via k_work_delayable + // to give the event loop a bit of time to perform all the close actions. + k_work_schedule(&eswifi_reconnect_work, K_SECONDS(5)); + } else { + k_work_schedule(&eswifi_keepalive_work, + K_SECONDS(CONFIG_ANJAY_CLIENT_NETWORK_KEEPALIVE_RATE)); + } +} + +static void eswifi_disconnect_work_cb(struct k_work *work) +{ + struct eswifi_dev *eswifi = eswifi_by_iface_idx(0); + + while (true) { + struct in_addr *addr = + net_if_ipv4_get_global_addr(eswifi->iface, NET_ADDR_ANY_STATE); + + if (!addr) { + break; + } + net_if_ipv4_addr_rm(eswifi->iface, addr); + } + + while (true) { + struct in_addr *addr = net_if_ipv4_get_ll(eswifi->iface, NET_ADDR_ANY_STATE); + + if (!addr) { + break; + } + net_if_ipv4_addr_rm(eswifi->iface, addr); + } +} + +static K_WORK_DEFINE(eswifi_disconnect_work, eswifi_disconnect_work_cb); + +static struct net_mgmt_event_callback eswifi_mgmt_cb_obj; + +static void eswifi_mgmt_cb(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, + struct net_if *iface) +{ + if (mgmt_event == NET_EVENT_WIFI_CONNECT_RESULT) { + struct k_work_delayable *dwork = &eswifi_keepalive_work; + const struct wifi_status *status = (const struct wifi_status *)cb->info; + + if (status->status < 0) { + // Connect error, retry + LOG_WRN("Could not connect to WiFi, retrying..."); + dwork = &eswifi_reconnect_work; + } + + k_work_schedule(dwork, K_SECONDS(CONFIG_ANJAY_CLIENT_NETWORK_KEEPALIVE_RATE)); + } else if (mgmt_event == NET_EVENT_WIFI_DISCONNECT_RESULT) { + // eswifi driver doesn't clear IP addrs on disconnect, let's remove them manually + // Nested network event handling is explicitly disabled, so let's do that via k_work + k_work_submit(&eswifi_disconnect_work); + } +} + +int network_internal_platform_initialize(void) +{ + struct eswifi_dev *eswifi = eswifi_by_iface_idx(0); + + assert(eswifi); + eswifi_lock(eswifi); + // Set regulatory domain to "World Wide (passive Ch12-14)"; eS-WiFi defaults + // to "US" which prevents connecting to networks that use channels 12-14. + if (eswifi_at_cmd(eswifi, "CN=XV\r") < 0) { + LOG_WRN("Failed to set Wi-Fi regulatory domain"); + } + + net_mgmt_init_event_callback(&eswifi_mgmt_cb_obj, eswifi_mgmt_cb, + NET_EVENT_WIFI_CONNECT_RESULT | + NET_EVENT_WIFI_DISCONNECT_RESULT); + net_mgmt_add_event_callback(&eswifi_mgmt_cb_obj); + + eswifi_unlock(eswifi); + + return 0; +} + +int network_connect_async(void) +{ + wifi_params = config_get_wifi_params(); + + int ret = net_mgmt(NET_REQUEST_WIFI_CONNECT, eswifi_by_iface_idx(0)->iface, &wifi_params, + sizeof(struct wifi_connect_req_params)); + + if (ret > 0 || ret == -EALREADY || ret == -EINPROGRESS) { + ret = 0; + } + if (ret) { + LOG_ERR("Failed to configure Wi-Fi"); + } + return ret; +} + +void network_disconnect(void) +{ + struct k_work_sync sync; + + k_work_cancel_delayable_sync(&eswifi_reconnect_work, &sync); + k_work_cancel_delayable_sync(&eswifi_keepalive_work, &sync); + + net_mgmt(NET_REQUEST_WIFI_DISCONNECT, eswifi_by_iface_idx(0)->iface, NULL, 0); +} diff --git a/demo/src/network/network_internal.h b/demo/src/network/network_internal.h new file mode 100644 index 0000000..4401110 --- /dev/null +++ b/demo/src/network/network_internal.h @@ -0,0 +1,31 @@ +/* + * Copyright 2020-2022 AVSystem + * + * 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 + * + * http://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. + */ + +#pragma once + +#include +#include + +extern struct k_mutex network_internal_connect_mutex; +extern struct k_condvar network_internal_connect_condvar; + +int network_internal_platform_initialize(void); + +#if defined(CONFIG_NET_NATIVE_IPV4) || defined(CONFIG_NET_NATIVE_IPV6) +bool network_internal_has_ip_address(struct net_if *iface); +#endif // defined(CONFIG_NET_NATIVE_IPV4) || defined(CONFIG_NET_NATIVE_IPV6) + +void network_internal_connection_state_changed(void); diff --git a/demo/src/network/network_nrf91.c b/demo/src/network/network_nrf91.c new file mode 100644 index 0000000..a416f3a --- /dev/null +++ b/demo/src/network/network_nrf91.c @@ -0,0 +1,104 @@ +/* + * Copyright 2020-2022 AVSystem + * + * 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 + * + * http://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. + */ + +#include + +#include + +#include "network.h" +#include "network_internal.h" + +#include "../common.h" +#include "../config.h" +#include "../gps.h" +#include "../utils.h" + +LOG_MODULE_REGISTER(network_nrf91); + +static volatile atomic_int lte_nw_reg_status; // enum lte_lc_nw_reg_status +static volatile atomic_int lte_mode; // enum lte_lc_lte_mode + +static void lte_evt_handler(const struct lte_lc_evt *const evt) +{ + if (evt) { + if (evt->type == LTE_LC_EVT_NW_REG_STATUS) { + atomic_store(<e_nw_reg_status, (int)evt->nw_reg_status); + } else if (evt->type == LTE_LC_EVT_LTE_MODE_UPDATE) { + atomic_store(<e_mode, (int)evt->lte_mode); + } + } + network_internal_connection_state_changed(); +} + +int network_internal_platform_initialize(void) +{ + int ret = lte_lc_init(); + + if (!ret) { + lte_lc_register_handler(lte_evt_handler); + } + + return ret; +} + +int network_connect_async(void) +{ + int ret = 0; + + // Note: this is supposed to be handled by lte_lc_connect_async(), + // but there is a nasty bug in in_progress flag handling + if (!network_is_connected()) { + ret = lte_lc_connect_async(lte_evt_handler); + } + + if (ret > 0 || ret == -EALREADY || ret == -EINPROGRESS) { + ret = 0; + } + if (ret) { + LOG_ERR("LTE link could not be established."); + } + return ret; +} + +enum network_bearer_t network_current_bearer(void) +{ +#ifdef CONFIG_ANJAY_CLIENT_GPS_NRF + if (atomic_load(&gps_prio_mode)) { + return NETWORK_BEARER_LIMIT; + } +#endif // CONFIG_ANJAY_CLIENT_GPS_NRF + + if (atomic_load(<e_mode) == LTE_LC_LTE_MODE_NONE) { + return NETWORK_BEARER_LIMIT; + } + + int status = atomic_load(<e_nw_reg_status); + + if (status == LTE_LC_NW_REG_REGISTERED_HOME || status == LTE_LC_NW_REG_REGISTERED_ROAMING) { + return NETWORK_BEARER_CELLULAR; + } else { + return NETWORK_BEARER_LIMIT; + } +} + +void network_disconnect(void) +{ + int ret = lte_lc_offline(); + + if (ret) { + LOG_WRN("LTE link could not be disconnected: %d", ret); + } +} diff --git a/demo/src/network/network_openthread.c b/demo/src/network/network_openthread.c new file mode 100644 index 0000000..47dd2c2 --- /dev/null +++ b/demo/src/network/network_openthread.c @@ -0,0 +1,74 @@ +/* + * Copyright 2020-2022 AVSystem + * + * 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 + * + * http://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. + */ + +#include + +#include +#include + +#include + +#include "network.h" +#include "network_internal.h" + +#include "../utils.h" +#include "../default_config.h" + +/* + * Due to the lack of a function declaration in the openthread.h + * file, it has been added here. + */ +int openthread_stop(struct openthread_context *ot_context); + +LOG_MODULE_REGISTER(network_openthread); + +static void ot_state_changed(uint32_t flags, void *context) +{ + network_internal_connection_state_changed(); +} + +int network_internal_platform_initialize(void) +{ + openthread_set_state_changed_cb(ot_state_changed); + return 0; +} + +int network_connect_async(void) +{ + int ret = openthread_start(openthread_get_default_context()); + + if (ret) { + LOG_WRN("Failed to start Openthread."); + } + + return ret; +} + +enum network_bearer_t network_current_bearer(void) +{ + struct openthread_context *ctx = openthread_get_default_context(); + + bool connected = + otThreadGetDeviceRole(ctx->instance) >= OT_DEVICE_ROLE_CHILD && + net_if_ipv6_get_global_addr(NET_ADDR_PREFERRED, &(struct net_if *){ ctx->iface }); + + return connected ? NETWORK_BEARER_OPENTHREAD : NETWORK_BEARER_LIMIT; +} + +void network_disconnect(void) +{ + openthread_stop(openthread_get_default_context()); +} diff --git a/demo/src/network/network_wifi.c b/demo/src/network/network_wifi.c new file mode 100644 index 0000000..3126914 --- /dev/null +++ b/demo/src/network/network_wifi.c @@ -0,0 +1,54 @@ +/* + * Copyright 2020-2022 AVSystem + * + * 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 + * + * http://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. + */ + +#include + +#include +#include + +#include "network.h" +#include "network_internal.h" + +#include "../config.h" +#include "../utils.h" + +LOG_MODULE_REGISTER(network_wifi); + +int network_internal_platform_initialize(void) +{ + return 0; +} + +int network_connect_async(void) +{ + struct wifi_connect_req_params wifi_params = config_get_wifi_params(); + + int ret = net_mgmt(NET_REQUEST_WIFI_CONNECT, net_if_get_default(), &wifi_params, + sizeof(struct wifi_connect_req_params)); + + if (ret > 0 || ret == -EALREADY || ret == -EINPROGRESS) { + ret = 0; + } + if (ret) { + LOG_ERR("Failed to configure Wi-Fi"); + } + return ret; +} + +void network_disconnect(void) +{ + net_mgmt(NET_REQUEST_WIFI_DISCONNECT, net_if_get_default(), NULL, 0); +} diff --git a/demo/src/nrf_lc_info.c b/demo/src/nrf_lc_info.c index 86f7174..8c847b5 100644 --- a/demo/src/nrf_lc_info.c +++ b/demo/src/nrf_lc_info.c @@ -17,10 +17,11 @@ #include #include -#include +#include +#include + #include #include -#include #include "common.h" #include "nrf_lc_info.h" @@ -98,7 +99,8 @@ static void lte_lc_evt_handler(const struct lte_lc_evt *const evt) static void periodic_search_work_handler(struct k_work *work) { // TODO: consider using other search types, which work on nRF9160's with FW 1.3.1 and up - int err = lte_lc_neighbor_cell_measurement(LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT); + int err = lte_lc_neighbor_cell_measurement(&(struct lte_lc_ncellmeas_params){ + .search_type = LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT }); if (err) { LOG_ERR("Can't search for neighbor cells, error: %d", err); diff --git a/demo/src/nrf_lc_info.h b/demo/src/nrf_lc_info.h index 627bb8e..92f2e75 100644 --- a/demo/src/nrf_lc_info.h +++ b/demo/src/nrf_lc_info.h @@ -19,7 +19,8 @@ #include #include -#include +#include + #include #include diff --git a/demo/src/objects/basic_sensors.c b/demo/src/objects/basic_sensors.c index 4c20d8e..b015ba7 100644 --- a/demo/src/objects/basic_sensors.c +++ b/demo/src/objects/basic_sensors.c @@ -17,9 +17,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "objects.h" diff --git a/demo/src/objects/buzzer.c b/demo/src/objects/buzzer.c index a823880..b211cb7 100644 --- a/demo/src/objects/buzzer.c +++ b/demo/src/objects/buzzer.c @@ -22,9 +22,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "../utils.h" #include "objects.h" diff --git a/demo/src/objects/conn_mon.c b/demo/src/objects/conn_mon.c index 0bd834b..e13dfce 100644 --- a/demo/src/objects/conn_mon.c +++ b/demo/src/objects/conn_mon.c @@ -23,9 +23,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "objects.h" #include "../nrf_lc_info.h" diff --git a/demo/src/objects/device.c b/demo/src/objects/device.c index b46d33f..f43f602 100644 --- a/demo/src/objects/device.c +++ b/demo/src/objects/device.c @@ -21,8 +21,8 @@ #include #include -#include -#include +#include +#include #include "../default_config.h" #include "../utils.h" diff --git a/demo/src/objects/ecid.c b/demo/src/objects/ecid.c index ba0a312..be6718b 100644 --- a/demo/src/objects/ecid.c +++ b/demo/src/objects/ecid.c @@ -21,7 +21,8 @@ #include #include -#include +#include + #include #include "objects.h" diff --git a/demo/src/objects/led_color_light.c b/demo/src/objects/led_color_light.c index 0978244..4fcde8f 100644 --- a/demo/src/objects/led_color_light.c +++ b/demo/src/objects/led_color_light.c @@ -23,9 +23,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "objects.h" diff --git a/demo/src/objects/loc_assist.c b/demo/src/objects/loc_assist.c index 2eabc79..b4b589b 100644 --- a/demo/src/objects/loc_assist.c +++ b/demo/src/objects/loc_assist.c @@ -15,7 +15,8 @@ */ #include #include -#include + +#include #include #include diff --git a/demo/src/objects/objects.h b/demo/src/objects/objects.h index 1b1f431..3e85f64 100644 --- a/demo/src/objects/objects.h +++ b/demo/src/objects/objects.h @@ -17,9 +17,10 @@ #pragma once #include -#include -#include -#include + +#include +#include +#include #ifdef CONFIG_ANJAY_CLIENT_NRF_LC_INFO #include "../nrf_lc_info.h" @@ -58,7 +59,8 @@ void three_axis_sensors_update(anjay_t *anjay); #define PUSH_BUTTON_NODE(idx) DT_ALIAS(push_button_##idx) #define PUSH_BUTTON_AVAILABLE(idx) DT_NODE_HAS_STATUS(PUSH_BUTTON_NODE(idx), okay) #define PUSH_BUTTON_AVAILABLE_ANY \ - (PUSH_BUTTON_AVAILABLE(0) || PUSH_BUTTON_AVAILABLE(1) || PUSH_BUTTON_AVAILABLE(2)) + (PUSH_BUTTON_AVAILABLE(0) || PUSH_BUTTON_AVAILABLE(1) || PUSH_BUTTON_AVAILABLE(2) || \ + PUSH_BUTTON_AVAILABLE(3)) int push_button_object_install(anjay_t *anjay); #define SWITCH_NODE(idx) DT_ALIAS(switch_##idx) diff --git a/demo/src/objects/push_button.c b/demo/src/objects/push_button.c index ff27494..20ff796 100644 --- a/demo/src/objects/push_button.c +++ b/demo/src/objects/push_button.c @@ -23,9 +23,10 @@ #include #include -#include -#include -#include +#include +#include +#include +#include #include "objects.h" @@ -57,6 +58,9 @@ static struct push_button_instance_glue button_glue[] = { #if PUSH_BUTTON_AVAILABLE(2) PUSH_BUTTON_GLUE_ITEM(2), #endif // PUSH_BUTTON_AVAILABLE(2) +#if PUSH_BUTTON_AVAILABLE(3) + PUSH_BUTTON_GLUE_ITEM(3), +#endif // PUSH_BUTTON_AVAILABLE(3) }; #define BUTTON_CHANGE_WORKS_NUM 256 @@ -97,8 +101,7 @@ static void button_state_changed(const struct device *dev, struct gpio_callback work->reserved = true; work->anjay = glue->anjay; work->state = (bool)gpio_pin_get(dev, glue->gpio_pin); - work->iid = (anjay_iid_t)(((size_t)(glue - button_glue)) / - sizeof(struct push_button_instance_glue)); + work->iid = (anjay_iid_t)((size_t)(glue - button_glue)); k_work_init(&work->work, button_change_state_handler); diff --git a/demo/src/objects/switch.c b/demo/src/objects/switch.c index 632a023..c3316c9 100644 --- a/demo/src/objects/switch.c +++ b/demo/src/objects/switch.c @@ -22,8 +22,8 @@ #include #include -#include -#include +#include +#include #include "objects.h" diff --git a/demo/src/objects/three_axis_sensors.c b/demo/src/objects/three_axis_sensors.c index 2e16404..49db2e1 100644 --- a/demo/src/objects/three_axis_sensors.c +++ b/demo/src/objects/three_axis_sensors.c @@ -17,9 +17,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "objects.h" diff --git a/demo/src/persistence.c b/demo/src/persistence.c index 31dbb50..e6c4c06 100644 --- a/demo/src/persistence.c +++ b/demo/src/persistence.c @@ -14,9 +14,9 @@ * limitations under the License. */ -#include -#include -#include +#include +#include +#include #include #include diff --git a/demo/src/status_led.c b/demo/src/status_led.c index 43e39b8..13bc588 100644 --- a/demo/src/status_led.c +++ b/demo/src/status_led.c @@ -16,9 +16,9 @@ #include "status_led.h" -#include -#include -#include +#include +#include +#include LOG_MODULE_REGISTER(status_led); diff --git a/demo/src/status_led.h b/demo/src/status_led.h index b73f2c8..07cf99c 100644 --- a/demo/src/status_led.h +++ b/demo/src/status_led.h @@ -16,7 +16,7 @@ #pragma once -#include +#include #define STATUS_LED_NODE DT_ALIAS(status_led) #define STATUS_LED_AVAILABLE DT_NODE_HAS_STATUS(STATUS_LED_NODE, okay) diff --git a/demo/src/utils.c b/demo/src/utils.c index 566ecb3..ad36d54 100644 --- a/demo/src/utils.c +++ b/demo/src/utils.c @@ -16,20 +16,28 @@ #include "utils.h" #include -#include +#include + #include #include #ifdef CONFIG_ANJAY_CLIENT_FOTA -#include +#include +#include + #include -#include #endif // CONFIG_ANJAY_CLIENT_FOTA -#if defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#ifdef CONFIG_NRF_MODEM_LIB #include +#ifdef CONFIG_MODEM_KEY_MGMT #include -#endif // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) +#endif // CONFIG_MODEM_KEY_MGMT +#endif // CONFIG_NRF_MODEM_LIB + +#ifdef CONFIG_NET_IPV6 +#include +#endif // CONFIG_NET_IPV6 int get_device_id(struct device_id *out_id) { @@ -86,12 +94,22 @@ static int get_fw_version(char *out_buf, size_t buf_size, uint8_t area_id) int get_fw_version_image_0(char *out_buf, size_t buf_size) { - return get_fw_version(out_buf, buf_size, FLASH_AREA_ID(image_0)); +#ifdef FIXED_PARTITION_ID + uint8_t area_id = FIXED_PARTITION_ID(slot0_partition); +#else + uint8_t area_id = FLASH_AREA_ID(image_0); +#endif + return get_fw_version(out_buf, buf_size, area_id); } int get_fw_version_image_1(char *out_buf, size_t buf_size) { - return get_fw_version(out_buf, buf_size, FLASH_AREA_ID(image_1)); +#ifdef FIXED_PARTITION_ID + uint8_t area_id = FIXED_PARTITION_ID(slot1_partition); +#else + uint8_t area_id = FLASH_AREA_ID(image_1); +#endif + return get_fw_version(out_buf, buf_size, area_id); } #endif // CONFIG_ANJAY_CLIENT_FOTA @@ -116,3 +134,73 @@ int tls_session_cache_purge(void) } } #endif // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) + +/* + * Copyright (c) 2019 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + * + * This sntp_simple_ipv6 function is a copy of sntp_simple function from Zephyr + * (https://github.com/zephyrproject-rtos/zephyr/blob/zephyr-v3.0.0/subsys/net/lib/sntp/sntp_simple.c) + * repository. The only change is the desired address family. + */ + +#ifdef CONFIG_NET_IPV6 +int sntp_simple_ipv6(const char *server, uint32_t timeout, struct sntp_time *time) +{ + int res; + static struct addrinfo hints; + struct addrinfo *addr; + struct sntp_ctx sntp_ctx; + uint64_t deadline; + uint32_t iter_timeout; + + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + /* 123 is the standard SNTP port per RFC4330 */ + res = net_getaddrinfo_addr_str(server, "123", &hints, &addr); + + if (res < 0) { + /* Just in case, as namespace for getaddrinfo errors is + * different from errno errors. + */ + errno = EDOM; + return res; + } + + res = sntp_init(&sntp_ctx, addr->ai_addr, addr->ai_addrlen); + if (res < 0) { + goto freeaddr; + } + + if (timeout == SYS_FOREVER_MS) { + deadline = (uint64_t)timeout; + } else { + deadline = k_uptime_get() + (uint64_t)timeout; + } + + /* Timeout for current iteration */ + iter_timeout = 100; + + while (k_uptime_get() < deadline) { + res = sntp_query(&sntp_ctx, iter_timeout, time); + + if (res != -ETIMEDOUT) { + break; + } + + /* Exponential backoff with limit */ + if (iter_timeout < 1000) { + iter_timeout *= 2; + } + } + + sntp_close(&sntp_ctx); + +freeaddr: + freeaddrinfo(addr); + + return res; +} +#endif // CONFIG_NET_IPV6 diff --git a/demo/src/utils.h b/demo/src/utils.h index 95908c3..1ae74d5 100644 --- a/demo/src/utils.h +++ b/demo/src/utils.h @@ -18,8 +18,10 @@ #include +#include + #ifdef CONFIG_ANJAY_CLIENT_FOTA -#include +#include #endif // CONFIG_ANJAY_CLIENT_FOTA struct device_id { @@ -41,3 +43,7 @@ int get_fw_version_image_1(char *out_buf, size_t buf_size); #if defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) int tls_session_cache_purge(void); #endif // defined(CONFIG_NRF_MODEM_LIB) && defined(CONFIG_MODEM_KEY_MGMT) + +#ifdef CONFIG_NET_IPV6 +int sntp_simple_ipv6(const char *server, uint32_t timeout, struct sntp_time *time); +#endif // CONFIG_NET_IPV6 diff --git a/demo/west-nrf.yml b/demo/west-nrf.yml index d8dfc64..6419746 100644 --- a/demo/west-nrf.yml +++ b/demo/west-nrf.yml @@ -23,10 +23,10 @@ manifest: - name: sdk-nrf path: nrf remote: ncs - revision: v2.0.0 + revision: v2.2.0-rc1 import: true - name: Anjay-zephyr submodules: true remote: anjay - revision: 3.1.2 + revision: 3.2.1 path: modules/lib/anjay diff --git a/demo/west.yml b/demo/west.yml index 1805ca6..671ae56 100644 --- a/demo/west.yml +++ b/demo/west.yml @@ -23,10 +23,10 @@ manifest: - name: zephyr path: zephyr remote: zephyrproject-rtos - revision: v3.1.0 + revision: v3.2.0 import: true - name: Anjay-zephyr submodules: true remote: anjay - revision: 3.1.2 + revision: 3.2.1 path: modules/lib/anjay diff --git a/ei_demo/Kconfig b/ei_demo/Kconfig index e8a511d..60d6332 100644 --- a/ei_demo/Kconfig +++ b/ei_demo/Kconfig @@ -14,7 +14,7 @@ config ANJAY_CLIENT_ENDPOINT_NAME config ANJAY_CLIENT_VERSION string "Client Version" - default "22.08-75-gd102363" + default "22.12" config ANJAY_CLIENT_SERVER_URI string "Server URI" diff --git a/ei_demo/README.md b/ei_demo/README.md index 9141670..ef54ec2 100644 --- a/ei_demo/README.md +++ b/ei_demo/README.md @@ -35,7 +35,7 @@ LwM2M Server, please register at https://eu.iot.avsystem.cloud/. Then have a look at the Configuration menu using `west build -t guiconfig` or `west build -t menuconfig` to configure security credentials and other necessary settings (like Wi-Fi SSID etc.). -[Guide showing basic usage of Coiote DM](https://iotdevzone.avsystem.com/docs/Coiote_DM_Device_Onboarding/Quick_start/) +[Guide showing basic usage of Coiote DM](https://iotdevzone.avsystem.com/docs/IoT_quick_start/Device_onboarding/) is available on IoT Developer Zone. NOTE: You may use any LwM2M Server compliant with LwM2M 1.0 TS. The server URI diff --git a/ei_demo/src/led.c b/ei_demo/src/led.c index 817923b..ff4169c 100644 --- a/ei_demo/src/led.c +++ b/ei_demo/src/led.c @@ -16,7 +16,7 @@ #include -#include +#include #include "led.h" diff --git a/ei_demo/src/led.h b/ei_demo/src/led.h index f47a692..6fe1164 100644 --- a/ei_demo/src/led.h +++ b/ei_demo/src/led.h @@ -18,8 +18,8 @@ #include -#include -#include +#include +#include #define LED_NODE(n) DT_ALIAS(led##n) #define LED_AVAILABLE(n) DT_NODE_HAS_STATUS(LED_NODE(n), okay) diff --git a/ei_demo/src/main.c b/ei_demo/src/main.c index 963ea4c..d5a7043 100644 --- a/ei_demo/src/main.c +++ b/ei_demo/src/main.c @@ -14,16 +14,19 @@ * limitations under the License. */ -#include #include -#include -#include + #include -#include -#include +#include +#include +#include + +#include +#include + #ifdef CONFIG_POSIX_API -#include +#include #else // CONFIG_POSIX_API #include #endif // CONFIG_POSIX_API @@ -36,10 +39,11 @@ #include #include #include -#include -#include -#include -#include + +#include +#include +#include +#include #include diff --git a/ei_demo/src/objects/device.c b/ei_demo/src/objects/device.c index 611a5b1..24a8b0e 100644 --- a/ei_demo/src/objects/device.c +++ b/ei_demo/src/objects/device.c @@ -21,8 +21,8 @@ #include #include -#include -#include +#include +#include #include "../utils.h" diff --git a/ei_demo/src/objects/pattern_detector.c b/ei_demo/src/objects/pattern_detector.c index 29e2d73..4649a3d 100644 --- a/ei_demo/src/objects/pattern_detector.c +++ b/ei_demo/src/objects/pattern_detector.c @@ -32,9 +32,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "../led.h" #include "objects.h" diff --git a/ei_demo/src/utils.c b/ei_demo/src/utils.c index f092da1..f938944 100644 --- a/ei_demo/src/utils.c +++ b/ei_demo/src/utils.c @@ -18,7 +18,7 @@ #include -#include +#include #include diff --git a/ei_demo/west-nrf.yml b/ei_demo/west-nrf.yml index d8dfc64..6419746 100644 --- a/ei_demo/west-nrf.yml +++ b/ei_demo/west-nrf.yml @@ -23,10 +23,10 @@ manifest: - name: sdk-nrf path: nrf remote: ncs - revision: v2.0.0 + revision: v2.2.0-rc1 import: true - name: Anjay-zephyr submodules: true remote: anjay - revision: 3.1.2 + revision: 3.2.1 path: modules/lib/anjay diff --git a/minimal/Kconfig b/minimal/Kconfig index 54218e1..df55453 100644 --- a/minimal/Kconfig +++ b/minimal/Kconfig @@ -14,7 +14,7 @@ config ANJAY_CLIENT_ENDPOINT_NAME config ANJAY_CLIENT_VERSION string "Client Version" - default "22.08-75-gd102363" + default "22.12" config ANJAY_CLIENT_SERVER_URI string "Server URI" diff --git a/minimal/README.md b/minimal/README.md index fcabd1d..659e962 100644 --- a/minimal/README.md +++ b/minimal/README.md @@ -10,6 +10,7 @@ This folder contains LwM2M Client minimal application example for following targ - [nrf9160dk_nrf9160_ns](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_nrf9160.html) - [thingy91_nrf9160_ns](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_thingy91.html) - [ESP32-DevKitC](https://www.espressif.com/en/products/devkits/esp32-devkitc) + - [nrf52840dk_nrf52840](https://docs.zephyrproject.org/latest/boards/arm/nrf52840dk_nrf52840/doc/index.html) The following LwM2M Objects are supported: - Security (/0) @@ -27,7 +28,7 @@ west update You can now compile the project using `west build -b ` in `minimal` directory. -### Compilation guide for nRF9160DK and Thingy:91 +### Compilation guide for nRF9160DK, Thingy:91 and nRF52840DK Because NCS uses different Zephyr version, it is necessary to change our Zephyr workspace, it is handled by using different manifest file. Set West manifest path to `Anjay-zephyr-client/minimal`, and manifest file to `west-nrf.yml` and do `west update`. @@ -36,7 +37,7 @@ west config manifest.path Anjay-zephyr-client/minimal west config manifest.file west-nrf.yml west update ``` -Now you can compile the project using `west build -b nrf9160dk_nrf9160_ns` or `west build -b thingy91_nrf9160_ns` in `minimal` directory, respectively. +Now you can compile the project using `west build -b nrf9160dk_nrf9160_ns`, `west build -b thingy91_nrf9160_ns` or `west build -b nrf52840dk_nrf52840` in `minimal` directory, respectively. The last command compiles project for use with the OpenThread network, more about this can be found in the section `Connecting to the LwM2M Server with OpenThread`. > **__NOTE:__** @@ -99,8 +100,18 @@ LwM2M Server, please register at https://eu.iot.avsystem.cloud/. Then have a look at the Configuration menu using `west build -t guiconfig` or `west build -t menuconfig` to configure security credentials and other necessary settings (like Wi-Fi SSID etc.). -[Guide showing basic usage of Coiote DM](https://iotdevzone.avsystem.com/docs/Coiote_DM_Device_Onboarding/Quick_start/) +[Guide showing basic usage of Coiote DM](https://iotdevzone.avsystem.com/docs/IoT_quick_start/Device_onboarding/) is available on IoT Developer Zone. NOTE: You may use any LwM2M Server compliant with LwM2M 1.0 TS. The server URI can be changed in the Configuration menu. + +## Connecting to the LwM2M Server with OpenThread + +To use this project on the nRF52840dk board, in addition to the configuration shown in the previous paragraph, you will need to configure the OpenThread Border Router and Commissioner as described in the guides from the links below. +You can change default `CONFIG_OPENTHREAD_JOINER_PSKD` value in the `boards/nrf52840dk_nrf52840.conf`. In same file, replace `CONFIG_OPENTHREAD_MTD=y` with `CONFIG_OPENTHREAD_FTD=y` if you want your device to run as an FTD. + +Resources: +- [Introduction to OpenThread](https://openthread.io/guides) +- [Border Router guide](https://openthread.io/guides/border-router) +- [Commissioner guide](https://openthread.io/guides/commissioner) diff --git a/minimal/boards/esp32.conf b/minimal/boards/esp32.conf index 0744d82..8a24f13 100644 --- a/minimal/boards/esp32.conf +++ b/minimal/boards/esp32.conf @@ -3,7 +3,7 @@ CONFIG_ANJAY_CLIENT_DEVICE_MANUFACTURER="Espressif" CONFIG_ANJAY_CLIENT_MODEL_NUMBER="ESP32-DevKitC" # General settings -CONFIG_MAIN_STACK_SIZE=32768 +CONFIG_MAIN_STACK_SIZE=15360 CONFIG_POSIX_API=y # Networking diff --git a/minimal/boards/esp32.overlay b/minimal/boards/esp32.overlay new file mode 100644 index 0000000..c4fa06f --- /dev/null +++ b/minimal/boards/esp32.overlay @@ -0,0 +1,3 @@ +&wifi { + status = "okay"; +}; diff --git a/minimal/boards/nrf52840dk_nrf52840.conf b/minimal/boards/nrf52840dk_nrf52840.conf new file mode 100644 index 0000000..b6ef9e2 --- /dev/null +++ b/minimal/boards/nrf52840dk_nrf52840.conf @@ -0,0 +1,44 @@ +# Anjay Settings +CONFIG_ANJAY_COMPAT_TIME=y +CONFIG_ANJAY_COMPAT_MBEDTLS=y + +# General Settings +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 + +# Logging +CONFIG_LOG_BLOCK_IN_THREAD=y +CONFIG_LOG_MODE_DEFERRED=y + +# Clock synchronization +CONFIG_DATE_TIME=y +CONFIG_DATE_TIME_AUTO_UPDATE=n + +# Networking +CONFIG_NET_IPV4=n +CONFIG_NET_TCP=n +CONFIG_NET_IPV6=y +CONFIG_NET_IPV6_NBR_CACHE=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_CONFIG_NEED_IPV4=n +CONFIG_NET_L2_OPENTHREAD=y + +# DNS +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_SERVER_IP_ADDRESSES=y +CONFIG_DNS_SERVER1="fdaa:bb:1::2" + +# OpenThread +CONFIG_OPENTHREAD_JOINER=y +CONFIG_OPENTHREAD_JOINER_AUTOSTART=y +CONFIG_OPENTHREAD_MANUAL_START=n +CONFIG_OPENTHREAD_SLAAC=y +CONFIG_OPENTHREAD_JOINER_PSKD="J01NME" +CONFIG_OPENTHREAD_MTD=y + +# MbedTLS and security +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED=y +CONFIG_MBEDTLS_DTLS=y +CONFIG_MBEDTLS_ENTROPY_ENABLED=y diff --git a/minimal/src/main.c b/minimal/src/main.c index 485d732..f44aa4a 100644 --- a/minimal/src/main.c +++ b/minimal/src/main.c @@ -14,28 +14,34 @@ * limitations under the License. */ -#include #include -#include -#include + #include -#include -#include +#include +#include +#include + +#include +#include #include #include #include #include -#include -#include -#include -#include + +#include +#include +#include +#include #include #include "objects/objects.h" -#include + +#include + +#include "utils.h" #ifdef CONFIG_WIFI #ifdef CONFIG_WIFI_ESWIFI @@ -46,8 +52,8 @@ #include #include #endif // CONFIG_WIFI_ESP32 -#include -#include +#include +#include #endif // CONFIG_WIFI #ifdef CONFIG_LTE_LINK_CONTROL @@ -55,13 +61,18 @@ #endif // CONFIG_LTE_LINK_CONTROL #ifdef CONFIG_POSIX_API -#include +#include #endif // CONFIG_POSIX_API #ifdef CONFIG_DATE_TIME #include #endif // CONFIG_DATE_TIME +#ifdef CONFIG_NET_L2_OPENTHREAD +#include +#include +#endif // CONFIG_NET_L2_OPENTHREAD + LOG_MODULE_REGISTER(zephyr_demo); @@ -75,6 +86,33 @@ static avs_sched_handle_t update_objects_handle; static struct k_thread anjay_thread; K_THREAD_STACK_DEFINE(anjay_stack, ANJAY_THREAD_STACK_SIZE); +#ifdef CONFIG_NET_L2_OPENTHREAD +#define RETRY_SYNC_CLOCK_DELAY_TIME_S 1 + +K_SEM_DEFINE(ot_ready, 0, 1); +static struct k_work_delayable sync_clock_work; + +static void ot_state_changed(uint32_t flags, void *context) +{ + struct openthread_context *ot_context = context; + + if (flags & OT_CHANGED_THREAD_ROLE) { + switch (otThreadGetDeviceRole(ot_context->instance)) { + case OT_DEVICE_ROLE_CHILD: + case OT_DEVICE_ROLE_ROUTER: + case OT_DEVICE_ROLE_LEADER: + k_sem_give(&ot_ready); + break; + + case OT_DEVICE_ROLE_DISABLED: + case OT_DEVICE_ROLE_DETACHED: + default: + break; + } + } +} +#endif // CONFIG_NET_L2_OPENTHREAD + static void set_system_time(const struct sntp_time *time) { #ifdef CONFIG_POSIX_API @@ -98,13 +136,30 @@ void synchronize_clock(void) struct sntp_time time; const uint32_t timeout_ms = 5000; - if (sntp_simple(CONFIG_ANJAY_CLIENT_NTP_SERVER, timeout_ms, &time)) { - LOG_WRN("Failed to get current time"); - } else { + if (false +#if defined(CONFIG_NET_IPV6) + || !sntp_simple_ipv6(CONFIG_ANJAY_CLIENT_NTP_SERVER, timeout_ms, &time) +#endif +#if defined(CONFIG_NET_IPV4) + || !sntp_simple(CONFIG_ANJAY_CLIENT_NTP_SERVER, timeout_ms, &time) +#endif + ) { set_system_time(&time); + } else { + LOG_WRN("Failed to get current time"); +#ifdef CONFIG_NET_L2_OPENTHREAD + k_work_schedule(&sync_clock_work, K_SECONDS(RETRY_SYNC_CLOCK_DELAY_TIME_S)); +#endif // CONFIG_NET_L2_OPENTHREAD } } +#ifdef CONFIG_NET_L2_OPENTHREAD +static void retry_synchronize_clock_work_handler(struct k_work *work) +{ + synchronize_clock(); +} +#endif // CONFIG_NET_L2_OPENTHREAD + static int register_objects(anjay_t *anjay) { device_obj = device_object_create(); @@ -140,7 +195,7 @@ static void release_objects(void) */ } -void initialize_network(void) +void network_initialize(void) { LOG_INF("Initializing network connection..."); #ifdef CONFIG_WIFI @@ -173,7 +228,7 @@ void initialize_network(void) strncpy(wifi_config.sta.password, CONFIG_ANJAY_CLIENT_WIFI_PASSWORD, sizeof(wifi_config.sta.password)); - if (esp_wifi_set_mode(WIFI_MODE_STA) || + if (esp_wifi_set_mode(ESP32_WIFI_MODE_STA) || esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) || esp_wifi_connect()) { LOG_ERR("connection failed"); } @@ -204,6 +259,12 @@ void initialize_network(void) abort(); } #endif // CONFIG_LTE_LINK_CONTROL + +#ifdef CONFIG_NET_L2_OPENTHREAD + k_work_init_delayable(&sync_clock_work, retry_synchronize_clock_work_handler); + openthread_set_state_changed_cb(ot_state_changed); + k_sem_take(&ot_ready, K_FOREVER); +#endif // CONFIG_NET_L2_OPENTHREAD LOG_INF("Connected to network"); } @@ -282,7 +343,7 @@ void run_anjay(void *arg1, void *arg2, void *arg3) void main(void) { - initialize_network(); + network_initialize(); k_sleep(K_SECONDS(1)); synchronize_clock(); diff --git a/minimal/src/objects/device.c b/minimal/src/objects/device.c index bc18f01..24a8b0e 100644 --- a/minimal/src/objects/device.c +++ b/minimal/src/objects/device.c @@ -21,8 +21,8 @@ #include #include -#include -#include +#include +#include #include "../utils.h" diff --git a/minimal/src/utils.c b/minimal/src/utils.c index f092da1..5056934 100644 --- a/minimal/src/utils.c +++ b/minimal/src/utils.c @@ -18,10 +18,14 @@ #include -#include +#include #include +#ifdef CONFIG_NET_IPV6 +#include +#endif // CONFIG_NET_IPV6 + int get_device_id(struct device_id *out_id) { memset(out_id->value, 0, sizeof(out_id->value)); @@ -35,3 +39,73 @@ int get_device_id(struct device_id *out_id) return avs_hexlify(out_id->value, sizeof(out_id->value), NULL, id, (size_t)retval); } + +/* + * Copyright (c) 2019 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + * + * This sntp_simple_ipv6 function is a copy of sntp_simple function from Zephyr + * (https://github.com/zephyrproject-rtos/zephyr/blob/zephyr-v3.0.0/subsys/net/lib/sntp/sntp_simple.c) + * repository. The only change is the desired address family. + */ + +#ifdef CONFIG_NET_IPV6 +int sntp_simple_ipv6(const char *server, uint32_t timeout, struct sntp_time *time) +{ + int res; + static struct addrinfo hints; + struct addrinfo *addr; + struct sntp_ctx sntp_ctx; + uint64_t deadline; + uint32_t iter_timeout; + + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + /* 123 is the standard SNTP port per RFC4330 */ + res = net_getaddrinfo_addr_str(server, "123", &hints, &addr); + + if (res < 0) { + /* Just in case, as namespace for getaddrinfo errors is + * different from errno errors. + */ + errno = EDOM; + return res; + } + + res = sntp_init(&sntp_ctx, addr->ai_addr, addr->ai_addrlen); + if (res < 0) { + goto freeaddr; + } + + if (timeout == SYS_FOREVER_MS) { + deadline = (uint64_t)timeout; + } else { + deadline = k_uptime_get() + (uint64_t)timeout; + } + + /* Timeout for current iteration */ + iter_timeout = 100; + + while (k_uptime_get() < deadline) { + res = sntp_query(&sntp_ctx, iter_timeout, time); + + if (res != -ETIMEDOUT) { + break; + } + + /* Exponential backoff with limit */ + if (iter_timeout < 1000) { + iter_timeout *= 2; + } + } + + sntp_close(&sntp_ctx); + +freeaddr: + freeaddrinfo(addr); + + return res; +} +#endif // CONFIG_NET_IPV6 diff --git a/minimal/src/utils.h b/minimal/src/utils.h index 9c82fd2..09e4b0e 100644 --- a/minimal/src/utils.h +++ b/minimal/src/utils.h @@ -16,9 +16,18 @@ #pragma once +#include + +#ifdef CONFIG_NET_IPV6 +#include +#endif // CONFIG_NET_IPV6 + struct device_id { // 96 bits as hex + NULL-byte char value[25]; }; int get_device_id(struct device_id *out_id); +#ifdef CONFIG_NET_IPV6 +int sntp_simple_ipv6(const char *server, uint32_t timeout, struct sntp_time *time); +#endif // CONFIG_NET_IPV6 diff --git a/minimal/west-nrf.yml b/minimal/west-nrf.yml index d8dfc64..6419746 100644 --- a/minimal/west-nrf.yml +++ b/minimal/west-nrf.yml @@ -23,10 +23,10 @@ manifest: - name: sdk-nrf path: nrf remote: ncs - revision: v2.0.0 + revision: v2.2.0-rc1 import: true - name: Anjay-zephyr submodules: true remote: anjay - revision: 3.1.2 + revision: 3.2.1 path: modules/lib/anjay diff --git a/minimal/west.yml b/minimal/west.yml index 1805ca6..671ae56 100644 --- a/minimal/west.yml +++ b/minimal/west.yml @@ -23,10 +23,10 @@ manifest: - name: zephyr path: zephyr remote: zephyrproject-rtos - revision: v3.1.0 + revision: v3.2.0 import: true - name: Anjay-zephyr submodules: true remote: anjay - revision: 3.1.2 + revision: 3.2.1 path: modules/lib/anjay diff --git a/tools/board_adapters/esp_adapter.py b/tools/board_adapters/esp_adapter.py new file mode 100644 index 0000000..9d5b26a --- /dev/null +++ b/tools/board_adapters/esp_adapter.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2020-2022 AVSystem +# +# 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 +# +# http://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. + +import serial +import subprocess +import os +import shlex +import yaml + +import zephyr_adapter_common + +COLOR_OFF = "\033[0m" +COLOR_MAGENTA = "\033[35m" +COLOR_CYAN = "\033[36m" + +class TargetAdapter(zephyr_adapter_common.ZephyrAdapterCommon): + def acquire_device(self): + self.device = serial.Serial( + self.config["device"], + self.config["baudrate"], + timeout=self.config["timeout"] + ) + + def release_device(self): + if self.device is not None: + self.__run_with_idf([ + os.environ["ESPTOOL_PATH"], + "-p", self.config["device"], + "erase_flash" + ]) + self.device.close() + self.device = None + + def flash_device(self): + if self.__run_with_idf(self.__get_flasher_command()).returncode != 0: + raise Exception("Flash failed") + + def __get_flasher_command(self): + with open(self.config["yaml_path"], mode="r", encoding="utf-8") as flasher_args_file: + flasher_args = yaml.safe_load(flasher_args_file) + + res = [ + os.environ["ESPTOOL_PATH"], + "-p", self.config["device"], + "--before", "default_reset", + "--after", "hard_reset", + "--chip", flasher_args["flash-runner"], + "write_flash", + "--flash_mode", "dio", + "--flash_size", "detect", + "--flash_freq", "40m", + ] + + boot_addr = "" + part_table_addr = "" + app_addr = "" + for option in flasher_args["args"][flasher_args["flash-runner"]]: + if option.startswith("--esp-boot-address="): + boot_addr = option.split("--esp-boot-address=")[1] + elif option.startswith("--esp-partition-table-address="): + part_table_addr = option.split("--esp-partition-table-address=")[1] + elif option.startswith("--esp-app-address="): + app_addr = option.split("--esp-app-address=")[1] + + res.extend([boot_addr, self.config["binaries"]["bootloader"]]) + res.extend([part_table_addr, self.config["binaries"]["partitions"]]) + res.extend([app_addr, self.config["binaries"]["main_app"]]) + + return res + + def __run_in_shell(self, commands): + script = "\n".join([shlex.join(command) for command in commands]) + print(f"{COLOR_CYAN}Running script:\n{script}{COLOR_OFF}", flush=True) + return subprocess.run(["/bin/bash", "-e"], input=script.encode("utf-8")) + + def __run_with_idf(self, command): + source_idf = [".", f"{os.environ['IDF_PATH']}/export.sh"] + return self.__run_in_shell([source_idf, command]) diff --git a/tools/board_adapters/nrf_adapter.py b/tools/board_adapters/nrf_adapter.py new file mode 100644 index 0000000..c94959d --- /dev/null +++ b/tools/board_adapters/nrf_adapter.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2020-2022 AVSystem +# +# 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 +# +# http://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. + +import subprocess + +import serial + +import zephyr_adapter_common + +class TargetAdapter(zephyr_adapter_common.ZephyrAdapterCommon): + @property + def nrfjprog_serial(self): + # As empirically checked (using PRoot and a faked contents of /sys/devices/.../serial), + # nrfjprog always strips all leading zeros from the serial number + return self.config['nrfjprog-serial'].strip().lstrip('0') + + def get_port(self): + with subprocess.Popen(['nrfjprog', '-s', self.nrfjprog_serial, '--com'], + stdout=subprocess.PIPE) as p: + ports = [port[1] for port in + (line.strip().decode().split() for line in p.stdout.readlines()) if + port[2] == 'VCOM0'] + + if len(ports) != 1: + raise Exception( + f"Found {len(ports)} VCOM0 ports for given nrfjprog serial ({self.config['nrfjprog-serial']})") + + return ports[0] + + def acquire_device(self): + self.device = serial.Serial(self.get_port(), self.config['baudrate'], + timeout=self.config['timeout']) + + def flash_device(self, hex_image=None, chiperase=False): + erase_option = '--chiperase' if chiperase else '--sectorerase' + image = hex_image if hex_image else self.config['hex-path'] + + if subprocess.run(['nrfjprog', '-s', self.nrfjprog_serial, '--program', image, erase_option]).returncode != 0: + raise Exception('Flash failed') + + if subprocess.run(['nrfjprog', '-s', self.nrfjprog_serial, '--pinreset']).returncode != 0: + raise Exception('Flash failed') + + def release_device(self, erase=True): + if self.device is not None: + if erase: + subprocess.run(['nrfjprog', '-s', self.nrfjprog_serial, '-e']) + self.device.close() + self.device = None diff --git a/tools/board_adapters/st_adapter.py b/tools/board_adapters/st_adapter.py new file mode 100644 index 0000000..a2d7bda --- /dev/null +++ b/tools/board_adapters/st_adapter.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2020-2022 AVSystem +# +# 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 +# +# http://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. + +import subprocess + +import serial +import serial.tools.list_ports + +import zephyr_adapter_common + + +# Yo dawg, I heard you like hexdumps +# So we put a hexdump in your hexdump so you can hexdump while you hexdump + +# Apparently, st-flash's required format of --serial +# parameter is a hexlified ASCII-hexstring (sic!), while +# pyserial displays the serial number properly. +def unhexlify(hexstring): + return bytes.fromhex(hexstring).decode("ASCII") + + +class TargetAdapter(zephyr_adapter_common.ZephyrAdapterCommon): + def acquire_device(self): + stlink_serial_unhexlified = unhexlify(self.config["stlink-serial"]) + ports = [ + port + for port + in serial.tools.list_ports.comports() + if port.serial_number == stlink_serial_unhexlified + ] + + if len(ports) != 1: + raise Exception( + f"Found {len(ports)} ports for given ST-Link serial ({self.config['stlink-serial']})") + + self.device = serial.Serial(ports[0].device, self.config["baudrate"], + timeout=self.config["timeout"]) + + def flash_device(self): + if subprocess.run(["st-flash", + "--serial", self.config["stlink-serial"], + "--format", "ihex", + "write", self.config["hex-path"] + ]).returncode != 0: + raise Exception("Flash failed") + + def release_device(self): + if self.device is not None: + subprocess.run(["st-flash", "--serial", self.config["stlink-serial"], "erase"]) + self.device.close() + self.device = None diff --git a/tools/board_adapters/zephyr_adapter_common.py b/tools/board_adapters/zephyr_adapter_common.py new file mode 100644 index 0000000..7ab76dc --- /dev/null +++ b/tools/board_adapters/zephyr_adapter_common.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2020-2022 AVSystem +# +# 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 +# +# http://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. + +import contextlib +import time + +THROTTLED_WRITE_DELAY = 0.01 + + +def throttled_write(device, payload): + for byte in payload: + device.write(bytes([byte])) + device.flush() + time.sleep(THROTTLED_WRITE_DELAY) + + +class ZephyrAdapterCommon: + def __init__(self, config): + self.config = config + self.device = None + + def prepare_device(self): + self.flash_device() + + self.skip_until("Initializing Anjay-zephyr-client demo") + self.skip_until_prompt() + print("Prompt message received, configuring...") + + self.__write_options() + print("Device configured, waiting for connection...") + + self.skip_until("registration successful") + print("Device successfully registered") + + @contextlib.contextmanager + def __override_timeout(self, timeout): + old_timeout = self.device.timeout + try: + if timeout is not None: + self.device.timeout = timeout + yield + finally: + self.device.timeout = old_timeout + + def __passthrough(self, incoming): + if self.config["passthrough"]: + decoded_incoming = incoming.decode("ASCII", errors="ignore") + print("\033[35m", end="") + print(decoded_incoming, end="") + print("\033[0m") + + def passthrough(self, timeout): + result = b'' + deadline = time.time() + timeout + while True: + new_timeout = max(deadline - time.time(), 0) + with self.__override_timeout(new_timeout): + result += self.device.read(65536) + if new_timeout <= 0: + break + self.__passthrough(result) + + def skip_until(self, expected): + expected_encoded = expected.encode("ASCII") + incoming = self.device.read_until(expected_encoded) + + self.__passthrough(incoming) + + if not incoming.endswith(expected_encoded): + raise Exception("skip_until timed out") + + return incoming + + def skip_until_prompt(self): + return self.skip_until("uart:~$ ") + + def __write_options(self): + throttled_write(self.device, b"\r\nanjay stop\r\n") + while True: + with self.__override_timeout(90): + line = self.skip_until('\n') + + if b'Anjay stopped' in line or b'Anjay is not running' in line: + break + + for opt_key, opt_val in self.config["device-config"].items(): + print(f"Writing option {opt_key}") + throttled_write(self.device, + f"anjay config set {opt_key} {opt_val}\r\n".encode("ASCII")) + self.skip_until_prompt() + + throttled_write(self.device, b"anjay config save\r\n") + + self.skip_until("Configuration successfully saved") + + time.sleep(1) + throttled_write(self.device, b"kernel reboot cold\r\n") diff --git a/tools/provisioning-tool/configs/cert_info.json b/tools/provisioning-tool/configs/cert_info.json new file mode 100644 index 0000000..db2a86c --- /dev/null +++ b/tools/provisioning-tool/configs/cert_info.json @@ -0,0 +1,9 @@ +{ + "countryName": "PL", + "stateOrProvinceName": "Malopolska", + "localityName": "Cracow", + "organizationName": "AVSystem", + "organizationUnitName": "Embedded", + "serialNumber": 1, + "validityOffsetInSeconds": 220752000 +} diff --git a/tools/provisioning-tool/configs/endpoint_cfg b/tools/provisioning-tool/configs/endpoint_cfg new file mode 100644 index 0000000..799d809 --- /dev/null +++ b/tools/provisioning-tool/configs/endpoint_cfg @@ -0,0 +1,21 @@ +{ + OID.Security: { + 1: { + RID.Security.ServerURI : 'coaps://eu.iot.avsystem.cloud:5684', + RID.Security.Bootstrap : False, + RID.Security.Mode : 0, # 0: PSK, 2: Cert, 3: NoSec + RID.Security.PKOrIdentity : b'reg-test-psk-identity', + RID.Security.SecretKey : b'3x@mpl3P5K53cr3tK3y', + RID.Security.ShortServerID : 1 + }, + }, + + OID.Server: { + 1: { + RID.Server.ShortServerID : 1, + RID.Server.Lifetime : 86400, + RID.Server.NotificationStoring : False, + RID.Server.Binding : 'U' + }, + } +} diff --git a/tools/provisioning-tool/configs/endpoint_cfg_cert b/tools/provisioning-tool/configs/endpoint_cfg_cert new file mode 100644 index 0000000..4f505d2 --- /dev/null +++ b/tools/provisioning-tool/configs/endpoint_cfg_cert @@ -0,0 +1,19 @@ +{ + OID.Security: { + 1: { + RID.Security.ServerURI : 'coaps://eu.iot.avsystem.cloud:5684', + RID.Security.Bootstrap : False, + RID.Security.Mode : 2, # 0: PSK, 2: Cert, 3: NoSec + RID.Security.ShortServerID : 1 + }, + }, + + OID.Server: { + 1: { + RID.Server.ShortServerID : 1, + RID.Server.Lifetime : 86400, + RID.Server.NotificationStoring : False, + RID.Server.Binding : 'U' + }, + } +} diff --git a/tools/provisioning-tool/configs/lwm2m_server.json b/tools/provisioning-tool/configs/lwm2m_server.json new file mode 100644 index 0000000..ce03238 --- /dev/null +++ b/tools/provisioning-tool/configs/lwm2m_server.json @@ -0,0 +1,5 @@ +{ + "url": "https://eu.iot.avsystem.cloud", + "port": 8087, + "domain": "/main//" +} diff --git a/tools/provisioning-tool/final_overlay.conf b/tools/provisioning-tool/final_overlay.conf new file mode 100644 index 0000000..e9904e9 --- /dev/null +++ b/tools/provisioning-tool/final_overlay.conf @@ -0,0 +1,6 @@ +# Anjay settings necessary to perform the device provisioning +CONFIG_ANJAY_WITH_LWM2M11=y +CONFIG_ANJAY_WITH_CBOR=y +CONFIG_ANJAY_CLIENT_PERSISTENCE=y +CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING=y +CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING_INITIAL_FLASH=n diff --git a/tools/provisioning-tool/initial_overlay.conf b/tools/provisioning-tool/initial_overlay.conf new file mode 100644 index 0000000..45597a6 --- /dev/null +++ b/tools/provisioning-tool/initial_overlay.conf @@ -0,0 +1,6 @@ +# Anjay settings necessary to perform the device provisioning +CONFIG_ANJAY_WITH_LWM2M11=y +CONFIG_ANJAY_WITH_CBOR=y +CONFIG_ANJAY_CLIENT_PERSISTENCE=y +CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING=y +CONFIG_ANJAY_CLIENT_FACTORY_PROVISIONING_INITIAL_FLASH=y diff --git a/tools/provisioning-tool/ptool.py b/tools/provisioning-tool/ptool.py new file mode 100755 index 0000000..5909725 --- /dev/null +++ b/tools/provisioning-tool/ptool.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2020-2022 AVSystem +# +# 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 +# +# http://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. + +import argparse +import importlib +import importlib.util +import subprocess +import sys +import os +import shutil +import tempfile +import yaml +import west.util + +from requests import HTTPError +from subprocess import CalledProcessError + +class ZephyrImageBuilder: + def __init__(self, board, image_dir): + script_directory = os.path.dirname(os.path.realpath(__file__)) + + self.board = board + self.image_dir = image_dir if image_dir else os.path.join(os.getcwd(), 'provisioning_builds') + self.image_path = {} + self.overlay = {} + + for kind in ['initial', 'final']: + self.image_path[kind] = os.path.join(self.image_dir, f'{kind}.hex') + self.overlay[kind] = os.path.join(script_directory, f'{kind}_overlay.conf') + + def __build(self, kind): + current_build_directory = os.path.join(self.image_dir, kind) + os.makedirs(current_build_directory, exist_ok=True) + + subprocess.run(['west', 'build', '-b', self.board, '-d', current_build_directory, + '-p', '--', f'-DOVERLAY_CONFIG={self.overlay[kind]}'], check=True) + + shutil.move(os.path.join(current_build_directory, 'zephyr/merged.hex'), self.image_path[kind]) + shutil.rmtree(current_build_directory) + + return self.image_path[kind] + + def build_initial_image(self): + return self.__build('initial') + + def build_final_image(self): + return self.__build('final') + + +def get_images(args): + initial_image = None + final_image = None + + if args.image_dir: + potential_initial_image = os.path.join(args.image_dir, 'initial.hex') + if os.path.exists(potential_initial_image): + initial_image = potential_initial_image + print('Using provided initial.hex file as an initial provisioning image') + + potential_final_image = os.path.join(args.image_dir, 'final.hex') + if os.path.exists(potential_final_image): + final_image = potential_final_image + print('Using provided final.hex file as a final provisioning image') + + if args.board and (initial_image is None or final_image is None): + builder = ZephyrImageBuilder(args.board, args.image_dir) + + if initial_image is None: + print('Initial provisioning image not provided - building') + initial_image = builder.build_initial_image() + + if final_image is None: + print('Final provisioning image not provided - building') + final_image = builder.build_final_image() + + if initial_image is None or final_image is None: + raise ValueError('Zephyr images cannot be obtained') + + return initial_image, final_image + +def get_device_adapter(serial_number, baudrate): + sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../board_adapters')) + adapter_module = importlib.import_module('nrf_adapter') + + config = { + 'nrfjprog-serial': serial_number, + 'baudrate': str(baudrate), + 'timeout': 60, + 'hex-path': None, + 'passthrough': False + } + + return adapter_module.TargetAdapter(config) + +def flash_device(device, image, chiperase, success_text): + device.acquire_device() + device.flash_device(image, chiperase) + device.skip_until(success_text) + device.skip_until_prompt() + device.release_device(erase=False) + + print('Device flashed succesfully') + +def mcumgr_download(port, src, baud=115200): + with tempfile.TemporaryDirectory() as dst_dir_name: + dst_file_name = os.path.join(dst_dir_name, 'result.txt') + connstring = f'dev={port},baud={baud}' + command = ['mcumgr', '--conntype', 'serial', '--connstring', connstring, + 'fs', 'download', src, dst_file_name, '-t', '30'] + + subprocess.run(command, cwd=os.getcwd(), universal_newlines=True, check=True) + + with open(dst_file_name) as dst_file: + return dst_file.read() + +def mcumgr_upload(port, src, dst, baud=115200): + subprocess.run(['mcumgr', '--conntype', 'serial', '--connstring', f'dev={port},baud={baud}', 'fs', 'upload', src, dst], + cwd=os.getcwd(), universal_newlines=True, check=True) + +def get_anjay_zephyr_path(manifest_path): + with open(manifest_path, 'r') as stream: + projects = yaml.safe_load(stream)['manifest']['projects'] + for project in projects: + if project['name'] == 'anjay-zephyr': + return os.path.join(west.util.west_topdir(), project['path']) + +def get_manifest_path(): + west_manifest_path = None + west_manifest_file = None + + west_config = subprocess.run(['west', 'config', '-l'], capture_output=True).stdout.split(b'\n') + for config_entry in west_config: + if config_entry.startswith(b'manifest.file'): + west_manifest_file = config_entry.split(b'=')[1].strip() + if config_entry.startswith(b'manifest.path'): + west_manifest_path = config_entry.split(b'=')[1].strip() + + print(f'Manifest path: {west_manifest_path}') + print(f'Manifest file: {west_manifest_file}') + + if (not west_manifest_file) or (not west_manifest_path): + raise Exception('west config incomplete') + + return os.path.join(west_manifest_path, west_manifest_file) + + +def main(): + parser = argparse.ArgumentParser( + description='Factory provisioning tool') + + # Arguments for building the Zephyr images + parser.add_argument('-b', '--board', type=str, + help='Board for which the image should be built, may be not provided if images are cached', + required=False) + parser.add_argument('-i', '--image_dir', type=str, + help='Directory for the cached Zephyr hex images', + required=False, + default=None) + + # Arguments for flashing + parser.add_argument('-s', '--serial', type=str, + help='Serial number of the device to be used', + required=True) + parser.add_argument('-B', '--baudrate', type=int, + help='Baudrate for the used serial port', + required=False, default=115200) + + # Arguments for factory_provisioning library + parser.add_argument('-c', '--endpoint_cfg', type=str, + help='Configuration file containing device information to be loaded on the device', + required=True) + parser.add_argument('-S', '--server', type=str, + help='JSON format file containing Coiote server information', + required=False) + parser.add_argument('-t', '--token', type=str, + help='Access token for authorization to Coiote server', + required=False) + parser.add_argument('-C', '--cert', type=str, + help='JSON format file containing information for the generation of a self signed certificate', + required=False) + parser.add_argument('-k', '--pkey', type=str, + help='Endpoint private key in DER format, ignored if CERT parameter is set', + required=False) + parser.add_argument('-r', '--pcert', type=str, + help='Endpoint public cert in DER format, ignored if CERT parameter is set', + required=False,) + parser.add_argument('-p', '--scert', type=str, + help='Server public cert in DER format', + required=False) + + args = parser.parse_args() + + # This is called also as an early check if the proper west config is set + manifest_path = get_manifest_path() + + if bool(args.server) != bool(args.token): + raise Exception('Server and token cli arguments should be always provided together') + + anjay_path = os.path.join(get_anjay_zephyr_path(manifest_path), 'deps/anjay') + provisioning_tool_path = os.path.join(anjay_path, 'tools/provisioning-tool') + sys.path.append(provisioning_tool_path) + fp = importlib.import_module('factory_prov.factory_prov') + + initial_image, final_image = get_images(args) + + print('Zephyr Images ready!') + + adapter = get_device_adapter(args.serial, args.baudrate) + flash_device(adapter, initial_image, True, 'Device ready for provisioning.') + + port = adapter.get_port() + endpoint_name = mcumgr_download(port, '/factory/endpoint.txt', args.baudrate) + + print(f'Downloaded endpoint name: {endpoint_name}') + + fcty = fp.FactoryProvisioning(args.endpoint_cfg, endpoint_name, args.server, + args.token, args.cert) + if fcty.get_sec_mode() == 'cert': + if args.scert is not None: + fcty.set_server_cert(args.scert) + + if args.cert is not None: + fcty.generate_self_signed_cert() + elif args.pkey is not None and args.pcert is not None: + fcty.set_endpoint_cert_and_key(args.pcert, args.pkey) + + with tempfile.TemporaryDirectory() as temp_directory: + last_cwd = os.getcwd() + os.chdir(temp_directory) + fcty.provision_device() + mcumgr_upload(adapter.get_port(), 'SenMLCBOR', '/factory/provision.cbor', args.baudrate) + os.chdir(last_cwd) + + if int(mcumgr_download(port, '/factory/result.txt', args.baudrate)) != 0: + raise RuntimeError('Bad device provisioning result') + + if args.server and args.token: + fcty.register() + + flash_device(adapter, final_image, False, 'persistence: Anjay restored from') + + +if __name__ == '__main__': + main()