├── .gitignore ├── Documentation └── Images │ ├── packet.jpg │ ├── welcome.jpg │ └── interface.jpg ├── Code ├── .gitignore ├── include │ ├── webserver.h │ ├── display.h │ ├── L1.h │ ├── message.h │ ├── L3.h │ ├── L2.h │ ├── config.h │ └── typedefs.h ├── platformio.ini └── src │ ├── main.cpp │ ├── display.cpp │ ├── webserver.cpp │ ├── message.cpp │ ├── L2.cpp │ ├── L3.cpp │ └── L1.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Documentation/Other 2 | .pio 3 | .vscode -------------------------------------------------------------------------------- /Documentation/Images/packet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheNico14/LoRaMessenger/HEAD/Documentation/Images/packet.jpg -------------------------------------------------------------------------------- /Documentation/Images/welcome.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheNico14/LoRaMessenger/HEAD/Documentation/Images/welcome.jpg -------------------------------------------------------------------------------- /Documentation/Images/interface.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheNico14/LoRaMessenger/HEAD/Documentation/Images/interface.jpg -------------------------------------------------------------------------------- /Code/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /Code/include/webserver.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file webserver.h 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief Webserver functions 8 | */ 9 | 10 | #ifndef WEBSERVER_H 11 | #define WEBSERVER_H 12 | 13 | // Functions 14 | void webserver_init(); 15 | void webserver_loop(); 16 | 17 | #endif -------------------------------------------------------------------------------- /Code/include/display.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file display.ch 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief Display functions 8 | */ 9 | 10 | #ifndef DISPLAY_H 11 | #define DISPLAY_H 12 | 13 | // Functions 14 | void display_init(); 15 | void display_turnOff(); 16 | void display_printWelcome(); 17 | void display_printLastMessage(char *message, uint8_t sender_node); 18 | 19 | #endif -------------------------------------------------------------------------------- /Code/include/L1.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file L1.h 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief OSI layer 1: Physical layer. 8 | * This layer takes care of sending and receiving LoRa packets and 9 | * controls the anti-collision and duty cycle timings. 10 | */ 11 | 12 | #ifndef L1_H 13 | #define L1_H 14 | 15 | #include "typedefs.h" 16 | 17 | // Functions 18 | void L1_init(); 19 | return_type L1_enqueue_outPacket(pack_struct packet); 20 | return_type L1_send_outPacket(); 21 | return_type L1_receive(); 22 | 23 | void L1_printPacket(pack_struct packet); 24 | 25 | #endif -------------------------------------------------------------------------------- /Code/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:ttgo-lora32-v1] 12 | platform = espressif32 13 | board = ttgo-lora32-v1 14 | framework = arduino 15 | 16 | 17 | monitor_speed = 115200 18 | 19 | lib_deps = 20 | AsyncTCP 21 | DNSServer 22 | ESP Async WebServer@1.1.0 23 | LoRa 24 | U8g2 -------------------------------------------------------------------------------- /Code/include/message.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file message.h 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief Messages list functions 8 | */ 9 | 10 | #ifndef MESSAGE_H 11 | #define MESSAGE_H 12 | 13 | #include "typedefs.h" 14 | 15 | // Functions 16 | void message_init(); 17 | 18 | return_type message_save(uint8_t receiver, uint8_t sender, char *message, uint32_t id); 19 | return_type message_saveAck(uint8_t sender, uint32_t id); 20 | 21 | uint8_t message_getAckNode(uint8_t sender, uint32_t id, uint8_t ack_number); 22 | uint8_t message_getAckNum(uint8_t sender, uint32_t id); 23 | 24 | void message_printLastN(int number); 25 | 26 | int message_checkDuplicate(uint8_t sender, uint32_t id); 27 | String message_getStringMessageList(); 28 | #endif -------------------------------------------------------------------------------- /Code/include/L3.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file L3.h 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief OSI layer 3: Network layer. 8 | * This layer takes care of nodes network and packet routing 9 | */ 10 | 11 | #ifndef L3_H 12 | #define L3_h 13 | 14 | // Functions 15 | void L3_init(); 16 | 17 | void L3_updateNode(); 18 | int L3_removeInactiveNodes(); 19 | 20 | void L3_printNodes(); 21 | int elapsedSeconds(uint32_t timestamp); 22 | 23 | int L3_getActive(uint8_t destination); 24 | int L3_getNextNode(uint8_t destination); 25 | int L3_getHops(uint8_t destination); 26 | int L3_getRssi(uint8_t destination); 27 | int L3_getLastID(uint8_t destination); 28 | char *L3_getNodeName(uint8_t destination); 29 | int L3_getNodeNumber(char *name); 30 | 31 | return_type L3_handlePacket(pack_struct packet); 32 | return_type L3_handleAnnounce(pack_struct packet); 33 | String L3_getStringNodeList(); 34 | 35 | #endif -------------------------------------------------------------------------------- /Code/include/L2.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file L2.h 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief OSI layer 2: Datalink layer. 8 | * This layer handles received packets, relaying of packets and 9 | * provides functions for sending announces, messages 10 | * and acknowledgments. 11 | */ 12 | 13 | #ifndef L2_H 14 | #define L2_h 15 | 16 | #include "typedefs.h" 17 | 18 | // Functions 19 | return_type L2_handleMessage(pack_struct packet); 20 | return_type L2_handleacknowledgment(pack_struct packet); 21 | return_type L2_handleAnnounce(pack_struct packet); 22 | 23 | return_type L2_sendMessage(uint8_t receiver, char *message); 24 | return_type L2_sendacknowledgment(uint8_t receiver, uint32_t packet_id); 25 | return_type L2_sendAnnounce(); 26 | 27 | void *L2_setPayloadMessage(char *message); 28 | void *L2_setPayloadacknowledgment(uint32_t packet_id); 29 | void *L2_setPayloadAnnounce(char *name, uint8_t name_size); 30 | 31 | #endif -------------------------------------------------------------------------------- /Code/include/config.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file config.h 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief LoRaMessenger configuration. 8 | */ 9 | 10 | // LoRa config (check allowed parameters in your country!) 11 | #define LORABAND 868E6 // LoRa frequency: 433E6, 866E6, 915E6 12 | #define SPREADINGFACTOR 7 // Spreading factor 13 | #define TXDBM 20 // TX power of the radio. 14 | 15 | #define LORADUTY 1 // TX max duty cycle 16 | #define NETID 121 // Network id 17 | 18 | // L1 config (needs to be the same on each node!) 19 | #define L1BUFFER 20 // Packet queue, increase if using high spreading factor 20 | #define TTL 2 // Packet Time To Live (maximum number of hops) 21 | #define BROADCASTADDR 255 // Broadcast address 22 | 23 | // L3 config 24 | #define NODENUMBER 1 // Node number (1-n) 25 | #define MAXNODES 10 // Maximum nodes in network 26 | #define ANNOUNCEMINS 1 // Availability announce interval (min) 27 | #define INACTIVEMINS 3 // Inactivity time needed to consider a node offline (min) 28 | #define INACTIVESECONDSREMOVECHECK 10 // Interval for checking inactive nodes (sec) 29 | 30 | // Messages config 31 | #define SHOWNMESSAGES 5 // Number of messages to display on web interface 32 | #define KEEPNMESSAGES 20 // Number of messages to keep in memory 33 | 34 | // Display config 35 | #define DISPLAYSTBYSECS 10 // Display standby time (sec) 36 | 37 | // Network config 38 | #define WIFIENABLED 1 // Wi-Fi enabled 39 | #define NODENAMEOVERRIDEEN 0 // Node name override enable (ex: relay without Wi-Fi) 40 | #define NODENAMEOVERRIDE "Home" // Node name override 41 | #define WIFISSID "LoRaMessenger" // Wi-Fi prefix (ex: LoRaMessenger 1) 42 | #define DNSPORT 53 // DNS port 43 | 44 | // Pinout 45 | #define SCK 5 46 | #define MISO 19 47 | #define MOSI 27 48 | #define SS 18 49 | #define RST 23 50 | #define DI0 26 51 | #define I2CSCL 22 52 | #define I2CSDA 21 53 | #define LCDRESET 16 -------------------------------------------------------------------------------- /Code/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file main.cpp 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief Main code of LoRaMessenger 8 | */ 9 | 10 | // Include libraries 11 | #include 12 | #include "config.h" 13 | #include "typedefs.h" 14 | #include "L1.h" 15 | #include "L2.h" 16 | #include "L3.h" 17 | #include "message.h" 18 | #include "display.h" 19 | #include "webserver.h" 20 | 21 | // Imported variables 22 | extern int L1_outBuffer_left; 23 | extern bool L1_flag_received; 24 | extern bool display_flag_screenOn; 25 | 26 | extern uint32_t display_standby_timer; 27 | 28 | // Global variables 29 | uint32_t announce_mins = INACTIVEMINS * 60000; 30 | uint32_t announce_timer = 0; 31 | uint32_t announce_remove_seconds_check = INACTIVESECONDSREMOVECHECK * 1000; 32 | uint32_t announce_remove_timer = 0; 33 | uint32_t display_standby_secs = DISPLAYSTBYSECS * 1000; 34 | uint8_t showmessages = SHOWNMESSAGES; 35 | uint8_t tx_dbm = TXDBM; 36 | uint8_t spreading_factor = SPREADINGFACTOR; 37 | 38 | // Global Flags 39 | 40 | /** 41 | * @brief LoRaMessenger setup 42 | * 43 | */ 44 | void setup() 45 | { 46 | Serial.begin(115200); 47 | 48 | L1_init(); 49 | 50 | L3_init(); 51 | 52 | message_init(); 53 | 54 | if (WIFIENABLED) 55 | webserver_init(); 56 | 57 | display_init(); 58 | display_printWelcome(); 59 | } 60 | 61 | /** 62 | * @brief LoRaMessenger main loop 63 | * 64 | */ 65 | void loop() 66 | { 67 | // Packet received 68 | if (L1_flag_received) 69 | { 70 | L1_flag_received = 0; 71 | L1_receive(); 72 | } 73 | 74 | // Webserver 75 | if (WIFIENABLED) 76 | webserver_loop(); 77 | 78 | // Packet ready to send 79 | if (L1_outBuffer_left) 80 | L1_send_outPacket(); 81 | 82 | // Send announce to network 83 | if ((millis() - announce_timer) > announce_mins) 84 | { 85 | announce_timer = millis(); 86 | L2_sendAnnounce(); 87 | } 88 | 89 | // Check for inactive nodes 90 | if ((millis() - announce_remove_timer) > announce_remove_seconds_check) 91 | { 92 | announce_remove_timer = millis(); 93 | L3_removeInactiveNodes(); 94 | } 95 | 96 | // Turn off display 97 | if ((millis() - display_standby_timer) > display_standby_secs) 98 | { 99 | display_turnOff(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Code/include/typedefs.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file typedefs.h 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief Typedef definitions 8 | */ 9 | 10 | #ifndef TYPEDEFS_H 11 | #define TYPEDEFS_H 12 | 13 | #include 14 | 15 | /** 16 | * @brief Packet payload type 17 | * 18 | */ 19 | typedef enum payload_type 20 | { 21 | payload_msg = 0, 22 | payload_ack, 23 | payload_ann 24 | } payload_type; 25 | 26 | /** 27 | * @brief Function return type 28 | * 29 | */ 30 | typedef enum return_type 31 | { 32 | ret_ok = 0, 33 | ret_error, 34 | ret_buffer_empty, 35 | ret_buffer_full, 36 | ret_send_duty_error, 37 | ret_send_anticollision_error, 38 | ret_send_error, 39 | ret_send_size_error, 40 | ret_receive_netid_error, 41 | ret_receive_wrong_node, 42 | ret_receive_duplicate, 43 | ret_ttl_error, 44 | ret_routing_worse, 45 | ret_routing_better, 46 | ret_routing_updated, 47 | ret_message_not_found, 48 | ret_message_found 49 | } return_type; 50 | 51 | /** 52 | * @brief Packet structure 53 | * 54 | */ 55 | typedef struct 56 | { 57 | uint8_t ttl; 58 | uint8_t receiver; 59 | uint8_t sender; 60 | uint8_t last_node; 61 | uint8_t next_node; 62 | uint32_t id; 63 | uint8_t type; 64 | void *payload; 65 | int rssi; 66 | } pack_struct; 67 | 68 | /** 69 | * @brief Message payload structure 70 | * 71 | */ 72 | typedef struct 73 | { 74 | uint8_t message_size; 75 | char *message_ptr; 76 | } payload_message_struct; 77 | 78 | /** 79 | * @brief Acknowledgment payload structure 80 | * 81 | */ 82 | typedef struct 83 | { 84 | uint32_t packet_id; 85 | } payload_acknowledgment_struct; 86 | 87 | /** 88 | * @brief Announce payload structure 89 | * 90 | */ 91 | typedef struct 92 | { 93 | uint8_t name_size; 94 | char *name_ptr; 95 | } payload_announce_struct; 96 | 97 | /** 98 | * @brief Routing table structure 99 | * 100 | */ 101 | typedef struct 102 | { 103 | uint8_t active; 104 | uint8_t next_node; 105 | uint8_t hops; 106 | int8_t rssi; 107 | uint32_t last_id; 108 | char name[16]; 109 | uint32_t timestamp; 110 | } routing_table_struct; 111 | 112 | /** 113 | * @brief Message structure 114 | * 115 | */ 116 | typedef struct 117 | { 118 | uint8_t receiver; 119 | uint8_t sender; 120 | uint32_t id; 121 | char *message; 122 | uint8_t acks; 123 | uint8_t acks_nodes[MAXNODES - 1]; 124 | } message_struct; 125 | 126 | #endif -------------------------------------------------------------------------------- /Code/src/display.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file display.cpp 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief Display functions 8 | */ 9 | 10 | // Include libraries 11 | #include 12 | #include "config.h" 13 | #include "typedefs.h" 14 | #include "display.h" 15 | #include "L3.h" 16 | #include 17 | #include 18 | 19 | U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(I2CSCL, I2CSDA, LCDRESET); 20 | 21 | // Imported variables 22 | extern char node_name[16]; 23 | extern char wifi_ssid[20]; 24 | 25 | // Exported variables 26 | uint32_t display_standby_timer = 0; 27 | bool display_flag_screenOn = 0; 28 | 29 | // Functions 30 | 31 | /** 32 | * @brief Initializes the display 33 | * 34 | */ 35 | void display_init() 36 | { 37 | u8x8.begin(); 38 | u8x8.setFont(u8x8_font_artossans8_r); 39 | u8x8.setFlipMode(0); 40 | } 41 | 42 | /** 43 | * @brief Turns off the display 44 | * 45 | */ 46 | void display_turnOff() 47 | { 48 | display_flag_screenOn = false; 49 | u8x8.setPowerSave(1); 50 | } 51 | 52 | /** 53 | * @brief Prints startup information 54 | * 55 | */ 56 | void display_printWelcome() 57 | { 58 | char string[17]; 59 | sprintf(string, "Node number: %-2d", NODENUMBER); 60 | 61 | u8x8.clear(); 62 | u8x8.drawString(0, 0, "LoRaMessenger"); 63 | u8x8.drawString(0, 3, string); 64 | if (WIFIENABLED) 65 | { 66 | u8x8.drawString(0, 5, "Wi-Fi hotspot:"); 67 | u8x8.drawString(0, 6, wifi_ssid); 68 | } 69 | else 70 | { 71 | u8x8.drawString(0, 5, "Node name:"); 72 | u8x8.drawString(0, 6, L3_getNodeName(NODENUMBER)); 73 | } 74 | 75 | display_flag_screenOn = true; 76 | display_standby_timer = millis(); 77 | u8x8.setPowerSave(0); 78 | } 79 | 80 | /** 81 | * @brief Prints last message received 82 | * 83 | * @param message: Pointer to message to be printed 84 | * @param sender_node: Sender node number 85 | */ 86 | void display_printLastMessage(char *message, uint8_t sender_node) 87 | { 88 | char sender_name[16]; 89 | char message_str[17]; 90 | 91 | strcpy(sender_name, L3_getNodeName(sender_node)); 92 | 93 | int message_length = strlen(message); 94 | 95 | if (message_length > 48) 96 | message_length = 48; 97 | 98 | u8x8.clear(); 99 | u8x8.drawString(0, 0, "Message from:"); 100 | u8x8.drawString(0, 1, sender_name); 101 | 102 | strncpy(message_str, message, 16); 103 | u8x8.drawString(0, 4, message_str); 104 | for (int i = 1; i < 3; i++) 105 | { 106 | if (message_length > (16 * i)) 107 | { 108 | strncpy(message_str, message + (16 * i), 16); 109 | u8x8.drawString(0, 4 + i, message_str); 110 | } 111 | } 112 | 113 | display_flag_screenOn = true; 114 | display_standby_timer = millis(); 115 | u8x8.setPowerSave(0); 116 | } 117 | -------------------------------------------------------------------------------- /Code/src/webserver.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file webserver.cpp 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief Webserver functions 8 | */ 9 | 10 | // Include libraries 11 | #include 12 | #include "config.h" 13 | #include "typedefs.h" 14 | #include "webserver.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "L2.h" 21 | #include "L3.h" 22 | #include "message.h" 23 | 24 | char wifi_ssid[20]; 25 | 26 | AsyncWebServer webServer(80); 27 | DNSServer dnsServer; 28 | 29 | // Imported variables 30 | extern char node_name[16]; 31 | 32 | // Variables 33 | char recipient[16] = "Broadcast"; 34 | 35 | // IP 36 | IPAddress ap_local_IP(1, 1, 1, 1); 37 | IPAddress ap_subnet(255, 255, 255, 0); 38 | 39 | // Private Functions 40 | String index_html(); 41 | 42 | // Functions 43 | 44 | /** 45 | * @brief Initializes webserver 46 | * 47 | */ 48 | void webserver_init() 49 | { 50 | sprintf(wifi_ssid, "%s %-2d", WIFISSID, NODENUMBER); 51 | 52 | WiFi.mode(WIFI_AP); 53 | WiFi.softAP(wifi_ssid); 54 | delay(2000); 55 | WiFi.softAPConfig(ap_local_IP, ap_local_IP, ap_subnet); 56 | 57 | dnsServer.start(DNSPORT, "*", ap_local_IP); 58 | 59 | webServer.on("/", [](AsyncWebServerRequest *request) { 60 | request->send(200, "text/html", index_html()); 61 | }); 62 | webServer.on("/generate_204", [](AsyncWebServerRequest *request) { 63 | request->send(200, "text/html", index_html()); 64 | }); 65 | webServer.on("/captive-portal/api", [](AsyncWebServerRequest *request) { 66 | request->send(200, "text/html", index_html()); 67 | }); 68 | webServer.on("/rename", HTTP_POST, [](AsyncWebServerRequest *request) { 69 | AsyncWebParameter *p = request->getParam(0); 70 | if (strlen(p->value().c_str()) > 0 && strlen(p->value().c_str()) < 15) 71 | { 72 | if (strcmp(p->value().c_str(), "Broadcast") != 0 && strcmp(p->value().c_str(), "broadcast") != 0) 73 | { 74 | strcpy(node_name, p->value().c_str()); 75 | L3_updateNode(); 76 | L2_sendAnnounce(); 77 | } 78 | } 79 | request->redirect("/"); 80 | }); 81 | 82 | webServer.on("/send", HTTP_POST, [](AsyncWebServerRequest *request) { 83 | { 84 | AsyncWebParameter *p = request->getParam(0); 85 | 86 | if (strlen(p->value().c_str()) > 0 && strlen(p->value().c_str()) < 15) 87 | { 88 | strcpy(recipient, p->value().c_str()); 89 | } 90 | } 91 | { 92 | AsyncWebParameter *p = request->getParam(1); 93 | if (strlen(p->value().c_str()) > 0 && strlen(p->value().c_str()) < 160) 94 | { 95 | L2_sendMessage(L3_getNodeNumber(recipient), const_cast(p->value().c_str())); 96 | } 97 | } 98 | request->redirect("/"); 99 | }); 100 | 101 | webServer.on("/refresh", [](AsyncWebServerRequest *request) { 102 | request->redirect("/"); 103 | }); 104 | 105 | webServer.begin(); 106 | return; 107 | } 108 | 109 | /** 110 | * @brief Processes dns requests 111 | * 112 | */ 113 | void webserver_loop() 114 | { 115 | dnsServer.processNextRequest(); 116 | } 117 | 118 | /** 119 | * @brief Creates a string containg the web page 120 | * 121 | * @return String 122 | */ 123 | String index_html() 124 | { 125 | String html = "" 126 | "LoRaMessenger" 127 | "" 128 | "" 137 | "" 138 | "

