├── .gitignore ├── Cayenne-LPP.lib ├── LICENSE ├── README.md ├── at45-blockdevice.lib ├── bootloader ├── xdot-at45-develop.bin └── xdot-at45-release.bin ├── fotalora_mbedtls_config.h ├── inc ├── dot_util.h └── tiny-aes128 │ ├── LICENSE │ ├── tiny-aes.cpp │ └── tiny-aes.h ├── libxDot-dev-mbed5.lib ├── mbed-delta-update.lib ├── mbed-lorawan-frag-lib.lib ├── mbed-os.lib ├── mbed_app.json ├── package-signer ├── .gitignore ├── README.md ├── create-and-sign-diff.js ├── generate-keys.js ├── package.json └── sign-package.js ├── profiles ├── debug.json ├── develop.json └── release.json └── src ├── ProtocolLayer.h ├── RadioEvent.h ├── UpdateParameters.h ├── dot_util.cpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | libmDot-dev-mbed5/ 2 | mbed-os/ 3 | .hg 4 | BUILD/ 5 | mbed_settings.* 6 | .mbed 7 | .vscode/ 8 | mbed-lorawan-frag-lib/ 9 | bootloader/xdot-at45-symlinked.bin 10 | src/UpdateCerts.h 11 | package-signer/certs/ 12 | package-signer/tmp/ 13 | package-lock.json 14 | 15 | -------------------------------------------------------------------------------- /Cayenne-LPP.lib: -------------------------------------------------------------------------------- 1 | https://os.mbed.com/teams/myDevicesIoT/code/Cayenne-LPP/#5a9d65b33e85 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 ARM Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is superseded by [mbed-os-example-lorawan-fuota](https://github.com/armmbed/mbed-os-example-lorawan-fuota) which implements the latest specifications 2 | 3 | This application implements multicast firmware updates over LoRaWAN. It runs on Multi-Tech xDot devices with external flash (AT45 SPI flash by default) like the [L-TEK FF1705](https://os.mbed.com/platforms/L-TEK-FF1705/). 4 | 5 | The application implements: 6 | 7 | * LoRaWAN Data block proposal - including forward error correction. 8 | * LoRaWAN Multicast proposal. 9 | * External flash support for storing firmware update fragments. 10 | * Binary patching for delta updates. 11 | * Bootloader support to perform the actual firmware update. 12 | 13 | ## How to build 14 | 15 | 1. Create an account at the [experimental The Things Network FOTA](https://console.fotademo.thethings.network) deployment. 16 | 1. Point your gateway to `router.fotademo.thethings.network`. 17 | 1. Create a new device in the The Things Network console. 18 | 1. Set your application EUI and application key in `main.cpp`. 19 | 1. Install Mbed CLI and either ARMCC 5 or GCC ARM 6 (not 4.9!), and import this application: 20 | 21 | ``` 22 | $ mbed import https://github.com/armmbed/lorawan-fota-demo 23 | ``` 24 | 25 | 1. Generate a set of keys to sign updates: 26 | 27 | ``` 28 | $ cd package-signer 29 | $ npm install 30 | $ node generate-keys.js your-domain.com your-device-model 31 | ``` 32 | 33 | 1. Compile this application: 34 | 35 | ``` 36 | # ARMCC 37 | $ mbed compile -m xdot_l151cc -t ARM --profile=./profiles/develop.json 38 | 39 | # GCC 40 | $ mbed compile -m xdot_l151cc -t GCC_ARM --profile=./profiles/develop.json 41 | ``` 42 | 43 | 1. Flash the application on your development board. 44 | 45 | ## Relevant projects 46 | 47 | * [Bootloader](https://github.com/armmbed/lorawan-fota-bootloader) 48 | * [Fragmentation library](https://github.com/janjongboom/mbed-lorawan-frag-lib) 49 | * [Patching library](https://github.com/janjongboom/janpatch) 50 | * [Standalone fragmentation demonstration](https://github.com/janjongboom/lorawan-fragmentation-in-flash) - useful when developing, as you don't need a LoRa network. 51 | 52 | ## Other flash drivers 53 | 54 | If you're using a different flash chip, you'll need to implement the [BlockDevice](https://docs.mbed.com/docs/mbed-os-api-reference/en/latest/APIs/storage/block_device/) interface. See `AT45BlockDevice.h` in the `at45-blockdevice` driver for more information. 55 | 56 | ## Update keys 57 | 58 | Updates need to be signed using ECDSA/SHA256. The private key is held by the manufacturer of the device, whilst the public key is baked into the device firmware. When an update comes in the signature is verified by the public key. In addition, firmware is tagged with the manufacturer UUID and the device model UUID. These are also baked into the device firmware. This is a prevention mechanism designed to avoid flashing incompatible firmware to devices. 59 | 60 | To generate a new key pair, and to generate the UUIDs, run (requires OpenSSL): 61 | 62 | ``` 63 | $ node package-signer/generate-keys.js your-domain.com your-device-model 64 | ``` 65 | 66 | ### Update format 67 | 68 | The update file format is: 69 | 70 | 1. 1 byte, size of the signature (70, 71 or 72 bytes). 71 | 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`. 72 | 1. 16 bytes, manufacturer UUID. 73 | 1. 16 bytes, device class UUID. 74 | 1. 1 byte, diff indication. If `0`, then this is not a delta update. If `1` it's a delta update. 75 | 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. 76 | 1. The update file (either diff or full image). 77 | 78 | ### Creating update file 79 | 80 | These scripts uses the keys / device IDs in the `package-signer/certs` folder. 81 | 82 | **Full update** 83 | 84 | A full update file can be created via: 85 | 86 | ``` 87 | $ node package-signer/sign-package.js BUILD/PATH/TO/BINARY_application.bin > full-new-fw.bin 88 | ``` 89 | 90 | **Diff update** 91 | 92 | A diff update file can be created via: 93 | 94 | ``` 95 | $ node package-signer/create-and-sign-diff.js OLD_FILE_application.bin NEW_FILE_application.bin > diff-new-fw.bin 96 | ``` 97 | 98 | Make sure to tag the applications with a version number, and store them somewhere, to make your life significantly easier. 99 | -------------------------------------------------------------------------------- /at45-blockdevice.lib: -------------------------------------------------------------------------------- 1 | https://github.com/janjongboom/at45-blockdevice/#44766342130b373f59ac372add62e73f55469f9b 2 | -------------------------------------------------------------------------------- /bootloader/xdot-at45-develop.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/lorawan-fota-demo/36577175949e3c0bee31191f48d288efa94d16cf/bootloader/xdot-at45-develop.bin -------------------------------------------------------------------------------- /bootloader/xdot-at45-release.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARMmbed/lorawan-fota-demo/36577175949e3c0bee31191f48d288efa94d16cf/bootloader/xdot-at45-release.bin -------------------------------------------------------------------------------- /fotalora_mbedtls_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2017 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_NO_PLATFORM_ENTROPY 30 | 31 | #define MBEDTLS_ASN1_PARSE_C 32 | #define MBEDTLS_ASN1_WRITE_C 33 | #define MBEDTLS_BIGNUM_C 34 | #define MBEDTLS_ECDSA_C 35 | #define MBEDTLS_ECP_C 36 | #define MBEDTLS_ERROR_C 37 | #define MBEDTLS_MD_C 38 | #define MBEDTLS_OID_C 39 | #define MBEDTLS_PK_C 40 | #define MBEDTLS_PK_PARSE_C 41 | #define MBEDTLS_SHA256_C 42 | 43 | #include "check_config.h" 44 | 45 | #endif /* FOTALORA_MBEDTLS_CONFIG_H */ 46 | -------------------------------------------------------------------------------- /inc/dot_util.h: -------------------------------------------------------------------------------- 1 | #ifndef __DOT_UTIL_H__ 2 | #define __DOT_UTIL_H__ 3 | 4 | #include "mbed.h" 5 | #include "mDot.h" 6 | #include "MTSLog.h" 7 | #include "MTSText.h" 8 | 9 | extern mDot* dot; 10 | 11 | void display_config(); 12 | 13 | void update_ota_config_name_phrase(std::string network_name, std::string network_passphrase, uint8_t frequency_sub_band, bool public_network, uint8_t ack); 14 | 15 | void update_ota_config_id_key(uint8_t *network_id, uint8_t *network_key, uint8_t frequency_sub_band, bool public_network, uint8_t ack); 16 | 17 | void update_manual_config(uint8_t *network_address, uint8_t *network_session_key, uint8_t *data_session_key, uint8_t frequency_sub_band, bool public_network, uint8_t ack); 18 | 19 | void update_peer_to_peer_config(uint8_t *network_address, uint8_t *network_session_key, uint8_t *data_session_key, uint32_t tx_frequency, uint8_t tx_datarate, uint8_t tx_power); 20 | 21 | void update_network_link_check_config(uint8_t link_check_count, uint8_t link_check_threshold); 22 | 23 | void join_network(); 24 | 25 | uint32_t calculate_actual_sleep_time(uint32_t delay_s); 26 | 27 | void sleep_wake_rtc_only(uint32_t delay_s, bool deepsleep); 28 | 29 | void sleep_wake_interrupt_only(bool deepsleep); 30 | 31 | void sleep_wake_rtc_or_interrupt(uint32_t delay_s, bool deepsleep); 32 | 33 | void sleep_save_io(); 34 | 35 | void sleep_configure_io(); 36 | 37 | void sleep_restore_io(); 38 | 39 | void send_data(std::vector data); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /inc/tiny-aes128/LICENSE: -------------------------------------------------------------------------------- 1 | From https://github.com/kokke/tiny-AES128-C 2 | 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | -------------------------------------------------------------------------------- /inc/tiny-aes128/tiny-aes.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is an implementation of the AES algorithm, specifically ECB and CBC mode. 4 | Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. 5 | 6 | The implementation is verified against the test vectors in: 7 | National Institute of Standards and Technology Special Publication 800-38A 2001 ED 8 | 9 | ECB-AES128 10 | ---------- 11 | 12 | plain-text: 13 | 6bc1bee22e409f96e93d7e117393172a 14 | ae2d8a571e03ac9c9eb76fac45af8e51 15 | 30c81c46a35ce411e5fbc1191a0a52ef 16 | f69f2445df4f9b17ad2b417be66c3710 17 | 18 | key: 19 | 2b7e151628aed2a6abf7158809cf4f3c 20 | 21 | resulting cipher 22 | 3ad77bb40d7a3660a89ecaf32466ef97 23 | f5d3d58503b9699de785895a96fdbaaf 24 | 43b1cd7f598ece23881b00e3ed030688 25 | 7b0c785e27e8ad3f8223207104725dd4 26 | 27 | 28 | NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) 29 | You should pad the end of the string with zeros if this is not the case. 30 | For AES192/256 the block size is proportionally larger. 31 | 32 | */ 33 | 34 | 35 | /*****************************************************************************/ 36 | /* Includes: */ 37 | /*****************************************************************************/ 38 | #include 39 | #include // CBC mode, for memset 40 | #include "aes.h" 41 | 42 | /*****************************************************************************/ 43 | /* Defines: */ 44 | /*****************************************************************************/ 45 | // The number of columns comprising a state in AES. This is a constant in AES. Value=4 46 | #define Nb 4 47 | #define BLOCKLEN 16 //Block length in bytes AES is 128b block only 48 | 49 | #if defined(AES256) && (AES256 == 1) 50 | #define Nk 8 51 | #define KEYLEN 32 52 | #define Nr 14 53 | #define keyExpSize 240 54 | #elif defined(AES192) && (AES192 == 1) 55 | #define Nk 6 56 | #define KEYLEN 24 57 | #define Nr 12 58 | #define keyExpSize 208 59 | #else 60 | #define Nk 4 // The number of 32 bit words in a key. 61 | #define KEYLEN 16 // Key length in bytes 62 | #define Nr 10 // The number of rounds in AES Cipher. 63 | #define keyExpSize 176 64 | #endif 65 | 66 | // jcallan@github points out that declaring Multiply as a function 67 | // reduces code size considerably with the Keil ARM compiler. 68 | // See this link for more information: https://github.com/kokke/tiny-AES128-C/pull/3 69 | #ifndef MULTIPLY_AS_A_FUNCTION 70 | #define MULTIPLY_AS_A_FUNCTION 0 71 | #endif 72 | 73 | 74 | /*****************************************************************************/ 75 | /* Private variables: */ 76 | /*****************************************************************************/ 77 | // state - array holding the intermediate results during decryption. 78 | typedef uint8_t state_t[4][4]; 79 | static state_t* state; 80 | 81 | // The array that stores the round keys. 82 | static uint8_t RoundKey[keyExpSize]; 83 | 84 | // The Key input to the AES Program 85 | static const uint8_t* Key; 86 | 87 | #if defined(CBC) && CBC 88 | // Initial Vector used only for CBC mode 89 | static uint8_t* Iv; 90 | #endif 91 | 92 | // The lookup-tables are marked const so they can be placed in read-only storage instead of RAM 93 | // The numbers below can be computed dynamically trading ROM for RAM - 94 | // This can be useful in (embedded) bootloader applications, where ROM is often limited. 95 | static const uint8_t sbox[256] = { 96 | //0 1 2 3 4 5 6 7 8 9 A B C D E F 97 | 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 98 | 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 99 | 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 100 | 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 101 | 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 102 | 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 103 | 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 104 | 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 105 | 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 106 | 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 107 | 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 108 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 109 | 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 110 | 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 111 | 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 112 | 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; 113 | 114 | static const uint8_t rsbox[256] = { 115 | 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 116 | 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 117 | 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 118 | 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 119 | 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 120 | 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 121 | 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 122 | 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 123 | 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 124 | 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 125 | 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 126 | 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 127 | 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 128 | 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 129 | 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 130 | 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; 131 | 132 | // The round constant word array, Rcon[i], contains the values given by 133 | // x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) 134 | static const uint8_t Rcon[11] = { 135 | 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; 136 | 137 | /* 138 | * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES128-C/pull/12), 139 | * that you can remove most of the elements in the Rcon array, because they are unused. 140 | * 141 | * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon 142 | * 143 | * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), 144 | * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." 145 | * 146 | * ... which is why the full array below has been 'disabled' below. 147 | */ 148 | #if 0 149 | static const uint8_t Rcon[256] = { 150 | 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 151 | 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 152 | 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 153 | 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 154 | 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 155 | 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 156 | 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 157 | 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 158 | 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 159 | 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 160 | 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 161 | 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 162 | 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 163 | 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 164 | 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 165 | 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d }; 166 | #endif 167 | 168 | /*****************************************************************************/ 169 | /* Private functions: */ 170 | /*****************************************************************************/ 171 | static uint8_t getSBoxValue(uint8_t num) 172 | { 173 | return sbox[num]; 174 | } 175 | 176 | static uint8_t getSBoxInvert(uint8_t num) 177 | { 178 | return rsbox[num]; 179 | } 180 | 181 | // This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. 182 | static void KeyExpansion(void) 183 | { 184 | uint32_t i, k; 185 | uint8_t tempa[4]; // Used for the column/row operations 186 | 187 | // The first round key is the key itself. 188 | for (i = 0; i < Nk; ++i) 189 | { 190 | RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; 191 | RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; 192 | RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; 193 | RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; 194 | } 195 | 196 | // All other round keys are found from the previous round keys. 197 | //i == Nk 198 | for (; i < Nb * (Nr + 1); ++i) 199 | { 200 | { 201 | tempa[0]=RoundKey[(i-1) * 4 + 0]; 202 | tempa[1]=RoundKey[(i-1) * 4 + 1]; 203 | tempa[2]=RoundKey[(i-1) * 4 + 2]; 204 | tempa[3]=RoundKey[(i-1) * 4 + 3]; 205 | } 206 | 207 | if (i % Nk == 0) 208 | { 209 | // This function shifts the 4 bytes in a word to the left once. 210 | // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] 211 | 212 | // Function RotWord() 213 | { 214 | k = tempa[0]; 215 | tempa[0] = tempa[1]; 216 | tempa[1] = tempa[2]; 217 | tempa[2] = tempa[3]; 218 | tempa[3] = k; 219 | } 220 | 221 | // SubWord() is a function that takes a four-byte input word and 222 | // applies the S-box to each of the four bytes to produce an output word. 223 | 224 | // Function Subword() 225 | { 226 | tempa[0] = getSBoxValue(tempa[0]); 227 | tempa[1] = getSBoxValue(tempa[1]); 228 | tempa[2] = getSBoxValue(tempa[2]); 229 | tempa[3] = getSBoxValue(tempa[3]); 230 | } 231 | 232 | tempa[0] = tempa[0] ^ Rcon[i/Nk]; 233 | } 234 | #if defined(AES256) && (AES256 == 1) 235 | if (i % Nk == 4) 236 | { 237 | // Function Subword() 238 | { 239 | tempa[0] = getSBoxValue(tempa[0]); 240 | tempa[1] = getSBoxValue(tempa[1]); 241 | tempa[2] = getSBoxValue(tempa[2]); 242 | tempa[3] = getSBoxValue(tempa[3]); 243 | } 244 | } 245 | #endif 246 | RoundKey[i * 4 + 0] = RoundKey[(i - Nk) * 4 + 0] ^ tempa[0]; 247 | RoundKey[i * 4 + 1] = RoundKey[(i - Nk) * 4 + 1] ^ tempa[1]; 248 | RoundKey[i * 4 + 2] = RoundKey[(i - Nk) * 4 + 2] ^ tempa[2]; 249 | RoundKey[i * 4 + 3] = RoundKey[(i - Nk) * 4 + 3] ^ tempa[3]; 250 | } 251 | } 252 | 253 | // This function adds the round key to state. 254 | // The round key is added to the state by an XOR function. 255 | static void AddRoundKey(uint8_t round) 256 | { 257 | uint8_t i,j; 258 | for (i=0;i<4;++i) 259 | { 260 | for (j = 0; j < 4; ++j) 261 | { 262 | (*state)[i][j] ^= RoundKey[round * Nb * 4 + i * Nb + j]; 263 | } 264 | } 265 | } 266 | 267 | // The SubBytes Function Substitutes the values in the 268 | // state matrix with values in an S-box. 269 | static void SubBytes(void) 270 | { 271 | uint8_t i, j; 272 | for (i = 0; i < 4; ++i) 273 | { 274 | for (j = 0; j < 4; ++j) 275 | { 276 | (*state)[j][i] = getSBoxValue((*state)[j][i]); 277 | } 278 | } 279 | } 280 | 281 | // The ShiftRows() function shifts the rows in the state to the left. 282 | // Each row is shifted with different offset. 283 | // Offset = Row number. So the first row is not shifted. 284 | static void ShiftRows(void) 285 | { 286 | uint8_t temp; 287 | 288 | // Rotate first row 1 columns to left 289 | temp = (*state)[0][1]; 290 | (*state)[0][1] = (*state)[1][1]; 291 | (*state)[1][1] = (*state)[2][1]; 292 | (*state)[2][1] = (*state)[3][1]; 293 | (*state)[3][1] = temp; 294 | 295 | // Rotate second row 2 columns to left 296 | temp = (*state)[0][2]; 297 | (*state)[0][2] = (*state)[2][2]; 298 | (*state)[2][2] = temp; 299 | 300 | temp = (*state)[1][2]; 301 | (*state)[1][2] = (*state)[3][2]; 302 | (*state)[3][2] = temp; 303 | 304 | // Rotate third row 3 columns to left 305 | temp = (*state)[0][3]; 306 | (*state)[0][3] = (*state)[3][3]; 307 | (*state)[3][3] = (*state)[2][3]; 308 | (*state)[2][3] = (*state)[1][3]; 309 | (*state)[1][3] = temp; 310 | } 311 | 312 | static uint8_t xtime(uint8_t x) 313 | { 314 | return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); 315 | } 316 | 317 | // MixColumns function mixes the columns of the state matrix 318 | static void MixColumns(void) 319 | { 320 | uint8_t i; 321 | uint8_t Tmp,Tm,t; 322 | for (i = 0; i < 4; ++i) 323 | { 324 | t = (*state)[i][0]; 325 | Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; 326 | Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; 327 | Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; 328 | Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; 329 | Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; 330 | } 331 | } 332 | 333 | // Multiply is used to multiply numbers in the field GF(2^8) 334 | #if MULTIPLY_AS_A_FUNCTION 335 | static uint8_t Multiply(uint8_t x, uint8_t y) 336 | { 337 | return (((y & 1) * x) ^ 338 | ((y>>1 & 1) * xtime(x)) ^ 339 | ((y>>2 & 1) * xtime(xtime(x))) ^ 340 | ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ 341 | ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); 342 | } 343 | #else 344 | #define Multiply(x, y) \ 345 | ( ((y & 1) * x) ^ \ 346 | ((y>>1 & 1) * xtime(x)) ^ \ 347 | ((y>>2 & 1) * xtime(xtime(x))) ^ \ 348 | ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ 349 | ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ 350 | 351 | #endif 352 | 353 | // MixColumns function mixes the columns of the state matrix. 354 | // The method used to multiply may be difficult to understand for the inexperienced. 355 | // Please use the references to gain more information. 356 | static void InvMixColumns(void) 357 | { 358 | int i; 359 | uint8_t a, b, c, d; 360 | for (i = 0; i < 4; ++i) 361 | { 362 | a = (*state)[i][0]; 363 | b = (*state)[i][1]; 364 | c = (*state)[i][2]; 365 | d = (*state)[i][3]; 366 | 367 | (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); 368 | (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); 369 | (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); 370 | (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); 371 | } 372 | } 373 | 374 | 375 | // The SubBytes Function Substitutes the values in the 376 | // state matrix with values in an S-box. 377 | static void InvSubBytes(void) 378 | { 379 | uint8_t i,j; 380 | for (i = 0; i < 4; ++i) 381 | { 382 | for (j = 0; j < 4; ++j) 383 | { 384 | (*state)[j][i] = getSBoxInvert((*state)[j][i]); 385 | } 386 | } 387 | } 388 | 389 | static void InvShiftRows(void) 390 | { 391 | uint8_t temp; 392 | 393 | // Rotate first row 1 columns to right 394 | temp = (*state)[3][1]; 395 | (*state)[3][1] = (*state)[2][1]; 396 | (*state)[2][1] = (*state)[1][1]; 397 | (*state)[1][1] = (*state)[0][1]; 398 | (*state)[0][1] = temp; 399 | 400 | // Rotate second row 2 columns to right 401 | temp = (*state)[0][2]; 402 | (*state)[0][2] = (*state)[2][2]; 403 | (*state)[2][2] = temp; 404 | 405 | temp = (*state)[1][2]; 406 | (*state)[1][2] = (*state)[3][2]; 407 | (*state)[3][2] = temp; 408 | 409 | // Rotate third row 3 columns to right 410 | temp = (*state)[0][3]; 411 | (*state)[0][3] = (*state)[1][3]; 412 | (*state)[1][3] = (*state)[2][3]; 413 | (*state)[2][3] = (*state)[3][3]; 414 | (*state)[3][3] = temp; 415 | } 416 | 417 | 418 | // Cipher is the main function that encrypts the PlainText. 419 | static void Cipher(void) 420 | { 421 | uint8_t round = 0; 422 | 423 | // Add the First round key to the state before starting the rounds. 424 | AddRoundKey(0); 425 | 426 | // There will be Nr rounds. 427 | // The first Nr-1 rounds are identical. 428 | // These Nr-1 rounds are executed in the loop below. 429 | for (round = 1; round < Nr; ++round) 430 | { 431 | SubBytes(); 432 | ShiftRows(); 433 | MixColumns(); 434 | AddRoundKey(round); 435 | } 436 | 437 | // The last round is given below. 438 | // The MixColumns function is not here in the last round. 439 | SubBytes(); 440 | ShiftRows(); 441 | AddRoundKey(Nr); 442 | } 443 | 444 | static void InvCipher(void) 445 | { 446 | uint8_t round=0; 447 | 448 | // Add the First round key to the state before starting the rounds. 449 | AddRoundKey(Nr); 450 | 451 | // There will be Nr rounds. 452 | // The first Nr-1 rounds are identical. 453 | // These Nr-1 rounds are executed in the loop below. 454 | for (round = (Nr - 1); round > 0; --round) 455 | { 456 | InvShiftRows(); 457 | InvSubBytes(); 458 | AddRoundKey(round); 459 | InvMixColumns(); 460 | } 461 | 462 | // The last round is given below. 463 | // The MixColumns function is not here in the last round. 464 | InvShiftRows(); 465 | InvSubBytes(); 466 | AddRoundKey(0); 467 | } 468 | 469 | 470 | /*****************************************************************************/ 471 | /* Public functions: */ 472 | /*****************************************************************************/ 473 | void AES_ECB_encrypt(const uint8_t* input, const uint8_t* key, uint8_t* output, const uint32_t length) 474 | { 475 | // Copy input to output, and work in-memory on output 476 | memcpy(output, input, length); 477 | state = (state_t*)output; 478 | 479 | Key = key; 480 | KeyExpansion(); 481 | 482 | // The next function call encrypts the PlainText with the Key using AES algorithm. 483 | Cipher(); 484 | } 485 | 486 | void AES_ECB_decrypt(const uint8_t* input, const uint8_t* key, uint8_t *output, const uint32_t length) 487 | { 488 | // Copy input to output, and work in-memory on output 489 | memcpy(output, input, length); 490 | state = (state_t*)output; 491 | 492 | // The KeyExpansion routine must be called before encryption. 493 | Key = key; 494 | KeyExpansion(); 495 | 496 | InvCipher(); 497 | } 498 | 499 | 500 | 501 | 502 | #if defined(CBC) && (CBC == 1) 503 | 504 | 505 | static void XorWithIv(uint8_t* buf) 506 | { 507 | uint8_t i; 508 | for (i = 0; i < BLOCKLEN; ++i) //WAS for(i = 0; i < KEYLEN; ++i) but the block in AES is always 128bit so 16 bytes! 509 | { 510 | buf[i] ^= Iv[i]; 511 | } 512 | } 513 | 514 | void AES_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv) 515 | { 516 | uintptr_t i; 517 | uint8_t extra = length % BLOCKLEN; /* Remaining bytes in the last non-full block */ 518 | 519 | // Skip the key expansion if key is passed as 0 520 | if (0 != key) 521 | { 522 | Key = key; 523 | KeyExpansion(); 524 | } 525 | 526 | if (iv != 0) 527 | { 528 | Iv = (uint8_t*)iv; 529 | } 530 | 531 | for (i = 0; i < length; i += BLOCKLEN) 532 | { 533 | XorWithIv(input); 534 | memcpy(output, input, BLOCKLEN); 535 | state = (state_t*)output; 536 | Cipher(); 537 | Iv = output; 538 | input += BLOCKLEN; 539 | output += BLOCKLEN; 540 | //printf("Step %d - %d", i/16, i); 541 | } 542 | 543 | if (extra) 544 | { 545 | memcpy(output, input, extra); 546 | state = (state_t*)output; 547 | Cipher(); 548 | } 549 | } 550 | 551 | void AES_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv) 552 | { 553 | uintptr_t i; 554 | uint8_t extra = length % BLOCKLEN; /* Remaining bytes in the last non-full block */ 555 | 556 | // Skip the key expansion if key is passed as 0 557 | if (0 != key) 558 | { 559 | Key = key; 560 | KeyExpansion(); 561 | } 562 | 563 | // If iv is passed as 0, we continue to encrypt without re-setting the Iv 564 | if (iv != 0) 565 | { 566 | Iv = (uint8_t*)iv; 567 | } 568 | 569 | for (i = 0; i < length; i += BLOCKLEN) 570 | { 571 | memcpy(output, input, BLOCKLEN); 572 | state = (state_t*)output; 573 | InvCipher(); 574 | XorWithIv(output); 575 | Iv = input; 576 | input += BLOCKLEN; 577 | output += BLOCKLEN; 578 | } 579 | 580 | if (extra) 581 | { 582 | memcpy(output, input, extra); 583 | state = (state_t*)output; 584 | InvCipher(); 585 | } 586 | } 587 | 588 | #endif // #if defined(CBC) && (CBC == 1) 589 | -------------------------------------------------------------------------------- /inc/tiny-aes128/tiny-aes.h: -------------------------------------------------------------------------------- 1 | #ifndef _AES_H_ 2 | #define _AES_H_ 3 | 4 | #include 5 | 6 | 7 | // #define the macros below to 1/0 to enable/disable the mode of operation. 8 | // 9 | // CBC enables AES encryption in CBC-mode of operation. 10 | // ECB enables the basic ECB 16-byte block algorithm. Both can be enabled simultaneously. 11 | 12 | // The #ifndef-guard allows it to be configured before #include'ing or at compile time. 13 | #ifndef CBC 14 | #define CBC 1 15 | #endif 16 | 17 | #ifndef ECB 18 | #define ECB 1 19 | #endif 20 | 21 | #define AES128 1 22 | //#define AES192 1 23 | //#define AES256 1 24 | 25 | #if defined(ECB) && (ECB == 1) 26 | 27 | void AES_ECB_encrypt(const uint8_t* input, const uint8_t* key, uint8_t *output, const uint32_t length); 28 | void AES_ECB_decrypt(const uint8_t* input, const uint8_t* key, uint8_t *output, const uint32_t length); 29 | 30 | #endif // #if defined(ECB) && (ECB == !) 31 | 32 | 33 | #if defined(CBC) && (CBC == 1) 34 | 35 | void AES_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv); 36 | void AES_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv); 37 | 38 | #endif // #if defined(CBC) && (CBC == 1) 39 | 40 | 41 | #endif //_AES_H_ 42 | -------------------------------------------------------------------------------- /libxDot-dev-mbed5.lib: -------------------------------------------------------------------------------- 1 | https://os.mbed.com/teams/MultiTech/code/libxDot-dev-mbed5/#8c938c4e4b6a 2 | -------------------------------------------------------------------------------- /mbed-delta-update.lib: -------------------------------------------------------------------------------- 1 | https://github.com/janjongboom/mbed-delta-update/#6a147fced7437920515b7a1710912776ea9289fe 2 | -------------------------------------------------------------------------------- /mbed-lorawan-frag-lib.lib: -------------------------------------------------------------------------------- 1 | https://github.com/janjongboom/mbed-lorawan-frag-lib/#90df0fd9d4225d1a50df4fb7503a4eb94b6121c4 2 | -------------------------------------------------------------------------------- /mbed-os.lib: -------------------------------------------------------------------------------- 1 | https://github.com/armmbed/mbed-os/#cc7556a92fb9320f4bebb190c6e1315af116c50c 2 | -------------------------------------------------------------------------------- /mbed_app.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "max-redundancy-packets": { 4 | "help": "The maximum number of redundancy packets this device can process. A higher number requires more heap space.", 5 | "value": 40 6 | }, 7 | "ack-mac-commands": { 8 | "help": "Whether to ACK fragmentation / datablock MAC commands", 9 | "value": 0 10 | } 11 | }, 12 | "macros": [ 13 | "CBC=0", 14 | "EBC=1", 15 | "MBED_HEAP_STATS_ENABLED=1", 16 | "JANPATCH_STREAM=BDFILE", 17 | "MBEDTLS_CONFIG_FILE=\"fotalora_mbedtls_config.h\"" 18 | ], 19 | "target_overrides": { 20 | "XDOT_L151CC": { 21 | "target.bootloader_img": "bootloader/xdot-at45-develop.bin" 22 | }, 23 | "FF1705_L151CC": { 24 | "target.bootloader_img": "bootloader/xdot-at45-develop.bin" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package-signer/.gitignore: -------------------------------------------------------------------------------- 1 | calculate-crc64/crc64 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /package-signer/README.md: -------------------------------------------------------------------------------- 1 | # Package signer 2 | 3 | The scripts in this folder can create certificates, and sign firmware packages. 4 | 5 | ## Prerequisites 6 | 7 | * node.js (8 or higher) 8 | * OpenSSL 9 | 10 | ## Generating keys 11 | 12 | To sign packages you need a public/private key pair. The public key is embedded in your device, and the private key can be used to sign new firmware packages. In addition, a device has a manufacturer UUID and a device class UUID. Firmware packages are tagged with these UUIDs to prevent flashing an incompatible firmware on device. 13 | 14 | To generate a new set of keys, run: 15 | 16 | ``` 17 | $ node generate-keys.js your-organisation-domain device-class-name 18 | ``` 19 | 20 | ## Signing packages 21 | 22 | To sign a new package, run: 23 | 24 | ``` 25 | $ node sign-package.js path-to-your_application.bin > signed_application.bin 26 | ``` 27 | 28 | ## Keys 29 | 30 | A firmware needs to be signed with a private key. You find the keys in the `certs` directory. The public key needs to be included in the device firmware (in the `update_certs.h` file), which uses the key to verify that the firmware was signed with the private key. 31 | 32 | To create new keys, run: 33 | 34 | ``` 35 | $ openssl genrsa -out certs/update.key 2048 36 | $ openssl rsa -pubout -in certs/update.key -out update.pub 37 | ``` 38 | 39 | Firmware is also tagged with a device manufacturer UUID and device class UUID, used to prevent flashing the wrong application. 40 | 41 | ## Generating an application package 42 | 43 | 1. Compile an image for Multi-Tech xDot with bootloader enabled (the same bootloader as in this project, so the offsets are correct). 44 | 1. Copy the `_application.bin` file into this folder. 45 | 1. Run: 46 | 47 | ``` 48 | $ npm install 49 | $ node create-packets-h.js my-app_application.bin ../src/packets.h 50 | ``` 51 | 52 | 1. This command creates the `packets.h` and the `update_certs.h` files. 53 | 1. Re-compile lorawan-fragmentation-in-flash and see the xDot update to your new application. 54 | -------------------------------------------------------------------------------- /package-signer/create-and-sign-diff.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const Path = require('path'); 3 | const execSync = require('child_process').execSync; 4 | const UUID = require('uuid-1345'); 5 | const crypto = require('crypto'); 6 | const deviceId = require('./certs/device-ids'); 7 | 8 | let manufacturerUUID = new UUID(deviceId['manufacturer-uuid']).toBuffer(); 9 | let deviceClassUUID = new UUID(deviceId['device-class-uuid']).toBuffer(); 10 | 11 | const oldPath = Path.resolve(process.argv[2]); 12 | const newPath = Path.resolve(process.argv[3]); 13 | const oldFile = fs.readFileSync(oldPath); 14 | const newFile = fs.readFileSync(newPath); 15 | 16 | const oldSize = fs.statSync(oldPath).size; 17 | 18 | // console.error('oldSize is', oldSize); 19 | 20 | // create diff... 21 | let diff; 22 | try { 23 | diff = execSync(`jdiff ${oldPath} ${newPath}`); 24 | } 25 | catch (ex) { 26 | console.error('Add jdiff to your PATH, see http://jojodiff.sourceforge.net'); 27 | process.exit(1); 28 | } 29 | 30 | let oldHash = crypto.createHash('sha256').update(oldFile).digest('hex'); 31 | let diffHash = crypto.createHash('sha256').update(diff).digest('hex'); 32 | let newHash = crypto.createHash('sha256').update(newFile).digest('hex'); 33 | 34 | // diff info contains (bool is_diff, 3 bytes for the size of the *old* firmware) 35 | let isDiffBuffer = Buffer.from([ 1, oldSize >> 16 & 0xff, oldSize >> 8 & 0xff, oldSize & 0xff ]); 36 | 37 | console.error('old hash: ', oldHash); 38 | console.error('diff hash:', diffHash); 39 | console.error('new hash: ', newHash); 40 | console.error(''); 41 | 42 | // we need to create signature based on the *new file* 43 | let signature = execSync(`openssl dgst -sha256 -sign ${Path.join(__dirname, 'certs', 'update.key')} ${newPath}`); 44 | console.error('Signed signature is', signature.toString('hex')); 45 | 46 | let sigLength = Buffer.from([ signature.length ]); 47 | 48 | if (signature.length === 70) { 49 | signature = Buffer.concat([ signature, Buffer.from([ 0, 0 ]) ]); 50 | } 51 | else if (signature.length === 71) { 52 | signature = Buffer.concat([ signature, Buffer.from([ 0 ]) ]); 53 | } 54 | 55 | // now make a temp file which contains signature + class IDs + if it's a diff or not + bin 56 | process.stdout.write(Buffer.concat([ sigLength, signature, manufacturerUUID, deviceClassUUID, isDiffBuffer, diff ])); 57 | -------------------------------------------------------------------------------- /package-signer/generate-keys.js: -------------------------------------------------------------------------------- 1 | const execSync = require('child_process').execSync; 2 | const UUID = require('uuid-1345'); 3 | const fs = require('fs'); 4 | const Path = require('path'); 5 | 6 | let org = process.argv[2]; 7 | let deviceClass = process.argv[3]; 8 | 9 | if (!org || !deviceClass) { 10 | console.log('Usage: generate-keys.js organization device-class'); 11 | process.exit(1); 12 | } 13 | 14 | const certsFolder = Path.join(__dirname, 'certs'); 15 | 16 | if (fs.existsSync(certsFolder)) { 17 | console.log(`'certs' folder already exists, refusing to overwrite existing certificates`); 18 | process.exit(1); 19 | } 20 | 21 | fs.mkdirSync(Path.join(__dirname, 'certs')); 22 | 23 | console.log('Creating keypair'); 24 | 25 | execSync(`openssl ecparam -genkey -name prime256v1 -out ${Path.join(certsFolder, 'update.key')}`) 26 | execSync(`openssl ec -in ${Path.join(certsFolder, 'update.key')} -pubout > ${Path.join(certsFolder, 'update.pub')}`); 27 | 28 | let pubKey = fs.readFileSync(Path.join(certsFolder, 'update.pub'), 'utf-8'); 29 | pubKey = pubKey.split('\n'); 30 | pubKey = pubKey.slice(1, pubKey.length - 2); 31 | pubKey = Buffer.concat(pubKey.map(k => new Buffer(k, 'base64'))); 32 | pubKey = Array.from(pubKey).map(c => '0x' + c.toString(16)).join(', '); 33 | 34 | console.log('Creating keypair OK'); 35 | 36 | let deviceIds = { 37 | 'manufacturer-uuid': UUID.v5({ 38 | namespace: UUID.namespace.url, 39 | name: org 40 | }), 41 | 'device-class-uuid': UUID.v5({ 42 | namespace: UUID.namespace.url, 43 | name: deviceClass 44 | }) 45 | }; 46 | 47 | fs.writeFileSync(Path.join(certsFolder, 'device-ids.js'), `module.exports = ${JSON.stringify(deviceIds, null, 4)};`, 'utf-8'); 48 | 49 | console.log('Wrote device-ids.js OK'); 50 | 51 | // now create the .H file... 52 | let manufacturerUUID = new UUID(deviceIds['manufacturer-uuid']).toBuffer(); 53 | let deviceClassUUID = new UUID(deviceIds['device-class-uuid']).toBuffer(); 54 | 55 | let certs = `#ifndef _UPDATE_CERTS_H 56 | #define _UPDATE_CERTS_H 57 | 58 | const char UPDATE_CERT_PUBKEY[] = { ${pubKey} }; 59 | const size_t UPDATE_CERT_LENGTH = sizeof(UPDATE_CERT_PUBKEY); 60 | 61 | const uint8_t UPDATE_CERT_MANUFACTURER_UUID[16] = { ${Array.from(manufacturerUUID).map(c => '0x' + c.toString(16)).join(', ')} }; 62 | const uint8_t UPDATE_CERT_DEVICE_CLASS_UUID[16] = { ${Array.from(deviceClassUUID).map(c => '0x' + c.toString(16)).join(', ')} }; 63 | 64 | #endif // _UPDATE_CERTS_H_ 65 | `; 66 | 67 | console.log('Writing UpdateCerts.h'); 68 | fs.writeFileSync(Path.join(certsFolder, 'UpdateCerts.h'), certs, 'utf-8'); 69 | fs.writeFileSync(Path.join(__dirname, '..', 'src', 'UpdateCerts.h'), certs, 'utf-8'); 70 | console.log('Writing UpdateCerts.h OK'); 71 | -------------------------------------------------------------------------------- /package-signer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lora-update-generator", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Generates firmware update manifests", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/armmbed/fota-lora-radio.git" 9 | }, 10 | "author": "Jan Jongboom ", 11 | "license": "Apache-2.0", 12 | "bugs": { 13 | "url": "https://github.com/armmbed/fota-lora-radio/issues" 14 | }, 15 | "homepage": "https://github.com/armmbed/fota-lora-radio#readme", 16 | "dependencies": { 17 | "uuid-1345": "^0.99.6" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package-signer/sign-package.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const Path = require('path'); 3 | const execSync = require('child_process').execSync; 4 | const UUID = require('uuid-1345'); 5 | const deviceId = require('./certs/device-ids'); 6 | 7 | let manufacturerUUID = new UUID(deviceId['manufacturer-uuid']).toBuffer(); 8 | let deviceClassUUID = new UUID(deviceId['device-class-uuid']).toBuffer(); 9 | 10 | // diff info contains (bool is_diff, 3 bytes for the size of the *old* firmware) 11 | let isDiffBuffer = Buffer.from([ 0, 0, 0, 0 ]); 12 | 13 | const binaryPath = Path.resolve(process.argv[2]); 14 | const tempFilePath = Path.join(__dirname, 'temp.bin'); 15 | 16 | // now we need to create a signature... 17 | let signature = execSync(`openssl dgst -sha256 -sign ${Path.join(__dirname, 'certs', 'update.key')} ${binaryPath}`); 18 | console.error('Signed signature is', signature.toString('hex')); 19 | 20 | let sigLength = Buffer.from([ signature.length ]); 21 | 22 | if (signature.length === 70) { 23 | signature = Buffer.concat([ signature, Buffer.from([ 0, 0 ]) ]); 24 | } 25 | else if (signature.length === 71) { 26 | signature = Buffer.concat([ signature, Buffer.from([ 0 ]) ]); 27 | } 28 | 29 | // now make a temp file which contains signature + class IDs + if it's a diff or not + bin 30 | process.stdout.write(Buffer.concat([ sigLength, signature, manufacturerUUID, deviceClassUUID, isDiffBuffer, fs.readFileSync(binaryPath) ])); 31 | 32 | -------------------------------------------------------------------------------- /profiles/debug.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", "-O0", "-g3", "-DMBED_DEBUG", 9 | "-DMBED_TRAP_ERRORS_ENABLED=1"], 10 | "asm": ["-x", "assembler-with-cpp"], 11 | "c": ["-std=gnu99"], 12 | "cxx": ["-std=gnu++98", "-fno-rtti", "-Wvla"], 13 | "ld": ["-Wl,--gc-sections", "-Wl,--wrap,main", "-Wl,--wrap,_malloc_r", 14 | "-Wl,--wrap,_free_r", "-Wl,--wrap,_realloc_r", "-Wl,--wrap,_memalign_r", 15 | "-Wl,--wrap,_calloc_r", "-Wl,--wrap,exit", "-Wl,--wrap,atexit", 16 | "-Wl,-n", "--specs=nano.specs"] 17 | }, 18 | "ARM": { 19 | "common": ["-c", "--gnu", "-Otime", "--split_sections", 20 | "--apcs=interwork", "--brief_diagnostics", "--restrict", 21 | "--multibyte_chars", "-O0", "-g", "-DMBED_DEBUG", 22 | "-DMBED_TRAP_ERRORS_ENABLED=1"], 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", "-Otime", "--split_sections", 30 | "--apcs=interwork", "--brief_diagnostics", "--restrict", 31 | "--multibyte_chars", "-O0", "-D__MICROLIB", "-g", 32 | "--library_type=microlib", "-DMBED_RTOS_SINGLE_THREAD", "-DMBED_DEBUG", 33 | "-DMBED_TRAP_ERRORS_ENABLED=1"], 34 | "asm": [], 35 | "c": ["--md", "--no_depend_system_headers", "--c99", "-D__ASSERT_MSG"], 36 | "cxx": ["--cpp", "--no_rtti", "--no_vla"], 37 | "ld": ["--library_type=microlib"] 38 | }, 39 | "IAR": { 40 | "common": [ 41 | "--no_wrap_diagnostics", "-e", 42 | "--diag_suppress=Pa050,Pa084,Pa093,Pa082", "-On", "-r", "-DMBED_DEBUG", 43 | "-DMBED_TRAP_ERRORS_ENABLED=1"], 44 | "asm": [], 45 | "c": ["--vla"], 46 | "cxx": ["--guard_calls", "--no_static_destruction"], 47 | "ld": ["--skip_dynamic_initialization", "--threaded_lib"] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /profiles/develop.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"], 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", "-Wl,--wrap,_memalign_r", 14 | "-Wl,--wrap,_calloc_r", "-Wl,--wrap,exit", "-Wl,--wrap,atexit", 15 | "-Wl,-n", "--specs=nano.specs"] 16 | }, 17 | "ARM": { 18 | "common": ["-c", "--gnu", "-Otime", "--split_sections", 19 | "--apcs=interwork", "--brief_diagnostics", "--restrict", 20 | "--multibyte_chars", "-O3"], 21 | "asm": [], 22 | "c": ["--md", "--no_depend_system_headers", "--c99", "-D__ASSERT_MSG"], 23 | "cxx": ["--cpp", "--no_rtti", "--no_vla"], 24 | "ld": [] 25 | }, 26 | "uARM": { 27 | "common": ["-c", "--gnu", "-Otime", "--split_sections", 28 | "--apcs=interwork", "--brief_diagnostics", "--restrict", 29 | "--multibyte_chars", "-O3", "-D__MICROLIB", 30 | "--library_type=microlib", "-DMBED_RTOS_SINGLE_THREAD"], 31 | "asm": [], 32 | "c": ["--md", "--no_depend_system_headers", "--c99", "-D__ASSERT_MSG"], 33 | "cxx": ["--cpp", "--no_rtti", "--no_vla"], 34 | "ld": ["--library_type=microlib"] 35 | }, 36 | "IAR": { 37 | "common": [ 38 | "--no_wrap_diagnostics", "-e", 39 | "--diag_suppress=Pa050,Pa084,Pa093,Pa082", "-Oh"], 40 | "asm": [], 41 | "c": ["--vla"], 42 | "cxx": ["--guard_calls", "--no_static_destruction"], 43 | "ld": ["--skip_dynamic_initialization", "--threaded_lib"] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /profiles/release.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"], 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", "-Wl,--wrap,_memalign_r", 14 | "-Wl,--wrap,_calloc_r", "-Wl,--wrap,exit", "-Wl,--wrap,atexit", 15 | "-Wl,-n", "--specs=nano.specs"] 16 | }, 17 | "ARM": { 18 | "common": ["-c", "--gnu", "-Ospace", "--split_sections", 19 | "--apcs=interwork", "--brief_diagnostics", "--restrict", 20 | "--multibyte_chars", "-O3", "-DNDEBUG"], 21 | "asm": [], 22 | "c": ["--md", "--no_depend_system_headers", "--c99", "-D__ASSERT_MSG"], 23 | "cxx": ["--cpp", "--no_rtti", "--no_vla"], 24 | "ld": [] 25 | }, 26 | "uARM": { 27 | "common": ["-c", "--gnu", "-Ospace", "--split_sections", 28 | "--apcs=interwork", "--brief_diagnostics", "--restrict", 29 | "--multibyte_chars", "-O3", "-D__MICROLIB", 30 | "--library_type=microlib", "-DMBED_RTOS_SINGLE_THREAD", "-DNDEBUG"], 31 | "asm": [], 32 | "c": ["--md", "--no_depend_system_headers", "--c99", "-D__ASSERT_MSG"], 33 | "cxx": ["--cpp", "--no_rtti", "--no_vla"], 34 | "ld": ["--library_type=microlib"] 35 | }, 36 | "IAR": { 37 | "common": [ 38 | "--no_wrap_diagnostics", "-e", 39 | "--diag_suppress=Pa050,Pa084,Pa093,Pa082", "-Ohz", "-DNDEBUG"], 40 | "asm": [], 41 | "c": ["--vla"], 42 | "cxx": ["--guard_calls", "--no_static_destruction"], 43 | "ld": ["--skip_dynamic_initialization", "--threaded_lib"] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ProtocolLayer.h: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2017 Semtech 8 | 9 | Description: Firmware update over the air with LoRa proof of concept 10 | General functions for the (de)fragmentation algorithm 11 | 12 | */ 13 | 14 | #ifndef __PROTOCOLLAYER_H 15 | #define __PROTOCOLLAYER_H 16 | 17 | #include "mbed.h" 18 | 19 | #define LORAWAN_APP_PROTOCOL_DATA_MAX_SIZE 100 20 | #define LORAWAN_PROTOCOL_DEFAULT_DATARATE DR_5 21 | #define LORAWAN_PROTOCOL_PORT 200 22 | 23 | #define MC_GROUP_STATUS_REQ 0x01 24 | #define MC_GROUP_STATUS_ANS 0x01 25 | #define MC_GROUP_SETUP_REQ 0x02 26 | #define MC_GROUP_SETUP_ANS 0x02 27 | #define MC_GROUP_DELETE_REQ 0x03 28 | #define MC_GROUP_DELETE_ANS 0x03 29 | #define MC_CLASSC_SESSION_REQ 0x04 30 | #define MC_CLASSC_SESSION_ANS 0x04 31 | #define MC_CLASSC_SESSION_REQ_LENGTH 0xa 32 | #define MC_CLASSC_SESSION_ANS_LENGTH 0x5 33 | #define FRAGMENTATION_ON_GOING 0xFFFFFFFF 34 | #define FRAGMENTATION_NOT_STARTED 0xFFFFFFFE 35 | #define FRAGMENTATION_FINISH 0x0 36 | #define MAX_UPLINK_T0_UIFCNTREF 0x3 37 | 38 | #define FRAG_STATUS_REQ 0x01 39 | #define FRAG_STATUS_ANS 0x01 40 | #define FRAG_SESSION_SETUP_REQ 0x02 41 | #define FRAG_SESSION_SETUP_ANS 0x02 42 | #define FRAG_SESSION_DELETE_REQ 0x03 43 | #define FRAG_SESSION_DELETE_ANS 0x03 44 | #define DATA_BLOCK_AUTH_REQ 0x05 45 | #define DATA_BLOCK_AUTH_ANS 0x05 46 | #define DATA_FRAGMENT 0x08 47 | #define FRAG_SESSION_SETUP_REQ_LENGTH 0x7 48 | 49 | #define FRAG_SESSION_SETUP_ANS_LENGTH 0x2 50 | #define DATA_BLOCK_AUTH_REQ_LENGTH 0xa 51 | #define LORAWAN_APP_FTM_PACKAGE_DATA_MAX_SIZE 20 52 | 53 | #define REDUNDANCYMAX 80 54 | 55 | #define DELAY_BW2FCNT 10 // 5s 56 | #define STATUS_ERROR 1 57 | #define STATUS_OK 0 58 | 59 | typedef struct sMcGroupSetParams { 60 | uint8_t McGroupIDHeader; 61 | 62 | uint32_t McAddr; 63 | 64 | uint8_t McKey[16]; 65 | 66 | uint16_t McCountMSB; 67 | 68 | uint32_t Validity; 69 | } McGroupSetParams_t; 70 | 71 | /*! 72 | * Global McClassCSession parameters 73 | */ 74 | typedef struct sMcClassCSessionParams 75 | { 76 | /*! 77 | * is the identifier of the multicast group being used. 78 | */ 79 | uint8_t McGroupIDHeader ; 80 | /*! 81 | * encodes the maximum length in seconds of the multicast fragmentation session 82 | */ 83 | uint32_t TimeOut; 84 | 85 | /*! 86 | * encodes the maximum length in seconds of the multicast fragmentation session 87 | */ 88 | uint32_t TimeToStart; 89 | 90 | /*! 91 | * encodes the maximum length in seconds of the multicast fragmentation session ans 92 | */ 93 | int32_t TimeToStartRec; 94 | 95 | /*! 96 | * equal to the 8LSBs of the device�s uplink frame counter used by the network as the reference to provide the session timing information 97 | */ 98 | uint8_t UlFCountRef; 99 | 100 | /*! 101 | * reception frequency 102 | */ 103 | uint32_t DLFrequencyClassCSession; 104 | 105 | /*! 106 | * datarate of the current class c session 107 | */ 108 | uint8_t DataRateClassCSession ; 109 | 110 | /*! 111 | * bit signals the server that the timing information of the uplink 112 | * specified by UlFCounter is no longer available 113 | */ 114 | uint8_t UlFCounterError ; 115 | 116 | }McClassCSessionParams_t; 117 | 118 | 119 | /*! 120 | * Global DataBlockTransport parameters 121 | */ 122 | typedef struct sDataBlockTransportParams 123 | { 124 | /*! 125 | * Channels TX power 126 | */ 127 | int8_t ChannelsTxPower; 128 | /*! 129 | * Channels data rate 130 | */ 131 | int8_t ChannelsDatarate; 132 | 133 | }DataBlockTransportParams_t; 134 | 135 | /*! 136 | * Global FTMPackage parameters 137 | */ 138 | typedef struct sFTMPackageParams 139 | { 140 | /*! 141 | * identifies the fragmentation session and contains the following fields 142 | */ 143 | uint8_t FragSession ; 144 | /*! 145 | * specifies the total number of fragments of the data block to be transported 146 | * during the coming multicast fragmentation session 147 | */ 148 | uint16_t NbFrag; 149 | 150 | /*! 151 | * is the size in byte of each fragment. 152 | */ 153 | uint8_t FragSize; 154 | 155 | /*! 156 | * FragmentationMatrix encodes the type of fragmentation algorithm used 157 | * Block_ack_delay encodes the amplitude of the random delay that devices have 158 | * to wait between the moment they have reconstructed the full data block and the moment 159 | * they transmit the DataBlockAuthReq message 160 | */ 161 | uint8_t Encoding; 162 | /*! 163 | * specifies the total number of Redundancy fragments of the data block to be transported 164 | * during the coming multicast fragmentation session 165 | */ 166 | uint16_t Redundancy; 167 | 168 | uint8_t Padding; 169 | 170 | }FTMPackageParams_t; 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /src/RadioEvent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2017 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 __RADIO_EVENT_H__ 19 | #define __RADIO_EVENT_H__ 20 | 21 | #include "mbed.h" 22 | 23 | #include "dot_util.h" 24 | #include "mDotEvent.h" 25 | #include "ProtocolLayer.h" 26 | #include "base64.h" 27 | #include "AT45BlockDevice.h" 28 | #include "mbed_lorawan_frag_lib.h" 29 | #include "tiny-aes.h" 30 | #include "mbed_stats.h" 31 | #include "UpdateParameters.h" 32 | #include "UpdateCerts.h" 33 | #include "BDFile.h" 34 | #include "janpatch.h" 35 | #include "mbed_delta_update.h" 36 | 37 | typedef struct { 38 | uint32_t uplinkCounter; 39 | time_t time; 40 | } UplinkEvent_t; 41 | 42 | typedef struct { 43 | uint8_t DevAddr[4]; 44 | uint8_t AppSKey[16]; 45 | uint8_t NwkSKey[16]; 46 | 47 | uint32_t UplinkCounter; 48 | uint32_t DownlinkCounter; 49 | 50 | uint8_t TxDataRate; 51 | uint8_t RxDataRate; 52 | 53 | uint32_t Rx2Frequency; 54 | } LoRaWANCredentials_t; 55 | 56 | static bool compare_buffers(uint8_t* buff1, const uint8_t* buff2, size_t size) { 57 | for (size_t ix = 0; ix < size; ix++) { 58 | if (buff1[ix] != buff2[ix]) return false; 59 | } 60 | return true; 61 | } 62 | 63 | static void calculate_sha256(BlockDevice* bd, size_t offset, size_t size, unsigned char sha_out_buffer[32]) { 64 | uint8_t sha_buffer[128]; 65 | 66 | // SHA256 requires a large buffer, alloc on heap instead of stack 67 | FragmentationSha256* sha256 = new FragmentationSha256(bd, sha_buffer, sizeof(sha_buffer)); 68 | 69 | sha256->calculate( 70 | offset, 71 | size, 72 | sha_out_buffer); 73 | 74 | delete sha256; 75 | } 76 | 77 | static void print_sha256(unsigned char sha_out_buffer[32]) { 78 | debug("SHA256 hash is: "); 79 | for (size_t ix = 0; ix < 32; ix++) { 80 | debug("%02x", sha_out_buffer[ix]); 81 | } 82 | debug("\n"); 83 | } 84 | 85 | class RadioEvent : public mDotEvent 86 | { 87 | 88 | public: 89 | RadioEvent( 90 | // EventQueue* aevent_queue, 91 | Callback*)> asend_msg_cb, 92 | Callback aclass_switch_cb 93 | ) : /*event_queue(aevent_queue), */send_msg_cb(asend_msg_cb), class_switch_cb(aclass_switch_cb) 94 | { 95 | join_succeeded = false; 96 | cls = '0'; 97 | has_received_frag_session = false; 98 | 99 | int ain; 100 | if ((ain = at45.init()) != BD_ERROR_OK) { 101 | printf("Failed to initialize AT45BlockDevice (%d)\n", ain); 102 | } 103 | } 104 | 105 | virtual ~RadioEvent() {} 106 | 107 | /*! 108 | * MAC layer event callback prototype. 109 | * 110 | * \param [IN] flags Bit field indicating the MAC events occurred 111 | * \param [IN] info Details about MAC events occurred 112 | */ 113 | virtual void MacEvent(LoRaMacEventFlags* flags, LoRaMacEventInfo* info) { 114 | HandleMacEvent(flags, info); 115 | 116 | // Process on the events thread which has bigger stack 117 | // event_queue->call(callback(this, &RadioEvent::HandleMacEvent), flags, info); 118 | } 119 | 120 | void OnTx(uint32_t uplinkCounter) { 121 | // move all one up 122 | uplinkEvents[0] = uplinkEvents[1]; 123 | uplinkEvents[1] = uplinkEvents[2]; 124 | uplinkEvents[2] = uplinkEvents[3]; 125 | uplinkEvents[3] = uplinkEvents[4]; 126 | uplinkEvents[4] = uplinkEvents[5]; 127 | uplinkEvents[5] = uplinkEvents[6]; 128 | uplinkEvents[6] = uplinkEvents[7]; 129 | uplinkEvents[7] = uplinkEvents[8]; 130 | uplinkEvents[8] = uplinkEvents[9]; 131 | 132 | uplinkEvents[9].uplinkCounter = uplinkCounter; 133 | uplinkEvents[9].time = time(NULL); 134 | 135 | // logInfo("UplinkEvents are:"); 136 | // printf("\t[0] %li %li\n", uplinkEvents[0].uplinkCounter, uplinkEvents[0].time); 137 | // printf("\t[1] %li %li\n", uplinkEvents[1].uplinkCounter, uplinkEvents[1].time); 138 | // printf("\t[2] %li %li\n", uplinkEvents[2].uplinkCounter, uplinkEvents[2].time); 139 | } 140 | 141 | void switchedToClassC() { 142 | cls = 'C'; 143 | 144 | class_c_cancel_timeout.attach(callback(this, &RadioEvent::ClassCTimeout), class_c_cancel_s); 145 | } 146 | 147 | void switchedToClassA() { 148 | cls = 'A'; 149 | 150 | class_c_cancel_timeout.detach(); 151 | } 152 | 153 | void OnClassAJoinSucceeded(LoRaWANCredentials_t* credentials) { 154 | 155 | join_succeeded = true; 156 | 157 | UpdateClassACredentials(credentials); 158 | 159 | printf("ClassAJoinSucceeded:\n"); 160 | printf("\tDevAddr: %s\n", mts::Text::bin2hexString(class_a_credentials.DevAddr, 4).c_str()); 161 | printf("\tNwkSKey: %s\n", mts::Text::bin2hexString(class_a_credentials.NwkSKey, 16).c_str()); 162 | printf("\tAppSKey: %s\n", mts::Text::bin2hexString(class_a_credentials.AppSKey, 16).c_str()); 163 | printf("\tUplinkCounter: %li\n", class_a_credentials.UplinkCounter); 164 | printf("\tDownlinkCounter: %li\n", class_a_credentials.DownlinkCounter); 165 | printf("\tTxDataRate: %d\n", class_a_credentials.TxDataRate); 166 | printf("\tRxDataRate: %d\n", class_a_credentials.RxDataRate); 167 | 168 | mbed_stats_heap_t heap_stats; 169 | mbed_stats_heap_get(&heap_stats); 170 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 171 | } 172 | 173 | void UpdateClassACredentials(LoRaWANCredentials_t* credentials) { 174 | memcpy(&class_a_credentials, credentials, sizeof(LoRaWANCredentials_t)); 175 | } 176 | 177 | LoRaWANCredentials_t* GetClassACredentials() { 178 | return &class_a_credentials; 179 | } 180 | 181 | LoRaWANCredentials_t* GetClassCCredentials() { 182 | return &class_c_credentials; 183 | } 184 | 185 | private: 186 | 187 | void processFragmentationMacCommand(LoRaMacEventFlags* flags, LoRaMacEventInfo* info) { 188 | switch(info->RxBuffer[0]) { 189 | case FRAG_SESSION_SETUP_REQ: 190 | { 191 | mbed_stats_heap_t heap_stats; 192 | mbed_stats_heap_get(&heap_stats); 193 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 194 | 195 | if(info->RxBufferSize != FRAG_SESSION_SETUP_REQ_LENGTH) { 196 | logError("Invalid FRAG_SESSION_SETUP_REQ command"); 197 | return; 198 | } 199 | 200 | // @todo: fragsession is bit 5 and 6 on byte 1 201 | // @todo: extract mc group from byte 1 202 | frag_params.FragSession = (info->RxBuffer[1] >> 4) & 0x03; 203 | frag_params.NbFrag = ( info->RxBuffer[3] << 8 ) + info->RxBuffer[2]; 204 | frag_params.FragSize = info->RxBuffer[4]; 205 | frag_params.Encoding = info->RxBuffer[5]; 206 | frag_params.Padding = info->RxBuffer[6]; 207 | frag_params.Redundancy = REDUNDANCYMAX-1; 208 | 209 | // [2, 0, 26, 0, 204, 0, 184] 210 | 211 | printf("FRAG_SESSION_SETUP_REQ:\n"); 212 | printf("\tFragSession: %d\n", frag_params.FragSession); 213 | printf("\tNbFrag: %d\n", frag_params.NbFrag); 214 | printf("\tFragSize: %d\n", frag_params.FragSize); 215 | printf("\tEncoding: %d\n", frag_params.Encoding); 216 | printf("\tRedundancy: %d\n", frag_params.Redundancy); 217 | printf("\tPadding: %d\n", frag_params.Padding); 218 | 219 | std::vector* ack = new std::vector(); 220 | ack->push_back(FRAG_SESSION_SETUP_ANS); 221 | ack->push_back(0b01000000); 222 | send_msg_cb(201, ack); 223 | 224 | frag_opts.NumberOfFragments = frag_params.NbFrag; 225 | frag_opts.FragmentSize = frag_params.FragSize; 226 | frag_opts.Padding = frag_params.Padding; 227 | 228 | // @todo: make this dependent on the space on the heap 229 | frag_opts.RedundancyPackets = MBED_CONF_APP_MAX_REDUNDANCY_PACKETS; 230 | 231 | frag_opts.FlashOffset = FOTA_UPDATE_PAGE * at45.get_read_size(); 232 | 233 | if (frag_session != NULL) { 234 | delete frag_session; 235 | } 236 | 237 | mbed_stats_heap_get(&heap_stats); 238 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 239 | 240 | frag_session = new FragmentationSession(&at45, frag_opts); 241 | FragResult result = frag_session->initialize(); 242 | if (result != FRAG_OK) { 243 | printf("FragmentationSession could not initialize! %d %s\n", result, FragmentationSession::frag_result_string(result)); 244 | break; 245 | } 246 | 247 | printf("FragmentationSession initialized OK\n"); 248 | 249 | has_received_frag_session = true; 250 | 251 | mbed_stats_heap_get(&heap_stats); 252 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 253 | } 254 | break; 255 | 256 | case DATA_FRAGMENT: 257 | { 258 | uint16_t frameCounter = (info->RxBuffer[2] << 8) + info->RxBuffer[1]; 259 | 260 | if (frag_session == NULL) return; 261 | 262 | FragResult result; 263 | 264 | if ((result = frag_session->process_frame(frameCounter, info->RxBuffer + 3, info->RxBufferSize - 3)) != FRAG_OK) { 265 | if (result == FRAG_COMPLETE) { 266 | printf("FragmentationSession is complete at frame %d\n", frameCounter); 267 | delete frag_session; 268 | frag_session = NULL; 269 | 270 | mbed_stats_heap_t heap_stats; 271 | mbed_stats_heap_get(&heap_stats); 272 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 273 | 274 | InvokeClassASwitch(); 275 | 276 | // Calculate the CRC of the data in flash to see if the file was unpacked correctly 277 | // CRC64 of the original file is 150eff2bcd891e18 (see fake-fw/test-crc64/main.cpp) 278 | uint8_t crc_buffer[128]; 279 | 280 | FragmentationCrc64 crc64(&at45, crc_buffer, sizeof(crc_buffer)); 281 | uint64_t crc_res = crc64.calculate(frag_opts.FlashOffset, (frag_opts.NumberOfFragments * frag_opts.FragmentSize) - frag_opts.Padding); 282 | 283 | printf("Hash is %08llx\n", crc_res); 284 | 285 | // Write the parameters to flash; but don't set update_pending yet (only after verification by the network) 286 | UpdateParams_t update_params; 287 | update_params.update_pending = 0; 288 | update_params.size = (frag_opts.NumberOfFragments * frag_opts.FragmentSize) - frag_opts.Padding - FOTA_SIGNATURE_LENGTH; 289 | update_params.offset = frag_opts.FlashOffset + FOTA_SIGNATURE_LENGTH; 290 | update_params.signature = UpdateParams_t::MAGIC; 291 | at45.program(&update_params, FOTA_INFO_PAGE * at45.get_read_size(), sizeof(UpdateParams_t)); 292 | 293 | std::vector* ack = new std::vector(); 294 | ack->push_back(DATA_BLOCK_AUTH_REQ); 295 | ack->push_back(frag_params.FragSession); // fragindex 296 | 297 | uint8_t* crc_buff = (uint8_t*)&crc_res; 298 | ack->push_back(crc_buff[0]); 299 | ack->push_back(crc_buff[1]); 300 | ack->push_back(crc_buff[2]); 301 | ack->push_back(crc_buff[3]); 302 | ack->push_back(crc_buff[4]); 303 | ack->push_back(crc_buff[5]); 304 | ack->push_back(crc_buff[6]); 305 | ack->push_back(crc_buff[7]); 306 | 307 | send_msg_cb(201, ack); 308 | 309 | break; 310 | } 311 | else { 312 | printf("FragmentationSession process_frame %d failed: %s\n", 313 | frameCounter, FragmentationSession::frag_result_string(result)); 314 | break; 315 | } 316 | } 317 | 318 | printf("Processed frame with frame counter %d, packets lost %d\n", frameCounter, frag_session->get_lost_frame_count()); 319 | break; 320 | } 321 | break; 322 | 323 | case DATA_BLOCK_AUTH_ANS: 324 | { 325 | // sanity check in case old DATA_BLOCK_AUTH_ANS is still in the queue 326 | if (!has_received_frag_session) return; 327 | 328 | printf("DATA_BLOCK_AUTH_ANS: "); 329 | for (size_t ix = 0; ix < info->RxBufferSize; ix++) { 330 | printf("%02x ", info->RxBuffer[ix]); 331 | } 332 | printf("\n"); 333 | 334 | 335 | 336 | // fragindex and success bit are on info->RxBuffer[1] 337 | if (info->RxBufferSize == 2) { 338 | // not good! 339 | } 340 | else { 341 | // do MIC check... 342 | // if MIC check is OK, then start flashing the firmware 343 | // TTN has MIC not implemented yet 344 | 345 | UpdateParams_t update_params; 346 | // read the current page (with offset and size info) 347 | at45.read(&update_params, FOTA_INFO_PAGE * at45.get_read_size(), sizeof(UpdateParams_t)); 348 | 349 | // Read out the header of the package... 350 | UpdateSignature_t* header = new UpdateSignature_t(); 351 | at45.read(header, update_params.offset - FOTA_SIGNATURE_LENGTH, FOTA_SIGNATURE_LENGTH); 352 | 353 | if (!compare_buffers(header->manufacturer_uuid, UPDATE_CERT_MANUFACTURER_UUID, 16)) { 354 | debug("Manufacturer UUID does not match\n"); 355 | return; 356 | } 357 | 358 | debug("Manufacturer UUID matches\n"); 359 | 360 | if (!compare_buffers(header->device_class_uuid, UPDATE_CERT_DEVICE_CLASS_UUID, 16)) { 361 | debug("Device class UUID does not match\n"); 362 | return; 363 | } 364 | 365 | debug("Device class UUID matches\n"); 366 | 367 | // Is this a diff? 368 | uint8_t* diff_info = (uint8_t*)&header->diff_info; 369 | 370 | printf("Diff? %d, size=%d\n", diff_info[0], (diff_info[1] << 16) + (diff_info[2] << 8) + diff_info[3]); 371 | 372 | if (diff_info[0] == 1) { 373 | int old_size = (diff_info[1] << 16) + (diff_info[2] << 8) + diff_info[3]; 374 | 375 | int v; 376 | 377 | // calculate sha256 hash for current fw & diff file (for debug purposes) 378 | unsigned char sha_out_buff[32]; 379 | calculate_sha256(&at45, FOTA_DIFF_OLD_FW_PAGE * at45.get_read_size(), old_size, sha_out_buff); 380 | debug("Current firmware hash: "); 381 | print_sha256(sha_out_buff); 382 | 383 | calculate_sha256(&at45, update_params.offset, update_params.size, sha_out_buff); 384 | debug("Diff file hash: "); 385 | print_sha256(sha_out_buff); 386 | 387 | // so now use JANPatch 388 | printf("source start=%llu size=%d\n", FOTA_DIFF_OLD_FW_PAGE * at45.get_read_size(), old_size); 389 | BDFILE source(&at45, FOTA_DIFF_OLD_FW_PAGE * at45.get_read_size(), old_size); 390 | printf("diff start=%lu size=%u\n", update_params.offset, update_params.size); 391 | BDFILE diff(&at45, update_params.offset, update_params.size); 392 | printf("target start=%llu\n", FOTA_DIFF_TARGET_PAGE * at45.get_read_size()); 393 | BDFILE target(&at45, FOTA_DIFF_TARGET_PAGE * at45.get_read_size(), 0); 394 | 395 | v = apply_delta_update(&at45, 528, &source, &diff, &target); 396 | 397 | if (v != MBED_DELTA_UPDATE_OK) { 398 | debug("apply_delta_update failed %d\n", v); 399 | return; 400 | } 401 | 402 | debug("Patched firmware length is %ld\n", target.ftell()); 403 | 404 | update_params.offset = FOTA_DIFF_TARGET_PAGE * at45.get_read_size(); 405 | update_params.size = target.ftell(); 406 | } 407 | 408 | 409 | // Calculate the SHA256 hash of the file, and then verify whether the signature was signed with a trusted private key 410 | unsigned char sha_out_buffer[32]; 411 | { 412 | calculate_sha256(&at45, update_params.offset, update_params.size, sha_out_buffer); 413 | 414 | debug("Patched firmware hash: "); 415 | print_sha256(sha_out_buffer); 416 | 417 | mbed_stats_heap_t heap_stats; 418 | mbed_stats_heap_get(&heap_stats); 419 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 420 | 421 | // now check that the signature is correct... 422 | { 423 | printf("ECDSA signature is: "); 424 | for (size_t ix = 0; ix < header->signature_length; ix++) { 425 | printf("%02x ", header->signature[ix]); 426 | } 427 | printf("\n"); 428 | 429 | // ECDSA requires a large buffer, alloc on heap instead of stack 430 | FragmentationEcdsaVerify* ecdsa = new FragmentationEcdsaVerify(UPDATE_CERT_PUBKEY, UPDATE_CERT_LENGTH); 431 | bool valid = ecdsa->verify(sha_out_buffer, header->signature, header->signature_length); 432 | if (!valid) { 433 | debug("ECDSA verification of firmware failed\n"); 434 | free(header); 435 | return; 436 | } 437 | else { 438 | debug("ECDSA verification OK\n"); 439 | } 440 | 441 | delete ecdsa; 442 | } 443 | } 444 | 445 | free(header); 446 | 447 | // Hash is matching, now populate the FOTA_INFO_PAGE with information about the update, so the bootloader can flash the update 448 | if (1) { 449 | update_params.update_pending = 1; 450 | memcpy(update_params.sha256_hash, sha_out_buffer, sizeof(sha_out_buffer)); 451 | at45.program(&update_params, FOTA_INFO_PAGE * at45.get_read_size(), sizeof(UpdateParams_t)); 452 | 453 | debug("Stored the update parameters in flash on page 0x%x\n", FOTA_INFO_PAGE); 454 | } 455 | else { 456 | debug("Has not stored update parameters in flash, override in RadioEvent.h\n"); 457 | } 458 | 459 | // and now reboot the device... 460 | printf("System going down for reset *NOW*!\n"); 461 | NVIC_SystemReset(); 462 | } 463 | } 464 | break; 465 | } 466 | } 467 | 468 | void processMulticastMacCommand(LoRaMacEventFlags* flags, LoRaMacEventInfo* info) { 469 | switch (info->RxBuffer[0]) { 470 | case MC_GROUP_SETUP_REQ: 471 | { 472 | class_c_group_params.McGroupIDHeader = info->RxBuffer[1]; 473 | class_c_group_params.McAddr = (info->RxBuffer[5] << 24 ) + ( info->RxBuffer[4] << 16 ) + ( info->RxBuffer[3] << 8 ) + info->RxBuffer[2]; 474 | memcpy(class_c_group_params.McKey, info->RxBuffer + 6, 16); 475 | 476 | class_c_group_params.McCountMSB = (info->RxBuffer[23] << 8) + info->RxBuffer[22]; 477 | class_c_group_params.Validity = (info->RxBuffer[27] << 24 ) + ( info->RxBuffer[26] << 16 ) + ( info->RxBuffer[25] << 8 ) + info->RxBuffer[24]; 478 | 479 | printf("MC_GROUP_SETUP_REQ:\n"); 480 | printf("\tMcGroupIDHeader: %d\n", class_c_group_params.McGroupIDHeader); 481 | printf("\tMcAddr: %s\n", mts::Text::bin2hexString((uint8_t*)&class_c_group_params.McAddr, 4).c_str()); 482 | printf("\tMcKey: %s\n", mts::Text::bin2hexString(class_c_group_params.McKey, 16).c_str()); 483 | printf("\tMcCountMSB: %d\n", class_c_group_params.McCountMSB); 484 | printf("\tValidity: %li\n", class_c_group_params.Validity); 485 | 486 | memcpy(class_c_credentials.DevAddr, &class_c_group_params.McAddr, 4); 487 | 488 | const uint8_t nwk_input[16] = { 0x01, class_c_credentials.DevAddr[0], class_c_credentials.DevAddr[1], class_c_credentials.DevAddr[2], class_c_credentials.DevAddr[3], 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; 489 | const uint8_t app_input[16] = { 0x02, class_c_credentials.DevAddr[0], class_c_credentials.DevAddr[1], class_c_credentials.DevAddr[2], class_c_credentials.DevAddr[3], 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; 490 | 491 | AES_ECB_encrypt(nwk_input, class_c_group_params.McKey, class_c_credentials.NwkSKey, 16); 492 | AES_ECB_encrypt(app_input, class_c_group_params.McKey, class_c_credentials.AppSKey, 16); 493 | 494 | printf("ClassCCredentials:\n"); 495 | printf("\tDevAddr: %s\n", mts::Text::bin2hexString(class_c_credentials.DevAddr, 4).c_str()); 496 | printf("\tNwkSKey: %s\n", mts::Text::bin2hexString(class_c_credentials.NwkSKey, 16).c_str()); 497 | printf("\tAppSKey: %s\n", mts::Text::bin2hexString(class_c_credentials.AppSKey, 16).c_str()); 498 | 499 | std::vector* ack = new std::vector(); 500 | ack->push_back(MC_GROUP_SETUP_ANS); 501 | ack->push_back(class_c_group_params.McGroupIDHeader); 502 | send_msg_cb(200, ack); 503 | } 504 | break; 505 | 506 | case MC_CLASSC_SESSION_REQ: 507 | { 508 | McClassCSessionParams_t class_c_session_params; 509 | class_c_session_params.McGroupIDHeader = info->RxBuffer[1] & 0x03 ; 510 | class_c_session_params.TimeOut = info->RxBuffer[4] >> 4; 511 | class_c_session_params.TimeToStart = ((info->RxBuffer[4] & 0x0F ) << 16 ) + ( info->RxBuffer[3] << 8 ) + info->RxBuffer[2]; 512 | class_c_session_params.UlFCountRef = info->RxBuffer[5]; 513 | class_c_session_params.DLFrequencyClassCSession = (info->RxBuffer[8] << 16 ) + ( info->RxBuffer[7] << 8 ) + info->RxBuffer[6]; 514 | class_c_session_params.DataRateClassCSession = info->RxBuffer[9]; 515 | 516 | printf("MC_CLASSC_SESSION_REQ:\n"); 517 | printf("\tMcGroupIDHeader: %d\n", class_c_session_params.McGroupIDHeader); 518 | printf("\tTimeOut: %lu\n", class_c_session_params.TimeOut); 519 | printf("\tTimeToStart: %li\n", class_c_session_params.TimeToStart); 520 | printf("\tUlFCountRef: %d\n", class_c_session_params.UlFCountRef); 521 | printf("\tDLFrequencyClassCSession: %li\n", class_c_session_params.DLFrequencyClassCSession); 522 | printf("\tDataRateClassCSession: %d\n", class_c_session_params.DataRateClassCSession); 523 | 524 | class_c_credentials.TxDataRate = class_c_session_params.DataRateClassCSession; 525 | class_c_credentials.RxDataRate = class_c_session_params.DataRateClassCSession; 526 | 527 | class_c_credentials.UplinkCounter = 0; 528 | class_c_credentials.DownlinkCounter = 0; 529 | 530 | class_c_credentials.Rx2Frequency = class_c_session_params.DLFrequencyClassCSession; 531 | 532 | std::vector* ack = new std::vector(); 533 | ack->push_back(MC_CLASSC_SESSION_ANS); 534 | 535 | uint8_t status = class_c_session_params.McGroupIDHeader; 536 | 537 | class_c_cancel_s = pow(2.0f, static_cast(class_c_session_params.TimeOut)); 538 | 539 | bool switch_err = false; 540 | 541 | // so time to start depends on the UlFCountRef... 542 | UplinkEvent_t ulEvent; 543 | bool foundUlEvent = false; 544 | 545 | for (size_t ix = 0; ix < 10; ix++) { 546 | if (class_c_session_params.UlFCountRef == uplinkEvents[ix].uplinkCounter) { 547 | ulEvent = uplinkEvents[ix]; 548 | foundUlEvent = true; 549 | } 550 | } 551 | 552 | if (!foundUlEvent) { 553 | logError("UlFCountRef %d not found in uplinkEvents array", class_c_session_params.UlFCountRef); 554 | status += 0b00000100; 555 | switch_err = true; 556 | } 557 | 558 | ack->push_back(status); 559 | 560 | // going to switch to class C in... ulEvent.time + params.TimeToStart 561 | if (!switch_err) { 562 | time_t switch_to_class_c_t = ulEvent.time + class_c_session_params.TimeToStart - time(NULL); 563 | printf("Going to switch to class C in %li seconds\n", switch_to_class_c_t); 564 | 565 | if (switch_to_class_c_t < 0) { 566 | switch_to_class_c_t = 1; 567 | } 568 | 569 | // class_c_start_timeout.attach(event_queue->event(this, &RadioEvent::InvokeClassCSwitch), switch_to_class_c_t); 570 | class_c_start_timeout.attach(callback(this, &RadioEvent::InvokeClassCSwitch), switch_to_class_c_t); 571 | 572 | // timetostart in seconds 573 | // @TODO: if this message fails to ack we should update this timing here otherwise the server is out of sync 574 | ack->push_back(switch_to_class_c_t & 0xff); 575 | ack->push_back(switch_to_class_c_t >> 8 & 0xff); 576 | ack->push_back(switch_to_class_c_t >> 16 & 0xff); 577 | } 578 | 579 | send_msg_cb(200, ack); 580 | } 581 | 582 | break; 583 | 584 | default: 585 | printf("Got MAC command, but ignoring... %d\n", info->RxBuffer[0]); 586 | break; 587 | } 588 | } 589 | 590 | void InvokeClassCSwitch() { 591 | // no frag_session? abort 592 | if (frag_session == NULL) { 593 | logError("Refusing class C switch. No frag_session"); 594 | return; 595 | } 596 | 597 | class_switch_cb('C'); 598 | } 599 | 600 | void InvokeClassASwitch() { 601 | class_switch_cb('A'); 602 | } 603 | 604 | void ClassCTimeout() { 605 | if (cls == 'C') { 606 | logInfo("Class C Timeout"); 607 | 608 | InvokeClassASwitch(); 609 | } 610 | } 611 | 612 | void HandleMacEvent(LoRaMacEventFlags* flags, LoRaMacEventInfo* info) { 613 | 614 | if (mts::MTSLog::getLogLevel() == mts::MTSLog::TRACE_LEVEL) { 615 | std::string msg = "OK"; 616 | switch (info->Status) { 617 | case LORAMAC_EVENT_INFO_STATUS_ERROR: 618 | msg = "ERROR"; 619 | break; 620 | case LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT: 621 | msg = "TX_TIMEOUT"; 622 | break; 623 | case LORAMAC_EVENT_INFO_STATUS_RX_TIMEOUT: 624 | msg = "RX_TIMEOUT"; 625 | break; 626 | case LORAMAC_EVENT_INFO_STATUS_RX_ERROR: 627 | msg = "RX_ERROR"; 628 | break; 629 | case LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL: 630 | msg = "JOIN_FAIL"; 631 | break; 632 | case LORAMAC_EVENT_INFO_STATUS_DOWNLINK_FAIL: 633 | msg = "DOWNLINK_FAIL"; 634 | break; 635 | case LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL: 636 | msg = "ADDRESS_FAIL"; 637 | break; 638 | case LORAMAC_EVENT_INFO_STATUS_MIC_FAIL: 639 | msg = "MIC_FAIL"; 640 | break; 641 | default: 642 | break; 643 | } 644 | logTrace("Event: %s", msg.c_str()); 645 | 646 | logTrace("Flags Tx: %d Rx: %d RxData: %d RxSlot: %d LinkCheck: %d JoinAccept: %d", 647 | flags->Bits.Tx, flags->Bits.Rx, flags->Bits.RxData, flags->Bits.RxSlot, flags->Bits.LinkCheck, flags->Bits.JoinAccept); 648 | logTrace("Info: Status: %d ACK: %d Retries: %d TxDR: %d RxPort: %d RxSize: %d RSSI: %d SNR: %d Energy: %d Margin: %d Gateways: %d", 649 | info->Status, info->TxAckReceived, info->TxNbRetries, info->TxDatarate, info->RxPort, info->RxBufferSize, 650 | info->RxRssi, info->RxSnr, info->Energy, info->DemodMargin, info->NbGateways); 651 | } 652 | 653 | if (flags->Bits.Rx) { 654 | 655 | logDebug("Rx %d bytes", info->RxBufferSize); 656 | if (info->RxBufferSize > 0) { 657 | if (cls == 'C') { 658 | class_c_cancel_timeout.attach(callback(this, &RadioEvent::ClassCTimeout), class_c_cancel_s); 659 | } 660 | 661 | // Forward the data to the target MCU 662 | // logInfo("PacketRx port=%d, size=%d, rssi=%d, FPending=%d", info->RxPort, info->RxBufferSize, info->RxRssi, 0); 663 | // logInfo("Rx data: %s", mts::Text::bin2hexString(info->RxBuffer, info->RxBufferSize).c_str()); 664 | 665 | // Process MAC events ourselves 666 | if (info->RxPort == 200) { 667 | processMulticastMacCommand(flags, info); 668 | } 669 | else if (info->RxPort == 201) { 670 | processFragmentationMacCommand(flags, info); 671 | } 672 | } 673 | } 674 | } 675 | 676 | 677 | Callback*)> send_msg_cb; 678 | Callback class_switch_cb; 679 | UplinkEvent_t uplinkEvents[10]; 680 | 681 | McClassCSessionParams_t class_c_session_params; 682 | McGroupSetParams_t class_c_group_params; 683 | FTMPackageParams_t frag_params; 684 | 685 | LoRaWANCredentials_t class_a_credentials; 686 | LoRaWANCredentials_t class_c_credentials; 687 | 688 | Timeout class_c_start_timeout; 689 | Timeout class_c_cancel_timeout; 690 | uint32_t class_c_cancel_s; 691 | 692 | AT45BlockDevice at45; 693 | FragmentationSession* frag_session; 694 | FragmentationSessionOpts_t frag_opts; 695 | 696 | bool join_succeeded; 697 | char cls; 698 | bool has_received_frag_session; 699 | }; 700 | 701 | #endif 702 | 703 | -------------------------------------------------------------------------------- /src/UpdateParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2017 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 | /** 19 | * This file is shared between bootloader and the target application 20 | */ 21 | 22 | #ifndef _MBED_FOTA_UPDATE_PARAMS 23 | #define _MBED_FOTA_UPDATE_PARAMS 24 | 25 | // These values need to be the same between target application and bootloader! 26 | #define FOTA_INFO_PAGE 0x1800 // The information page for the firmware update 27 | #define FOTA_UPDATE_PAGE 0x1801 // The update starts at this page (and then continues) 28 | #define FOTA_DIFF_OLD_FW_PAGE 0x2100 29 | #define FOTA_DIFF_TARGET_PAGE 0x2500 30 | #define FOTA_SIGNATURE_LENGTH sizeof(UpdateSignature_t) // Length of ECDSA signature + class UUIDs + diff struct (5 bytes) -> matches sizeof(UpdateSignature_t) 31 | 32 | // This structure is shared between the bootloader and the target application 33 | // it contains information on whether there's an update pending, and the hash of the update 34 | struct UpdateParams_t { 35 | bool update_pending; // whether there's a pending update 36 | size_t size; // size of the update 37 | uint32_t offset; // Location of the patch in flash 38 | unsigned char sha256_hash[32]; // SHA256 hash of the update block 39 | 40 | uint32_t signature; // the value of MAGIC (to indicate that we actually wrote to this block) 41 | 42 | static const uint32_t MAGIC = 0x1BEAC000; 43 | }; 44 | 45 | // This structure contains the update header (which is the first FOTA_SIGNATURE_LENGTH bytes of a package) 46 | typedef struct __attribute__((__packed__)) { 47 | uint8_t signature_length; // Length of the ECDSA/SHA256 signature 48 | unsigned char signature[72]; // ECDSA/SHA256 signature, signed with private key of the firmware (after applying patching) 49 | uint8_t manufacturer_uuid[16]; // Manufacturer UUID 50 | uint8_t device_class_uuid[16]; // Device Class UUID 51 | 52 | uint32_t diff_info; // first byte indicates whether this is a diff, last three bytes are the size of the *old* file 53 | } UpdateSignature_t; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/dot_util.cpp: -------------------------------------------------------------------------------- 1 | #include "dot_util.h" 2 | #if defined(TARGET_XDOT_L151CC) 3 | #include "xdot_low_power.h" 4 | #endif 5 | 6 | #include "mbed_stats.h" 7 | 8 | #if defined(TARGET_MTS_MDOT_F411RE) 9 | uint32_t portA[6]; 10 | uint32_t portB[6]; 11 | uint32_t portC[6]; 12 | uint32_t portD[6]; 13 | uint32_t portH[6]; 14 | #endif 15 | 16 | 17 | void display_config() { 18 | // display configuration and library version information 19 | logInfo("====================="); 20 | logInfo("general configuration"); 21 | logInfo("====================="); 22 | logInfo("version ------------------ %s", dot->getId().c_str()); 23 | logInfo("device ID/EUI ------------ %s", mts::Text::bin2hexString(dot->getDeviceId()).c_str()); 24 | logInfo("frequency band ----------- %s", mDot::FrequencyBandStr(dot->getFrequencyBand()).c_str()); 25 | logInfo("frequency sub band ------- %u", dot->getFrequencySubBand()); 26 | logInfo("public network ----------- %s", dot->getPublicNetwork() ? "on" : "off"); 27 | logInfo("========================="); 28 | logInfo("credentials configuration"); 29 | logInfo("========================="); 30 | logInfo("device class ------------- %s", dot->getClass().c_str()); 31 | logInfo("network join mode -------- %s", mDot::JoinModeStr(dot->getJoinMode()).c_str()); 32 | if (dot->getJoinMode() == mDot::MANUAL || dot->getJoinMode() == mDot::PEER_TO_PEER) { 33 | logInfo("network address ---------- %s", mts::Text::bin2hexString(dot->getNetworkAddress()).c_str()); 34 | logInfo("network session key------- %s", mts::Text::bin2hexString(dot->getNetworkSessionKey()).c_str()); 35 | logInfo("data session key---------- %s", mts::Text::bin2hexString(dot->getDataSessionKey()).c_str()); 36 | } else { 37 | logInfo("network name ------------- %s", dot->getNetworkName().c_str()); 38 | logInfo("network phrase ----------- %s", dot->getNetworkPassphrase().c_str()); 39 | logInfo("network EUI -------------- %s", mts::Text::bin2hexString(dot->getNetworkId()).c_str()); 40 | logInfo("network KEY -------------- %s", mts::Text::bin2hexString(dot->getNetworkKey()).c_str()); 41 | } 42 | logInfo("========================"); 43 | logInfo("communication parameters"); 44 | logInfo("========================"); 45 | if (dot->getJoinMode() == mDot::PEER_TO_PEER) { 46 | logInfo("TX frequency ------------- %lu", dot->getTxFrequency()); 47 | } else { 48 | logInfo("acks --------------------- %s, %u attempts", dot->getAck() > 0 ? "on" : "off", dot->getAck()); 49 | } 50 | logInfo("TX datarate -------------- %s", mDot::DataRateStr(dot->getTxDataRate()).c_str()); 51 | logInfo("TX power ----------------- %lu dBm", dot->getTxPower()); 52 | logInfo("antenna gain ------------- %u dBm", dot->getAntennaGain()); 53 | } 54 | 55 | void update_ota_config_name_phrase(std::string network_name, std::string network_passphrase, uint8_t frequency_sub_band, bool public_network, uint8_t ack) { 56 | std::string current_network_name = dot->getNetworkName(); 57 | std::string current_network_passphrase = dot->getNetworkPassphrase(); 58 | uint8_t current_frequency_sub_band = dot->getFrequencySubBand(); 59 | bool current_public_network = dot->getPublicNetwork(); 60 | uint8_t current_ack = dot->getAck(); 61 | 62 | if (current_network_name != network_name) { 63 | logInfo("changing network name from \"%s\" to \"%s\"", current_network_name.c_str(), network_name.c_str()); 64 | if (dot->setNetworkName(network_name) != mDot::MDOT_OK) { 65 | logError("failed to set network name to \"%s\"", network_name.c_str()); 66 | } 67 | } 68 | 69 | if (current_network_passphrase != network_passphrase) { 70 | logInfo("changing network passphrase from \"%s\" to \"%s\"", current_network_passphrase.c_str(), network_passphrase.c_str()); 71 | if (dot->setNetworkPassphrase(network_passphrase) != mDot::MDOT_OK) { 72 | logError("failed to set network passphrase to \"%s\"", network_passphrase.c_str()); 73 | } 74 | } 75 | 76 | if (current_frequency_sub_band != frequency_sub_band) { 77 | logInfo("changing frequency sub band from %u to %u", current_frequency_sub_band, frequency_sub_band); 78 | if (dot->setFrequencySubBand(frequency_sub_band) != mDot::MDOT_OK) { 79 | logError("failed to set frequency sub band to %u", frequency_sub_band); 80 | } 81 | } 82 | 83 | if (current_public_network != public_network) { 84 | logInfo("changing public network from %s to %s", current_public_network ? "on" : "off", public_network ? "on" : "off"); 85 | if (dot->setPublicNetwork(public_network) != mDot::MDOT_OK) { 86 | logError("failed to set public network to %s", public_network ? "on" : "off"); 87 | } 88 | } 89 | 90 | if (current_ack != ack) { 91 | logInfo("changing acks from %u to %u", current_ack, ack); 92 | if (dot->setAck(ack) != mDot::MDOT_OK) { 93 | logError("failed to set acks to %u", ack); 94 | } 95 | } 96 | } 97 | 98 | void update_ota_config_id_key(uint8_t *network_id, uint8_t *network_key, uint8_t frequency_sub_band, bool public_network, uint8_t ack) { 99 | std::vector current_network_id = dot->getNetworkId(); 100 | std::vector current_network_key = dot->getNetworkKey(); 101 | uint8_t current_frequency_sub_band = dot->getFrequencySubBand(); 102 | bool current_public_network = dot->getPublicNetwork(); 103 | uint8_t current_ack = dot->getAck(); 104 | 105 | std::vector network_id_vector(network_id, network_id + 8); 106 | std::vector network_key_vector(network_key, network_key + 16); 107 | 108 | if (current_network_id != network_id_vector) { 109 | logInfo("changing network ID from \"%s\" to \"%s\"", mts::Text::bin2hexString(current_network_id).c_str(), mts::Text::bin2hexString(network_id_vector).c_str()); 110 | if (dot->setNetworkId(network_id_vector) != mDot::MDOT_OK) { 111 | logError("failed to set network ID to \"%s\"", mts::Text::bin2hexString(network_id_vector).c_str()); 112 | } 113 | } 114 | 115 | if (current_network_key != network_key_vector) { 116 | logInfo("changing network KEY from \"%s\" to \"%s\"", mts::Text::bin2hexString(current_network_key).c_str(), mts::Text::bin2hexString(network_key_vector).c_str()); 117 | if (dot->setNetworkKey(network_key_vector) != mDot::MDOT_OK) { 118 | logError("failed to set network KEY to \"%s\"", mts::Text::bin2hexString(network_key_vector).c_str()); 119 | } 120 | } 121 | 122 | if (current_frequency_sub_band != frequency_sub_band) { 123 | logInfo("changing frequency sub band from %u to %u", current_frequency_sub_band, frequency_sub_band); 124 | if (dot->setFrequencySubBand(frequency_sub_band) != mDot::MDOT_OK) { 125 | logError("failed to set frequency sub band to %u", frequency_sub_band); 126 | } 127 | } 128 | 129 | if (current_public_network != public_network) { 130 | logInfo("changing public network from %s to %s", current_public_network ? "on" : "off", public_network ? "on" : "off"); 131 | if (dot->setPublicNetwork(public_network) != mDot::MDOT_OK) { 132 | logError("failed to set public network to %s", public_network ? "on" : "off"); 133 | } 134 | } 135 | 136 | if (current_ack != ack) { 137 | logInfo("changing acks from %u to %u", current_ack, ack); 138 | if (dot->setAck(ack) != mDot::MDOT_OK) { 139 | logError("failed to set acks to %u", ack); 140 | } 141 | } 142 | } 143 | 144 | void update_manual_config(uint8_t *network_address, uint8_t *network_session_key, uint8_t *data_session_key, uint8_t frequency_sub_band, bool public_network, uint8_t ack) { 145 | std::vector current_network_address = dot->getNetworkAddress(); 146 | std::vector current_network_session_key = dot->getNetworkSessionKey(); 147 | std::vector current_data_session_key = dot->getDataSessionKey(); 148 | uint8_t current_frequency_sub_band = dot->getFrequencySubBand(); 149 | bool current_public_network = dot->getPublicNetwork(); 150 | uint8_t current_ack = dot->getAck(); 151 | 152 | std::vector network_address_vector(network_address, network_address + 4); 153 | std::vector network_session_key_vector(network_session_key, network_session_key + 16); 154 | std::vector data_session_key_vector(data_session_key, data_session_key + 16); 155 | 156 | if (current_network_address != network_address_vector) { 157 | logInfo("changing network address from \"%s\" to \"%s\"", mts::Text::bin2hexString(current_network_address).c_str(), mts::Text::bin2hexString(network_address_vector).c_str()); 158 | if (dot->setNetworkAddress(network_address_vector) != mDot::MDOT_OK) { 159 | logError("failed to set network address to \"%s\"", mts::Text::bin2hexString(network_address_vector).c_str()); 160 | } 161 | } 162 | 163 | if (current_network_session_key != network_session_key_vector) { 164 | logInfo("changing network session key from \"%s\" to \"%s\"", mts::Text::bin2hexString(current_network_session_key).c_str(), mts::Text::bin2hexString(network_session_key_vector).c_str()); 165 | if (dot->setNetworkSessionKey(network_session_key_vector) != mDot::MDOT_OK) { 166 | logError("failed to set network session key to \"%s\"", mts::Text::bin2hexString(network_session_key_vector).c_str()); 167 | } 168 | } 169 | 170 | if (current_data_session_key != data_session_key_vector) { 171 | logInfo("changing data session key from \"%s\" to \"%s\"", mts::Text::bin2hexString(current_data_session_key).c_str(), mts::Text::bin2hexString(data_session_key_vector).c_str()); 172 | if (dot->setDataSessionKey(data_session_key_vector) != mDot::MDOT_OK) { 173 | logError("failed to set data session key to \"%s\"", mts::Text::bin2hexString(data_session_key_vector).c_str()); 174 | } 175 | } 176 | 177 | if (current_frequency_sub_band != frequency_sub_band) { 178 | logInfo("changing frequency sub band from %u to %u", current_frequency_sub_band, frequency_sub_band); 179 | if (dot->setFrequencySubBand(frequency_sub_band) != mDot::MDOT_OK) { 180 | logError("failed to set frequency sub band to %u", frequency_sub_band); 181 | } 182 | } 183 | 184 | if (current_public_network != public_network) { 185 | logInfo("changing public network from %s to %s", current_public_network ? "on" : "off", public_network ? "on" : "off"); 186 | if (dot->setPublicNetwork(public_network) != mDot::MDOT_OK) { 187 | logError("failed to set public network to %s", public_network ? "on" : "off"); 188 | } 189 | } 190 | 191 | if (current_ack != ack) { 192 | logInfo("changing acks from %u to %u", current_ack, ack); 193 | if (dot->setAck(ack) != mDot::MDOT_OK) { 194 | logError("failed to set acks to %u", ack); 195 | } 196 | } 197 | } 198 | 199 | void update_peer_to_peer_config(uint8_t *network_address, uint8_t *network_session_key, uint8_t *data_session_key, uint32_t tx_frequency, uint8_t tx_datarate, uint8_t tx_power) { 200 | std::vector current_network_address = dot->getNetworkAddress(); 201 | std::vector current_network_session_key = dot->getNetworkSessionKey(); 202 | std::vector current_data_session_key = dot->getDataSessionKey(); 203 | uint32_t current_tx_frequency = dot->getTxFrequency(); 204 | uint8_t current_tx_datarate = dot->getTxDataRate(); 205 | uint8_t current_tx_power = dot->getTxPower(); 206 | 207 | std::vector network_address_vector(network_address, network_address + 4); 208 | std::vector network_session_key_vector(network_session_key, network_session_key + 16); 209 | std::vector data_session_key_vector(data_session_key, data_session_key + 16); 210 | 211 | if (current_network_address != network_address_vector) { 212 | logInfo("changing network address from \"%s\" to \"%s\"", mts::Text::bin2hexString(current_network_address).c_str(), mts::Text::bin2hexString(network_address_vector).c_str()); 213 | if (dot->setNetworkAddress(network_address_vector) != mDot::MDOT_OK) { 214 | logError("failed to set network address to \"%s\"", mts::Text::bin2hexString(network_address_vector).c_str()); 215 | } 216 | } 217 | 218 | if (current_network_session_key != network_session_key_vector) { 219 | logInfo("changing network session key from \"%s\" to \"%s\"", mts::Text::bin2hexString(current_network_session_key).c_str(), mts::Text::bin2hexString(network_session_key_vector).c_str()); 220 | if (dot->setNetworkSessionKey(network_session_key_vector) != mDot::MDOT_OK) { 221 | logError("failed to set network session key to \"%s\"", mts::Text::bin2hexString(network_session_key_vector).c_str()); 222 | } 223 | } 224 | 225 | if (current_data_session_key != data_session_key_vector) { 226 | logInfo("changing data session key from \"%s\" to \"%s\"", mts::Text::bin2hexString(current_data_session_key).c_str(), mts::Text::bin2hexString(data_session_key_vector).c_str()); 227 | if (dot->setDataSessionKey(data_session_key_vector) != mDot::MDOT_OK) { 228 | logError("failed to set data session key to \"%s\"", mts::Text::bin2hexString(data_session_key_vector).c_str()); 229 | } 230 | } 231 | 232 | if (current_tx_frequency != tx_frequency) { 233 | logInfo("changing TX frequency from %lu to %lu", current_tx_frequency, tx_frequency); 234 | if (dot->setTxFrequency(tx_frequency) != mDot::MDOT_OK) { 235 | logError("failed to set TX frequency to %lu", tx_frequency); 236 | } 237 | } 238 | 239 | if (current_tx_datarate != tx_datarate) { 240 | logInfo("changing TX datarate from %u to %u", current_tx_datarate, tx_datarate); 241 | if (dot->setTxDataRate(tx_datarate) != mDot::MDOT_OK) { 242 | logError("failed to set TX datarate to %u", tx_datarate); 243 | } 244 | } 245 | 246 | if (current_tx_power != tx_power) { 247 | logInfo("changing TX power from %u to %u", current_tx_power, tx_power); 248 | if (dot->setTxPower(tx_power) != mDot::MDOT_OK) { 249 | logError("failed to set TX power to %u", tx_power); 250 | } 251 | } 252 | } 253 | 254 | void update_network_link_check_config(uint8_t link_check_count, uint8_t link_check_threshold) { 255 | uint8_t current_link_check_count = dot->getLinkCheckCount(); 256 | uint8_t current_link_check_threshold = dot->getLinkCheckThreshold(); 257 | 258 | if (current_link_check_count != link_check_count) { 259 | logInfo("changing link check count from %u to %u", current_link_check_count, link_check_count); 260 | if (dot->setLinkCheckCount(link_check_count) != mDot::MDOT_OK) { 261 | logError("failed to set link check count to %u", link_check_count); 262 | } 263 | } 264 | 265 | if (current_link_check_threshold != link_check_threshold) { 266 | logInfo("changing link check threshold from %u to %u", current_link_check_threshold, link_check_threshold); 267 | if (dot->setLinkCheckThreshold(link_check_threshold) != mDot::MDOT_OK) { 268 | logError("failed to set link check threshold to %u", link_check_threshold); 269 | } 270 | } 271 | } 272 | 273 | void join_network() { 274 | int32_t j_attempts = 0; 275 | int32_t ret = mDot::MDOT_ERROR; 276 | 277 | // attempt to join the network 278 | while (ret != mDot::MDOT_OK) { 279 | logInfo("attempt %d to join network", ++j_attempts); 280 | ret = dot->joinNetwork(); 281 | if (ret != mDot::MDOT_OK) { 282 | logError("failed to join network %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); 283 | 284 | mbed_stats_heap_t heap_stats; 285 | mbed_stats_heap_get(&heap_stats); 286 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 287 | 288 | // in some frequency bands we need to wait until another channel is available before transmitting again 289 | uint32_t delay_s = (dot->getNextTxMs() / 1000) + 1; 290 | if (delay_s < 2) { 291 | logInfo("waiting %lu s until next free channel", delay_s); 292 | wait(delay_s); 293 | } else { 294 | logInfo("sleeping %lu s until next free channel", delay_s); 295 | dot->sleep(delay_s, mDot::RTC_ALARM, false); 296 | } 297 | } 298 | } 299 | } 300 | 301 | void sleep_wake_rtc_only(uint32_t delay_s, bool deepsleep) { 302 | delay_s = calculate_actual_sleep_time(delay_s); 303 | 304 | logInfo("%ssleeping %lus", deepsleep ? "deep" : "", delay_s); 305 | logInfo("application will %s after waking up", deepsleep ? "execute from beginning" : "resume"); 306 | 307 | // lowest current consumption in sleep mode can only be achieved by configuring IOs as analog inputs with no pull resistors 308 | // the library handles all internal IOs automatically, but the external IOs are the application's responsibility 309 | // certain IOs may require internal pullup or pulldown resistors because leaving them floating would cause extra current consumption 310 | // for xDot: UART_*, I2C_*, SPI_*, GPIO*, WAKE 311 | // for mDot: XBEE_*, USBTX, USBRX, PB_0, PB_1 312 | // steps are: 313 | // * save IO configuration 314 | // * configure IOs to reduce current consumption 315 | // * sleep 316 | // * restore IO configuration 317 | if (! deepsleep) { 318 | // save the GPIO state. 319 | sleep_save_io(); 320 | 321 | // configure GPIOs for lowest current 322 | sleep_configure_io(); 323 | } 324 | 325 | // go to sleep/deepsleep for delay_s seconds and wake using the RTC alarm 326 | dot->sleep(delay_s, mDot::RTC_ALARM, deepsleep); 327 | 328 | if (! deepsleep) { 329 | // restore the GPIO state. 330 | sleep_restore_io(); 331 | } 332 | } 333 | 334 | void sleep_wake_interrupt_only(bool deepsleep) { 335 | #if defined (TARGET_XDOT_L151CC) 336 | if (deepsleep) { 337 | // for xDot, WAKE pin (connected to S2 on xDot-DK) is the only pin that can wake the processor from deepsleep 338 | // it is automatically configured when INTERRUPT or RTC_ALARM_OR_INTERRUPT is the wakeup source and deepsleep is true in the mDot::sleep call 339 | } else { 340 | // configure WAKE pin (connected to S2 on xDot-DK) as the pin that will wake the xDot from low power modes 341 | // other pins can be confgured instead: GPIO0-3 or UART_RX 342 | dot->setWakePin(WAKE); 343 | } 344 | 345 | logInfo("%ssleeping until interrupt on %s pin", deepsleep ? "deep" : "", deepsleep ? "WAKE" : mDot::pinName2Str(dot->getWakePin()).c_str()); 346 | #else 347 | 348 | if (deepsleep) { 349 | // for mDot, XBEE_DIO7 pin is the only pin that can wake the processor from deepsleep 350 | // it is automatically configured when INTERRUPT or RTC_ALARM_OR_INTERRUPT is the wakeup source and deepsleep is true in the mDot::sleep call 351 | } else { 352 | // configure XBEE_DIO7 pin as the pin that will wake the mDot from low power modes 353 | // other pins can be confgured instead: XBEE_DIO2-6, XBEE_DI8, XBEE_DIN 354 | dot->setWakePin(XBEE_DIO7); 355 | } 356 | 357 | logInfo("%ssleeping until interrupt on %s pin", deepsleep ? "deep" : "", deepsleep ? "DIO7" : mDot::pinName2Str(dot->getWakePin()).c_str()); 358 | #endif 359 | 360 | logInfo("application will %s after waking up", deepsleep ? "execute from beginning" : "resume"); 361 | 362 | // lowest current consumption in sleep mode can only be achieved by configuring IOs as analog inputs with no pull resistors 363 | // the library handles all internal IOs automatically, but the external IOs are the application's responsibility 364 | // certain IOs may require internal pullup or pulldown resistors because leaving them floating would cause extra current consumption 365 | // for xDot: UART_*, I2C_*, SPI_*, GPIO*, WAKE 366 | // for mDot: XBEE_*, USBTX, USBRX, PB_0, PB_1 367 | // steps are: 368 | // * save IO configuration 369 | // * configure IOs to reduce current consumption 370 | // * sleep 371 | // * restore IO configuration 372 | if (! deepsleep) { 373 | // save the GPIO state. 374 | sleep_save_io(); 375 | 376 | // configure GPIOs for lowest current 377 | sleep_configure_io(); 378 | } 379 | 380 | // go to sleep/deepsleep and wake on rising edge of configured wake pin (only the WAKE pin in deepsleep) 381 | // since we're not waking on the RTC alarm, the interval is ignored 382 | dot->sleep(0, mDot::INTERRUPT, deepsleep); 383 | 384 | if (! deepsleep) { 385 | // restore the GPIO state. 386 | sleep_restore_io(); 387 | } 388 | } 389 | 390 | uint32_t calculate_actual_sleep_time(uint32_t delay_s) { 391 | uint32_t next_tx_window = dot->getNextTxMs() / 1000; 392 | // If next TX window is after the delay_ms, wait at least until the next TX window is... 393 | if (next_tx_window > delay_s) { 394 | delay_s = next_tx_window; 395 | } 396 | 397 | // next window is right now? 398 | if (delay_s == 0) { 399 | delay_s = 10; // wait 10s. 400 | } 401 | 402 | return delay_s; 403 | } 404 | 405 | void sleep_wake_rtc_or_interrupt(uint32_t delay_s, bool deepsleep) { 406 | delay_s = calculate_actual_sleep_time(delay_s); 407 | 408 | #if defined (TARGET_XDOT_L151CC) 409 | if (deepsleep) { 410 | // for xDot, WAKE pin (connected to S2 on xDot-DK) is the only pin that can wake the processor from deepsleep 411 | // it is automatically configured when INTERRUPT or RTC_ALARM_OR_INTERRUPT is the wakeup source and deepsleep is true in the mDot::sleep call 412 | } else { 413 | // configure WAKE pin (connected to S2 on xDot-DK) as the pin that will wake the xDot from low power modes 414 | // other pins can be confgured instead: GPIO0-3 or UART_RX 415 | dot->setWakePin(WAKE); 416 | } 417 | 418 | logInfo("%ssleeping %lus or until interrupt on %s pin", deepsleep ? "deep" : "", delay_s, deepsleep ? "WAKE" : mDot::pinName2Str(dot->getWakePin()).c_str()); 419 | #else 420 | if (deepsleep) { 421 | // for mDot, XBEE_DIO7 pin is the only pin that can wake the processor from deepsleep 422 | // it is automatically configured when INTERRUPT or RTC_ALARM_OR_INTERRUPT is the wakeup source and deepsleep is true in the mDot::sleep call 423 | } else { 424 | // configure XBEE_DIO7 pin as the pin that will wake the mDot from low power modes 425 | // other pins can be confgured instead: XBEE_DIO2-6, XBEE_DI8, XBEE_DIN 426 | dot->setWakePin(XBEE_DIO7); 427 | } 428 | 429 | logInfo("%ssleeping %lus or until interrupt on %s pin", deepsleep ? "deep" : "", delay_s, deepsleep ? "DIO7" : mDot::pinName2Str(dot->getWakePin()).c_str()); 430 | #endif 431 | 432 | logInfo("application will %s after waking up", deepsleep ? "execute from beginning" : "resume"); 433 | 434 | // lowest current consumption in sleep mode can only be achieved by configuring IOs as analog inputs with no pull resistors 435 | // the library handles all internal IOs automatically, but the external IOs are the application's responsibility 436 | // certain IOs may require internal pullup or pulldown resistors because leaving them floating would cause extra current consumption 437 | // for xDot: UART_*, I2C_*, SPI_*, GPIO*, WAKE 438 | // for mDot: XBEE_*, USBTX, USBRX, PB_0, PB_1 439 | // steps are: 440 | // * save IO configuration 441 | // * configure IOs to reduce current consumption 442 | // * sleep 443 | // * restore IO configuration 444 | if (! deepsleep) { 445 | // save the GPIO state. 446 | sleep_save_io(); 447 | 448 | // configure GPIOs for lowest current 449 | sleep_configure_io(); 450 | } 451 | 452 | // go to sleep/deepsleep and wake using the RTC alarm after delay_s seconds or rising edge of configured wake pin (only the WAKE pin in deepsleep) 453 | // whichever comes first will wake the xDot 454 | dot->sleep(delay_s, mDot::RTC_ALARM_OR_INTERRUPT, deepsleep); 455 | 456 | if (! deepsleep) { 457 | // restore the GPIO state. 458 | sleep_restore_io(); 459 | } 460 | } 461 | 462 | 463 | void sleep_save_io() { 464 | #if defined(TARGET_XDOT_L151CC) 465 | xdot_save_gpio_state(); 466 | #else 467 | portA[0] = GPIOA->MODER; 468 | portA[1] = GPIOA->OTYPER; 469 | portA[2] = GPIOA->OSPEEDR; 470 | portA[3] = GPIOA->PUPDR; 471 | portA[4] = GPIOA->AFR[0]; 472 | portA[5] = GPIOA->AFR[1]; 473 | 474 | portB[0] = GPIOB->MODER; 475 | portB[1] = GPIOB->OTYPER; 476 | portB[2] = GPIOB->OSPEEDR; 477 | portB[3] = GPIOB->PUPDR; 478 | portB[4] = GPIOB->AFR[0]; 479 | portB[5] = GPIOB->AFR[1]; 480 | 481 | portC[0] = GPIOC->MODER; 482 | portC[1] = GPIOC->OTYPER; 483 | portC[2] = GPIOC->OSPEEDR; 484 | portC[3] = GPIOC->PUPDR; 485 | portC[4] = GPIOC->AFR[0]; 486 | portC[5] = GPIOC->AFR[1]; 487 | 488 | portD[0] = GPIOD->MODER; 489 | portD[1] = GPIOD->OTYPER; 490 | portD[2] = GPIOD->OSPEEDR; 491 | portD[3] = GPIOD->PUPDR; 492 | portD[4] = GPIOD->AFR[0]; 493 | portD[5] = GPIOD->AFR[1]; 494 | 495 | portH[0] = GPIOH->MODER; 496 | portH[1] = GPIOH->OTYPER; 497 | portH[2] = GPIOH->OSPEEDR; 498 | portH[3] = GPIOH->PUPDR; 499 | portH[4] = GPIOH->AFR[0]; 500 | portH[5] = GPIOH->AFR[1]; 501 | #endif 502 | } 503 | 504 | void sleep_configure_io() { 505 | #if defined(TARGET_XDOT_L151CC) 506 | // GPIO Ports Clock Enable 507 | __GPIOA_CLK_ENABLE(); 508 | __GPIOB_CLK_ENABLE(); 509 | __GPIOC_CLK_ENABLE(); 510 | __GPIOH_CLK_ENABLE(); 511 | 512 | GPIO_InitTypeDef GPIO_InitStruct; 513 | 514 | // UART1_TX, UART1_RTS & UART1_CTS to analog nopull - RX could be a wakeup source 515 | GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_11 | GPIO_PIN_12; 516 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 517 | GPIO_InitStruct.Pull = GPIO_NOPULL; 518 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 519 | 520 | // I2C_SDA & I2C_SCL to analog nopull 521 | GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; 522 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 523 | GPIO_InitStruct.Pull = GPIO_NOPULL; 524 | HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 525 | 526 | // SPI_MOSI, SPI_MISO, SPI_SCK, & SPI_NSS to analog nopull 527 | GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; 528 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 529 | GPIO_InitStruct.Pull = GPIO_NOPULL; 530 | HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 531 | 532 | // iterate through potential wake pins - leave the configured wake pin alone if one is needed 533 | if (dot->getWakePin() != WAKE || dot->getWakeMode() == mDot::RTC_ALARM) { 534 | GPIO_InitStruct.Pin = GPIO_PIN_0; 535 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 536 | GPIO_InitStruct.Pull = GPIO_NOPULL; 537 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 538 | } 539 | if (dot->getWakePin() != GPIO0 || dot->getWakeMode() == mDot::RTC_ALARM) { 540 | GPIO_InitStruct.Pin = GPIO_PIN_4; 541 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 542 | GPIO_InitStruct.Pull = GPIO_NOPULL; 543 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 544 | } 545 | if (dot->getWakePin() != GPIO1 || dot->getWakeMode() == mDot::RTC_ALARM) { 546 | GPIO_InitStruct.Pin = GPIO_PIN_5; 547 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 548 | GPIO_InitStruct.Pull = GPIO_NOPULL; 549 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 550 | } 551 | if (dot->getWakePin() != GPIO2 || dot->getWakeMode() == mDot::RTC_ALARM) { 552 | GPIO_InitStruct.Pin = GPIO_PIN_0; 553 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 554 | GPIO_InitStruct.Pull = GPIO_NOPULL; 555 | HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 556 | } 557 | if (dot->getWakePin() != GPIO3 || dot->getWakeMode() == mDot::RTC_ALARM) { 558 | GPIO_InitStruct.Pin = GPIO_PIN_2; 559 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 560 | GPIO_InitStruct.Pull = GPIO_NOPULL; 561 | HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 562 | } 563 | if (dot->getWakePin() != UART1_RX || dot->getWakeMode() == mDot::RTC_ALARM) { 564 | GPIO_InitStruct.Pin = GPIO_PIN_10; 565 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 566 | GPIO_InitStruct.Pull = GPIO_NOPULL; 567 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 568 | } 569 | #else 570 | /* GPIO Ports Clock Enable */ 571 | __GPIOA_CLK_ENABLE(); 572 | __GPIOB_CLK_ENABLE(); 573 | __GPIOC_CLK_ENABLE(); 574 | 575 | GPIO_InitTypeDef GPIO_InitStruct; 576 | 577 | // XBEE_DOUT, XBEE_DIN, XBEE_DO8, XBEE_RSSI, USBTX, USBRX, PA_12, PA_13, PA_14 & PA_15 to analog nopull 578 | GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_6 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 579 | | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; 580 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 581 | GPIO_InitStruct.Pull = GPIO_NOPULL; 582 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 583 | 584 | // PB_0, PB_1, PB_3 & PB_4 to analog nopull 585 | GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_3 | GPIO_PIN_4; 586 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 587 | GPIO_InitStruct.Pull = GPIO_NOPULL; 588 | HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 589 | 590 | // PC_9 & PC_13 to analog nopull 591 | GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_13; 592 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 593 | GPIO_InitStruct.Pull = GPIO_NOPULL; 594 | HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); 595 | 596 | // iterate through potential wake pins - leave the configured wake pin alone if one is needed 597 | // XBEE_DIN - PA3 598 | // XBEE_DIO2 - PA5 599 | // XBEE_DIO3 - PA4 600 | // XBEE_DIO4 - PA7 601 | // XBEE_DIO5 - PC1 602 | // XBEE_DIO6 - PA1 603 | // XBEE_DIO7 - PA0 604 | // XBEE_SLEEPRQ - PA11 605 | 606 | if (dot->getWakePin() != XBEE_DIN || dot->getWakeMode() == mDot::RTC_ALARM) { 607 | GPIO_InitStruct.Pin = GPIO_PIN_3; 608 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 609 | GPIO_InitStruct.Pull = GPIO_NOPULL; 610 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 611 | } 612 | 613 | if (dot->getWakePin() != XBEE_DIO2 || dot->getWakeMode() == mDot::RTC_ALARM) { 614 | GPIO_InitStruct.Pin = GPIO_PIN_5; 615 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 616 | GPIO_InitStruct.Pull = GPIO_NOPULL; 617 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 618 | } 619 | 620 | if (dot->getWakePin() != XBEE_DIO3 || dot->getWakeMode() == mDot::RTC_ALARM) { 621 | GPIO_InitStruct.Pin = GPIO_PIN_4; 622 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 623 | GPIO_InitStruct.Pull = GPIO_NOPULL; 624 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 625 | } 626 | 627 | if (dot->getWakePin() != XBEE_DIO4 || dot->getWakeMode() == mDot::RTC_ALARM) { 628 | GPIO_InitStruct.Pin = GPIO_PIN_7; 629 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 630 | GPIO_InitStruct.Pull = GPIO_NOPULL; 631 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 632 | } 633 | 634 | if (dot->getWakePin() != XBEE_DIO5 || dot->getWakeMode() == mDot::RTC_ALARM) { 635 | GPIO_InitStruct.Pin = GPIO_PIN_1; 636 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 637 | GPIO_InitStruct.Pull = GPIO_NOPULL; 638 | HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); 639 | } 640 | 641 | if (dot->getWakePin() != XBEE_DIO6 || dot->getWakeMode() == mDot::RTC_ALARM) { 642 | GPIO_InitStruct.Pin = GPIO_PIN_1; 643 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 644 | GPIO_InitStruct.Pull = GPIO_NOPULL; 645 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 646 | } 647 | 648 | if (dot->getWakePin() != XBEE_DIO7 || dot->getWakeMode() == mDot::RTC_ALARM) { 649 | GPIO_InitStruct.Pin = GPIO_PIN_0; 650 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 651 | GPIO_InitStruct.Pull = GPIO_NOPULL; 652 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 653 | } 654 | 655 | if (dot->getWakePin() != XBEE_SLEEPRQ|| dot->getWakeMode() == mDot::RTC_ALARM) { 656 | GPIO_InitStruct.Pin = GPIO_PIN_11; 657 | GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; 658 | GPIO_InitStruct.Pull = GPIO_NOPULL; 659 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 660 | } 661 | #endif 662 | } 663 | 664 | void sleep_restore_io() { 665 | #if defined(TARGET_XDOT_L151CC) 666 | xdot_restore_gpio_state(); 667 | #else 668 | GPIOA->MODER = portA[0]; 669 | GPIOA->OTYPER = portA[1]; 670 | GPIOA->OSPEEDR = portA[2]; 671 | GPIOA->PUPDR = portA[3]; 672 | GPIOA->AFR[0] = portA[4]; 673 | GPIOA->AFR[1] = portA[5]; 674 | 675 | GPIOB->MODER = portB[0]; 676 | GPIOB->OTYPER = portB[1]; 677 | GPIOB->OSPEEDR = portB[2]; 678 | GPIOB->PUPDR = portB[3]; 679 | GPIOB->AFR[0] = portB[4]; 680 | GPIOB->AFR[1] = portB[5]; 681 | 682 | GPIOC->MODER = portC[0]; 683 | GPIOC->OTYPER = portC[1]; 684 | GPIOC->OSPEEDR = portC[2]; 685 | GPIOC->PUPDR = portC[3]; 686 | GPIOC->AFR[0] = portC[4]; 687 | GPIOC->AFR[1] = portC[5]; 688 | 689 | GPIOD->MODER = portD[0]; 690 | GPIOD->OTYPER = portD[1]; 691 | GPIOD->OSPEEDR = portD[2]; 692 | GPIOD->PUPDR = portD[3]; 693 | GPIOD->AFR[0] = portD[4]; 694 | GPIOD->AFR[1] = portD[5]; 695 | 696 | GPIOH->MODER = portH[0]; 697 | GPIOH->OTYPER = portH[1]; 698 | GPIOH->OSPEEDR = portH[2]; 699 | GPIOH->PUPDR = portH[3]; 700 | GPIOH->AFR[0] = portH[4]; 701 | GPIOH->AFR[1] = portH[5]; 702 | #endif 703 | } 704 | 705 | void send_data(std::vector data) { 706 | uint32_t ret; 707 | 708 | ret = dot->send(data); 709 | if (ret != mDot::MDOT_OK) { 710 | logError("failed to send data to %s [%d][%s]", dot->getJoinMode() == mDot::PEER_TO_PEER ? "peer" : "gateway", ret, mDot::getReturnCodeString(ret).c_str()); 711 | } else { 712 | logInfo("successfully sent data to %s", dot->getJoinMode() == mDot::PEER_TO_PEER ? "peer" : "gateway"); 713 | } 714 | } 715 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2017 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 "dot_util.h" 20 | #include "RadioEvent.h" 21 | #include "ChannelPlans.h" 22 | #include "CayenneLPP.h" 23 | 24 | #define EU868 25 | // #define US915 26 | #define TTN 27 | 28 | #define APP_VERSION 27 29 | #define IS_NEW_APP 0 30 | 31 | using namespace std; 32 | 33 | // Application EUI 34 | static uint8_t network_id[] = { 0x70, 0xB3, 0xD5, 0x7E, 0xF0, 0x00, 0x6A, 0x73 }; 35 | // Application Key 36 | static uint8_t network_key[] = { 0xB8, 0xB4, 0x33, 0x0D, 0xFD, 0xD5, 0xD8, 0x61, 0xE7, 0x37, 0xA6, 0xC9, 0x5E, 0x5F, 0xD3, 0xF0 }; 37 | 38 | static uint8_t ack = 0; 39 | 40 | #ifdef US915 41 | static lora::ChannelPlan_US915 plan; 42 | static mDot::DataRates tx_data_rate = mDot::DR4; 43 | static mDot::DataRates join_rx2_data_rate = mDot::DR8; 44 | #ifdef TTN 45 | static uint8_t frequency_sub_band = 2; 46 | #else 47 | static uint8_t frequency_sub_band = 0; // try all subbands 48 | #endif // TTN 49 | #endif // US915 50 | 51 | #ifdef EU868 52 | static lora::ChannelPlan_EU868 plan; 53 | static mDot::DataRates tx_data_rate = mDot::DR5; 54 | static uint8_t frequency_sub_band = 0; // not applicable 55 | #ifdef TTN 56 | static mDot::DataRates join_rx2_data_rate = mDot::DR3; // SF9 57 | #else 58 | static mDot::DataRates join_rx2_data_rate = mDot::DR0; // SF12 59 | #endif // TTN 60 | #endif // EU868 61 | 62 | // deepsleep consumes slightly less current than sleep 63 | // in sleep mode, IO state is maintained, RAM is retained, and application will resume after waking up 64 | // in deepsleep mode, IOs float, RAM is lost, and application will start from beginning after waking up 65 | // if deep_sleep == true, device will enter deepsleep mode 66 | static bool deep_sleep = false; 67 | 68 | mDot* dot = NULL; 69 | 70 | // // fwd declaration 71 | void send_mac_msg(uint8_t port, vector* data); 72 | void class_switch(char cls); 73 | 74 | // Custom event handler for automatically displaying RX data 75 | RadioEvent radio_events(&send_mac_msg, &class_switch); 76 | 77 | typedef struct { 78 | uint8_t port; 79 | bool is_mac; 80 | std::vector* data; 81 | } UplinkMessage; 82 | 83 | vector* message_queue = new vector(); 84 | static bool in_class_c_mode = false; 85 | 86 | static mbed_stats_heap_t heap_stats; 87 | 88 | void get_current_credentials(LoRaWANCredentials_t* creds) { 89 | memcpy(creds->DevAddr, &(dot->getNetworkAddress()[0]), 4); 90 | memcpy(creds->NwkSKey, &(dot->getNetworkSessionKey()[0]), 16); 91 | memcpy(creds->AppSKey, &(dot->getDataSessionKey()[0]), 16); 92 | 93 | creds->UplinkCounter = dot->getUpLinkCounter(); 94 | creds->DownlinkCounter = dot->getDownLinkCounter(); 95 | 96 | creds->TxDataRate = dot->getTxDataRate(); 97 | creds->RxDataRate = dot->getRxDataRate(); 98 | 99 | creds->Rx2Frequency = dot->getJoinRx2Frequency(); 100 | // somehow this still goes wrong when switching back to class A... 101 | } 102 | 103 | void set_class_a_creds(); 104 | 105 | void set_class_c_creds() { 106 | LoRaWANCredentials_t* credentials = radio_events.GetClassCCredentials(); 107 | 108 | // logInfo("Switching to class C (DevAddr=%s)", mts::Text::bin2hexString(credentials->DevAddr, 4).c_str()); 109 | 110 | // @todo: this is weird, ah well... 111 | std::vector address; 112 | address.push_back(credentials->DevAddr[3]); 113 | address.push_back(credentials->DevAddr[2]); 114 | address.push_back(credentials->DevAddr[1]); 115 | address.push_back(credentials->DevAddr[0]); 116 | std::vector nwkskey(credentials->NwkSKey, credentials->NwkSKey + 16); 117 | std::vector appskey(credentials->AppSKey, credentials->AppSKey + 16); 118 | 119 | dot->setNetworkAddress(address); 120 | dot->setNetworkSessionKey(nwkskey); 121 | dot->setDataSessionKey(appskey); 122 | 123 | // dot->setTxDataRate(credentials->TxDataRate); 124 | // dot->setRxDataRate(credentials->RxDataRate); 125 | 126 | dot->setUpLinkCounter(credentials->UplinkCounter); 127 | dot->setDownLinkCounter(credentials->DownlinkCounter); 128 | 129 | // update_network_link_check_config(0, 0); 130 | 131 | // fake MAC command to switch to DR5 132 | std::vector mac_cmd; 133 | mac_cmd.push_back(0x05); 134 | mac_cmd.push_back(credentials->RxDataRate); 135 | mac_cmd.push_back(credentials->Rx2Frequency & 0xff); 136 | mac_cmd.push_back(credentials->Rx2Frequency >> 8 & 0xff); 137 | mac_cmd.push_back(credentials->Rx2Frequency >> 16 & 0xff); 138 | 139 | int32_t ret; 140 | if ((ret = dot->injectMacCommand(mac_cmd)) != mDot::MDOT_OK) { 141 | printf("Failed to set Class C Rx parameters (%lu)\n", ret); 142 | set_class_a_creds(); 143 | return; 144 | } 145 | 146 | dot->setClass("C"); 147 | 148 | printf("Switched to class C\n"); 149 | 150 | radio_events.switchedToClassC(); 151 | } 152 | 153 | void set_class_a_creds() { 154 | LoRaWANCredentials_t* credentials = radio_events.GetClassACredentials(); 155 | 156 | // logInfo("Switching to class A (DevAddr=%s)", mts::Text::bin2hexString(credentials->DevAddr, 4).c_str()); 157 | 158 | std::vector address(credentials->DevAddr, credentials->DevAddr + 4); 159 | std::vector nwkskey(credentials->NwkSKey, credentials->NwkSKey + 16); 160 | std::vector appskey(credentials->AppSKey, credentials->AppSKey + 16); 161 | 162 | dot->setNetworkAddress(address); 163 | dot->setNetworkSessionKey(nwkskey); 164 | dot->setDataSessionKey(appskey); 165 | 166 | // dot->setTxDataRate(credentials->TxDataRate); 167 | // dot->setRxDataRate(credentials->RxDataRate); 168 | 169 | dot->setUpLinkCounter(credentials->UplinkCounter); 170 | dot->setDownLinkCounter(credentials->DownlinkCounter); 171 | 172 | // update_network_link_check_config(3, 5); 173 | 174 | // reset rx2 datarate... however, this gets rejected because the datarate is not valid for receiving 175 | // wondering if we actually need to do this... 176 | std::vector mac_cmd; 177 | mac_cmd.push_back(0x05); 178 | mac_cmd.push_back(credentials->RxDataRate); 179 | mac_cmd.push_back(credentials->Rx2Frequency & 0xff); 180 | mac_cmd.push_back(credentials->Rx2Frequency >> 8 & 0xff); 181 | mac_cmd.push_back(credentials->Rx2Frequency >> 16 & 0xff); 182 | 183 | // printf("Setting RX2 freq to %02x %02x %02x\n", credentials->Rx2Frequency & 0xff, 184 | // credentials->Rx2Frequency >> 8 & 0xff, credentials->Rx2Frequency >> 16 & 0xff); 185 | 186 | // int32_t ret; 187 | // if ((ret = dot->injectMacCommand(mac_cmd)) != mDot::MDOT_OK) { 188 | // printf("Failed to set Class A Rx parameters (%lu)\n", ret); 189 | // // don't fail here... 190 | // } 191 | 192 | dot->setClass("A"); 193 | 194 | printf("Switched to class A\n"); 195 | 196 | radio_events.switchedToClassA(); 197 | } 198 | 199 | void send_packet(UplinkMessage* message) { 200 | if (message_queue->size() > 0 && !message->is_mac) { 201 | // logInfo("MAC messages in queue, dropping this packet"); 202 | delete message->data; 203 | delete message; 204 | } 205 | else { 206 | // otherwise, add to queue 207 | message_queue->push_back(message); 208 | } 209 | 210 | // take the first item from the queue 211 | UplinkMessage* m = message_queue->at(0); 212 | 213 | // OK... soooooo we can only send in Class A 214 | bool switched_creds = false; 215 | if (in_class_c_mode) { 216 | logError("Cannot send in Class C mode. Switch back to Class A first.\n"); 217 | return; 218 | } 219 | 220 | dot->setAppPort(m->port); 221 | 222 | printf("[INFO] Going to send a message. port=%d, dr=%s, data=", m->port, dot->getDateRateDetails(dot->getTxDataRate()).c_str()); 223 | for (size_t ix = 0; ix < m->data->size(); ix++) { 224 | printf("%02x ", m->data->at(ix)); 225 | } 226 | printf("\n"); 227 | 228 | mbed_stats_heap_get(&heap_stats); 229 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 230 | 231 | uint32_t ret; 232 | 233 | radio_events.OnTx(dot->getUpLinkCounter() + 1); 234 | 235 | if (m->is_mac) { 236 | #if MBED_CONF_APP_ACK_MAC_COMMANDS == 1 237 | dot->setAck(true); 238 | #endif 239 | 240 | ret = dot->send(*(m->data)); 241 | 242 | #if MBED_CONF_APP_ACK_MAC_COMMANDS == 1 243 | dot->setAck(false); 244 | #endif 245 | } 246 | else { 247 | dot->setAck(false); 248 | ret = dot->send(*(m->data)); 249 | } 250 | 251 | if (ret != mDot::MDOT_OK) { 252 | logError("failed to send data to %s [%d][%s]", dot->getJoinMode() == mDot::PEER_TO_PEER ? "peer" : "gateway", ret, mDot::getReturnCodeString(ret).c_str()); 253 | } else { 254 | logInfo("successfully sent data to %s", dot->getJoinMode() == mDot::PEER_TO_PEER ? "peer" : "gateway"); 255 | } 256 | 257 | // Message was sent, or was not mac message? remove from queue 258 | if (ret == mDot::MDOT_OK || !m->is_mac) { 259 | // logInfo("Removing first item from the queue"); 260 | 261 | // remove message from the queue 262 | message_queue->erase(message_queue->begin()); 263 | delete m->data; 264 | delete m; 265 | } 266 | 267 | // update credentials with the new counter 268 | LoRaWANCredentials_t* creds = in_class_c_mode ? 269 | radio_events.GetClassCCredentials() : 270 | radio_events.GetClassACredentials(); 271 | 272 | creds->UplinkCounter = dot->getUpLinkCounter(); 273 | creds->DownlinkCounter = dot->getDownLinkCounter(); 274 | 275 | // switch back 276 | if (switched_creds) { 277 | // switch to class A credentials 278 | set_class_c_creds(); 279 | } 280 | } 281 | 282 | void send_mac_msg(uint8_t port, std::vector* data) { 283 | UplinkMessage* m = new UplinkMessage(); 284 | m->is_mac = true; 285 | m->data = data; 286 | m->port = port; 287 | 288 | message_queue->push_back(m); 289 | } 290 | 291 | void class_switch(char cls) { 292 | logInfo("class_switch to %c", cls); 293 | 294 | // in class A mode? then back up credentials and counters... 295 | if (!in_class_c_mode) { 296 | LoRaWANCredentials_t creds; 297 | get_current_credentials(&creds); 298 | radio_events.UpdateClassACredentials(&creds); 299 | } 300 | 301 | // @todo; make enum 302 | if (cls == 'C') { 303 | in_class_c_mode = true; 304 | set_class_c_creds(); 305 | } 306 | else if (cls == 'A') { 307 | in_class_c_mode = false; 308 | set_class_a_creds(); 309 | } 310 | else { 311 | logError("Cannot switch to class %c", cls); 312 | } 313 | } 314 | 315 | DigitalOut led(LED1); 316 | void blink() { 317 | led = !led; 318 | } 319 | 320 | int main() { 321 | printf("Hello from application version %d\n", APP_VERSION); 322 | 323 | #if IS_NEW_APP == 1 324 | Ticker t; 325 | t.attach(callback(blink), 1.0f); 326 | #else 327 | Ticker t; 328 | t.attach(callback(blink), 0.5f); 329 | #endif 330 | 331 | mts::MTSLog::setLogLevel(mts::MTSLog::INFO_LEVEL); 332 | 333 | dot = mDot::getInstance(&plan); 334 | 335 | // attach the custom events handler 336 | dot->setEvents(&radio_events); 337 | 338 | if (!dot->getStandbyFlag()) { 339 | // start from a well-known state 340 | logInfo("defaulting Dot configuration"); 341 | dot->resetConfig(); 342 | dot->resetNetworkSession(); 343 | 344 | logInfo("setting data rate to %d", tx_data_rate); 345 | if (dot->setTxDataRate(tx_data_rate) != mDot::MDOT_OK) { 346 | logError("failed to set data rate"); 347 | } 348 | 349 | logInfo("setting join RX2 data rate to %d", join_rx2_data_rate); 350 | if (dot->setJoinRx2DataRate(join_rx2_data_rate) != mDot::MDOT_OK) { 351 | logError("failed to set join RX2 data rate"); 352 | } 353 | 354 | // update configuration if necessary 355 | if (dot->getJoinMode() != mDot::OTA) { 356 | logInfo("changing network join mode to OTA"); 357 | if (dot->setJoinMode(mDot::OTA) != mDot::MDOT_OK) { 358 | logError("failed to set network join mode to OTA"); 359 | } 360 | } 361 | update_ota_config_id_key(network_id, network_key, frequency_sub_band, true, ack); 362 | 363 | dot->setAdr(false); // @todo enable 364 | 365 | dot->setDisableDutyCycle(true); 366 | 367 | // save changes to configuration 368 | logInfo("saving configuration"); 369 | if (!dot->saveConfig()) { 370 | logError("failed to save configuration"); 371 | } 372 | 373 | // display configuration 374 | display_config(); 375 | 376 | dot->setLogLevel(mts::MTSLog::ERROR_LEVEL); 377 | } else { 378 | // restore the saved session if the dot woke from deepsleep mode 379 | // useful to use with deepsleep because session info is otherwise lost when the dot enters deepsleep 380 | logInfo("restoring network session from NVM"); 381 | dot->restoreNetworkSession(); 382 | } 383 | 384 | mbed_stats_heap_t heap_stats; 385 | mbed_stats_heap_get(&heap_stats); 386 | printf("Heap stats: Used %lu / %lu bytes\n", heap_stats.current_size, heap_stats.reserved_size); 387 | 388 | while (true) { 389 | if (!in_class_c_mode) { 390 | 391 | // join network if not joined 392 | if (!dot->getNetworkJoinStatus()) { 393 | join_network(); 394 | 395 | LoRaWANCredentials_t creds; 396 | get_current_credentials(&creds); 397 | radio_events.OnClassAJoinSucceeded(&creds); 398 | 399 | // turn duty cycle back on after joining 400 | dot->setDisableDutyCycle(false); 401 | } 402 | 403 | // send some data in CayenneLPP format 404 | static AnalogIn moisture(GPIO2); 405 | static float last_reading = 0.0f; 406 | 407 | float moisture_value = moisture.read(); 408 | 409 | CayenneLPP payload(50); 410 | payload.addAnalogOutput(1, moisture.read()); 411 | 412 | vector* tx_data = new vector(); 413 | for (size_t ix = 0; ix < payload.getSize(); ix++) { 414 | tx_data->push_back(payload.getBuffer()[ix]); 415 | } 416 | 417 | UplinkMessage* uplink = new UplinkMessage(); 418 | uplink->port = 5; 419 | uplink->data = tx_data; 420 | 421 | send_packet(uplink); 422 | 423 | last_reading = moisture_value; 424 | } 425 | 426 | // if going into deepsleep mode, save the session so we don't need to join again after waking up 427 | // not necessary if going into sleep mode since RAM is retained 428 | if (deep_sleep) { 429 | // logInfo("saving network session to NVM"); 430 | dot->saveNetworkSession(); 431 | } 432 | 433 | uint32_t sleep_time = calculate_actual_sleep_time(3 + (rand() % 8)); 434 | // logInfo("going to wait %d seconds for duty-cycle...", sleep_time); 435 | 436 | // @todo: in class A can go to deepsleep, in class C cannot 437 | if (in_class_c_mode) { 438 | wait(sleep_time); 439 | continue; // for now just send as fast as possible 440 | } 441 | else { 442 | wait(sleep_time); // @todo, wait for all frames to be processed before going to sleep. need a wakelock. 443 | // sleep_wake_rtc_or_interrupt(10, deep_sleep); 444 | } 445 | } 446 | 447 | return 0; 448 | } 449 | --------------------------------------------------------------------------------