├── .gitignore ├── .mbedignore_no_rtos ├── LICENSE ├── README.md ├── at45-blockdevice.lib ├── bootloader ├── DISCO_L475VG_IOT01A.bin ├── FF1705_L151CC.bin └── source.txt ├── docs └── porting-guide.md ├── example-firmware ├── disco-l475vg-blinky.bin └── xdot-blinky.bin ├── fuota-server ├── .gitignore ├── README.md ├── class-c-activation │ ├── disco-l475vg-iot01a1-eu868.bin │ └── xdot-eu868.bin ├── loraserver.js ├── package.json └── test-file.bin ├── mbed-lora-radio-drv.lib ├── mbed-lorawan-update-client.lib ├── mbed-os.lib ├── mbed-printf.lib ├── mbed_app.json ├── profiles └── tiny.json ├── simconfig.json ├── source ├── example_insecure_rot.c ├── fotalora_mbedtls_config.h ├── helpers │ ├── dev_eui_helper.h │ ├── lora_radio_helper.h │ ├── memory_helper.h │ └── storage_helper.h └── main.cpp └── testplan.md /.gitignore: -------------------------------------------------------------------------------- 1 | .mbed 2 | .mbedignore 3 | at45-blockdevice/ 4 | bootlloader/FF1705_L151CC_linked.bin 5 | mbed-lora-radio-drv/ 6 | mbed-lorawan-update-client/ 7 | mbed-os/ 8 | mbed-printf/ 9 | source/UpdateCerts.h 10 | UpdateCerts.h 11 | updates/ 12 | .fota-keys/ 13 | -------------------------------------------------------------------------------- /.mbedignore_no_rtos: -------------------------------------------------------------------------------- 1 | mbed-os/rtos/* 2 | mbed-os/components/wifi/* 3 | mbed-os/features/netsocket/* 4 | mbed-os/features/cellular/* 5 | mbed-os/features/frameworks/greentea-client/* 6 | mbed-os/features/frameworks/utest/* 7 | mbed-os/features/frameworks/unity/* 8 | mbed-os/features/nanostack/* 9 | mbed-os/features/lwipstack/* 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firmware-updates enabled LoRaWAN example application 2 | 3 | This application implements multicast firmware updates over LoRaWAN for Mbed OS 5. It implements: 4 | 5 | * [LoRaWAN Remote Multicast Setup Specification v1.0.0](https://lora-alliance.org/resource-hub/lorawan-remote-multicast-setup-specification-v100). 6 | * [LoRaWAN Fragmented Data Block Transport Specification v1.0.0](https://lora-alliance.org/resource-hub/lorawan-fragmented-data-block-transport-specification-v100). 7 | * [LoRaWAN Application Layer Clock Synchronization Specification v1.0.0](https://lora-alliance.org/resource-hub/lorawan-application-layer-clock-synchronization-specification-v100). 8 | * Delta updates. 9 | * Cryptographic verification of new firmware. 10 | * Bootloader for flashing new firmware images. 11 | 12 | Want to learn more? [Here's a video about the process](https://www.youtube.com/watch?v=0NoshDOqmdM), and [here's a demo of this application](https://www.youtube.com/watch?v=SSbDT1jxxwg&feature=youtu.be). 13 | 14 | ## Target configuration 15 | 16 | This application runs on any Mbed-enabled development board, but requires some configuration. See the [porting guide](docs/porting-guide.md) for more information. 17 | 18 | The storage layer and firmware slots are already present for the [L-TEK FF1705](https://os.mbed.com/platforms/L-TEK-FF1705/) and [DISCO-L475VG](https://os.mbed.com/platforms/ST-Discovery-L475E-IOT01A/) (with a radio shield) development boards. We suggest these boards if you want to test this solution out. 19 | 20 | If you've added a new target configuration, please send a pull request to this repo! 21 | 22 | ## Build this application 23 | 24 | 1. Install [Mbed CLI](https://os.mbed.com/docs/v5.10/tools/installation-and-setup.html) and the [GNU ARM Embedded Toolchain 6](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm). 25 | 1. Import this repository via: 26 | 27 | ``` 28 | $ mbed import https://github.com/armmbed/mbed-os-example-lorawan-fuota 29 | ``` 30 | 31 | 1. In `main.cpp` specify your AppEui and AppKey. 32 | 1. In `mbed_app.json` specify your frequency plan (and [FSB](https://github.com/ARMmbed/mbed-os/blob/master/features/lorawan/FSB_Usage.txt)). 33 | 34 | Next, you need to generate a public/private key pair. The public key is held in your application, and the private key is used to sign updates. 35 | 36 | 1. Install [Node.js 8 or higher](https://nodejs.org). 37 | 1. Install the [lorawan-fota-signing-tool](https://github.com/janjongboom/lorawan-fota-signing-tool) via: 38 | 39 | ``` 40 | $ npm install lorawan-fota-signing-tool -g 41 | ``` 42 | 43 | **Note:** this also requires Python 2.7 and OpenSSL installed. 44 | 45 | 1. Create a public/private key pair: 46 | 47 | ``` 48 | $ lorawan-fota-signing-tool create-keypair -d yourdomain.com -m your-device-model-string 49 | ``` 50 | 51 | With everything configured, you can build the application. 52 | 53 | 1. Copy `.mbedignore_no_rtos` to `.mbedignore` - this removes the RTOS, and is required on targets with less than 32K SRAM (incl. L-TEK FF1705) without a crypto engine. The ECDSA/SHA256 verification will run out of memory if this is not done (`-0xfffffff0` error code). See [Build configuration](#build-configuration) for more information. 54 | 1. Build this application via: 55 | 56 | ``` 57 | $ mbed compile -m auto -t GCC_ARM --profile=./profiles/tiny.json 58 | ``` 59 | 60 | 1. Drag `BUILD/YOUR_TARGET/GCC_ARM-TINY/mbed-os-example-lorawan-fuota.bin` onto your development board (mounts as flash storage device). 61 | 1. When flashing is complete, attach a serial monitor on baud rate 115,200. 62 | 63 | ## Sending a first firmware update 64 | 65 | After the device joined the network, and with the public key in place, you can send a firmware update. 66 | 67 | 1. Sign the example binary: 68 | 69 | ``` 70 | $ lorawan-fota-signing-tool sign-binary -b example-firmware/xdot-blinky.bin -o xdot-blinky-signed.bin --output-format bin --override-version 71 | ``` 72 | 73 | 1. Give `xdot-blinky-signed.bin` to your LoRaWAN network (differs per network) for a firmware update. 74 | 75 | **Note:** A testing script for LoRaServer.io is included with this repository, [see below](#testing-using-loraserverio). 76 | 77 | The device will join a multicast session, receive the update, repair any missing packets, and then cryptographically verify the firmware using the public key. When this succeeds hit the **RESET** button, and the bootloader will update the firmware. After this, blinky will run. 78 | 79 | ## Creating a delta update 80 | 81 | To create a delta update, re-flash mbed-os-example-lorawan-fuota.bin to your development board. 82 | 83 | Then: 84 | 85 | 1. Back up your current `_update.bin` file. This is the application without the bootloader: 86 | 87 | ``` 88 | $ mkdir updates 89 | $ cp BUILD/YOUR_TARGET/GCC_ARM-TINY/mbed-os-example-lorawan-fuota_update.bin updates/v1_update.bin 90 | ``` 91 | 92 | 1. Make a small change in your application. 93 | 1. Compile the application. 94 | 1. Copy the new `_update.bin` file into the `updates` folder: 95 | 96 | ``` 97 | $ cp BUILD/YOUR_TARGET/GCC_ARM-TINY/mbed-os-example-lorawan-fuota_update.bin updates/v2_update.bin 98 | ``` 99 | 100 | 1. Create and sign the diff: 101 | 102 | ``` 103 | $ lorawan-fota-signing-tool sign-delta --old updates/v1_update.bin --new updates/v2_update.bin --output-format bin -o updates/v1_to_v2.bin 104 | ``` 105 | 106 | 1. Give `v1_to_v2.bin` to your LoRaWAN network (differs per network) for a firmware update. 107 | 108 | The update will come in, and after updating the new program should run. 109 | 110 | ## Testing using LoRaServer.io 111 | 112 | 1. Follow the steps to install and configure LoRaServer.io, as described in [fuota-server/README.md](fuota-server/README.md). 113 | 1. Create fragments from a signed binary, via: 114 | 115 | ``` 116 | $ lorawan-fota-signing-tool sign-binary -b example/xdot-blinky.bin -o fuota-server/xdot-blinky-signed.txt --frag-size 40 --redundancy-packets 20 --output-format packets-plain --override-version 117 | ``` 118 | 119 | 1. Run: 120 | 121 | ``` 122 | $ node fuota-server/loraserver.js fuota-server/xdot-blinky-signed.txt 123 | ``` 124 | 125 | **Note:** If you're using SF7, use a frag size of 204 (maximum size) for faster testing. 126 | 127 | ### Interop testing 128 | 129 | The LoRa Alliance FUOTA test scenarios is also supported, but for this you need to enable interop mode. This will disable firmware verification (as it's not a real valid firmware going over the line). 130 | 131 | **Note:** This does not override the GenAppKey. If you have changed the GenAppKey you need to change it back to the interop GenAppKey in main.cpp. 132 | 133 | 1. In `mbed_app.json`, set `lorawan-update-client.interop-testing` to `true`. 134 | 1. Create fragments for the test file, via: 135 | 136 | ``` 137 | $ lorawan-fota-signing-tool create-frag-packets -i fuota-server/test-file.bin --output-format plain --frag-size 40 --redundancy-packets 5 -o fuota-server/test-file-unsigned.txt 138 | ``` 139 | 140 | 1. Run: 141 | 142 | ``` 143 | $ node fuota-server/loraserver.js fuota-server/test-file-unsigned.txt 144 | ``` 145 | 146 | **FlashIAPBlockDevice** 147 | 148 | The interop tests require a lot less flash (only a few KB in slot 0) than the full update client. You can use the upper part of the internal flash as a scratch space in interop mode. See the [FlashIAPBlockDevice](https://os.mbed.com/docs/v5.10/apis/flashiapblockdevice.html) section in the Mbed OS documentation. 149 | 150 | ## Using the simulator for testing 151 | 152 | You can test most things also in the simulator, including the interop tests against loraserver. 153 | 154 | 1. Install the [Mbed Simulator](https://github.com/janjongboom/mbed-simulator) v1.8 or higher. 155 | 1. From the 'mbed-os-example-lorawan-fuota' folder, run: 156 | 157 | ``` 158 | $ LORA_HOST=LORASERVER_IP mbed-simulator . 159 | 160 | # LoRaWAN information: 161 | # Gateway ID: fe:00:89:00:00:29:cd:01 162 | ``` 163 | 164 | 1. Register the gateway with that ID in LoRaServer. 165 | 1. Refresh the page with the simulator and the device should join the network. 166 | 1. Start the interop tests as you'd normally do. 167 | 168 | ## Application configuration 169 | 170 | You can set some additional settings in `mbed_app.json`: 171 | 172 | * `lorawan-update-client.overwrite-version` - the manifest contains the build date of the binary, and binaries that are older than the current firmware are rejected. You might not want this in testing. Set to `true` to overwrite the version at runtime. 173 | * `lorawan-update-client.interop-testing` - skips firmware verification and writing the bootloader header. In addition this will start broadcasting the CRC32 hash of the received file after receiving the full file. Use this for interop testing with the LoRa Alliance FUOTA test scenarios. 174 | 175 | ## Build configuration 176 | 177 | For optimized builds you can build without the RTOS enabled, with newlib-nano, and a different printf library. Some background is in [this blog post](https://os.mbed.com/blog/entry/Reducing-memory-usage-with-a-custom-prin/). To do this: 178 | 179 | 1. Rename `.mbedignore_no_rtos` to `.mbedignore`. 180 | 1. Build with: 181 | 182 | ``` 183 | $ mbed compile --profile=./profiles/tiny.json 184 | ``` 185 | 186 | On the L-TEK FF1705 this consumes: 187 | 188 | ``` 189 | Total Static RAM memory (data + bss): 10712 bytes 190 | Total Flash memory (text + data): 109254 bytes 191 | ``` 192 | 193 | ## Memory usage 194 | 195 | Memory usage also depends on the presence of the RTOS. 196 | 197 | Baseline numbers on the FF1705, which include Mbed OS + RTOS + the LoRaWAN stack + the Update Client: 198 | 199 | ``` 200 | Static RAM: 19560 bytes 201 | Heap size: 5384 bytes 202 | Free: 6480 bytes 203 | ``` 204 | 205 | Without RTOS, with newlib-nano, and with tiny profile: 206 | 207 | ``` 208 | Static RAM: 10600 bytes 209 | Heap size: 4812 bytes 210 | Free: 16488 bytes 211 | ``` 212 | 213 | In addition, memory is used during the firmware update. This is dynamically allocated memory to reconstruct the firmware, and do cryptographic verification of the image. Details on the memory usage, and memory pressure events that your application can subscribe to are found in [mbed-lorawan-update-client#memory-usage](https://github.com/janjongboom/mbed-lorawan-update-client#memory-usage). 214 | 215 | ## Automated testing 216 | 217 | See [mbed-lorawan-update-client#unit-tests](https://github.com/janjongboom/mbed-lorawan-update-client#unit-tests) to configure your storage layer. No changes are required for the L-TEK FF1705. 218 | 219 | If you want to run the tests from this folder: 220 | 221 | 1. Copy the UpdateCerts.h file from the test folder: 222 | 223 | ``` 224 | $ cp mbed-lorawan-update-client/TESTS/COMMON/UpdateCerts.h . 225 | ``` 226 | 227 | 1. Create the following `.mbedignore` file: 228 | 229 | ``` 230 | source/main.cpp 231 | source/example_insecure_rot.c 232 | ``` 233 | 234 | 1. And run the tests: 235 | 236 | ``` 237 | $ mbed test --app-config mbed-lorawan-update-client/TESTS/tests/mbed_app.json -n mbed-lorawan-update-client-tests-tests-* -v 238 | ``` 239 | 240 | ## Manifest format and update signing procedure 241 | 242 | **Note:** We're planning to switch to the IETF SUIT manifest when it's ready, the current manifest format is a stop-gap solution. 243 | 244 | Every firmware update is accompanied with a manifest, a file with metadata about the update. This manifest contains a cryptographic signature, information on which devices the update can be applied to, and whether it's a delta update. Updates are signed with the private key of an Elliptic Curve key pair using ECDSA/SHA256. This means that the SHA256 hash of the firmware is signed. This signature is then placed in the manifest. When doing a firmware update, the new binary (outcome after patching) is signed, thus also acting as a way to verify that patching was successful. 245 | 246 | ### Update format 247 | 248 | The update file format is: 249 | 250 | 1. The update file (either diff or full image). 251 | 1. 1 byte, size of the signature (70, 71 or 72 bytes). 252 | 1. 72 bytes, ECDSA/SHA256 signature of the update file. In case of a patch file, this is the signature of the file *after* patching (thus it's also a way of checking if patching succeeded). If the signature is smaller than 72 bytes, right pad with `00`. 253 | 1. 16 bytes, manufacturer UUID. 254 | 1. 16 bytes, device class UUID. 255 | 1. 4 bytes, version - this is the last modified data of the update file. 256 | 1. 1 byte, diff indication. If `0`, then this is not a delta update. If `1` it's a delta update. 257 | 1. 3 bytes, size of current firmware (if delta update). If sending a delta update then this field indicates the size of the current (before patching) firmware. 258 | 259 | ## Building a small firmware image for testing 260 | 261 | It's useful to have a small, valid firmware image during testing (similar to `example-firmware/xdot-blinky.bin`) to quickly test the full update flow. To build one: 262 | 263 | 1. Clone [mbed-os-example-blinky-no-rtos](https://github.com/janjongboom/mbed-os-example-blinky-no-rtos). 264 | 1. Open `mbed_app.json` and add a new section under `target_overrides`: 265 | 266 | ```json 267 | { 268 | "target_overrides": { 269 | "*": { 270 | "platform.stdio-flush-at-exit": false 271 | }, 272 | "YOUR_TARGET_NAME": { 273 | "target.features_add": ["BOOTLOADER"], 274 | "target.app_offset": "0x8400", 275 | "target.header_offset": "0x8000", 276 | "target.bootloader_img": "bootloader/YOUR_TARGET_NAME.bin" 277 | } 278 | } 279 | } 280 | ``` 281 | 282 | **Note:** `app_offset` and `header_offset` need to be the same as in 'mbed-os-example-lorawan-fuota'. 283 | 284 | 1. Build the application: 285 | 286 | ``` 287 | $ mbed compile -m YOUR_TARGET_NAME -t GCC_ARM --profile=./profiles/tiny.json 288 | ``` 289 | 290 | **Note:** Compiling with ARMCC typically yields smaller binaries. 291 | 292 | 1. Find `BUILD/YOUR_TARGET_NAME/GCC_ARM-TINY/mbed-os-example-blinky-no-rtos_update.bin`. This is a binary you can send over a firmware update. 293 | 294 | ## Mbed OS version 295 | 296 | This application is built on Mbed OS 5.11, but requires a single patch for session serialization ([here](https://github.com/janjongboom/mbed-os/commit/271c33d63f6cb1c01de7dd983552ab4af435d9af)). However, we believe that this is a sub-optimal solution that probably has some race conditions and are working to integrate a better solution into Mbed OS core. 297 | -------------------------------------------------------------------------------- /at45-blockdevice.lib: -------------------------------------------------------------------------------- 1 | https://github.com/janjongboom/at45-blockdevice/#398e749a8bd07d5ef33d0b4a816e16a061c74c89 2 | -------------------------------------------------------------------------------- /bootloader/DISCO_L475VG_IOT01A.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/mbed-os-example-lorawan-fuota/2fb3350a388369e5b2ade2976f3497df5355acda/bootloader/DISCO_L475VG_IOT01A.bin -------------------------------------------------------------------------------- /bootloader/FF1705_L151CC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/mbed-os-example-lorawan-fuota/2fb3350a388369e5b2ade2976f3497df5355acda/bootloader/FF1705_L151CC.bin -------------------------------------------------------------------------------- /bootloader/source.txt: -------------------------------------------------------------------------------- 1 | The bootloader source code is here: https://github.com/janjongboom/mbed-bootloader/tree/ff1705_l151cc 2 | -------------------------------------------------------------------------------- /docs/porting-guide.md: -------------------------------------------------------------------------------- 1 | # Porting guide 2 | 3 | This application runs on every Mbed-enabled development board, but some configuration needs to be set. 4 | 5 | 1. Storage layer needs to be specified in both the [bootloader](https://github.com/janjongboom/mbed-bootloader/tree/ff1705_l151cc) and in this application (both in `main.cpp`). 6 | 1. Firmware slots need to be configured in both the bootloader and this application. 7 | 1. A Root of Trust needs to be configured. An example insecure RoT is provided, but you **should** replace this before going into production. See the [Pelion Device Management docs](https://cloud.mbed.com/docs/v1.4/connecting/pelion-device-management-edge-security-considerations.html#root-of-trust) for more information. 8 | 9 | ## Adding a new section in mbed_app.json 10 | 11 | The [mbed_app.json](../mbed-app.json) file contains all the configuration for the application, and this also contains per-target configuration. To start a new port you'll need to add a new section for your board under `target_overrides`. The key of the configuration is the name of your target. You can find this name through Mbed CLI: 12 | 13 | ``` 14 | $ mbed detect 15 | [mbed] Detected DISCO_L475VG_IOT01A, port /dev/tty.usbmodem14403, mounted /Volumes/DIS_L4IOT, interface version 0221: 16 | [mbed] Supported toolchains for DISCO_L475VG_IOT01A 17 | | Target | mbed OS 2 | mbed OS 5 | uARM | IAR | ARM | GCC_ARM | 18 | |---------------------|-----------|-----------|-----------|-----------|-----------|-----------| 19 | | DISCO_L475VG_IOT01A | Supported | Supported | Supported | Supported | Supported | Supported | 20 | Supported targets: 1 21 | Supported toolchains: 4 22 | ``` 23 | 24 | Here `DISCO_L475VG_IOT01A` is the target name. 25 | 26 | Thus, add the following section: 27 | 28 | ```json 29 | "DISCO_L475VG_IOT01A": { 30 | "target.features_add" : ["BOOTLOADER"] 31 | } 32 | ``` 33 | 34 | All configuration options in this porting guide need to be added to this section. 35 | 36 | ## Activity LED 37 | 38 | An activity LED is used when fragments are received. This is required by the LoRa Alliance interop tests. By default `LED1` is used, but you can override it. For example, ST boards use the same pin for `LED1` and for pins used to communicate to the radio. You can also disable the LED, by setting the value to `NC` (Not Connected). 39 | 40 | The LED is set through: 41 | 42 | ```json 43 | "activity-led": "D3", 44 | ``` 45 | 46 | ## LoRa radio 47 | 48 | You need to tell the application how the radio is connected to your development board over the SPI interface. For the [SX1272](https://os.mbed.com/components/SX1272MB2xAS/) and [SX1276](https://os.mbed.com/components/SX1276MB1xAS/) shields the configuration is: 49 | 50 | ```json 51 | "lora-radio": "SX1276", 52 | "lora-spi-mosi": "D11", 53 | "lora-spi-miso": "D12", 54 | "lora-spi-sclk": "D13", 55 | "lora-cs": "D10", 56 | "lora-reset": "A0", 57 | "lora-dio0": "D2", 58 | "lora-dio1": "D3", 59 | "lora-dio2": "D4", 60 | "lora-dio3": "D5", 61 | "lora-dio4": "D8", 62 | "lora-dio5": "D9", 63 | "lora-rf-switch-ctl1": "NC", 64 | "lora-rf-switch-ctl2": "NC", 65 | "lora-txctl": "NC", 66 | "lora-rxctl": "NC", 67 | "lora-ant-switch": "A4", 68 | "lora-pwr-amp-ctl": "NC", 69 | "lora-tcxo": "NC", 70 | ``` 71 | 72 | Set `lora-radio` to `SX1272` if you use the SX1272 shield. 73 | 74 | ## Storage layer 75 | 76 | Mbed OS has a wide variety of storage drivers available, including SD cards, DataFlash, SPI flash, and QSPI flash. The drivers are in the [components/storage/blockdevice](https://github.com/ARMmbed/mbed-os/tree/master/components/storage/blockdevice) folder of Mbed OS. You can use any of these storage layers to store the firmware fragments. If you have a lot of internal flash you can also use the [FlashIAP](https://github.com/ARMmbed/mbed-os/tree/master/components/storage/blockdevice/COMPONENT_FLASHIAP) driver. 77 | 78 | To select a built-in driver, add: 79 | 80 | ```json 81 | "target.components_add" : ["QSPIF"], 82 | ``` 83 | 84 | Note that you can write your own storage drivers, as long as they implement the `BlockDevice` interface. In that case you don't need to do add the component in configuration, just add the driver to the project. 85 | 86 | ### Selecting the storage layer 87 | 88 | To instantiate the storage layer, open [storage_helper.h](../source/helpers/storage_helper.h), and add a section for your board where you innstantiate the block device. Make sure to name it `bd`. 89 | 90 | ```cpp 91 | if defined(TARGET_DISCO_L475VG_IOT01A) 92 | // QSPI Flash interface 93 | #include "QSPIFBlockDevice.h" 94 | QSPIFBlockDevice bd(QSPI_FLASH1_IO0, QSPI_FLASH1_IO1, QSPI_FLASH1_IO2, QSPI_FLASH1_IO3, QSPI_FLASH1_SCK, QSPI_FLASH1_CSN, QSPIF_POLARITY_MODE_0, MBED_CONF_QSPIF_QSPI_FREQ); 95 | #endif 96 | ``` 97 | 98 | ### Slicing block devices 99 | 100 | The update client assumes it has full access to the storage layer. If you want to slice the storage in multiple parts, for example to mount a file system on part of the block device, use [SlicingBlockDevice](https://os.mbed.com/docs/mbed-os/v5.11/apis/slicingblockdevice.html). 101 | 102 | ## Configuring firmware slots 103 | 104 | The update client requires three firmware slots: 105 | 106 | * Slot 0: to download incoming firmware. 107 | * Slot 1: to store the results of a delta update. 108 | * Slot 2: to store the current active firmware. 109 | 110 | In a full update only slot 0 is used. In a delta update slot 0 and slot 2 are combined and the result is stored in slot 1. During boot the bootloader checks these slots to see if a new update is present. 111 | 112 | You need to configure the location and sizes of these slots in the configuration file. For every slot we need two locations: the beginning of the slot (where the slot header is placed), and the beginning of the firmware (after the slot header). The exact location depends on: 113 | 114 | * Maximum size of firmware. Typically this is the size of internal flash. 115 | * Sector alignment of the external flash. 116 | 117 | Let's say we have a device with: 118 | 119 | * 512K internal flash. 120 | * 4096 bytes erase sector size. 121 | * 1 byte program sector size. 122 | * 1 byte read sector size. 123 | 124 | We can lay out the three blocks at the beginning of flash. Every slot needs to be erase sector aligned, and you want to start at the second sector (due to a bug in the bootloader). We get the following locations: 125 | 126 | * Slot size: `512 * 1024` (max size of the firmware). 127 | * Slot 0: 128 | * header-address: `4096` - needs to be erase sector aligned, and skipping the first sector. 129 | * fw-address: `4096 + 296` - the header is 296 bytes, and the fw-address needs to be program sector aligned to the next sector. For this device the sector size is 1 byte, so it's already aligned. 130 | * Slot 1: 131 | * header-address: `4096 + (512 * 1024)` 132 | * fw-address: `4096 + (512 * 1024) + 296` 133 | * Slot 2: 134 | * header-address: `4096 + (2 * 512 * 1024)` 135 | * fw-address: `4096 + (2 * 512 * 1024) + 72` - the slot 2 header is 72 bytes, and does **not** need to be program sector aligned. So this value is _always_ 72. 136 | 137 | You place these values in your configuration as such: 138 | 139 | ``` 140 | "lorawan-update-client.slot-size" : "512 * 1024", 141 | "lorawan-update-client.slot0-header-address": "4096", 142 | "lorawan-update-client.slot0-fw-address" : "4096 + 296", 143 | "lorawan-update-client.slot1-header-address": "4096 + (512 * 1024)", 144 | "lorawan-update-client.slot1-fw-address" : "4096 + (512 * 1024) + 296", 145 | "lorawan-update-client.slot2-header-address": "4096 + (2 * 512 * 1024)", 146 | "lorawan-update-client.slot2-fw-address" : "4096 + (2 * 512 * 1024) + 72", 147 | ``` 148 | 149 | ## Bootloader 150 | 151 | To actually perform a firmware update you need to have the bootloader present. The bootloader checks the external flash for new images and actually performs the update. The internal flash is laid out like this: 152 | 153 | ``` 154 | +--------------------------+ 155 | | | 156 | | | 157 | | | 158 | | | 159 | | | 160 | | | 161 | | Application | 162 | | | 163 | | | 164 | | | 165 | | | 166 | +--------------------------+ APPLICATION (e.g. 0x8800) 167 | | Active fw header | 168 | | | 169 | +--------------------------+ HEADER (e.g. 0x8000) 170 | | | 171 | | Bootloader | 172 | | | 173 | +--------------------------+ BEGIN (e.g. 0x0) 174 | ``` 175 | 176 | The typical bootloader size is 32K, and the active firmware header is under 1K. These locations however need to be erase sector aligned (on internal flash! it differs from the external flash alignment). You can get the alignment from your data sheet or through the `FlashIAP` API in Mbed OS. These addresses need to be configured. 177 | 178 | In addition you need to configure the external flash driver (similar to your main application), and configure the firmware slot locations. 179 | 180 | To create a new bootloader for your target: 181 | 182 | 1. Clone the bootloader source code from here: [mbed-bootloader](https://github.com/janjongboom/mbed-bootloader/tree/ff1705_l151cc). 183 | 1. Create a new section in `mbed_app.json` for your target. 184 | 1. Add the storage layer in the same way as above. Loading the driver is done in `main.cpp`. 185 | 186 | Then add the following options to the `mbed_app.json` section: 187 | 188 | ```json 189 | "flash-start-address" : "0x08000000", 190 | "flash-size" : "(512*1024)", 191 | "update-client.application-details": "(MBED_CONF_APP_FLASH_START_ADDRESS+32*1024)", 192 | "application-start-address" : "(MBED_CONF_APP_FLASH_START_ADDRESS+34*1024)", 193 | "update-client.storage-address" : "4096", 194 | "update-client.storage-size" : "512 * 1024 * 2", 195 | "update-client.storage-page" : "4096", 196 | "update-client.storage-locations" : 2, 197 | "external-flash-application-copy-address": "4096 + (2 * 512 * 1024)", 198 | "max-application-size" : "DEFAULT_MAX_APPLICATION_SIZE", 199 | "target.macros_add" : [ 200 | "MBED_CLOUD_CLIENT_UPDATE_BUFFER=0x2000", 201 | "BUFFER_SIZE=0x4000" 202 | ] 203 | ``` 204 | 205 | These values are: 206 | 207 | | Key | Description | 208 | | ------------- | ------------- | 209 | | flash-start-address | Location where your memory-mapped flash starts. Look in the data sheet for your device to find this. E.g. `0x08000000` for STM32. | 210 | | flash-size | Size of the internal flash for your device. | 211 | | update-client.application-details | Location of the active firmware header. This needs to be after the bootloader. So look at the size of the bootloader, and place it after. 32K is pretty safe. This needs to be erase-sector aligned on the internal flash. You can use the `FlashIAP` API in Mbed OS to find alignment. | 212 | | application-start-address | Location where the application starts. This is after the bootloader and the firmware header. This needs to be erase-sector aligned on the internal flash. | 213 | | update-client.storage-address | Offset in external storage where the firmware slots start. Same value as `lorawan-update-client.slot0-header-address` in your application. | 214 | | update-client.storage-size | *Total* size of the first two firmware slots. Not all three because slot 2 contains the active firmware, and we never want to flash from that. | 215 | | update-client.storage-page | Erase sector size on the external flash. | 216 | | update-client.storage-locations | Always 2 for this application. | 217 | | external-flash-application-copy-address | Start address of slot 2. Same as `lorawan-update-client.slot2-header-address` in the application. | 218 | | MBED_CLOUD_CLIENT_UPDATE_BUFFER | Needs to be twice `update-client.storage-page`. | 219 | | BUFFER_SIZE | Scratch buffer, needs to be a multiple of `update-client.storage-page`. | 220 | 221 | After this, build your bootloader via: 222 | 223 | ``` 224 | $ mbed compile --profile=./tiny.json 225 | ``` 226 | 227 | Copy the output file to the `bootloader` folder in this project. 228 | 229 | ### Loading the bootloader 230 | 231 | Go back to `mbed_app.json` of your main project, and specify the bootloader via: 232 | 233 | ```json 234 | "target.header_offset" : "0x8000", 235 | "target.app_offset" : "0x8800", 236 | "target.bootloader_img" : "bootloader/MY_BOOTLOADER.bin", 237 | ``` 238 | 239 | Make sure that `header_offset` and `app_offset` match the values in the bootloader config for `update-client.application-details` and `application-start-address`, but offset to the start of the flash. 240 | -------------------------------------------------------------------------------- /example-firmware/disco-l475vg-blinky.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/mbed-os-example-lorawan-fuota/2fb3350a388369e5b2ade2976f3497df5355acda/example-firmware/disco-l475vg-blinky.bin -------------------------------------------------------------------------------- /example-firmware/xdot-blinky.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/mbed-os-example-lorawan-fuota/2fb3350a388369e5b2ade2976f3497df5355acda/example-firmware/xdot-blinky.bin -------------------------------------------------------------------------------- /fuota-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test-file-packets-* 3 | *.txt 4 | -------------------------------------------------------------------------------- /fuota-server/README.md: -------------------------------------------------------------------------------- 1 | # FUOTA Server 2 | 3 | Simple test FUOTA server for loraserver.io. To be used together with [this device software](https://github.com/armmbed/mbed-os-example-lorawan-fuota). Based on FUOTA test scenarios document. 4 | 5 | ## Setup 6 | 7 | 1. Install loraserver and all dependencies. 8 | 1. Add your devices to loraserver with parameters: 9 | * LoRaWAN 1.0.2 10 | * OTAA activation 11 | * App Key: `000102030405060708090A0B0C0D0E0F` 12 | 1. Add a single extra device (this will be used as multicast group), with parameters: 13 | * LoRaWAN 1.0.2 14 | * ABP activation 15 | * Class C 16 | * DevAddr: `0x01FFFFFF` 17 | * NwkSKey: `BB75C362588F5D65FCC61C080B76DBA3` 18 | * AppSKey: `C3F6C39B6B6496C29629F7E7E9B0CD29` 19 | 1. To establish a connection between this device and the gateway make sure to send at least one message from the Class C device to the network (can also be done in the simulator). If you're on an L-TEK FF1705, Multi-Tech xDot or DISCO-L475VG-IOT01A1 development board and the EU868 channel plan, you can do this by flashing one of the [class-c-activation](class-c-activation/) to your device, clicking **RESET** and pressing **BUTTON1**. Observe the 'Live LoRaWAN frame logs' to verify that the message appeared. 20 | 1. In `loraserver.js`: 21 | * Set the IP address of your server under `LORASERVER_HOST`. 22 | * Add your device EUIs from step 2 to the `devices` array. 23 | * Add the device EUI from step 3 to the `mcDetails` object. 24 | 1. Run: 25 | 26 | ``` 27 | $ node loraserver.js PATH_TO_A_PACKETS_FILE 28 | ``` 29 | 30 | 1. Restart your devices to re-trigger a clock sync. 31 | 32 | Once all devices have done a clock sync, a multicast session will automatically start. 33 | 34 | ## Switching to a higher spreading factor 35 | 36 | LoRaServer has no notion of multicast, thus always sends out Class C packets on the RX2 data rate and frequency. This will be very slow in most regions (e.g. EU868). You can however overwrite this with some changes. This is how to use SF7 in EU868. 37 | 38 | 1. Log in to the LoRaServer server, and open `/etc/loraserver/loraserver.toml`. 39 | 1. Set: 40 | 41 | ``` 42 | rx2_dr=5 43 | ``` 44 | 45 | 1. Restart the server: 46 | 47 | ``` 48 | $ sudo systemctl restart lora-app-server 49 | $ sudo systemctl restart lora-gateway-bridge 50 | $ sudo systemctl restart loraserver 51 | ``` 52 | 53 | 1. In `loraserver.js` under `DATARATE`, set to `5`. 54 | 1. Change the sleep time under `startSendingClassCPackets` to send faster. 55 | 1. Afterwards, send at least one message from the Class C device again. 56 | 57 | **Note:** This will not allow you to receive any messages in RX2 window on your Class A sessions, so use with care. 58 | -------------------------------------------------------------------------------- /fuota-server/class-c-activation/disco-l475vg-iot01a1-eu868.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/mbed-os-example-lorawan-fuota/2fb3350a388369e5b2ade2976f3497df5355acda/fuota-server/class-c-activation/disco-l475vg-iot01a1-eu868.bin -------------------------------------------------------------------------------- /fuota-server/class-c-activation/xdot-eu868.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/mbed-os-example-lorawan-fuota/2fb3350a388369e5b2ade2976f3497df5355acda/fuota-server/class-c-activation/xdot-eu868.bin -------------------------------------------------------------------------------- /fuota-server/loraserver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Experimental FUOTA server for loraserver.io 3 | * 4 | * Note that this is completely hacked together, and I'd suggest to never 5 | */ 6 | 7 | const LORASERVER_HOST = process.env.LORA_HOST || '192.168.122.132'; 8 | const PACKET_FILE = process.argv[2]; 9 | const DATARATE = process.env.LORA_DR || 0; 10 | 11 | if (!PACKET_FILE) throw 'Syntax: loraserver.io PACKET_FILE' 12 | 13 | const LORASERVER_API = 'https://' + LORASERVER_HOST + ':8080'; 14 | const LORASERVER_MQTT = 'mqtt://' + LORASERVER_HOST + ':1883'; 15 | 16 | const mqtt = require('mqtt') 17 | const client = mqtt.connect(LORASERVER_MQTT); 18 | const gpsTime = require('gps-time'); 19 | const fs = require('fs'); 20 | const rp = require('request-promise'); 21 | 22 | const CLASS_C_WAIT_S = 15; 23 | 24 | // all devices that you want to update 25 | const devices = [ 26 | // '00a99d4921b26d75' 27 | '00800000040004c9' 28 | ]; 29 | 30 | // details for the multicast group 31 | const mcDetails = { 32 | applicationID: '1', 33 | devEUI: '00a99d4921b26d76', 34 | }; 35 | 36 | let classCStarted = false; 37 | 38 | let deviceMap = devices.reduce((curr, eui) => { 39 | curr[eui] = { clockSynced: false, fragSessionAns: false, mcSetupAns: false, mcStartAns: false, applicationID: null, msgWaiting: null }; 40 | return curr; 41 | }, {}); 42 | 43 | let startTime = null; 44 | 45 | process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; 46 | 47 | client.on('connect', function () { 48 | client.subscribe('application/#', function (err) { 49 | if (err) { 50 | return console.error('Failed to subscribe', err); 51 | } 52 | 53 | console.log('Subscribed to all application events'); 54 | }); 55 | }) 56 | 57 | client.on('message', async function (topic, message) { 58 | console.log('msg', message.toString('utf-8')); 59 | 60 | // only interested in rx for now... 61 | if (!/\/rx$/.test(topic)) return; 62 | 63 | 64 | // message is Buffer 65 | let m = JSON.parse(message.toString('utf-8')); 66 | // console.log('Rx', topic, m); 67 | 68 | // device that we don't care about 69 | if (!deviceMap[m.devEUI]) { 70 | return; 71 | } 72 | 73 | if (m.fPort === 202 /* clock sync */) { 74 | let body = Buffer.from(m.data, 'base64'); 75 | if (body[0] === 0x1 /* CLOCK_APP_TIME_REQ */) { 76 | let deviceTime = body[1] + (body[2] << 8) + (body[3] << 16) + (body[4] << 24); 77 | let serverTime = gpsTime.toGPSMS(Date.now()) / 1000 | 0; 78 | console.log('deviceTime', deviceTime, 'serverTime', serverTime); 79 | 80 | let adjust = serverTime - deviceTime | 0; 81 | let resp = [ 1, adjust & 0xff, (adjust >> 8) & 0xff, (adjust >> 16) & 0xff, (adjust >> 24) & 0xff, 0b0000 /* tokenAns */ ]; 82 | 83 | let responseMessage = { 84 | "reference": "jan" + Date.now(), 85 | "confirmed": false, 86 | "fPort": 202, 87 | "data": Buffer.from(resp).toString('base64') 88 | }; 89 | 90 | deviceMap[m.devEUI].msgWaiting = responseMessage; 91 | 92 | deviceMap[m.devEUI].clockSynced = true; 93 | deviceMap[m.devEUI].applicationID = m.applicationID; 94 | 95 | console.log('Clock sync for device', m.devEUI, adjust, 'seconds'); 96 | 97 | if (devices.every(eui => deviceMap[eui].clockSynced)) { 98 | console.log('All devices have had their clocks synced, setting up mc group...'); 99 | setTimeout(sendMcGroupSetup, 1000); 100 | } 101 | } 102 | else { 103 | console.warn('Could not handle clock sync request', body); 104 | } 105 | } 106 | 107 | if (m.fPort === 200 /* mc group cmnds */) { 108 | let body = Buffer.from(m.data, 'base64'); 109 | if (body[0] === 0x2) { // McGroupSetupAns 110 | if (body[1] === 0x0) { 111 | deviceMap[m.devEUI].mcSetupAns = true; 112 | } 113 | else { 114 | console.warn('Unexpected answer for McGroupSetupAns from', m.devEUI, body) 115 | } 116 | 117 | if (devices.every(eui => deviceMap[eui].mcSetupAns)) { 118 | console.log('All devices have received multicast group, setting up fragsession...'); 119 | setTimeout(sendFragSessionSetup, 1000); 120 | } 121 | } 122 | else if (body[0] === 0x4) { // McClassCSessionAns 123 | if (body[1] !== 0x0) return console.warn('Unexpected byte[1] for McClassCSessionAns', m.devEUI, body); 124 | 125 | let tts = body[2] + (body[3] << 8) + (body[4] << 16); 126 | console.log(m.devEUI, 'time to start', tts, 'startTime is', startTime, 'currtime is', gpsTime.toGPSMS(Date.now()) / 1000 | 0); 127 | 128 | deviceMap[m.devEUI].mcStartAns = true; 129 | 130 | // so this app cannot properly check the delta, as we don't know when the network is gonna send 131 | // should be calculated at that very moment, so now there can be a few seconds delay 132 | let delta = (gpsTime.toGPSMS(Date.now()) / 1000 | 0) + tts - startTime; 133 | if (Math.abs(delta) > 6) { 134 | console.log('Delta is too big for', m.devEUI, Math.abs(delta)); 135 | } 136 | else { 137 | console.log('Delta is OK', m.devEUI, delta); 138 | } 139 | } 140 | else { 141 | console.warn('Could not handle Mc Group command', body); 142 | } 143 | } 144 | 145 | if (m.fPort === 201 /* frag session */) { 146 | let body = Buffer.from(m.data, 'base64'); 147 | if (body[0] === 0x2) { // FragSessionSetupAns 148 | if (body[1] === 0x0) { 149 | deviceMap[m.devEUI].fragSessionAns = true; 150 | } 151 | else { 152 | console.warn('Unexpected answer for FragSessionSetupAns from', m.devEUI, body) 153 | } 154 | 155 | if (devices.every(eui => deviceMap[eui].fragSessionAns)) { 156 | console.log('All devices have received frag session, sending mc start msg...'); 157 | setTimeout(sendMcClassCSessionReq, 1000); 158 | } 159 | } 160 | else if (body[0] === 0x5) { // DATA_BLOCK_AUTH_REQ 161 | let hash = ''; 162 | for (let ix = 5; ix > 1; ix--) { 163 | hash += body.slice(ix, ix+1).toString('hex'); 164 | } 165 | console.log('Received DATA_BLOCK_AUTH_REQ', m.devEUI, hash); 166 | } 167 | else { 168 | console.warn('Could not handle Mc Group command', body); 169 | } 170 | } 171 | 172 | if (deviceMap[m.devEUI].msgWaiting) { 173 | let msgWaiting = deviceMap[m.devEUI].msgWaiting; 174 | client.publish(`application/${m.applicationID}/device/${m.devEUI}/tx`, Buffer.from(JSON.stringify(msgWaiting), 'utf8')); 175 | deviceMap[m.devEUI].msgWaiting = null; 176 | } 177 | }); 178 | 179 | function sendMcGroupSetup() { 180 | if (classCStarted) return; 181 | 182 | console.log('sendMcGroupSetup'); 183 | // mcgroupsetup 184 | let mcGroupSetup = { 185 | "reference": "jan" + Date.now(), 186 | "confirmed": false, 187 | "fPort": 200, 188 | "data": Buffer.from([ 0x02, 0x00, 189 | 0xFF, 0xFF, 0xFF, 0x01, // McAddr 190 | 0x01, 0x5E, 0x85, 0xF4, 0xB9, 0x9D, 0xC0, 0xB9, 0x44, 0x06, 0x6C, 0xD0, 0x74, 0x98, 0x33, 0x0B, //McKey_encrypted 191 | 0x0, 0x0, 0x0, 0x0, // minFCnt 192 | 0xff, 0x0, 0x0, 0x0 // maxFCnt 193 | ]).toString('base64') 194 | }; 195 | 196 | devices.forEach(eui => { 197 | let dm = deviceMap[eui]; 198 | if (dm.mcSetupAns) return; 199 | 200 | dm.msgWaiting = mcGroupSetup; 201 | }); 202 | 203 | // retry 204 | setTimeout(() => { 205 | if (devices.some(eui => !deviceMap[eui].mcSetupAns)) { 206 | sendMcGroupSetup(); 207 | } 208 | }, 20000); 209 | } 210 | 211 | function sendFragSessionSetup() { 212 | if (classCStarted) return; 213 | 214 | console.log('sendFragSessionSetup'); 215 | let msg = { 216 | "reference": "jan" + Date.now(), 217 | "confirmed": false, 218 | "fPort": 201, 219 | "data": Buffer.from(parsePackets()[0]).toString('base64') 220 | }; 221 | 222 | devices.forEach(eui => { 223 | let dm = deviceMap[eui]; 224 | if (dm.fragSessionAns) return; 225 | 226 | dm.msgWaiting = msg; 227 | }); 228 | 229 | // retry 230 | setTimeout(() => { 231 | if (devices.some(eui => !deviceMap[eui].fragSessionAns)) { 232 | sendFragSessionSetup(); 233 | } 234 | }, 20000); 235 | } 236 | 237 | function sendMcClassCSessionReq() { 238 | if (classCStarted) return; 239 | 240 | console.log('sendMcClassCSessionReq'); 241 | 242 | if (!startTime) { 243 | let serverTime = gpsTime.toGPSMS(Date.now()) / 1000 | 0; 244 | startTime = serverTime + CLASS_C_WAIT_S; // 60 seconds from now 245 | 246 | setTimeout(() => { 247 | startSendingClassCPackets(); 248 | }, (CLASS_C_WAIT_S + 10) * 1000); // because the delta drift that we don't know (see above) 249 | } 250 | 251 | let msg = { 252 | "reference": "jan" + Date.now(), 253 | "confirmed": false, 254 | "fPort": 200, 255 | "data": Buffer.from([ 256 | 0x4, 257 | 0x0, // mcgroupidheader 258 | startTime & 0xff, (startTime >> 8) & 0xff, (startTime >> 16) & 0xff, (startTime >> 24) & 0xff, 259 | 0x07, // session timeout 260 | 0xd2, 0xad, 0x84, // dlfreq 261 | DATARATE // dr 262 | ]).toString('base64') 263 | }; 264 | 265 | devices.forEach(eui => { 266 | let dm = deviceMap[eui]; 267 | if (dm.mcStartAns) return; 268 | 269 | dm.msgWaiting = msg; 270 | }); 271 | 272 | // retry 273 | setTimeout(() => { 274 | if (devices.some(eui => !deviceMap[eui].mcStartAns)) { 275 | sendMcClassCSessionReq(); 276 | } 277 | }, 20000); 278 | } 279 | 280 | function sleep(ms) { 281 | return new Promise((res, rej) => setTimeout(res, ms)); 282 | } 283 | 284 | function parsePackets() { 285 | let packets = fs.readFileSync(PACKET_FILE, 'utf-8').split('\n').map(row => { 286 | return row.split(' ').map(c=>parseInt(c, 16)) 287 | }); 288 | return packets; 289 | } 290 | 291 | async function startSendingClassCPackets() { 292 | classCStarted = true; 293 | console.log('startSendingClassCPackets'); 294 | console.log('All devices ready?', deviceMap); 295 | 296 | let packets = parsePackets(); 297 | 298 | let counter = 0; 299 | 300 | for (let p of packets) { 301 | // first row is header, don't use that one 302 | if (counter === 0) { 303 | counter++; 304 | continue; 305 | } 306 | 307 | let msg = { 308 | "reference": "jan" + Date.now(), 309 | "confirmed": false, 310 | "fPort": 201, 311 | "data": Buffer.from(p).toString('base64') 312 | }; 313 | 314 | client.publish(`application/${mcDetails.applicationID}/device/${mcDetails.devEUI}/tx`, Buffer.from(JSON.stringify(msg), 'utf8')); 315 | 316 | console.log('Sent packet', ++counter); 317 | 318 | await sleep(2200); // tpacket on SF12 is 2100 ms. so this should just work (although loraserver doesn't think it's a problem to send faster) 319 | } 320 | 321 | console.log('Done sending all packets'); 322 | } 323 | 324 | client.on('error', err => console.error('Error on MQTT subscriber', err)); 325 | -------------------------------------------------------------------------------- /fuota-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lorawan-fuota-server", 3 | "version": "1.0.0", 4 | "description": "Experimental FUOTA server for loraserver.io", 5 | "main": "loraserver.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/armmbed/mbed-os-example-lorawan-fuota.git" 12 | }, 13 | "keywords": [ 14 | "lorawan", 15 | "mbed" 16 | ], 17 | "author": "Jan Jongboom ", 18 | "license": "Apache-2.0", 19 | "bugs": { 20 | "url": "https://github.com/armmbed/mbed-os-example-lorawan-fuota/issues" 21 | }, 22 | "homepage": "https://github.com/armmbed/mbed-os-example-lorawan-fuota#readme", 23 | "dependencies": { 24 | "gps-time": "^1.0.3", 25 | "mqtt": "^2.18.8", 26 | "request": "^2.88.0", 27 | "request-promise": "^4.2.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /fuota-server/test-file.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/mbed-os-example-lorawan-fuota/2fb3350a388369e5b2ade2976f3497df5355acda/fuota-server/test-file.bin -------------------------------------------------------------------------------- /mbed-lora-radio-drv.lib: -------------------------------------------------------------------------------- 1 | https://github.com/ARMmbed/mbed-semtech-lora-rf-drivers/#16958f814d505cfbbedfa16d9bf8b9dff0e0442b 2 | -------------------------------------------------------------------------------- /mbed-lorawan-update-client.lib: -------------------------------------------------------------------------------- 1 | https://github.com/janjongboom/mbed-lorawan-update-client/#da8bf497eeee02053265cd56c6636a28c819eb32 2 | -------------------------------------------------------------------------------- /mbed-os.lib: -------------------------------------------------------------------------------- 1 | https://github.com/janjongboom/mbed-os/#e834950dd9b8a4ac3b02bcd99a5a134fa94131a1 2 | -------------------------------------------------------------------------------- /mbed-printf.lib: -------------------------------------------------------------------------------- 1 | https://github.com/ARMmbed/mbed-printf/#5cbf9d1dcb5812a8a804a55162b3118523a4eb77 2 | -------------------------------------------------------------------------------- /mbed_app.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "activity-led": { 4 | "help": "Which LED to blink on activity, on STM targets do NOT use LED1 as it interferes with the LoRa radio", 5 | "macro_name": "ACTIVITY_LED", 6 | "value": "LED1" 7 | }, 8 | "lora-radio": { 9 | "help": "Which radio to use (options: SX1272,SX1276)", 10 | "value": "SX1276" 11 | }, 12 | "sotp-section-1-address": { 13 | "help": "Flash sector address for SOTP sector 1", 14 | "macro_name": "PAL_INTERNAL_FLASH_SECTION_1_ADDRESS", 15 | "value": null 16 | }, 17 | "sotp-section-1-size": { 18 | "help": "Flash sector size for SOTP sector 1", 19 | "macro_name": "PAL_INTERNAL_FLASH_SECTION_1_SIZE", 20 | "value": null 21 | }, 22 | "sotp-section-2-address": { 23 | "help": "Flash sector address for SOTP sector 2", 24 | "macro_name": "PAL_INTERNAL_FLASH_SECTION_2_ADDRESS", 25 | "value": null 26 | }, 27 | "sotp-section-2-size": { 28 | "help": "Flash sector size for SOTP sector 2", 29 | "macro_name": "PAL_INTERNAL_FLASH_SECTION_2_SIZE", 30 | "value": null 31 | }, 32 | "fragmentation-bootloader-header-offset": { 33 | "help": "Address in external flash where to store the header for the bootloader, needs to be erase & write sector aligned", 34 | "value": "0" 35 | }, 36 | "fragmentation-storage-offset": { 37 | "help": "Address in external flash where to start storing fragments, needs to be erase & write sector aligned", 38 | "value": "0x210" 39 | }, 40 | "update-client-application-details": { 41 | "help": "Location in *internal* flash to store application details (used by the combine script)", 42 | "value": "0x0" 43 | }, 44 | "flash-start-address": { 45 | "help": "Start address of internal flash", 46 | "value": null 47 | }, 48 | 49 | "main_stack_size": { "value": 4096 }, 50 | 51 | "lora-spi-mosi": { "value": "NC" }, 52 | "lora-spi-miso": { "value": "NC" }, 53 | "lora-spi-sclk": { "value": "NC" }, 54 | "lora-cs": { "value": "NC" }, 55 | "lora-reset": { "value": "NC" }, 56 | "lora-dio0": { "value": "NC" }, 57 | "lora-dio1": { "value": "NC" }, 58 | "lora-dio2": { "value": "NC" }, 59 | "lora-dio3": { "value": "NC" }, 60 | "lora-dio4": { "value": "NC" }, 61 | "lora-dio5": { "value": "NC" }, 62 | "lora-rf-switch-ctl1": { "value": "NC" }, 63 | "lora-rf-switch-ctl2": { "value": "NC" }, 64 | "lora-txctl": { "value": "NC" }, 65 | "lora-rxctl": { "value": "NC" }, 66 | "lora-ant-switch": { "value": "NC" }, 67 | "lora-pwr-amp-ctl": { "value": "NC" }, 68 | "lora-tcxo": { "value": "NC" } 69 | }, 70 | "target_overrides": { 71 | "*": { 72 | "platform.stdio-convert-newlines": true, 73 | "platform.stdio-baud-rate": 115200, 74 | "mbed-trace.enable": 1, 75 | "lora.over-the-air-activation": true, 76 | "lora.duty-cycle-on": true, 77 | "lora.phy": "EU868" 78 | }, 79 | 80 | "FF1705_L151CC": { 81 | "target.features_add": ["BOOTLOADER"], 82 | 83 | "lora-radio": "SX1272", 84 | "lora-spi-mosi": "LORA_MOSI", 85 | "lora-spi-miso": "LORA_MISO", 86 | "lora-spi-sclk": "LORA_SCK", 87 | "lora-cs": "LORA_NSS", 88 | "lora-reset": "LORA_RESET", 89 | "lora-dio0": "LORA_DIO0", 90 | "lora-dio1": "LORA_DIO1", 91 | "lora-dio2": "LORA_DIO2", 92 | "lora-dio3": "LORA_DIO3", 93 | "lora-dio4": "LORA_DIO4", 94 | "lora-dio5": "NC", 95 | "lora-rf-switch-ctl1": "NC", 96 | "lora-rf-switch-ctl2": "NC", 97 | "lora-txctl": "NC", 98 | "lora-rxctl": "NC", 99 | "lora-ant-switch": "NC", 100 | "lora-pwr-amp-ctl": "NC", 101 | "lora-tcxo": "NC", 102 | 103 | "lorawan-update-client.max-redundancy" : "40", 104 | "lorawan-update-client.slot-size" : "(256*1024 + 272)", 105 | "lorawan-update-client.slot0-header-address": "0x210", 106 | "lorawan-update-client.slot0-fw-address" : "(0x210 + 0x210)", 107 | "lorawan-update-client.slot1-header-address": "0x40320", 108 | "lorawan-update-client.slot1-fw-address" : "(0x40320 + 0x210)", 109 | "lorawan-update-client.slot2-header-address": "0x80430", 110 | "lorawan-update-client.slot2-fw-address" : "(0x80430 + 72)", 111 | "lorawan-update-client.internal-flash-header": "0x08008000", 112 | "lorawan-update-client.interop-testing" : false, 113 | "lorawan-update-client.overwrite-version" : false, 114 | "target.app_offset" : "0x8400", 115 | "target.header_offset" : "0x8000", 116 | "target.bootloader_img" : "bootloader/FF1705_L151CC.bin" 117 | }, 118 | "DISCO_L475VG_IOT01A": { 119 | "target.features_add" : ["BOOTLOADER"], 120 | "target.components_add" : ["QSPIF"], 121 | 122 | "activity-led": "NC", 123 | "lora-radio": "SX1276", 124 | "lora-spi-mosi": "D11", 125 | "lora-spi-miso": "D12", 126 | "lora-spi-sclk": "D13", 127 | "lora-cs": "D10", 128 | "lora-reset": "A0", 129 | "lora-dio0": "D2", 130 | "lora-dio1": "D3", 131 | "lora-dio2": "D4", 132 | "lora-dio3": "D5", 133 | "lora-dio4": "D8", 134 | "lora-dio5": "D9", 135 | "lora-rf-switch-ctl1": "NC", 136 | "lora-rf-switch-ctl2": "NC", 137 | "lora-txctl": "NC", 138 | "lora-rxctl": "NC", 139 | "lora-ant-switch": "A4", 140 | "lora-pwr-amp-ctl": "NC", 141 | "lora-tcxo": "NC", 142 | 143 | "lorawan-update-client.max-redundancy" : "40", 144 | "lorawan-update-client.slot-size" : "0x10000", 145 | "lorawan-update-client.slot0-header-address": "0x1000", 146 | "lorawan-update-client.slot0-fw-address" : "(0x1000 + 296)", 147 | "lorawan-update-client.slot1-header-address": "0x101000", 148 | "lorawan-update-client.slot1-fw-address" : "(0x101000 + 296)", 149 | "lorawan-update-client.slot2-header-address": "0x201000", 150 | "lorawan-update-client.slot2-fw-address" : "(0x201000 + 72)", 151 | "lorawan-update-client.internal-flash-header": "(0x08000000 + (34 * 1024))", 152 | "lorawan-update-client.overwrite-version" : false, 153 | "target.app_offset" : "0x9000", 154 | "target.header_offset" : "0x8800", 155 | "target.bootloader_img" : "bootloader/DISCO_L475VG_IOT01A.bin" 156 | }, 157 | "SIMULATOR": { 158 | "lorawan-update-client.max-redundancy" : "40", 159 | "lorawan-update-client.slot-size" : "528 * 100", 160 | "lorawan-update-client.slot0-header-address": "0x0", 161 | "lorawan-update-client.slot0-fw-address" : "528", 162 | "lorawan-update-client.slot1-header-address": "528*100", 163 | "lorawan-update-client.slot1-fw-address" : "528*101", 164 | "lorawan-update-client.slot2-header-address": "528*200", 165 | "lorawan-update-client.slot2-fw-address" : "528*201", 166 | "lorawan-update-client.internal-flash-header": "0x0", 167 | "lorawan-update-client.interop-testing" : false, 168 | "flash-start-address" : "0x0" 169 | } 170 | }, 171 | "macros": [ 172 | "MBED_HEAP_STATS_ENABLED=1", 173 | "MBED_STACK_STATS_ENABLED=1", 174 | "JANPATCH_STREAM=BDFILE", 175 | "MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES", 176 | "MBEDTLS_CONFIG_FILE=\"fotalora_mbedtls_config.h\"" 177 | ] 178 | } 179 | -------------------------------------------------------------------------------- /profiles/tiny.json: -------------------------------------------------------------------------------- 1 | { 2 | "GCC_ARM": { 3 | "common": ["-c", "-Wall", "-Wextra", 4 | "-Wno-unused-parameter", "-Wno-missing-field-initializers", 5 | "-fmessage-length=0", "-fno-exceptions", "-fno-builtin", 6 | "-ffunction-sections", "-fdata-sections", "-funsigned-char", 7 | "-MMD", "-fno-delete-null-pointer-checks", 8 | "-fomit-frame-pointer", "-Os", "-DNDEBUG", "-g"], 9 | "asm": ["-x", "assembler-with-cpp"], 10 | "c": ["-std=gnu99"], 11 | "cxx": ["-std=gnu++98", "-fno-rtti", "-Wvla"], 12 | "ld": ["-Wl,--gc-sections", "-Wl,--wrap,main", "-Wl,--wrap,_malloc_r", 13 | "-Wl,--wrap,_free_r", "-Wl,--wrap,_realloc_r", 14 | "-Wl,--wrap,_calloc_r", "-Wl,--wrap,exit", "-Wl,--wrap,atexit", 15 | "-Wl,-n", "-Wl,--wrap,printf", "-Wl,--wrap,snprintf", 16 | "-Wl,--wrap,sprintf", "-Wl,--wrap,vsnprintf", "-Wl,--wrap,vprintf", 17 | "-specs=nano.specs"] 18 | }, 19 | "ARM": { 20 | "common": ["-c", "--gnu", "-Ospace", "--split_sections", 21 | "--apcs=interwork", "--brief_diagnostics", "--restrict", 22 | "--multibyte_chars", "-O3", "-DNDEBUG"], 23 | "asm": [], 24 | "c": ["--md", "--no_depend_system_headers", "--c99", "-D__ASSERT_MSG"], 25 | "cxx": ["--cpp", "--no_rtti", "--no_vla"], 26 | "ld": [] 27 | }, 28 | "uARM": { 29 | "common": ["-c", "--gnu", "-Ospace", "--split_sections", 30 | "--apcs=interwork", "--brief_diagnostics", "--restrict", 31 | "--multibyte_chars", "-O3", "-D__MICROLIB", 32 | "--library_type=microlib", "-DMBED_RTOS_SINGLE_THREAD", "-DNDEBUG"], 33 | "asm": [], 34 | "c": ["--md", "--no_depend_system_headers", "--c99", "-D__ASSERT_MSG"], 35 | "cxx": ["--cpp", "--no_rtti", "--no_vla"], 36 | "ld": ["--library_type=microlib"] 37 | }, 38 | "IAR": { 39 | "common": [ 40 | "--no_wrap_diagnostics", "-e", 41 | "--diag_suppress=Pa050,Pa084,Pa093,Pa082", "-Ohz", "-DNDEBUG", "--enable_restrict"], 42 | "asm": [], 43 | "c": ["--vla"], 44 | "cxx": ["--guard_calls", "--no_static_destruction"], 45 | "ld": ["--skip_dynamic_initialization", "--threaded_lib"] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /simconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "./at45-blockdevice", 4 | "./mbed-lorawan-update-client/update-client-hub-common/", 5 | "./mbed-lorawan-update-client/janpatch/cli/", 6 | "./mbed-lora-radio-drv/", 7 | "./mbed-printf", 8 | "./source/example_insecure_rot.c", 9 | "./test-fw/" 10 | ], 11 | "disableTlsNullEntropy": true 12 | } 13 | -------------------------------------------------------------------------------- /source/example_insecure_rot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2018 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #if !defined(ARM_UC_USE_SOTP) || ARM_UC_USE_SOTP == 0 19 | 20 | #include 21 | #include 22 | 23 | #define DEVICE_KEY_SIZE_IN_BYTES (128/8) 24 | 25 | /** 26 | * @brief Function to get the device root of trust 27 | * @details The device root of trust should be a 128 bit value. It should never leave the device. 28 | * It should be unique to the device. It should have enough entropy to avoid contentional 29 | * entropy attacks. The porter should implement the following device signature to provide 30 | * device root of trust on different platforms. 31 | * 32 | * @param key_buf buffer to be filled with the device root of trust. 33 | * @param length length of the buffer provided to make sure no overflow occurs. 34 | * 35 | * @return 0 on success, non-zero on failure. 36 | */ 37 | 38 | // THIS CODE IS FOR TESTING PURPOSES ONLY. DO NOT USE IN PRODUCTION ENVIRONMENTS. REPLACE WITH A PROPER IMPLEMENTATION BEFORE USE 39 | int8_t mbed_cloud_client_get_rot_128bit(uint8_t *key_buf, uint32_t length) 40 | { 41 | #warning "You are using insecure Root Of Trust implementation, DO NOT USE IN PRODUCTION ENVIRONMENTS. REPLACE WITH A PROPER IMPLEMENTATION BEFORE USE" 42 | 43 | if (length < DEVICE_KEY_SIZE_IN_BYTES || key_buf == NULL) 44 | { 45 | return -1; 46 | } 47 | 48 | for (uint8_t i = 0; i < DEVICE_KEY_SIZE_IN_BYTES; i++) 49 | { 50 | key_buf[i] = i; 51 | } 52 | 53 | return 0; 54 | } 55 | 56 | #endif // #if !defined(ARM_UC_USE_SOTP) || ARM_UC_USE_SOTP == 0 57 | -------------------------------------------------------------------------------- /source/fotalora_mbedtls_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2018 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef FOTALORA_MBEDTLS_CONFIG_H 19 | 20 | #include "platform/inc/platform_mbed.h" 21 | 22 | #define FOTALORA_MBEDTLS_CONFIG_H 23 | 24 | #define MBEDTLS_HAVE_ASM 25 | #define MBEDTLS_HAVE_TIME 26 | 27 | #define MBEDTLS_REMOVE_ARC4_CIPHERSUITES 28 | #define MBEDTLS_ECP_DP_SECP256R1_ENABLED 29 | #define MBEDTLS_ECDSA_DETERMINISTIC 30 | #define MBEDTLS_NO_PLATFORM_ENTROPY 31 | #define MBEDTLS_AES_FEWER_TABLES 32 | 33 | #define MBEDTLS_AES_C 34 | #define MBEDTLS_ASN1_PARSE_C 35 | #define MBEDTLS_ASN1_WRITE_C 36 | #define MBEDTLS_BASE64_C 37 | #define MBEDTLS_BIGNUM_C 38 | #define MBEDTLS_CIPHER_C 39 | #define MBEDTLS_CMAC_C 40 | #define MBEDTLS_ECDSA_C 41 | #define MBEDTLS_ECP_C 42 | #define MBEDTLS_ERROR_C 43 | #define MBEDTLS_HMAC_DRBG_C 44 | #define MBEDTLS_MD_C 45 | #define MBEDTLS_OID_C 46 | #define MBEDTLS_PEM_PARSE_C 47 | #define MBEDTLS_PK_C 48 | #define MBEDTLS_PK_PARSE_C 49 | #define MBEDTLS_PLATFORM_C 50 | #define MBEDTLS_SHA256_C 51 | 52 | #undef MBEDTLS_GCM_C 53 | #undef MBEDTLS_CHACHA20_C 54 | #undef MBEDTLS_CHACHAPOLY_C 55 | #undef MBEDTLS_POLY1305_C 56 | 57 | #include "check_config.h" 58 | 59 | #endif /* FOTALORA_MBEDTLS_CONFIG_H */ 60 | -------------------------------------------------------------------------------- /source/helpers/dev_eui_helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2018 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _DEV_EUI_HELPER_H 19 | #define _DEV_EUI_HELPER_H 20 | 21 | #include "mbed.h" 22 | 23 | /** 24 | * This file reads DevEUI from ROM/RAM/Flash if the target has one. 25 | * 26 | * @param buffer 8 byte buffer to store the DevEUI in 27 | * if the target does not have a built-in DevEUI this buffer is not touched 28 | * @param size Size of the buffer 29 | * @returns 0 if successful, -1 if no DevEUI could be read, -2 if the buffer size was not correct 30 | */ 31 | 32 | #if defined(TARGET_FF1705_L151CC) || defined(TARGET_XDOT_L151CC) 33 | #include "xdot_eeprom.h" 34 | 35 | int8_t get_built_in_dev_eui(uint8_t *buffer, size_t size) { 36 | if (size != 8) return -2; 37 | 38 | int v = xdot_eeprom_read_buf(0x401, buffer, size); 39 | if (v != 0) { 40 | return -1; 41 | } 42 | 43 | return 0; 44 | } 45 | 46 | #else 47 | int8_t get_built_in_dev_eui(uint8_t *, size_t) { 48 | return -1; 49 | } 50 | #endif 51 | 52 | #endif // _DEV_EUI_HELPER_H 53 | -------------------------------------------------------------------------------- /source/helpers/lora_radio_helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2018 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "lorawan/LoRaRadio.h" 19 | 20 | #ifndef APP_LORA_RADIO_HELPER_H_ 21 | #define APP_LORA_RADIO_HELPER_H_ 22 | 23 | #include "SX1272_LoRaRadio.h" 24 | #include "SX1276_LoRaRadio.h" 25 | 26 | #define SX1272 0xFF 27 | #define SX1276 0xEE 28 | 29 | #if (MBED_CONF_APP_LORA_RADIO == SX1272) 30 | 31 | SX1272_LoRaRadio radio(MBED_CONF_APP_LORA_SPI_MOSI, 32 | MBED_CONF_APP_LORA_SPI_MISO, 33 | MBED_CONF_APP_LORA_SPI_SCLK, 34 | MBED_CONF_APP_LORA_CS, 35 | MBED_CONF_APP_LORA_RESET, 36 | MBED_CONF_APP_LORA_DIO0, 37 | MBED_CONF_APP_LORA_DIO1, 38 | MBED_CONF_APP_LORA_DIO2, 39 | MBED_CONF_APP_LORA_DIO3, 40 | MBED_CONF_APP_LORA_DIO4, 41 | MBED_CONF_APP_LORA_DIO5, 42 | MBED_CONF_APP_LORA_RF_SWITCH_CTL1, 43 | MBED_CONF_APP_LORA_RF_SWITCH_CTL2, 44 | MBED_CONF_APP_LORA_TXCTL, 45 | MBED_CONF_APP_LORA_RXCTL, 46 | MBED_CONF_APP_LORA_ANT_SWITCH, 47 | MBED_CONF_APP_LORA_PWR_AMP_CTL, 48 | MBED_CONF_APP_LORA_TCXO); 49 | 50 | #elif (MBED_CONF_APP_LORA_RADIO == SX1276) 51 | 52 | SX1276_LoRaRadio radio(MBED_CONF_APP_LORA_SPI_MOSI, 53 | MBED_CONF_APP_LORA_SPI_MISO, 54 | MBED_CONF_APP_LORA_SPI_SCLK, 55 | MBED_CONF_APP_LORA_CS, 56 | MBED_CONF_APP_LORA_RESET, 57 | MBED_CONF_APP_LORA_DIO0, 58 | MBED_CONF_APP_LORA_DIO1, 59 | MBED_CONF_APP_LORA_DIO2, 60 | MBED_CONF_APP_LORA_DIO3, 61 | MBED_CONF_APP_LORA_DIO4, 62 | MBED_CONF_APP_LORA_DIO5, 63 | MBED_CONF_APP_LORA_RF_SWITCH_CTL1, 64 | MBED_CONF_APP_LORA_RF_SWITCH_CTL2, 65 | MBED_CONF_APP_LORA_TXCTL, 66 | MBED_CONF_APP_LORA_RXCTL, 67 | MBED_CONF_APP_LORA_ANT_SWITCH, 68 | MBED_CONF_APP_LORA_PWR_AMP_CTL, 69 | MBED_CONF_APP_LORA_TCXO); 70 | 71 | #else 72 | #error "Unknown LoRa radio specified (SX1272,SX1276 are valid)" 73 | #endif 74 | 75 | #endif /* APP_LORA_RADIO_HELPER_H_ */ 76 | -------------------------------------------------------------------------------- /source/helpers/memory_helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2018 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _MEMORY_HELPER_H 19 | #define _MEMORY_HELPER_H 20 | 21 | #include "mbed.h" 22 | #include "mbed_mem_trace.h" 23 | 24 | void print_memory_info() { 25 | #if MBED_CONF_RTOS_PRESENT 26 | // allocate enough room for every thread's stack statistics 27 | int cnt = osThreadGetCount(); 28 | mbed_stats_stack_t *stats = (mbed_stats_stack_t*) malloc(cnt * sizeof(mbed_stats_stack_t)); 29 | 30 | cnt = mbed_stats_stack_get_each(stats, cnt); 31 | for (int i = 0; i < cnt; i++) { 32 | printf("Thread: 0x%lX, Stack size: %lu / %lu\r\n", stats[i].thread_id, stats[i].max_size, stats[i].reserved_size); 33 | } 34 | free(stats); 35 | #endif 36 | 37 | // Grab the heap statistics 38 | mbed_stats_heap_t heap_stats; 39 | mbed_stats_heap_get(&heap_stats); 40 | printf("Heap size: %lu / %lu bytes (max: %lu bytes)\r\n", heap_stats.current_size, heap_stats.reserved_size, heap_stats.max_size); 41 | } 42 | 43 | #endif // _MEMORY_HELPER_H 44 | -------------------------------------------------------------------------------- /source/helpers/storage_helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2018 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _LORAWAN_FUOTA_STORAGE_HELPER_H 19 | #define _LORAWAN_FUOTA_STORAGE_HELPER_H 20 | 21 | #include "mbed.h" 22 | 23 | #if defined(TARGET_SIMULATOR) 24 | // Initialize a persistent block device with 528 bytes block size, and 256 blocks (mimicks the at45, which also has 528 size blocks) 25 | #include "SimulatorBlockDevice.h" 26 | SimulatorBlockDevice bd("lorawan-frag-in-flash", 256 * 528, static_cast(528)); 27 | #elif defined(TARGET_FF1705_L151CC) 28 | // Flash interface on the L-TEK xDot shield 29 | #include "AT45BlockDevice.h" 30 | AT45BlockDevice bd(SPI_MOSI, SPI_MISO, SPI_SCK, SPI_NSS); 31 | #elif defined(COMPONENT_QSPIF) 32 | // QSPI Flash interface 33 | #include "QSPIFBlockDevice.h" 34 | QSPIFBlockDevice bd(QSPI_FLASH1_IO0, QSPI_FLASH1_IO1, QSPI_FLASH1_IO2, QSPI_FLASH1_IO3, QSPI_FLASH1_SCK, QSPI_FLASH1_CSN, QSPIF_POLARITY_MODE_0, MBED_CONF_QSPIF_QSPI_FREQ); 35 | #endif 36 | 37 | #endif // _LORAWAN_FUOTA_STORAGE_HELPER_H 38 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2018 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "mbed.h" 19 | #include "mbed_trace.h" 20 | #include "LoRaWANInterface.h" 21 | #include "lora_radio_helper.h" 22 | #include "dev_eui_helper.h" 23 | #include "storage_helper.h" 24 | #include "UpdateCerts.h" 25 | #include "LoRaWANUpdateClient.h" 26 | 27 | EventQueue evqueue; 28 | 29 | // Note: if the device has built-in dev eui (see dev_eui_helper.h), the dev eui will be overwritten in main() 30 | static uint8_t DEV_EUI[] = { 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x39, 0x94 }; 31 | static uint8_t APP_EUI[] = { 0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x00, 0xC1, 0x84 }; 32 | static uint8_t APP_KEY[] = { 0xB8, 0xB4, 0x33, 0x0D, 0xFD, 0xD5, 0xD8, 0x61, 0xE7, 0x37, 0xA6, 0xC9, 0x5E, 0x5F, 0xD3, 0xF0 }; 33 | 34 | static void lora_event_handler(lorawan_event_t event); 35 | static void lora_uc_send(LoRaWANUpdateClientSendParams_t ¶ms); 36 | static void queue_next_send_message(); 37 | static void send_message(); 38 | 39 | static LoRaWANInterface lorawan(radio); 40 | static lorawan_app_callbacks_t callbacks; 41 | static LoRaWANUpdateClient uc(&bd, APP_KEY, lora_uc_send); 42 | static loramac_protocol_params class_a_params; // @todo: this is 816 bytes, can we use a smaller structure? 43 | static LoRaWANUpdateClientClassCSession_t class_c_details; 44 | static bool in_class_c_mode = false; 45 | static bool clock_is_synced = false; 46 | static LoRaWANUpdateClientSendParams_t queued_message; 47 | static bool queued_message_waiting = false; 48 | 49 | static DigitalOut led1(ACTIVITY_LED); 50 | 51 | static void turn_led_on() { 52 | led1 = 1; 53 | } 54 | static void turn_led_off() { 55 | led1 = 0; 56 | } 57 | 58 | // This is already debounced to the eventqueue, so safe to run printf here 59 | static void switch_to_class_a() { 60 | printf("Switch to Class A\n"); 61 | turn_led_off(); 62 | uc.printHeapStats("CLASSA "); 63 | 64 | in_class_c_mode = false; 65 | 66 | // put back the class A session 67 | lorawan.set_session(&class_a_params); 68 | lorawan.enable_adaptive_datarate(); 69 | lorawan.set_device_class(CLASS_A); 70 | 71 | // wait for a few seconds to send the message 72 | evqueue.call_in(5000, &send_message); 73 | } 74 | 75 | static void switch_class_c_rx2_params() { 76 | loramac_protocol_params class_c_params; 77 | 78 | // copy them to the class C params... 79 | memcpy(&class_c_params, &class_a_params, sizeof(loramac_protocol_params)); 80 | class_c_params.dl_frame_counter = 0; 81 | class_c_params.ul_frame_counter = 0; 82 | class_c_params.dev_addr = class_c_details.deviceAddr; 83 | memcpy(class_c_params.keys.nwk_skey, class_c_details.nwkSKey, 16); 84 | memcpy(class_c_params.keys.app_skey, class_c_details.appSKey, 16); 85 | 86 | class_c_params.sys_params.rx2_channel.frequency = class_c_details.downlinkFreq; 87 | class_c_params.sys_params.rx2_channel.datarate = class_c_details.datarate; 88 | 89 | // and set the class C session 90 | lorawan.set_session(&class_c_params); 91 | lorawan.set_device_class(CLASS_C); 92 | } 93 | 94 | static void switch_to_class_c() { 95 | printf("Switch to Class C\n"); 96 | turn_led_on(); 97 | 98 | lorawan.cancel_sending(); 99 | lorawan.disable_adaptive_datarate(); 100 | 101 | if (queued_message_waiting) { 102 | queued_message_waiting = false; 103 | free(queued_message.data); 104 | } 105 | 106 | in_class_c_mode = true; 107 | 108 | // store the class A parameters 109 | lorawan.get_session(&class_a_params); 110 | 111 | // in 1.5 second, actually switch to Class C (allow clearing the queue in the LoRaWAN stack) 112 | evqueue.call_in(1500, &switch_class_c_rx2_params); 113 | } 114 | 115 | // This runs in an interrupt routine, so just copy the parameter and dispatch to event queue 116 | static void switch_to_class_c_irq(LoRaWANUpdateClientClassCSession_t* session) { 117 | core_util_critical_section_enter(); 118 | memcpy(&class_c_details, session, sizeof(LoRaWANUpdateClientClassCSession_t)); 119 | core_util_critical_section_exit(); 120 | 121 | evqueue.call(&switch_to_class_c); 122 | } 123 | 124 | static void lorawan_uc_fragsession_complete() { 125 | printf("Frag session is complete\n"); 126 | } 127 | 128 | #if MBED_CONF_LORAWAN_UPDATE_CLIENT_INTEROP_TESTING 129 | uint32_t interop_crc32 = 0x0; 130 | static void lorawan_uc_firmware_ready(uint32_t crc) { 131 | uc.printHeapStats("FWREADY "); 132 | printf("Firmware is ready, CRC32 hash is %08lx\n", crc); 133 | interop_crc32 = crc; 134 | } 135 | #else 136 | static void lorawan_uc_firmware_ready() { 137 | uc.printHeapStats("FWREADY "); 138 | printf("Firmware is ready, hit **RESET** to flash the firmware\n"); 139 | 140 | // reboot system 141 | NVIC_SystemReset(); 142 | } 143 | #endif 144 | 145 | static void lora_uc_send(LoRaWANUpdateClientSendParams_t ¶ms) { 146 | queued_message = params; 147 | // copy the buffer 148 | queued_message.data = (uint8_t*)malloc(params.length); 149 | if (!queued_message.data) { 150 | printf("ERR! Failed to allocate %u bytes for queued_message!\n", params.length); 151 | return; 152 | } 153 | memcpy(queued_message.data, params.data, params.length); 154 | queued_message_waiting = true; 155 | 156 | // will be sent in the next iteration 157 | } 158 | 159 | // Send a message over LoRaWAN - todo, check for duty cycle 160 | static void send_message() { 161 | if (in_class_c_mode) return; 162 | 163 | #if MBED_CONF_LORAWAN_UPDATE_CLIENT_INTEROP_TESTING 164 | // after calculating the crc32, that's the only thing we'll send 165 | if (interop_crc32 != 0x0) { 166 | uint8_t buffer[6] = { 167 | DATA_BLOCK_AUTH_REQ, 0 /* fragIndex, always 0 */, 168 | interop_crc32 & 0xff, interop_crc32 >> 8 & 0xff, interop_crc32 >> 16 & 0xff, interop_crc32 >> 24 & 0xff 169 | }; 170 | int16_t retcode = lorawan.send(201, buffer, sizeof(buffer), MSG_UNCONFIRMED_FLAG); 171 | if (retcode < 0) { 172 | printf("send_message for DATA_BLOCK_AUTH_REQ on port %d failed (%d)\n", 201, retcode); 173 | queue_next_send_message(); 174 | } 175 | else { 176 | printf("%d bytes scheduled for transmission on port %d\n", sizeof(buffer), 201); 177 | } 178 | return; 179 | } 180 | #endif 181 | 182 | // @todo: implement retries allowed 183 | if (queued_message_waiting) { 184 | // detect if this is class c session start message 185 | // because if so, we should change the timeToStart to the current moment as we don't send immediately 186 | if (queued_message.port == MCCONTROL_PORT && queued_message.length == MC_CLASSC_SESSION_ANS_LENGTH 187 | && queued_message.data[0] == MC_CLASSC_SESSION_ANS) { 188 | uc.updateClassCSessionAns(&queued_message); 189 | } 190 | 191 | int16_t retcode = lorawan.send( 192 | queued_message.port, 193 | queued_message.data, 194 | queued_message.length, 195 | queued_message.confirmed ? MSG_CONFIRMED_FLAG : MSG_UNCONFIRMED_FLAG); 196 | 197 | if (retcode < 0) { 198 | printf("send_message for queued_message on port %d failed (%d)\n", queued_message.port, retcode); 199 | queue_next_send_message(); 200 | } 201 | else { 202 | free(queued_message.data); 203 | queued_message_waiting = false; 204 | printf("%d bytes scheduled for transmission on port %d\n", queued_message.length, queued_message.port); 205 | } 206 | 207 | return; 208 | } 209 | 210 | if (!clock_is_synced) { 211 | // this will trigger a lora_uc_send command 212 | uc.requestClockSync(true); 213 | // in the next command we'll actually send it 214 | queue_next_send_message(); 215 | return; 216 | } 217 | 218 | // otherwise just send a random message (this is where you'd put your sensor data) 219 | int r = rand(); 220 | int16_t retcode = lorawan.send(15, (uint8_t*)(&r), sizeof(r), MSG_UNCONFIRMED_FLAG); 221 | 222 | if (retcode < 0) { 223 | printf("send_message for normal message on port %d failed (%d)\n", 15, retcode); 224 | queue_next_send_message(); 225 | } 226 | else { 227 | printf("%d bytes scheduled for transmission on port %d\n", sizeof(r), 15); 228 | } 229 | } 230 | 231 | static void queue_next_send_message() { 232 | if (in_class_c_mode) return; 233 | 234 | int backoff; 235 | lorawan.get_backoff_metadata(backoff); 236 | 237 | if (backoff < 0) { 238 | backoff = 5000; 239 | } 240 | 241 | evqueue.call_in(backoff, &send_message); 242 | } 243 | 244 | int main() { 245 | printf("\nMbed OS 5 Firmware Update over LoRaWAN\n"); 246 | 247 | // Enable trace output for this demo, so we can see what the LoRaWAN stack does 248 | mbed_trace_init(); 249 | mbed_trace_exclude_filters_set("QSPIF"); 250 | 251 | if (lorawan.initialize(&evqueue) != LORAWAN_STATUS_OK) { 252 | printf("LoRa initialization failed!\n"); 253 | return -1; 254 | } 255 | 256 | // update client callbacks, note that these run in an ISR! 257 | uc.callbacks.switchToClassA = evqueue.event(switch_to_class_a); // dispatch to eventqueue 258 | uc.callbacks.switchToClassC = switch_to_class_c_irq; 259 | 260 | // These run in the context that calls the update client 261 | uc.callbacks.fragSessionComplete = evqueue.event(lorawan_uc_fragsession_complete); 262 | uc.callbacks.firmwareReady = evqueue.event(lorawan_uc_firmware_ready); 263 | 264 | // prepare application callbacks 265 | callbacks.events = callback(lora_event_handler); 266 | lorawan.add_app_callbacks(&callbacks); 267 | 268 | // Enable adaptive data rating 269 | if (lorawan.enable_adaptive_datarate() != LORAWAN_STATUS_OK) { 270 | printf("enable_adaptive_datarate failed!\n"); 271 | return -1; 272 | } 273 | 274 | lorawan.set_device_class(CLASS_A); 275 | 276 | if (get_built_in_dev_eui(DEV_EUI, sizeof(DEV_EUI)) == 0) { 277 | printf("read built-in dev eui: %02x %02x %02x %02x %02x %02x %02x %02x\n", 278 | DEV_EUI[0], DEV_EUI[1], DEV_EUI[2], DEV_EUI[3], DEV_EUI[4], DEV_EUI[5], DEV_EUI[6], DEV_EUI[7]); 279 | } 280 | 281 | lorawan_connect_t connect_params; 282 | connect_params.connect_type = LORAWAN_CONNECTION_OTAA; 283 | connect_params.connection_u.otaa.dev_eui = DEV_EUI; 284 | connect_params.connection_u.otaa.app_eui = APP_EUI; 285 | connect_params.connection_u.otaa.app_key = APP_KEY; 286 | connect_params.connection_u.otaa.nb_trials = 3; 287 | 288 | lorawan_status_t retcode = lorawan.connect(connect_params); 289 | 290 | if (retcode == LORAWAN_STATUS_OK || 291 | retcode == LORAWAN_STATUS_CONNECT_IN_PROGRESS) { 292 | } else { 293 | printf("Connection error, code = %d\n", retcode); 294 | return -1; 295 | } 296 | 297 | printf("Connection - In Progress ...\r\n"); 298 | 299 | // make your event queue dispatching events forever 300 | evqueue.dispatch_forever(); 301 | } 302 | 303 | // This is called from RX_DONE, so whenever a message came in 304 | static void receive_message() 305 | { 306 | uint8_t rx_buffer[255] = { 0 }; 307 | uint8_t port; 308 | int flags; 309 | int16_t retcode = lorawan.receive(rx_buffer, sizeof(rx_buffer), port, flags); 310 | 311 | if (retcode < 0) { 312 | printf("receive() - Error code %d\n", retcode); 313 | return; 314 | } 315 | 316 | printf("Received %d bytes on port %u\n", retcode, port); 317 | 318 | LW_UC_STATUS status = LW_UC_OK; 319 | 320 | if (port == 200) { 321 | status = uc.handleMulticastControlCommand(rx_buffer, retcode); 322 | } 323 | else if (port == 201) { 324 | // retrieve current session and set dev addr 325 | loramac_protocol_params params; 326 | lorawan.get_session(¶ms); 327 | status = uc.handleFragmentationCommand(params.dev_addr, rx_buffer, retcode); 328 | 329 | // blink LED when receiving a packet in Class C mode 330 | if (in_class_c_mode) { 331 | turn_led_on(); 332 | evqueue.call_in(200, &turn_led_off); 333 | } 334 | } 335 | else if (port == 202) { 336 | status = uc.handleClockSyncCommand(rx_buffer, retcode); 337 | if (status == LW_UC_OK) { 338 | clock_is_synced = true; 339 | } 340 | } 341 | else { 342 | printf("Data received on port %d (length %d): ", port, retcode); 343 | 344 | for (uint8_t i = 0; i < retcode; i++) { 345 | printf("%02x ", rx_buffer[i]); 346 | } 347 | printf("\n"); 348 | } 349 | 350 | if (status != LW_UC_OK) { 351 | printf("Failed to handle UC command on port %d, status %d\n", port, status); 352 | } 353 | } 354 | 355 | // Event handler 356 | static void lora_event_handler(lorawan_event_t event) { 357 | switch (event) { 358 | case CONNECTED: 359 | printf("Connection - Successful\n"); 360 | 361 | uc.printHeapStats("CONNECTED "); 362 | 363 | queue_next_send_message(); 364 | break; 365 | case DISCONNECTED: 366 | printf("Disconnected Successfully\n"); 367 | break; 368 | case TX_DONE: 369 | { 370 | printf("Message Sent to Network Server\n"); 371 | queue_next_send_message(); 372 | break; 373 | } 374 | case TX_TIMEOUT: 375 | case TX_ERROR: 376 | case TX_CRYPTO_ERROR: 377 | case TX_SCHEDULING_ERROR: 378 | printf("Transmission Error - EventCode = %d\n", event); 379 | queue_next_send_message(); 380 | break; 381 | case RX_DONE: 382 | printf("Received message from Network Server\n"); 383 | receive_message(); 384 | break; 385 | case RX_TIMEOUT: 386 | case RX_ERROR: 387 | printf("Error in reception - Code = %d\n", event); 388 | break; 389 | case JOIN_FAILURE: 390 | printf("OTAA Failed - Check Keys\n"); 391 | break; 392 | default: 393 | MBED_ASSERT("Unknown Event"); 394 | break; 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /testplan.md: -------------------------------------------------------------------------------- 1 | # Test plan 2 | 3 | Please run the following tests before a release. 4 | 5 | - Unit tests - OK 6 | 7 | ``` 8 | mbed test --app-config mbed-lorawan-update-client/TESTS/tests/mbed_app.json -n mbed-lorawan-update-client-tests-tests-* -v 9 | ``` 10 | 11 | - Interop test 12 | 13 | ``` 14 | # in mbed_app.json set 15 | # "lorawan-update-client.interop-testing" : true, 16 | 17 | $ cp .mbedignore_no_rtos .mbedignore 18 | $ mbed compile --profile=./profiles/tiny.json 19 | $ cp BUILD/FF1705_L151CC/GCC_ARM-TINY/mbed-os-example-lorawan-fuota.bin /Volumes/FF1705 20 | $ lorawan-fota-signing-tool create-frag-packets -i fuota-server/test-file.bin --output-format plain --frag-size 40 --redundancy-packets 5 -o fuota-server/test-file-unsigned.txt 21 | $ LORA_HOST=192.168.122.134 LORA_DR=5 node fuota-server/loraserver.js fuota-server/test-file-unsigned.txt 22 | ``` 23 | 24 | - Blinky 25 | 26 | ``` 27 | # in mbed_app.json set 28 | # "lorawan-update-client.interop-testing" : false, 29 | 30 | $ mbed compile --profile=./profiles/tiny.json 31 | $ cp BUILD/FF1705_L151CC/GCC_ARM-TINY/mbed-os-example-lorawan-fuota.bin /Volumes/FF1705 32 | $ lorawan-fota-signing-tool sign-binary -b example-firmware/xdot-blinky.bin -o fuota-server/xdot-blinky-signed.txt --frag-size 204 --redundancy-packets 20 --output-format packets-plain --override-version 33 | $ LORA_HOST=192.168.122.134 LORA_DR=5 node fuota-server/loraserver.js fuota-server/xdot-blinky-signed.txt 34 | ``` 35 | 36 | - Delta update 37 | 38 | ``` 39 | $ rm updates/* 40 | $ mbed compile --profile=./profiles/tiny.json 41 | $ cp BUILD/FF1705_L151CC/GCC_ARM-TINY/mbed-os-example-lorawan-fuota.bin /Volumes/FF1705 42 | $ cp BUILD/FF1705_L151CC/GCC_ARM-TINY/mbed-os-example-lorawan-fuota_application.bin updates/v1.bin 43 | 44 | # make change in app 45 | 46 | $ mbed compile --profile=./profiles/tiny.json 47 | $ cp BUILD/FF1705_L151CC/GCC_ARM-TINY/mbed-os-example-lorawan-fuota_application.bin updates/v2.bin 48 | $ lorawan-fota-signing-tool sign-delta --old updates/v1.bin --new updates/v2.bin --output-format packets-plain -o updates/v1_to_v2.txt --frag-size 204 --redundancy-packets 10 49 | $ LORA_HOST=192.168.122.132 LORA_DR=5 node fuota-server/loraserver.js updates/v1_to_v2.txt 50 | ``` 51 | --------------------------------------------------------------------------------