├── images ├── images.txt ├── ack.jpg ├── lcm.jpg ├── man.jpg ├── menu.jpg ├── nack.jpg ├── otaa.jpg ├── set.jpg ├── ssv-1.jpg ├── ssv-2.jpg ├── IMG_1973.jpg └── IMG_2434.jpg ├── platformio.ini ├── LICENSE ├── README.md └── networktester └── networktester.ino /images/images.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /images/ack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/ack.jpg -------------------------------------------------------------------------------- /images/lcm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/lcm.jpg -------------------------------------------------------------------------------- /images/man.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/man.jpg -------------------------------------------------------------------------------- /images/menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/menu.jpg -------------------------------------------------------------------------------- /images/nack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/nack.jpg -------------------------------------------------------------------------------- /images/otaa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/otaa.jpg -------------------------------------------------------------------------------- /images/set.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/set.jpg -------------------------------------------------------------------------------- /images/ssv-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/ssv-1.jpg -------------------------------------------------------------------------------- /images/ssv-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/ssv-2.jpg -------------------------------------------------------------------------------- /images/IMG_1973.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/IMG_1973.jpg -------------------------------------------------------------------------------- /images/IMG_2434.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/HEAD/images/IMG_2434.jpg -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = m5stack-core-esp32 13 | src_dir = networktester 14 | 15 | [env:m5stack-core-esp32] 16 | platform = espressif32 17 | framework = arduino 18 | board = m5stack-core-esp32 19 | 20 | build_flags = -D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS -D CFG_eu868 -D CFG_sx1276_radio 21 | 22 | lib_deps = 23 | m5stack/M5Stack 24 | https://github.com/Bjoerns-TB/M5_UI.git#TTN-Mapper-Colours-Progressbar 25 | https://github.com/Bjoerns-TB/arduino-lmic.git#LMIC_setLinkCheckRequestOnce 26 | makuna/NeoPixelBus 27 | mikalhart/TinyGPSPlus 28 | 29 | upload_port = /dev/ttyUSB* 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Björn Amann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M5Stack-LoRa-868-Network-Tester 2 | 3 | A LoRaWAN Network Tester based on the M5Stack for the LoRa 868 module, compatible with TTN (The Things Network) 4 | 5 | [Version for LoRaWAN Module] 6 | 7 | [Version for COM.LoRaWAN Module] 8 | 9 | ## Setup 10 | The tester is designed to work with the following hardware: 11 | - M5Core (Basic, Gray or Fire) 12 | - M5Go Base (optional) 13 | - M5Stack GPS Module or Mini GPS/BDS Unit (optional) 14 | - [M5Stack LoRa 868 Module] 15 | 16 | #### Required Libraries! 17 | - [M5Stack] 18 | - [TinyGPSPlus] 19 | - [NeoPixelBus] 20 | - [M5_UI] - this Fork enables TTN Mapper like colours for RSSI values in the progressbar 21 | - [MCCI Arduino LMIC] - this Fork enables the LinkCheckRequest to get info on gateway number and margin from the backend 22 | 23 | 24 | #### Installation and Configuration 25 | Upload this sketch to your M5 using the Arduino IDE. M5Stack Fire users have to disable PSRAM, because it will interfer with UART2. 26 | UART2 with GPIO 16 and 17 willbe used for the GPS module. 27 | 28 | Edit the lmic_project_config.h 29 | 30 | ``` 31 | #define CFG_eu868 1 32 | #define CFG_sx1276_radio 1 33 | ``` 34 | 35 | By commenting out #define M5go it is possible to disable the M5GO Base. This will disable all NeoPixel related code and features. 36 | By commenting out #define M5gps ist is possible to disable the M5GPS module. This will disable all GPS related code and features. 37 | 38 | Change the your TTN keys under //LoRaWAN ABP and //LoRaWAN OTAA in the networktester.ino file. If you want yo use OTAA mode you have to register a second device for your application. Only the OTAA mode uses OTAA, all other modes use ABP. 39 | 40 | In oder to make the LoRa 868 module work with LMIC, you have to connect DIO1 of the Ra-01H with Pin 35 of the M5Stack bus. 41 | 42 | **An older picture showed that i connected DIO1 to PIN 35 via two solder pads next to the M5Bus connector. With this i connected DIO0 with DIO1 and Pin 35 and 36, which was not intended. With the solderpads it is possible to connect DIO0 to PIN 35 or PIN 34 with an 0 Ohm resistor (same for 25/26 and RST), so the right pads are both connected to DIO1. Please use only the left solderpad or better directly the M5Bus connector, as shown in the picture. With this change the #define LMIC_USE_INTERRUPTS in lmic_project_config is obsolete. Furthermore the pin mapping has also changed. Please notice that there are two versions of LoRa 868 modules (before and after december 2020 with different pin mapping), if your M5Stack resets in a loop, this can be a cause. (Thanks to Andreas D. for the appointment). 43 | Sorry for any problems.** 44 | 45 | ![DIO1 Image](https://github.com/Bjoerns-TB/M5Stack-LoRa-868-Network-Tester/blob/main/images/IMG_2434.jpg "Fig 1. DIO1 solder") 46 | 47 | Payload Decoder for TTN (also compatible with TTN Mapper integration): 48 | 49 | ``` 50 | function Decoder(b, port) { 51 | 52 | var lat = (b[0] | b[1]<<8 | b[2]<<16 | (b[2] & 0x80 ? 0xFF<<24 : 0)) / 10000; 53 | var lon = (b[3] | b[4]<<8 | b[5]<<16 | (b[5] & 0x80 ? 0xFF<<24 : 0)) / 10000; 54 | var alt = (b[6] | b[7]<<8 | (b[7] & 0x80 ? 0xFF<<16 : 0)) / 100; 55 | var hdop = b[8] / 10; 56 | 57 | return { 58 | 59 | latitude: lat, 60 | longitude: lon, 61 | altitude: alt, 62 | hdop: hdop 63 | 64 | }; 65 | } 66 | ``` 67 | 68 | To be compliant with the TTN Stack V3, the Frame Counter is now stored on the SD card, so an inserted card is now mandatory. The sent counter displays the sent packet based on the Frame Counter. Thic can be reverted to the previous behaviour by changeing the follwing lines: 69 | 70 | ``` 71 | //txcnt = String("Sent " + String(cnt)); 72 | txcnt = String("Sent " + String(LMIC.seqnoUp - 1)); 73 | ``` 74 | 75 | ## Instructions for Use 76 | 77 | #### Menu 78 | 79 | On boot you will be presented with the "Boot-Logo" followed by the first working mode. At the moment the tester has 7 modes to select: 80 | - [NACK](#nack) 81 | - [ACK](#ack) 82 | - [MAN](#man) 83 | - [LCM](#lcm) 84 | - [SSV](#ssv) 85 | - [OTAA](#otaa) 86 | - [SET](#set) 87 | 88 | You can move between menu items by pushing the button A. 89 | 90 | ![Menu Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/menu.jpg "Fig 1. Menu") 91 | 92 | #### NACK 93 | #### (No Acknowladge) 94 | "NACK" is a mode that utlises the current device [settings](#set) to perform periodic transmissions. "NACK" mode is great for use with TTN Mapper. 95 | 96 | ![NACK Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/nack.jpg "Fig 2. NACK") 97 | 98 | Pushing button B will let you cycle through each spreadfactor. By pushig button C the display and LEDs will be turned off. Pushing button C again will turn them on. 99 | 100 | #### ACK 101 | #### (Acknowladge) 102 | "ACK" will perform the same test as NACK but it will request an ACK for every transmission. The RSSI and SNR values of the received packet will be shown on the display. Pushing button B will let you cycle through each spreadfactor. By pushig button C the display and LEDs will be turned off. Pushing button C again will turn them on. 103 | 104 | ![ACK Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/ack.jpg "Fig 3. ACK") 105 | 106 | #### MAN 107 | #### (Manual) 108 | "MAN" will send a LoRaWAN packet with ACK by pushing button C. Pushing button B will let you cycle through each spreadfactor. 109 | 110 | ![MAN Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/man.jpg "Fig 4. MAN") 111 | 112 | #### LCM 113 | #### (LinkCheckMode) 114 | "LCM" is a mode that will trigger a LinkCheckRequest. The TTN backend will report back the number of gateways which received the request. Pushing button B will let you cycle through each spreadfactor. The request is triggered by button C. 115 | 116 | ![LCM Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/lcm.jpg "Fig 5. LCM") 117 | 118 | #### SSV 119 | #### (SiteSurvey) 120 | "SSV" is supposed as mode for testing a location. During SSV mode the DutyCycle check will be disabled an the Node will send a LinkCheckRequest for every spreadfactor from SF7 to SF12. After the test the node will show you on which datarates a ACK was received back (DR5 = SF7, DR0 = SF12). The data is also stored on the SD card in GeoJSON format an could be analyzed with [geojson.io] 121 | 122 | ![SSV Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/ssv-1.jpg "Fig 6. SSV running") 123 | ![SSV Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/ssv-2.jpg "Fig 7. SSV results") 124 | 125 | #### OTAA 126 | #### (OverTheAirActivation) 127 | "OTAA" enables the tester to perform OTAA-Joins. By selecting Join the tester will try to join the TTN Network. After an successful the the tester will start with periodic transmissions. You have the choice between transmission with or without ACK. The RSSI and SNR values of the received packet will be shown on the display. If there is no valid GPS fix, a packet can by manually send by pushing button B. 128 | 129 | ![OTAA Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/otaa.jpg "Fig 7. OTAA") 130 | 131 | #### SET 132 | #### (Settings) 133 | 134 | "SET" allows to change the transmission intervall in NACK or ACK mode. Possible settings are 15/30/45/60/120 seconds. Pressing button C will active the powersaving mode. The node will go to deep sleep and wakes up according to the transmission intervall. Sleep mode can only be stopped by resetting the devicde. 135 | 136 | ![SET Image](https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester/blob/master/images/set.jpg "Fig 7. SET") 137 | 138 | ## Notes 139 | - The DutyCycle check ist activated except for the SSV mode. 140 | - If you have a valid GPS fix the GPS track will be written to the SD card as a GPX file. 141 | - Periodic transmission will only work with a valid GPS fix and an GPS age below 2 seconds 142 | 143 | 144 | ## Known Bugs/Limitations 145 | - Sometimes, if an ACK is not received, the node tries to resend the last message multiple times. This will interfer with other jobs. Retries are turned of, but sometimes happen. 146 | - When SSV mode ist left, the Duty Cycle limit is activated. So the next transfer will wait about 150 seconds. Status will display "Queued". 147 | 148 | 149 | ## Accesories 150 | - Michael designed a [Case]. Thank you! 151 | 152 | 153 | ## Changelog 154 | 155 | - 09.06.2021 156 | - set Rx Delay to 5 seconds for V3 157 | 158 | - 12.04.2021 159 | - remove old margin calculation (completely wrong) 160 | - calculate the uplink SNR based on the margin in SSV Mode, save UL SNR in SSV file 161 | - In LCM mode the uplink SNR will be displayed next to the gateway count 162 | 163 | - 01.04.2021 164 | - Corrected the Instructions for connecting DIO1 to M5Bus 165 | - Updated Pin Mapping 166 | - Updated lmic_project_config 167 | 168 | - 17.02.2021 169 | - Replace drawBitmap by pushImage for corrected colours 170 | 171 | - 09.02.2021 172 | - disabled ADR in ABP mode; this removed unwanted ADR related uplinks 173 | 174 | - 07.02.2021 175 | - fix SSV mode on fast retry 176 | - in SSV mode save yellow lighthouse when only reception of ACK with SF11 and/or SF12 177 | - in SSV mode save red lighthouse when no ACK is received 178 | 179 | - 05.02.2021 180 | - fix SSV mode data save and display 181 | 182 | - 03.02.2021 183 | - fix periodic transmissions in OTAA mode 184 | - write and read framecounter from sd card; display sent packet based on LMIC framecounter (reversible) 185 | 186 | - 04.01.2021 187 | - play beep on ACK received in ACK mode 188 | - reset counters on no ACK received 189 | 190 | - 03.01.2021 191 | - Allow different regions in ABP mode 192 | 193 | - 01.01.2021 194 | - change progessbar scale 0: -130 100:-80 RSSI 195 | - Display information about queued packets in LCM and OTAA mode 196 | - Reset nGWs and gwMargin after mode Change 197 | 198 | - 30.12.2020 199 | - Reset RSSI, SNR and No of GWs values on change of mode 200 | 201 | - 29.12.2020 202 | - First commit 203 | 204 | 205 | ## ToDo 206 | - create tasks for M5UI 207 | - improve powersave features (GPS module) 208 | 209 | 210 | [M5Stack]: https://github.com/m5stack/M5Stack 211 | [TinyGPSPlus]: https://github.com/mikalhart/TinyGPSPlus 212 | [NeoPixelBus]: https://github.com/Makuna/NeoPixelBus 213 | [M5_UI]: https://github.com/Bjoerns-TB/M5_UI/tree/TTN-Mapper-Colours-Progressbar 214 | [geojson.io]: http://geojson.io/ 215 | [M5Stack LoRa 868 Module]: https://m5stack.com/products/lora-module-868mhz 216 | [MCCI Arduino LMIC]: https://github.com/Bjoerns-TB/arduino-lmic/tree/LMIC_setLinkCheckRequestOnce 217 | [Case]: https://www.thingiverse.com/thing:4706335 218 | [Version for COM.LoRaWAN Module]: https://github.com/Bjoerns-TB/M5Stack-COM-LoRaWAN-Network-Tester 219 | [Version for LoRaWAN Module]: https://github.com/Bjoerns-TB/M5Stack-LoRaWAN-Network-Tester 220 | -------------------------------------------------------------------------------- /networktester/networktester.ino: -------------------------------------------------------------------------------- 1 | #include // https://github.com/m5stack/M5Stack 2 | #include // https://github.com/Bjoerns-TB/M5_UI/tree/TTN-Mapper-Colours-Progressbar 3 | #include // https://github.com/Bjoerns-TB/arduino-lmic/tree/LMIC_setLinkCheckRequestOnce 4 | #include 5 | #include 6 | #include "soc/timer_group_struct.h" 7 | #include "soc/timer_group_reg.h" 8 | 9 | #define M5go 10 | #define M5gps 11 | 12 | #ifdef M5go 13 | #include // https://github.com/Makuna/NeoPixelBus 14 | #endif 15 | 16 | #ifdef M5gps 17 | #include // https://github.com/mikalhart/TinyGPSPlus 18 | #endif 19 | 20 | //Task 21 | TaskHandle_t TaskGPS; 22 | TaskHandle_t TaskPixel; 23 | TaskHandle_t TaskLMIC; 24 | 25 | //Image 26 | extern const unsigned char gImage_logoM5[]; 27 | 28 | #ifdef M5go 29 | //NeoPixel 30 | const uint16_t PixelCount = 10; 31 | const uint8_t PixelPin = 15; 32 | NeoPixelBrightnessBus strip(PixelCount, PixelPin); 33 | RgbColor red(128, 0, 0); 34 | RgbColor green(0, 128, 0); 35 | RgbColor blue(0, 0, 128); 36 | RgbColor lightblue(0, 95, 128); 37 | RgbColor yellow(128, 128, 0); 38 | RgbColor orange(128, 64, 0); 39 | RgbColor off(0, 0, 0); 40 | #endif 41 | 42 | #ifdef M5gps 43 | //GPS 44 | static const uint32_t GPSBaud = 9600; 45 | TinyGPSPlus gps; 46 | HardwareSerial serialgps(2); 47 | #endif 48 | float latitude, longitude, latitude2, longitude2, hdop, alt, hdop2; 49 | int sats; 50 | 51 | 52 | //LoRa 53 | int isf = 0; 54 | const char *dr[6] = {"DR5", "DR4", "DR3", "DR2", "DR1", "DR0"}; 55 | const char *sf[6] = {"SF7", "SF8", "SF9", "SF10", "SF11", "SF12"}; 56 | RTC_DATA_ATTR int iwm = 0; 57 | const char *workmode[7] = {"NACK", "ACK", "MAN", "LCM", "SSV", "OTAA", "SET"}; 58 | char buffer[256]; 59 | short length; 60 | short rssi; 61 | float snr; 62 | char charsnr[5]; 63 | short gwcnt; 64 | byte coords[9]; 65 | byte ncoords[1]; 66 | long sentMillis = 0; 67 | long currentMillis = 0; 68 | RTC_DATA_ATTR int iiv = 0; 69 | long interval[5] = {15000, 30000, 45000, 60000, 120000}; 70 | const char *ttext[5] = {"15s", "30s", "45s", "60s", "120s"}; 71 | RTC_DATA_ATTR int cnt = -1; 72 | String txcnt; 73 | int otaa = 0; 74 | int otaaack = 0; 75 | static osjob_t sendjob; 76 | bool next = true; 77 | bool ackrx = false; 78 | int margin; 79 | char counterfile[] = "/framecounter.txt"; 80 | long framecounter = 0; 81 | 82 | //LoRaWAN ABP 83 | // LoRaWAN NwkSKey, network session key 84 | // This should be in big-endian (aka msb). 85 | static const PROGMEM u1_t NWKSKEY[16] = { 0x00, ..., 0x00 }; 86 | 87 | // LoRaWAN AppSKey, application session key 88 | // This should also be in big-endian (aka msb). 89 | static const u1_t PROGMEM APPSKEY[16] = { 0x00, ..., 0x00 }; 90 | 91 | // LoRaWAN end-device address (DevAddr) 92 | // See http://thethingsnetwork.org/wiki/AddressSpace 93 | // The library converts the address to network byte order as needed, so this should be in big-endian (aka msb) too. 94 | static const u4_t DEVADDR = 0x12345678 ; // <-- Change this address for every node! 95 | 96 | 97 | //LoRaWAN OTAA 98 | // This EUI must be in little-endian format (aka lsb), so least-significant-byte 99 | // first. When copying an EUI from ttnctl output, this means to reverse 100 | // the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 101 | // 0x70. 102 | static const u1_t PROGMEM APPEUI[8] = { 0x00, ..., 0x00 }; 103 | void os_getArtEui (u1_t* buf) { 104 | memcpy_P(buf, APPEUI, 8); 105 | } 106 | 107 | // This should also be in little endian format (aka lsb), see above. 108 | static const u1_t PROGMEM DEVEUI[8] = { 0x00, ..., 0x00 }; 109 | void os_getDevEui (u1_t* buf) { 110 | memcpy_P(buf, DEVEUI, 8); 111 | } 112 | 113 | // This key should be in big endian format (aka msb) (or, since it is not really a 114 | // number but a block of memory, endianness does not really apply). In 115 | // practice, a key taken from ttnctl can be copied as-is. 116 | static const u1_t PROGMEM APPKEY[16] = { 0x00, ..., 0x00 }; 117 | void os_getDevKey (u1_t* buf) { 118 | memcpy_P(buf, APPKEY, 16); 119 | } 120 | 121 | 122 | 123 | //Battery 124 | int8_t BattLevel = 0; 125 | #define FULL ( 3) 126 | 127 | //SDCard 128 | char filename[] = "/"; 129 | bool cardin = false; 130 | bool sdwrite = false; 131 | File dataFile; 132 | 133 | //GPX 134 | int year; 135 | byte month, day, hour, minute, second; 136 | char filename1[20]; 137 | char date1[22]; 138 | char filepath[20]; 139 | 140 | //SSV 141 | char filename2[20]; 142 | char date2[22]; 143 | char filepath2[20]; 144 | bool firstssv = false; 145 | bool lastssv = false; 146 | String ssvresult = "DR "; 147 | 148 | //M5Stack 149 | bool dim = false; 150 | RTC_DATA_ATTR bool powersave = false; 151 | 152 | /* RootVar's for UI elements (note: not edit manually) */ 153 | String UIInputbox_6nssds = ""; //No GWs for LCR 154 | String UITextbox_vimqus = "SF7"; //SpreadingFactor (B2) 155 | String UITextbox_eq79hh46 = "NACK"; //Workmode (B1) 156 | String UITextbox_67ofwdh = "Dim"; //Dimming (B3) 157 | String UIProgressbar_eymzer = "0"; //Progressbar RSSI 158 | String UITextbox_859t1hi = "-130"; //RSSI 159 | String UIInputbox_awnh87 = "inactive";//Status 160 | String UITextbox_4t0l0bn = "0"; //Stattelites 161 | String UITextbox_q7sl3uo = "0"; //HDOP 162 | String UITextbox_403ohip = "0"; //Battery Level 163 | String UITextbox_olwwlae = "-20.00"; //SNR 164 | String UITextbox_7mnuudb = "SNR"; //SNR 165 | 166 | /* Function for layer default: */ 167 | void LayerFunction_default(String* rootVar) { 168 | /* UI Elements */ 169 | UIInputbox(160, 58, 150, "default", "No of GWs", 0, &UIInputbox_6nssds); 170 | UITextbox(144, 214, 50, 20, 0x0000, "default", &UITextbox_vimqus); 171 | UITextbox(44, 215, 50, 20, 0x0000, "default", &UITextbox_eq79hh46); 172 | UITextbox(227, 215, 50, 20, 0x0000, "default", &UITextbox_67ofwdh); 173 | UIProgressbar(10, 144, 300, "default", "RSSI, dB", &UIProgressbar_eymzer); 174 | UITextbox(124, 142, 50, 20, 0x0000, "default", &UITextbox_859t1hi); 175 | UIInputbox(5, 58, 150, "default", "Status", 0, &UIInputbox_awnh87); 176 | UITextbox(40, 11, 25, 20, 0x0000, "default", &UITextbox_4t0l0bn); 177 | UITextbox(100, 11, 60, 20, 0x0000, "default", &UITextbox_q7sl3uo); 178 | UITextbox(270, 11, 50, 20, 0x0000, "default", &UITextbox_403ohip); 179 | UITextbox(249, 142, 70, 20, 0x0000, "default", &UITextbox_olwwlae); 180 | UITextbox(200, 142, 40, 20, 0x0000, "default", &UITextbox_7mnuudb); 181 | 182 | /* To open this layer use: */ 183 | UILayer("default"); 184 | } 185 | 186 | #ifdef M5gps 187 | static void gpsupdate(void * pcParameters) 188 | { 189 | for (;;) { 190 | unsigned long start = millis(); 191 | do { 192 | while (serialgps.available()) { 193 | gps.encode(serialgps.read()); 194 | } 195 | } while (millis() - start < 1000); 196 | TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE; 197 | TIMERG0.wdt_feed = 1; 198 | TIMERG0.wdt_wprotect = 0; 199 | } 200 | } 201 | #endif 202 | 203 | #ifdef M5go 204 | static void pixelupdate(void * pcParameters) 205 | { 206 | for (;;) { 207 | 208 | #ifdef M5gps 209 | //Change NeoPixel 4 210 | if (gps.satellites.value() < 3) { 211 | strip.SetPixelColor(4, red); 212 | } 213 | else if (gps.satellites.value() < 6) { 214 | strip.SetPixelColor(4, yellow); 215 | } 216 | else { 217 | strip.SetPixelColor(4, green); 218 | } 219 | 220 | //Change NeoPixel 0 221 | if (gps.hdop.value() < 500) { 222 | strip.SetPixelColor(0, green); 223 | } 224 | else if (gps.hdop.value() < 1000) { 225 | strip.SetPixelColor(0, yellow); 226 | } 227 | else { 228 | strip.SetPixelColor(0, red); 229 | } 230 | 231 | //Change NeoPixel 2 232 | if (gps.location.isValid() == false) { 233 | strip.SetPixelColor(2, red); 234 | } 235 | else if (gps.location.isValid() && gps.location.age() > 2000) { 236 | strip.SetPixelColor(2, red); 237 | } 238 | else if (gps.location.isValid() == true) { 239 | strip.SetPixelColor(2, green); 240 | } 241 | else { 242 | strip.SetPixelColor(2, green); 243 | } 244 | #endif 245 | 246 | //Change NeoPixel 7 for RX status 247 | if ((iwm == 0) || (iwm == 4) || (iwm == 6)) { 248 | strip.SetPixelColor(7, off); 249 | } 250 | 251 | strip.Show(); 252 | 253 | TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE; 254 | TIMERG0.wdt_feed = 1; 255 | TIMERG0.wdt_wprotect = 0; 256 | smartDelay(500); 257 | } 258 | } 259 | #endif 260 | 261 | // Pin mapping for modules from december 2020 262 | const lmic_pinmap lmic_pins = { 263 | .nss = 5, 264 | .rxtx = LMIC_UNUSED_PIN, 265 | .rst = 26, 266 | .dio = {36, 35, LMIC_UNUSED_PIN}, 267 | }; 268 | 269 | // Pin mapping for modules before december 2020 270 | //const lmic_pinmap lmic_pins = { 271 | // .nss = 5, 272 | // .rxtx = LMIC_UNUSED_PIN, 273 | // .rst = 26, 274 | // .dio = {36, 35, LMIC_UNUSED_PIN}, 275 | //}; 276 | 277 | 278 | void onEvent (ev_t ev) { 279 | Serial.print(os_getTime()); 280 | Serial.print(": "); 281 | switch (ev) { 282 | case EV_SCAN_TIMEOUT: 283 | Serial.println(F("EV_SCAN_TIMEOUT")); 284 | break; 285 | case EV_BEACON_FOUND: 286 | Serial.println(F("EV_BEACON_FOUND")); 287 | break; 288 | case EV_BEACON_MISSED: 289 | Serial.println(F("EV_BEACON_MISSED")); 290 | break; 291 | case EV_BEACON_TRACKED: 292 | Serial.println(F("EV_BEACON_TRACKED")); 293 | break; 294 | case EV_JOINING: 295 | Serial.println(F("EV_JOINING")); 296 | UISet(&UIInputbox_awnh87, "Joining"); 297 | break; 298 | case EV_JOINED: 299 | Serial.println(F("EV_JOINED")); 300 | UISet(&UIInputbox_awnh87, "Joined"); 301 | next = true; 302 | break; 303 | /* 304 | || This event is defined but not used in the code. No 305 | || point in wasting codespace on it. 306 | || 307 | || case EV_RFU1: 308 | || Serial.println(F("EV_RFU1")); 309 | || break; 310 | */ 311 | case EV_JOIN_FAILED: 312 | Serial.println(F("EV_JOIN_FAILED")); 313 | break; 314 | case EV_REJOIN_FAILED: 315 | Serial.println(F("EV_REJOIN_FAILED")); 316 | break; 317 | case EV_TXCOMPLETE: 318 | Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); 319 | cnt++; 320 | if (iwm != 4) { 321 | //txcnt = String("Sent " + String(cnt)); 322 | txcnt = String("Sent " + String(LMIC.seqnoUp - 1)); 323 | UISet(&UIInputbox_awnh87, txcnt); 324 | } 325 | if (LMIC.txrxFlags & TXRX_ACK) { 326 | Serial.println(F("Received ack")); 327 | ackrx = true; 328 | if (iwm == 1) { 329 | M5.Speaker.beep(); 330 | } 331 | rssi = LMIC.rssi - 64; 332 | snr = LMIC.snr * 0.25; 333 | dtostrf(snr, 5, 1, charsnr); 334 | UISet(&UIProgressbar_eymzer, (rssi + 130) * 2); 335 | UISet(&UITextbox_859t1hi, rssi); 336 | UISet(&UITextbox_olwwlae, charsnr); 337 | 338 | if ((iwm == 3) || (iwm == 4)) { 339 | 340 | gwcnt = LMIC.nGws; 341 | margin = LMIC.gwMargin -7.5 - (isf * 2.5); 342 | String gwcntsnr = String(String(gwcnt) + " SNR: " + String(margin)); 343 | UISet(&UIInputbox_6nssds, gwcntsnr); 344 | } 345 | 346 | #ifdef M5go 347 | if (rssi < -120) { 348 | strip.SetPixelColor(7, blue); 349 | } else if (rssi < -115) { 350 | strip.SetPixelColor(7, lightblue); 351 | } else if (rssi < -110) { 352 | strip.SetPixelColor(7, green); 353 | } else if (rssi < -105) { 354 | strip.SetPixelColor(7, yellow); 355 | } else if (rssi < -100) { 356 | strip.SetPixelColor(7, orange); 357 | } else { 358 | strip.SetPixelColor(7, red); 359 | } 360 | #endif 361 | 362 | } 363 | 364 | if (LMIC.txrxFlags & TXRX_NACK) { 365 | Serial.println(F("No ACK received ")); 366 | ackrx = false; 367 | UISet(&UITextbox_859t1hi, "-130"); 368 | UISet(&UITextbox_olwwlae, "-20.0"); 369 | UISet(&UIProgressbar_eymzer, 0); 370 | UISet(&UIInputbox_6nssds, ""); 371 | } 372 | 373 | if (LMIC.dataLen) { 374 | Serial.println(F("Received ")); 375 | Serial.println(LMIC.dataLen); 376 | Serial.println(F(" bytes of payload")); 377 | } 378 | next = true; 379 | break; 380 | case EV_LOST_TSYNC: 381 | Serial.println(F("EV_LOST_TSYNC")); 382 | break; 383 | case EV_RESET: 384 | Serial.println(F("EV_RESET")); 385 | break; 386 | case EV_RXCOMPLETE: 387 | // data received in ping slot 388 | Serial.println(F("EV_RXCOMPLETE")); 389 | break; 390 | case EV_LINK_DEAD: 391 | Serial.println(F("EV_LINK_DEAD")); 392 | break; 393 | case EV_LINK_ALIVE: 394 | Serial.println(F("EV_LINK_ALIVE")); 395 | break; 396 | /* 397 | || This event is defined but not used in the code. No 398 | || point in wasting codespace on it. 399 | || 400 | || case EV_SCAN_FOUND: 401 | || Serial.println(F("EV_SCAN_FOUND")); 402 | || break; 403 | */ 404 | case EV_TXSTART: 405 | Serial.println(F("EV_TXSTART")); 406 | UISet(&UIInputbox_awnh87, "Sending"); 407 | break; 408 | case EV_TXCANCELED: 409 | Serial.println(F("EV_TXCANCELED")); 410 | break; 411 | case EV_RXSTART: 412 | /* do not print anything -- it wrecks timing */ 413 | break; 414 | case EV_JOIN_TXCOMPLETE: 415 | Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept")); 416 | break; 417 | default: 418 | Serial.print(F("Unknown event: ")); 419 | Serial.println((unsigned) ev); 420 | break; 421 | } 422 | } 423 | 424 | //Delay without delay 425 | static void smartDelay(unsigned long ms) 426 | { 427 | unsigned long start = millis(); 428 | do 429 | {} while (millis() - start < ms); 430 | } 431 | 432 | #ifdef M5gps 433 | //Write GPS-Data into variables 434 | void gpsdata() { 435 | year = gps.date.year(); 436 | month = gps.date.month(); 437 | day = gps.date.day(); 438 | hour = gps.time.hour(); 439 | minute = gps.time.minute(); 440 | second = gps.time.second(); 441 | latitude = gps.location.lat(); 442 | longitude = gps.location.lng(); 443 | alt = gps.altitude.meters(); 444 | hdop = gps.hdop.value(); 445 | } 446 | #endif 447 | 448 | #ifdef M5gps 449 | //Initialize GPX-Track to SD-Card 450 | void gpxinit() { 451 | if (cardin == true && gps.location.isValid() == true) { 452 | sdwrite = true; 453 | sprintf(filename1, "/%02d-%02d-%02d", day, month, year - 2000); 454 | sprintf(filepath, "/%02d-%02d-%02d/%02d-%02d%s", day, month, year - 2000, hour, minute, ".GPX"); 455 | 456 | SD.mkdir(filename1); 457 | if (!SD.exists(filepath)) { 458 | dataFile = SD.open(filepath, FILE_WRITE); 459 | dataFile.print(F( 460 | "\r\n" 461 | "\r\n" 464 | "\t\r\n\r\n")); 465 | dataFile.print(F("\r\n\r\n\r\n")); 466 | dataFile.close(); 467 | } 468 | } 469 | } 470 | 471 | //Write data to GPX-File 472 | void writegpx() { 473 | if (gps.location.isValid() == true) { 474 | gpsdata(); 475 | sprintf(date1, "%4d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second); 476 | dataFile = SD.open(filepath, FILE_WRITE); 477 | unsigned long filesize = dataFile.size(); 478 | filesize -= 27; 479 | dataFile.seek(filesize); 480 | dataFile.print(F("")); 485 | dataFile.print(F("")); 488 | dataFile.print(F("")); 489 | dataFile.print(alt, 1); 490 | dataFile.print(F("\r\n")); 491 | dataFile.print(hdop2, 1); 492 | dataFile.println(F("\r\n")); 493 | dataFile.print(F("\r\n\r\n\r\n")); 494 | dataFile.close(); 495 | } 496 | } 497 | #endif 498 | 499 | //Settings for LoRaWAN 500 | void initlora() { 501 | 502 | // LMIC init 503 | os_init(); 504 | // Reset the MAC state. Session and pending data transfers will be discarded. 505 | LMIC_reset(); 506 | 507 | // Set static session parameters. Instead of dynamically establishing a session 508 | // by joining the network, precomputed session parameters are be provided. 509 | #ifdef PROGMEM 510 | // On AVR, these values are stored in flash and only copied to RAM 511 | // once. Copy them to a temporary buffer here, LMIC_setSession will 512 | // copy them into a buffer of its own again. 513 | uint8_t appskey[sizeof(APPSKEY)]; 514 | uint8_t nwkskey[sizeof(NWKSKEY)]; 515 | memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); 516 | memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); 517 | LMIC_setSession (0x13, DEVADDR, nwkskey, appskey); 518 | #else 519 | // If not running an AVR with PROGMEM, just use the arrays directly 520 | LMIC_setSession (0x13, DEVADDR, NWKSKEY, APPSKEY); 521 | #endif 522 | 523 | #if defined(CFG_eu868) 524 | Serial.println("Region eu868"); 525 | // Set up the channels used by the Things Network, which corresponds 526 | // to the defaults of most gateways. Without this, only three base 527 | // channels from the LoRaWAN specification are used, which certainly 528 | // works, so it is good for debugging, but can overload those 529 | // frequencies, so be sure to configure the full frequency range of 530 | // your network here (unless your network autoconfigures them). 531 | // Setting up channels should happen after LMIC_setSession, as that 532 | // configures the minimal channel set. The LMIC doesn't let you change 533 | // the three basic settings, but we show them here. 534 | LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 535 | LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band 536 | LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 537 | LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 538 | LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 539 | LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 540 | LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 541 | LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 542 | LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band 543 | // TTN defines an additional channel at 869.525Mhz using SF9 for class B 544 | // devices' ping slots. LMIC does not have an easy way to define set this 545 | // frequency and support for class B is spotty and untested, so this 546 | // frequency is not configured here. 547 | #elif defined(CFG_us915) || defined(CFG_au915) 548 | Serial.println("Region us915/au915"); 549 | // NA-US and AU channels 0-71 are configured automatically 550 | // but only one group of 8 should (a subband) should be active 551 | // TTN recommends the second sub band, 1 in a zero based count. 552 | // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json 553 | LMIC_selectSubBand(1); 554 | #elif defined(CFG_as923) 555 | Serial.println("Region as923"); 556 | // Set up the channels used in your country. Only two are defined by default, 557 | // and they cannot be changed. Use BAND_CENTI to indicate 1% duty cycle. 558 | // LMIC_setupChannel(0, 923200000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); 559 | // LMIC_setupChannel(1, 923400000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); 560 | 561 | // ... extra definitions for channels 2..n here 562 | #elif defined(CFG_kr920) 563 | Serial.println("Region kr920"); 564 | // Set up the channels used in your country. Three are defined by default, 565 | // and they cannot be changed. Duty cycle doesn't matter, but is conventionally 566 | // BAND_MILLI. 567 | // LMIC_setupChannel(0, 922100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 568 | // LMIC_setupChannel(1, 922300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 569 | // LMIC_setupChannel(2, 922500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 570 | 571 | // ... extra definitions for channels 3..n here. 572 | #elif defined(CFG_in866) 573 | Serial.println("Region in866"); 574 | // Set up the channels used in your country. Three are defined by default, 575 | // and they cannot be changed. Duty cycle doesn't matter, but is conventionally 576 | // BAND_MILLI. 577 | // LMIC_setupChannel(0, 865062500, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 578 | // LMIC_setupChannel(1, 865402500, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 579 | // LMIC_setupChannel(2, 865985000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 580 | 581 | // ... extra definitions for channels 3..n here. 582 | #else 583 | # error Region not supported 584 | #endif 585 | 586 | //Disable Adaptive Data Rate 587 | LMIC_setAdrMode(0); 588 | 589 | // Disable link check validation 590 | LMIC_setLinkCheckMode(0); 591 | 592 | // TTN uses SF9 for its RX2 window. 593 | LMIC.dn2Dr = DR_SF9; 594 | 595 | // Set data rate and transmit power for uplink 596 | LMIC_setDrTxpow(DR_SF7, 14); 597 | 598 | LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); 599 | LMIC.rxDelay = 5; 600 | 601 | frcntinit(); 602 | } 603 | 604 | //Settings for LoRaWAN ABP 605 | void initloraabp() { 606 | // LMIC init 607 | os_init(); 608 | // Reset the MAC state. Session and pending data transfers will be discarded. 609 | LMIC_reset(); 610 | 611 | // Set static session parameters. Instead of dynamically establishing a session 612 | // by joining the network, precomputed session parameters are be provided. 613 | #ifdef PROGMEM 614 | // On AVR, these values are stored in flash and only copied to RAM 615 | // once. Copy them to a temporary buffer here, LMIC_setSession will 616 | // copy them into a buffer of its own again. 617 | uint8_t appskey[sizeof(APPSKEY)]; 618 | uint8_t nwkskey[sizeof(NWKSKEY)]; 619 | memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); 620 | memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); 621 | LMIC_setSession (0x13, DEVADDR, nwkskey, appskey); 622 | #else 623 | // If not running an AVR with PROGMEM, just use the arrays directly 624 | LMIC_setSession (0x13, DEVADDR, NWKSKEY, APPSKEY); 625 | #endif 626 | 627 | #if defined(CFG_eu868) 628 | Serial.println("Region eu868"); 629 | // Set up the channels used by the Things Network, which corresponds 630 | // to the defaults of most gateways. Without this, only three base 631 | // channels from the LoRaWAN specification are used, which certainly 632 | // works, so it is good for debugging, but can overload those 633 | // frequencies, so be sure to configure the full frequency range of 634 | // your network here (unless your network autoconfigures them). 635 | // Setting up channels should happen after LMIC_setSession, as that 636 | // configures the minimal channel set. The LMIC doesn't let you change 637 | // the three basic settings, but we show them here. 638 | LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 639 | LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band 640 | LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 641 | LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 642 | LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 643 | LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 644 | LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 645 | LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band 646 | LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band 647 | // TTN defines an additional channel at 869.525Mhz using SF9 for class B 648 | // devices' ping slots. LMIC does not have an easy way to define set this 649 | // frequency and support for class B is spotty and untested, so this 650 | // frequency is not configured here. 651 | #elif defined(CFG_us915) || defined(CFG_au915) 652 | Serial.println("Region us915/au915"); 653 | // NA-US and AU channels 0-71 are configured automatically 654 | // but only one group of 8 should (a subband) should be active 655 | // TTN recommends the second sub band, 1 in a zero based count. 656 | // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json 657 | LMIC_selectSubBand(1); 658 | #elif defined(CFG_as923) 659 | Serial.println("Region as923"); 660 | // Set up the channels used in your country. Only two are defined by default, 661 | // and they cannot be changed. Use BAND_CENTI to indicate 1% duty cycle. 662 | // LMIC_setupChannel(0, 923200000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); 663 | // LMIC_setupChannel(1, 923400000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); 664 | 665 | // ... extra definitions for channels 2..n here 666 | #elif defined(CFG_kr920) 667 | Serial.println("Region kr920"); 668 | // Set up the channels used in your country. Three are defined by default, 669 | // and they cannot be changed. Duty cycle doesn't matter, but is conventionally 670 | // BAND_MILLI. 671 | // LMIC_setupChannel(0, 922100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 672 | // LMIC_setupChannel(1, 922300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 673 | // LMIC_setupChannel(2, 922500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 674 | 675 | // ... extra definitions for channels 3..n here. 676 | #elif defined(CFG_in866) 677 | Serial.println("Region in866"); 678 | // Set up the channels used in your country. Three are defined by default, 679 | // and they cannot be changed. Duty cycle doesn't matter, but is conventionally 680 | // BAND_MILLI. 681 | // LMIC_setupChannel(0, 865062500, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 682 | // LMIC_setupChannel(1, 865402500, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 683 | // LMIC_setupChannel(2, 865985000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_MILLI); 684 | 685 | // ... extra definitions for channels 3..n here. 686 | #else 687 | # error Region not supported 688 | #endif 689 | 690 | //Disable Adaptive Data Rate 691 | LMIC_setAdrMode(0); 692 | 693 | // Disable link check validation 694 | LMIC_setLinkCheckMode(0); 695 | 696 | LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); 697 | LMIC.rxDelay = 5; 698 | 699 | // TTN uses SF9 for its RX2 window. 700 | LMIC.dn2Dr = DR_SF9; 701 | 702 | // Set data rate and transmit power for uplink 703 | LMIC_setDrTxpow(DR_SF7, 14); 704 | otaa = 0; 705 | cnt = -1; 706 | frcntinit(); 707 | } 708 | 709 | //Settings for LoRaWAN OTAA 710 | void initloraotaa() { 711 | // LMIC init 712 | os_init(); 713 | 714 | // Reset the MAC state. Session and pending data transfers will be discarded. 715 | LMIC_reset(); 716 | 717 | //LMIC specific parameters 718 | LMIC_setAdrMode(0); 719 | LMIC_setLinkCheckMode(0); 720 | LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); 721 | LMIC.rxDelay = 5; 722 | next = false; 723 | LMIC_startJoining(); 724 | UISet(&UIInputbox_awnh87, "Joining"); 725 | otaa = 1; 726 | cnt = -1; 727 | } 728 | 729 | //Send data using LoRaWAN 730 | void sendobject(osjob_t* j) { 731 | 732 | 733 | 734 | int32_t lat = latitude * 10000; 735 | int32_t lon = longitude * 10000; 736 | int16_t altitude = alt * 100; 737 | int8_t hdopGPS = hdop / 10; 738 | 739 | coords[0] = lat; 740 | coords[1] = lat >> 8; 741 | coords[2] = lat >> 16; 742 | 743 | coords[3] = lon; 744 | coords[4] = lon >> 8; 745 | coords[5] = lon >> 16; 746 | 747 | coords[6] = altitude; 748 | coords[7] = altitude >> 8; 749 | 750 | coords[8] = hdopGPS; 751 | 752 | sentMillis = millis(); 753 | 754 | #ifndef M5gps 755 | if (iwm == 0) { 756 | #else 757 | if (iwm == 0 && gps.location.isValid() == true && gps.location.age() < 2000) { 758 | #endif 759 | 760 | //UISet(&UIInputbox_awnh87, "Sending"); 761 | 762 | if (isf == 0) { 763 | LMIC_setDrTxpow(DR_SF7, 14); 764 | } else if (isf == 1) { 765 | LMIC_setDrTxpow(DR_SF8, 14); 766 | } else if (isf == 2) { 767 | LMIC_setDrTxpow(DR_SF9, 14); 768 | } else if (isf == 3) { 769 | LMIC_setDrTxpow(DR_SF10, 14); 770 | } else if (isf == 4) { 771 | LMIC_setDrTxpow(DR_SF11, 14); 772 | } else if (isf == 5) { 773 | LMIC_setDrTxpow(DR_SF12, 14); 774 | } 775 | 776 | #ifdef M5gps 777 | if (LMIC.opmode & OP_TXRXPEND) { 778 | Serial.println(F("OP_TXRXPEND, not sending")); 779 | UISet(&UIInputbox_awnh87, "TX Pending"); 780 | } else { 781 | // Prepare upstream data transmission at the next possible time. 782 | next = false; 783 | LMIC_setTxData2(1, coords, sizeof(coords) , 0); 784 | Serial.println(F("Packet queued")); 785 | UISet(&UIInputbox_awnh87, "Queued"); 786 | } 787 | #else 788 | if (LMIC.opmode & OP_TXRXPEND) { 789 | Serial.println(F("OP_TXRXPEND, not sending")); 790 | UISet(&UIInputbox_awnh87, "TX Pending"); 791 | } else { 792 | // Prepare upstream data transmission at the next possible time. 793 | next = false; 794 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 0); 795 | Serial.println(F("Packet queued")); 796 | UISet(&UIInputbox_awnh87, "Queued"); 797 | } 798 | #endif 799 | 800 | #ifndef M5gps 801 | } else if ((iwm == 1) || (iwm == 2)) { 802 | #else 803 | } else if (((iwm == 1) && gps.location.isValid() == true && gps.location.age() < 2000) || (iwm == 2)) { 804 | #endif 805 | 806 | UISet(&UIInputbox_awnh87, "ACK"); 807 | 808 | if (isf == 0) { 809 | LMIC_setDrTxpow(DR_SF7, 14); 810 | } else if (isf == 1) { 811 | LMIC_setDrTxpow(DR_SF8, 14); 812 | } else if (isf == 2) { 813 | LMIC_setDrTxpow(DR_SF9, 14); 814 | } else if (isf == 3) { 815 | LMIC_setDrTxpow(DR_SF10, 14); 816 | } else if (isf == 4) { 817 | LMIC_setDrTxpow(DR_SF11, 14); 818 | } else if (isf == 5) { 819 | LMIC_setDrTxpow(DR_SF12, 14); 820 | } 821 | 822 | #ifdef M5gps 823 | if (LMIC.opmode & OP_TXRXPEND) { 824 | Serial.println(F("OP_TXRXPEND, not sending")); 825 | UISet(&UIInputbox_awnh87, "TX Pending"); 826 | } else { 827 | // Prepare upstream data transmission at the next possible time. 828 | next = false; 829 | LMIC_setTxData2(1, coords, sizeof(coords) , 1); 830 | LMIC.txCnt = TXCONF_ATTEMPTS; 831 | Serial.println(F("Packet queued")); 832 | UISet(&UIInputbox_awnh87, "Queued"); 833 | } 834 | #else 835 | if (LMIC.opmode & OP_TXRXPEND) { 836 | Serial.println(F("OP_TXRXPEND, not sending")); 837 | UISet(&UIInputbox_awnh87, "TX Pending"); 838 | } else { 839 | // Prepare upstream data transmission at the next possible time. 840 | next = false; 841 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 842 | LMIC.txCnt = TXCONF_ATTEMPTS; 843 | Serial.println(F("Packet queued")); 844 | UISet(&UIInputbox_awnh87, "Queued"); 845 | } 846 | #endif 847 | 848 | } else if (iwm == 3) { 849 | 850 | UISet(&UIInputbox_awnh87, "LCR"); 851 | 852 | if (isf == 0) { 853 | LMIC_setDrTxpow(DR_SF7, 14); 854 | cnt = -1; 855 | } else if (isf == 1) { 856 | LMIC_setDrTxpow(DR_SF8, 14); 857 | cnt = -1; 858 | } else if (isf == 2) { 859 | LMIC_setDrTxpow(DR_SF9, 14); 860 | cnt = -1; 861 | } else if (isf == 3) { 862 | LMIC_setDrTxpow(DR_SF10, 14); 863 | cnt = -1; 864 | } else if (isf == 4) { 865 | LMIC_setDrTxpow(DR_SF11, 14); 866 | cnt = -1; 867 | } else if (isf == 5) { 868 | LMIC_setDrTxpow(DR_SF12, 14); 869 | cnt = -1; 870 | } 871 | 872 | #ifdef M5gps 873 | if (LMIC.opmode & OP_TXRXPEND) { 874 | Serial.println(F("OP_TXRXPEND, not sending")); 875 | UISet(&UIInputbox_awnh87, "Error"); 876 | } else { 877 | // Prepare upstream data transmission at the next possible time. 878 | next = false; 879 | LMIC_setLinkCheckRequestOnce(1); 880 | LMIC_setTxData2(1, coords, sizeof(coords) , 1); 881 | LMIC.txCnt = TXCONF_ATTEMPTS; 882 | UISet(&UIInputbox_awnh87, "Queued"); 883 | Serial.println(F("Packet queued")); 884 | } 885 | #else 886 | if (LMIC.opmode & OP_TXRXPEND) { 887 | Serial.println(F("OP_TXRXPEND, not sending")); 888 | UISet(&UIInputbox_awnh87, "Error"); 889 | } else { 890 | // Prepare upstream data transmission at the next possible time. 891 | LMIC_setLinkCheckRequestOnce(1); 892 | next = false; 893 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 894 | LMIC.txCnt = TXCONF_ATTEMPTS; 895 | UISet(&UIInputbox_awnh87, "Queued"); 896 | Serial.println(F("Packet queued")); 897 | } 898 | #endif 899 | 900 | } 901 | } 902 | 903 | void sendobjectotaa(osjob_t* j) { 904 | 905 | int32_t lat = latitude * 10000; 906 | int32_t lon = longitude * 10000; 907 | int16_t altitude = alt * 100; 908 | int8_t hdopGPS = hdop / 10; 909 | 910 | coords[0] = lat; 911 | coords[1] = lat >> 8; 912 | coords[2] = lat >> 16; 913 | 914 | coords[3] = lon; 915 | coords[4] = lon >> 8; 916 | coords[5] = lon >> 16; 917 | 918 | coords[6] = altitude; 919 | coords[7] = altitude >> 8; 920 | 921 | coords[8] = hdopGPS; 922 | 923 | sentMillis = millis(); 924 | 925 | UISet(&UIInputbox_awnh87, "Sending"); 926 | 927 | #ifdef M5gps 928 | if (otaaack == 0) { 929 | if (LMIC.opmode & OP_TXRXPEND) { 930 | Serial.println(F("OP_TXRXPEND, not sending")); 931 | UISet(&UIInputbox_awnh87, "Error"); 932 | } else { 933 | // Prepare upstream data transmission at the next possible time. 934 | next = false; 935 | LMIC_setTxData2(1, coords, sizeof(coords) , 0); 936 | LMIC.txCnt = TXCONF_ATTEMPTS; 937 | UISet(&UIInputbox_awnh87, "Queued"); 938 | Serial.println(F("Packet queued")); 939 | } 940 | } else if (otaaack == 1) { 941 | if (LMIC.opmode & OP_TXRXPEND) { 942 | Serial.println(F("OP_TXRXPEND, not sending")); 943 | UISet(&UIInputbox_awnh87, "Error"); 944 | } else { 945 | // Prepare upstream data transmission at the next possible time. 946 | next = false; 947 | LMIC_setTxData2(1, coords, sizeof(coords) , 1); 948 | LMIC.txCnt = TXCONF_ATTEMPTS; 949 | UISet(&UIInputbox_awnh87, "Queued"); 950 | Serial.println(F("Packet queued")); 951 | } 952 | } 953 | #else 954 | if (otaaack == 0) { 955 | if (LMIC.opmode & OP_TXRXPEND) { 956 | Serial.println(F("OP_TXRXPEND, not sending")); 957 | UISet(&UIInputbox_awnh87, "Error"); 958 | } else { 959 | // Prepare upstream data transmission at the next possible time. 960 | next = false; 961 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 0); 962 | UISet(&UIInputbox_awnh87, "Queued"); 963 | Serial.println(F("Packet queued")); 964 | } 965 | } else if (otaaack == 1) { 966 | if (LMIC.opmode & OP_TXRXPEND) { 967 | Serial.println(F("OP_TXRXPEND, not sending")); 968 | UISet(&UIInputbox_awnh87, "Error"); 969 | } else { 970 | // Prepare upstream data transmission at the next possible time. 971 | next = false; 972 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 973 | LMIC.txCnt = TXCONF_ATTEMPTS; 974 | UISet(&UIInputbox_awnh87, "Queued"); 975 | Serial.println(F("Packet queued")); 976 | } 977 | } 978 | #endif 979 | } 980 | 981 | #ifdef M5gps 982 | //SiteSurvey function 983 | void ssv() { 984 | 985 | latitude2 = latitude; 986 | longitude2 = longitude; 987 | bool ack710 = false; 988 | bool ackok = false; 989 | 990 | UISet(&UIInputbox_awnh87, "SSV running"); 991 | ssvinit(); 992 | 993 | ssvresult = "DR "; 994 | 995 | LMIC_setDrTxpow(DR_SF7, 14); 996 | isf = 0; 997 | LMIC_setLinkCheckRequestOnce(1); 998 | next = false; 999 | UISet(&UIInputbox_awnh87, "Queued"); 1000 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 1001 | LMIC.txCnt = TXCONF_ATTEMPTS; 1002 | while (next == false){ 1003 | smartDelay(5000); 1004 | } 1005 | 1006 | if (ackrx == true) { 1007 | writessv(); 1008 | ssvresult += "5"; 1009 | ackrx = false; 1010 | ack710 = true; 1011 | ackok = true; 1012 | } 1013 | 1014 | LMIC.bands[BAND_MILLI].avail = os_getTime(); 1015 | LMIC.bands[BAND_CENTI].avail = os_getTime(); 1016 | LMIC.bands[BAND_DECI].avail = os_getTime(); 1017 | LMIC_setDrTxpow(DR_SF8, 14); 1018 | isf = 1; 1019 | LMIC_setLinkCheckRequestOnce(1); 1020 | next = false; 1021 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 1022 | LMIC.txCnt = TXCONF_ATTEMPTS; 1023 | while (next == false){ 1024 | smartDelay(5000); 1025 | } 1026 | 1027 | if (ackrx == true) { 1028 | writessv(); 1029 | ssvresult += "4"; 1030 | ackrx = false; 1031 | ack710 = true; 1032 | ackok = true; 1033 | } 1034 | 1035 | LMIC.bands[BAND_MILLI].avail = os_getTime(); 1036 | LMIC.bands[BAND_CENTI].avail = os_getTime(); 1037 | LMIC.bands[BAND_DECI].avail = os_getTime(); 1038 | LMIC_setDrTxpow(DR_SF9, 14); 1039 | isf = 2; 1040 | LMIC_setLinkCheckRequestOnce(1); 1041 | next = false; 1042 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 1043 | LMIC.txCnt = TXCONF_ATTEMPTS; 1044 | while (next == false){ 1045 | smartDelay(5000); 1046 | } 1047 | 1048 | if (ackrx == true) { 1049 | writessv(); 1050 | ssvresult += "3"; 1051 | ackrx = false; 1052 | ack710 = true; 1053 | ackok = true; 1054 | } 1055 | 1056 | LMIC.bands[BAND_MILLI].avail = os_getTime(); 1057 | LMIC.bands[BAND_CENTI].avail = os_getTime(); 1058 | LMIC.bands[BAND_DECI].avail = os_getTime(); 1059 | LMIC_setDrTxpow(DR_SF10, 14); 1060 | isf = 3; 1061 | LMIC_setLinkCheckRequestOnce(1); 1062 | next = false; 1063 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 1064 | LMIC.txCnt = TXCONF_ATTEMPTS; 1065 | while (next == false){ 1066 | smartDelay(5000); 1067 | } 1068 | 1069 | if (ackrx == true) { 1070 | writessv(); 1071 | ssvresult += "2"; 1072 | ackrx = false; 1073 | ack710 = true; 1074 | ackok = true; 1075 | } 1076 | 1077 | LMIC.bands[BAND_MILLI].avail = os_getTime(); 1078 | LMIC.bands[BAND_CENTI].avail = os_getTime(); 1079 | LMIC.bands[BAND_DECI].avail = os_getTime(); 1080 | LMIC_setDrTxpow(DR_SF11, 14); 1081 | isf = 4; 1082 | LMIC_setLinkCheckRequestOnce(1); 1083 | next = false; 1084 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 1085 | LMIC.txCnt = TXCONF_ATTEMPTS; 1086 | while (next == false){ 1087 | smartDelay(5000); 1088 | } 1089 | 1090 | if (ackrx == true) { 1091 | if (ack710 == true) { 1092 | writessv(); 1093 | } else { 1094 | writessvy(); 1095 | } 1096 | ssvresult += "1"; 1097 | ackrx = false; 1098 | ackok = true; 1099 | } 1100 | 1101 | LMIC.bands[BAND_MILLI].avail = os_getTime(); 1102 | LMIC.bands[BAND_CENTI].avail = os_getTime(); 1103 | LMIC.bands[BAND_DECI].avail = os_getTime(); 1104 | LMIC_setDrTxpow(DR_SF12, 14); 1105 | isf = 5; 1106 | LMIC_setLinkCheckRequestOnce(1); 1107 | next = false; 1108 | LMIC_setTxData2(1, ncoords, sizeof(ncoords) , 1); 1109 | LMIC.txCnt = TXCONF_ATTEMPTS; 1110 | while (next == false){ 1111 | smartDelay(5000); 1112 | } 1113 | 1114 | if (ackrx == true) { 1115 | if (ack710 == true) { 1116 | writessv(); 1117 | } else { 1118 | writessvy(); 1119 | } 1120 | ssvresult += "0"; 1121 | ackrx = false; 1122 | ackok = true; 1123 | } 1124 | 1125 | LMIC.bands[BAND_MILLI].avail = os_getTime(); 1126 | LMIC.bands[BAND_CENTI].avail = os_getTime(); 1127 | LMIC.bands[BAND_DECI].avail = os_getTime(); 1128 | 1129 | if (ackok == false) { 1130 | rssi = 0; 1131 | snr = 0; 1132 | gwcnt = 0; 1133 | margin = 0; 1134 | writessvr(); 1135 | } 1136 | 1137 | lastssv = true; 1138 | writessv(); 1139 | 1140 | lastssv = false; 1141 | firstssv = false; 1142 | 1143 | UISet(&UIInputbox_awnh87, ssvresult); 1144 | 1145 | LMIC_setDrTxpow(DR_SF7, 14); 1146 | isf = 0; 1147 | UISet(&UITextbox_vimqus, sf[isf]); 1148 | } 1149 | 1150 | //Initialize GeoJSON file 1151 | void ssvinit() { 1152 | if (cardin == true && gps.location.isValid() == true) { 1153 | sprintf(filename2, "/%02d-%02d-%02d", day, month, year - 2000); 1154 | sprintf(filepath2, "/%02d-%02d-%02d/%02d-%02d%s", day, month, year - 2000, hour, minute, ".json"); 1155 | 1156 | SD.mkdir(filename2); 1157 | if (!SD.exists(filepath2)) { 1158 | dataFile = SD.open(filepath2, FILE_WRITE); 1159 | dataFile.close(); 1160 | } 1161 | } 1162 | } 1163 | 1164 | //Write data to GeoJSON file - green colour 1165 | void writessv() { 1166 | if (gps.location.isValid() == true) { 1167 | gpsdata(); 1168 | sprintf(date1, "%4d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second); 1169 | 1170 | dataFile = SD.open(filepath2, FILE_WRITE); 1171 | unsigned long filesize = dataFile.size(); 1172 | dataFile.seek(filesize); 1173 | 1174 | if (lastssv == false) { 1175 | if (firstssv == false) { 1176 | firstssv = true; 1177 | dataFile.println(F("{")); 1178 | dataFile.println(F("\"type\": \"FeatureCollection\",")); 1179 | dataFile.println(F("\"features\": [{")); 1180 | } else { 1181 | dataFile.println(F(",{")); 1182 | } 1183 | dataFile.println(F("\"type\": \"Feature\",")); 1184 | dataFile.println(F("\"properties\": {")); 1185 | dataFile.print(F("\"sf\": \"")); 1186 | dataFile.print(sf[isf]); 1187 | dataFile.print(F("\",\r\n")); 1188 | dataFile.print(F("\"rssi\": \"")); 1189 | dataFile.print(rssi); 1190 | dataFile.print(F("\",\r\n")); 1191 | dataFile.print(F("\"snr\": \"")); 1192 | dataFile.print(snr); 1193 | dataFile.print(F("\",\r\n")); 1194 | dataFile.print(F("\"gwcnt\": \"")); 1195 | dataFile.print(gwcnt); 1196 | dataFile.print(F("\",\r\n")); 1197 | dataFile.print(F("\"ulsnr\": \"")); 1198 | dataFile.print(margin); 1199 | dataFile.print(F("\",\r\n")); 1200 | dataFile.println(F("\"marker-color\": \"#008800\",")); 1201 | dataFile.println(F("\"marker-symbol\": \"lighthouse\"")); 1202 | dataFile.println(F("},")); 1203 | dataFile.println(F("\"geometry\": {")); 1204 | dataFile.println(F("\"type\": \"Point\",")); 1205 | dataFile.print(F("\"coordinates\": [")); 1206 | dataFile.print(longitude2, 7); 1207 | dataFile.print(F(", ")); 1208 | dataFile.print(latitude2, 7); 1209 | dataFile.print(F("]\r\n")); 1210 | dataFile.println(F("}")); 1211 | dataFile.println(F("}")); 1212 | dataFile.close(); 1213 | } else { 1214 | dataFile.println(F("]}")); 1215 | dataFile.close(); 1216 | } 1217 | } 1218 | } 1219 | 1220 | //Write data to GeoJSON file - yellow colour 1221 | void writessvy() { 1222 | if (gps.location.isValid() == true) { 1223 | gpsdata(); 1224 | sprintf(date1, "%4d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second); 1225 | 1226 | dataFile = SD.open(filepath2, FILE_WRITE); 1227 | unsigned long filesize = dataFile.size(); 1228 | dataFile.seek(filesize); 1229 | 1230 | if (lastssv == false) { 1231 | if (firstssv == false) { 1232 | firstssv = true; 1233 | dataFile.println(F("{")); 1234 | dataFile.println(F("\"type\": \"FeatureCollection\",")); 1235 | dataFile.println(F("\"features\": [{")); 1236 | } else { 1237 | dataFile.println(F(",{")); 1238 | } 1239 | dataFile.println(F("\"type\": \"Feature\",")); 1240 | dataFile.println(F("\"properties\": {")); 1241 | dataFile.print(F("\"sf\": \"")); 1242 | dataFile.print(sf[isf]); 1243 | dataFile.print(F("\",\r\n")); 1244 | dataFile.print(F("\"rssi\": \"")); 1245 | dataFile.print(rssi); 1246 | dataFile.print(F("\",\r\n")); 1247 | dataFile.print(F("\"snr\": \"")); 1248 | dataFile.print(snr); 1249 | dataFile.print(F("\",\r\n")); 1250 | dataFile.print(F("\"gwcnt\": \"")); 1251 | dataFile.print(gwcnt); 1252 | dataFile.print(F("\",\r\n")); 1253 | dataFile.print(F("\"ulsnr\": \"")); 1254 | dataFile.print(margin); 1255 | dataFile.print(F("\",\r\n")); 1256 | dataFile.println(F("\"marker-color\": \"#888800\",")); 1257 | dataFile.println(F("\"marker-symbol\": \"lighthouse\"")); 1258 | dataFile.println(F("},")); 1259 | dataFile.println(F("\"geometry\": {")); 1260 | dataFile.println(F("\"type\": \"Point\",")); 1261 | dataFile.print(F("\"coordinates\": [")); 1262 | dataFile.print(longitude2, 7); 1263 | dataFile.print(F(", ")); 1264 | dataFile.print(latitude2, 7); 1265 | dataFile.print(F("]\r\n")); 1266 | dataFile.println(F("}")); 1267 | dataFile.println(F("}")); 1268 | dataFile.close(); 1269 | } else { 1270 | dataFile.println(F("]}")); 1271 | dataFile.close(); 1272 | } 1273 | } 1274 | } 1275 | 1276 | //Write data to GeoJSON file - red colour 1277 | void writessvr() { 1278 | if (gps.location.isValid() == true) { 1279 | gpsdata(); 1280 | sprintf(date1, "%4d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second); 1281 | 1282 | dataFile = SD.open(filepath2, FILE_WRITE); 1283 | unsigned long filesize = dataFile.size(); 1284 | dataFile.seek(filesize); 1285 | 1286 | if (lastssv == false) { 1287 | if (firstssv == false) { 1288 | firstssv = true; 1289 | dataFile.println(F("{")); 1290 | dataFile.println(F("\"type\": \"FeatureCollection\",")); 1291 | dataFile.println(F("\"features\": [{")); 1292 | } else { 1293 | dataFile.println(F(",{")); 1294 | } 1295 | dataFile.println(F("\"type\": \"Feature\",")); 1296 | dataFile.println(F("\"properties\": {")); 1297 | dataFile.print(F("\"sf\": \"")); 1298 | dataFile.print(sf[isf]); 1299 | dataFile.print(F("\",\r\n")); 1300 | dataFile.print(F("\"rssi\": \"")); 1301 | dataFile.print(rssi); 1302 | dataFile.print(F("\",\r\n")); 1303 | dataFile.print(F("\"snr\": \"")); 1304 | dataFile.print(snr); 1305 | dataFile.print(F("\",\r\n")); 1306 | dataFile.print(F("\"gwcnt\": \"")); 1307 | dataFile.print(gwcnt); 1308 | dataFile.print(F("\",\r\n")); 1309 | dataFile.print(F("\"ulsnr\": \"")); 1310 | dataFile.print(margin); 1311 | dataFile.print(F("\",\r\n")); 1312 | dataFile.println(F("\"marker-color\": \"#880000\",")); 1313 | dataFile.println(F("\"marker-symbol\": \"lighthouse\"")); 1314 | dataFile.println(F("},")); 1315 | dataFile.println(F("\"geometry\": {")); 1316 | dataFile.println(F("\"type\": \"Point\",")); 1317 | dataFile.print(F("\"coordinates\": [")); 1318 | dataFile.print(longitude2, 7); 1319 | dataFile.print(F(", ")); 1320 | dataFile.print(latitude2, 7); 1321 | dataFile.print(F("]\r\n")); 1322 | dataFile.println(F("}")); 1323 | dataFile.println(F("}")); 1324 | dataFile.close(); 1325 | } else { 1326 | dataFile.println(F("]}")); 1327 | dataFile.close(); 1328 | } 1329 | } 1330 | } 1331 | 1332 | #endif 1333 | 1334 | // Manage LoRa communication 1335 | void lmictask( void * parameter ) { 1336 | for (;;) { 1337 | // Execute the LMIC scheduler 1338 | os_runloop_once(); 1339 | 1340 | // Let other tasks run 1341 | vTaskDelay(1); 1342 | } 1343 | } 1344 | 1345 | 1346 | 1347 | 1348 | 1349 | 1350 | 1351 | 1352 | //init SD card for frame counter 1353 | void frcntinit() { 1354 | if (cardin == true) { 1355 | if (!SD.exists(counterfile)) { 1356 | dataFile = SD.open(counterfile, FILE_WRITE); 1357 | dataFile.print(F("0")); 1358 | dataFile.close(); 1359 | } else { 1360 | dataFile = SD.open(counterfile); 1361 | framecounter = dataFile.parseInt(); 1362 | //Serial.println(framecounter); 1363 | dataFile.close(); 1364 | LMIC.seqnoUp = framecounter; 1365 | //Serial.println(LMIC.seqnoUp); 1366 | } 1367 | } 1368 | } 1369 | 1370 | //Write frame counter to sd card for ABP mode, to be compliant with LoRaWAN and TTN Stack V3. Only updates if framecounter changed. 1371 | void frcnt() { 1372 | if (cardin == true) { 1373 | if (framecounter != LMIC.seqnoUp) { 1374 | framecounter = LMIC.seqnoUp; 1375 | dataFile = SD.open(counterfile, FILE_WRITE); 1376 | dataFile.println(framecounter); 1377 | dataFile.close(); 1378 | } 1379 | } 1380 | } 1381 | 1382 | //initial setup 1383 | void setup() { 1384 | /* Prepare M5STACK */ 1385 | M5.begin(); 1386 | M5.Power.begin(); 1387 | Wire.begin(); 1388 | #ifdef M5gps 1389 | serialgps.begin(9600, SERIAL_8N1, 16, 17); 1390 | #endif 1391 | M5.Lcd.setBrightness(50); 1392 | //M5.Lcd.drawBitmap(0, 0, 320, 240, (uint16_t *)imgName); 1393 | M5.Lcd.pushImage(0, 0, 320, 240, (uint16_t *)gImage_logoM5); 1394 | smartDelay(2000); 1395 | 1396 | if (SD.exists(filename)) { 1397 | cardin = true; 1398 | } 1399 | 1400 | initlora(); 1401 | 1402 | xTaskCreatePinnedToCore( 1403 | lmictask, 1404 | "LMIC-Task", 1405 | 2048, 1406 | NULL, 1407 | 1, 1408 | &TaskLMIC, 1409 | 1); 1410 | 1411 | #ifdef M5gps 1412 | xTaskCreatePinnedToCore( 1413 | gpsupdate, 1414 | "TaskGPS", 1415 | 10000, 1416 | NULL, 1417 | 1, 1418 | &TaskGPS, 1419 | 0); 1420 | #endif 1421 | 1422 | #ifdef M5go 1423 | strip.Begin(); 1424 | strip.Show(); 1425 | 1426 | if (powersave == false) { 1427 | strip.SetBrightness(50); 1428 | } else { 1429 | strip.SetBrightness(0); 1430 | } 1431 | 1432 | xTaskCreatePinnedToCore( 1433 | pixelupdate, 1434 | "TaskPixel", 1435 | 10000, 1436 | NULL, 1437 | 1, 1438 | &TaskPixel, 1439 | 0); 1440 | #endif 1441 | 1442 | /* Prepare UI */ 1443 | UIBegin(); 1444 | LayerFunction_default(0); 1445 | 1446 | #ifdef M5gps 1447 | M5.Lcd.pushImage(5, 2, 24, 24, (uint16_t *)ICON_10_24); 1448 | M5.Lcd.pushImage(65, 5, 24, 24, (uint16_t *)ICON_23_24); 1449 | #endif 1450 | 1451 | if (SD.exists(filename)) { 1452 | M5.Lcd.pushImage(200, 5, 24, 24, (uint16_t *)ICON_22_24); 1453 | cardin = true; 1454 | } 1455 | 1456 | //Prepare UI for iwm = 0 1457 | UISet(&UITextbox_vimqus, sf[isf]); 1458 | UIDisable(true, &UIProgressbar_eymzer); 1459 | UIDisable(true, &UITextbox_859t1hi); 1460 | UIDisable(true, &UITextbox_olwwlae); 1461 | UIDisable(true, &UIInputbox_6nssds); 1462 | UIDisable(true, &UITextbox_7mnuudb); 1463 | UIDisable(false, &UIInputbox_awnh87); 1464 | 1465 | #ifndef M5gps 1466 | UIDisable(true, &UITextbox_4t0l0bn); 1467 | UIDisable(true, &UITextbox_q7sl3uo); 1468 | #endif 1469 | 1470 | Serial.println("Started"); 1471 | 1472 | if (powersave == true) { 1473 | smartDelay(1000); 1474 | #ifdef M5gps 1475 | gpsdata(); 1476 | #endif 1477 | sendobject(&sendjob); 1478 | esp_sleep_enable_timer_wakeup(interval[iiv] * 1000); 1479 | esp_deep_sleep_start(); 1480 | } 1481 | sendobject(&sendjob); 1482 | } 1483 | 1484 | void loop() { 1485 | 1486 | //update button status 1487 | if (M5.BtnA.wasPressed()) { 1488 | if (iwm == 6) { 1489 | iwm = 0; 1490 | UISet(&UITextbox_eq79hh46, workmode[iwm]); 1491 | if (otaa == 1) { 1492 | initloraabp(); 1493 | } 1494 | } else if (iwm == 3) { 1495 | iwm++; 1496 | #ifndef M5gps 1497 | iwm++; 1498 | #endif 1499 | UISet(&UITextbox_eq79hh46, workmode[iwm]); 1500 | } else { 1501 | iwm++; 1502 | UISet(&UITextbox_eq79hh46, workmode[iwm]); 1503 | } 1504 | 1505 | if (iwm == 0) { 1506 | UISet(&UITextbox_vimqus, sf[isf]); 1507 | UIDisable(false, &UIInputbox_awnh87); 1508 | UISet(&UITextbox_67ofwdh, "Dim"); 1509 | } else if (iwm == 1) { 1510 | UIDisable(false, &UIProgressbar_eymzer); 1511 | UIDisable(false, &UITextbox_859t1hi); 1512 | UIDisable(false, &UITextbox_olwwlae); 1513 | UIDisable(false, &UITextbox_7mnuudb); 1514 | UISet(&UITextbox_859t1hi, "-130"); 1515 | UISet(&UITextbox_olwwlae, "-20.0"); 1516 | UISet(&UIProgressbar_eymzer, 0); 1517 | } else if (iwm == 2) { 1518 | UISet(&UITextbox_67ofwdh, "Send"); 1519 | UISet(&UITextbox_859t1hi, "-130"); 1520 | UISet(&UITextbox_olwwlae, "-20.0"); 1521 | UISet(&UIProgressbar_eymzer, 0); 1522 | } else if (iwm == 3) { 1523 | UIDisable(false, &UIInputbox_6nssds); 1524 | UISet(&UITextbox_859t1hi, "-130"); 1525 | UISet(&UITextbox_olwwlae, "-20.0"); 1526 | UISet(&UIProgressbar_eymzer, 0); 1527 | UISet(&UIInputbox_6nssds, ""); 1528 | } else if (iwm == 4) { 1529 | LMIC.nGws = 0; 1530 | LMIC.gwMargin = 0; 1531 | UIDisable(true, &UIProgressbar_eymzer); 1532 | UIDisable(true, &UITextbox_859t1hi); 1533 | UIDisable(true, &UITextbox_olwwlae); 1534 | UIDisable(true, &UIInputbox_6nssds); 1535 | UIDisable(true, &UITextbox_7mnuudb); 1536 | } else if (iwm == 5) { 1537 | #ifndef M5gps 1538 | UIDisable(true, &UIInputbox_6nssds); 1539 | UIDisable(true, &UITextbox_7mnuudb); 1540 | #endif 1541 | UISet(&UITextbox_vimqus, "Join"); 1542 | UISet(&UITextbox_67ofwdh, "NACK"); 1543 | UIDisable(false, &UIProgressbar_eymzer); 1544 | UIDisable(false, &UITextbox_859t1hi); 1545 | UIDisable(false, &UITextbox_olwwlae); 1546 | UISet(&UITextbox_859t1hi, "-130"); 1547 | UISet(&UITextbox_olwwlae, "-20.0"); 1548 | UISet(&UIProgressbar_eymzer, 0); 1549 | //strip.SetPixelColor(7, off); 1550 | } else if (iwm == 6) { 1551 | UISet(&UITextbox_vimqus, ttext[iiv]); 1552 | UIDisable(true, &UIInputbox_awnh87); 1553 | UIDisable(true, &UIProgressbar_eymzer); 1554 | UIDisable(true, &UITextbox_859t1hi); 1555 | UIDisable(true, &UITextbox_olwwlae); 1556 | UISet(&UITextbox_67ofwdh, "PS"); 1557 | } 1558 | } 1559 | 1560 | if (M5.BtnB.wasPressed()) { 1561 | if (isf == 5) { 1562 | isf = 0; 1563 | UISet(&UITextbox_vimqus, sf[isf]); 1564 | } else if (iwm == 5 && otaa == 0) { 1565 | initloraotaa(); //OTAA Join 1566 | UISet(&UITextbox_vimqus, "Send"); 1567 | } else if (iwm == 5 && otaa == 1) { 1568 | next = false; 1569 | sendobjectotaa(&sendjob); //Manual send 1570 | } else if (iwm == 6) { 1571 | if (iiv == 4) { 1572 | iiv = 0; 1573 | UISet(&UITextbox_vimqus, ttext[iiv]); 1574 | } else { 1575 | iiv++; 1576 | UISet(&UITextbox_vimqus, ttext[iiv]); 1577 | } 1578 | } else { 1579 | isf++; 1580 | UISet(&UITextbox_vimqus, sf[isf]); 1581 | } 1582 | } 1583 | 1584 | if (M5.BtnC.wasPressed()) { 1585 | if (iwm < 2 && dim == false) { 1586 | dim = true; 1587 | M5.Lcd.setBrightness(0); 1588 | #ifdef M5go 1589 | strip.SetBrightness(0); 1590 | #endif 1591 | } else if (iwm < 2 && dim == true) { 1592 | dim = false; 1593 | M5.Lcd.setBrightness(50); 1594 | #ifdef M5go 1595 | strip.SetBrightness(50); 1596 | #endif 1597 | } else if (iwm == 4) { 1598 | #ifdef M5gps 1599 | ssv(); 1600 | #endif 1601 | } else if (iwm == 5 && otaaack == 0) { 1602 | otaaack = 1; 1603 | UISet(&UITextbox_67ofwdh, "ACK"); 1604 | } else if (iwm == 5 && otaaack == 1) { 1605 | otaaack = 0; 1606 | UISet(&UITextbox_67ofwdh, "NACK"); 1607 | } else if (iwm == 6 && powersave == false) { 1608 | powersave = true; 1609 | iwm = 0; 1610 | } else if (iwm == 6 && powersave == true) { 1611 | powersave = false; 1612 | } else if (iwm > 1) { 1613 | sendobject(&sendjob); 1614 | } 1615 | } 1616 | 1617 | #ifdef M5gps 1618 | //Update GPS Data 1619 | gpsdata(); 1620 | #endif 1621 | 1622 | #ifdef M5gps 1623 | //Print satellites and change NeoPixel 4 1624 | sats = gps.satellites.value(); 1625 | UISet(&UITextbox_4t0l0bn, sats); 1626 | 1627 | //Print HDOP and change NeoPixel 0 1628 | hdop = gps.hdop.value(); 1629 | hdop2 = hdop / 100.0; 1630 | String stringhdop = String(hdop2); 1631 | UISet(&UITextbox_q7sl3uo, stringhdop); 1632 | 1633 | //Print GPS fix status und change NeoPixel 2 1634 | if (gps.location.isValid() == false) { 1635 | M5.Lcd.pushImage(160, 5, 24, 24, (uint16_t *)ICON_25_24); 1636 | } 1637 | else if (gps.location.isValid() && gps.location.age() > 2000) { 1638 | M5.Lcd.pushImage(160, 5, 24, 24, (uint16_t *)ICON_25_24); 1639 | } 1640 | else if (gps.location.isValid() == true) { 1641 | M5.Lcd.pushImage(160, 5, 24, 24, (uint16_t *)ICON_20_24); 1642 | } 1643 | else { 1644 | M5.Lcd.pushImage(160, 5, 24, 24, (uint16_t *)ICON_20_24); 1645 | } 1646 | #endif 1647 | 1648 | //Battery Status 1649 | if (M5.Power.isCharging() == true) { 1650 | M5.Lcd.pushImage(240, 5, 24, 24, (uint16_t *)ICON_40_24); 1651 | } 1652 | 1653 | if (M5.Power.isChargeFull() == true) { 1654 | UISet(&UITextbox_403ohip, "Full"); 1655 | } 1656 | else { 1657 | BattLevel = M5.Power.getBatteryLevel(); 1658 | String strbattlevel = String(BattLevel); 1659 | strbattlevel = String(strbattlevel + "%"); 1660 | UISet(&UITextbox_403ohip, strbattlevel); 1661 | } 1662 | 1663 | #ifdef M5go 1664 | strip.Show(); 1665 | #endif 1666 | 1667 | #ifdef M5gps 1668 | //Init of SD Card for GPX-file 1669 | if (sdwrite == false) { 1670 | gpxinit(); 1671 | } 1672 | 1673 | //Write GPS-Track 1674 | if (sdwrite == true) { 1675 | writegpx(); 1676 | } 1677 | #endif 1678 | 1679 | if (next == false) { 1680 | 1681 | } else { 1682 | if (otaa == 0) { 1683 | //Update Frame Counter for ABP mode 1684 | frcnt(); 1685 | } 1686 | //Sending intervall 1687 | currentMillis = millis(); 1688 | if ((currentMillis - sentMillis > interval[iiv]) && iwm < 2) { 1689 | sendobject(&sendjob); 1690 | } 1691 | 1692 | #ifdef M5gps 1693 | if ((currentMillis - sentMillis > interval[iiv]) && iwm == 5 && otaa == 1 && gps.location.isValid() == true && gps.location.age() < 2000) { 1694 | sendobjectotaa(&sendjob); 1695 | } 1696 | #else 1697 | if ((currentMillis - sentMillis > interval[iiv]) && iwm == 5 && otaa == 1) { 1698 | sendobjectotaa(&sendjob); 1699 | } 1700 | #endif 1701 | } 1702 | //sleep timer 1703 | if (powersave == true) { 1704 | #ifdef M5go 1705 | strip.SetBrightness(0); 1706 | #endif 1707 | esp_sleep_enable_timer_wakeup(interval[iiv] * 1000); 1708 | esp_deep_sleep_start(); 1709 | } 1710 | 1711 | //used to deflicker the display, more possible, but with less reactive buttons 1712 | smartDelay(200); 1713 | 1714 | M5.update(); 1715 | 1716 | } 1717 | --------------------------------------------------------------------------------