" 139 | "
" 142 | "

    " + 143 | L3_getStringNodeList() + 144 | "

    " + 145 | message_getStringMessageList() + 146 | "

" 147 | "


" 150 | "
"; 151 | return html; 152 | } -------------------------------------------------------------------------------- /Code/src/message.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file message.cpp 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief Messages list functions 8 | */ 9 | 10 | // Include libraries 11 | #include 12 | #include "config.h" 13 | #include "typedefs.h" 14 | #include "L1.h" 15 | #include "L2.h" 16 | #include "L3.h" 17 | #include "message.h" 18 | #include "rom/crc.h" 19 | 20 | // Private 21 | message_struct message_list[KEEPNMESSAGES]; 22 | int write_index_msg = 0; 23 | 24 | // Imported variables 25 | extern uint8_t showmessages; 26 | 27 | // Functions 28 | 29 | /** 30 | * @brief Initializes messages list 31 | * 32 | */ 33 | void message_init() 34 | { 35 | for (int i = 0; i < KEEPNMESSAGES; i++) 36 | { 37 | memset(&message_list[i], 0, sizeof(message_struct)); 38 | message_list[i].message = NULL; 39 | } 40 | 41 | return; 42 | } 43 | 44 | /** 45 | * @brief Saves a message into messages list 46 | * 47 | * @param receiver: Message receiver 48 | * @param sender: Message sender 49 | * @param message: Pointer to message to be saved 50 | * @param id: Message id 51 | * @return return_type status 52 | */ 53 | return_type message_save(uint8_t receiver, uint8_t sender, char *message, uint32_t id) 54 | { 55 | message_list[write_index_msg].sender = sender; 56 | message_list[write_index_msg].receiver = receiver; 57 | message_list[write_index_msg].id = id; 58 | message_list[write_index_msg].acks = 0; 59 | 60 | message_list[write_index_msg].message = (char *)realloc(message_list[write_index_msg].message, strlen(message) + 1); 61 | strcpy(message_list[write_index_msg].message, message); 62 | 63 | if (write_index_msg == KEEPNMESSAGES - 1) 64 | write_index_msg = 0; 65 | else 66 | write_index_msg++; 67 | return ret_ok; 68 | } 69 | 70 | /** 71 | * @brief Saves an acknowledgment into messages list 72 | * 73 | * @param sender: Message sender 74 | * @param id: Message id 75 | * @return return_type status 76 | */ 77 | return_type message_saveAck(uint8_t sender, uint32_t id) 78 | { 79 | return_type ret; 80 | for (int i = 0; i < KEEPNMESSAGES; i++) 81 | { 82 | ret = ret_message_not_found; 83 | bool already_saved = 0; 84 | 85 | if (message_list[i].receiver == sender || message_list[i].receiver == BROADCASTADDR) 86 | { 87 | if (message_list[i].id == id) 88 | { 89 | 90 | for (int j = 0; j < MAXNODES - 1; j++) 91 | { 92 | if (message_list[i].acks_nodes[j] == sender) 93 | { 94 | already_saved = 1; 95 | } 96 | } 97 | if (!already_saved) 98 | { 99 | message_list[i].acks_nodes[message_list[i].acks] = sender; 100 | message_list[i].acks++; 101 | ret = ret_ok; 102 | } 103 | } 104 | } 105 | } 106 | return ret; 107 | } 108 | 109 | /** 110 | * @brief Returns node number from message acknowledgment list 111 | * 112 | * @param sender: Message sender 113 | * @param id: Message id 114 | * @param ack_number: acknowledgment list index 115 | * @return uint8_t node number 116 | */ 117 | uint8_t message_getAckNode(uint8_t sender, uint32_t id, uint8_t ack_number) 118 | { 119 | uint8_t ret = 0; 120 | for (int i = 0; i < KEEPNMESSAGES; i++) 121 | { 122 | if (message_list[i].receiver == sender && message_list[i].id == id) 123 | { 124 | ret = message_list[i].acks_nodes[ack_number]; 125 | } 126 | } 127 | return ret; 128 | } 129 | 130 | /** 131 | * @brief Returns number of acknowledgments for a given message 132 | * 133 | * @param sender: Message sender 134 | * @param id: Message id 135 | * @return uint8_t acknowledgments number 136 | */ 137 | uint8_t message_getAckNum(uint8_t sender, uint32_t id) 138 | { 139 | for (int i = 0; i < KEEPNMESSAGES; i++) 140 | { 141 | if (message_list[i].receiver == sender && message_list[i].id == id) 142 | { 143 | return message_list[i].acks; 144 | } 145 | } 146 | return 0; 147 | } 148 | 149 | /** 150 | * @brief Prints the last n messages 151 | * 152 | * @param number: Number of messages to print 153 | */ 154 | void message_printLastN(int number) 155 | { 156 | int read_position = write_index_msg; 157 | 158 | if (number > KEEPNMESSAGES) 159 | number = KEEPNMESSAGES; 160 | 161 | if (read_position - number < 0) 162 | read_position = KEEPNMESSAGES + read_position - number; 163 | else 164 | read_position = read_position - number; 165 | 166 | Serial.printf("--- Last %d messages ---\n", number); 167 | for (int i = 0; i < number; i++) 168 | { 169 | 170 | if (read_position == KEEPNMESSAGES) 171 | read_position = 0; 172 | 173 | if (message_list[read_position].message != NULL) 174 | { 175 | char receiver_name[16]; 176 | char sender_name[16]; 177 | 178 | strcpy(receiver_name, L3_getNodeName(message_list[read_position].receiver)); 179 | strcpy(sender_name, L3_getNodeName(message_list[read_position].sender)); 180 | 181 | Serial.printf("%s->%s: %s\n", sender_name, receiver_name, message_list[read_position].message); 182 | } 183 | read_position++; 184 | } 185 | 186 | Serial.printf("\n"); 187 | return; 188 | } 189 | 190 | /** 191 | * @brief Checks if a message is already been saved 192 | * 193 | * @param sender: Message sender 194 | * @param id: Message id 195 | * @return int 196 | */ 197 | int message_checkDuplicate(uint8_t sender, uint32_t id) 198 | { 199 | for (int i = 0; i < KEEPNMESSAGES; i++) 200 | { 201 | if (message_list[i].sender == sender && message_list[i].id == id) 202 | { 203 | return ret_message_found; 204 | } 205 | } 206 | return ret_message_not_found; 207 | } 208 | 209 | /** 210 | * @brief Creates a string containg the message list (webserver) 211 | * 212 | * @return String message list 213 | */ 214 | String message_getStringMessageList() 215 | { 216 | int read_position = write_index_msg; 217 | int number = showmessages; 218 | 219 | String list = ""; 220 | 221 | if (number > KEEPNMESSAGES) 222 | number = KEEPNMESSAGES; 223 | 224 | if (read_position - number < 0) 225 | read_position = KEEPNMESSAGES + read_position - number; 226 | else 227 | read_position = read_position - number; 228 | 229 | for (int i = 0; i < number; i++) 230 | { 231 | 232 | if (read_position == KEEPNMESSAGES) 233 | read_position = 0; 234 | 235 | if (message_list[read_position].message != NULL) 236 | { 237 | char receiver_name[16]; 238 | char sender_name[16]; 239 | 240 | strcpy(receiver_name, L3_getNodeName(message_list[read_position].receiver)); 241 | strcpy(sender_name, L3_getNodeName(message_list[read_position].sender)); 242 | 243 | list += "
  • " + String(sender_name) + " -> " + String(receiver_name) + ": " + String(message_list[read_position].message); 244 | if (message_list[read_position].acks) 245 | { 246 | list += "
    Received by:"; 247 | for (int i = 0; i < message_list[read_position].acks; i++) 248 | { 249 | if (i > 0) 250 | list += ","; 251 | list += " " + String(L3_getNodeName(message_list[read_position].acks_nodes[i])); 252 | } 253 | } 254 | } 255 | list += "
  • "; 256 | read_position++; 257 | } 258 | return list; 259 | } -------------------------------------------------------------------------------- /Code/src/L2.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file L2.cpp 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief OSI layer 2: Datalink layer. 8 | * This layer handles received packets, relaying of packets and 9 | * provides functions for sending announces, messages 10 | * and acknowledgments. 11 | */ 12 | 13 | // Include libraries 14 | #include 15 | #include "config.h" 16 | #include "typedefs.h" 17 | #include "L1.h" 18 | #include "L2.h" 19 | #include "L3.h" 20 | #include "message.h" 21 | #include "display.h" 22 | 23 | // Private functions 24 | return_type L2_relayPacket(pack_struct packet); 25 | 26 | // Imported variables 27 | extern char node_name[16]; 28 | 29 | // Private variables 30 | 31 | // Functions 32 | 33 | /** 34 | * @brief Handles a received message packet 35 | * 36 | * @param packet: Packet to be handled 37 | * @return return_type status 38 | */ 39 | return_type L2_handleMessage(pack_struct packet) 40 | { 41 | if (packet.sender != NODENUMBER && (packet.next_node == NODENUMBER || packet.next_node == BROADCASTADDR)) 42 | { 43 | if (packet.receiver == NODENUMBER || packet.receiver == BROADCASTADDR) 44 | { 45 | if (packet.receiver == BROADCASTADDR && message_checkDuplicate(packet.sender, packet.id) == ret_message_found) 46 | return ret_receive_duplicate; 47 | 48 | message_save(NODENUMBER, packet.sender, ((payload_message_struct *)packet.payload)->message_ptr, packet.id); 49 | L2_sendacknowledgment(packet.sender, packet.id); 50 | 51 | message_printLastN(5); 52 | 53 | display_printLastMessage(((payload_message_struct *)packet.payload)->message_ptr, packet.sender); 54 | } 55 | 56 | if (packet.receiver != NODENUMBER && packet.ttl > 1) 57 | { 58 | L2_relayPacket(packet); 59 | } 60 | } 61 | return ret_ok; 62 | } 63 | 64 | /** 65 | * @brief Handles a received acknowledgment packet 66 | * 67 | * @param packet: Packet to be handled 68 | * @return return_type status 69 | */ 70 | return_type L2_handleacknowledgment(pack_struct packet) 71 | { 72 | if (packet.sender != NODENUMBER && (packet.next_node == NODENUMBER || packet.next_node == BROADCASTADDR)) 73 | { 74 | if (packet.receiver == NODENUMBER) 75 | { 76 | message_saveAck(packet.sender, ((payload_acknowledgment_struct *)packet.payload)->packet_id); 77 | int acks = message_getAckNum(packet.sender, ((payload_acknowledgment_struct *)packet.payload)->packet_id); 78 | int node_number; 79 | 80 | for (int i = 0; i < acks; i++) 81 | { 82 | node_number = message_getAckNode(packet.sender, ((payload_acknowledgment_struct *)packet.payload)->packet_id, i); 83 | Serial.printf("Message received by %s\n\n", L3_getNodeName(node_number)); 84 | } 85 | } 86 | 87 | if (packet.receiver != NODENUMBER && packet.ttl > 1) 88 | { 89 | L2_relayPacket(packet); 90 | } 91 | } 92 | return ret_ok; 93 | } 94 | 95 | /** 96 | * @brief Handles a received announce packet 97 | * 98 | * @param packet: Packet to be handled 99 | * @return return_type status 100 | */ 101 | return_type L2_handleAnnounce(pack_struct packet) 102 | { 103 | if (packet.receiver == BROADCASTADDR && packet.sender != NODENUMBER) 104 | { 105 | L3_handleAnnounce(packet); 106 | 107 | if (packet.ttl > 1) 108 | { 109 | L2_relayPacket(packet); 110 | } 111 | return ret_ok; 112 | } 113 | return ret_error; 114 | } 115 | 116 | /** 117 | * @brief Relays a packet 118 | * 119 | * @param original_packet: Packet to be relayed 120 | * @return return_type status 121 | */ 122 | return_type L2_relayPacket(pack_struct original_packet) 123 | { 124 | if (original_packet.receiver == 0 || original_packet.receiver == NODENUMBER || (original_packet.receiver > MAXNODES && original_packet.receiver != BROADCASTADDR)) 125 | return ret_send_error; 126 | 127 | if (original_packet.ttl == 0) 128 | return ret_ttl_error; 129 | 130 | pack_struct packet = original_packet; 131 | 132 | packet.ttl--; 133 | packet.last_node = NODENUMBER; 134 | packet.next_node = L3_getNextNode(original_packet.receiver); 135 | 136 | return L1_enqueue_outPacket(packet); 137 | } 138 | 139 | /** 140 | * @brief Sends a message 141 | * 142 | * @param receiver: Receiver node 143 | * @param message: Pointer to message to be sent 144 | * @return return_type status 145 | */ 146 | return_type L2_sendMessage(uint8_t receiver, char *message) 147 | { 148 | int message_size = strlen(message); 149 | 150 | if (message_size == 0 || message_size > 161) 151 | return ret_send_size_error; 152 | 153 | if (receiver == 0 || receiver == NODENUMBER || (receiver > MAXNODES && receiver != BROADCASTADDR)) 154 | return ret_send_error; 155 | 156 | pack_struct packet; 157 | 158 | packet.ttl = TTL; 159 | packet.receiver = receiver; 160 | packet.sender = NODENUMBER; 161 | packet.last_node = NODENUMBER; 162 | packet.next_node = L3_getNextNode(receiver); 163 | packet.id = millis(); 164 | packet.type = payload_msg; 165 | 166 | packet.payload = L2_setPayloadMessage(message); 167 | 168 | message_save(packet.receiver, NODENUMBER, ((payload_message_struct *)packet.payload)->message_ptr, packet.id); 169 | 170 | message_printLastN(5); 171 | 172 | return L1_enqueue_outPacket(packet); 173 | } 174 | 175 | /** 176 | * @brief Sends an acknowledgment packet 177 | * 178 | * @param receiver: Receiver node 179 | * @param packet_id: Packet id 180 | * @return return_type status 181 | */ 182 | return_type L2_sendacknowledgment(uint8_t receiver, uint32_t packet_id) 183 | { 184 | if (receiver == 0 || receiver == NODENUMBER || (receiver > MAXNODES && receiver != BROADCASTADDR)) 185 | return ret_send_error; 186 | 187 | pack_struct packet; 188 | 189 | packet.ttl = TTL; 190 | packet.receiver = receiver; 191 | packet.sender = NODENUMBER; 192 | packet.last_node = NODENUMBER; 193 | packet.next_node = L3_getNextNode(receiver); 194 | packet.id = millis(); 195 | packet.type = payload_ack; 196 | 197 | packet.payload = L2_setPayloadacknowledgment(packet_id); 198 | 199 | return L1_enqueue_outPacket(packet); 200 | } 201 | 202 | /** 203 | * @brief Sends a network announce packet 204 | * 205 | * @return return_type status 206 | */ 207 | return_type L2_sendAnnounce() 208 | { 209 | int name_size = strlen(node_name); 210 | 211 | if (name_size == 0 || name_size > 15) 212 | return ret_send_size_error; 213 | 214 | pack_struct packet; 215 | 216 | packet.ttl = TTL; 217 | packet.receiver = BROADCASTADDR; 218 | packet.sender = NODENUMBER; 219 | packet.last_node = NODENUMBER; 220 | packet.next_node = BROADCASTADDR; 221 | packet.id = millis(); 222 | packet.type = payload_ann; 223 | 224 | packet.payload = L2_setPayloadAnnounce(node_name, name_size); 225 | 226 | return L1_enqueue_outPacket(packet); 227 | } 228 | 229 | /** 230 | * @brief Sets packet payload as message 231 | * 232 | * @param message: Pointer to message to be sent 233 | * @return void* payload pointer 234 | */ 235 | void *L2_setPayloadMessage(char *message) 236 | { 237 | payload_message_struct *payload_message; 238 | payload_message = (payload_message_struct *)malloc(sizeof(payload_message_struct)); 239 | int message_size = strlen(message); 240 | 241 | payload_message->message_size = message_size; 242 | payload_message->message_ptr = (char *)malloc(message_size + 1); 243 | strcpy(payload_message->message_ptr, message); 244 | 245 | return payload_message; 246 | } 247 | 248 | /** 249 | * @brief Sets packet payload as acknowledgment 250 | * 251 | * @param packet_id: Pointer to message to be sent 252 | * @return void* payload pointer 253 | */ 254 | void *L2_setPayloadacknowledgment(uint32_t packet_id) 255 | { 256 | payload_acknowledgment_struct *payload_acknowledgment; 257 | payload_acknowledgment = (payload_acknowledgment_struct *)malloc(sizeof(payload_acknowledgment_struct)); 258 | 259 | payload_acknowledgment->packet_id = packet_id; 260 | 261 | return payload_acknowledgment; 262 | } 263 | 264 | /** 265 | * @brief Sets packet payload as network announce 266 | * 267 | * @param name: Pointer to node name 268 | * @param name_size: Name length 269 | * @return void* payload pointer 270 | */ 271 | void *L2_setPayloadAnnounce(char *name, uint8_t name_size) 272 | { 273 | payload_announce_struct *payload_announce; 274 | payload_announce = (payload_announce_struct *)malloc(sizeof(payload_announce_struct)); 275 | 276 | payload_announce->name_size = name_size; 277 | payload_announce->name_ptr = (char *)malloc(name_size + 1); 278 | strcpy(payload_announce->name_ptr, name); 279 | 280 | return payload_announce; 281 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoRaMessenger 2 | 3 | LoRaMessenger is an off-grid chat between LoRa equipped ESP32 nodes with a simple wireless web interface. 4 | 5 | 6 | 7 | The ESP32 hosts a Wi-Fi network that will provide a simple chat-like interface that will display online nodes, received and sent messages, and some text boxes to set the node name, destination node, and message. 8 | 9 | Since the LoRa communication protocol is used to send and receive messages, it is possible to set two or more modules to transmit and receive messages up to a distance of a few kilometers. 10 | 11 | Most ESP32 LoRa modules, such as the TTGO LoRa32 or Heltec Wifi LoRa 32, are equipped with a display, so you can use them independently to receive messages like a pager. 12 | 13 | 14 | 15 | A node can be easily installed inside a small box with a battery, thus creating a communication system that can communicate even in areas where there is no phone signal. 16 | 17 | ## Web interface 18 | 19 | After the ESP32 is started, a new Wi-Fi network called LoRaMessenger [number] is created. 20 | 21 | When a device connects to the network, the web interface should open automatically, if nothing happens, the interface can be accessed by opening a web browser and navigating to the ESP32 IP, which is by default 1.1.1.1. 22 | 23 | On android, a wifi login page containing the web inteface is opened after connecting to the Wi-Fi network. This way, background 4g network connection is mantained even when using LoRaMessenger. A notification saying login to the network should stay into the notification panel and can be opened anytime to show the interface again. 24 | 25 | The web interface is now presented on your browser, the chat has the following features: 26 | 27 | - At the top of the page, the node name can be entered so that the recipient knows who is writing. After pressing update, the name is saved and sent to all reachable nodes. 28 | - The online section shows all available nodes detected, with some additional information such as the relay node that is being used by the receiving node if present, the receiving RSSI, the number of hops between relays, and the time elapsed since the last contact. 29 | - The message section shows the last 5 (by default, user-settable) sent and received messages in chronological order. 30 | The name of all the nodes that have received the message correctly is indicated under each message. 31 | - At the bottom of the page, there are two text boxes, the first one is used for setting the destination node and the second one to write the message.\ 32 | The destination field contains the Broadcast value by default. This way the message is sent to all available nodes. You can also write the name of a node exactly as reported in the online section to send the message only to a specific recipient. 33 | 34 | Note that as of right now a page refresh is necessary to update the received messages and read receipts. 35 | 36 | ## LoRa protocol 37 | 38 | LoRaMessenger uses a custom communication protocol, each packet sent consists of a header and a payload. 39 | 40 | 41 | 42 | The header provides the information needed for the network and packet routing to work properly, the parameters contained in the header are as follows: 43 | 44 | - NETID: Network ID, specified in config.h. This allows the creation of multiple independent networks. 45 | - TTL: Packet time to live, specified in config.h. This value is used to know how many hops a packet has done and is needed by the routing algorithm. 46 | - RECEIVER: Receiver node number. 47 | - SENDER: Sender node number. 48 | - LAST NODE: Sender node number or last node that relayed the packet. 49 | - NEXT NODE: Receiver node number or next node needed to relay the packet to the receiver node. 50 | - ID: Packet ID, each packet sent from the same node has its unique 4 bytes long ID. This is needed to discard already received packets and for sending a received acknowledgment. 51 | - PAYLOAD TYPE: Payload type, used for correctly interpreting the payload. Possible payloads types are: Message, Acknowledgment, and Announce. 52 | 53 | Message payload: 54 | 55 | - MESSAGE SIZE: Message size in bytes, needed for message reading. 56 | - MESSAGE: Message content. This is sent in plain text for now! 57 | 58 | Acknowledgment payload: 59 | 60 | - RECEIVED PACKET ID: ID from received message packet. This is sent back to the sender to let him know that the packet has been received. 61 | 62 | Announce payload: 63 | 64 | - NAME SIZE: Node name size in bytes, needed for name reading. 65 | - NODE NAME: Node name. This is displayed on every node web interface and can be written in the destination field to send a message to only a specific node. 66 | 67 | ## Packet relaying and routing 68 | 69 | LoRaMessenger creates a network of nodes capable of forwarding messages to nodes not directly reachable by the sender. 70 | 71 | To do this, each node utilizes an automatic routing table containing the destination nodes and the best route to reach them. 72 | The table is updated through announcement packets that are sent periodically or upon name change by all nodes. 73 | 74 | The current routing algorithm is very simple and prefers a lower number of hops, in the case of two routes with the same number of hops the one with the connection to the next strongest node is chosen. 75 | 76 | ## Installation 77 | 78 | This program can be easily installed by importing the project in platformio, updating the settings, and uploading it to the boards. 79 | 80 | ## Configuration 81 | 82 | Into the includes folder, a configuration file called config.h is present. This file contains all the settings necessary for LoRaMessenger to function. 83 | 84 | LoRa config: 85 | 86 | - LORABAND: LoRa chip frequency. The frequency depends on your board and local allowed frequencies, please be sure to use only allowed frequencies in your country, [more info here](https://www.thethingsnetwork.org/wiki/LoRaWAN/Frequencies/By-Country).\ 87 | Possible values: 433E6, 866E6, 915E6. 88 | - SPREADINGFACTOR: LoRa spreading factor. Be careful when using values higher than 7 because LoRaMessenger respects the transmission duty-cycle. High values greatly slow down the waiting time between transmissions and could affect the correct operation, other adjustments like anticollision time may be needed. 89 | Possible values: 7 - 12. 90 | - TXDBM: Transmission power of LoRa chip.\ 91 | Possible values: 1 - 20 92 | - LORADUTY: Transmission duty-cycle. Be sure to use only allowed values in your country. 93 | Possible values: 1 - 99. 94 | - NETID: LoRaMessenger network id. This allows the creation of multiple independent networks.\ 95 | Possible values: 0 - 255. 96 | 97 | L1 config: 98 | 99 | - L1BUFFER: Transmission packet queue. Increase if using big networks of nodes or using high spreading factors. 100 | - TTL: Packet time to live. Sets the maximum number of hops that a packet can do before expiring.\ 101 | Possible values: 1 (only direct messages, no relaying), >1. 102 | - BROADCASTADDR: Broadcast address number. 103 | 104 | L3 config: 105 | 106 | - NODENUMBER: Local node number. Each node needs a different node number! You can think this as the equivalent of an IP address for a regular network.\ 107 | Possible values: 1 - 255. Caution not to use the same address of BROADCASTADDR! 108 | - MAXNODES: Max number of nodes expected in the network. 109 | - ANNOUNCEMINS: Node presence announce and name update. This message is needed to inform all nodes of the presence of all other nodes. The interval can be increased to prevent spam if using static nodes, high spreading factors, or big networks. 110 | - INACTIVEMINS: Inactivity time needed for a node to be considered offline. Caution to use at least 2-3 times the value of ANNOUNCEMINS or even bigger if poor reception. 111 | - INACTIVESECONDSREMOVECHECK: Interval for checking the removal of offline nodes. 112 | 113 | Messages config: 114 | 115 | - SHOWNMESSAGES: Number of messages to display on the web interface. 116 | - KEEPNMESSAGES: Number of messages to keep in memory. 117 | 118 | Display config: 119 | 120 | - DISPLAYSTBYSECS: Number of seconds after the display is switched off. 121 | 122 | Network config: 123 | 124 | - WIFIENABLED: Wi-Fi enabled. This can be used to reduce power used in case of deploying a node only for relaying of messages. 125 | - NODENAMEOVERRIDEEN: Node name override enable. Used to override the default node name (Node [number]) to the string specified in NODENAMEOVERRIDE. 126 | - NODENAMEOVERRIDE: Nome name override. This can be used for setting the node name without modifying it on the web interface. Useful when setting up a relay only node. 127 | - WIFISSID: Wi-Fi network name prefix (LoRaMessenger [number]) 128 | - DNSPORT: DNS port. 129 | 130 | Pinout: 131 | 132 | - SCK: SPI clock. 133 | - MISO: SPI MISO. 134 | - MOSI: SPI MOSI. 135 | - SS: SPI slave select. 136 | - RST: LoRa chip reset. 137 | - DI0: LoRa receive callback. 138 | - I2CSCL: I2C clock. 139 | - I2CSDA: I2C data. 140 | - LCDRESET: LCD reset. 141 | 142 | Pin definitions may need to be edited in case another board is used (Pin definitions are based on a TTGO LoRa32 V2). 143 | 144 | ## Future improvements/fixes 145 | 146 | Other features that are planned for the future are: 147 | 148 | - Message encryption, as of right one all messages are sent unencrypted. 149 | - Automatic message refresh. 150 | - Testing and improvements of routing algorithm. 151 | 152 | ## License 153 | 154 | MIT license. 155 | -------------------------------------------------------------------------------- /Code/src/L3.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file L3.cpp 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief OSI layer 3: Network layer. 8 | * This layer takes care of nodes network and packet routing 9 | */ 10 | 11 | // Include libraries 12 | #include 13 | #include "config.h" 14 | #include "typedefs.h" 15 | #include "L1.h" 16 | #include "L2.h" 17 | #include "L3.h" 18 | 19 | // Exported variables 20 | char node_name[16]; 21 | 22 | // Private variables 23 | routing_table_struct routing_table[MAXNODES]; 24 | 25 | /** 26 | * @brief Initializes the L3 layer 27 | * 28 | */ 29 | void L3_init() 30 | { 31 | memset(routing_table, 0, MAXNODES * sizeof(routing_table_struct)); 32 | 33 | routing_table[NODENUMBER - 1].active = 1; 34 | if (NODENAMEOVERRIDEEN) 35 | strcpy(node_name, NODENAMEOVERRIDE); 36 | else 37 | sprintf(node_name, "Node %d", NODENUMBER); 38 | strcpy(routing_table[NODENUMBER - 1].name, node_name); 39 | 40 | L2_sendAnnounce(); 41 | 42 | return; 43 | } 44 | 45 | /** 46 | * @brief Sets node name and timestamp of this node 47 | * 48 | */ 49 | void L3_updateNode() 50 | { 51 | routing_table[NODENUMBER - 1].timestamp = millis(); 52 | routing_table[NODENUMBER - 1].active = 1; 53 | strcpy(routing_table[NODENUMBER - 1].name, node_name); 54 | 55 | return; 56 | } 57 | 58 | /** 59 | * @brief Searches and removes inactive nodes 60 | * 61 | * @return int number of removed nodes 62 | */ 63 | int L3_removeInactiveNodes() 64 | { 65 | int ret = 0; 66 | 67 | for (int i = 0; i < MAXNODES; i++) 68 | { 69 | if (i != (NODENUMBER - 1) && routing_table[i].active) 70 | { 71 | if ((elapsedSeconds(routing_table[i].timestamp) / 60) >= INACTIVEMINS) 72 | { 73 | routing_table[i].active = 0; 74 | Serial.printf("Removed node %s from routing list\n\n", L3_getNodeName(i)); 75 | 76 | ret++; 77 | } 78 | } 79 | } 80 | 81 | if (ret > 0) 82 | L3_printNodes(); 83 | 84 | return ret; 85 | } 86 | 87 | /** 88 | * @brief Prints current routing table 89 | * 90 | */ 91 | void L3_printNodes() 92 | { 93 | Serial.printf("--- Routing table ---\n\n"); 94 | for (int i = 0; i < MAXNODES; i++) 95 | { 96 | if (routing_table[i].active) 97 | { 98 | Serial.printf("Name: %s\n", routing_table[i].name); 99 | Serial.printf("Destination: %d\n", i + 1); 100 | Serial.printf("Next hop: %d\n", routing_table[i].next_node); 101 | Serial.printf("Hops: %d\n", routing_table[i].hops); 102 | Serial.printf("ID: %d\n", routing_table[i].last_id); 103 | Serial.printf("RSSI: %d\n", routing_table[i].rssi); 104 | Serial.printf("Updated: %d seconds ago\n\n", elapsedSeconds(routing_table[i].timestamp)); 105 | } 106 | } 107 | return; 108 | } 109 | 110 | /** 111 | * @brief Returns elapsed seconds since timestamp 112 | * 113 | * @param timestamp: Timestamp to compare with current timestamp 114 | * @return int seconds elapsed 115 | */ 116 | int elapsedSeconds(uint32_t timestamp) 117 | { 118 | return (millis() - timestamp) / 1000; 119 | } 120 | 121 | /** 122 | * @brief Returns if a node is active 123 | * 124 | * @param destination: Node to check 125 | * @return int state 126 | */ 127 | int L3_getActive(uint8_t destination) 128 | { 129 | return routing_table[destination - 1].active; 130 | } 131 | 132 | /** 133 | * @brief Returns next node needed to reach destination node 134 | * 135 | * @param destination: Destination node 136 | * @return int node number 137 | */ 138 | int L3_getNextNode(uint8_t destination) 139 | { 140 | if (destination == BROADCASTADDR) 141 | return BROADCASTADDR; 142 | else if (routing_table[destination - 1].active) 143 | return routing_table[destination - 1].next_node; 144 | else 145 | return 0; 146 | } 147 | 148 | /** 149 | * @brief Returns number of hops needed to reach destination node 150 | * 151 | * @param destination: Destination node 152 | * @return int number of hops 153 | */ 154 | int L3_getHops(uint8_t destination) 155 | { 156 | return routing_table[destination - 1].hops; 157 | } 158 | 159 | /** 160 | * @brief Returns RSSI of destination node 161 | * 162 | * @param destination: Destination node 163 | * @return int RSSI 164 | */ 165 | int L3_getRssi(uint8_t destination) 166 | { 167 | return routing_table[destination - 1].rssi; 168 | } 169 | 170 | /** 171 | * @brief Returns the name of the destination node given node number 172 | * 173 | * @param destination: Destination node 174 | * @return char* node name 175 | */ 176 | char *L3_getNodeName(uint8_t destination) 177 | { 178 | static char broadcast_string[10] = "Broadcast"; 179 | if (destination == BROADCASTADDR) 180 | return broadcast_string; 181 | else 182 | return routing_table[destination - 1].name; 183 | } 184 | 185 | /** 186 | * @brief Returns the number of the destination node given node name 187 | * 188 | * @param name: Pointer to the name to be searched 189 | * @return int node number 190 | */ 191 | int L3_getNodeNumber(char *name) 192 | { 193 | for (int i = 0; i < MAXNODES; i++) 194 | { 195 | if (routing_table[i].active) 196 | { 197 | if (strcmp(name, routing_table[i].name) == 0 && i != NODENUMBER - 1) 198 | return i + 1; 199 | } 200 | } 201 | if (strcmp(name, "Broadcast") == 0 || strcmp(name, "broadcast") == 0) 202 | return BROADCASTADDR; 203 | else 204 | return 0; 205 | } 206 | 207 | /** 208 | * @brief Returns the last id associated to a node 209 | * 210 | * @param destination: Destination node 211 | * @return int id 212 | */ 213 | int L3_getLastID(uint8_t destination) 214 | { 215 | return routing_table[destination - 1].last_id; 216 | } 217 | 218 | /** 219 | * @brief Handles routing table after a generic packet is received 220 | * 221 | * @param packet: Packet to be handled 222 | * @return return_type status 223 | */ 224 | return_type L3_handlePacket(pack_struct packet) 225 | { 226 | int packet_rssi = packet.rssi; 227 | int packet_hops = TTL - packet.ttl; 228 | 229 | routing_table[packet.last_node - 1].timestamp = millis(); 230 | routing_table[packet.last_node - 1].rssi = packet_rssi; 231 | 232 | if (packet.sender != NODENUMBER) 233 | { 234 | if (routing_table[packet.last_node - 1].active == 0) 235 | { 236 | routing_table[packet.last_node - 1].active = 1; 237 | routing_table[packet.last_node - 1].next_node = packet.last_node; 238 | routing_table[packet.last_node - 1].hops = 0; 239 | routing_table[packet.last_node - 1].last_id = 0; 240 | strcpy(routing_table[packet.last_node - 1].name, "Unknown"); 241 | } 242 | 243 | if (packet_hops > 0) 244 | { 245 | routing_table[packet.sender - 1].timestamp = millis(); 246 | if (routing_table[packet.sender - 1].active == 0) 247 | { 248 | routing_table[packet.sender - 1].active = 1; 249 | routing_table[packet.sender - 1].next_node = packet.last_node; 250 | routing_table[packet.sender - 1].hops = packet_hops; 251 | routing_table[packet.sender - 1].last_id = 0; 252 | strcpy(routing_table[packet.sender - 1].name, "Unknown"); 253 | } 254 | } 255 | return ret_ok; 256 | } 257 | return ret_error; 258 | } 259 | 260 | /** 261 | * @brief Handles routing table after an announce packet is received 262 | * 263 | * @param packet: Packet to be handled 264 | * @return return_type status 265 | */ 266 | return_type L3_handleAnnounce(pack_struct packet) 267 | { 268 | int packet_rssi = packet.rssi; 269 | int packet_hops = TTL - packet.ttl; 270 | 271 | int current_hops = routing_table[packet.sender - 1].hops; 272 | int current_rssi = routing_table[packet.sender - 1].rssi; 273 | 274 | strncpy(routing_table[packet.sender - 1].name, ((payload_announce_struct *)packet.payload)->name_ptr, ((payload_announce_struct *)packet.payload)->name_size); 275 | routing_table[packet.sender - 1].name[((payload_announce_struct *)packet.payload)->name_size] = 0; 276 | 277 | if (packet.id == routing_table[packet.sender - 1].last_id) 278 | { 279 | if (packet_hops < current_hops || (packet_hops == current_hops && packet_rssi > current_rssi)) 280 | { 281 | routing_table[packet.sender - 1].rssi = packet_rssi; 282 | routing_table[packet.sender - 1].next_node = packet.last_node; 283 | routing_table[packet.sender - 1].hops = packet_hops; 284 | return ret_routing_better; 285 | } 286 | else 287 | return ret_routing_worse; 288 | } 289 | else 290 | { 291 | routing_table[packet.sender - 1].rssi = packet_rssi; 292 | routing_table[packet.sender - 1].last_id = packet.id; 293 | routing_table[packet.sender - 1].next_node = packet.last_node; 294 | routing_table[packet.sender - 1].hops = packet_hops; 295 | return ret_routing_updated; 296 | } 297 | } 298 | 299 | /** 300 | * @brief Creates a string containg active nodes (webserver) 301 | * 302 | * @return String active nodes 303 | */ 304 | String L3_getStringNodeList() 305 | { 306 | String list = ""; 307 | for (int i = 0; i < MAXNODES; i++) 308 | { 309 | if (routing_table[i].active && i != NODENUMBER - 1) 310 | { 311 | list += "
  • " + String(routing_table[i].name) + ""; 312 | if (routing_table[i].hops > 0) 313 | { 314 | list += " via " + String(L3_getNodeName(routing_table[i].next_node)); 315 | } 316 | 317 | list += " | RSSI: " + String(routing_table[i].rssi) + " | Hops: " + String(routing_table[i].hops) + 318 | +" | " + String(elapsedSeconds(routing_table[i].timestamp)) + "s ago
  • "; 319 | } 320 | } 321 | return list; 322 | } -------------------------------------------------------------------------------- /Code/src/L1.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file L1.cpp 3 | * @author Nicholas Polledri 4 | * @version 1.0 5 | * @date 09-08-2020 6 | * 7 | * @brief OSI layer 1: Physical layer. 8 | * This layer takes care of sending and receiving LoRa packets and 9 | * controls the anti-collision and duty cycle timings. 10 | */ 11 | 12 | #include 13 | #include "config.h" 14 | #include "typedefs.h" 15 | #include "L1.h" 16 | #include "L2.h" 17 | #include "L3.h" 18 | #include 19 | #include 20 | 21 | // Exported variables 22 | int L1_outBuffer_left = 0; 23 | bool L1_flag_received = 0; 24 | 25 | // Imported variables 26 | extern uint8_t tx_dbm; 27 | extern uint8_t spreading_factor; 28 | 29 | // Private variables 30 | static pack_struct outBuffer[L1BUFFER]; 31 | static int outBuffer_front = 0; 32 | static int outBuffer_rear = 0; 33 | 34 | uint32_t transmit_duration = 0; 35 | uint32_t last_transmit_timestamp = 0; 36 | uint32_t last_receive_timestamp = 0; 37 | uint32_t anticollision_time; 38 | 39 | // Private functions 40 | void L1_onReceive(int packetSize); 41 | return_type L1_packSend(pack_struct packet); 42 | void L1_emptyBuffer(); 43 | 44 | // Functions 45 | 46 | /** 47 | * @brief Initializes the L1 layer 48 | * 49 | */ 50 | void L1_init() 51 | { 52 | anticollision_time = 500 * NODENUMBER; // WARNING: may need to be adusted if SF > 7 is used 53 | 54 | SPI.begin(SCK, MISO, MOSI, SS); 55 | LoRa.setPins(SS, RST, DI0); 56 | LoRa.setTxPower(tx_dbm); 57 | 58 | if (LoRa.begin(LORABAND)) 59 | Serial.println("LoRa module started correctly"); 60 | else 61 | { 62 | Serial.println("Error starting LoRa module"); 63 | exit(0); 64 | } 65 | LoRa.setSpreadingFactor(spreading_factor); 66 | LoRa.enableCrc(); 67 | LoRa.onReceive(L1_onReceive); 68 | LoRa.receive(); 69 | } 70 | 71 | /** 72 | * @brief Adds a packet to the sending queue 73 | * 74 | * @param packet: Packet to be enqueued 75 | * @return return_type status 76 | */ 77 | return_type L1_enqueue_outPacket(pack_struct packet) 78 | { 79 | if (outBuffer_rear == L1BUFFER) 80 | return ret_buffer_full; 81 | 82 | else 83 | { 84 | outBuffer[outBuffer_rear].ttl = packet.ttl; 85 | outBuffer[outBuffer_rear].receiver = packet.receiver; 86 | outBuffer[outBuffer_rear].sender = packet.sender; 87 | outBuffer[outBuffer_rear].last_node = packet.last_node; 88 | outBuffer[outBuffer_rear].next_node = packet.next_node; 89 | outBuffer[outBuffer_rear].id = packet.id; 90 | outBuffer[outBuffer_rear].type = packet.type; 91 | outBuffer[outBuffer_rear].payload = packet.payload; 92 | outBuffer_rear++; 93 | L1_outBuffer_left++; 94 | } 95 | return ret_ok; 96 | } 97 | 98 | /** 99 | * @brief Sends the first enqueued packet 100 | * 101 | * @return return_type status 102 | */ 103 | return_type L1_send_outPacket() 104 | { 105 | if (outBuffer_front == outBuffer_rear) 106 | return ret_buffer_empty; 107 | 108 | else if ((millis() - last_transmit_timestamp) < (100 - LORADUTY) * transmit_duration) 109 | return ret_send_duty_error; 110 | 111 | else if ((millis() - last_receive_timestamp) < anticollision_time) 112 | return ret_send_anticollision_error; 113 | 114 | else 115 | { 116 | pack_struct packet = outBuffer[outBuffer_front]; 117 | 118 | return_type ret = L1_packSend(packet); 119 | 120 | if (packet.type == payload_msg) 121 | free(((payload_message_struct *)packet.payload)->message_ptr); 122 | 123 | else if (packet.type == payload_ann) 124 | free(((payload_announce_struct *)packet.payload)->name_ptr); 125 | 126 | free(packet.payload); 127 | 128 | for (int i = 0; i < outBuffer_rear - 1; i++) 129 | outBuffer[i] = outBuffer[i + 1]; 130 | 131 | outBuffer_rear--; 132 | L1_outBuffer_left--; 133 | return ret; 134 | } 135 | } 136 | 137 | /** 138 | * @brief Creates a LoRa packet and sends it 139 | * 140 | * @param packet: Packet to be sent 141 | * @return return_type status 142 | */ 143 | return_type L1_packSend(pack_struct packet) 144 | { 145 | int ret = LoRa.beginPacket(); 146 | if (ret) 147 | { 148 | LoRa.write(NETID); 149 | LoRa.write(packet.ttl); 150 | LoRa.write(packet.receiver); 151 | LoRa.write(packet.sender); 152 | LoRa.write(packet.last_node); 153 | LoRa.write(packet.next_node); 154 | LoRa.write((uint8_t *)&(packet.id), 4); 155 | LoRa.write(packet.type); 156 | 157 | switch (packet.type) 158 | { 159 | case payload_msg: 160 | { 161 | payload_message_struct *payload_message = (payload_message_struct *)packet.payload; 162 | LoRa.write(payload_message->message_size); 163 | 164 | char message_temp[payload_message->message_size + 1]; 165 | strcpy(message_temp, payload_message->message_ptr); 166 | LoRa.print(message_temp); 167 | } 168 | break; 169 | case payload_ack: 170 | { 171 | payload_acknowledgment_struct *payload_acknowledgment = (payload_acknowledgment_struct *)packet.payload; 172 | LoRa.write((uint8_t *)&(payload_acknowledgment->packet_id), 4); 173 | } 174 | break; 175 | case payload_ann: 176 | { 177 | payload_announce_struct *payload_announce = (payload_announce_struct *)packet.payload; 178 | LoRa.write(payload_announce->name_size); 179 | 180 | char *message_temp = (char *)malloc(payload_announce->name_size + 1); 181 | strcpy(message_temp, payload_announce->name_ptr); 182 | LoRa.print(message_temp); 183 | } 184 | } 185 | 186 | transmit_duration = millis(); 187 | LoRa.endPacket(); 188 | int current_millis = millis(); 189 | transmit_duration = current_millis - transmit_duration; 190 | last_transmit_timestamp = current_millis; 191 | 192 | Serial.printf("--- Sent "); 193 | L1_printPacket(packet); 194 | 195 | LoRa.receive(); 196 | return ret_ok; 197 | } 198 | 199 | LoRa.receive(); 200 | return ret_error; 201 | } 202 | 203 | /** 204 | * @brief Callback function after receiving LoRa packet 205 | * 206 | * @param packetSize: Packet size 207 | */ 208 | void L1_onReceive(int packetSize) 209 | { 210 | L1_flag_received = 1; 211 | return; 212 | } 213 | 214 | /** 215 | * @brief Receives a LoRa packet and calls the correct handler 216 | * 217 | * @return return_type status 218 | */ 219 | return_type L1_receive() 220 | { 221 | pack_struct packet; 222 | 223 | uint8_t netid = LoRa.read(); 224 | if (netid != NETID) 225 | { 226 | L1_emptyBuffer(); 227 | return ret_receive_netid_error; 228 | } 229 | 230 | packet.ttl = LoRa.read(); 231 | if (packet.ttl == 0) 232 | { 233 | L1_emptyBuffer(); 234 | return ret_ttl_error; 235 | } 236 | 237 | packet.receiver = LoRa.read(); 238 | packet.sender = LoRa.read(); 239 | packet.last_node = LoRa.read(); 240 | packet.next_node = LoRa.read(); 241 | 242 | if (packet.next_node != NODENUMBER && packet.next_node != BROADCASTADDR) 243 | { 244 | L1_emptyBuffer(); 245 | return ret_receive_wrong_node; 246 | } 247 | 248 | LoRa.readBytes((uint8_t *)&packet.id, 4); 249 | packet.type = LoRa.read(); 250 | packet.rssi = LoRa.packetRssi(); 251 | 252 | L3_handlePacket(packet); 253 | 254 | switch (packet.type) 255 | { 256 | case payload_msg: 257 | { 258 | uint8_t message_size = LoRa.read(); 259 | 260 | char *message = (char *)malloc(message_size + 1); 261 | int i; 262 | for (i = 0; i < message_size; i++) 263 | { 264 | *(message + i) = (char)LoRa.read(); 265 | } 266 | *(message + i) = 0; 267 | 268 | packet.payload = L2_setPayloadMessage(message); 269 | 270 | free(message); 271 | 272 | L2_handleMessage(packet); 273 | } 274 | break; 275 | case payload_ack: 276 | { 277 | uint32_t message_crc; 278 | LoRa.readBytes((uint8_t *)&message_crc, 4); 279 | 280 | packet.payload = L2_setPayloadacknowledgment(message_crc); 281 | 282 | L2_handleacknowledgment(packet); 283 | } 284 | break; 285 | case payload_ann: 286 | { 287 | uint8_t name_size = LoRa.read(); 288 | 289 | char *name = (char *)malloc(name_size + 1); 290 | int i; 291 | for (i = 0; i < name_size; i++) 292 | { 293 | *(name + i) = (char)LoRa.read(); 294 | } 295 | *(name + i) = 0; 296 | 297 | packet.payload = L2_setPayloadAnnounce(name, name_size); 298 | 299 | L2_handleAnnounce(packet); 300 | 301 | free(name); 302 | } 303 | break; 304 | default: 305 | break; 306 | } 307 | 308 | last_receive_timestamp = millis(); 309 | 310 | Serial.printf("--- Received "); 311 | L1_printPacket(packet); 312 | 313 | return ret_ok; 314 | } 315 | 316 | /** 317 | * @brief Empties the LoRa input buffer 318 | * 319 | */ 320 | void L1_emptyBuffer() 321 | { 322 | if (LoRa.available()) 323 | { 324 | while (LoRa.available()) 325 | LoRa.read(); 326 | } 327 | return; 328 | } 329 | 330 | /** 331 | * @brief Prints packet's information 332 | * 333 | * @param packet: Packet to be printed 334 | */ 335 | void L1_printPacket(pack_struct packet) 336 | { 337 | 338 | switch (packet.type) 339 | { 340 | case payload_msg: 341 | Serial.printf("message packet ---\n"); 342 | break; 343 | 344 | case payload_ack: 345 | Serial.printf("acknowledgment packet: ---\n"); 346 | break; 347 | 348 | case payload_ann: 349 | Serial.printf("announce packet ---\n"); 350 | break; 351 | } 352 | 353 | Serial.printf("TTL: %d\n", packet.ttl); 354 | Serial.printf("Receiver: %d\n", packet.receiver); 355 | Serial.printf("Sender: %d\n", packet.sender); 356 | Serial.printf("Last node: %d\n", packet.last_node); 357 | Serial.printf("Next node: %d\n", packet.next_node); 358 | Serial.printf("id: %zu\n", packet.id); 359 | Serial.printf("RSSI: %d\n", packet.rssi); 360 | 361 | switch (packet.type) 362 | { 363 | case payload_msg: 364 | { 365 | payload_message_struct *payload_message = (payload_message_struct *)packet.payload; 366 | Serial.printf("Message: %s\n", payload_message->message_ptr); 367 | } 368 | break; 369 | case payload_ack: 370 | { 371 | payload_acknowledgment_struct *payload_acknowledgment = (payload_acknowledgment_struct *)packet.payload; 372 | Serial.printf("Packet id: %x\n", payload_acknowledgment->packet_id); 373 | } 374 | break; 375 | case payload_ann: 376 | { 377 | payload_announce_struct *payload_announce = (payload_announce_struct *)packet.payload; 378 | Serial.printf("Name: %s\n", payload_announce->name_ptr); 379 | } 380 | } 381 | Serial.printf("\n\n"); 382 | } --------------------------------------------------------------------------------