├── test ├── CLI.h ├── Buffer.h ├── CLI.cpp ├── Config.h ├── Dict.h ├── Modf_R.h ├── NVRAM.h ├── Packet.h ├── Params.h ├── Task.cpp ├── Task.h ├── Telnet.h ├── Vector.h ├── Buffer.cpp ├── Callsign.h ├── Console.h ├── Display.h ├── HMACKeys.h ├── LoRaL2 ├── LoRaL2-test ├── Modf_R.cpp ├── Modifier.h ├── NVRAM.cpp ├── Network.h ├── Packet.cpp ├── Params.cpp ├── Pointer.h ├── Proto_C.h ├── Telnet.cpp ├── Version.h ├── Callsign.cpp ├── Console.cpp ├── Display.cpp ├── HMACKeys.cpp ├── L4Protocol.h ├── L7Protocol.h ├── Modf_Rreq.cpp ├── Modf_Rreq.h ├── Modifier.cpp ├── Network.cpp ├── Proto_C.cpp ├── Proto_HMAC.h ├── Proto_Ping.h ├── Proto_Rreq.h ├── Timestamp.cpp ├── Timestamp.h ├── ArduinoBridge.h ├── L4Protocol.cpp ├── L7Protocol.cpp ├── Proto_Beacon.h ├── Proto_HMAC.cpp ├── Proto_Ping.cpp ├── Proto_Rreq.cpp ├── Proto_Switch.h ├── ArduinoBridge.cpp ├── Proto_Beacon.cpp ├── Proto_Switch.cpp ├── .gitignore ├── Serial.h ├── Preferences.h ├── test_hmac.py ├── interactive_test ├── Preferences.cpp ├── lora_ping.py ├── lora_loop.py ├── FakeArduino.cpp ├── Makefile ├── testnet2.cpp ├── Serial.cpp ├── lora_conn.py ├── lora_switch.py ├── lora_packet.py ├── switch_test.py └── run_test ├── src ├── LoRaL2 ├── Version.h ├── Config.h ├── Timestamp.h ├── Display.h ├── Telnet.h ├── ArduinoBridge.h ├── CLI.h ├── HMACKeys.h ├── Timestamp.cpp ├── ArduinoBridge.cpp ├── Modf_R.cpp ├── Modf_R.h ├── Modf_Rreq.h ├── Proto_Ping.h ├── Proto_Rreq.h ├── Proto_C.h ├── Proto_Beacon.h ├── Callsign.h ├── Proto_Ping.cpp ├── Proto_Rreq.cpp ├── Modf_Rreq.cpp ├── Modifier.h ├── Proto_HMAC.h ├── Params.h ├── NVRAM.h ├── Console.h ├── Proto_Switch.h ├── Proto_C.cpp ├── L7Protocol.h ├── Modifier.cpp ├── Display.cpp ├── Proto_Beacon.cpp ├── L4Protocol.h ├── Packet.h ├── HMACKeys.cpp ├── Task.h ├── Buffer.h ├── Pointer.h ├── L4Protocol.cpp ├── Proto_HMAC.cpp ├── Console.cpp ├── L7Protocol.cpp ├── Network.h ├── Callsign.cpp ├── Vector.h ├── Task.cpp ├── NVRAM.cpp ├── Dict.h ├── Packet.cpp └── Telnet.cpp ├── TODO ├── tooling ├── esp32hw.txt ├── lora32u4.zip ├── loralibs.zip ├── EspExceptionDecoder-1.1.0.zip └── refs.txt ├── _pocs_ ├── lora32u4_receiver │ ├── .DS_Store │ └── lora32u4_receiver.ino ├── simulator │ ├── __pycache__ │ │ ├── sim_packet.cpython-37.pyc │ │ ├── sim_radio.cpython-37.pyc │ │ ├── sim_handler.cpython-37.pyc │ │ ├── sim_modifier.cpython-37.pyc │ │ ├── sim_network.cpython-37.pyc │ │ └── sim_trafficgen.cpython-37.pyc │ ├── sim_modifier.py │ ├── ring.py │ ├── dring.py │ ├── sim_handler.py │ ├── diamond.py │ ├── sim_trafficgen.py │ ├── sim_radio.py │ └── adv │ │ ├── sim_packet.py │ │ ├── sim_mapper.py │ │ └── sim_router.py ├── lora32u4_sender │ └── lora32u4_sender.ino ├── ttgo │ ├── ttgo.ino │ └── images.h ├── ttgo_sender │ ├── ttgo_sender.ino │ └── images.h └── ttgo_receiver │ ├── ttgo_receiver.ino │ └── images.h ├── NonAmateur.md ├── Roadmap.md ├── LoRaMaDoR.ino ├── python ├── lorasun.py ├── readme.md ├── aprsisfeed.py ├── loraham-console.py └── chat.py ├── TNC.md ├── PacketFormat.md └── README.md /test/CLI.h: -------------------------------------------------------------------------------- 1 | ../src/CLI.h -------------------------------------------------------------------------------- /test/Buffer.h: -------------------------------------------------------------------------------- 1 | ../src/Buffer.h -------------------------------------------------------------------------------- /test/CLI.cpp: -------------------------------------------------------------------------------- 1 | ../src/CLI.cpp -------------------------------------------------------------------------------- /test/Config.h: -------------------------------------------------------------------------------- 1 | ../src/Config.h -------------------------------------------------------------------------------- /test/Dict.h: -------------------------------------------------------------------------------- 1 | ../src/Dict.h -------------------------------------------------------------------------------- /test/Modf_R.h: -------------------------------------------------------------------------------- 1 | ../src/Modf_R.h -------------------------------------------------------------------------------- /test/NVRAM.h: -------------------------------------------------------------------------------- 1 | ../src/NVRAM.h -------------------------------------------------------------------------------- /test/Packet.h: -------------------------------------------------------------------------------- 1 | ../src/Packet.h -------------------------------------------------------------------------------- /test/Params.h: -------------------------------------------------------------------------------- 1 | ../src/Params.h -------------------------------------------------------------------------------- /test/Task.cpp: -------------------------------------------------------------------------------- 1 | ../src/Task.cpp -------------------------------------------------------------------------------- /test/Task.h: -------------------------------------------------------------------------------- 1 | ../src/Task.h -------------------------------------------------------------------------------- /test/Telnet.h: -------------------------------------------------------------------------------- 1 | ../src/Telnet.h -------------------------------------------------------------------------------- /test/Vector.h: -------------------------------------------------------------------------------- 1 | ../src/Vector.h -------------------------------------------------------------------------------- /src/LoRaL2: -------------------------------------------------------------------------------- 1 | ../../LoRaL2/LoRaL2/ -------------------------------------------------------------------------------- /test/Buffer.cpp: -------------------------------------------------------------------------------- 1 | ../src/Buffer.cpp -------------------------------------------------------------------------------- /test/Callsign.h: -------------------------------------------------------------------------------- 1 | ../src/Callsign.h -------------------------------------------------------------------------------- /test/Console.h: -------------------------------------------------------------------------------- 1 | ../src/Console.h -------------------------------------------------------------------------------- /test/Display.h: -------------------------------------------------------------------------------- 1 | ../src/Display.h -------------------------------------------------------------------------------- /test/HMACKeys.h: -------------------------------------------------------------------------------- 1 | ../src/HMACKeys.h -------------------------------------------------------------------------------- /test/LoRaL2: -------------------------------------------------------------------------------- 1 | ../../LoRaL2/LoRaL2 -------------------------------------------------------------------------------- /test/LoRaL2-test: -------------------------------------------------------------------------------- 1 | ../../LoRaL2/test -------------------------------------------------------------------------------- /test/Modf_R.cpp: -------------------------------------------------------------------------------- 1 | ../src/Modf_R.cpp -------------------------------------------------------------------------------- /test/Modifier.h: -------------------------------------------------------------------------------- 1 | ../src/Modifier.h -------------------------------------------------------------------------------- /test/NVRAM.cpp: -------------------------------------------------------------------------------- 1 | ../src/NVRAM.cpp -------------------------------------------------------------------------------- /test/Network.h: -------------------------------------------------------------------------------- 1 | ../src/Network.h -------------------------------------------------------------------------------- /test/Packet.cpp: -------------------------------------------------------------------------------- 1 | ../src/Packet.cpp -------------------------------------------------------------------------------- /test/Params.cpp: -------------------------------------------------------------------------------- 1 | ../src/Params.cpp -------------------------------------------------------------------------------- /test/Pointer.h: -------------------------------------------------------------------------------- 1 | ../src/Pointer.h -------------------------------------------------------------------------------- /test/Proto_C.h: -------------------------------------------------------------------------------- 1 | ../src/Proto_C.h -------------------------------------------------------------------------------- /test/Telnet.cpp: -------------------------------------------------------------------------------- 1 | ../src/Telnet.cpp -------------------------------------------------------------------------------- /test/Version.h: -------------------------------------------------------------------------------- 1 | ../src/Version.h -------------------------------------------------------------------------------- /test/Callsign.cpp: -------------------------------------------------------------------------------- 1 | ../src/Callsign.cpp -------------------------------------------------------------------------------- /test/Console.cpp: -------------------------------------------------------------------------------- 1 | ../src/Console.cpp -------------------------------------------------------------------------------- /test/Display.cpp: -------------------------------------------------------------------------------- 1 | ../src/Display.cpp -------------------------------------------------------------------------------- /test/HMACKeys.cpp: -------------------------------------------------------------------------------- 1 | ../src/HMACKeys.cpp -------------------------------------------------------------------------------- /test/L4Protocol.h: -------------------------------------------------------------------------------- 1 | ../src/L4Protocol.h -------------------------------------------------------------------------------- /test/L7Protocol.h: -------------------------------------------------------------------------------- 1 | ../src/L7Protocol.h -------------------------------------------------------------------------------- /test/Modf_Rreq.cpp: -------------------------------------------------------------------------------- 1 | ../src/Modf_Rreq.cpp -------------------------------------------------------------------------------- /test/Modf_Rreq.h: -------------------------------------------------------------------------------- 1 | ../src/Modf_Rreq.h -------------------------------------------------------------------------------- /test/Modifier.cpp: -------------------------------------------------------------------------------- 1 | ../src/Modifier.cpp -------------------------------------------------------------------------------- /test/Network.cpp: -------------------------------------------------------------------------------- 1 | ../src/Network.cpp -------------------------------------------------------------------------------- /test/Proto_C.cpp: -------------------------------------------------------------------------------- 1 | ../src/Proto_C.cpp -------------------------------------------------------------------------------- /test/Proto_HMAC.h: -------------------------------------------------------------------------------- 1 | ../src/Proto_HMAC.h -------------------------------------------------------------------------------- /test/Proto_Ping.h: -------------------------------------------------------------------------------- 1 | ../src/Proto_Ping.h -------------------------------------------------------------------------------- /test/Proto_Rreq.h: -------------------------------------------------------------------------------- 1 | ../src/Proto_Rreq.h -------------------------------------------------------------------------------- /test/Timestamp.cpp: -------------------------------------------------------------------------------- 1 | ../src/Timestamp.cpp -------------------------------------------------------------------------------- /test/Timestamp.h: -------------------------------------------------------------------------------- 1 | ../src/Timestamp.h -------------------------------------------------------------------------------- /test/ArduinoBridge.h: -------------------------------------------------------------------------------- 1 | ../src/ArduinoBridge.h -------------------------------------------------------------------------------- /test/L4Protocol.cpp: -------------------------------------------------------------------------------- 1 | ../src/L4Protocol.cpp -------------------------------------------------------------------------------- /test/L7Protocol.cpp: -------------------------------------------------------------------------------- 1 | ../src/L7Protocol.cpp -------------------------------------------------------------------------------- /test/Proto_Beacon.h: -------------------------------------------------------------------------------- 1 | ../src/Proto_Beacon.h -------------------------------------------------------------------------------- /test/Proto_HMAC.cpp: -------------------------------------------------------------------------------- 1 | ../src/Proto_HMAC.cpp -------------------------------------------------------------------------------- /test/Proto_Ping.cpp: -------------------------------------------------------------------------------- 1 | ../src/Proto_Ping.cpp -------------------------------------------------------------------------------- /test/Proto_Rreq.cpp: -------------------------------------------------------------------------------- 1 | ../src/Proto_Rreq.cpp -------------------------------------------------------------------------------- /test/Proto_Switch.h: -------------------------------------------------------------------------------- 1 | ../src/Proto_Switch.h -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | LoRa simulation with socket x LoRaL2 2 | -------------------------------------------------------------------------------- /test/ArduinoBridge.cpp: -------------------------------------------------------------------------------- 1 | ../src/ArduinoBridge.cpp -------------------------------------------------------------------------------- /test/Proto_Beacon.cpp: -------------------------------------------------------------------------------- 1 | ../src/Proto_Beacon.cpp -------------------------------------------------------------------------------- /test/Proto_Switch.cpp: -------------------------------------------------------------------------------- 1 | ../src/Proto_Switch.cpp -------------------------------------------------------------------------------- /src/Version.h: -------------------------------------------------------------------------------- 1 | #define LORAMADOR_VERSION "2021-03-12 00:07:14" 2 | -------------------------------------------------------------------------------- /tooling/esp32hw.txt: -------------------------------------------------------------------------------- 1 | https://dl.espressif.com/dl/package_esp32_index.json 2 | -------------------------------------------------------------------------------- /tooling/lora32u4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/tooling/lora32u4.zip -------------------------------------------------------------------------------- /tooling/loralibs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/tooling/loralibs.zip -------------------------------------------------------------------------------- /_pocs_/lora32u4_receiver/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/_pocs_/lora32u4_receiver/.DS_Store -------------------------------------------------------------------------------- /tooling/EspExceptionDecoder-1.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/tooling/EspExceptionDecoder-1.1.0.zip -------------------------------------------------------------------------------- /_pocs_/simulator/__pycache__/sim_packet.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/_pocs_/simulator/__pycache__/sim_packet.cpython-37.pyc -------------------------------------------------------------------------------- /_pocs_/simulator/__pycache__/sim_radio.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/_pocs_/simulator/__pycache__/sim_radio.cpython-37.pyc -------------------------------------------------------------------------------- /_pocs_/simulator/__pycache__/sim_handler.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/_pocs_/simulator/__pycache__/sim_handler.cpython-37.pyc -------------------------------------------------------------------------------- /_pocs_/simulator/__pycache__/sim_modifier.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/_pocs_/simulator/__pycache__/sim_modifier.cpython-37.pyc -------------------------------------------------------------------------------- /_pocs_/simulator/__pycache__/sim_network.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/_pocs_/simulator/__pycache__/sim_network.cpython-37.pyc -------------------------------------------------------------------------------- /_pocs_/simulator/__pycache__/sim_trafficgen.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elvis-epx/LoRaMaDoR/HEAD/_pocs_/simulator/__pycache__/sim_trafficgen.cpython-37.pyc -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | test 3 | test.DSYM 4 | testnet 5 | testnet2 6 | testnet.DSYM 7 | testnet2.DSYM 8 | out/ 9 | *.gc* 10 | *.log 11 | *.info 12 | *.val 13 | __pycache__/ 14 | -------------------------------------------------------------------------------- /src/Config.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H 2 | #define __CONFIG_H 3 | 4 | #define SWITCH_PROTO_SUPPORT 1 5 | 6 | /* LoRa parameters */ 7 | #define BAND 916750000 8 | #define SPREAD 7 9 | #define BWIDTH 125000 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/Timestamp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | #ifndef __TIMESTAMP_H 7 | #define __TIMESTAMP_H 8 | 9 | #include 10 | 11 | int64_t sys_timestamp(); 12 | #define SECONDS 1000 13 | #define MINUTES 60000 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/Display.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Functions related to OLED display 7 | 8 | #ifndef __DISPLAY_H 9 | #define __DISPLAY_H 10 | 11 | void oled_init(); 12 | void oled_show(const char *ma, const char *mb, const char *mc, const char *md); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /tooling/refs.txt: -------------------------------------------------------------------------------- 1 | https://www.youtube.com/watch?v=WHl2oC8fqZU 2 | 3 | https://github.com/osresearch/esp32-ttgo 4 | 5 | https://github.com/fcgdam/TTGO_LoRa32 6 | 7 | https://github.com/BSFrance/BSFrance-avr 8 | 9 | http://iotbyskovholm.dk/?q=content/lora32u4-ii-v12-development-board 10 | 11 | https://primalcortex.files.wordpress.com/2017/11/lora32u4ii-pinout-diagram.pdf 12 | 13 | -------------------------------------------------------------------------------- /src/Telnet.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Functions related to Wi-Fi and Telnet server support 7 | 8 | #ifndef __TELNET_H 9 | #define __TELNET_H 10 | 11 | class Network; 12 | 13 | void wifi_setup(Ptr); 14 | void wifi_handle(); 15 | void telnet_print(const char *); 16 | Buffer get_wifi_status(); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/ArduinoBridge.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Platform-dependent functions. They are faked on Linux to run tests. 7 | 8 | #ifndef __ARDUINO_BRIDGE 9 | #define __ARDUINO_BRIDGE 10 | 11 | #include 12 | 13 | uint32_t _arduino_millis(); 14 | int32_t arduino_random2(int32_t min, int32_t max); 15 | void arduino_restart(); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/CLI.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Command-line interface implementation. 7 | 8 | #ifndef __CLI_H 9 | #define __CLI_H 10 | 11 | void logs(const char*, const char*); 12 | void logs(const char*, const Buffer&); 13 | void logi(const char*, int32_t); 14 | void app_recv(Ptr); 15 | void cli_type(const char); 16 | void cli_simtype(const char *); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/HMACKeys.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | #ifndef __HMACKEYS_H 7 | #define __HMACKEYS_H 8 | 9 | #include "Buffer.h" 10 | #include "Callsign.h" 11 | 12 | class HMACKeys { 13 | public: 14 | static Buffer get_key_for(const Callsign &c); 15 | static Buffer hmac(const Buffer& key, const Buffer& data); 16 | static void invalidate(); 17 | static Buffer hash_key(const Buffer& key); 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /test/Serial.h: -------------------------------------------------------------------------------- 1 | #ifndef __SERIAL_H 2 | #define __SERIAL_H 3 | 4 | #include 5 | 6 | struct SerialClass { 7 | static int available(); 8 | static int availableForWrite(); 9 | static char read(); 10 | static void write(const uint8_t* s, int len); 11 | static void emu_listen_handle(); 12 | static void emu_conn_handle(); 13 | static int emu_conn_socket(); 14 | static int emu_listen_socket(); 15 | static void emu_port(int); 16 | }; 17 | 18 | extern SerialClass Serial; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /test/Preferences.h: -------------------------------------------------------------------------------- 1 | #ifndef __PREFERENCES_H 2 | #define __PREFERENCES_H 3 | 4 | #include 5 | 6 | struct Preferences { 7 | static void begin(const char*); 8 | static void begin(const char*, bool); 9 | static void end(); 10 | static uint32_t getUInt(const char*); 11 | static void putUInt(const char*, uint32_t); 12 | static size_t getString(const char*, char*, size_t); 13 | static void putString(const char*, const char*); 14 | static void clear(); 15 | }; 16 | 17 | #endif // __PREFERENCES_H 18 | -------------------------------------------------------------------------------- /src/Timestamp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2020 PU5EPX 4 | */ 5 | 6 | #include "ArduinoBridge.h" 7 | #include "Timestamp.h" 8 | 9 | static uint32_t epoch = 0; 10 | static uint32_t last_millis = 0; 11 | 12 | int64_t sys_timestamp() 13 | { 14 | uint32_t m = _arduino_millis(); 15 | if ((m < last_millis) && (last_millis - m) > 0x10000000) { 16 | // wrapped around 17 | epoch += 1; 18 | } 19 | last_millis = m; 20 | return (((int64_t) epoch) << 32) + m; 21 | } 22 | -------------------------------------------------------------------------------- /src/ArduinoBridge.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | #include 7 | #include 8 | #include "Buffer.h" 9 | #include "Callsign.h" 10 | 11 | // Platform-dependent functions. They are faked on Linux to run tests. 12 | 13 | uint32_t _arduino_millis() 14 | { 15 | return millis(); 16 | } 17 | 18 | int32_t arduino_random2(int32_t min, int32_t max) 19 | { 20 | return random(min, max); 21 | } 22 | 23 | void arduino_restart() { 24 | ESP.restart(); 25 | } 26 | -------------------------------------------------------------------------------- /src/Modf_R.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of R earmarking of forwarded packets 7 | 8 | #include "Modf_R.h" 9 | #include "Network.h" 10 | #include "Packet.h" 11 | 12 | Modf_R::Modf_R(Network *net): Modifier(net) 13 | { 14 | } 15 | 16 | Ptr Modf_R::modify(const Packet& pkt) 17 | { 18 | // earmarks all forwarded packets 19 | Params new_params = pkt.params(); 20 | new_params.put_naked("R"); 21 | return pkt.change_params(new_params); 22 | } 23 | -------------------------------------------------------------------------------- /test/test_hmac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import hmac 4 | import hashlib 5 | 6 | text_key = 'abracadabra'.encode('ascii') 7 | print("Text key:", text_key) 8 | key = hashlib.sha256(bytearray([1]) + text_key).hexdigest()[0:32].encode('ascii') 9 | print("Hashed armored key:", key) 10 | 11 | to = 'BBBB' 12 | fro = 'AAAA' 13 | ident = '23' 14 | msg = 'Ola' 15 | data = to + fro + ident + msg 16 | data = data.encode('utf-8') 17 | 18 | signature = hmac.new(key, msg=data, digestmod=hashlib.sha256).hexdigest() 19 | print(data) 20 | print(signature) 21 | -------------------------------------------------------------------------------- /src/Modf_R.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of R earmarking of forwarded packets 7 | 8 | #ifndef __MODF_R_H 9 | #define __MODF_R_H 10 | 11 | #include "Modifier.h" 12 | 13 | class Modf_R: public Modifier { 14 | public: 15 | Modf_R(Network* net); 16 | virtual Ptr modify(const Packet&); 17 | 18 | Modf_R() = delete; 19 | Modf_R(const Modf_R&) = delete; 20 | Modf_R(Modf_R&&) = delete; 21 | Modf_R& operator=(const Modf_R&) = delete; 22 | Modf_R& operator=(Modf_R&&) = delete; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/Modf_Rreq.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of RREQ protocol responder 7 | 8 | #ifndef __MODF_RREQ_H 9 | #define __MODF_RREQ_H 10 | 11 | #include "Modifier.h" 12 | 13 | class Modf_Rreq: public Modifier { 14 | public: 15 | Modf_Rreq(Network* net); 16 | virtual Ptr modify(const Packet&); 17 | 18 | Modf_Rreq() = delete; 19 | Modf_Rreq(const Modf_Rreq&) = delete; 20 | Modf_Rreq(Modf_Rreq&&) = delete; 21 | Modf_Rreq& operator=(const Modf_Rreq&) = delete; 22 | Modf_Rreq& operator=(Modf_Rreq&&) = delete; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/Proto_Ping.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of PING protocol responder 7 | 8 | #ifndef __PROTO_PING_H 9 | #define __PROTO_PING_H 10 | 11 | #include "L7Protocol.h" 12 | 13 | class Proto_Ping: public L7Protocol { 14 | public: 15 | Proto_Ping(Network* net); 16 | virtual L7HandlerResponse handle(const Packet&); 17 | 18 | Proto_Ping() = delete; 19 | Proto_Ping(const Proto_Ping&) = delete; 20 | Proto_Ping(Proto_Ping&&) = delete; 21 | Proto_Ping& operator=(const Proto_Ping&) = delete; 22 | Proto_Ping& operator=(Proto_Ping&&) = delete; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/Proto_Rreq.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of RREQ protocol responder 7 | 8 | #ifndef __PROTO_RREQ_H 9 | #define __PROTO_RREQ_H 10 | 11 | #include "L7Protocol.h" 12 | 13 | class Proto_Rreq: public L7Protocol { 14 | public: 15 | Proto_Rreq(Network* net); 16 | virtual L7HandlerResponse handle(const Packet&); 17 | 18 | Proto_Rreq() = delete; 19 | Proto_Rreq(const Proto_Rreq&) = delete; 20 | Proto_Rreq(Proto_Rreq&&) = delete; 21 | Proto_Rreq& operator=(const Proto_Rreq&) = delete; 22 | Proto_Rreq& operator=(Proto_Rreq&&) = delete; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/Proto_C.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of packet confirmation (C,CO options) 7 | 8 | #ifndef __PROTO_C_H 9 | #define __PROTO_C_H 10 | 11 | #include "L4Protocol.h" 12 | 13 | class Proto_C: public L4Protocol { 14 | public: 15 | Proto_C(Network* net); 16 | virtual L4txHandlerResponse tx(const Packet&); 17 | virtual L4rxHandlerResponse rx(const Packet&); 18 | 19 | Proto_C() = delete; 20 | Proto_C(const Proto_C&) = delete; 21 | Proto_C(Proto_C&&) = delete; 22 | Proto_C& operator=(const Proto_C&) = delete; 23 | Proto_C& operator=(Proto_C&&) = delete; 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/Proto_Beacon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of periodic beacon (sent to QB/QR). 7 | 8 | #ifndef __PROTO_BEACON_H 9 | #define __PROTO_BEACON_H 10 | 11 | #include "L7Protocol.h" 12 | 13 | class BeaconTask; 14 | 15 | class Proto_Beacon: public L7Protocol { 16 | public: 17 | Proto_Beacon(Network* net); 18 | private: 19 | int64_t beacon() const; 20 | friend class BeaconTask; 21 | 22 | Proto_Beacon() = delete; 23 | Proto_Beacon(const Proto_Beacon&) = delete; 24 | Proto_Beacon(Proto_Beacon&&) = delete; 25 | Proto_Beacon& operator=(const Proto_Beacon&) = delete; 26 | Proto_Beacon& operator=(Proto_Beacon&&) = delete; 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/Callsign.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Class that encapsulates a callsign 7 | 8 | #ifndef __CALLSIGN_H 9 | #define __CALLSIGN_H 10 | 11 | #include "Buffer.h" 12 | #include 13 | 14 | class Callsign 15 | { 16 | public: 17 | Callsign(); 18 | Callsign(Buffer); 19 | operator Buffer() const; 20 | bool is_valid() const; 21 | bool is_bcast() const; 22 | bool is_repeater() const; 23 | bool is_q() const; 24 | bool is_lo() const; 25 | bool is_reserved() const; 26 | bool operator==(Buffer) const; 27 | bool operator==(const Callsign&) const; 28 | private: 29 | static bool check(const Buffer&); 30 | Buffer name; 31 | bool valid; 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/Proto_Ping.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of PING protocol 7 | 8 | #include "Proto_Ping.h" 9 | #include "Network.h" 10 | #include "Packet.h" 11 | 12 | Proto_Ping::Proto_Ping(Network *net): L7Protocol(net) 13 | { 14 | } 15 | 16 | L7HandlerResponse Proto_Ping::handle(const Packet& pkt) 17 | { 18 | // We don't allow broadcast PING to Q* because many stations would 19 | // transmit at the same time, corrupting each other's messages. 20 | if (!pkt.to().is_bcast() && pkt.params().has("PING")) { 21 | Params pong = Params(); 22 | pong.put_naked("PONG"); 23 | return L7HandlerResponse(true, pkt.from(), pong, pkt.msg()); 24 | } 25 | 26 | return L7HandlerResponse(); 27 | } 28 | -------------------------------------------------------------------------------- /src/Proto_Rreq.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of RREQ protocol 7 | 8 | #include "Proto_Rreq.h" 9 | #include "Network.h" 10 | #include "Packet.h" 11 | 12 | Proto_Rreq::Proto_Rreq(Network *net): L7Protocol(net) 13 | { 14 | } 15 | 16 | L7HandlerResponse Proto_Rreq::handle(const Packet& pkt) 17 | { 18 | // Respond to RREQ packet 19 | if (!pkt.to().is_bcast() && pkt.params().has("RREQ")) { 20 | Buffer msg = pkt.msg(); 21 | msg += (msg.empty() ? "*" : " *"); 22 | msg += net->me(); 23 | msg += ' '; 24 | msg += Buffer::itoa(pkt.rssi()); 25 | 26 | Params rrsp = Params(); 27 | rrsp.put_naked("RRSP"); 28 | 29 | return L7HandlerResponse(true, pkt.from(), rrsp, msg); 30 | } 31 | return L7HandlerResponse(); 32 | } 33 | -------------------------------------------------------------------------------- /_pocs_/simulator/sim_modifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | # Modifiers of packets that are going to be forwarded 8 | 9 | class Rreqi: 10 | @staticmethod 11 | def match(pkt): 12 | return ("RREQ" in pkt.params or "RRSP" in pkt.params) and len(pkt.to) > 2 13 | 14 | @staticmethod 15 | def handle(station, pkt): 16 | msg = pkt.msg + "\n" + station.callsign 17 | return None, msg 18 | 19 | class RetransBeacon: 20 | @staticmethod 21 | def match(pkt): 22 | return len(pkt.to) == 2 and pkt.to in ("QB", "QC") and "R" not in pkt.params 23 | 24 | @staticmethod 25 | def handle(station, pkt): 26 | return {"R": None}, None 27 | 28 | fwd_modifiers = [ Rreqi, RetransBeacon ] 29 | -------------------------------------------------------------------------------- /src/Modf_Rreq.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of RREQ protocol 7 | 8 | #include "Modf_Rreq.h" 9 | #include "Network.h" 10 | #include "Packet.h" 11 | 12 | Modf_Rreq::Modf_Rreq(Network *net): Modifier(net) 13 | { 14 | } 15 | 16 | Ptr Modf_Rreq::modify(const Packet& pkt) 17 | { 18 | // Add ourselves to chain in forwarded RREQ and RRSP pkts 19 | if (! pkt.to().is_q()) { 20 | // not QB, QR, QC, etc. 21 | if (pkt.params().has("RREQ") || pkt.params().has("RRSP")) { 22 | Buffer new_msg = pkt.msg(); 23 | if (! new_msg.empty()) new_msg += ' '; 24 | new_msg += net->me(); 25 | new_msg += ' '; 26 | new_msg += Buffer::itoa(pkt.rssi()); 27 | return pkt.change_msg(new_msg); 28 | } 29 | } 30 | return Ptr(0); 31 | } 32 | -------------------------------------------------------------------------------- /src/Modifier.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Abstract class for application protocols that modify a forwarded packet 7 | 8 | #ifndef __MODIFIER_H 9 | #define __MODIFIER_H 10 | 11 | #include 12 | #include 13 | #include "Pointer.h" 14 | 15 | class Network; 16 | class Packet; 17 | 18 | class Modifier { 19 | public: 20 | Modifier(Network*); 21 | virtual Ptr modify(const Packet&) = 0; 22 | virtual ~Modifier(); 23 | protected: 24 | Network *net; 25 | // This class must be new()ed and not fooled around 26 | Modifier() = delete; 27 | Modifier(const Modifier&) = delete; 28 | Modifier(Modifier&&) = delete; 29 | Modifier& operator=(const Modifier&) = delete; 30 | Modifier& operator=(Modifier&&) = delete; 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /_pocs_/simulator/ring.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | import random, math, asyncio 8 | from sim_radio import Radio 9 | from sim_network import Station, run 10 | from sim_trafficgen import * 11 | 12 | STATION_COUNT=5 13 | 14 | stations = {} 15 | r = Radio() 16 | 17 | # create stations 18 | for i in range(0, STATION_COUNT): 19 | callsign = chr(ord('A') + i) * 4 20 | stations[callsign] = Station(callsign, r) 21 | 22 | r.edge("BBBB", "AAAA", -40) 23 | r.edge("CCCC", "BBBB", -70) 24 | r.edge("DDDD", "CCCC", -65) 25 | r.edge("EEEE", "DDDD", -65) 26 | r.edge("AAAA", "EEEE", -65) 27 | 28 | for callsign, station in stations.items(): 29 | for c in traffic_gens: 30 | station.add_traffic_gen(c) 31 | pass 32 | 33 | run() 34 | -------------------------------------------------------------------------------- /_pocs_/simulator/dring.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | import random, math, asyncio 8 | from sim_radio import Radio 9 | from sim_network import Station, run 10 | from sim_trafficgen import * 11 | 12 | STATION_COUNT = 5 13 | 14 | stations = {} 15 | r = Radio() 16 | 17 | # create stations 18 | for i in range(0, STATION_COUNT): 19 | callsign = chr(ord('A') + i) * 4 20 | stations[callsign] = Station(callsign, r) 21 | 22 | r.biedge("BBBB", "AAAA", -40, -45) 23 | r.biedge("CCCC", "BBBB", -70, -60) 24 | r.biedge("DDDD", "CCCC", -70, -60) 25 | r.biedge("EEEE", "DDDD", -70, -60) 26 | r.biedge("AAAA", "EEEE", -70, -60) 27 | 28 | for callsign, station in stations.items(): 29 | station.add_traffic_gen(RagChewer) 30 | pass 31 | 32 | run() 33 | -------------------------------------------------------------------------------- /_pocs_/simulator/sim_handler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | # Automatic handlers for certain application protocols 8 | 9 | import random, asyncio, sys, string 10 | 11 | loop = asyncio.get_event_loop() 12 | 13 | class Ping: 14 | @staticmethod 15 | def match(pkt): 16 | return "PING" in pkt.params and len(pkt.to) > 2 17 | 18 | def __init__(self, station, pkt): 19 | who = pkt.fr0m 20 | msg = pkt.msg 21 | station.send(who, {"PONG": None}, msg) 22 | 23 | class Rreq: 24 | @staticmethod 25 | def match(pkt): 26 | return "RREQ" in pkt.params and len(pkt.to) > 2 27 | 28 | def __init__(self, station, pkt): 29 | who = pkt.fr0m 30 | msg = pkt.msg + "\n" 31 | station.send(who, {"RRSP": None}, msg) 32 | 33 | app_handlers = [ Ping, Rreq ] 34 | -------------------------------------------------------------------------------- /src/Proto_HMAC.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of HMAC signature (H=) 7 | 8 | #ifndef __PROTO_HMAC_H 9 | #define __PROTO_HMAC_H 10 | 11 | #include "L4Protocol.h" 12 | #include "Buffer.h" 13 | 14 | class Proto_HMAC: public L4Protocol { 15 | public: 16 | Proto_HMAC(Network* net); 17 | virtual L4txHandlerResponse tx(const Packet&); 18 | virtual L4rxHandlerResponse rx(const Packet&); 19 | 20 | Proto_HMAC() = delete; 21 | Proto_HMAC(const Proto_HMAC&) = delete; 22 | Proto_HMAC(Proto_HMAC&&) = delete; 23 | Proto_HMAC& operator=(const Proto_HMAC&) = delete; 24 | Proto_HMAC& operator=(Proto_HMAC&&) = delete; 25 | }; 26 | 27 | L4txHandlerResponse Proto_HMAC_tx(const Buffer&, const Packet&); 28 | L4rxHandlerResponse Proto_HMAC_rx(const Buffer&, const Packet&); 29 | Buffer Proto_HMAC_hmac(const Buffer& key, const Buffer& data); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/Params.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Class that encapsulates the parameters of a LoRaMaDoR packet. 7 | 8 | #ifndef __PARAMS_H 9 | #define __PARAMS_H 10 | 11 | #include 12 | #include "Buffer.h" 13 | #include "Dict.h" 14 | 15 | class Params 16 | { 17 | public: 18 | Params(); 19 | Params(Buffer); 20 | Buffer serialized() const; 21 | bool is_valid_with_ident() const; 22 | bool is_valid_without_ident() const; 23 | uint32_t ident() const; 24 | Buffer s_ident() const; 25 | size_t count() const; 26 | Buffer get(const char *) const; 27 | bool has(const char *) const; 28 | void put(const char *, const Buffer&); 29 | void put_naked(const char *); 30 | void remove(const char *); 31 | bool is_key_naked(const char *) const; 32 | void set_ident(uint32_t); 33 | Vector keys() const; 34 | private: 35 | Dict items; 36 | uint32_t _ident; 37 | bool valid; 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/NVRAM.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Platform-dependent functions. They are faked on Linux to run tests. 7 | 8 | #ifndef __NVRAM 9 | #define __NVRAM 10 | 11 | #include 12 | #include "Pointer.h" 13 | #include "Packet.h" 14 | #include "Callsign.h" 15 | 16 | void arduino_nvram_clear_all(); 17 | 18 | uint32_t arduino_nvram_repeater_load(); 19 | void arduino_nvram_repeater_save(uint32_t); 20 | 21 | uint32_t arduino_nvram_beacon_load(); 22 | void arduino_nvram_beacon_save(uint32_t); 23 | 24 | uint32_t arduino_nvram_id_load(); 25 | void arduino_nvram_id_save(uint32_t); 26 | 27 | Callsign arduino_nvram_callsign_load(); 28 | void arduino_nvram_callsign_save(const Callsign&); 29 | 30 | Buffer arduino_nvram_hmac_psk_load(); 31 | void arduino_nvram_hmac_psk_save(const Buffer &b); 32 | 33 | Buffer arduino_nvram_load(const char *); 34 | void arduino_nvram_save(const char *, const Buffer&); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /test/interactive_test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VALGRIND=1 4 | if [ "$1" = "-v" ]; then 5 | VALGRIND=0 6 | fi 7 | 8 | PIDS="" 9 | for eee in AAAA:0:1:somekey:6000:None BBBB:0:1:somekey:6001:None; do 10 | sta=$(echo $eee | cut -f 1 -d ':') 11 | rep=$(echo $eee | cut -f 2 -d ':') 12 | cov=$(echo $eee | cut -f 3 -d ':') 13 | psk=$(echo $eee | cut -f 4 -d ':') 14 | port=$(echo $eee | cut -f 5 -d ':') 15 | cpsk=$(echo $eee | cut -f 6 -d ':') 16 | if [ $VALGRIND = 1 ]; then 17 | valgrind --leak-check=full --error-exitcode=1 --log-file=${sta}.val --gen-suppressions=all \ 18 | --suppressions=valgrind.supp ./testnet2 ${sta} ${rep} ${cov} ${psk} ${port} ${cpsk} > ${sta}.log & 19 | PIDS+=" $!" 20 | else 21 | ./testnet2 ${sta} ${rep} ${cov} ${psk} ${port} ${cpsk} > ${sta}.log & 22 | PIDS+=" $!" 23 | fi 24 | done 25 | 26 | ERR=0 27 | for P in $PIDS; do 28 | if ! wait $P; then 29 | ERR=1 30 | fi 31 | done 32 | if [ "$ERR" != 0 ]; then 33 | echo 34 | echo Execution error, check logs 35 | echo 36 | fi 37 | -------------------------------------------------------------------------------- /src/Console.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Serial and telnet console. Intermediates communication between 7 | // CLI and the platform streams (serial, Telnet). 8 | 9 | #ifndef __CONSOLE_H 10 | #define __CONSOLE_H 11 | 12 | #include "Packet.h" 13 | #include "Network.h" 14 | #include "Display.h" 15 | #include "ArduinoBridge.h" 16 | #include "CLI.h" 17 | 18 | class Network; 19 | 20 | void console_setup(Ptr net); 21 | void console_handle(); 22 | void console_telnet_enable(); 23 | void console_telnet_disable(); 24 | 25 | // Receive keystrokes from Telnet 26 | void console_telnet_type(char c); 27 | 28 | // Goes to Telnet session, or serial 29 | void console_print(const char *); 30 | void console_print(const Buffer &); 31 | void console_print(char); 32 | void console_println(const char *); 33 | void console_println(const Buffer &); 34 | void console_println(); 35 | 36 | // Goes straight to serial 37 | void serial_print(const char *); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/Proto_Switch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2020 PU5EPX 4 | */ 5 | 6 | // Implementation of remote switch command 7 | 8 | #ifndef __PROTO_SWITCH_H 9 | #define __PROTO_SWITCH_H 10 | 11 | #include "L7Protocol.h" 12 | #include "Callsign.h" 13 | #include "Dict.h" 14 | 15 | struct SwitchTransaction { 16 | Callsign from; 17 | Buffer challenge; 18 | Buffer response; 19 | int64_t timeout; 20 | bool done; 21 | }; 22 | 23 | class SwitchTimeoutTask; 24 | 25 | class Proto_Switch: public L7Protocol { 26 | public: 27 | Proto_Switch(Network* net); 28 | virtual L7HandlerResponse handle(const Packet&); 29 | 30 | Proto_Switch() = delete; 31 | Proto_Switch(const Proto_Switch&) = delete; 32 | Proto_Switch(Proto_Switch&&) = delete; 33 | Proto_Switch& operator=(const Proto_Switch&) = delete; 34 | Proto_Switch& operator=(Proto_Switch&&) = delete; 35 | private: 36 | friend class SwitchTimeoutTask; 37 | void process_timeouts(int64_t); 38 | Dict transactions; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/Proto_C.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of confirmed request (C parameter) 7 | 8 | #include "Proto_C.h" 9 | #include "Network.h" 10 | #include "Packet.h" 11 | 12 | Proto_C::Proto_C(Network *net): L4Protocol(net) 13 | { 14 | } 15 | 16 | L4rxHandlerResponse Proto_C::rx(const Packet& pkt) 17 | { 18 | if (! pkt.params().has("C")) { 19 | // does not request confirmation 20 | return L4rxHandlerResponse(); 21 | } 22 | if (pkt.params().has("CO")) { 23 | // do not confirm a confirmation 24 | return L4rxHandlerResponse(); 25 | } 26 | if (pkt.to().is_bcast()) { 27 | // do not confirm broadcast packets 28 | return L4rxHandlerResponse(); 29 | } 30 | 31 | Buffer msg = Buffer("confirm ") + pkt.params().s_ident(); 32 | 33 | Params co = Params(); 34 | co.put_naked("CO"); 35 | 36 | return L4rxHandlerResponse(true, pkt.from(), co, msg, false, ""); 37 | } 38 | 39 | L4txHandlerResponse Proto_C::tx(const Packet& pkt) 40 | { 41 | // Do not act upon packets sent with C flag 42 | return L4txHandlerResponse(); 43 | } 44 | -------------------------------------------------------------------------------- /src/L7Protocol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Abstract class for application protocols 7 | 8 | #ifndef __L7PROTOCOL_H 9 | #define __L7PROTOCOL_H 10 | 11 | #include 12 | #include 13 | #include "Pointer.h" 14 | #include "Callsign.h" 15 | #include "Buffer.h" 16 | #include "Params.h" 17 | 18 | class Network; 19 | class Packet; 20 | 21 | struct L7HandlerResponse { 22 | L7HandlerResponse(); 23 | L7HandlerResponse(bool, const Callsign &, const Params&, const Buffer&); 24 | 25 | bool has_packet; 26 | Callsign to; 27 | Params params; 28 | Buffer msg; 29 | }; 30 | 31 | class L7Protocol { 32 | public: 33 | L7Protocol(Network*); 34 | virtual L7HandlerResponse handle(const Packet&); 35 | virtual ~L7Protocol(); 36 | protected: 37 | Network *net; 38 | // This class must be new()ed and not fooled around 39 | L7Protocol() = delete; 40 | L7Protocol(const L7Protocol&) = delete; 41 | L7Protocol(L7Protocol&&) = delete; 42 | L7Protocol& operator=(const L7Protocol&) = delete; 43 | L7Protocol& operator=(L7Protocol&&) = delete; 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/Modifier.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | /* Abstract class for modifier application protocols. 7 | * 8 | * A modifier (concrete impl. of modify()). This method is called 9 | * when a packet is to be forwarded by the station, that is, this 10 | * station is not the *sole* final destination (therefore, packets 11 | * from Q* will be offered to modify()). 12 | * 13 | * The modifier may return a modified packet, or 0 to pass it on. 14 | * More than one Modifier can modify a packet. The order is not 15 | * guaranteed, so implementations must take care not to disrupt 16 | * other protocols' work. 17 | * 18 | * See Modf_Rreq.cpp for a concrete example. This class adds our 19 | * callsign to the payload of a forwarded RREQ or RRSP packet 20 | * (and also handles a RREQ request). 21 | */ 22 | 23 | #include "Modifier.h" 24 | #include "Network.h" 25 | #include "Packet.h" 26 | 27 | Modifier::Modifier(Network *net): net(net) 28 | { 29 | // Network becomes the owner 30 | net->add_modifier(this); 31 | } 32 | 33 | Modifier::~Modifier() 34 | { 35 | net = 0; 36 | } 37 | -------------------------------------------------------------------------------- /src/Display.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | #include "ArduinoBridge.h" 7 | #include "Timestamp.h" 8 | #include "Display.h" 9 | #include "SSD1306.h" 10 | #include "Buffer.h" 11 | 12 | // Functions related to OLED display 13 | 14 | SSD1306 display(0x3c, 4, 15); 15 | 16 | void oled_init() 17 | { 18 | pinMode(16, OUTPUT); 19 | pinMode(25, OUTPUT); 20 | 21 | digitalWrite(16, LOW); // reset 22 | delay(50); 23 | digitalWrite(16, HIGH); // keep high while operating display 24 | 25 | display.init(); 26 | display.flipScreenVertically(); 27 | display.setFont(ArialMT_Plain_10); 28 | display.setTextAlignment(TEXT_ALIGN_LEFT); 29 | } 30 | 31 | void oled_show(const char *ma, const char *mb, const char *mc, const char *md) 32 | { 33 | char ms[20]; 34 | Buffer uptime = Buffer::millis_to_hms(sys_timestamp()); 35 | sprintf(ms, "uptime %s", uptime.c_str()); 36 | display.clear(); 37 | display.drawString(0, 0, ma); 38 | display.drawString(0, 12, mb); 39 | display.drawString(0, 24, mc); 40 | display.drawString(0, 36, md); 41 | display.drawString(0, 48, ms); 42 | display.display(); 43 | } 44 | -------------------------------------------------------------------------------- /NonAmateur.md: -------------------------------------------------------------------------------- 1 | # Non-amateur usage 2 | 3 | Even though this project sees amateur radio community as the primary 4 | user, we feel LoRaMaDoR provides a good foundation for non-amateur 5 | uses of LoRa radios. Encryption was specifically implemented for 6 | these use cases. 7 | 8 | (Amateurs must communicate using cleartext; on the 9 | other hand, amateurs are not bound to ISM band limitations of 10 | transmitter power, antenna gain and duty cycle.) 11 | 12 | The Proto\_Switch protocol is the initial PoC of something that 13 | I intend to use in a real-world scenario: control lights and 14 | actuators remotely over a reasonably high distance (~1km). 15 | Other home automation technologies I have checked (X10, Bluetooth, 16 | Wi-Fi) have 1/10 of the needed range. 17 | 18 | The Switch protocol has to implement protection against relay 19 | attacks. It uses a challenge-response scheme, and packets must 20 | be HMAC-signed to make sure the response can be trusted. 21 | 22 | This project does not use encryption, but LoRaL2 23 | encapsulation does support encryption, so you can change the 24 | code. Specifically, pass the encryption key and its length 25 | to LoRaL2 constructor, called in Network.cpp. 26 | -------------------------------------------------------------------------------- /Roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | Create prototype of "sensor" protocol, analogous to the Switch 4 | protocol that models an actuator. 5 | 6 | Abstract the challenge-response protection against replay attacks, 7 | found in Switch, so it is reusable in other protocols. 8 | 9 | Improve the Python stack quality, not just for testing. 10 | 11 | Experiment with 433MHz LoRa, 500mW modules, amplifiers, Si4463 12 | modules, so the project can work over a wider range of physical 13 | media, and be more versatile. Supporting the Si4463 could be an 14 | interesting challenge. Estimate the range of every option and 15 | add to README. 16 | 17 | Test with discrete LoRa modules (currently, the reference hardware 18 | is TTGO that integrates ESP32 and LoRa in the same board). 19 | 20 | Multiple devices for 1 callsign. 21 | Case in view: LoRa module with amplifier, if the amplifier hinders RX, 22 | the cheapest solution is to use a second LoRa module just for RX, but 23 | both modules belong to the same station. 24 | 25 | Telnet, serial x security. 26 | Cases: Telnet login/sniffing, stolen device. Add Telnet password? 27 | 28 | HMAC - multiple keys? 29 | 30 | Possibility of remote, NAT-piercing access 31 | 32 | Gateway via Internet, IP router 33 | 34 | -------------------------------------------------------------------------------- /LoRaMaDoR.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Main LoRaMaDoR network class, plus some auxiliary types 7 | 8 | #include 9 | #include "src/Network.h" 10 | #include "src/Display.h" 11 | #include "src/ArduinoBridge.h" 12 | #include "src/Timestamp.h" 13 | #include "src/Console.h" 14 | #include "src/Telnet.h" 15 | 16 | #include "driver/adc.h" 17 | #include 18 | #include 19 | 20 | Ptr Net; 21 | Preferences prefs; 22 | 23 | void setup() 24 | { 25 | // Base current, beacons every 10s: 106mA (avg over 30min) 26 | // 80MHz CPU freq: -34mA 27 | // OLED completely off: -5mA 28 | // OLED with no text: -2mA 29 | // beacons every 600s: -3mA 30 | 31 | setCpuFrequencyMhz(80); 32 | esp_bt_controller_disable(); 33 | // adc_power_off(); 34 | 35 | Serial.begin(115200); 36 | oled_init(); 37 | oled_show("Starting...", "", "", ""); 38 | 39 | Net = Ptr(new Network()); 40 | 41 | oled_show("Net up!", Buffer(Net->me()).c_str(), "", ""); 42 | console_setup(Net); 43 | wifi_setup(Net); 44 | } 45 | 46 | void loop() 47 | { 48 | wifi_handle(); 49 | console_handle(); 50 | Net->run_tasks(sys_timestamp()); 51 | } 52 | -------------------------------------------------------------------------------- /test/Preferences.cpp: -------------------------------------------------------------------------------- 1 | #include "Dict.h" 2 | #include "Buffer.h" 3 | #include "Preferences.h" 4 | 5 | // Emulation of Arduino ESP32 Preferences class 6 | 7 | static Dict nvram; 8 | 9 | void Preferences::begin(const char*) 10 | { 11 | } 12 | 13 | void Preferences::begin(const char*, bool) 14 | { 15 | } 16 | 17 | void Preferences::end() 18 | { 19 | } 20 | 21 | void Preferences::clear() 22 | { 23 | nvram = Dict(); 24 | } 25 | 26 | uint32_t Preferences::getUInt(const char* key) 27 | { 28 | if (!nvram.has(key)) { 29 | return 0; 30 | } 31 | return nvram[key].toInt(); 32 | } 33 | 34 | void Preferences::putUInt(const char* key, uint32_t value) 35 | { 36 | nvram[key] = Buffer::itoa(value); 37 | } 38 | 39 | size_t Preferences::getString(const char* key, char* value, size_t maxlen) 40 | { 41 | if (!nvram.has(key) || maxlen == 0) { 42 | return 0; 43 | } 44 | 45 | const Buffer bvalue = nvram[key]; 46 | if (bvalue.empty()) { 47 | value[0] = 0; 48 | return 1; 49 | } 50 | 51 | size_t i = bvalue.length() + 1; // with \0 52 | if (i > maxlen) { 53 | i = maxlen; 54 | } 55 | memcpy(value, bvalue.c_str(), i); 56 | 57 | return i; 58 | } 59 | 60 | void Preferences::putString(const char* key, const char* value) 61 | { 62 | nvram[key] = value; 63 | } 64 | -------------------------------------------------------------------------------- /test/lora_ping.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time, random 4 | 5 | class Ping: 6 | transactions = {} 7 | 8 | def __init__(self, tnc, server, loop, payload): 9 | self.tnc = tnc 10 | self.server = server 11 | self.loop = loop 12 | self.payload = payload 13 | self.callback = lambda payload: None 14 | self.sendPing() 15 | # TODO better transaction id 16 | Ping.transactions[self.server] = self 17 | 18 | def on_result(self, cb): 19 | self.callback = cb 20 | 21 | @staticmethod 22 | def rx(pkt): 23 | print("PING: received packet", pkt.msg) 24 | if pkt.fromm not in Ping.transactions: 25 | print("PING: pong packet has unknown sender") 26 | return True 27 | Ping.transactions[pkt.fromm].rxPong(pkt) 28 | return True 29 | 30 | def sendPing(self): 31 | print("PING: sending") 32 | self.tnc.sendpkt(self.server.encode("ascii") + b":PING " + self.payload) 33 | self.to = self.loop.schedule(self.timeoutPing, 10.0) 34 | 35 | def timeoutPing(self): 36 | # TODO maximum number of requests, exponential backoff 37 | self.sendPing() 38 | 39 | def rxPong(self, pkt): 40 | if self.to: 41 | self.loop.cancel(self.to) 42 | self.to = None 43 | self.callback(pkt.msg) 44 | del Ping.transactions[pkt.fromm] 45 | print("PING: **** finished transaction ****") 46 | -------------------------------------------------------------------------------- /src/Proto_Beacon.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of periodic beacon (sent to QB/QR). 7 | 8 | #include "Proto_Beacon.h" 9 | #include "Network.h" 10 | #include "ArduinoBridge.h" 11 | #include "Timestamp.h" 12 | #include "NVRAM.h" 13 | 14 | // Task for periodic transmission of beacon packet. 15 | class BeaconTask: public Task 16 | { 17 | public: 18 | BeaconTask(Proto_Beacon *b, int64_t offset): 19 | Task("beacon", offset), beacon(b) 20 | { 21 | } 22 | ~BeaconTask() { 23 | beacon = 0; 24 | } 25 | protected: 26 | virtual int64_t run2(int64_t now) 27 | { 28 | return beacon->beacon(); 29 | } 30 | private: 31 | Proto_Beacon *beacon; 32 | }; 33 | 34 | 35 | Proto_Beacon::Proto_Beacon(Network *net): L7Protocol(net) 36 | { 37 | net->schedule(new BeaconTask(this, Network::fudge(5000, 0.5))); 38 | } 39 | 40 | int64_t Proto_Beacon::beacon() const 41 | { 42 | Buffer uptime = Buffer::millis_to_hms(sys_timestamp()); 43 | Buffer msg = Buffer("up ") + uptime; 44 | if (net->am_i_repeater()) { 45 | net->send(Callsign("QR"), Params(), msg); 46 | } else { 47 | net->send(Callsign("QB"), Params(), msg); 48 | } 49 | uint32_t next = Network::fudge(arduino_nvram_beacon_load() * 1000, 0.5); 50 | // logi("Next beacon in ", next); 51 | return next; 52 | } 53 | -------------------------------------------------------------------------------- /src/L4Protocol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Abstract class for transport protocols 7 | 8 | #ifndef __L4PROTOCOL_H 9 | #define __L4PROTOCOL_H 10 | 11 | #include 12 | #include 13 | #include "Pointer.h" 14 | #include "Buffer.h" 15 | #include "Callsign.h" 16 | #include "Params.h" 17 | 18 | class Network; 19 | class Packet; 20 | 21 | struct L4rxHandlerResponse { 22 | L4rxHandlerResponse(); 23 | L4rxHandlerResponse(bool, const Callsign &, const Params&, const Buffer&, bool, const Buffer&); 24 | bool has_packet; 25 | Callsign to; 26 | Params params; 27 | Buffer msg; 28 | bool error; 29 | Buffer error_msg; 30 | }; 31 | 32 | struct L4txHandlerResponse { 33 | L4txHandlerResponse(); 34 | L4txHandlerResponse(Ptr); 35 | Ptr pkt; 36 | }; 37 | 38 | class L4Protocol { 39 | public: 40 | L4Protocol(Network*); 41 | virtual L4rxHandlerResponse rx(const Packet&) = 0; 42 | virtual L4txHandlerResponse tx(const Packet&) = 0; 43 | virtual ~L4Protocol(); 44 | protected: 45 | Network *net; 46 | // This class must be new()ed and not fooled around 47 | L4Protocol() = delete; 48 | L4Protocol(const L4Protocol&) = delete; 49 | L4Protocol(L4Protocol&&) = delete; 50 | L4Protocol& operator=(const L4Protocol&) = delete; 51 | L4Protocol& operator=(L4Protocol&&) = delete; 52 | }; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/Packet.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Class that encapsulates a LoRaMaDoR packet. 7 | // Includes layer-2 and layer-3 parsing and encoding. 8 | 9 | #ifndef __PACKET_H 10 | #define __PACKET_H 11 | 12 | #include "Vector.h" 13 | #include "Buffer.h" 14 | #include "Pointer.h" 15 | #include "Callsign.h" 16 | #include "Params.h" 17 | 18 | class Packet { 19 | public: 20 | Packet(const Callsign &to, const Callsign &from, 21 | const Params& params, const Buffer& msg, int rssi=0); 22 | ~Packet(); 23 | 24 | /* next 2 are public for unit testing */ 25 | static Ptr decode_l3(const char* data, size_t len, int rssi, int& error); 26 | static Ptr decode_l3_test(const char *data, int& error); 27 | 28 | Packet(const Packet &) = delete; 29 | Packet(Packet &&) = delete; 30 | Packet() = delete; 31 | Packet& operator=(const Packet &) = delete; 32 | bool operator==(const Packet &) = delete; 33 | 34 | Ptr change_msg(const Buffer&) const; 35 | Ptr change_params(const Params&) const; 36 | Buffer encode_l3(size_t max) const; 37 | Buffer signature() const; 38 | Callsign to() const; 39 | Callsign from() const; 40 | const Params params() const; 41 | const Buffer msg() const; 42 | int rssi() const; 43 | 44 | private: 45 | Callsign _to; 46 | Callsign _from; 47 | const Params _params; 48 | Buffer _signature; 49 | const Buffer _msg; 50 | int _rssi; 51 | }; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /test/lora_loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import socket, sys, select, time, random 4 | 5 | class EventLoop: 6 | def __init__(self): 7 | self.task_counter = 1 8 | self.tasks = {} 9 | 10 | def schedule(self, task, to): 11 | handle = self.task_counter 12 | self.task_counter += 1 13 | self.tasks[handle] = (task, time.time() + to) 14 | return handle 15 | 16 | def cancel(self, handle): 17 | del self.tasks[handle] 18 | 19 | def service(self, *conns): 20 | rd = [] 21 | wr = [] 22 | for conn in conns: 23 | if not conn.sock or conn.sock.fileno() < 0: 24 | continue 25 | rd.append(conn.sock) 26 | if conn.writebuf: 27 | wr.append(conn.sock) 28 | if not rd and not wr: 29 | return False 30 | 31 | min_to = time.time() + 60 32 | for n, task_record in self.tasks.items(): 33 | task, to = task_record 34 | if to < min_to: 35 | min_to = to 36 | to = min_to - time.time() 37 | if to < 0: 38 | to = 0 39 | # print("Timeout %f" % to) 40 | 41 | rdok, wrok, dummy = select.select(rd, wr, [], to) 42 | for conn in conns: 43 | if conn.sock in rdok: 44 | conn.do_recv() 45 | if conn.sock in wrok: 46 | conn.do_send() 47 | now = time.time() 48 | done = [] 49 | to_call = [] 50 | for n, task_record in self.tasks.items(): 51 | task, to = task_record 52 | if to < now: 53 | to_call.append(task) 54 | done.append(n) 55 | for task in to_call: 56 | task() 57 | for n in done: 58 | del self.tasks[n] 59 | return True 60 | -------------------------------------------------------------------------------- /test/FakeArduino.cpp: -------------------------------------------------------------------------------- 1 | // Emulation of certain Arduino APIs for testing on UNIX 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "Buffer.h" 12 | #include "Pointer.h" 13 | #include "Packet.h" 14 | 15 | // Emulation of millis() and random() 16 | 17 | static struct timeval tm_first; 18 | static bool virgin = true; 19 | 20 | static void init_things() 21 | { 22 | virgin = false; 23 | gettimeofday(&tm_first, 0); 24 | srandom(tm_first.tv_sec + tm_first.tv_usec); 25 | } 26 | 27 | uint32_t _arduino_millis() 28 | { 29 | if (virgin) init_things(); 30 | struct timeval tm; 31 | gettimeofday(&tm, 0); 32 | int64_t now_us = tm.tv_sec * 1000000LL + tm.tv_usec; 33 | int64_t start_us = tm_first.tv_sec * 1000000LL + tm_first.tv_usec; 34 | // uptime in ms 35 | int64_t uptime_ms = (now_us - start_us) / 1000 + 1; 36 | // add 0xffffffff so we start near wrapping point 37 | uptime_ms += 0xfffffe00ULL; 38 | return (uint32_t) (uptime_ms & 0xffffffffULL); 39 | } 40 | 41 | int32_t arduino_random2(int32_t min, int32_t max) 42 | { 43 | if (virgin) init_things(); 44 | return min + random() % (max - min); 45 | } 46 | 47 | void arduino_restart() { 48 | exit(0); 49 | } 50 | 51 | void oled_show(const char *, const char *, const char *, const char*) 52 | { 53 | } 54 | 55 | Buffer get_wifi_status() 56 | { 57 | return "Fake Wi-Fi status"; 58 | } 59 | 60 | void telnet_print(const char *) 61 | { 62 | // dummy; no Telnet mode in desktop testing mode 63 | } 64 | -------------------------------------------------------------------------------- /src/HMACKeys.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2020 PU5EPX 4 | */ 5 | 6 | #include "HMACKeys.h" 7 | #include "NVRAM.h" 8 | #include "LoRaL2/src/sha256.h" 9 | 10 | static bool valid = false; 11 | static Buffer psk; 12 | 13 | // TODO allow to store keys per-prefix (with or without SSID, etc.) 14 | Buffer HMACKeys::get_key_for(const Callsign &c) 15 | { 16 | if (!valid) { 17 | psk = arduino_nvram_hmac_psk_load(); 18 | valid = true; 19 | } 20 | return psk; 21 | } 22 | 23 | void HMACKeys::invalidate() 24 | { 25 | valid = false; 26 | } 27 | 28 | static const char* hex = "0123456789abcdef"; 29 | 30 | Buffer HMACKeys::hmac(const Buffer& key, const Buffer& data) 31 | { 32 | Sha256 hmac; 33 | hmac.initHmac((uint8_t*) key.c_str(), key.length()); 34 | for (size_t i = 0; i < data.length(); ++i) { 35 | hmac.write((uint8_t) data.charAt(i)); 36 | } 37 | uint8_t* res = hmac.resultHmac(); 38 | // convert the first 48 bits of HMAC (6 octets) to hex 39 | char b64[13]; 40 | for (size_t i = 0; i < 6; ++i) { 41 | b64[i*2+0] = hex[(res[i] >> 4) & 0xf]; 42 | b64[i*2+1] = hex[res[i] & 0xf]; 43 | } 44 | 45 | return Buffer(b64, 12); 46 | } 47 | 48 | Buffer HMACKeys::hash_key(const Buffer& key) 49 | { 50 | Sha256 hash; 51 | hash.init(); 52 | hash.write(1); 53 | for (size_t i = 0; i < key.length(); ++i) { 54 | hash.write((uint8_t) key.charAt(i)); 55 | } 56 | uint8_t* res = hash.result(); 57 | char b64[32]; 58 | for (size_t i = 0; i < 16; ++i) { 59 | b64[i*2+0] = hex[(res[i] >> 4) & 0xf]; 60 | b64[i*2+1] = hex[res[i] & 0xf]; 61 | } 62 | return Buffer(b64, 32); 63 | } 64 | -------------------------------------------------------------------------------- /python/lorasun.py: -------------------------------------------------------------------------------- 1 | # this is an attempt to make something useful for the LoRaMaDor project 2 | # this program will check the times (UTC) of the Sun rise, and Sun set for 3 | # a given latitude and longitude. After getting the information it will send 4 | # to a LoRaMaDor node of your choice. 5 | # You need a LoRaMaDor board hooked to the computer. 6 | # Problem, this sends too much for the node to display correctly, but you can read it from a serial port 7 | # copyright 2020, Aug 17, 2020 LeRoy Miller KD8BXP v0.0.1 8 | 9 | #python 3 10 | 11 | import urllib.request, json 12 | import serial 13 | 14 | ENCODING = 'utf-8' # don't change me 15 | 16 | #change the lat, lng to your lat/lng 17 | with urllib.request.urlopen("https://api.sunrise-sunset.org/json?lat=39.515057&lng=-84.398277&formatted=0") as url: 18 | data = json.loads(url.read().decode()) 19 | #print(data) 20 | sunrise = data['results']['sunrise'].replace("T"," ") 21 | sunset = data['results']['sunset'].replace("T"," ") 22 | print(sunrise) 23 | print(sunset) 24 | 25 | #change to the node you want this information sent too 26 | msg = '{} {} {} {}'.format('N0CALL-2 Sunrise ',sunrise.replace("+00:00",""), 'Sunset ', sunset.replace("+00:00","")).encode(ENCODING) 27 | 28 | print(msg) 29 | 30 | print ("Sending page...") 31 | ser = serial.Serial('/dev/ttyUSB0', 115200) #you may need to change the serial port 32 | ser.write(msg) 33 | ser.write(b'\n\r') 34 | ser.close() 35 | 36 | # I am pretty new to python, so there is still a lot I need to learn, please feel free to update this with improvements 37 | # but please share so I can see and learn something. Thanks, LeRoy KD8BXP 38 | 39 | -------------------------------------------------------------------------------- /src/Task.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Abstract class for Task classes: delayed code execution 7 | 8 | #ifndef __TASK_H 9 | #define __TASK_H 10 | 11 | #include 12 | #include 13 | #include "Vector.h" 14 | #include "Buffer.h" 15 | #include "Pointer.h" 16 | 17 | class TaskManager; 18 | 19 | class Task { 20 | public: 21 | Task(const char *name, int64_t offset); 22 | virtual ~Task(); 23 | int64_t next_run() const; 24 | Buffer get_name() const; 25 | 26 | protected: 27 | // overridden by concrete task subclasses 28 | virtual int64_t run2(int64_t now) = 0; 29 | 30 | private: 31 | bool run(int64_t now); 32 | void set_timebase(int64_t timebase); 33 | bool should_run(int64_t now) const; 34 | bool cancelled() const; 35 | 36 | Buffer name; 37 | int64_t offset; 38 | int64_t timebase; 39 | 40 | // Tasks must be manipulated through (smart) pointers, 41 | // the pointer is the ID, no copies allowed 42 | Task() = delete; 43 | Task(const Task&) = delete; 44 | Task(Task&&) = delete; 45 | Task& operator=(const Task&) = delete; 46 | Task& operator=(Task&&) = delete; 47 | 48 | friend class TaskManager; 49 | }; 50 | 51 | class TaskManager { 52 | public: 53 | TaskManager(); 54 | ~TaskManager(); 55 | void stop(); 56 | void run(int64_t); 57 | void schedule(Ptr task); 58 | void cancel(const Task* task); 59 | // for testing purposes 60 | Ptr next_task() const; 61 | private: 62 | Vector< Ptr > tasks; 63 | 64 | TaskManager(const TaskManager&) = delete; 65 | TaskManager(const TaskManager&&) = delete; 66 | TaskManager& operator=(const TaskManager&) = delete; 67 | TaskManager& operator=(TaskManager&&) = delete; 68 | }; 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /src/Buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Class that encapsulates a buffer or string 7 | 8 | #ifndef __BUFFER_H 9 | #define __BUFFER_H 10 | 11 | #include 12 | #include 13 | 14 | class Buffer { 15 | public: 16 | Buffer(); 17 | Buffer(int len); 18 | Buffer(const char *, int len); 19 | Buffer(const char *); 20 | Buffer(const Buffer&); 21 | Buffer(Buffer&&); 22 | Buffer& operator=(const Buffer&); 23 | Buffer& operator=(Buffer&&); 24 | ~Buffer(); 25 | 26 | static Buffer itoa(int32_t); 27 | static Buffer millis_to_hms(int64_t); 28 | Buffer substr(size_t start) const; 29 | Buffer substr(size_t start, size_t end) const; 30 | 31 | bool empty() const; 32 | size_t length() const; 33 | const char* c_str() const; 34 | Buffer& uppercase(); 35 | bool operator==(const char *cmp) const; 36 | bool operator==(const Buffer &) const; 37 | bool operator!=(const char *cmp) const; 38 | bool operator!=(const Buffer &) const; 39 | int compareTo(const Buffer &) const; 40 | int compareTo(const char *cmp) const; 41 | bool startsWith(const char *cmp) const; 42 | bool startsWith(const Buffer &) const; 43 | Buffer& append(const char *s, size_t length); 44 | Buffer& operator+=(const char s); 45 | Buffer& operator+=(const Buffer&); 46 | Buffer& operator+=(const char *); 47 | Buffer operator+(const Buffer&) const; 48 | Buffer operator+(const char *) const; 49 | Buffer operator+(const char) const; 50 | Buffer& cut(int); 51 | Buffer& lstrip(); 52 | Buffer& rstrip(); 53 | Buffer& strip(); 54 | int indexOf(const char) const; 55 | int charAt(int) const; 56 | int charAt(size_t) const; 57 | int toInt() const; 58 | Buffer tohex() const; 59 | Buffer fromhex() const; 60 | 61 | friend class BufferImpl; 62 | private: 63 | char *buf; 64 | size_t len; 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/Pointer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Smart pointer implementation. 7 | 8 | #ifndef __PTR_H 9 | #define __PTR_H 10 | 11 | #include 12 | 13 | template class PtrRef; 14 | 15 | template class Ptr 16 | { 17 | public: 18 | inline Ptr() 19 | { 20 | payload = new PtrRef(0); 21 | } 22 | 23 | explicit inline Ptr(T* parg) 24 | { 25 | payload = new PtrRef(parg); 26 | } 27 | 28 | inline Ptr(const Ptr& arg) 29 | { 30 | payload = arg.payload; 31 | ++payload->refcount; 32 | } 33 | 34 | inline Ptr& operator=(const Ptr& outro) 35 | { 36 | ++outro.payload->refcount; 37 | if(--payload->refcount == 0) { 38 | delete payload; 39 | } 40 | payload = outro.payload; 41 | return *this; 42 | } 43 | 44 | inline ~Ptr() 45 | { 46 | if (--payload->refcount == 0) { 47 | delete payload; 48 | payload = 0; 49 | } 50 | } 51 | 52 | inline T* operator->() const 53 | { 54 | return payload->pointer; 55 | } 56 | 57 | inline T& operator*() const 58 | { 59 | return *(payload->pointer); 60 | } 61 | 62 | inline bool operator!() const 63 | { 64 | return (payload->pointer == 0); 65 | } 66 | 67 | inline operator bool() const 68 | { 69 | return (payload->pointer != 0); 70 | } 71 | 72 | inline const T* id() const 73 | { 74 | return payload->pointer; 75 | } 76 | 77 | private: 78 | PtrRef* payload; 79 | }; 80 | 81 | template class PtrRef 82 | { 83 | inline PtrRef(T* p) 84 | { 85 | pointer = p; 86 | refcount = 1; 87 | } 88 | 89 | inline ~PtrRef() 90 | { 91 | delete pointer; 92 | pointer = 0; 93 | } 94 | 95 | T* pointer; 96 | size_t refcount; 97 | 98 | friend class Ptr; 99 | }; 100 | 101 | #endif 102 | 103 | -------------------------------------------------------------------------------- /_pocs_/simulator/diamond.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | import random, math, asyncio 8 | from sim_radio import Radio 9 | from sim_network import Station, run 10 | from sim_trafficgen import * 11 | 12 | STATION_COUNT=10 13 | 14 | stations = {} 15 | r = Radio() 16 | 17 | # create stations 18 | for i in range(0, STATION_COUNT): 19 | callsign = chr(ord('A') + i) * 5 20 | stations[callsign] = Station(callsign, r) 21 | 22 | # create model mesh 23 | r.edge("AAAAA", "BBBBB", -50) # "AAAAA" <- "BBBBB", rssi -50 24 | r.edge("AAAAA", "CCCCC", -70) 25 | 26 | r.edge("CCCCC", "AAAAA", -60) 27 | r.edge("CCCCC", "BBBBB", -75) 28 | r.edge("CCCCC", "EEEEE", -80) 29 | r.edge("CCCCC", "FFFFF", -60) 30 | 31 | r.edge("BBBBB", "AAAAA", -55) 32 | r.edge("BBBBB", "CCCCC", -75) 33 | r.edge("BBBBB", "DDDDD", -51) 34 | r.edge("BBBBB", "EEEEE", -51) 35 | 36 | r.edge("DDDDD", "BBBBB", -45) 37 | r.edge("DDDDD", "EEEEE", -65) 38 | r.edge("DDDDD", "GGGGG", -85) 39 | 40 | r.edge("EEEEE", "BBBBB", -70) 41 | r.edge("EEEEE", "CCCCC", -60) 42 | r.edge("EEEEE", "DDDDD", -90) 43 | r.edge("EEEEE", "FFFFF", None) # out 44 | r.edge("EEEEE", "GGGGG", -65) 45 | r.edge("EEEEE", "HHHHH", -62) 46 | 47 | r.edge("FFFFF", "CCCCC", None) # out 48 | r.edge("FFFFF", "EEEEE", None) # out 49 | r.edge("FFFFF", "HHHHH", -80) 50 | 51 | r.edge("GGGGG", "DDDDD", -61) 52 | r.edge("GGGGG", "EEEEE", -62) 53 | r.edge("GGGGG", "HHHHH", -63) 54 | r.edge("GGGGG", "IIIII", None) # out 55 | 56 | r.edge("HHHHH", "FFFFF", None) # out 57 | r.edge("HHHHH", "EEEEE", -66) 58 | r.edge("HHHHH", "GGGGG", -60) 59 | r.edge("HHHHH", "IIIII", -47) 60 | 61 | r.edge("IIIII", "GGGGG", -40) 62 | r.edge("IIIII", "HHHHH", -70) 63 | r.edge("IIIII", "JJJJJ", -65) 64 | 65 | r.edge("JJJJJ", "IIIII", -60) 66 | 67 | # add talkers 68 | for callsign, station in stations.items(): 69 | station.add_traffic_gen(RagChewer) 70 | pass 71 | 72 | run() 73 | -------------------------------------------------------------------------------- /_pocs_/simulator/sim_trafficgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | import random, asyncio, sys, string, time 8 | from sim_network import Station 9 | 10 | loop = asyncio.get_event_loop() 11 | 12 | class RagChewer: 13 | def __init__(self, station): 14 | async def talk(): 15 | while True: 16 | await asyncio.sleep(60 * random.random()) 17 | to_options = Station.get_all_callsigns() 18 | # to_options.remove(station.callsign) 19 | to_options.append("UNKNOWN") 20 | to_options.append("QC") 21 | to_options.append("QL") 22 | to = random.choice(to_options) 23 | msg = ''.join(random.choice(" " + string.ascii_lowercase + string.digits) \ 24 | for _ in range(20)) 25 | params = {} 26 | if random.random() < 0.1: 27 | params["T"] = int(time.time()) - 1552265462 28 | if random.random() < 0.1: 29 | params["S"] = "de4db33f" 30 | 31 | station.send(to, params, msg) 32 | loop.create_task(talk()) 33 | 34 | class Pinger: 35 | def __init__(self, station): 36 | async def talk(): 37 | while True: 38 | await asyncio.sleep(150 * random.random()) 39 | to_options = Station.get_all_callsigns() 40 | to = random.choice(to_options) 41 | msg = ''.join(random.choice(" " + string.ascii_lowercase + string.digits) \ 42 | for _ in range(3)) 43 | 44 | station.send(to, {"PING": None}, msg) 45 | loop.create_task(talk()) 46 | 47 | class TraceRouter: 48 | def __init__(self, station): 49 | async def talk(): 50 | while True: 51 | await asyncio.sleep(600 * random.random()) 52 | to_options = Station.get_all_callsigns() 53 | to = random.choice(to_options) 54 | msg = ''.join(random.choice(" " + string.ascii_lowercase + string.digits) \ 55 | for _ in range(3)) 56 | 57 | station.send(to, {"RREQ": None}, msg) 58 | loop.create_task(talk()) 59 | 60 | traffic_gens = [ RagChewer, Pinger, TraceRouter ] 61 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDEBUG -DUNDER_TEST -fsanitize=undefined -fstack-protector-strong -fstack-protector-all -std=c++1y -Wall -g -O0 -fprofile-arcs -ftest-coverage -fno-elide-constructors 2 | OBJ=Packet.o Buffer.o Task.o FakeArduino.o Network.o Callsign.o Params.o CLI.o L4Protocol.o L7Protocol.o Modifier.o Proto_Ping.o Proto_Rreq.o Modf_Rreq.o Modf_R.o Proto_Beacon.o Proto_C.o Proto_HMAC.o HMACKeys.o Proto_Switch.o NVRAM.o Preferences.o Timestamp.o Console.o Serial.o 3 | 4 | all: test testnet testnet2 5 | 6 | clean: 7 | rm -rf *.o test testnet *.gcda *.gcno *.info out *.dSYM *.log *.val *.gcov 8 | 9 | .cpp.o: *.h 10 | gcc $(CFLAGS) -c $< 11 | 12 | test: test.cpp $(OBJ) *.h 13 | gcc $(CFLAGS) -o test test.cpp $(OBJ) LoRaL2-test/*.o -lstdc++ 14 | 15 | testnet: testnet.cpp $(OBJ) *.h 16 | gcc $(CFLAGS) -o testnet testnet.cpp $(OBJ) LoRaL2-test/*.o -lstdc++ 17 | 18 | testnet2: testnet2.cpp $(OBJ) *.h 19 | gcc $(CFLAGS) -o testnet2 testnet2.cpp $(OBJ) LoRaL2-test/*.o -lstdc++ 20 | 21 | recov: 22 | rm -f *.gcda 23 | 24 | coverage: 25 | rm -f *.info 26 | gcov test.cpp 27 | gcov testnet.cpp 28 | gcov testnet2.cpp 29 | lcov -c --directory . --output-file main_coverage.info 30 | lcov -r main_coverage.info '*/RS-FEC.h' -o main_coverage.info 31 | lcov -r main_coverage.info '*/testnet.cpp' -o main_coverage.info 32 | lcov -r main_coverage.info '*/testnet2.cpp' -o main_coverage.info 33 | lcov -r main_coverage.info '*/FakeArduino.cpp' -o main_coverage.info 34 | lcov -r main_coverage.info '*/Serial.cpp' -o main_coverage.info 35 | lcov -r main_coverage.info '*/Preferences.cpp' -o main_coverage.info 36 | lcov -r main_coverage.info '*/sha256.cpp' -o main_coverage.info 37 | lcov -r main_coverage.info '*/Crypto.h' -o main_coverage.info 38 | lcov -r main_coverage.info '*/Crypto.cpp' -o main_coverage.info 39 | lcov -r main_coverage.info '*/AESCommon.cpp' -o main_coverage.info 40 | lcov -r main_coverage.info '*/AES256.cpp' -o main_coverage.info 41 | lcov -r main_coverage.info '*/BlockCipher.cpp' -o main_coverage.info 42 | genhtml main_coverage.info --output-directory out 43 | xdg-open out/index.html 44 | -------------------------------------------------------------------------------- /_pocs_/simulator/sim_radio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | import random, asyncio, sys 8 | 9 | VERBOSITY=50 10 | SPEED=1400.0 # bps 11 | 12 | loop = asyncio.get_event_loop() 13 | 14 | class Radio: 15 | dbg_pkts_sent = 0 16 | dbg_bits_sent = 0 17 | 18 | def __init__(self): 19 | self.edges = {} 20 | self.stations = {} 21 | async def transmitted(): 22 | while True: 23 | runtime = 30 24 | await asyncio.sleep(runtime) 25 | print("########### radio: sent %d pkts %d bits %d bps" % 26 | (Radio.dbg_pkts_sent, Radio.dbg_bits_sent, 27 | Radio.dbg_bits_sent / runtime)) 28 | Radio.dbg_pkts_sent = 0 29 | Radio.dbg_bits_sent = 0 30 | loop.create_task(transmitted()) 31 | 32 | def active_edges(self): 33 | n = 0 34 | for _, v in self.edges.items(): 35 | for __, rssi in v.items(): 36 | if rssi is not None: 37 | n += 1 38 | return n 39 | 40 | def edge(self, to, fr0m, rssi): 41 | if fr0m not in self.edges: 42 | self.edges[fr0m] = {} 43 | self.edges[fr0m][to] = rssi 44 | 45 | def biedge(self, to, fr0m, rssi1, rssi2): 46 | self.edge(to, fr0m, rssi1) 47 | self.edge(fr0m, to, rssi2) 48 | 49 | def attach(self, callsign, station): 50 | self.stations[callsign] = station 51 | 52 | def send(self, fr0m, pkt_string): 53 | Radio.dbg_pkts_sent += 1 54 | bits = 8 * len(pkt_string) 55 | Radio.dbg_bits_sent += bits 56 | 57 | if fr0m not in self.edges or not self.edges[fr0m]: 58 | print("radio %s: nobody listens to me", fr0m) 59 | return 60 | 61 | for dest, rssi in self.edges[fr0m].items(): 62 | if rssi is None: 63 | if VERBOSITY > 80: 64 | print("radio %s: not bcasting to %s" % \ 65 | (fr0m, dest)) 66 | continue 67 | else: 68 | if VERBOSITY > 70: 69 | print("radio %s: bcasting %s " % \ 70 | (fr0m, dest)) 71 | 72 | async def asend(d, r, ps): 73 | await asyncio.sleep((bits / SPEED) * (1 + 0.2 * random.random())) 74 | self.stations[d].radio_recv(r, ps) 75 | 76 | loop.create_task(asend(dest, rssi, pkt_string)) 77 | -------------------------------------------------------------------------------- /python/readme.md: -------------------------------------------------------------------------------- 1 | # Some Python to enteract with the LoRaMaDor project 2 | 3 | Aprsisfeed.py - An attempt to watch the APRS-IS stream and look for messages to a callsign, the messages will be forwarded to the LoRaMaDor board The forwarded message will also contain the callsign of the sending ham you will need a LoRa board setup with the LoRaMaDor sketch to act as a gateway You will need to add your call/settings. 4 | install aprslib using pip, this script works with python3. 5 | 6 | Chat.py - This is a modified version of the LoraHam-Console from Travis Goodspeed's loraham project https://github.com/travisgoodspeed/loraham it now sends packets and will send a packet to another LoRaMaDor node. this modified version removes most of the buttons, and moves the send button to below the message, making it a little easier to use this as a "chat". It will also show the verbose output of your board. To use this you will need a LoRaMaDor board hooked to your computer. Aug 18, 2020 LeRoy Miller I am still learning python, and I used this program as a good example, all credit should go to Travis Goodspeed, my changes were small. 7 | 8 | Loraham-console.py - slightly modified version of LoRaHam-Console by Travis Goodspeed, modified by LeRoy Miller Aug 17, 2020, all credit should go to Travis Goodspeed's and the loraham project https://github.com/travisgoodspeed/loraham it now sends packets and will send a packet to another LoRaMaDor node. To use this you will need a LoRaMaDor board hooked to your computer. 9 | 10 | This two scripts use urwid. 11 | 12 | lorasun.py - this is an attempt to make something useful for the LoRaMaDor project this program will check the times (UTC) of the Sun rise, and Sun set for a given latitude and longitude. After getting the information it will send to a LoRaMaDor node of your choice. You need a LoRaMaDor board hooked to the computer. Problem, this sends too much for the node to display correctly, but you can read it from a serial port copyright 2020, Aug 17, 2020 LeRoy Miller KD8BXP v0.0.1 13 | this uses urllib.request, and json 14 | 15 | I am pretty new to python, so there is still a lot I need to learn, please feel free to update this with improvements but please share so I can see and learn something. Thanks, LeRoy KD8BXP 16 | 17 | -------------------------------------------------------------------------------- /src/L4Protocol.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | /* Abstract class for transport (intermediate) protocols. 7 | * A transport protocol class may have: 8 | * 9 | * 1) a handler (concrete implementation of rx()). 10 | * This method is called when a packet is received by the station, 11 | * that is, this station is the final destination. This is true 12 | * even for QC/QB/QR packets (in which this station is not the *only* 13 | * destination). 14 | * 15 | * The class may choose to handle the packet and return a response. 16 | * Regardless of any handling, all other L4 protocols and some L7 17 | * protocol may also handle the packet in their own way. 18 | * 19 | * See Proto_C.cpp (confirm packet) for a concrete example. 20 | * 21 | * 2) a tweaker (concrete implementation of tx()). 22 | * This method is called when a packet is sent by the station. 23 | * All L4 protocols have a chance to analyse and tweak the 24 | * packet as necessary, independently of the L7 protocol. 25 | * 26 | * The Proto_HMAC protocol implements both tx() and rx() methods: 27 | * when a packet is generated by the application protocol, tx() 28 | * adds the HMAC signature. When a packet is received, rx() 29 | * checks the HMAC and discards packets with bad signatures. 30 | */ 31 | 32 | #include "L4Protocol.h" 33 | #include "Network.h" 34 | #include "Packet.h" 35 | 36 | L4rxHandlerResponse::L4rxHandlerResponse(bool has_packet, const Callsign& to, 37 | const Params& params, const Buffer& msg, bool error, 38 | const Buffer& error_msg): 39 | has_packet(has_packet), to(to), params(params), msg(msg), 40 | error(error), error_msg(error_msg) 41 | {} 42 | 43 | L4rxHandlerResponse::L4rxHandlerResponse(): 44 | has_packet(false), to(Callsign()), params(Params()), msg(""), 45 | error(false), error_msg("") 46 | {} 47 | 48 | L4txHandlerResponse::L4txHandlerResponse(Ptr pkt): 49 | pkt(pkt) 50 | {} 51 | 52 | L4txHandlerResponse::L4txHandlerResponse(): 53 | pkt(Ptr(0)) 54 | {} 55 | 56 | L4Protocol::L4Protocol(Network *net): net(net) 57 | { 58 | // Network becomes the owner 59 | net->add_l4protocol(this); 60 | } 61 | 62 | L4Protocol::~L4Protocol() 63 | { 64 | net = 0; 65 | } 66 | -------------------------------------------------------------------------------- /python/aprsisfeed.py: -------------------------------------------------------------------------------- 1 | # An attempt to watch the APRS-IS stream and look for messages to 2 | # a callsign, the messages will be forwarded to the LoRaMaDor board 3 | # The forwarded message will also contain the callsign of the sending ham 4 | # you will need a LoRa board setup with the LoRaMaDor sketch to act as a gateway 5 | # You will need to add your call/settings below 6 | # 7 | # copyright 2020, Aug 17, 2020 LeRoy Miller KD8BXP v0.0.1 8 | 9 | import aprslib, json, serial #not sure json is really needed 10 | 11 | def callback(packet): 12 | pto = packet['to'] 13 | pfrom = packet['from'] 14 | pformat = packet['format'] 15 | if pto == 'N0CALL' and pformat == 'message': #put the callsign from APRS you want the messages forward from in pto == 16 | print(pto + " " + pfrom + " " + packet['message_text']) 17 | msg = '{} {} {}'.format('N0CALL-2',pfrom,packet['message_text']).encode('utf-8') #put the callsign of the LoRaMaDor board you wish to recieve your message at 18 | print("Sending message...") 19 | print(msg) 20 | ser = serial.Serial('/dev/ttyUSB0', 115200) #You may need to change the serial port here, the speed should be the same 21 | ser.write(msg) 22 | ser.write(b'\n\r') 23 | ser.close() 24 | 25 | AIS = aprslib.IS("N0CALL",passwd='999999', port=14580) #at this point a passwd probably isn't required. 26 | AIS.connect() 27 | AIS.set_filter("t/m") #message filter 28 | #AIS.consumer(callback, raw=True) 29 | AIS.consumer(callback, raw=False) 30 | 31 | #{'raw': 'HS0QKD-9>APGJW6-1,WIDE1-1,qAS,E27HCD-1:!1305.41N/10055.29Ev080/007/A=000085', 'from': 'HS0QKD-9', 'to': 'APGJW6-1', 'path': ['WIDE1-1', 'qAS', 'E27HCD-1'], 'via': 'E27HCD-1', 'messagecapable': False, 'format': 'uncompressed', 'posambiguity': 0, 'symbol': 'v', 'symbol_table': '/', 'latitude': 13.090166666666667, 'longitude': 100.9215, 'course': 80, 'speed': 12.964, 'altitude': 25.908, 'comment': ''} 32 | 33 | # I am pretty new to python, so there is still a lot I need to learn, please feel free to update this with improvements 34 | # but please share so I can see and learn something. Thanks, LeRoy KD8BXP 35 | 36 | # Idea for next version - make it a 2 way system, so that it can also send a message back into the APRS system passwd would be required for that. 37 | -------------------------------------------------------------------------------- /src/Proto_HMAC.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Implementation of confirmed request (C parameter) 7 | 8 | #include "Proto_HMAC.h" 9 | #include "HMACKeys.h" 10 | #include "Network.h" 11 | #include "Packet.h" 12 | #include "LoRaL2/src/sha256.h" 13 | 14 | Proto_HMAC::Proto_HMAC(Network *net): L4Protocol(net) 15 | { 16 | } 17 | 18 | L4rxHandlerResponse Proto_HMAC::rx(const Packet& orig_pkt) 19 | { 20 | Buffer key = HMACKeys::get_key_for(orig_pkt.from()); 21 | if (key.empty()) { 22 | return L4rxHandlerResponse(); 23 | } 24 | return Proto_HMAC_rx(key, orig_pkt); 25 | } 26 | 27 | L4rxHandlerResponse Proto_HMAC_rx(const Buffer& key, const Packet& orig_pkt) 28 | { 29 | auto p = orig_pkt.params(); 30 | 31 | if (p.has("RREQ") || p.has("RRSP")) { 32 | return L4rxHandlerResponse(); 33 | } 34 | 35 | if (! p.has("H")) { 36 | return L4rxHandlerResponse(false, Callsign(), Params(), "", 37 | true, "Packet w/o HMAC"); 38 | } 39 | 40 | Buffer recv_hmac = p.get("H"); 41 | if (recv_hmac.length() != 12) { 42 | return L4rxHandlerResponse(false, Callsign(), Params(), "", 43 | true, "Invalid HMAC size"); 44 | } 45 | 46 | // recalculate HMAC locally and compare 47 | auto data = Buffer(orig_pkt.to()) + orig_pkt.from() + 48 | orig_pkt.params().s_ident() + orig_pkt.msg(); 49 | auto hmac = HMACKeys::hmac(key, data); 50 | 51 | if (hmac != recv_hmac) { 52 | return L4rxHandlerResponse(false, Callsign(), Params(), "", 53 | true, "Bad HMAC"); 54 | } 55 | 56 | return L4rxHandlerResponse(); 57 | } 58 | 59 | L4txHandlerResponse Proto_HMAC::tx(const Packet& orig_pkt) 60 | { 61 | Buffer key = HMACKeys::get_key_for(orig_pkt.from()); 62 | if (key.empty()) { 63 | return L4txHandlerResponse(); 64 | } 65 | return Proto_HMAC_tx(key, orig_pkt); 66 | } 67 | 68 | L4txHandlerResponse Proto_HMAC_tx(const Buffer& key, const Packet& orig_pkt) 69 | { 70 | auto p = orig_pkt.params(); 71 | if (p.has("RREQ") || p.has("RRSP")) { 72 | return L4txHandlerResponse(); 73 | } 74 | 75 | auto data = Buffer(orig_pkt.to()) + orig_pkt.from() + 76 | orig_pkt.params().s_ident() + orig_pkt.msg(); 77 | auto hmac = HMACKeys::hmac(key, data); 78 | 79 | p.put("H", hmac); 80 | return L4txHandlerResponse(orig_pkt.change_params(p)); 81 | } 82 | -------------------------------------------------------------------------------- /src/Console.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | #ifdef UNDER_TEST 7 | #include "Serial.h" 8 | #else 9 | #include 10 | #endif 11 | #include "Network.h" 12 | #include "CLI.h" 13 | #include "Telnet.h" 14 | #include "Console.h" 15 | 16 | // Serial and telnet console. Intermediates communication between 17 | // CLI and the platform streams (serial, Telnet). 18 | 19 | bool redirect_to_telnet = false; 20 | 21 | static Ptr Net; 22 | static Buffer output_buffer; 23 | 24 | // Called by main Arduino setup(). 25 | void console_setup(Ptr net) 26 | { 27 | Net = net; 28 | console_print("callsign: "); 29 | console_print(Net->me()); 30 | console_println(" ready. Type !help to see available commands."); 31 | } 32 | 33 | // Called by main Arduino loop(). 34 | // Handles serial communication, using non-blocking writes. 35 | void console_handle() 36 | { 37 | if (Serial.available() > 0) { 38 | int c = Serial.read(); 39 | if (!redirect_to_telnet) cli_type(c); 40 | } 41 | 42 | int a = Serial.availableForWrite(); 43 | int b = output_buffer.length(); 44 | int c = (a < b ? a : b); 45 | if (c > 0) { 46 | Serial.write((const uint8_t*) output_buffer.c_str(), c); 47 | output_buffer.cut(c); 48 | } 49 | } 50 | 51 | // Print to serial console (through a buffer; see console_handle()) 52 | void serial_print(const char *msg) 53 | { 54 | output_buffer += msg; 55 | } 56 | 57 | // Redirects console print to Telnet if there is a connection, 58 | // otherwise sends to serial console. 59 | static void platform_print(const char *msg) 60 | { 61 | if (!redirect_to_telnet) { 62 | serial_print(msg); 63 | } else { 64 | telnet_print(msg); 65 | } 66 | } 67 | 68 | // Print string on console. 69 | void console_print(const char *msg) { 70 | platform_print(msg); 71 | } 72 | 73 | void console_print(const Buffer &msg) { 74 | platform_print(msg.c_str()); 75 | } 76 | 77 | void console_print(char c) { 78 | char msg[] = {c, 0}; 79 | platform_print(msg); 80 | } 81 | 82 | void console_println(const char *msg) { 83 | platform_print(msg); 84 | platform_print("\r\n"); 85 | } 86 | 87 | void console_println(const Buffer &msg) { 88 | platform_print(msg.c_str()); 89 | platform_print("\r\n"); 90 | } 91 | 92 | void console_println() { 93 | platform_print("\r\n"); 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/L7Protocol.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | /* Abstract class for application protocols. 7 | * 8 | * A protocol class may have one or two elements: 9 | * 10 | * 1) a handler (concrete implementation of rx()). 11 | * This method is called when a packet is received by the station, 12 | * that is, this station is the final destination. This is true 13 | * even for QC/QR/QB packets (in which this station is not the *only* 14 | * destination). 15 | * 16 | * The class may choose to handle the packet and return a response, 17 | * or return 0 and pass it on. Packets handled by a protocol are not 18 | * offered to other Protocols, and normally they are not delivered to 19 | * application level (i.e. the user won't see it unless in debug mode). 20 | * 21 | * See Proto_Ping.cpp for a concrete example. This class answers 22 | * PONG to PING packets. 23 | * 24 | * 2) It can generate packets. In order to do this, it must subclass, 25 | * instantiate and schedule a Task that calls asynchronously some 26 | * tx method. 27 | * 28 | * See Proto_Beacon for a concrete example. In this example, the Task 29 | * is created only once, and returning a non-zero value effectively 30 | * reschedules the next packet. 31 | * 32 | * Tasks that interact with hardware must be careful not blocking 33 | * the main loop for too long, in particular when Wi-Fi is on, 34 | * because the ESP32 supervisor will reset the program if some 35 | * interruption is not handled in time. Depending on the case, 36 | * if there is bitbanging involved, etc. the Wi-Fi should stay 37 | * disabled. 38 | */ 39 | 40 | #include "L7Protocol.h" 41 | #include "Network.h" 42 | #include "Packet.h" 43 | 44 | L7HandlerResponse::L7HandlerResponse(bool has_packet, const Callsign& to, 45 | const Params& params, const Buffer& msg): 46 | has_packet(has_packet), to(to), params(params), msg(msg) 47 | {} 48 | 49 | L7HandlerResponse::L7HandlerResponse(): 50 | has_packet(false), to(Callsign()), params(Params()), msg("") 51 | {} 52 | 53 | L7Protocol::L7Protocol(Network *net): net(net) 54 | { 55 | // Network becomes the owner 56 | net->add_l7protocol(this); 57 | } 58 | 59 | L7Protocol::~L7Protocol() 60 | { 61 | net = 0; 62 | } 63 | 64 | L7HandlerResponse L7Protocol::handle(const Packet&) 65 | { 66 | // by default, does not handle upon receiving 67 | return L7HandlerResponse(); 68 | } 69 | -------------------------------------------------------------------------------- /_pocs_/simulator/adv/sim_packet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | class Packet: 8 | last_ident = 1000 9 | last_tag = 1 10 | 11 | def __init__(self, to, via, fr0m, ttl, msg, base_pkt=None): 12 | self.frozen = False 13 | 14 | self.to = to.upper() 15 | self.via = via.upper() 16 | self.fr0m = fr0m.upper() 17 | self.ttl = ttl 18 | self.msg = msg 19 | 20 | if not base_pkt: 21 | # New packet 22 | # ident: ID for human logging 23 | Packet.last_ident += 1 24 | self.ident = "%d" % Packet.last_ident 25 | # tag: ID for delivery debugging 26 | Packet.last_tag += 1 27 | self.tag = Packet.last_tag 28 | else: 29 | # Derived packet 30 | self.ident = base_pkt.ident + "'" 31 | self.tag = base_pkt.tag 32 | 33 | self.frozen = True 34 | 35 | def encode(self): 36 | return self.to + "<" + self.via + "<" + self.fr0m + " " + \ 37 | ("%d" % self.ttl) + " " + self.msg 38 | 39 | def decode(s): 40 | raise Exception("TODO") 41 | 42 | def __len__(self): 43 | return len(self.encode()) 44 | 45 | def decrement_ttl(self): 46 | return Packet(self.to, self.via, self.fr0m, self.ttl - 1, self.msg, self) 47 | 48 | def update_via(self, via): 49 | return Packet(self.to, via, self.fr0m, self.ttl, self.msg, self) 50 | 51 | def replace_msg(self, msg): 52 | return Packet(self.to, self.via, self.fr0m, self.ttl, msg, self) 53 | 54 | def __eq__(self, other): 55 | raise Exception("Packets cannot be compared") 56 | 57 | def __ne__(self, other): 58 | raise Exception("Packets cannot be compared") 59 | 60 | # Pseudo encoding for packet equality test 61 | def meaning(self): 62 | return self.to + "<" + self.fr0m + " " + self.msg 63 | 64 | # "Equal" in terms of content 65 | def equal(self, other): 66 | return self.meaning() == other.meaning() 67 | 68 | def myrepr(self): 69 | if self.via: 70 | return "pkt %s < %s < %s ttl %d id %s msg %s" % \ 71 | (self.to, self.via, self.fr0m, self.ttl, self.ident, self.msg) 72 | return "pkt %s << %s ttl %d id %s msg %s" % \ 73 | (self.to, self.fr0m, self.ttl, self.ident, self.msg) 74 | 75 | def __repr__(self): 76 | return self.myrepr() 77 | 78 | def __str__(self): 79 | return self.myrepr() 80 | 81 | def __setattr__(self, *args): 82 | if 'frozen' not in self.__dict__ or not self.frozen: 83 | return super.__setattr__(self, *args) 84 | raise NotImplementedError 85 | 86 | def __delattr__(self, *ignored): 87 | raise NotImplementedError 88 | -------------------------------------------------------------------------------- /_pocs_/lora32u4_sender/lora32u4_sender.ino: -------------------------------------------------------------------------------- 1 | // Use a LoRa32u4 as beacon sender for tests 2 | // Even though the LoRa32u4 has a better crystal than TTGO 3 | // and could go down to 31.25kHz bandwidth, the 32u4 can't 4 | // even load a program with proper packet encoding and decoding, 5 | // so we need to make veeeery simple to even use 32u4 as a 6 | // simple beacon for distance reach tests :( 7 | 8 | #include // include libraries 9 | #include 10 | #include 11 | 12 | const int csPin = 8; // LoRa radio chip select 13 | const int resetPin = 4; // LoRa radio reset 14 | const int irqPin = 7; // change for your board; must be a hardware interrupt pin 15 | 16 | long int msgCount = 0; // count of outgoing messages 17 | long lastSendTime = millis(); // last send time 18 | int interval = 10000; 19 | 20 | #define POWER 20 // dBm 21 | #define PABOOST 1 22 | 23 | void setup() { 24 | pinMode(LED_BUILTIN, OUTPUT); 25 | digitalWrite(LED_BUILTIN, LOW); 26 | 27 | Serial.begin(9600); 28 | Serial.println("LoRa"); 29 | LoRa.setPins(csPin, resetPin, irqPin); 30 | 31 | if (!LoRa.begin(916750000)) { 32 | Serial.println("LoRa init failed. Check your connections."); 33 | while (true); 34 | } 35 | 36 | LoRa.setTxPower(POWER, PABOOST); 37 | LoRa.setSpreadingFactor(9); 38 | LoRa.setSignalBandwidth(62500); 39 | LoRa.setCodingRate4(5); 40 | LoRa.disableCrc(); 41 | 42 | Serial.println("LoRa init succeeded."); 43 | } 44 | 45 | void loop() { 46 | if (millis() - lastSendTime > interval) { 47 | digitalWrite(LED_BUILTIN, HIGH); 48 | Serial.println("Preparing"); 49 | sendMessage(); 50 | lastSendTime = millis(); 51 | interval = 10000; 52 | } 53 | } 54 | 55 | static const int MSGSIZE = 80; 56 | static const int REDUNDANCY = 20; 57 | RS::ReedSolomon rs; 58 | unsigned char message[MSGSIZE]; 59 | unsigned char encoded[MSGSIZE + REDUNDANCY]; 60 | 61 | void sendMessage() { 62 | digitalWrite(LED_BUILTIN, LOW); 63 | 64 | String msg = "QB 12 | #include 13 | #include "Vector.h" 14 | #include "Dict.h" 15 | #include "Task.h" 16 | #include "Params.h" 17 | #include "Callsign.h" 18 | #include "LoRaL2/LoRaL2.h" 19 | 20 | #define MAX_PACKET_ID 9999 21 | 22 | class L7Protocol; 23 | class L4Protocol; 24 | class Modifier; 25 | class Packet; 26 | 27 | struct Peer { 28 | Peer(int rssi, int64_t timestamp); 29 | Peer(); 30 | int rssi; 31 | int64_t timestamp; 32 | }; 33 | 34 | struct RecvLogItem { 35 | RecvLogItem(int rssi, int64_t timestamp); 36 | RecvLogItem(); 37 | int rssi; 38 | int64_t timestamp; 39 | }; 40 | 41 | class Network: public LoRaL2Observer { 42 | public: 43 | Network(); 44 | virtual ~Network(); 45 | 46 | Callsign me() const; 47 | bool am_i_repeater() const; 48 | uint32_t send(const Callsign &to, Params params, const Buffer& msg); 49 | void run_tasks(int64_t); 50 | const Dict& neighbors() const; 51 | const Dict& repeaters() const; 52 | const Dict& peers() const; 53 | static uint32_t fudge(uint32_t avg, double fudge); 54 | static Buffer gen_random_token(int); 55 | size_t max_payload() const; 56 | 57 | // publicised to bridge with uncoupled code 58 | virtual void recv(LoRaL2Packet *); 59 | size_t get_last_pkt_id() const; 60 | 61 | // publicised to be called by protocols 62 | void schedule(Task*); 63 | 64 | // Called by each Protocol or Modifier subclass constructor. 65 | // Never call this yourself. 66 | void add_l7protocol(L7Protocol*); 67 | void add_l4protocol(L4Protocol*); 68 | void add_modifier(Modifier*); 69 | 70 | // publicised to be called by Tasks 71 | int64_t tx(const Buffer&); 72 | void route(Ptr, bool, int64_t); 73 | int64_t clean_recv_log(int64_t); 74 | int64_t clean_neigh(int64_t); 75 | 76 | // publicised for testing purposes 77 | TaskManager& _task_mgr(); 78 | Dict& _neighbors(); 79 | Dict& _repeaters(); 80 | Dict& _peers(); 81 | Dict& _recv_log(); 82 | 83 | private: 84 | void recv(Ptr pkt); 85 | size_t get_next_pkt_id(); 86 | void update_peerlist(int64_t, const Ptr &); 87 | 88 | Callsign my_callsign; 89 | uint32_t repeater_function_activated; 90 | 91 | Ptr transport; 92 | TaskManager task_mgr; 93 | Dict neigh; 94 | Dict reptr; 95 | Dict peerlist; 96 | Dict recv_log; 97 | size_t last_pkt_id; 98 | Vector< Ptr > l7protocols; 99 | Vector< Ptr > l4protocols; 100 | Vector< Ptr > modifiers; 101 | }; 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /TNC.md: -------------------------------------------------------------------------------- 1 | # TNC mode 2 | 3 | The default mode of command-line interface (CLI) is targeted at humans 4 | interacting directly with a LoRaMaDoR station via serial or Telnet. 5 | It has some amenities that are great for interactive sessions but 6 | inadequate for a parser e.g. when the station is to be controlled by 7 | a computer. 8 | 9 | Also, the interactive mode is not 8-bit clean due to special handling 10 | of control chars and Telnet char sequences. 11 | 12 | To remedy this, the CLI can be put in "TNC" mode using the !tnc 13 | command. In this mode, all RX data follows a definite format that is 14 | easier to parse. 15 | 16 | For now, only some test routines (Python scripts in test/ folder) use 17 | the TNC mode, and are practical examples. In time we will develop 18 | modules that will use TNC in real-world scenarios e.g. collector of 19 | sensor data, Internet repeater, Internet gateway, etc. 20 | 21 | In TNC mode, every line sent from station starts with a label, and 22 | ends with CR+LF. The prefix labels are: 23 | 24 | "debug: " for debug messages. In TNC mode, debug mode is always active, 25 | so there is no need to call !debug. In real-world usage, the debug 26 | messages would not be parsed, but should be logged or stored to aid 27 | any troubleshooting. 28 | 29 | "cli: " for CLI messages e.g. the return of interactive commands like 30 | !wifi, !neigh, etc. Their handling should be probably the same as 31 | debug messages. 32 | 33 | "net: " for out-of-band messages related to network e.g. report that 34 | a packet was sent with ID #, or the packet was not sent due to an error. 35 | Depending on the case, the payload of such messages will need to be 36 | parsed. 37 | 38 | "callsign: " for the first CLI message when the station is started up. 39 | Followed by the configured callsign plus some additional info. 40 | 41 | "pkrx: " when a packet is received. This prefix is exclusive to TNC 42 | mode. The packet contents are encoded in human-readable hexadecimal 43 | digits, no spaces between bytes. 44 | 45 | Any other message lines with different prefixes can be safely be ignored 46 | by the host. 47 | 48 | The host sends commands to the controller as lines terminated by CR 49 | (following the Telnet convention). All commands work as usual. 50 | 51 | Packets can be sent in two ways: 52 | 53 | a) without a prefix, in plain text, starting with the destination station 54 | as if a human was typing it; and 55 | 56 | b) using the !pktx command followed by the packet encoded as human-readable 57 | hexadecimal digits. 58 | 59 | ## 8-bit clean packet exchange 60 | 61 | Together, the !pktx command and the pkrx: prefix establish an 8-bit clean 62 | method of exchanging packets over a potentially ASCII-only channel. 63 | 64 | Note that layer-2 (FEC and encryption, if activated in LoRaL2) is still handled within the 65 | microcontroller, even in TNC mode. The host only sees valid, layer-3 66 | packets. It does not get "raw" packets. 67 | 68 | Likewise, packets sent over !pktx are not blindly transmitted. Invalid 69 | packets are rejected, and ID stamping, HMAC calculation, etc. are 70 | still carried out by the microcontroller. 71 | -------------------------------------------------------------------------------- /_pocs_/ttgo/ttgo.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "SSD1306.h" 6 | #include "images.h" 7 | 8 | // Pin definetion of WIFI LoRa 32 9 | // HelTec AutoMation 2017 support@heltec.cn 10 | #define SCK 5 // GPIO5 -- SX127x's SCK 11 | #define MISO 19 // GPIO19 -- SX127x's MISO 12 | #define MOSI 27 // GPIO27 -- SX127x's MOSI 13 | #define SS 18 // GPIO18 -- SX127x's CS 14 | #define RST 14 // GPIO14 -- SX127x's RESET 15 | #define DI00 26 // GPIO26 -- SX127x's IRQ(Interrupt Request) 16 | 17 | #define BAND 916750000 //you can set band here directly,e.g. 868E6,915E6 18 | #define POWER 20 // dBm 19 | #define PABOOST 1 20 | 21 | long int msgCount = 0; 22 | long lastSendTime = millis(); 23 | 24 | SSD1306 display(0x3c, 4, 15); 25 | String rssi = "RSSI --"; 26 | String packSize = "--"; 27 | String packet ; 28 | 29 | void setup() 30 | { 31 | Serial.begin(9600); 32 | pinMode(16,OUTPUT); 33 | pinMode(25,OUTPUT); 34 | 35 | digitalWrite(16, LOW); // set GPIO16 low to reset OLED 36 | delay(50); 37 | digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high 38 | 39 | SPI.begin(SCK,MISO,MOSI,SS); 40 | LoRa.setPins(SS,RST,DI00); 41 | 42 | display.init(); 43 | display.flipScreenVertically(); 44 | display.setFont(ArialMT_Plain_10); 45 | 46 | if (!LoRa.begin(BAND)) { 47 | display.drawString(0, 0, "Starting LoRa failed!"); 48 | display.display(); 49 | while (1); 50 | } 51 | 52 | LoRa.setTxPower(POWER, PABOOST); 53 | LoRa.setSpreadingFactor(9); 54 | LoRa.setSignalBandwidth(62500); 55 | LoRa.setCodingRate4(5); 56 | LoRa.disableCrc(); 57 | 58 | display.drawString(0, 0, "LoRa ok"); 59 | display.display(); 60 | } 61 | 62 | static const int MSGSIZE = 80; 63 | static const int REDUNDANCY = 20; 64 | RS::ReedSolomon rs; 65 | unsigned char message[MSGSIZE]; 66 | unsigned char encoded[MSGSIZE + REDUNDANCY]; 67 | 68 | void loop() { 69 | if (millis() > (lastSendTime + 5000)) { 70 | sendMessage(); 71 | lastSendTime = millis(); 72 | } 73 | } 74 | 75 | void sendMessage() { 76 | String msg = "QB 8 | 9 | // Class that validates and encapsulates a callsign. 10 | 11 | Callsign::Callsign() 12 | { 13 | valid = false; 14 | } 15 | 16 | Callsign::Callsign(Buffer c) 17 | { 18 | c.uppercase(); 19 | c.strip(); 20 | if (! check(c)) { 21 | // Invalid callsign is flagged, not stored 22 | valid = false; 23 | return; 24 | } 25 | name = c; 26 | valid = true; 27 | } 28 | 29 | bool Callsign::operator==(Buffer other) const 30 | { 31 | other.uppercase(); 32 | return valid && name == other; 33 | } 34 | 35 | bool Callsign::operator==(const Callsign& other) const 36 | { 37 | return valid && other.valid && name == other.name; 38 | } 39 | 40 | /* QL = wildcard for localhost */ 41 | bool Callsign::is_lo() const 42 | { 43 | return valid && name == "QL"; 44 | } 45 | 46 | /* QB, QR, QC are broadcast pseudo-callsigns, QB/QR are used by beacon */ 47 | bool Callsign::is_bcast() const 48 | { 49 | return valid && (name == "QB" || name == "QC" || name == "QR"); 50 | } 51 | 52 | bool Callsign::is_repeater() const 53 | { 54 | return valid && name == "QR"; 55 | } 56 | 57 | /* QB/QR is reserved for automatic beaconing */ 58 | bool Callsign::is_reserved() const 59 | { 60 | return valid && (name == "QB" || name == "QR"); 61 | } 62 | 63 | bool Callsign::is_q() const 64 | { 65 | return valid && name.startsWith("Q"); 66 | } 67 | 68 | Callsign::operator Buffer() const 69 | { 70 | return name; 71 | } 72 | 73 | bool Callsign::is_valid() const 74 | { 75 | return valid; 76 | } 77 | 78 | bool Callsign::check(const Buffer &sbuf) 79 | { 80 | size_t length = sbuf.length(); 81 | const char *s = sbuf.c_str(); 82 | 83 | if (length < 2) { 84 | return false; 85 | } 86 | 87 | char c0 = s[0]; 88 | char c1 = s[1]; 89 | 90 | if (c0 < 'A' || c0 > 'Z') { 91 | return false; 92 | } 93 | 94 | if (c0 == 'Q') { 95 | if (length != 2 || c1 < 'A' || c1 > 'Z') { 96 | return false; 97 | } 98 | } 99 | 100 | const char *ssid_delim = strchr(s, '-'); 101 | size_t prefix_length; 102 | 103 | if (ssid_delim) { 104 | const char *ssid = ssid_delim + 1; 105 | prefix_length = ssid_delim - s; 106 | int ssid_length = length - 1 - prefix_length; 107 | if (ssid_length <= 0 || ssid_length > 2) { 108 | return false; 109 | } 110 | bool sig = false; 111 | for (int i = 0; i < ssid_length; ++i) { 112 | char c = ssid[i]; 113 | if (c < '0' || c > '9') { 114 | return false; 115 | } 116 | if (c != '0') { 117 | // found significant digit 118 | sig = true; 119 | } else if (! sig) { 120 | // non-significant 0 121 | return false; 122 | } 123 | } 124 | } else { 125 | prefix_length = length; 126 | } 127 | 128 | if (prefix_length > 7 || prefix_length < 2) { 129 | return false; 130 | } 131 | 132 | for (size_t i = 1; i < prefix_length; ++i) { 133 | char c = s[i]; 134 | if (c >= '0' && c <= '9') { 135 | } else if (c >= 'A' && c <= 'Z') { 136 | } else { 137 | return false; 138 | } 139 | } 140 | 141 | return true; 142 | } 143 | 144 | -------------------------------------------------------------------------------- /_pocs_/lora32u4_receiver/lora32u4_receiver.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // uncomment the section corresponding to your board 6 | // BSFrance 2017 contact@bsrance.fr 7 | 8 | // NB : on this library with Atmega32u4 IRQ handling may not work 9 | 10 | // //LoR32u4 433MHz V1.0 (white board) 11 | // #define SCK 15 12 | // #define MISO 14 13 | // #define MOSI 16 14 | // #define SS 1 15 | // #define RST 4 16 | // #define DI0 7 17 | // #define BAND 433E6 18 | // #define PABOOST true 19 | 20 | // //LoR32u4 433MHz V1.2 (white board) 21 | // #define SCK 15 22 | // #define MISO 14 23 | // #define MOSI 16 24 | // #define SS 8 25 | // #define RST 4 26 | // #define DI0 7 27 | // #define BAND 433E6 28 | // #define PABOOST true 29 | 30 | //LoR32u4II 868MHz or 915MHz (black board) 31 | #define SCK 15 32 | #define MISO 14 33 | #define MOSI 16 34 | #define SS 8 35 | #define RST 4 36 | #define DI0 7 37 | 38 | #define BAND 916750000 39 | #define POWER 20 40 | #define PABOOST 1 41 | 42 | int led = LOW; 43 | 44 | void setup() { 45 | pinMode(LED_BUILTIN, OUTPUT); 46 | digitalWrite(LED_BUILTIN, led); 47 | 48 | Serial.begin(9600); 49 | while (!Serial); 50 | 51 | LoRa.setPins(SS,RST,DI0); 52 | 53 | if (!LoRa.begin(BAND)) { 54 | Serial.println("Starting LoRa failed!"); 55 | while (1); 56 | } 57 | 58 | LoRa.setTxPower(POWER, PABOOST); 59 | LoRa.setSignalBandwidth(62500); 60 | LoRa.setSpreadingFactor(9); 61 | LoRa.setCodingRate4(5); 62 | LoRa.disableCrc(); 63 | 64 | // register the receive callback 65 | LoRa.onReceive(onReceive); 66 | 67 | // put the radio into receive mode 68 | LoRa.receive(); 69 | Serial.println("Waiting..."); 70 | } 71 | 72 | void loop() { 73 | // do nothing 74 | } 75 | 76 | static const int MSGSIZE = 80; 77 | static const int REDUNDANCY = 20; 78 | RS::ReedSolomon rs; 79 | char decoded[MSGSIZE]; 80 | char encoded[MSGSIZE + REDUNDANCY]; 81 | char punctured[MSGSIZE + REDUNDANCY]; 82 | 83 | void onReceive(int plen) { 84 | Serial.println("Receiving..."); 85 | led = ! led; 86 | digitalWrite(LED_BUILTIN, led); 87 | // received a packet 88 | 89 | memset(encoded, 0, sizeof(encoded)); 90 | memset(decoded, 0, sizeof(decoded)); 91 | memset(punctured, 0, sizeof(punctured)); 92 | 93 | for (int i = 0; i < plen && i < sizeof(punctured); i++) { 94 | punctured[i] = LoRa.read(); 95 | } 96 | 97 | memcpy(encoded, punctured, plen - REDUNDANCY); 98 | memcpy(encoded + MSGSIZE, punctured + plen - REDUNDANCY, REDUNDANCY); 99 | 100 | // print RSSI of packet 101 | Serial.print("pkt len "); 102 | Serial.print(plen); 103 | Serial.print(" RSSI "); 104 | Serial.println(LoRa.packetRssi()); 105 | if (rs.Decode(encoded, decoded)) { 106 | Serial.println("\tPacket corrupted"); 107 | } else { 108 | Serial.print("\tGood packet"); 109 | String msg = decoded; 110 | Serial.print(" len "); 111 | Serial.print(plen - REDUNDANCY); 112 | Serial.print(" msg "); 113 | Serial.println(msg); 114 | } 115 | 116 | } 117 | 118 | -------------------------------------------------------------------------------- /src/Vector.h: -------------------------------------------------------------------------------- 1 | // 2 | // Based on Vector frm 3 | // Zac Staples 4 | // zacstaples (at) mac (dot) com 5 | // 6 | // Light implementation of a vector or list 7 | 8 | #ifndef __VECTOR_H 9 | #define __VECTOR_H 10 | 11 | #include 12 | #include 13 | 14 | template 15 | class Vector { 16 | size_t sz; 17 | T** elem; 18 | size_t space; 19 | 20 | public: 21 | Vector() : sz(0), elem(0), space(0) {} 22 | Vector(const int s) : sz(0) { 23 | reserve(s); 24 | } 25 | 26 | Vector& operator=(const Vector&); 27 | Vector& operator=(Vector&&); 28 | Vector(const Vector&); 29 | Vector(Vector&&); 30 | 31 | ~Vector() { 32 | clear(); 33 | } 34 | 35 | void clear(); 36 | T& operator[](size_t n) { return *elem[n]; } 37 | const T& operator[](size_t n) const { return *elem[n]; } 38 | 39 | size_t count() const { return sz; } 40 | size_t capacity() const { return space; } 41 | 42 | void reserve(size_t newalloc); 43 | void push_back(const T& val); 44 | void remov(size_t pos); 45 | void insert(size_t pos, const T& val); 46 | }; 47 | 48 | template 49 | Vector& Vector::operator=(const Vector& a) { 50 | if (this==&a) return *this; 51 | 52 | clear(); 53 | 54 | // new array 55 | elem = new T*[a.count()]; 56 | space = sz = a.count(); 57 | 58 | // copy elements 59 | for(size_t i=0; i < sz; ++i) { 60 | elem[i] = new T(a[i]); 61 | } 62 | 63 | return *this; 64 | } 65 | 66 | template 67 | Vector::Vector(const Vector& a) { 68 | if (this==&a) return; 69 | 70 | elem = new T*[a.count()]; 71 | space = sz = a.count(); 72 | 73 | // copy elements 74 | for(size_t i=0; i < sz; ++i) { 75 | elem[i] = new T(a[i]); 76 | } 77 | } 78 | 79 | template 80 | Vector::Vector(Vector&& a) { 81 | elem = a.elem; 82 | space = a.space; 83 | sz = a.sz; 84 | 85 | a.elem = 0; 86 | a.space = 0; 87 | a.sz = 0; 88 | } 89 | 90 | template 91 | void Vector::clear() 92 | { 93 | for (size_t i=0; i 100 | Vector& Vector::operator=(Vector&& a) { 101 | if(this==&a) return *this; 102 | 103 | clear(); 104 | 105 | elem = a.elem; 106 | sz = a.sz; 107 | space = a.sz; 108 | 109 | a.elem = 0; 110 | a.sz = 0; 111 | a.space = 0; 112 | } 113 | 114 | template void Vector::reserve(size_t newalloc){ 115 | if(newalloc <= space) return; 116 | 117 | T** p = new T*[newalloc]; 118 | if (elem) { 119 | memcpy(p, elem, sizeof(T*) * sz); 120 | delete [] elem; 121 | } 122 | elem = p; 123 | space = newalloc; 124 | } 125 | 126 | template void 127 | Vector::remov(size_t pos){ 128 | if (pos >= 0 && pos < sz) { 129 | delete elem[pos]; 130 | --sz; 131 | // move pointers 132 | for (size_t i = pos; i < sz; ++i) { 133 | elem[i] = elem[i+1]; 134 | } 135 | } 136 | } 137 | 138 | template 139 | void Vector::push_back(const T& val){ 140 | if(space == 0) reserve(4); //start small 141 | else if(sz==space) reserve(2*space); 142 | elem[sz] = new T(val); 143 | ++sz; 144 | } 145 | 146 | template 147 | void Vector::insert(size_t pos, const T& val){ 148 | if (pos >= sz || pos < 0) { 149 | push_back(val); 150 | return; 151 | } 152 | 153 | if(space == 0) reserve(4); 154 | else if(sz==space) reserve(2*space); 155 | 156 | // move pointers 157 | for (size_t i = sz; i > pos; --i) { 158 | elem[i] = elem[i-1]; 159 | } 160 | ++sz; 161 | elem[pos] = new T(val); 162 | } 163 | 164 | #endif 165 | -------------------------------------------------------------------------------- /test/testnet2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Network.h" 11 | #include "ArduinoBridge.h" 12 | #include "Timestamp.h" 13 | #include "NVRAM.h" 14 | #include "CLI.h" 15 | #include "Serial.h" 16 | #include "Console.h" 17 | 18 | // Radio emulation hooks in FakeArduino.cpp 19 | int lora_emu_socket(); 20 | void lora_emu_socket_coverage(int coverage); 21 | void lora_emu_rx(); 22 | 23 | Ptr Net(0); 24 | 25 | int main(int argc, char* argv[]) 26 | { 27 | if (argc < 7) { 28 | printf("Specify, callsign, repeater mode, coverage bitmask, " 29 | "HMAC PSK, serial emulation port and crypto PSK\n"); 30 | return 1; 31 | } 32 | 33 | int repeater = atoi(argv[2]) ? 1 : 0; 34 | int coverage = atoi(argv[3]) & 0xff; 35 | int serialemu = atoi(argv[5]); 36 | if (repeater < 0 || repeater > 1) { 37 | printf("repeater mode must be 0 or 1\n"); 38 | } 39 | if (!coverage) { 40 | printf("Coverage bitmask must not be 0\n"); 41 | return 1; 42 | } 43 | 44 | Serial.emu_port(serialemu); 45 | lora_emu_socket_coverage(coverage); 46 | 47 | Callsign cs(argv[1]); 48 | if (!cs.is_valid()) { 49 | printf("Callsign invalid.\n"); 50 | return 2; 51 | } 52 | 53 | char cli_cmd[100]; 54 | 55 | sprintf(cli_cmd, "!hmacpsk %s\r", argv[4]); 56 | cli_simtype(cli_cmd); 57 | sprintf(cli_cmd, "!cryptopsk %s\r", argv[6]); 58 | cli_simtype(cli_cmd); 59 | sprintf(cli_cmd, "!repeater %d\r", repeater); 60 | cli_simtype(cli_cmd); 61 | sprintf(cli_cmd, "!callsign %s\r", argv[1]); 62 | cli_simtype(cli_cmd); 63 | 64 | Net = Ptr(new Network()); 65 | console_setup(Net); 66 | cli_simtype("!beacon 30\r"); 67 | cli_simtype("!debug\r"); 68 | cli_simtype("!hmacpsk\r"); 69 | cli_simtype("!cryptopsk\r"); 70 | cli_simtype("!beacon1st 10\r"); 71 | 72 | // Main loop simulation (in Arduino, would be a busy loop) 73 | int s = lora_emu_socket(); 74 | int s2 = Serial.emu_listen_socket(); 75 | 76 | while (true) { 77 | Ptr tsk = Net->_task_mgr().next_task(); 78 | 79 | fd_set set; 80 | FD_ZERO(&set); 81 | FD_SET(s, &set); 82 | if (s2 >= 0) { 83 | FD_SET(s2, &set); 84 | } 85 | // may change because this is the conn socket 86 | int s3 = Serial.emu_conn_socket(); 87 | if (s3 >= 0) { 88 | FD_SET(s3, &set); 89 | } 90 | 91 | struct timeval timeout; 92 | struct timeval* ptimeout = 0; 93 | if (tsk) { 94 | int64_t now = sys_timestamp(); 95 | int64_t to = tsk->next_run() - now; 96 | // printf("Timeout: %s %ld\n", tsk->get_name().c_str(), to); 97 | if (to < 0) { 98 | to = 0; 99 | } 100 | // 1-second timeout due to call to console_handle() 101 | to = 1; 102 | timeout.tv_sec = to / 1000; 103 | timeout.tv_usec = (to % 1000) * 1000; 104 | ptimeout = &timeout; 105 | } else { 106 | printf("No timeout = bug\n"); 107 | return 2; 108 | // timeout.tv_sec = 1; 109 | // timeout.tv_usec = 0; 110 | } 111 | ptimeout = &timeout; 112 | 113 | int sel = select(FD_SETSIZE, &set, NULL, NULL, ptimeout); 114 | if (sel < 0) { 115 | perror("select() failure"); 116 | return 1; 117 | } 118 | 119 | if (FD_ISSET(s, &set)) { 120 | lora_emu_rx(); 121 | } else { 122 | Net->run_tasks(sys_timestamp()); 123 | } 124 | if (s2 >= 0 && FD_ISSET(s2, &set)) { 125 | Serial.emu_listen_handle(); 126 | } 127 | if (s3 >= 0 && FD_ISSET(s3, &set)) { 128 | Serial.emu_conn_handle(); 129 | } 130 | console_handle(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Task.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | /* Abstract class for Task classes: delayed code execution 7 | * 8 | * The scheme assumes the existence of a monotonic clock like 9 | * Arduino's millis_nw(). Each task is associated with a future 10 | * clock value (timebase + offset) All tasks whose value is 11 | * less than millis_nw() * are run. 12 | * 13 | * Concrete Task classes implement run2(). This method may 14 | * return 0 (meaning the task is finished) or a positive offset 15 | * (meaning the task should be rescheduled to now + offset). 16 | * 17 | * The task manager lives inside the Network class, so the 18 | * main loop calls a Network method periodically, which 19 | * calls the task manager. 20 | */ 21 | 22 | #include "Task.h" 23 | #include "ArduinoBridge.h" 24 | #include "Timestamp.h" 25 | 26 | Task::Task(const char *name, int64_t offset): 27 | name(name), offset(offset), timebase(0) 28 | { 29 | } 30 | 31 | Task::~Task() 32 | { 33 | this->timebase = 0; 34 | } 35 | 36 | void Task::set_timebase(int64_t now) 37 | { 38 | this->timebase = now; 39 | } 40 | 41 | bool Task::cancelled() const 42 | { 43 | return this->timebase == 0; 44 | } 45 | 46 | int64_t Task::next_run() const 47 | { 48 | // logi("Timebase", this->timebase); 49 | // logi("Offset", this->offset); 50 | return this->timebase + this->offset; 51 | } 52 | 53 | bool Task::should_run(int64_t now) const 54 | { 55 | return ! this->cancelled() && this->next_run() < now; 56 | } 57 | 58 | bool Task::run(int64_t now) 59 | { 60 | // concrete routine returns new timeout (which could be random) 61 | this->offset = this->run2(now); 62 | // task cancelled by default, rebased by task mgr if necessary 63 | this->timebase = 0; 64 | return this->offset > 0; 65 | } 66 | 67 | /* 68 | Buffer Task::get_name() const 69 | { 70 | return name; 71 | } 72 | */ 73 | 74 | TaskManager::TaskManager() {} 75 | 76 | TaskManager::~TaskManager() 77 | { 78 | stop(); 79 | } 80 | 81 | void TaskManager::stop() 82 | { 83 | tasks.clear(); 84 | } 85 | 86 | void TaskManager::schedule(Ptr task) 87 | { 88 | Ptr etask = task; 89 | tasks.push_back(etask); 90 | etask->set_timebase(sys_timestamp()); 91 | } 92 | 93 | Ptr TaskManager::next_task() const 94 | { 95 | Ptr ret(0); 96 | int64_t task_time = sys_timestamp() + 60 * 1000; 97 | for (size_t i = 0 ; i < tasks.count(); ++i) { 98 | Ptr t = tasks[i]; 99 | if (! t->cancelled()) { 100 | int64_t nr = t->next_run(); 101 | if (nr < task_time) { 102 | task_time = nr; 103 | ret = t; 104 | } 105 | } 106 | } 107 | return ret; 108 | } 109 | 110 | /* 111 | void TaskManager::cancel(const Task* task) 112 | { 113 | for (size_t i = 0 ; i < tasks.count(); ++i) { 114 | if (tasks[i].id() == task) { 115 | tasks.remov(i); 116 | break; 117 | } 118 | } 119 | } 120 | */ 121 | 122 | void TaskManager::run(int64_t now) 123 | { 124 | bool dirty = false; 125 | 126 | for (size_t i = 0 ; i < tasks.count(); ++i) { 127 | Ptr t = tasks[i]; 128 | if (t->should_run(now)) { 129 | bool stay = t->run(now); 130 | if (stay) { 131 | // reschedule 132 | t->set_timebase(sys_timestamp()); 133 | } else { 134 | // task list must be pruned 135 | dirty = true; 136 | } 137 | } 138 | } 139 | 140 | while (dirty) { 141 | dirty = false; 142 | for (size_t i = 0 ; i < tasks.count(); ++i) { 143 | Ptr t = tasks[i]; 144 | if (t->cancelled()) { 145 | tasks.remov(i); 146 | dirty = true; 147 | break; 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/NVRAM.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2020 PU5EPX 4 | */ 5 | 6 | #include 7 | #ifdef UNDER_TEST 8 | #include "Preferences.h" 9 | #else 10 | #include 11 | #endif 12 | #include "Buffer.h" 13 | #include "Callsign.h" 14 | #include "Network.h" 15 | #include "NVRAM.h" 16 | #include "HMACKeys.h" 17 | 18 | extern Preferences prefs; 19 | 20 | static const char* chapter = "LoRaMaDoR"; 21 | 22 | uint32_t arduino_nvram_id_load() 23 | { 24 | prefs.begin(chapter); 25 | uint32_t id = prefs.getUInt("lastid"); 26 | prefs.end(); 27 | 28 | if (id <= 0 || id > MAX_PACKET_ID) { 29 | id = 1; 30 | } 31 | 32 | return id; 33 | } 34 | 35 | void arduino_nvram_id_save(uint32_t id) 36 | { 37 | prefs.begin(chapter, false); 38 | prefs.putUInt("lastid", id); 39 | prefs.end(); 40 | 41 | } 42 | 43 | uint32_t arduino_nvram_repeater_load() 44 | { 45 | prefs.begin(chapter); 46 | uint32_t r = prefs.getUInt("repeater"); 47 | prefs.end(); 48 | 49 | return r; 50 | } 51 | 52 | void arduino_nvram_repeater_save(uint32_t r) 53 | { 54 | prefs.begin(chapter, false); 55 | prefs.putUInt("repeater", r); 56 | prefs.end(); 57 | } 58 | 59 | uint32_t arduino_nvram_beacon_load() 60 | { 61 | prefs.begin(chapter); 62 | uint32_t b = prefs.getUInt("beacon"); 63 | prefs.end(); 64 | 65 | if (b < 2 || b > 600) { 66 | b = 600; 67 | } 68 | 69 | return b; 70 | } 71 | 72 | void arduino_nvram_beacon_save(uint32_t b) 73 | { 74 | if (b < 2 || b > 600) { 75 | b = 600; 76 | } 77 | 78 | prefs.begin(chapter, false); 79 | prefs.putUInt("beacon", b); 80 | prefs.end(); 81 | } 82 | 83 | Callsign arduino_nvram_callsign_load() 84 | { 85 | char candidate[12]; 86 | prefs.begin(chapter); 87 | // len includes \0 88 | size_t len = prefs.getString("callsign", candidate, 11); 89 | prefs.end(); 90 | 91 | Callsign cs; 92 | 93 | if (len <= 1) { 94 | cs = Callsign("FIXMEE-1"); 95 | } else { 96 | cs = Callsign(Buffer(candidate, len - 1)); 97 | if (!cs.is_valid()) { 98 | cs = Callsign("FIXMEE-2"); 99 | } 100 | } 101 | 102 | return cs; 103 | } 104 | 105 | void arduino_nvram_callsign_save(const Callsign &new_callsign) 106 | { 107 | prefs.begin(chapter, false); 108 | prefs.putString("callsign", Buffer(new_callsign).c_str()); 109 | prefs.end(); 110 | } 111 | 112 | Buffer arduino_nvram_hmac_psk_load() 113 | { 114 | char candidate[33]; 115 | prefs.begin(chapter); 116 | // len includes \0 117 | size_t len = prefs.getString("psk", candidate, 33); 118 | prefs.end(); 119 | 120 | if (len <= 1) { 121 | return ""; 122 | } 123 | return Buffer(candidate, len - 1); 124 | } 125 | 126 | void arduino_nvram_hmac_psk_save(const Buffer &b) 127 | { 128 | prefs.begin(chapter, false); 129 | if (b.length() == 0) { 130 | prefs.putString("psk", ""); 131 | } else { 132 | prefs.putString("psk", HMACKeys::hash_key(b).c_str()); 133 | } 134 | prefs.end(); 135 | HMACKeys::invalidate(); 136 | } 137 | 138 | // used by Wi-Fi SSID and password 139 | void arduino_nvram_save(const char *key, const Buffer& value) 140 | { 141 | prefs.begin(chapter, false); 142 | prefs.putString(key, value.c_str()); 143 | prefs.end(); 144 | } 145 | 146 | // used by Wi-Fi SSID and password 147 | Buffer arduino_nvram_load(const char *key) 148 | { 149 | char candidate[65]; 150 | prefs.begin(chapter); 151 | // len includes \0 152 | size_t len = prefs.getString(key, candidate, 65); 153 | prefs.end(); 154 | 155 | if (len <= 1) { 156 | return "None"; 157 | } 158 | return Buffer(candidate, len - 1); 159 | } 160 | 161 | void arduino_nvram_clear_all() 162 | { 163 | prefs.begin(chapter, false); 164 | prefs.clear(); 165 | prefs.end(); 166 | } 167 | -------------------------------------------------------------------------------- /test/Serial.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "Serial.h" 11 | #include "Buffer.h" 12 | 13 | // This is an emulation of Arduino Serial module. Connect with Telnet 14 | // to communicate with the "serial" port. 15 | 16 | // Do not confuse this testing device with the Telnet implementation in 17 | // main/Telnet.h, which only works in Arduino and independently of the 18 | // serial port (one can be connected to Arduino via serial and via 19 | // Telnet at the same time). 20 | 21 | SerialClass Serial; 22 | 23 | static int port = 0; 24 | static int listen_socket = -1; 25 | static int conn_socket = -1; 26 | static Buffer readbuf; 27 | 28 | int SerialClass::available() 29 | { 30 | return readbuf.length(); 31 | } 32 | 33 | int SerialClass::availableForWrite() 34 | { 35 | // IMHO no need for write logic because it is just for testing 36 | return 999; 37 | } 38 | 39 | char SerialClass::read() 40 | { 41 | char c = 0; 42 | if (!readbuf.empty()) { 43 | c = readbuf.charAt(0); 44 | readbuf.cut(1); 45 | } 46 | return c; 47 | } 48 | 49 | void SerialClass::write(const uint8_t *data, int len) 50 | { 51 | if (conn_socket < 0) { 52 | char *tmp = (char*) calloc(1, len + 1); 53 | memcpy(tmp, data, len); 54 | printf("%s", tmp); 55 | free(tmp); 56 | } else { 57 | printf("fake: emu writing to telnet connection\n"); 58 | ::write(conn_socket, (const void*) data, len); 59 | } 60 | } 61 | 62 | int SerialClass::emu_conn_socket() 63 | { 64 | return conn_socket; 65 | } 66 | 67 | int SerialClass::emu_listen_socket() 68 | { 69 | return listen_socket; 70 | } 71 | 72 | void SerialClass::emu_conn_handle() 73 | { 74 | if (conn_socket < 0) return; 75 | printf("fake: emu reading from telnet connection\n"); 76 | char buf[1500]; 77 | int x = ::read(conn_socket, buf, sizeof(buf)); 78 | if (x <= 0) { 79 | printf("fake: emu closed telnet connection\n"); 80 | close(conn_socket); 81 | conn_socket = -1; 82 | return; 83 | } 84 | readbuf += Buffer(buf, x); 85 | } 86 | 87 | void SerialClass::emu_listen_handle() 88 | { 89 | if (listen_socket < 0) return; 90 | printf("fake: emu accepting telnet connection\n"); 91 | struct sockaddr_in connaddr; 92 | unsigned int len = sizeof(connaddr); 93 | 94 | if (conn_socket >= 0) { 95 | printf("fake: emu closed old connection\n"); 96 | close(conn_socket); 97 | } 98 | 99 | conn_socket = accept(listen_socket, (struct sockaddr*) &connaddr, &len); 100 | if (conn_socket < 0) { 101 | printf("fake: emu server acccept failed...\n"); 102 | return; 103 | } 104 | } 105 | 106 | void SerialClass::emu_port(int p) 107 | { 108 | port = p; 109 | struct sockaddr_in servaddr; 110 | 111 | listen_socket = socket(AF_INET, SOCK_STREAM, 0); 112 | if (listen_socket == -1) { 113 | printf("fake: emu socket creation failed\n"); 114 | exit(1); 115 | } 116 | bzero(&servaddr, sizeof(servaddr)); 117 | 118 | servaddr.sin_family = AF_INET; 119 | servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 120 | servaddr.sin_port = htons(port); 121 | 122 | int x = 1; 123 | setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); 124 | x = 1; 125 | setsockopt(listen_socket, SOL_SOCKET, SO_REUSEPORT, &x, sizeof(x)); 126 | 127 | if ((bind(listen_socket, (struct sockaddr *)&servaddr, sizeof(servaddr))) != 0) { 128 | printf("fake: emu socket bind failed...\n"); 129 | exit(1); 130 | } 131 | 132 | if ((listen(listen_socket, 5)) != 0) { 133 | printf("fake: emu Listen failed...\n"); 134 | exit(1); 135 | } 136 | 137 | signal(SIGPIPE, SIG_IGN); 138 | printf("fake: emu telnet ok!\n"); 139 | } 140 | -------------------------------------------------------------------------------- /src/Dict.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Light implementation of an associative array 7 | 8 | #ifndef _ODICT_H 9 | #define _ODICT_H 10 | 11 | #include "Vector.h" 12 | #include "Buffer.h" 13 | 14 | template class Dict { 15 | public: 16 | Dict() {} 17 | 18 | Dict(const Dict& model) { 19 | _keys = model._keys; 20 | _values = model._values; 21 | } 22 | 23 | Dict& operator=(const Dict& model) { 24 | _keys = model._keys; 25 | _values = model._values; 26 | return *this; 27 | } 28 | 29 | ~Dict() {} 30 | 31 | bool has(const char* key) const { 32 | return indexOf(key) != -1; 33 | } 34 | 35 | bool has(const Buffer& key) const { 36 | return has(key.c_str()); 37 | } 38 | 39 | const T& get(const char* key) const { 40 | return _values[indexOf(key)]; 41 | } 42 | 43 | const T& get(const Buffer& key) const { 44 | return get(key.c_str()); 45 | } 46 | 47 | const T& operator[](const char* key) const { 48 | return _values[indexOf(key)]; 49 | } 50 | 51 | const T& operator[](const Buffer& key) const { 52 | return operator[](key.c_str()); 53 | } 54 | 55 | void remove(const char* key) { 56 | int i = indexOf(key); 57 | if (i < 0) { 58 | return; 59 | } 60 | _keys.remov((unsigned) i); 61 | _values.remov((unsigned) i); 62 | } 63 | 64 | void remove(const Buffer& key) { 65 | return remove(key.c_str()); 66 | } 67 | 68 | // due to limitations of C++, we can't avoid creating a new 69 | // empty element when an unknown subscript is request 70 | T& operator[](const char* key) { 71 | if (!has(key)) { 72 | put(key, T()); 73 | } 74 | return _values[indexOf(key)]; 75 | } 76 | 77 | T& operator[](const Buffer& key) { 78 | if (!has(key)) { 79 | put(key, T()); 80 | } 81 | return _values[indexOf(key)]; 82 | } 83 | 84 | bool put(const Buffer &akey, const T& value) { 85 | return put(akey.c_str(), value); 86 | } 87 | 88 | bool put(const char *key, const T& value) { 89 | int pos = indexOf(key); 90 | bool new_key = pos <= -1; 91 | 92 | if (new_key) { 93 | int insertion_pos = indexOf(key, 0, _keys.count(), false); 94 | _keys.insert(insertion_pos, key); 95 | _values.insert(insertion_pos, value); 96 | } else { 97 | _keys[pos] = key; 98 | _values[pos] = value; 99 | } 100 | 101 | return new_key; 102 | } 103 | 104 | size_t count() const { 105 | return _keys.count(); 106 | } 107 | 108 | const Vector& keys() const{ 109 | return _keys; 110 | } 111 | 112 | int indexOf(const char *key, size_t from, size_t to, bool exact) const { 113 | if ((to - from) <= 3) { 114 | // linear search 115 | for (size_t i = from; i < to; ++i) { 116 | int cmp = _keys[i].compareTo(key); 117 | if (cmp == 0) { 118 | // good for exact and non-exact queriers 119 | return (int) i; 120 | } else if ((! exact) && (cmp > 0)) { 121 | // non-exact, looks for insertion point 122 | return (int) i; 123 | } 124 | } 125 | 126 | return exact ? -1 : (int) to; 127 | } 128 | 129 | // recursive binary search 130 | size_t middle = (from + to) / 2; 131 | int cmp = _keys[middle].compareTo(key); 132 | if (cmp == 0) { 133 | return (int) middle; 134 | } else if (cmp > 0) { 135 | // middle key > our key, look into left 136 | return indexOf(key, from, middle, exact); 137 | } else { 138 | // middle key < our key, look into right 139 | return indexOf(key, middle + 1, to, exact); 140 | } 141 | 142 | return -1; 143 | } 144 | 145 | int indexOf(const Buffer &key) const { 146 | return indexOf(key.c_str()); 147 | } 148 | 149 | int indexOf(const char *key) const { 150 | return indexOf(key, 0, _keys.count(), true); 151 | } 152 | 153 | private: 154 | Vector _keys; 155 | Vector _values; 156 | }; 157 | 158 | #endif 159 | -------------------------------------------------------------------------------- /_pocs_/ttgo_sender/ttgo_sender.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This is a simple example show the LoRa sended data in OLED. 3 | 4 | The onboard OLED display is SSD1306 driver and I2C interface. In order to make the 5 | OLED correctly operation, you should output a high-low-high(1-0-1) signal by soft- 6 | ware to OLED's reset pin, the low-level signal at least 5ms. 7 | 8 | OLED pins to ESP32 GPIOs via this connecthin: 9 | OLED_SDA -- GPIO4 10 | OLED_SCL -- GPIO15 11 | OLED_RST -- GPIO16 12 | 13 | by Aaron.Lee from HelTec AutoMation, ChengDu, China 14 | 成都惠利特自动化科技有限公司 15 | www.heltec.cn 16 | 17 | this project also realess in GitHub: 18 | https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series 19 | */ 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "SSD1306.h" 25 | #include "images.h" 26 | 27 | // Pin definetion of WIFI LoRa 32 28 | // HelTec AutoMation 2017 support@heltec.cn 29 | #define SCK 5 // GPIO5 -- SX127x's SCK 30 | #define MISO 19 // GPIO19 -- SX127x's MISO 31 | #define MOSI 27 // GPIO27 -- SX127x's MOSI 32 | #define SS 18 // GPIO18 -- SX127x's CS 33 | #define RST 14 // GPIO14 -- SX127x's RESET 34 | #define DI00 26 // GPIO26 -- SX127x's IRQ(Interrupt Request) 35 | 36 | #define BAND 916750000 //you can set band here directly,e.g. 868E6,915E6 37 | #define POWER 2 // dBm 38 | #define PABOOST 1 39 | 40 | unsigned int counter = 0; 41 | 42 | SSD1306 display(0x3c, 4, 15); 43 | String rssi = "RSSI --"; 44 | String packSize = "--"; 45 | String packet ; 46 | 47 | void logo() 48 | { 49 | display.clear(); 50 | display.drawXbm(0,5,logo_width,logo_height,logo_bits); 51 | display.display(); 52 | } 53 | 54 | void setup() 55 | { 56 | pinMode(16,OUTPUT); 57 | pinMode(25,OUTPUT); 58 | 59 | digitalWrite(16, LOW); // set GPIO16 low to reset OLED 60 | delay(50); 61 | digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high 62 | 63 | display.init(); 64 | display.flipScreenVertically(); 65 | display.setFont(ArialMT_Plain_10); 66 | logo(); 67 | delay(1500); 68 | display.clear(); 69 | 70 | SPI.begin(SCK,MISO,MOSI,SS); 71 | LoRa.setPins(SS,RST,DI00); 72 | 73 | if (!LoRa.begin(BAND)) 74 | { 75 | display.drawString(0, 0, "Starting LoRa failed!"); 76 | display.display(); 77 | while (1); 78 | } 79 | LoRa.setTxPower(POWER, PABOOST); 80 | LoRa.setSpreadingFactor(7); 81 | LoRa.setSignalBandwidth(125000); 82 | LoRa.setCodingRate4(5); 83 | LoRa.disableCrc(); 84 | 85 | display.drawString(0, 0, "LoRa Initial success!"); 86 | display.display(); 87 | delay(1000); 88 | } 89 | 90 | static const int MSGSIZE = 80; 91 | static const int REDUNDANCY = 20; 92 | RS::ReedSolomon rs; 93 | unsigned char message_frame[MSGSIZE]; 94 | unsigned char encoded_message[MSGSIZE + REDUNDANCY]; 95 | 96 | void loop() 97 | { 98 | display.clear(); 99 | display.setTextAlignment(TEXT_ALIGN_LEFT); 100 | display.setFont(ArialMT_Plain_10); 101 | 102 | display.drawString(0, 0, "send pkt "); 103 | display.drawString(90, 0, String(counter)); 104 | display.display(); 105 | 106 | String msg = "PU5EPX-1 LoRaMaDoR id " + String(counter); 107 | 108 | memset(message_frame, 0, sizeof(message_frame)); 109 | int net_length = msg.length(); 110 | for(int i = 0; i < msg.length() && i < MSGSIZE; i++) { 111 | message_frame[i] = msg[i]; 112 | } 113 | 114 | rs.Encode(message_frame, encoded_message); 115 | 116 | // send packet 117 | LoRa.beginPacket(); 118 | for (int i = 0; i < net_length; ++i) { 119 | LoRa.write(encoded_message[i]); 120 | } 121 | for (int i = 0; i < REDUNDANCY; ++i) { 122 | LoRa.write(encoded_message[MSGSIZE + i]); 123 | } 124 | LoRa.endPacket(); 125 | Serial.println("Sent packet " + msg); 126 | 127 | counter++; 128 | // digitalWrite(25, HIGH); // turn the LED on (HIGH is the voltage level) 129 | // delay(1000); // wait for a second 130 | // digitalWrite(25, LOW); // turn the LED off by making the voltage LOW 131 | delay(1000 + random(1000, 2000)); // wait for a second 132 | } 133 | -------------------------------------------------------------------------------- /src/Packet.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Class that encapsulates a LoRaMaDoR packet. 7 | // Includes layer-2 and layer-3 parsing and encoding. 8 | 9 | #include 10 | #include 11 | #include 12 | #include "Packet.h" 13 | #include "Params.h" 14 | 15 | // Decode packet preamble (except callsigns). 16 | static bool decode_preamble(const char* data, size_t len, 17 | Callsign &to, Callsign &from, Params& params, int& error) 18 | { 19 | const char *d1 = (const char*) memchr(data, '<', len); 20 | const char *d2 = (const char*) memchr(data, ':', len); 21 | 22 | if (d1 == 0 || d2 == 0) { 23 | error = 100; 24 | return false; 25 | } else if (d1 >= d2) { 26 | error = 101; 27 | return false; 28 | } 29 | 30 | to = Callsign(Buffer(data, d1 - data)); 31 | from = Callsign(Buffer(d1 + 1, d2 - d1 - 1)); 32 | 33 | if (!to.is_valid() || !from.is_valid()) { 34 | error = 104; 35 | return false; 36 | } 37 | 38 | const char *sparams = d2 + 1; 39 | size_t sparams_len = len - (d2 - data) - 1; 40 | params = Params(Buffer(sparams, sparams_len)); 41 | 42 | if (! params.is_valid_with_ident()) { 43 | error = 105; 44 | return false; 45 | } 46 | 47 | return true; 48 | } 49 | 50 | Packet::Packet(const Callsign &to, const Callsign &from, 51 | const Params& params, const Buffer& msg, int rssi): 52 | _to(to), _from(from), _params(params), _msg(msg), _rssi(rssi) 53 | { 54 | _signature = Buffer(_from) + ":" + params.s_ident(); 55 | } 56 | 57 | Packet::~Packet() 58 | { 59 | } 60 | 61 | Ptr Packet::decode_l3_test(const char* data, int& error) 62 | { 63 | return decode_l3(data, strlen(data), -50, error); 64 | } 65 | 66 | // Decode packet coming from layer 2. 67 | Ptr Packet::decode_l3(const char* data, size_t len, int rssi, int &error) 68 | { 69 | const char *preamble = 0; 70 | const char *msg = 0; 71 | size_t preamble_len = 0; 72 | size_t msg_len = 0; 73 | 74 | const char *msgd = (const char*) memchr(data, ' ', len); 75 | 76 | if (msgd) { 77 | preamble = data; 78 | preamble_len = msgd - data; 79 | msg = msgd + 1; 80 | msg_len = len - preamble_len - 1; 81 | } else { 82 | // valid packet with no message 83 | preamble = data; 84 | preamble_len = len; 85 | } 86 | 87 | Callsign to; 88 | Callsign from; 89 | Params params; 90 | 91 | if (! decode_preamble(preamble, preamble_len, to, from, params, error)) { 92 | return Ptr(0); 93 | } 94 | 95 | Ptr p = Ptr(new Packet(to, from, params, Buffer(msg, msg_len), rssi)); 96 | return p; 97 | } 98 | 99 | // Generate a new packet, based on present packet, with modified message. 100 | Ptr Packet::change_msg(const Buffer& msg) const 101 | { 102 | return Ptr(new Packet(this->to(), this->from(), this->params(), msg)); 103 | } 104 | 105 | // Generate a new packet, based on present packet, with modified parameters. 106 | Ptr Packet::change_params(const Params&new_params) const 107 | { 108 | return Ptr(new Packet(this->to(), this->from(), new_params, this->msg())); 109 | } 110 | 111 | // Encode a packet in layer 3. 112 | Buffer Packet::encode_l3(size_t max) const 113 | { 114 | Buffer b(_to); 115 | b += '<'; 116 | b += _from; 117 | b += ':'; 118 | b += _params.serialized(); 119 | b += ' '; 120 | b += _msg; 121 | b = b.substr(0, max); 122 | 123 | return b; 124 | } 125 | 126 | // Packet unique identification (prefix + ID). 127 | Buffer Packet::signature() const 128 | { 129 | return _signature; 130 | } 131 | 132 | // Returns destination callsign of this packet. 133 | Callsign Packet::to() const 134 | { 135 | return _to; 136 | } 137 | 138 | // Returns source callsign 139 | Callsign Packet::from() const 140 | { 141 | return _from; 142 | } 143 | 144 | // returns parameters of this packet 145 | const Params Packet::params() const 146 | { 147 | return _params; 148 | } 149 | 150 | // returns the message or payload of this packet. 151 | const Buffer Packet::msg() const 152 | { 153 | return _msg; 154 | } 155 | 156 | // returns the RSSI tagged to this packet upon reception 157 | // (undefined if packet was not received via LoRa.) 158 | int Packet::rssi() const 159 | { 160 | return _rssi; 161 | } 162 | -------------------------------------------------------------------------------- /test/lora_conn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import socket 4 | from lora_packet import RxPacket 5 | 6 | class Connection: 7 | def __init__(self, port): 8 | self.port = port 9 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 10 | try: 11 | self.sock.connect(("localhost", port)) 12 | except ConnectionRefusedError: 13 | self.sock.close() 14 | self.readbuf = b'' 15 | self.writebuf = b'' 16 | self.EOL = b'\r\n' 17 | self.handlers = { 18 | "debug: ": self.interpret_debug, 19 | "cli: ": self.interpret_cli, 20 | "net: ": self.interpret_net, 21 | "callsign: ": self.interpret_callsign, 22 | "pkrx: ": self.interpret_packet, 23 | "!tnc": self.interpret_tnc, 24 | } 25 | self.protocol_handlers = {} 26 | 27 | def add_proto_handler(self, keys, klass): 28 | for key in keys: 29 | self.protocol_handlers[key] = klass 30 | 31 | def send(self, data): 32 | self.writebuf += data.encode("utf-8") 33 | 34 | def sendpkt(self, data): 35 | if isinstance(data, str): 36 | data = data.encode("utf-8") 37 | self.writebuf += b"!pktx " + data.hex().encode("utf-8") + b"\r" 38 | 39 | def recv(self): 40 | data, self.readbuf = self.readbuf, "" 41 | return data 42 | 43 | def do_recv(self): 44 | # print("reading %d..." % self.port) 45 | try: 46 | data = self.sock.recv(1500) 47 | except ConnectionResetError: 48 | data = "" 49 | if len(data) == 0: 50 | print("Conn %d closed on recv" % self.port) 51 | self.sock.close() 52 | self.sock = None 53 | else: 54 | self.readbuf += data 55 | self.interpret() 56 | 57 | def do_send(self): 58 | # print("writing %d..." % self.port) 59 | n = self.sock.send(self.writebuf) 60 | if n < 0: 61 | print("Conn %d error on send" % self.port) 62 | self.sock.close() 63 | self.sock = None 64 | elif n == 0: 65 | print("Conn %d closed on send" % self.port) 66 | self.active = False 67 | self.sock.close() 68 | self.sock = None 69 | else: 70 | self.writebuf = self.writebuf[n:] 71 | 72 | def readline(self): 73 | i = self.readbuf.find(self.EOL) 74 | if i < 0: 75 | return None 76 | line, self.readbuf = \ 77 | self.readbuf[:i], self.readbuf[i+len(self.EOL):] 78 | return line 79 | 80 | def interpret(self): 81 | while self.readbuf: 82 | line = self.readline() 83 | if line is None: 84 | break 85 | if not line: 86 | continue 87 | # print("%d line: %s" % (self.port, line)) 88 | if not self.interpret_line(line): 89 | # not complete; put back 90 | self.readbuf = line + self.EOL + self.readbuf 91 | 92 | def interpret_line(self, line): 93 | for header, handler in self.handlers.items(): 94 | header = header.encode("utf-8") 95 | if line.startswith(header): 96 | return handler(line[len(header):]) 97 | print("Unrecognized line %d: %s" % (self.port, line)) 98 | return True 99 | 100 | def interpret_debug(self, line): 101 | print("%d debug" % self.port, line) 102 | return True 103 | 104 | def interpret_cli(self, line): 105 | print("%d cli" % self.port, line) 106 | return True 107 | 108 | def interpret_tnc(self, line): 109 | # Echo of "!tnc" command we send via serial 110 | return True 111 | 112 | def interpret_net(self, line): 113 | print("%d net" % self.port, line) 114 | return True 115 | 116 | def interpret_callsign(self, line): 117 | print("%d callsign" % self.port, line) 118 | return True 119 | 120 | def interpret_packet(self, line): 121 | print("%d packet" % self.port, line) 122 | i = line.find(b' ') 123 | if i < 0: 124 | print("%d invalid packet header, no RSSI delim" % self.port) 125 | return True 126 | try: 127 | rssi = int(line[:i], 10) 128 | except ValueError: 129 | print("%d invalid RSSI value" % self.port) 130 | return True 131 | line = line[i+1:] 132 | 133 | print("%d unwrapped packet RSSI=%d %s" % (self.port, rssi, line)) 134 | p = RxPacket(line) 135 | if p.err: 136 | print("\tinvalid packet: %s" % p.err) 137 | return True 138 | else: 139 | print("%d parsed packet %s" % (self.port, str(p))) 140 | 141 | for key, value in p.params.items(): 142 | if key in self.protocol_handlers: 143 | print("Calling handler for %s" % key) 144 | if self.protocol_handlers[key].rx(p): 145 | break 146 | 147 | return True 148 | -------------------------------------------------------------------------------- /test/lora_switch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time, random 4 | 5 | class Switch: 6 | transactions = {} 7 | 8 | def __init__(self, tnc, server, loop, target, value=None): 9 | self.tnc = tnc 10 | self.server = server 11 | self.loop = loop 12 | self.target = target 13 | self.value = value 14 | self.callback = lambda tgt, val: None 15 | self.challenge = self.gen_challenge(8) 16 | self.sendA() 17 | Switch.transactions[self.challenge] = self 18 | 19 | def on_result(self, cb): 20 | self.callback = cb 21 | 22 | def gen_challenge(self, length): 23 | c = "" 24 | for i in range(0, length): 25 | c += "0123456789abcdefghijklmnopqrstuvwxyz"[random.randint(0, 35)] 26 | return c 27 | 28 | @staticmethod 29 | def rx(pkt): 30 | print("SW: received packet", pkt.msg) 31 | try: 32 | umsg = pkt.msg.decode('utf-8') 33 | except UnicodeDecodeError: 34 | print("SW: packet msg has non-ASCII chars") 35 | return True 36 | 37 | fields = umsg.split(",") 38 | if len(fields) < 3: 39 | print("SW: packet msg has less than 3 fields") 40 | return True 41 | ptype, challenge, response = fields[0:3] 42 | 43 | if challenge not in Switch.transactions: 44 | print("SW: packet msg has unknown challenge") 45 | return True 46 | 47 | if ptype == "B": 48 | Switch.transactions[challenge].rxB(fields) 49 | elif ptype == "D": 50 | Switch.transactions[challenge].rxD(fields) 51 | else: 52 | print("SW: packet msg of unknown type") 53 | 54 | return True 55 | 56 | def sendA(self): 57 | print("SW: sending packet A") 58 | self.tnc.sendpkt("%s:SW A,%s" % (self.server, self.challenge)) 59 | self.state = 'A' 60 | self.to = self.loop.schedule(self.timeoutA, 3.0) 61 | 62 | def timeoutA(self): 63 | # TODO maximum number of requests, exponential backoff 64 | self.sendA() 65 | 66 | def rxB(self, fields): 67 | if len(fields) < 3: 68 | print("SW: received invalid packet B") 69 | return 70 | if self.state != 'A': 71 | print("SW: received packet B but state not A") 72 | return 73 | if fields[1] != self.challenge: 74 | print("SW: received packet B with wrong challenge") 75 | return 76 | if len(fields[2]) < 8: 77 | print("SW: received packet B with short response") 78 | return 79 | self.response = fields[2] 80 | if self.to: 81 | self.loop.cancel(self.to) 82 | self.to = None 83 | self.sendC() 84 | 85 | def sendC(self): 86 | print("SW: sending packet C") 87 | if self.value is None: 88 | # query 89 | self.tnc.sendpkt("%s:SW C,%s,%s,%d,?" % \ 90 | (self.server, self.challenge, self.response, 91 | self.target)) 92 | else: 93 | # set 94 | self.tnc.sendpkt("%s:SW C,%s,%s,%d,%d" % \ 95 | (self.server, self.challenge, self.response, 96 | self.target, self.value)) 97 | self.state = 'C' 98 | self.to = self.loop.schedule(self.timeoutC, 3.0) 99 | 100 | def timeoutC(self): 101 | # TODO maximum number of requests, exponential backoff 102 | self.sendC() 103 | 104 | def rxD(self, fields): 105 | if len(fields) < 5: 106 | print("SW: received invalid packet D") 107 | return 108 | if self.state != 'C': 109 | print("SW: received packet D but state not C") 110 | return 111 | if fields[1] != self.challenge: 112 | print("SW: received packet D with wrong challenge") 113 | return 114 | if fields[2] != self.response: 115 | print("SW: received packet D with wrong response") 116 | return 117 | if self.to: 118 | self.loop.cancel(self.to) 119 | self.to = None 120 | 121 | ok = True 122 | if fields[4] == '?': 123 | self.value = None 124 | else: 125 | try: 126 | self.value = int(fields[4], 10) 127 | except ValueError: 128 | print("SW: value is invalid") 129 | ok = False 130 | if self.value < 0 or self.value > 65535: 131 | print("SW: value is invalid") 132 | ok = False 133 | try: 134 | ptarget = int(fields[3], 10) 135 | except ValueError: 136 | print("SW: returned target is invalid") 137 | ok = False 138 | if ptarget != self.target: 139 | print("SW: returned target different from request") 140 | ok = False 141 | 142 | if ok: 143 | self.callback(self.target, self.value) 144 | self.state = 'E' 145 | del Switch.transactions[self.challenge] 146 | print("SW: **** finished transaction ****") 147 | -------------------------------------------------------------------------------- /test/lora_packet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | class RxPacket: 4 | def __init__(self, hexdata): 5 | self.params = {} 6 | self.msg = b'' 7 | self.to = "" 8 | self.fromm = "" 9 | self.ident = 0 10 | self.err = None 11 | data = self.decode_hex(hexdata.strip()) 12 | if data: 13 | self.decode_l3(data) 14 | 15 | def decode_hex(self, hexdata): 16 | if not hexdata or (len(hexdata) % 1) != 0: 17 | self.err = "Invalid hex encoded packet" 18 | return None 19 | try: 20 | uhexdata = hexdata.decode('utf-8') 21 | except UnicodeDecodeError: 22 | self.err = "Unicode error in hexdata" 23 | return None 24 | try: 25 | data = bytes.fromhex(uhexdata) 26 | except ValueError: 27 | self.err = "bad data in hexdata" 28 | return None 29 | return data 30 | 31 | def parse_symbol_param(self, data): 32 | i = data.find(b'=') 33 | if i < 0: 34 | # naked key 35 | key = data 36 | value = None 37 | uvalue = None 38 | else: 39 | key = data[:i] 40 | value = data[i+1:] 41 | 42 | for n in range(0, len(key)): 43 | c = key[n] 44 | if c >= ord('0') and c <= ord('9'): 45 | pass 46 | elif c >= ord('a') and c <= ord('z'): 47 | pass 48 | elif c >= ord('A') and c <= ord('Z'): 49 | pass 50 | else: 51 | self.err = "Param key with invalid char" 52 | return False 53 | 54 | try: 55 | ukey = key.decode('utf-8') 56 | except UnicodeDecodeError: 57 | self.err = "Unicode error in key" 58 | return False 59 | 60 | if value is not None: 61 | for n in range(0, len(value)): 62 | c = chr(value[n]) 63 | if "= ,:<".find(c) >= 0 or ord(c) >= 127: 64 | self.err = "Param value with invalid char" 65 | return False 66 | try: 67 | uvalue = value.decode('utf-8') 68 | except UnicodeDecodeError: 69 | self.err = "Unicode error in value" 70 | return False 71 | 72 | self.params[ukey] = uvalue 73 | 74 | return True 75 | 76 | def parse_ident_param(self, data): 77 | try: 78 | self.ident = int(data, 10) 79 | except ValueError: 80 | self.err = "Ident param not a valid decimal number" 81 | return False 82 | if self.ident <= 0: 83 | self.err = "Ident <= 0" 84 | return False 85 | if self.ident > 999999: 86 | self.err = "Ident > 999999" 87 | return False 88 | if len(data) != len(str(self.ident)): 89 | self.err = "Ident int repr with different length" 90 | return False 91 | return True 92 | 93 | def parse_param(self, data): 94 | c = data[0] 95 | if c >= ord('0') and c <= ord('9'): 96 | return self.parse_ident_param(data) 97 | elif c >= ord('a') and c <= ord('z'): 98 | return self.parse_symbol_param(data) 99 | elif c >= ord('A') and c <= ord('Z'): 100 | return self.parse_symbol_param(data) 101 | else: 102 | self.err = "Param with invalid first char" 103 | return False 104 | 105 | def parse_params(self, data): 106 | while data: 107 | comma = data.find(b',') 108 | if comma < 0: 109 | # last, or only, param 110 | param = data 111 | data = "" 112 | else: 113 | param = data[:comma] 114 | data = data[comma+1:] 115 | if not param: 116 | self.err = "Empty param" 117 | return 118 | 119 | if not self.parse_param(param): 120 | return 121 | 122 | def decode_preamble(self, data): 123 | d1 = data.find(b'<') 124 | d2 = data.find(b':') 125 | if d1 < 0 or d2 < 0: 126 | self.err = "Basic preamble delimiters" 127 | return 128 | elif d1 >= d2: 129 | self.err = "Basic preamble delimiters order" 130 | return 131 | try: 132 | self.to = data[:d1].decode('utf-8') 133 | self.fromm = data[d1+1:d2].decode('utf-8') 134 | except UnicodeDecodeError: 135 | self.err = "Unicode error in callsign" 136 | return 137 | # TODO check callsign internal format 138 | self.parse_params(data[d2+1:]) 139 | 140 | def decode_l3(self, data): 141 | i = data.find(b' ') 142 | if i >= 0: 143 | preamble = data[:i] 144 | self.msg = data[i+1:] 145 | else: 146 | # valid packet with no message 147 | preamble = data 148 | self.decode_preamble(preamble) 149 | 150 | def __str__(self): 151 | sparams = [] 152 | for key, value in self.params.items(): 153 | if value is None: 154 | sparams.append(key) 155 | else: 156 | sparams.append(key + "=" + value) 157 | sparams = ",".join(sparams) 158 | 159 | return "to %s from %s params %s msg %s" % \ 160 | (self.to, self.fromm, sparams, self.msg) 161 | -------------------------------------------------------------------------------- /PacketFormat.md: -------------------------------------------------------------------------------- 1 | # LoRaMaDoR protocol 2 | 3 | This page describes the more technical details of the protocol. 4 | 5 | ## LoRaMaDoR packet format 6 | 7 | A LoRaMaDoR packet is composed by three parts: header, payload and FEC code. 8 | The header contains destination and source callsigns, as well as some 9 | parameters: 10 | 11 | ``` 12 | Destination 90: 51 | print("%s rt: expiring edge %s < %s (%d)" % \ 52 | (self.callsign, to, fr0m, item.expiry)) 53 | 54 | for to, fr0m in expired: 55 | del self.graph[to][fr0m] 56 | 57 | # Calculate next 'via' station. 58 | # Returns: "" to resort to diffusion routing 59 | # None if packet should not be repeated 60 | 61 | def get_next_hop(self, to): 62 | repeater, cost = self._get_next_hop(to, (to, )) 63 | if repeater == self.callsign: 64 | repeater = "QB" 65 | else: 66 | if ROUTER_VERBOSITY > 60: 67 | print("%s: route to %s via %s cost %d" % \ 68 | (self.callsign, to, repeater, cost)) 69 | return repeater 70 | 71 | def _get_next_hop(self, to, path): 72 | if to in ("QF", "QB", "QM", "QN"): 73 | # these always go by diffusion 74 | return "", 0 75 | elif to and to[0] == "Q": 76 | raise Exception("%s rt: asked route to %s" % (self.callsign, to)) 77 | elif to == self.callsign: 78 | # should not happen 79 | raise Exception("%s rt: asked route to itself" % self.callsign) 80 | 81 | recursion = "=|" * (len(path) - 1) 82 | if recursion: 83 | recursion += " " 84 | 85 | if to not in self.graph: 86 | # Unknown destination 87 | if CAN_DIFFUSE_UNKNOWN: 88 | if ROUTER_VERBOSITY > 50: 89 | print("%s%s rt: dest %s unknown, use diffusion" % \ 90 | (recursion, self.callsign, to)) 91 | return "", 999999999 92 | if ROUTER_VERBOSITY > 50: 93 | print("%s%s rt: dest %s unknown, cannot route" % \ 94 | (recursion, self.callsign, to)) 95 | return None, 999999999 96 | 97 | if self.callsign in self.graph[to]: 98 | # last hop, no actual routing 99 | return to, self.graph[to][self.callsign].cost 100 | 101 | if ROUTER_VERBOSITY > 90: 102 | print("%s%s rt: looking for route %s < %s" % \ 103 | (recursion, self.callsign, to, self.callsign)) 104 | 105 | # Try to find cheapest route, walking backwards from 'to' 106 | best_cost = 999999999 107 | best_via = None 108 | for penultimate, edge in self.graph[to].items(): 109 | if ROUTER_VERBOSITY > 90: 110 | print("%s \tlooking for route to %s" % (recursion, penultimate)) 111 | 112 | if penultimate in path: 113 | # would create a loop 114 | if ROUTER_VERBOSITY > 90: 115 | print("%s \t\tloop %s" % (recursion, str(path))) 116 | continue 117 | 118 | via, cost = self._get_next_hop(penultimate, (penultimate,) + path) 119 | cost += edge.cost 120 | 121 | if not via: 122 | if ROUTER_VERBOSITY > 90: 123 | print("%s \t\tno route" % recursion) 124 | continue 125 | 126 | if ROUTER_VERBOSITY > 90: 127 | print("%s \t\tcandidate %s < %s < %s < %s cost %d" % \ 128 | (recursion, to, penultimate, via, self.callsign, cost)) 129 | 130 | if not best_via or cost < best_cost: 131 | best_via = via 132 | best_cost = cost 133 | 134 | if not best_via: 135 | # Did not find route 136 | if CAN_DIFFUSE_NOROUTE: 137 | if ROUTER_VERBOSITY > 90: 138 | print("%s \troute not found, using diffusion" % recursion) 139 | return "", 999999999 140 | if ROUTER_VERBOSITY > 90: 141 | print("%s \troute not found, giving up" % recursion) 142 | return None, 999999999 143 | 144 | if ROUTER_VERBOSITY > 90: 145 | print("%s \tadopted %s < %s < %s cost %d" % (recursion, to, via, self.callsign, best_cost)) 146 | 147 | return best_via, best_cost 148 | -------------------------------------------------------------------------------- /python/loraham-console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | """This is a half-assed attempt at a LoRaHam GUI that was written to 5 | learn the Urwid library. Transmitting packets does not yet work, but 6 | it does allow a user to conveniently bounce between viewing packets 7 | by different filters.""" 8 | 9 | #slightly modified version of LoRaHam-Console by Travis Goodspeed, modified by LeRoy Miller Aug 17, 2020, all credit should go to 10 | # Travis Goodspeed's and the loraham project https://github.com/travisgoodspeed/loraham 11 | # it now sends packets and will send a packet to another LoRaMaDor node. 12 | # To use this you will need a LoRaMaDor board hooked to your computer. 13 | 14 | # I am still learning python, and I used this program as a good example, all credit should go to Travis Goodspeed, my changes were small. 15 | 16 | import urwid, serial; 17 | 18 | 19 | MYCALL = "N0CALL" #Change to your callsign 20 | 21 | try: 22 | MYCALL 23 | except: 24 | raise Exception('set your callsign') 25 | ENCODING = 'utf-8' # don't change me 26 | #ser = serial.Serial('/dev/ttyS0'); 27 | ser = serial.Serial('/dev/ttyUSB0', 115200); #You may need to change your serial port 28 | 29 | SHOWALL=1; 30 | SHOWMINE=2; 31 | SHOWVERBOSE=3; 32 | toshow=SHOWALL; 33 | def on_showall_clicked(button): 34 | global toshow; 35 | toshow=SHOWALL; 36 | show_packets(); 37 | 38 | def on_showmine_clicked(button): 39 | global toshow; 40 | toshow=SHOWMINE; 41 | show_packets(); 42 | 43 | def on_showverbose_clicked(button): 44 | global toshow; 45 | toshow=SHOWVERBOSE; 46 | show_packets(); 47 | 48 | def on_send(button): 49 | global packet, dest, ser 50 | dest.set_edit_text(dest.get_edit_text().upper()) 51 | packet_to_send = '{} {} {}'.format(dest.get_edit_text(), 52 | MYCALL, 53 | packet.get_edit_text()).encode(ENCODING) 54 | ser.write(packet_to_send) 55 | ser.write(b'\n\r') 56 | packet.set_edit_text('') 57 | 58 | 59 | 60 | def show_packets(): 61 | global toshow, allpackets, mypackets, verbloselog; 62 | if toshow==SHOWALL: 63 | received.set_text(allpackets); 64 | elif toshow==SHOWMINE: 65 | received.set_text(mypackets); 66 | elif toshow==SHOWVERBOSE: 67 | received.set_text(verboselog); 68 | 69 | def on_packet_change(edit, new_edit_text): 70 | pass; 71 | 72 | #Is my callsign mentioned in the line? 73 | def ismentioned(line): 74 | for word in line.split(): 75 | if word==MYCALL: 76 | return True; 77 | return False; 78 | 79 | linestate=0; 80 | lastline=""; 81 | def handle_line(line): 82 | global allpackets, mypackets, verboselog, linestate, lastline; 83 | 84 | #Verbose log has everything. 85 | verboselog=verboselog+line+'\n'; 86 | 87 | #Empty lines delimit transmissions. 88 | if len(line)==0: 89 | linestate=0; 90 | return; 91 | 92 | #First line is the real message. 93 | if linestate==0 and line!=lastline: 94 | lastline=line; 95 | allpackets=allpackets+line+"\n"; 96 | if ismentioned(line): 97 | mypackets=mypackets+line+"\n"; 98 | linestate=linestate+1; 99 | 100 | def ser_available(): 101 | global ser, allpackets; 102 | try: 103 | newline=ser.readline().decode(ENCODING).strip(); 104 | handle_line(newline); 105 | show_packets(); 106 | except: 107 | handle_line("Data lost to UTF-8 corruption."); 108 | show_packets(); 109 | 110 | 111 | #Some globals for our state. 112 | allpackets=""; 113 | mypackets=""; 114 | verboselog=""; 115 | 116 | palette = [('entry', 'bold,yellow', 'black'),] 117 | 118 | dest=urwid.Edit(('entry', u"Destination:\n")); 119 | packet=urwid.Edit(('entry', u"Message to send:\n")); 120 | received=urwid.Text(('body_text', u"Packets will appear here when they arrive.")); 121 | div=urwid.Divider(); 122 | #button=urwid.Button(u'Exit'); 123 | 124 | showall=urwid.Button(('button', u'All')); 125 | showmine=urwid.Button(('button', u'Mine')); 126 | showverbose=urwid.Button(('button', u'Verbose')); 127 | sendpacket=urwid.Button(('button', u'Send')); 128 | buttons=urwid.Columns([showall,showmine,showverbose,sendpacket]); 129 | 130 | pile=urwid.Pile([received,div,buttons,dest,packet]); 131 | top=urwid.Filler(pile,valign='bottom'); 132 | 133 | urwid.connect_signal(packet, 'change', on_packet_change); 134 | urwid.connect_signal(showall, 'click', on_showall_clicked); 135 | urwid.connect_signal(showmine, 'click', on_showmine_clicked); 136 | urwid.connect_signal(showverbose, 'click', on_showverbose_clicked); 137 | urwid.connect_signal(sendpacket, 'click', on_send); 138 | 139 | loop=urwid.MainLoop(top, palette) 140 | loop.watch_file(ser, ser_available); 141 | loop.run(); 142 | 143 | -------------------------------------------------------------------------------- /_pocs_/ttgo_receiver/ttgo_receiver.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This is a simple example show the LoRa recived data in OLED. 3 | 4 | The onboard OLED display is SSD1306 driver and I2C interface. In order to make the 5 | OLED correctly operation, you should output a high-low-high(1-0-1) signal by soft- 6 | ware to OLED's reset pin, the low-level signal at least 5ms. 7 | 8 | OLED pins to ESP32 GPIOs via this connecthin: 9 | OLED_SDA -- GPIO4 10 | OLED_SCL -- GPIO15 11 | OLED_RST -- GPIO16 12 | 13 | by Aaron.Lee from HelTec AutoMation, ChengDu, China 14 | 成都惠利特自动化科技有限公司 15 | www.heltec.cn 16 | 17 | this project also realess in GitHub: 18 | https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series 19 | */ 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "SSD1306.h" 25 | #include "images.h" 26 | 27 | // Pin definetion of WIFI LoRa 32 28 | // HelTec AutoMation 2017 support@heltec.cn 29 | #define SCK 5 // GPIO5 -- SX127x's SCK 30 | #define MISO 19 // GPIO19 -- SX127x's MISO 31 | #define MOSI 27 // GPIO27 -- SX127x's MOSI 32 | #define SS 18 // GPIO18 -- SX127x's CS 33 | #define RST 14 // GPIO14 -- SX127x's RESET 34 | #define DI00 26 // GPIO26 -- SX127x's IRQ(Interrupt Request) 35 | 36 | #define BAND 916750000 //you can set band here directly,e.g. 868E6,915E6 37 | #define POWER 20 38 | #define PABOOST 1 39 | 40 | SSD1306 display(0x3c, 4, 15); 41 | String rssi = "RSSI --"; 42 | String grossSize = "--"; 43 | String netSize = "--"; 44 | 45 | void logo(){ 46 | display.clear(); 47 | display.drawXbm(0,5,logo_width,logo_height,logo_bits); 48 | display.display(); 49 | } 50 | 51 | void setup() { 52 | Serial.begin(9600); 53 | // while (!Serial); 54 | // Serial.println("Starting..."); 55 | 56 | pinMode(16,OUTPUT); 57 | digitalWrite(16, LOW); // set GPIO16 low to reset OLED 58 | delay(50); 59 | digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high、 60 | display.init(); 61 | display.flipScreenVertically(); 62 | display.setFont(ArialMT_Plain_10); 63 | logo(); 64 | delay(1000); 65 | display.clear(); 66 | 67 | SPI.begin(SCK,MISO,MOSI,SS); 68 | LoRa.setPins(SS,RST,DI00); 69 | 70 | if (!LoRa.begin(BAND)) { 71 | display.drawString(0, 0, "Starting LoRa failed!"); 72 | display.display(); 73 | while (1); 74 | } 75 | LoRa.setTxPower(POWER, PABOOST); 76 | LoRa.setSpreadingFactor(9); 77 | LoRa.setSignalBandwidth(62500); 78 | LoRa.setCodingRate4(5); 79 | LoRa.disableCrc(); 80 | 81 | display.drawString(0, 0, "LoRa ok"); 82 | display.display(); 83 | 84 | LoRa.onReceive(onReceive); 85 | LoRa.receive(); 86 | // Serial.println("Waiting for packets"); 87 | } 88 | 89 | int pcount = 0; 90 | int dcount = 0; 91 | 92 | void loop() { 93 | if (pcount > dcount) { 94 | loraData(); 95 | dcount = pcount; 96 | } 97 | } 98 | 99 | static const int MSGSIZE = 80; 100 | static const int REDUNDANCY = 20; 101 | RS::ReedSolomon rs; 102 | char decoded[MSGSIZE]; 103 | char encoded[MSGSIZE + REDUNDANCY]; 104 | char punctured[MSGSIZE + REDUNDANCY]; 105 | String msg; 106 | 107 | void loraData() 108 | { 109 | display.clear(); 110 | display.setTextAlignment(TEXT_ALIGN_LEFT); 111 | display.setFont(ArialMT_Plain_10); 112 | display.drawString(0 , 15, "Recv #" + String(pcount) + " = " + grossSize); 113 | display.drawStringMaxWidth(0 , 30, 128, msg); 114 | display.drawString(0, 0, rssi); 115 | display.display(); 116 | } 117 | 118 | void onReceive(int plen) { 119 | // Serial.println("onReceive"); 120 | ++pcount; 121 | rssi = "RSSI " + String(LoRa.packetRssi(), DEC); 122 | // Serial.println(rssi); 123 | grossSize = String(plen, DEC); 124 | // Serial.println(grossSize); 125 | int llen = plen - REDUNDANCY; 126 | netSize = String(llen, DEC); 127 | // Serial.println(netSize); 128 | 129 | if (plen > (MSGSIZE + REDUNDANCY)) { 130 | rssi += " too big"; 131 | loraData(); 132 | return; 133 | } 134 | if (plen <= REDUNDANCY) { 135 | rssi += " too small"; 136 | loraData(); 137 | return; 138 | } 139 | 140 | // Serial.println("Clearing"); 141 | memset(punctured, 0, sizeof(punctured)); 142 | memset(encoded, 0, sizeof(encoded)); 143 | memset(decoded, 0, sizeof(decoded)); 144 | 145 | // Serial.println("Recv octets"); 146 | for (int i = 0; i < plen && i < sizeof(punctured); i++) { 147 | punctured[i] = LoRa.read(); 148 | } 149 | 150 | // Serial.println("Unpuncturing"); 151 | memcpy(encoded, punctured, llen); 152 | memcpy(encoded + MSGSIZE, punctured + llen, REDUNDANCY); 153 | 154 | // Serial.println("Decoding"); 155 | if (rs.Decode(encoded, decoded)) { 156 | rssi += " corrupted"; 157 | msg = "corrupted"; 158 | } else { 159 | msg = decoded; 160 | } 161 | 162 | // Serial.println(msg); 163 | } 164 | 165 | -------------------------------------------------------------------------------- /test/switch_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys, select, time, random 4 | from lora_loop import EventLoop 5 | from lora_conn import Connection 6 | from lora_switch import Switch 7 | from lora_ping import Ping 8 | 9 | client_port = int(sys.argv[1]) 10 | server_port = int(sys.argv[2]) 11 | server_callsign = sys.argv[3] 12 | 13 | loop = EventLoop() 14 | 15 | print("Connecting...") 16 | client = Connection(client_port) 17 | server = Connection(server_port) 18 | print("Connected") 19 | 20 | # Configure TNC mode 21 | client.send("\r!tnc\r") 22 | server.send("\r!tnc\r") 23 | 24 | def rst(): 25 | client.send("\r!reset\r") 26 | server.send("\r!reset\r") 27 | loop.schedule(rst, 60.0) 28 | 29 | client.add_proto_handler(["SWC"], Switch) 30 | client.add_proto_handler(["PONG"], Ping) 31 | 32 | s = None 33 | 34 | def switch_on(): 35 | global s 36 | s = Switch(client, server_callsign, loop, 1, 1) 37 | def h(tgt, res): 38 | print("########", tgt, res) 39 | assert(res == 1) 40 | assert(tgt == 1) 41 | s.on_result(h) 42 | loop.schedule(switch_on, 1.0) 43 | 44 | def switch_query(): 45 | s2 = Switch(client, server_callsign, loop, 2) 46 | def h(tgt, res): 47 | print("########", tgt, res) 48 | assert(res == 0) 49 | assert(tgt == 2) 50 | s2.on_result(h) 51 | loop.schedule(switch_query, 1.0) 52 | 53 | def ping(): 54 | payload = "0123456789" * 10 55 | payload = payload.encode("ascii") 56 | p = Ping(client, server_callsign, loop, payload) 57 | def h(pong_payload): 58 | print("######## PONG") 59 | if payload != pong_payload: 60 | print("Error: payload different") 61 | print("Len sent: %d" % len(payload)) 62 | print("Len recv: %d" % len(pong_payload)) 63 | print("Payload sent:", payload) 64 | print("Payload recv:", pong_payload) 65 | sys.exit(1) 66 | p.on_result(h) 67 | loop.schedule(ping, 10.0) 68 | 69 | def ping2(): 70 | payload = bytearray(random.getrandbits(8) for _ in range(100)) 71 | p = Ping(client, server_callsign, loop, payload) 72 | def h(pong_payload): 73 | print("######## PONG") 74 | if payload != pong_payload: 75 | print("Error: payload different") 76 | print("Len sent: %d" % len(payload)) 77 | print("Len recv: %d" % len(pong_payload)) 78 | print("Payload sent:", payload) 79 | print("Payload recv:", pong_payload) 80 | sys.exit(1) 81 | 82 | p.on_result(h) 83 | loop.schedule(ping2, 20.0) 84 | 85 | def switch_query_bad(): 86 | # query switch out of range 87 | s2 = Switch(client, server_callsign, loop, 999) 88 | def h(tgt, res): 89 | print("########", tgt, res) 90 | assert(tgt == 999) 91 | assert(res is None) 92 | s2.on_result(h) 93 | loop.schedule(switch_query_bad, 1.0) 94 | 95 | def errors(): 96 | client.sendpkt("") 97 | # reuse challenge of spent transaction 98 | client.send("%s:SW,MOO A,%s\r" % (server_callsign, s.challenge)) 99 | # challenge too short 100 | client.send("%s:SW A,%s\r" % (server_callsign, "1111111")) 101 | # challenge too short 102 | client.sendpkt("%s:SW C,%s" % (server_callsign, "1111111")) 103 | # invalid type 104 | client.send("%s:SW B,%s\r" % (server_callsign, "12111111")) 105 | # invalid type 106 | client.sendpkt("%s:SW AA,%s" % (server_callsign, "12111111")) 107 | # invalid number of fields 108 | client.send("%s:SW A,%s,%s\r" % (server_callsign, "12111111", "12345678")) 109 | # initially valid request that will fail later 110 | client.sendpkt("%s:SW A,%s" % (server_callsign, "12345678")) 111 | loop.schedule(errors, 30.0) 112 | 113 | def errors2(): 114 | # repeat request that will fail later 115 | client.sendpkt("%s:SW A,%s" % (server_callsign, "12345678")) 116 | loop.schedule(errors2, 35.0) 117 | 118 | def errors3(): 119 | # fail request in C packet (wrong response) 120 | client.sendpkt("%s:SW C,%s,%s,%d,%d" % (server_callsign, "12345678", "12345678", 1, 1)) 121 | # send C packet with bad challenge 122 | client.sendpkt("%s:SW C,%s,%s,%d,%d" % (server_callsign, "87654321", "12345678", 1, 1)) 123 | # send C packet with short challenge 124 | client.sendpkt("%s:SW C,%s,%s,%d,%d" % (server_callsign, "87654321", "1234", 1, 1)) 125 | # send C packet w/o target 126 | client.sendpkt("%s:SW C,%s,%s" % (server_callsign, "87654321", "12345678")) 127 | # send C packet with bad target 128 | client.sendpkt("%s:SW C,%s,%s,%d,%d" % (server_callsign, "87654321", "12345678", 0, 1)) 129 | # send C packet with empty target, good value 130 | client.sendpkt("%s:SW C,%s,%s,,%d" % (server_callsign, "87654321", "12345678", 0)) 131 | # send C packet with good target, empty value 132 | client.sendpkt("%s:SW C,%s,%s,%d,,bla" % (server_callsign, "87654321", "12345678", 1)) 133 | # send C packet with good target, good value, extra field 134 | client.sendpkt("%s:SW C,%s,%s,%d,%d,bla" % (server_callsign, "87654321", "12345678", 1, 1)) 135 | # send C packet with bad value 136 | client.sendpkt("%s:SW C,%s,%s,%d,%d" % (server_callsign, "87654321", "12345678", 1, 10000000)) 137 | loop.schedule(errors3, 40.0) 138 | 139 | while loop.service(client, server): 140 | pass 141 | -------------------------------------------------------------------------------- /src/Telnet.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LoRaMaDoR (LoRa-based mesh network for hams) project 3 | * Copyright (c) 2019 PU5EPX 4 | */ 5 | 6 | // Functions related to Wi-Fi and Telnet server support 7 | 8 | #include 9 | #include 10 | #include 11 | #include "Console.h" 12 | #include "Timestamp.h" 13 | #include "NVRAM.h" 14 | 15 | extern bool redirect_to_telnet; 16 | 17 | static Ptr Net; 18 | static Buffer ssid; 19 | static Buffer password; 20 | static WiFiServer wifiServer(23); 21 | static int wifi_status = 0; 22 | static int64_t wifi_timeout = 0; 23 | static WiFiClient telnet_client; 24 | static Buffer output_buffer; 25 | static bool is_telnet = false; 26 | Buffer ip = "(none)"; 27 | bool mdns = false; 28 | 29 | static void serial_print(const Buffer &msg) 30 | { 31 | serial_print(msg.c_str()); 32 | } 33 | 34 | static void serial_println(const char *msg) 35 | { 36 | serial_print(msg); 37 | serial_print("\r\n"); 38 | } 39 | 40 | static void serial_println(const Buffer &msg) 41 | { 42 | serial_println(msg.c_str()); 43 | } 44 | 45 | // called by Arduino setup() 46 | void wifi_setup(Ptr net) 47 | { 48 | Net = net; 49 | ssid = arduino_nvram_load("ssid"); 50 | password = arduino_nvram_load("password"); 51 | 52 | serial_print("Wi-Fi SSID "); 53 | serial_println(ssid); 54 | 55 | if (ssid != "None") { 56 | wifi_status = 1; 57 | wifi_timeout = sys_timestamp() + 1 * SECONDS; 58 | } 59 | } 60 | 61 | // Called by CLI when user sends the !wifi cmmand 62 | Buffer get_wifi_status() 63 | { 64 | if (wifi_status == 0) { 65 | return "Wi-Fi disabled."; 66 | } 67 | 68 | Buffer status = Buffer("Wi-Fi SSID ") + ssid; 69 | if (wifi_status == 1) { 70 | status += ", waiting to reconnect"; 71 | } else if (wifi_status == 2) { 72 | status += ", connecting"; 73 | } else if (wifi_status == 3) { 74 | status += ", connected, IP "; 75 | status += ip; 76 | if (mdns) { 77 | status += ", Bonjour name "; 78 | status += Net->me(); 79 | status += ".local"; 80 | } else { 81 | status += ", no Bonjour"; 82 | } 83 | } 84 | return status; 85 | } 86 | 87 | // called periodically by Arduino loop() 88 | void wifi_handle() 89 | { 90 | if (wifi_status == 1) { 91 | if (sys_timestamp() > wifi_timeout) { 92 | WiFi.mode(WIFI_STA); 93 | if (password == "None") { 94 | WiFi.begin(ssid.c_str()); 95 | } else { 96 | WiFi.begin(ssid.c_str(), password.c_str()); 97 | } 98 | serial_println("Connecting to WiFi..."); 99 | wifi_status = 2; 100 | wifi_timeout = sys_timestamp() + 20 * SECONDS; 101 | } 102 | } else if (wifi_status == 2) { 103 | int ws = WiFi.status(); 104 | if (ws == WL_CONNECTED) { 105 | serial_println("Connected to WiFi"); 106 | ip = Buffer(WiFi.localIP().toString().c_str()); 107 | serial_println(ip); 108 | wifiServer.begin(); 109 | if (!MDNS.begin(Buffer(Net->me()).c_str())) { 110 | serial_println("mDNS not ok, use IP to connect."); 111 | mdns = false; 112 | } else { 113 | serial_print("Local net name: "); 114 | serial_print(Net->me()); 115 | serial_println(".local"); 116 | mdns = true; 117 | } 118 | wifi_status = 3; 119 | } else if (sys_timestamp() > wifi_timeout) { 120 | serial_println("Failed to connect to WiFi"); 121 | wifi_status = 1; 122 | wifi_timeout = sys_timestamp() + 1 * SECONDS; 123 | } 124 | } else if (wifi_status == 3) { 125 | if (WiFi.status() != WL_CONNECTED) { 126 | serial_println("Disconnected from WiFi."); 127 | wifi_status = 1; 128 | wifi_timeout = sys_timestamp() + 1 * SECONDS; 129 | telnet_client.stop(); 130 | ip = "(none)"; 131 | mdns = false; 132 | } 133 | } 134 | 135 | if (is_telnet && !telnet_client) { 136 | is_telnet = false; 137 | serial_println("Telnet client disconnected"); 138 | console_telnet_disable(); 139 | output_buffer = ""; 140 | } 141 | 142 | if (!is_telnet && wifi_status == 3 && (telnet_client = wifiServer.available())) { 143 | is_telnet = true; 144 | serial_println("Telnet client connected"); 145 | console_telnet_enable(); 146 | output_buffer = ""; 147 | } 148 | 149 | if (is_telnet) { 150 | if (telnet_client && telnet_client.available() > 0) { 151 | console_telnet_type(telnet_client.read()); 152 | } 153 | if (telnet_client && output_buffer.length() > 0) { 154 | // non-blocking write, otherwise supervisor may reset 155 | int written = telnet_client.write(output_buffer.c_str(), 156 | output_buffer.length()); 157 | if (written >= 0) { 158 | output_buffer.cut(written); 159 | } 160 | } 161 | } 162 | } 163 | 164 | void telnet_print(const char* c) 165 | { 166 | if (is_telnet) output_buffer += c; 167 | } 168 | 169 | // /////////////////////// Glue code with Console and CLI 170 | 171 | void console_telnet_enable() 172 | { 173 | redirect_to_telnet = true; 174 | } 175 | 176 | void console_telnet_disable() 177 | { 178 | redirect_to_telnet = false; 179 | } 180 | 181 | // Receive typed character from Telnet socket 182 | void console_telnet_type(char c) 183 | { 184 | if (redirect_to_telnet) cli_type(c); 185 | } 186 | 187 | -------------------------------------------------------------------------------- /python/chat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | """This is a half-assed attempt at a LoRaHam GUI that was written to 5 | learn the Urwid library. Transmitting packets does not yet work, but 6 | it does allow a user to conveniently bounce between viewing packets 7 | by different filters.""" 8 | 9 | # This is a modified version of the LoraHam-Console from Travis Goodspeed's loraham project https://github.com/travisgoodspeed/loraham 10 | # it now sends packets and will send a packet to another LoRaMaDor node. 11 | # this modified version removes most of the buttons, and moves the send button to below the message, making it a little easier to use this as a "chat". It will also show the verbose output of your board. 12 | # To use this you will need a LoRaMaDor board hooked to your computer. 13 | # Aug 18, 2020 LeRoy Miller 14 | # I am still learning python, and I used this program as a good example, all credit should go to Travis Goodspeed, my changes were small. 15 | 16 | import urwid, serial; 17 | 18 | 19 | MYCALL = "N0CALL" #Change to your callsign 20 | 21 | try: 22 | MYCALL 23 | except: 24 | raise Exception('set your callsign') 25 | ENCODING = 'utf-8' # don't change me 26 | #ser = serial.Serial('/dev/ttyS0'); 27 | ser = serial.Serial('/dev/ttyUSB0', 115200); #You may need to change your serial port 28 | 29 | SHOWALL=1; 30 | SHOWMINE=2; 31 | SHOWVERBOSE=3; 32 | #toshow=SHOWALL; 33 | toshow=SHOWVERBOSE; 34 | def on_showall_clicked(button): 35 | global toshow; 36 | toshow=SHOWALL; 37 | show_packets(); 38 | 39 | def on_showmine_clicked(button): 40 | global toshow; 41 | toshow=SHOWMINE; 42 | show_packets(); 43 | 44 | def on_showverbose_clicked(button): 45 | global toshow; 46 | toshow=SHOWVERBOSE; 47 | show_packets(); 48 | 49 | def on_send(button): 50 | global packet, dest, ser 51 | dest.set_edit_text(dest.get_edit_text().upper()) 52 | #packet_to_send = '{} {} {}'.format(dest.get_edit_text(), 53 | # MYCALL, 54 | # packet.get_edit_text()).encode(ENCODING) 55 | packet_to_send = '{} {}'.format(dest.get_edit_text(),packet.get_edit_text()).encode(ENCODING) 56 | ser.write(packet_to_send) 57 | ser.write(b'\n\r') 58 | packet.set_edit_text('') 59 | 60 | 61 | 62 | def show_packets(): 63 | global toshow, allpackets, mypackets, verbloselog; 64 | if toshow==SHOWALL: 65 | received.set_text(allpackets); 66 | elif toshow==SHOWMINE: 67 | received.set_text(mypackets); 68 | elif toshow==SHOWVERBOSE: 69 | received.set_text(verboselog); 70 | 71 | def on_packet_change(edit, new_edit_text): 72 | pass; 73 | 74 | #Is my callsign mentioned in the line? 75 | def ismentioned(line): 76 | for word in line.split(): 77 | if word==MYCALL: 78 | return True; 79 | return False; 80 | 81 | linestate=0; 82 | lastline=""; 83 | def handle_line(line): 84 | global allpackets, mypackets, verboselog, linestate, lastline; 85 | 86 | #Verbose log has everything. 87 | verboselog=verboselog+line+'\n'; 88 | 89 | #Empty lines delimit transmissions. 90 | if len(line)==0: 91 | linestate=0; 92 | return; 93 | 94 | #First line is the real message. 95 | if linestate==0 and line!=lastline: 96 | lastline=line; 97 | allpackets=allpackets+line+"\n"; 98 | if ismentioned(line): 99 | mypackets=mypackets+line+"\n"; 100 | linestate=linestate+1; 101 | 102 | def ser_available(): 103 | global ser, allpackets; 104 | try: 105 | newline=ser.readline().decode(ENCODING).strip(); 106 | handle_line(newline); 107 | show_packets(); 108 | except: 109 | handle_line("Data lost to UTF-8 corruption."); 110 | show_packets(); 111 | 112 | 113 | #Some globals for our state. 114 | allpackets=""; 115 | mypackets=""; 116 | verboselog=""; 117 | 118 | palette = [('entry', 'bold,yellow', 'black'),] 119 | 120 | dest=urwid.Edit(('entry', u"Destination:\n")); 121 | packet=urwid.Edit(('entry', u"Message to send:\n")); 122 | received=urwid.Text(('body_text', u"Packets will appear here when they arrive.")); 123 | div=urwid.Divider(); 124 | #button=urwid.Button(u'Exit'); 125 | 126 | #showall=urwid.Button(('button', u'All')); 127 | #showmine=urwid.Button(('button', u'Mine')); 128 | #showverbose=urwid.Button(('button', u'Verbose')); 129 | sendpacket=urwid.Button(('button', u'Send')); 130 | #buttons=urwid.Columns([showall,showmine,showverbose,sendpacket]); 131 | buttons=urwid.Columns([sendpacket]); 132 | 133 | pile=urwid.Pile([received,div,dest,packet,buttons]); 134 | top=urwid.Filler(pile,valign='bottom'); 135 | 136 | urwid.connect_signal(packet, 'change', on_packet_change); 137 | #urwid.connect_signal(showall, 'click', on_showall_clicked); 138 | #urwid.connect_signal(showmine, 'click', on_showmine_clicked); 139 | #urwid.connect_signal(showverbose, 'click', on_showverbose_clicked); 140 | urwid.connect_signal(sendpacket, 'click', on_send); 141 | 142 | loop=urwid.MainLoop(top, palette) 143 | loop.watch_file(ser, ser_available); 144 | loop.run(); 145 | 146 | -------------------------------------------------------------------------------- /test/run_test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VALGRIND=1 4 | if [ "$1" = "-v" ]; then 5 | VALGRIND=0 6 | fi 7 | 8 | make clean 9 | make -j 4 10 | if [ $VALGRIND = 1 ]; then 11 | nice valgrind --leak-check=full --show-reachable=yes --suppressions=valgrind.supp --error-exitcode=1 ./test || exit 1 12 | else 13 | nice ./test || exit 1 14 | fi 15 | 16 | echo Automatic part 17 | PIDS="" 18 | for eee in AAAA:0:1:somekey:6000 BBBB:1:1:somekey:6001 CCCC:1:3:somekey:6002 DDDD:0:2:somekey:6003 EEEE:1:2:None:6004 FFFF:0:3:None:6005 GGGG:1:2:badkey:6006; do 19 | sta=$(echo $eee | cut -f 1 -d ':') 20 | rep=$(echo $eee | cut -f 2 -d ':') 21 | cov=$(echo $eee | cut -f 3 -d ':') 22 | psk=$(echo $eee | cut -f 4 -d ':') 23 | port=$(echo $eee | cut -f 5 -d ':') 24 | if [ $VALGRIND = 1 ]; then 25 | nice valgrind --leak-check=full --show-reachable=yes --error-exitcode=1 --log-file=${sta}.val --gen-suppressions=all \ 26 | --suppressions=valgrind.supp ./testnet ${sta} ${rep} ${cov} ${psk} ${port} > ${sta}.log & 27 | PIDS+=" $!" 28 | else 29 | nice ./testnet ${sta} ${rep} ${cov} ${psk} ${port} > ${sta}.log & 30 | PIDS+=" $!" 31 | fi 32 | done 33 | 34 | sleep 120 35 | for eee in 6000 6001 6002 6003 6004 6005 6006; do 36 | # needs to retry because testnet also emulates typing internally 37 | # and it may intermingle with the reset command 38 | if [ $(($eee % 2)) -eq 0 ]; then 39 | while echo -e '\r!reset\r' >/dev/tcp/localhost/$eee; do 40 | echo "Killing port $eee with reset" 41 | sleep 1 42 | done 43 | else 44 | while echo -e '\r!defconfig\r' >/dev/tcp/localhost/$eee; do 45 | echo "Killing port $eee with defconfig" 46 | sleep 1 47 | done 48 | fi 49 | done 50 | 51 | ERR=0 52 | for P in $PIDS; do 53 | if ! wait $P; then 54 | echo $PIDS 55 | echo "PID $P exited with error" 56 | ERR=1 57 | fi 58 | done 59 | if [ "$ERR" != 0 ]; then 60 | echo 61 | echo Execution error, check logs 62 | echo 63 | exit 1 64 | fi 65 | 66 | 67 | echo Interactive/TNC part I 68 | PIDS="" 69 | for eee in AAAA:0:1:somekey:6000:None BBBB:0:1:somekey:6001:None; do 70 | sta=$(echo $eee | cut -f 1 -d ':') 71 | rep=$(echo $eee | cut -f 2 -d ':') 72 | cov=$(echo $eee | cut -f 3 -d ':') 73 | psk=$(echo $eee | cut -f 4 -d ':') 74 | port=$(echo $eee | cut -f 5 -d ':') 75 | cpsk=$(echo $eee | cut -f 6 -d ':') 76 | if [ $VALGRIND = 1 ]; then 77 | nice valgrind --leak-check=full --show-reachable=yes --error-exitcode=1 --log-file=${sta}_int.val --gen-suppressions=all \ 78 | --suppressions=valgrind.supp ./testnet2 ${sta} ${rep} ${cov} ${psk} ${port} ${cpsk} > ${sta}_int.log & 79 | PIDS+=" $!" 80 | else 81 | nice ./testnet2 ${sta} ${rep} ${cov} ${psk} ${port} ${cpsk} > ${sta}_int.log & 82 | PIDS+=" $!" 83 | fi 84 | done 85 | 86 | # uses 6000 (client) as TNC and uses port 6001 just to stop the server 87 | sleep 2 88 | TNCERR=0 89 | ./switch_test.py 6000 6001 BBBB || TNCERR=1 90 | 91 | for eee in 6000 6001; do 92 | while echo -e '\r!reset\r' >/dev/tcp/localhost/$eee; do 93 | echo "Killing port $eee with reset" 94 | sleep 1 95 | done 96 | done 97 | 98 | ERR=0 99 | for P in $PIDS; do 100 | if ! wait $P; then 101 | echo $PIDS 102 | echo "PID $P exited with error" 103 | ERR=1 104 | fi 105 | done 106 | if [ "$TNCERR" != 0 ]; then 107 | echo 108 | echo Execution error in TNC client script, check logs 109 | echo 110 | exit 1 111 | fi 112 | if [ "$ERR" != 0 ]; then 113 | echo 114 | echo Execution error, check logs 115 | echo 116 | exit 1 117 | fi 118 | 119 | 120 | echo Interactive/TNC part II 121 | PIDS="" 122 | for eee in AAAA:0:1:None:6000:cryptok BBBB:0:1:None:6001:cryptok; do 123 | sta=$(echo $eee | cut -f 1 -d ':') 124 | rep=$(echo $eee | cut -f 2 -d ':') 125 | cov=$(echo $eee | cut -f 3 -d ':') 126 | psk=$(echo $eee | cut -f 4 -d ':') 127 | port=$(echo $eee | cut -f 5 -d ':') 128 | cpsk=$(echo $eee | cut -f 6 -d ':') 129 | if [ $VALGRIND = 1 ]; then 130 | nice valgrind --leak-check=full --show-reachable=yes --error-exitcode=1 --log-file=${sta}_int2.val --gen-suppressions=all \ 131 | --suppressions=valgrind.supp ./testnet2 ${sta} ${rep} ${cov} ${psk} ${port} ${cpsk} > ${sta}_int2.log & 132 | PIDS+=" $!" 133 | else 134 | nice ./testnet2 ${sta} ${rep} ${cov} ${psk} ${port} ${cpsk} > ${sta}_int2.log & 135 | PIDS+=" $!" 136 | fi 137 | done 138 | 139 | # uses 6000 (client) as TNC and uses port 6001 just to stop the server 140 | sleep 2 141 | TNCERR=0 142 | ./switch_test.py 6000 6001 BBBB || TNCERR=1 143 | 144 | for eee in 6000 6001; do 145 | while echo -e '\r!reset\r' >/dev/tcp/localhost/$eee; do 146 | echo "Killing port $eee with reset" 147 | sleep 1 148 | done 149 | done 150 | 151 | ERR=0 152 | for P in $PIDS; do 153 | if ! wait $P; then 154 | echo $PIDS 155 | echo "PID $P exited with error" 156 | ERR=1 157 | fi 158 | done 159 | if [ "$TNCERR" != 0 ]; then 160 | echo 161 | echo Execution error in TNC client script, check logs 162 | echo 163 | exit 1 164 | fi 165 | if [ "$ERR" != 0 ]; then 166 | echo 167 | echo Execution error, check logs 168 | echo 169 | exit 1 170 | fi 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | ver=$(date '+%Y-%m-%d %H:%M:%S') 183 | echo "#define LORAMADOR_VERSION \"$ver\"" > ../main/Version.h 184 | 185 | make coverage 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoRaMaDoR - ham radio based on LoRa 2 | 3 | LoRaMaDoR is a communication system based on 4 | LoRa digital radio. The 5 | name is a wordplay with "LoRa" and "radioamador" (ham radio in Portuguese). 6 | The most obvious use case is live chat, but there's potential for many other uses. 7 | 8 | This project is inspired by [LoRaHam](https://github.com/travisgoodspeed/loraham). 9 | The major enhancement is a packet format 10 | which is more powerful and more extensible, allowing for true network formation. 11 | 12 | LoRaMaDoR is a priori targeted for amateur 13 | radio operators. The callsign is the network address. But anyone can try it, 14 | actually. And they can even do it legally, since LoRa operates on unlicensed 15 | ISM bands. 16 | 17 | # Quick demo 18 | 19 | [In this video,](https://www.youtube.com/embed/nXooq_4EkzU) 20 | LoRaMaDoR is running in 3 or 4 Arduinos, and we are interacting with 21 | one of them via USB serial port. We send "human" messages, 22 | and also ping a remote station to get an automatic response. 23 | 24 |

25 | The idea of the command-line interface is to make the operation as simple 26 | as possible. To send a typical message, just type "callsign message"; the 27 | controller fills in the red tape to produce the final, valid packet. 28 | 29 |

30 | LoRaMaDoR works without anything connected to the serial port. It responds 31 | to PINGs and can forward packets. It can be extended to be an autonomous 32 | weather sensor, location beacon, etc. 33 | 34 | # Building the project 35 | 36 | This project needs some Arduino libraries that are straightforward 37 | to obtain (LoRa, OLED display, etc.). FWIW they are included in 38 | a ZIP file (tooling/loralibs.zip). 39 | 40 | We also depend on LoRaL2 project (https://github.com/elvis-epx/LoRaL2). 41 | This library is included via symlinks, and it is expected that LoRaL2 42 | project folder exists at the same level as LoRaMaDoR. 43 | 44 | # Console and command-line interface (CLI) 45 | 46 | Use the terminal software of your choice (screen, minicom, etc.) 47 | to connect to Arduino serial port. Set the speed to 115200bps 48 | and disable RTS/CTS handshaking if necessary. 49 | 50 | Alternatively, you can operate the console wirelessly using any 51 | Telnet client e.g. PuTTY. Just fill in your Wi-Fi configuration 52 | (details further down this document). 53 | 54 | Users can play with the protocol with (seemingly) bare hands. 55 | The implementation fills in the red tape to generate a valid packet. 56 | For example, a user types: 57 | 58 | ``` 59 | QC Chat tonight 22:00 at repeater 147.000 60 | ``` 61 | 62 | "QC" is the pseudo-destination for broadcast messages. The 63 | actual transmitted packet (minus the FEC suffix) is something like 64 | 65 | ``` 66 | QCPP5ABC>PU5XYZ|PP5CRE-11>PU5ABC 115 | ``` 116 | 117 | ## The protocol 118 | 119 | See the [Packet format page](PacketFormat.md) for technical details about the 120 | wire protocol. 121 | 122 | There is a [TNC mode](TNC.md) for interaction between a computer and 123 | the microcontroller. 124 | 125 | See the possible [non-amateur uses](NonAmateur.md) of this stack, and the 126 | [tentative roadmap](Roadmap.md) of the project. 127 | 128 | ## Console via Telnet 129 | 130 | The console can be operated via Telnet. This is more convenient when 131 | the Arduino is far away, perhaps positioned in a good antenna spot. This 132 | is possible because ESP32 TTGO has built-in support for Wi-Fi and TCP/IP. 133 | 134 | You just need to enter your network details in LoRaMaDoR via serial console, 135 | using the commands !ssid and !password. Then !reset and check the console 136 | messages to confirm the network configuration is ok. Use the !wifi command 137 | to get the detailed connection status. 138 | 139 | The IP address is shown upon connection on console, and can be queried via 140 | !wifi command. Generally you won't need to find out the IP, since 141 | LoRaMaDoR also supports Bonjour (mDNS). For example, if the station callsign is 142 | ABCDEF-1, the network name will be ABCDEF-1.local or abcdef-1.local 143 | (network names are case-insensitive). 144 | 145 | While a Telnet client is connected, the serial console is silenced (save 146 | for Wi-Fi disconnection messages) and does not accept input. Once the Telnet 147 | connection is closed, the serial console is reenabled. 148 | -------------------------------------------------------------------------------- /_pocs_/simulator/adv/sim_router.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LoRaMaDoR (LoRa-based mesh network for hams) project 4 | # Mesh network simulator / routing algorithms testbed 5 | # Copyright (c) 2019 PU5EPX 6 | 7 | import random, asyncio, sys, time 8 | from sim_packet import Packet 9 | 10 | ROUTER_VERBOSITY = 60 11 | 12 | CAN_DIFFUSE_UNKNOWN = Fals 13 | CAN_DIFFUSE_NOROUTE = False 14 | 15 | class Edge: 16 | def __init__(self, cost, expiry): 17 | self.cost = cost 18 | self.expiry = expiry 19 | 20 | class CachedEdge: 21 | def __init__(self, via, cost): 22 | self.via = via 23 | self.cost = cost 24 | 25 | class Router: 26 | def __init__(self, callsign): 27 | self.callsign = callsign 28 | self.graph = {} 29 | self.cache = {} 30 | 31 | async def run_expire(): 32 | while True: 33 | await asyncio.sleep(60) 34 | self.expire() 35 | 36 | loop = asyncio.get_event_loop() 37 | loop.create_task(run_expire()) 38 | 39 | def add_edge(self, to, fr0m, rssi, expiry): 40 | self.cache = {} 41 | if to not in self.graph: 42 | self.graph[to] = {} 43 | 44 | # Cost formula ######### 45 | cost = -rssi 46 | 47 | self.graph[to][fr0m] = Edge(cost, expiry) 48 | 49 | def expire(self): 50 | now = time.time() 51 | expired = [] 52 | 53 | for to, allfrom in self.graph.items(): 54 | for fr0m, item in allfrom.items(): 55 | if item.expiry < now: 56 | expired.append((to, fr0m)) 57 | if ROUTER_VERBOSITY > 90: 58 | print("%s rt: expiring edge %s < %s (%d)" % \ 59 | (self.callsign, to, fr0m, item.expiry)) 60 | 61 | if expired: 62 | self.cache = {} 63 | 64 | for to, fr0m in expired: 65 | del self.graph[to][fr0m] 66 | 67 | # Calculate next 'via' station. 68 | # Returns: "" to resort to diffusion routing 69 | # None if packet should not be repeated 70 | 71 | def get_next_hop(self, to): 72 | repeater, cost = self._get_next_hop(to, (to, )) 73 | if repeater == self.callsign: 74 | repeater = "QB" 75 | else: 76 | if ROUTER_VERBOSITY > 60: 77 | print("%s: route to %s via %s cost %d" % \ 78 | (self.callsign, to, repeater, cost)) 79 | return repeater 80 | 81 | def _get_next_hop(self, to, path): 82 | if to in ("QF", "QB", "QM", "QN"): 83 | # these always go by diffusion 84 | return "", 0 85 | elif to and to[0] == "Q": 86 | raise Exception("%s rt: asked route to %s" % (self.callsign, to)) 87 | elif to == self.callsign: 88 | # should not happen 89 | raise Exception("%s rt: asked route to itself" % self.callsign) 90 | 91 | recursion = "=|" * (len(path) - 1) 92 | if recursion: 93 | recursion += " " 94 | 95 | if to in self.cache: 96 | cached = self.cache[to] 97 | if ROUTER_VERBOSITY > 90: 98 | print("%s%s rt: using cached %s < %s < %s" % \ 99 | (recursion, self.callsign, to, cached.via, self.callsign)) 100 | return cached.via, cached.cost 101 | 102 | if to not in self.graph: 103 | # Unknown destination 104 | if CAN_DIFFUSE_UNKNOWN: 105 | if ROUTER_VERBOSITY > 50: 106 | print("%s%s rt: dest %s unknown, use diffusion" % \ 107 | (recursion, self.callsign, to)) 108 | return "", 999999999 109 | if ROUTER_VERBOSITY > 50: 110 | print("%s%s rt: dest %s unknown, cannot route" % \ 111 | (recursion, self.callsign, to)) 112 | return None, 999999999 113 | 114 | if self.callsign in self.graph[to]: 115 | # last hop, no actual routing 116 | return to, self.graph[to][self.callsign].cost 117 | 118 | if ROUTER_VERBOSITY > 90: 119 | print("%s%s rt: looking for route %s < %s" % \ 120 | (recursion, self.callsign, to, self.callsign)) 121 | 122 | # Try to find cheapest route, walking backwards from 'to' 123 | best_cost = 999999999 124 | best_via = None 125 | for penultimate, edge in self.graph[to].items(): 126 | if ROUTER_VERBOSITY > 90: 127 | print("%s \tlooking for route to %s" % (recursion, penultimate)) 128 | 129 | if penultimate in path: 130 | # would create a loop 131 | if ROUTER_VERBOSITY > 90: 132 | print("%s \t\tloop %s" % (recursion, str(path))) 133 | continue 134 | 135 | via, cost = self._get_next_hop(penultimate, (penultimate,) + path) 136 | cost += edge.cost 137 | 138 | if not via: 139 | if ROUTER_VERBOSITY > 90: 140 | print("%s \t\tno route" % recursion) 141 | continue 142 | 143 | if ROUTER_VERBOSITY > 90: 144 | print("%s \t\tcandidate %s < %s < %s < %s cost %d" % \ 145 | (recursion, to, penultimate, via, self.callsign, cost)) 146 | 147 | if not best_via or cost < best_cost: 148 | best_via = via 149 | best_cost = cost 150 | 151 | if not best_via: 152 | # Did not find route 153 | if CAN_DIFFUSE_NOROUTE: 154 | if ROUTER_VERBOSITY > 90: 155 | print("%s \troute not found, using diffusion" % recursion) 156 | return "", 999999999 157 | if ROUTER_VERBOSITY > 90: 158 | print("%s \troute not found, giving up" % recursion) 159 | return None, 999999999 160 | 161 | if ROUTER_VERBOSITY > 90: 162 | print("%s \tadopted %s < %s < %s cost %d" % (recursion, to, via, self.callsign, best_cost)) 163 | 164 | if best_via: 165 | self.cache[to] = CachedEdge(best_via, cost) 166 | if ROUTER_VERBOSITY > 90: 167 | print("%s%s rt: caching %s < %s < %s cost %d" % \ 168 | (recursion, self.callsign, to, best_via, self.callsign, cost)) 169 | 170 | return best_via, best_cost 171 | -------------------------------------------------------------------------------- /_pocs_/ttgo_receiver/images.h: -------------------------------------------------------------------------------- 1 | #define logo_width 128 2 | #define logo_height 53 3 | static char logo_bits[] = { 4 | 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 5 | 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 6 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xF0, 0x03, 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8 | 0xC0, 0x07, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0xF0, 0x1F, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0xE0, 0x1F, 0xF0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0xF8, 0x3F, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0xE0, 0x1F, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0xF0, 0x1F, 0xF8, 0x1F, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x1F, 0xFC, 0x1F, 19 | 0x80, 0xFF, 0x8F, 0x7F, 0xC0, 0xFF, 0x7F, 0xC0, 0xFF, 0x03, 0xC0, 0x3F, 20 | 0xF0, 0x1F, 0xFC, 0x1F, 0xE0, 0xFF, 0x87, 0x3F, 0xC0, 0xFF, 0x7F, 0xF0, 21 | 0xFF, 0x01, 0xF8, 0xFF, 0xF0, 0x0F, 0xFC, 0x1F, 0xE0, 0xFF, 0x87, 0x3F, 22 | 0xE0, 0xFF, 0x7F, 0xF8, 0xFF, 0x01, 0xFE, 0xFF, 0xF8, 0x0F, 0xFE, 0x0F, 23 | 0xF0, 0xFF, 0x87, 0x3F, 0xE0, 0xFF, 0x7F, 0xFC, 0xFF, 0x01, 0xFF, 0xFF, 24 | 0xF8, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xC7, 0x3F, 0xE0, 0xFF, 0x3F, 0xFC, 25 | 0xFF, 0x81, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xC3, 0x1F, 26 | 0xE0, 0xFF, 0x3F, 0xFC, 0xFF, 0xC0, 0xFF, 0x7F, 0xFC, 0xFF, 0xFF, 0x0F, 27 | 0xF8, 0xFF, 0xC3, 0x1F, 0xE0, 0xFF, 0x3F, 0xFC, 0xFF, 0xE0, 0xFF, 0x7F, 28 | 0xFC, 0xFF, 0xFF, 0x07, 0xF8, 0x03, 0xC0, 0x1F, 0x00, 0xFE, 0x01, 0xFE, 29 | 0x00, 0xF0, 0x3F, 0x70, 0xFC, 0xFF, 0xFF, 0x07, 0xF8, 0x03, 0xE0, 0x0F, 30 | 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xF0, 0x1F, 0x60, 0xFC, 0xFF, 0xFF, 0x07, 31 | 0xF8, 0x03, 0xE0, 0x0F, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xF0, 0x07, 0x20, 32 | 0xFE, 0xFF, 0xFF, 0x07, 0xFC, 0xFF, 0xE1, 0x0F, 0x00, 0xFF, 0x00, 0xFF, 33 | 0x7F, 0xF8, 0x07, 0x00, 0xFE, 0x83, 0xFF, 0x03, 0xFC, 0xFF, 0xF1, 0x0F, 34 | 0x00, 0x7F, 0x00, 0xFF, 0x7F, 0xF8, 0x03, 0x00, 0xFE, 0x83, 0xFF, 0x03, 35 | 0xFC, 0xFF, 0xF0, 0x07, 0x00, 0x7F, 0x00, 0xFF, 0x7F, 0xFC, 0x03, 0x00, 36 | 0xFE, 0x81, 0xFF, 0x03, 0xFC, 0xFF, 0xF0, 0x07, 0x00, 0x7F, 0x00, 0xFF, 37 | 0x3F, 0xFC, 0x03, 0x00, 0xFF, 0xC1, 0xFF, 0x01, 0xFE, 0xFF, 0xF0, 0x07, 38 | 0x80, 0x3F, 0x80, 0xFF, 0x3F, 0xFC, 0x03, 0x00, 0xFF, 0xC1, 0xFF, 0x01, 39 | 0xFE, 0xFF, 0xF8, 0x07, 0x80, 0x3F, 0x80, 0xFF, 0x3F, 0xFC, 0x03, 0x10, 40 | 0xFF, 0xC1, 0xFF, 0x01, 0xFE, 0x00, 0xF8, 0x03, 0x80, 0x3F, 0x80, 0x3F, 41 | 0x00, 0xFC, 0x03, 0x0C, 0xFF, 0xC0, 0xFF, 0x01, 0xFF, 0x00, 0xF8, 0x03, 42 | 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0xFC, 0x07, 0x0E, 0xFF, 0xE0, 0xFF, 0x00, 43 | 0x7F, 0x00, 0xF8, 0x03, 0xC0, 0x1F, 0xC0, 0x3F, 0x00, 0xFC, 0xFF, 0x0F, 44 | 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0x7F, 0xFC, 0xFF, 0xC1, 0x1F, 0xC0, 0xFF, 45 | 0x1F, 0xFC, 0xFF, 0x0F, 0x7F, 0xE0, 0xFF, 0x00, 0xFF, 0x3F, 0xFC, 0xFF, 46 | 0xC1, 0x1F, 0xC0, 0xFF, 0x0F, 0xF8, 0xFF, 0x07, 0x7E, 0xE0, 0xFF, 0x80, 47 | 0xFF, 0x3F, 0xFC, 0xFF, 0xE0, 0x1F, 0xC0, 0xFF, 0x0F, 0xF8, 0xFF, 0x07, 48 | 0x7C, 0xF0, 0x7F, 0x80, 0xFF, 0x3F, 0xFE, 0xFF, 0xE0, 0x0F, 0xE0, 0xFF, 49 | 0x0F, 0xF0, 0xFF, 0x07, 0xF8, 0xF0, 0x7F, 0x80, 0xFF, 0x1F, 0xFE, 0xFF, 50 | 0xE0, 0x0F, 0xE0, 0xFF, 0x0F, 0xE0, 0xFF, 0x07, 0xE0, 0xF0, 0x7F, 0x80, 51 | 0xFF, 0x1F, 0xFE, 0xFF, 0xE0, 0x0F, 0xE0, 0xFF, 0x07, 0x80, 0xFF, 0x03, 52 | 0x00, 0xF0, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x8F, 0xF7, 0xFF, 0x7C, 58 | 0xBC, 0xC7, 0xF3, 0xFF, 0xFC, 0xBC, 0x07, 0x00, 0x00, 0xFC, 0x1F, 0x00, 59 | 0x8F, 0x73, 0xFF, 0xFE, 0xBE, 0xC7, 0xFB, 0xFF, 0xFE, 0xBD, 0x03, 0x00, 60 | 0x00, 0xFC, 0x1F, 0x80, 0x8F, 0x73, 0xFF, 0xEF, 0xFE, 0xE7, 0xFB, 0x77, 61 | 0xEF, 0xFD, 0x03, 0x00, 0x00, 0xFC, 0x1F, 0x80, 0xDF, 0x7B, 0x9C, 0xE7, 62 | 0xFE, 0xF7, 0xE3, 0x71, 0xE7, 0xFD, 0x03, 0x00, 0x00, 0xFC, 0x0F, 0xC0, 63 | 0xDF, 0x79, 0x9E, 0xE3, 0xFE, 0xF3, 0xE3, 0x78, 0xE7, 0xFF, 0x03, 0x00, 64 | 0x00, 0xFC, 0x0F, 0xE0, 0xDF, 0x39, 0x8E, 0xF3, 0xFF, 0xFB, 0xE7, 0xF8, 65 | 0xE7, 0xFE, 0x01, 0x00, 0x00, 0xFC, 0x0F, 0xE0, 0xDF, 0x3F, 0x8E, 0x7F, 66 | 0xFF, 0xFF, 0xE7, 0x38, 0x7F, 0xEE, 0x01, 0x00, 0x00, 0xF8, 0x0F, 0x70, 67 | 0xDC, 0x1F, 0x0E, 0x3F, 0xFF, 0x9D, 0xF7, 0x38, 0x3F, 0xEF, 0x01, 0x00, 68 | 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 69 | 0x08, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,}; 75 | -------------------------------------------------------------------------------- /_pocs_/ttgo/images.h: -------------------------------------------------------------------------------- 1 | #define logo_width 128 2 | #define logo_height 53 3 | static char logo_bits[] = { 4 | 5 | 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 6 | 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xF0, 0x03, 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0xC0, 0x07, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0xF0, 0x1F, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0xE0, 0x1F, 0xF0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0xF8, 0x3F, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0xE0, 0x1F, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0xF0, 0x1F, 0xF8, 0x1F, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x1F, 0xFC, 0x1F, 20 | 0x80, 0xFF, 0x8F, 0x7F, 0xC0, 0xFF, 0x7F, 0xC0, 0xFF, 0x03, 0xC0, 0x3F, 21 | 0xF0, 0x1F, 0xFC, 0x1F, 0xE0, 0xFF, 0x87, 0x3F, 0xC0, 0xFF, 0x7F, 0xF0, 22 | 0xFF, 0x01, 0xF8, 0xFF, 0xF0, 0x0F, 0xFC, 0x1F, 0xE0, 0xFF, 0x87, 0x3F, 23 | 0xE0, 0xFF, 0x7F, 0xF8, 0xFF, 0x01, 0xFE, 0xFF, 0xF8, 0x0F, 0xFE, 0x0F, 24 | 0xF0, 0xFF, 0x87, 0x3F, 0xE0, 0xFF, 0x7F, 0xFC, 0xFF, 0x01, 0xFF, 0xFF, 25 | 0xF8, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xC7, 0x3F, 0xE0, 0xFF, 0x3F, 0xFC, 26 | 0xFF, 0x81, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xC3, 0x1F, 27 | 0xE0, 0xFF, 0x3F, 0xFC, 0xFF, 0xC0, 0xFF, 0x7F, 0xFC, 0xFF, 0xFF, 0x0F, 28 | 0xF8, 0xFF, 0xC3, 0x1F, 0xE0, 0xFF, 0x3F, 0xFC, 0xFF, 0xE0, 0xFF, 0x7F, 29 | 0xFC, 0xFF, 0xFF, 0x07, 0xF8, 0x03, 0xC0, 0x1F, 0x00, 0xFE, 0x01, 0xFE, 30 | 0x00, 0xF0, 0x3F, 0x70, 0xFC, 0xFF, 0xFF, 0x07, 0xF8, 0x03, 0xE0, 0x0F, 31 | 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xF0, 0x1F, 0x60, 0xFC, 0xFF, 0xFF, 0x07, 32 | 0xF8, 0x03, 0xE0, 0x0F, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xF0, 0x07, 0x20, 33 | 0xFE, 0xFF, 0xFF, 0x07, 0xFC, 0xFF, 0xE1, 0x0F, 0x00, 0xFF, 0x00, 0xFF, 34 | 0x7F, 0xF8, 0x07, 0x00, 0xFE, 0x83, 0xFF, 0x03, 0xFC, 0xFF, 0xF1, 0x0F, 35 | 0x00, 0x7F, 0x00, 0xFF, 0x7F, 0xF8, 0x03, 0x00, 0xFE, 0x83, 0xFF, 0x03, 36 | 0xFC, 0xFF, 0xF0, 0x07, 0x00, 0x7F, 0x00, 0xFF, 0x7F, 0xFC, 0x03, 0x00, 37 | 0xFE, 0x81, 0xFF, 0x03, 0xFC, 0xFF, 0xF0, 0x07, 0x00, 0x7F, 0x00, 0xFF, 38 | 0x3F, 0xFC, 0x03, 0x00, 0xFF, 0xC1, 0xFF, 0x01, 0xFE, 0xFF, 0xF0, 0x07, 39 | 0x80, 0x3F, 0x80, 0xFF, 0x3F, 0xFC, 0x03, 0x00, 0xFF, 0xC1, 0xFF, 0x01, 40 | 0xFE, 0xFF, 0xF8, 0x07, 0x80, 0x3F, 0x80, 0xFF, 0x3F, 0xFC, 0x03, 0x10, 41 | 0xFF, 0xC1, 0xFF, 0x01, 0xFE, 0x00, 0xF8, 0x03, 0x80, 0x3F, 0x80, 0x3F, 42 | 0x00, 0xFC, 0x03, 0x0C, 0xFF, 0xC0, 0xFF, 0x01, 0xFF, 0x00, 0xF8, 0x03, 43 | 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0xFC, 0x07, 0x0E, 0xFF, 0xE0, 0xFF, 0x00, 44 | 0x7F, 0x00, 0xF8, 0x03, 0xC0, 0x1F, 0xC0, 0x3F, 0x00, 0xFC, 0xFF, 0x0F, 45 | 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0x7F, 0xFC, 0xFF, 0xC1, 0x1F, 0xC0, 0xFF, 46 | 0x1F, 0xFC, 0xFF, 0x0F, 0x7F, 0xE0, 0xFF, 0x00, 0xFF, 0x3F, 0xFC, 0xFF, 47 | 0xC1, 0x1F, 0xC0, 0xFF, 0x0F, 0xF8, 0xFF, 0x07, 0x7E, 0xE0, 0xFF, 0x80, 48 | 0xFF, 0x3F, 0xFC, 0xFF, 0xE0, 0x1F, 0xC0, 0xFF, 0x0F, 0xF8, 0xFF, 0x07, 49 | 0x7C, 0xF0, 0x7F, 0x80, 0xFF, 0x3F, 0xFE, 0xFF, 0xE0, 0x0F, 0xE0, 0xFF, 50 | 0x0F, 0xF0, 0xFF, 0x07, 0xF8, 0xF0, 0x7F, 0x80, 0xFF, 0x1F, 0xFE, 0xFF, 51 | 0xE0, 0x0F, 0xE0, 0xFF, 0x0F, 0xE0, 0xFF, 0x07, 0xE0, 0xF0, 0x7F, 0x80, 52 | 0xFF, 0x1F, 0xFE, 0xFF, 0xE0, 0x0F, 0xE0, 0xFF, 0x07, 0x80, 0xFF, 0x03, 53 | 0x00, 0xF0, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 56 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x8F, 0xF7, 0xFF, 0x7C, 59 | 0xBC, 0xC7, 0xF3, 0xFF, 0xFC, 0xBC, 0x07, 0x00, 0x00, 0xFC, 0x1F, 0x00, 60 | 0x8F, 0x73, 0xFF, 0xFE, 0xBE, 0xC7, 0xFB, 0xFF, 0xFE, 0xBD, 0x03, 0x00, 61 | 0x00, 0xFC, 0x1F, 0x80, 0x8F, 0x73, 0xFF, 0xEF, 0xFE, 0xE7, 0xFB, 0x77, 62 | 0xEF, 0xFD, 0x03, 0x00, 0x00, 0xFC, 0x1F, 0x80, 0xDF, 0x7B, 0x9C, 0xE7, 63 | 0xFE, 0xF7, 0xE3, 0x71, 0xE7, 0xFD, 0x03, 0x00, 0x00, 0xFC, 0x0F, 0xC0, 64 | 0xDF, 0x79, 0x9E, 0xE3, 0xFE, 0xF3, 0xE3, 0x78, 0xE7, 0xFF, 0x03, 0x00, 65 | 0x00, 0xFC, 0x0F, 0xE0, 0xDF, 0x39, 0x8E, 0xF3, 0xFF, 0xFB, 0xE7, 0xF8, 66 | 0xE7, 0xFE, 0x01, 0x00, 0x00, 0xFC, 0x0F, 0xE0, 0xDF, 0x3F, 0x8E, 0x7F, 67 | 0xFF, 0xFF, 0xE7, 0x38, 0x7F, 0xEE, 0x01, 0x00, 0x00, 0xF8, 0x0F, 0x70, 68 | 0xDC, 0x1F, 0x0E, 0x3F, 0xFF, 0x9D, 0xF7, 0x38, 0x3F, 0xEF, 0x01, 0x00, 69 | 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 70 | 0x08, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; 76 | 77 | const char activeSymbol[] PROGMEM = { 78 | B00000000, 79 | B00000000, 80 | B00011000, 81 | B00100100, 82 | B01000010, 83 | B01000010, 84 | B00100100, 85 | B00011000 86 | }; 87 | 88 | const char inactiveSymbol[] PROGMEM = { 89 | B00000000, 90 | B00000000, 91 | B00000000, 92 | B00000000, 93 | B00011000, 94 | B00011000, 95 | B00000000, 96 | B00000000 97 | }; 98 | -------------------------------------------------------------------------------- /_pocs_/ttgo_sender/images.h: -------------------------------------------------------------------------------- 1 | #define logo_width 128 2 | #define logo_height 53 3 | static char logo_bits[] = { 4 | 5 | 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 6 | 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xF0, 0x03, 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0xC0, 0x07, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0xF0, 0x1F, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0xE0, 0x1F, 0xF0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0xF8, 0x3F, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0xE0, 0x1F, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0xF0, 0x1F, 0xF8, 0x1F, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x1F, 0xFC, 0x1F, 20 | 0x80, 0xFF, 0x8F, 0x7F, 0xC0, 0xFF, 0x7F, 0xC0, 0xFF, 0x03, 0xC0, 0x3F, 21 | 0xF0, 0x1F, 0xFC, 0x1F, 0xE0, 0xFF, 0x87, 0x3F, 0xC0, 0xFF, 0x7F, 0xF0, 22 | 0xFF, 0x01, 0xF8, 0xFF, 0xF0, 0x0F, 0xFC, 0x1F, 0xE0, 0xFF, 0x87, 0x3F, 23 | 0xE0, 0xFF, 0x7F, 0xF8, 0xFF, 0x01, 0xFE, 0xFF, 0xF8, 0x0F, 0xFE, 0x0F, 24 | 0xF0, 0xFF, 0x87, 0x3F, 0xE0, 0xFF, 0x7F, 0xFC, 0xFF, 0x01, 0xFF, 0xFF, 25 | 0xF8, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xC7, 0x3F, 0xE0, 0xFF, 0x3F, 0xFC, 26 | 0xFF, 0x81, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xC3, 0x1F, 27 | 0xE0, 0xFF, 0x3F, 0xFC, 0xFF, 0xC0, 0xFF, 0x7F, 0xFC, 0xFF, 0xFF, 0x0F, 28 | 0xF8, 0xFF, 0xC3, 0x1F, 0xE0, 0xFF, 0x3F, 0xFC, 0xFF, 0xE0, 0xFF, 0x7F, 29 | 0xFC, 0xFF, 0xFF, 0x07, 0xF8, 0x03, 0xC0, 0x1F, 0x00, 0xFE, 0x01, 0xFE, 30 | 0x00, 0xF0, 0x3F, 0x70, 0xFC, 0xFF, 0xFF, 0x07, 0xF8, 0x03, 0xE0, 0x0F, 31 | 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xF0, 0x1F, 0x60, 0xFC, 0xFF, 0xFF, 0x07, 32 | 0xF8, 0x03, 0xE0, 0x0F, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xF0, 0x07, 0x20, 33 | 0xFE, 0xFF, 0xFF, 0x07, 0xFC, 0xFF, 0xE1, 0x0F, 0x00, 0xFF, 0x00, 0xFF, 34 | 0x7F, 0xF8, 0x07, 0x00, 0xFE, 0x83, 0xFF, 0x03, 0xFC, 0xFF, 0xF1, 0x0F, 35 | 0x00, 0x7F, 0x00, 0xFF, 0x7F, 0xF8, 0x03, 0x00, 0xFE, 0x83, 0xFF, 0x03, 36 | 0xFC, 0xFF, 0xF0, 0x07, 0x00, 0x7F, 0x00, 0xFF, 0x7F, 0xFC, 0x03, 0x00, 37 | 0xFE, 0x81, 0xFF, 0x03, 0xFC, 0xFF, 0xF0, 0x07, 0x00, 0x7F, 0x00, 0xFF, 38 | 0x3F, 0xFC, 0x03, 0x00, 0xFF, 0xC1, 0xFF, 0x01, 0xFE, 0xFF, 0xF0, 0x07, 39 | 0x80, 0x3F, 0x80, 0xFF, 0x3F, 0xFC, 0x03, 0x00, 0xFF, 0xC1, 0xFF, 0x01, 40 | 0xFE, 0xFF, 0xF8, 0x07, 0x80, 0x3F, 0x80, 0xFF, 0x3F, 0xFC, 0x03, 0x10, 41 | 0xFF, 0xC1, 0xFF, 0x01, 0xFE, 0x00, 0xF8, 0x03, 0x80, 0x3F, 0x80, 0x3F, 42 | 0x00, 0xFC, 0x03, 0x0C, 0xFF, 0xC0, 0xFF, 0x01, 0xFF, 0x00, 0xF8, 0x03, 43 | 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0xFC, 0x07, 0x0E, 0xFF, 0xE0, 0xFF, 0x00, 44 | 0x7F, 0x00, 0xF8, 0x03, 0xC0, 0x1F, 0xC0, 0x3F, 0x00, 0xFC, 0xFF, 0x0F, 45 | 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0x7F, 0xFC, 0xFF, 0xC1, 0x1F, 0xC0, 0xFF, 46 | 0x1F, 0xFC, 0xFF, 0x0F, 0x7F, 0xE0, 0xFF, 0x00, 0xFF, 0x3F, 0xFC, 0xFF, 47 | 0xC1, 0x1F, 0xC0, 0xFF, 0x0F, 0xF8, 0xFF, 0x07, 0x7E, 0xE0, 0xFF, 0x80, 48 | 0xFF, 0x3F, 0xFC, 0xFF, 0xE0, 0x1F, 0xC0, 0xFF, 0x0F, 0xF8, 0xFF, 0x07, 49 | 0x7C, 0xF0, 0x7F, 0x80, 0xFF, 0x3F, 0xFE, 0xFF, 0xE0, 0x0F, 0xE0, 0xFF, 50 | 0x0F, 0xF0, 0xFF, 0x07, 0xF8, 0xF0, 0x7F, 0x80, 0xFF, 0x1F, 0xFE, 0xFF, 51 | 0xE0, 0x0F, 0xE0, 0xFF, 0x0F, 0xE0, 0xFF, 0x07, 0xE0, 0xF0, 0x7F, 0x80, 52 | 0xFF, 0x1F, 0xFE, 0xFF, 0xE0, 0x0F, 0xE0, 0xFF, 0x07, 0x80, 0xFF, 0x03, 53 | 0x00, 0xF0, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 56 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x8F, 0xF7, 0xFF, 0x7C, 59 | 0xBC, 0xC7, 0xF3, 0xFF, 0xFC, 0xBC, 0x07, 0x00, 0x00, 0xFC, 0x1F, 0x00, 60 | 0x8F, 0x73, 0xFF, 0xFE, 0xBE, 0xC7, 0xFB, 0xFF, 0xFE, 0xBD, 0x03, 0x00, 61 | 0x00, 0xFC, 0x1F, 0x80, 0x8F, 0x73, 0xFF, 0xEF, 0xFE, 0xE7, 0xFB, 0x77, 62 | 0xEF, 0xFD, 0x03, 0x00, 0x00, 0xFC, 0x1F, 0x80, 0xDF, 0x7B, 0x9C, 0xE7, 63 | 0xFE, 0xF7, 0xE3, 0x71, 0xE7, 0xFD, 0x03, 0x00, 0x00, 0xFC, 0x0F, 0xC0, 64 | 0xDF, 0x79, 0x9E, 0xE3, 0xFE, 0xF3, 0xE3, 0x78, 0xE7, 0xFF, 0x03, 0x00, 65 | 0x00, 0xFC, 0x0F, 0xE0, 0xDF, 0x39, 0x8E, 0xF3, 0xFF, 0xFB, 0xE7, 0xF8, 66 | 0xE7, 0xFE, 0x01, 0x00, 0x00, 0xFC, 0x0F, 0xE0, 0xDF, 0x3F, 0x8E, 0x7F, 67 | 0xFF, 0xFF, 0xE7, 0x38, 0x7F, 0xEE, 0x01, 0x00, 0x00, 0xF8, 0x0F, 0x70, 68 | 0xDC, 0x1F, 0x0E, 0x3F, 0xFF, 0x9D, 0xF7, 0x38, 0x3F, 0xEF, 0x01, 0x00, 69 | 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 70 | 0x08, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; 76 | 77 | const char activeSymbol[] PROGMEM = { 78 | B00000000, 79 | B00000000, 80 | B00011000, 81 | B00100100, 82 | B01000010, 83 | B01000010, 84 | B00100100, 85 | B00011000 86 | }; 87 | 88 | const char inactiveSymbol[] PROGMEM = { 89 | B00000000, 90 | B00000000, 91 | B00000000, 92 | B00000000, 93 | B00011000, 94 | B00011000, 95 | B00000000, 96 | B00000000 97 | }; 98 | --------------------------------------------------------------------------------