# Project export: Resililink

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: A Lifeline When It Matters Most
- Devpost: https://devpost.com/software/resilink
- GitHub: https://github.com/12458/calhacks
- Video: https://www.youtube.com/embed/W8BvOO4-qPs?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Cal Hacks: Hack for Impact; Skylo: Seamlessly Switch from Cellular to Satellite)
- Team: 0 GitHub contributor(s) — 

## Devpost submission (written by the team)

### Overview

Overview Resililink is a node-based mesh network leveraging LoRa technology to facilitate communication in disaster-prone regions where traditional infrastructure, such as cell towers and internet services, is unavailable. The system is designed to operate in low-power environments and cover long distances, ensuring that essential communication can still occur when it is most needed. A key feature of this network is the integration of a "super" node equipped with satellite connectivity (via Skylo), which serves as the bridge between local nodes and a centralized server. The server processes the data and sends SMS notifications through Twilio to the intended recipients. Importantly, the system provides acknowledgment back to the originating node, confirming successful delivery of the message. This solution is aimed at enabling individuals to notify loved ones or emergency responders during critical times, such as natural disasters, when conventional communication channels are down. Project

### Inspiration

The inspiration for Resililink came from personal experiences of communication outages during hurricanes. In each instance, we found ourselves cut off from vital resources like the internet, making it impossible to check on family members, friends, or receive updates on the situation. These moments of helplessness highlighted the urgent need for a resilient communication network that could function even when the usual infrastructure fails. System Capabilities Resililink is designed to be resilient, easy to deploy, and scalable, with several key features: Ease of Deployment: The network is fast to set up, making it particularly useful in emergency situations. Dual Connectivity: It allows communication both across the internet and in peer-to-peer fashion over long ranges, ensuring continuous data flow even in remote areas. Cost-Efficiency: The nodes are inexpensive to produce, as each consists of a single LoRa radio and an ESP32 microcontroller, keeping hardware costs to a minimum. Development Approach The development of Resililink involved creating a custom communication protocol based on Protocol Buffers (protobufs) to efficiently manage data exchange. The core hardware components include LoRa radios, which provide long-range communication, and Skylo satellite connectivity, enabling nodes to transmit data to the internet using the MQTT protocol. On the backend, a server hosted on Microsoft Azure handles the incoming MQTT messages, decrypts them, and forwards the relevant information to appropriate APIs, such as Twilio, for further processing and notification delivery. This seamless integration of satellite technology and cloud infrastructure ensures the reliability and scalability of the system. Key Challenges Several challenges arose during the development process. One of the most significant issues was the lack of clear documentation for the AT commands on the Mutura evaluation board, which made it difficult to implement some of the core functionalities. Additionally, given the low-level nature of the project, debugging was particularly challenging, requiring in-depth tracing of system operations to identify and resolve issues. Another constraint was the limited packet size of 256 bytes, necessitating careful optimization to ensure efficient use of every byte of data transmitted. Achievements Despite these challenges, we successfully developed a fully functional network, complete with a working demonstration. The system proved capable of delivering messages over long distances with low power consumption, validating the concept and laying the groundwork for future enhancements. Lessons Learned Through this project, we gained a deeper understanding of computer networking, particularly in the context of low-power, long-range communication technologies like LoRa. The experience also provided valuable insights into the complexities of integrating satellite communication with terrestrial mesh networks. Future Plans for Resililink Looking ahead, we plan to explore ways to scale the network, focusing on enhancing its reliability and expanding its reach to serve larger geographic areas. We are also interested in further refining the underlying protocol and exploring new applications for Resililink beyond disaster recovery scenarios, such as in rural connectivity or industrial IoT use cases.

## README (from the GitHub repository)

## Calhacks 11.0

This is our project for [Calhacks 11.0](https://hackberkeley.org/).

[Devpost](https://devpost.com/software/resilink)

We won:
- 1st place for [Skylo: Seamlessly Switch from Cellular to Satellite](https://live.hackberkeley.org/prizes#:~:text=Skylo%3A%20Seamlessly%20Switch,our%20technical%20recruiter.)
- [Cal Hacks: Hack for Impact](https://live.hackberkeley.org/prizes#:~:text=Skylo%3A%20Seamlessly%20Switch,our%20technical%20recruiter.)

### Overview

Resililink is a node-based mesh network leveraging LoRa technology to facilitate communication in disaster-prone regions where traditional infrastructure, such as cell towers and internet services, is unavailable. The system is designed to operate in low-power environments and cover long distances, ensuring that essential communication can still occur when it is most needed. A key feature of this network is the integration of a "super" node equipped with satellite connectivity (via Skylo), which serves as the bridge between local nodes and a centralized server. The server processes the data and sends SMS notifications through Twilio to the intended recipients. Importantly, the system provides acknowledgment back to the originating node, confirming successful delivery of the message. This solution is aimed at enabling individuals to notify loved ones or emergency responders during critical times, such as natural disasters, when conventional communication channels are down.

### Project Inspiration

The inspiration for Resililink came from personal experiences of communication outages during hurricanes. In each instance, we found ourselves cut off from vital resources like the internet, making it impossible to check on family members, friends, or receive updates on the situation. These moments of helplessness highlighted the urgent need for a resilient communication network that could function even when the usual infrastructure fails.

### System Capabilities

Resililink is designed to be resilient, easy to deploy, and scalable, with several key features:

- **Ease of Deployment**: The network is fast to set up, making it particularly useful in emergency situations.
- **Dual Connectivity**: It allows communication both across the internet and in peer-to-peer fashion over long ranges, ensuring continuous data flow even in remote areas.
- **Cost-Efficiency**: The nodes are inexpensive to produce, as each consists of a single LoRa radio and an ESP32 microcontroller, keeping hardware costs to a minimum.

### Development Approach

The development of Resililink involved creating a custom communication protocol based on Protocol Buffers (protobufs) to efficiently manage data exchange. The core hardware components include LoRa radios, which provide long-range communication, and Skylo satellite connectivity, enabling nodes to transmit data to the internet using the MQTT protocol. 

On the backend, a server hosted on Microsoft Azure handles the incoming MQTT messages, decrypts them, and forwards the relevant information to appropriate APIs, such as Twilio, for further processing and notification delivery. This seamless integration of satellite technology and cloud infrastructure ensures the reliability and scalability of the system.

### Key Challenges

Several challenges arose during the development process. One of the most significant issues was the lack of clear documentation for the AT commands on the Mutura evaluation board, which made it difficult to implement some of the core functionalities. Additionally, given the low-level nature of the project, debugging was particularly challenging, requiring in-depth tracing of system operations to identify and resolve issues. Another constraint was the limited packet size of 256 bytes, necessitating careful optimization to ensure efficient use of every byte of data transmitted.

### Achievements

Despite these challenges, we successfully developed a fully functional network, complete with a working demonstration. The system proved capable of delivering messages over long distances with low power consumption, validating the concept and laying the groundwork for future enhancements.

### Lessons Learned

Through this project, we gained a deeper understanding of computer networking, particularly in the context of low-power, long-range communication technologies like LoRa. The experience also provided valuable insights into the complexities of integrating satellite communication with terrestrial mesh networks.

### Future Plans for Resililink

Looking ahead, we plan to explore ways to scale the network, focusing on enhancing its reliability and expanding its reach to serve larger geographic areas. We are also interested in further refining the underlying protocol and exploring new applications for Resililink beyond disaster recovery scenarios, such as in rural connectivity or industrial IoT use cases.

## Packet Specification
```proto
syntax = "proto3";

message Packet {
  // Unique identifier for the packet (UUID4 format)
  string packet_uuid = 1;

  // Enum to specify the type of packet
  PacketType packet_type = 2;

  // Oneof to hold different types of messages
  oneof payload {
    NetworkMessage network_message = 3;
    AckMessage ack_message = 4;
    DiscoverMessage discover_message = 5;
    AnnounceMessage announce_message = 6;
  }
}

// Enum to represent different packet types
enum PacketType {
  NETWORK_MESSAGE = 0;
  ACK_MESSAGE = 1;
  DISCOVER_MESSAGE = 2;
  ANNOUNCE_MESSAGE = 3;
}

message NetworkMessage {
  // Unique identifier for the node sending the message
  string node_id = 1;

  // Timestamp for when the message was sent, in UNIX format
  uint32 timestamp = 2;

  // GPS coordinates (latitude and longitude) of the node (optional)
  Location sender_location = 3;

  // Actual text message being sent
  bytes message_content = 4;

  // Destination: could be either a node UID or telephone number
  string destination = 5;
}

// Message type for acknowledgment
message AckMessage {
  // Unique identifier for the original message being acknowledged
  string message_id = 1;

  // Unique identifier of the node sending the ACK
  string node_id = 2;

  // Timestamp for when the ACK was sent, in UNIX format
  uint32 timestamp = 3;
}

// Message type for discovery request
message DiscoverMessage {
  // Timestamp for when the discover message was sent, in UNIX format
  uint32 timestamp = 1;
}

// Message type for node announcement
message AnnounceMessage {
  // Unique identifier of the node announcing itself
  string node_id = 1;

  // Timestamp for when the announce message was sent, in UNIX format
  uint32 timestamp = 2;

  // Optional location information of the announcing node
  Location node_location = 3;
}

// Structure to capture location details
message Location {
  double latitude = 1;
  double longitude = 2;
}
```

## Architecture

![ResiliLink_Arch](assets/ResiliLink_Arch.jpg)

## Detected evidence (automated analysis)

Indexed codebase: 16 recognized source files, 703 KB.
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (19 of 19)

```
.gitignore
LICENSE
normal_node/chat.py
normal_node/rylr998.py
normal_node/spec_pb2.py
normal_node/static/jquery.min.js
normal_node/static/socket.io.js
normal_node/static/tailwind.js
normal_node/templates/index.html
normal_node/web_chat.py
README.md
relay_server/relay.py
super_node/chat_2.py
super_node/chat.py
super_node/rylr998.py
super_node/sender.py
super_node/skylo.py
super_node/spec_pb2.py
super_node/spec.proto
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Add in project files
- Update README
- Update README
- Update README.md
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### super_node/sender.py

```python
from rylr998 import RYLR998
import spec_pb2
import uuid
import time
import base64

# Generate a UUID4 for the node ID
NODE_ID = uuid.uuid4().hex

# Create a NetworkMessage
network_message = spec_pb2.NetworkMessage()
network_message.node_id = NODE_ID
network_message.timestamp = int(time.time())
network_message.message_content = "Hello, this is a test message!".encode()
network_message.destination = "node-456"

# Create a Packet and assign the NetworkMessage to it
packet = spec_pb2.Packet()
packet.packet_uuid = str(uuid.uuid4())  # Generate a UUID4 for the packet
packet.packet_type = spec_pb2.NETWORK_MESSAGE
packet.network_message.CopyFrom(network_message)  # Add the NetworkMessage

# Serialize the Packet to a binary format (to send over the network)
serialized_packet = packet.SerializeToString()

# base64 encode the serialized packet
b64_packet = base64.b64encode(serialized_packet)


lora = RYLR998(port='/dev/ttyAMA0')
lora.set_address(120)
lora.set_network_id(18)
lora.send_data(0, str(b64_packet.decode()))
print(f"Sent data: {b64_packet} / {packet}")
#wait for ack, when received deserialize and print
while (x := lora.receive_data()) is None:
    print("Waiting for data...")
    time.sleep(1)
    lora.send_data(0, str(b64_packet.decode()))
    print("Retransmitting")
print(x)
# Deserialize the received data
received_packet = spec_pb2.Packet()
received_packet.ParseFromString(base64.b64decode(x.split(",")[2]))
print(received_packet)
lora.close()
```

### normal_node/web_chat.py

```python
from flask import Flask, render_template, request, jsonify
from flask_socketio import SocketIO, emit
import threading
import time
import chat as lora_chat  # Import the modified LoRa chat module

app = Flask(__name__)
socketio = SocketIO(app)

lora = None
messages = []

@app.route('/')
def index():
    return render_template('index.html', node_id=lora_chat.get_node_id())

@socketio.on('send_message')
def handle_message(data):
    destination = data['destination']
    content = data['content']
    result = lora_chat.send_message(lora, destination, content)
    messages.append(result)
    emit('new_message', result)

@socketio.on('discover')
def handle_discover():
    result = lora_chat.send_discover_message(lora)
    emit('discover_sent', result)
    time.sleep(5)  # Wait for responses
    discovered = lora_chat.get_discovered_nodes()
    emit('discovered_nodes', discovered)

@app.route('/messages')
def get_messages():
    return jsonify(messages)

@app.route('/acks')
def get_acks():
    return jsonify(lora_chat.get_acknowledgments())

def message_listener():
    while True:
        new_messages = lora_chat.listen_for_data(lora)
        if new_messages:
            for message in new_messages:
                if message is None:
                    continue
                messages.append(message)
                socketio.emit('new_message', message)
                
                if message['type'] == 'discover':
                    # If a discover message is received, update the discovered nodes
                    discovered = lora_chat.get_discovered_nodes()
                    socketio.emit('discovered_nodes', discovered)
                elif message['type'] == 'error':
                    # Emit error messages to the client
                    socketio.emit('error', {
                        'message': f"Error: {message['error']} - {message['details']}"
                    })

def initialize():
    global lora
    lora = lora_chat.initialize_lora(address=6, network_id=18)

    # Start the LoRa listener in a separate thread
    listener_thread = threading.Thread(target=message_listener)
    listener_thread.daemon = True
    listener_thread.start()

if __name__ == '__main__':
    initialize()
    socketio.run(app, debug=True, host='0.0.0.0', port=5000)
```

### normal_node/spec_pb2.py

```python
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: spec.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nspec.proto\"\xf6\x01\n\x06Packet\x12\x13\n\x0bpacket_uuid\x18\x01 \x01(\t\x12 \n\x0bpacket_type\x18\x02 \x01(\x0e\x32\x0b.PacketType\x12*\n\x0fnetwork_message\x18\x03 \x01(\x0b\x32\x0f.NetworkMessageH\x00\x12\"\n\x0b\x61\x63k_message\x18\x04 \x01(\x0b\x32\x0b.AckMessageH\x00\x12,\n\x10\x64iscover_message\x18\x05 \x01(\x0b\x32\x10.DiscoverMessageH\x00\x12,\n\x10\x61nnounce_message\x18\x06 \x01(\x0b\x32\x10.AnnounceMessageH\x00\x42\t\n\x07payload\"\x86\x01\n\x0eNetworkMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\r\x12\"\n\x0fsender_location\x18\x03 \x01(\x0b\x32\t.Location\x12\x17\n\x0fmessage_content\x18\x04 \x01(\x0c\x12\x13\n\x0b\x64\x65stination\x18\x05 \x01(\t\"D\n\nAckMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\t\x12\x11\n\ttimestamp\x18\x03 \x01(\r\"$\n\x0f\x44iscoverMessage\x12\x11\n\ttimestamp\x18\x01 \x01(\r\"W\n\x0f\x41nnounceMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\r\x12 \n\rnode_location\x18\x03 \x01(\x0b\x32\t.Location\"/\n\x08Location\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01*^\n\nPacketType\x12\x13\n\x0fNETWORK_MESSAGE\x10\x00\x12\x0f\n\x0b\x41\x43K_MESSAGE\x10\x01\x12\x14\n\x10\x44ISCOVER_MESSAGE\x10\x02\x12\x14\n\x10\x41NNOUNCE_MESSAGE\x10\x03\x62\x06proto3')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'spec_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  _PACKETTYPE._serialized_start=646
  _PACKETTYPE._serialized_end=740
  _PACKET._serialized_start=15
  _PACKET._serialized_end=261
  _NETWORKMESSAGE._serialized_start=264
  _NETWORKMESSAGE._serialized_end=398
  _ACKMESSAGE._serialized_start=400
  _ACKMESSAGE._serialized_end=468
  _DISCOVERMESSAGE._serialized_start=470
  _DISCOVERMESSAGE._serialized_end=506
  _ANNOUNCEMESSAGE._serialized_start=508
  _ANNOUNCEMESSAGE._serialized_end=595
  _LOCATION._serialized_start=597
  _LOCATION._serialized_end=644
# @@protoc_insertion_point(module_scope)

```

### super_node/spec_pb2.py

```python
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: spec.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nspec.proto\"\xf6\x01\n\x06Packet\x12\x13\n\x0bpacket_uuid\x18\x01 \x01(\t\x12 \n\x0bpacket_type\x18\x02 \x01(\x0e\x32\x0b.PacketType\x12*\n\x0fnetwork_message\x18\x03 \x01(\x0b\x32\x0f.NetworkMessageH\x00\x12\"\n\x0b\x61\x63k_message\x18\x04 \x01(\x0b\x32\x0b.AckMessageH\x00\x12,\n\x10\x64iscover_message\x18\x05 \x01(\x0b\x32\x10.DiscoverMessageH\x00\x12,\n\x10\x61nnounce_message\x18\x06 \x01(\x0b\x32\x10.AnnounceMessageH\x00\x42\t\n\x07payload\"\x86\x01\n\x0eNetworkMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\r\x12\"\n\x0fsender_location\x18\x03 \x01(\x0b\x32\t.Location\x12\x17\n\x0fmessage_content\x18\x04 \x01(\x0c\x12\x13\n\x0b\x64\x65stination\x18\x05 \x01(\t\"D\n\nAckMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\t\x12\x11\n\ttimestamp\x18\x03 \x01(\r\"$\n\x0f\x44iscoverMessage\x12\x11\n\ttimestamp\x18\x01 \x01(\r\"W\n\x0f\x41nnounceMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\r\x12 \n\rnode_location\x18\x03 \x01(\x0b\x32\t.Location\"/\n\x08Location\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01*^\n\nPacketType\x12\x13\n\x0fNETWORK_MESSAGE\x10\x00\x12\x0f\n\x0b\x41\x43K_MESSAGE\x10\x01\x12\x14\n\x10\x44ISCOVER_MESSAGE\x10\x02\x12\x14\n\x10\x41NNOUNCE_MESSAGE\x10\x03\x62\x06proto3')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'spec_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  _PACKETTYPE._serialized_start=646
  _PACKETTYPE._serialized_end=740
  _PACKET._serialized_start=15
  _PACKET._serialized_end=261
  _NETWORKMESSAGE._serialized_start=264
  _NETWORKMESSAGE._serialized_end=398
  _ACKMESSAGE._serialized_start=400
  _ACKMESSAGE._serialized_end=468
  _DISCOVERMESSAGE._serialized_start=470
  _DISCOVERMESSAGE._serialized_end=506
  _ANNOUNCEMESSAGE._serialized_start=508
  _ANNOUNCEMESSAGE._serialized_end=595
  _LOCATION._serialized_start=597
  _LOCATION._serialized_end=644
# @@protoc_insertion_point(module_scope)

```

### super_node/rylr998.py

```python
import serial
import time

class RYLR998:
    def __init__(self, port, baudrate=115200, timeout=1):
        # Initialize serial connection
        self.ser = serial.Serial(port, baudrate, timeout=timeout)
        time.sleep(2)  # Allow time for the module to initialize

    def send_command(self, command):
        # Send command to LoRa module
        full_command = f"{command}\r\n"
        self.ser.write(full_command.encode())
        time.sleep(0.1)
        return self.get_response()

    def get_response(self):
        # Read the response from the module
        response = []
        while self.ser.in_waiting > 0:
            response.append(self.ser.readline().decode('utf-8').strip())
        return response if response else None

    def reset(self):
        # Reset the module
        return self.send_command("AT+RESET")

    def set_address(self, address):
        # Set the module's address (0-65535)
        return self.send_command(f"AT+ADDRESS={address}")

    def get_address(self):
        # Get the module's current address
        return self.send_command("AT+ADDRESS?")

    def get_band(self):
        return self.send_command(f"AT+BAND?")
    
    def set_band(self, band):
        return self.send_command(f"AT+BAND={band}")

    def set_network_id(self, network_id):
        # Set the LoRa network ID (3-15, 18)
        return self.send_command(f"AT+NETWORKID={network_id}")

    def get_network_id(self):
        # Get the current LoRa network ID
        return self.send_command("AT+NETWORKID?")

    def set_rf_parameters(self, spreading_factor, bandwidth, coding_rate, preamble):
        # Set the RF parameters: Spreading Factor, Bandwidth, Coding Rate, and Preamble
        return self.send_command(f"AT+PARAMETER={spreading_factor},{bandwidth},{coding_rate},{preamble}")

    def send_data(self, address, data):
        # Send data to a specific address
        length = len(data)
        cmd = f"AT+SEND={address},{length},{data}"
        print(f"Sending: {cmd}")
        response = self.send_command(cmd)
        print(f"Response: {response}")
        # Check if the message was sent successfully
        if response and "+OK" in response:
            return response
        return None

    def receive_data(self):
        # Check for incoming data, looking for the +RCV pattern
        response = self.get_response()
        if response:
            for line in response:
                if line.startswith("+RCV"):
                    return line  # Return the received data in the format +RCV=Address,Length,Data,RSSI,SNR
        return None

    def get_uid(self):
        # Get the module's unique ID
        return self.send_command("AT+UID?")

    def get_version(self):
        # Get the module's firmware version
        return self.send_command("AT+VER?")

    def set_baud_rate(self, baudrate):
        # Set UART baud rate
        return self.send_command(f"AT+IPR={baudrate}")

    def close(self):
        # Close the serial connection
        if self.ser.is_open:
            self.ser.close()

# Example Usage
if __name__ == "__main__":
    lora = RYLR998(port='/dev/ttyAMA0')  # Replace with the appropriate port
    print(lora.reset())
    print(lora.set_address(120))
    print(lora.get_address())
    print(lora.set_network_id(6))
    print(lora.get_network_id())
    
    # Send and receive data
    print(lora.send_data(0, "HELLO"))
    while (x := lora.receive_data()) is None:
        print("Waiting for data...")
        time.sleep(1)
    print(x)

    print(lora.get_uid())
    print(lora.get_version())
    lora.close()

```

### normal_node/rylr998.py

```python
import serial
import time

class RYLR998:
    def __init__(self, port, baudrate=115200, timeout=1):
        self.ser = serial.Serial(port, baudrate, timeout=timeout)
        time.sleep(2)  # Allow time for the module to initialize

    def send_command(self, command):
        full_command = f"{command}\r\n"
        self.ser.write(full_command.encode())
        time.sleep(0.1)
        return self.get_response()

    def get_response(self):
        response = []
        start_time = time.time()
        while time.time() - start_time < 1:  # Wait for up to 1 second
            if self.ser.in_waiting > 0:
                line = self.ser.readline().decode('utf-8').strip()
                if line:
                    response.append(line)
            else:
                time.sleep(0.1)
        return response if response else None

    def reset(self):
        return self.send_command("AT+RESET")

    def set_address(self, address):
        return self.send_command(f"AT+ADDRESS={address}")

    def get_address(self):
        return self.send_command("AT+ADDRESS?")

    def set_network_id(self, network_id):
        return self.send_command(f"AT+NETWORKID={network_id}")

    def get_network_id(self):
        return self.send_command("AT+NETWORKID?")
    
    def get_band(self):
        return self.send_command(f"AT+BAND?")
    
    def set_band(self, band):
        return self.send_command(f"AT+BAND={band}")

    def set_rf_parameters(self, spreading_factor, bandwidth, coding_rate, preamble):
        return self.send_command(f"AT+PARAMETER={spreading_factor},{bandwidth},{coding_rate},{preamble}")

    def send_data(self, address, data):
        length = len(data)
        response = self.send_command(f"AT+SEND={address},{length},{data}")
        print(f"Sent Data: {data}({length})\nResponse:{response}")
        if response and "+OK" in response:
            return response
        return None

    def receive_data(self):
        received_data = []
        start_time = time.time()
        if self.ser.in_waiting > 0:
            line = self.ser.readline().decode('utf-8').strip()
            if line:
                if line.startswith("+RCV="):
                    print(line)
                    # Parse the received data
                    parts = line[5:].strip().split(',')
                    print(parts)
                    if len(parts) >= 5:
                        received_data.append({
                            'address': int(parts[0]),
                            'length': int(parts[1]),
                            'data': parts[2],
                            'rssi': int(parts[3]),
                            'snr': float(parts[4])
                        })
        
        return received_data if received_data else None

    def get_uid(self):
        return self.send_command("AT+UID?")

    def get_version(self):
        return self.send_command("AT+VER?")

    def set_baud_rate(self, baudrate):
        return self.send_command(f"AT+IPR={baudrate}")

    def close(self):
        if self.ser.is_open:
            self.ser.close()

# Example Usage
if __name__ == "__main__":
    lora = RYLR998(port='/dev/ttyAMA0')  # Replace with the appropriate port
    print(lora.reset())
    print(lora.set_address(120))
    print(lora.get_address())
    print(lora.set_network_id(6))
    print(lora.get_network_id())
    
    print(lora.send_data(0, "HELLO"))
    
    print("Waiting for data...")
    for _ in range(10):  # Try to receive data for 10 seconds
        received = lora.receive_data()
        if received:
            for item in received:
                if 'data' in item:
                    print(f"Received: Address={item['address']}, Length={item['length']}, "
                          f"Data={item['data']}, RSSI={item['rssi']}, SNR={item['snr']}")
                else:
                    print(f"Other response: {item['other']}")
        time.sleep(1)

    print(lora.get_uid())
    print(lora.get_version())
    lora.close()
```

### relay_server/relay.py

```python
import paho.mqtt.client as mqtt
import base64
from twilio.rest import Client
from spec_pb2 import Packet, PacketType, NetworkMessage
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding as sym_padding
import os
import uuid
import time

import google.generativeai as genai
import os

genai.configure(api_key="YOUR_API_KEY")
model = genai.GenerativeModel("gemini-1.5-pro")
chat = model.start_chat(history=[])

# Define Twilio API credentials
TWILIO_ACCOUNT_SID = 'YOUR_API_KEY'
TWILIO_AUTH_TOKEN = 'YOUR_API_KEY'
TWILIO_PHONE_NUMBER = 'YOUR_PHONE_NUMBER'

# AES key for encryption/decryption (must be 16, 24, or 32 bytes for AES-128/192/256)
AES_KEY = b'password'.ljust(16, b'\0')[:16]  # Ensure the key is 16 bytes

def aes_encrypt(message):
    if isinstance(message, str):
        message = message.encode()

    iv = os.urandom(16)
    cipher = Cipher(algorithms.AES(AES_KEY), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()

    padder = sym_padding.PKCS7(algorithms.AES.block_size).padder()
    padded_message = padder.update(message) + padder.finalize()

    encrypted_message = encryptor.update(padded_message) + encryptor.finalize()

    return iv + encrypted_message

def aes_decrypt(ciphertext):
    encrypted_data = ciphertext
    iv = encrypted_data[:16]
    encrypted_message = encrypted_data[16:]
    cipher = Cipher(algorithms.AES(AES_KEY), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    decrypted_padded_message = decryptor.update(encrypted_message) + decryptor.finalize()
    unpadder = sym_padding.PKCS7(algorithms.AES.block_size).unpadder()
    decrypted_message = unpadder.update(decrypted_padded_message) + unpadder.finalize()
    return decrypted_message.decode()

def perform_gemini_search(query):
    print(f"Performing Gemini search for: {query}")
    response = chat.send_message(query)
    print(f"Received response: {response.text}")
    return response.text

def split_message(message, chunk_size=45):
    return [message[i:i+chunk_size] for i in range(0, len(message), chunk_size)]

def on_connect(client, userdata, flags, rc):
    print("Connected with result code " + str(rc))
    client.subscribe("12458Test/pub")

def on_message(client, userdata, msg):
    print(msg.topic + " " + str(msg.payload))
    msg_decoded = base64.urlsafe_b64decode(msg.payload)

    try:
        packet = Packet()
        packet.ParseFromString(msg_decoded)
    except Exception as e:
        print(f"Failed to parse packet: {e}")
        return

    if packet.packet_type == PacketType.NETWORK_MESSAGE:
        network_message = packet.network_message
        try:
            decrypted_message = aes_decrypt(network_message.message_content)
            print(f"Decrypted message: {decrypted_message}")

            if network_message.destination.startswith("+Q"):
                query = decrypted_message.strip()  # Remove "+QUESTION" and leading/trailing spaces
                search_result = perform_gemini_search(query)

                chunks = split_message(search_result)


                # Send each chunk back via MQTT
                for i, chunk in enumerate(chunks):
                    encrypted_result = aes_encrypt(chunk)
                    response_packet = Packet()
                    response_packet.packet_uuid = uuid.uuid4().hex[:8]
                    response_packet.packet_type = PacketType.NETWORK_MESSAGE

                    response_network_message = NetworkMessage()
                    response_network_message.node_id = "Server"
                    response_network_message.timestamp = int(time.time())
                    response_network_message.message_content = encrypted_result
                    response_network_message.destination = network_message.node_id

                    response_packet.network_message.CopyFrom(response_network_message)

                    serialized_packet = response_packet.SerializeToString()
                    encoded_packet = base64.urlsafe_b64encode(serialized_packet)

                    client.publish("12458Test/sub", encoded_packet)
                    print(f"Sent chunk {i+1} (size {len(serialized_packet)})/{len(chunks)}")
            else:
                send_sms(network_message.destination, decrypted_message)
        except Exception as e:
            print(f"Failed to process message: {e}")

def send_sms(destination, message_content):
    print(f"Sending message to {destination}: {message_content}")
    try:
        client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
        message = client.messages.create(
            body=message_content,
            from_=TWILIO_PHONE_NUMBER,
            to=destination
        )
        print(f"Message sent to {destination}: {message.sid}")
    except Exception as e:
        print(f"Failed to send message: {e}")

mqtt_client = mqtt.Client()
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.connect("test.mosquitto.org", 1883, 60)
mqtt_client.loop_forever()
```

### super_node/skylo.py

```python
import serial
import time

class SerialWrapper:
    def __init__(self, port, baudrate=115200, timeout=1):
        """
        Initialize the serial connection.
        
        :param port: Serial port to use (e.g., 'COM3', '/dev/ttyUSB0')
        :param baudrate: Baud rate for the serial communication (default is 115200)
        :param timeout: Timeout for read/write operations (default is 1 second)
        """
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self.serial_conn = None
        
        # Initialize the connection
        self.open_connection()

    def open_connection(self):
        """
        Open the serial connection.
        """
        try:
            self.serial_conn = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                timeout=self.timeout,
                write_timeout=self.timeout
            )
            if self.serial_conn.is_open:
                print(f"Connected to {self.port} at {self.baudrate} baudrate.")
        except serial.SerialException as e:
            print(f"Error opening the serial connection: {e}")
            self.serial_conn = None

    def close_connection(self):
        """
        Close the serial connection.
        """
        if self.serial_conn and self.serial_conn.is_open:
            self.serial_conn.close()
            print(f"Connection to {self.port} closed.")

    def send_command(self, command, wait_for_response=True):
        """
        Send a command to the serial device.
        
        :param command: The AT command to send.
        :param wait_for_response: Whether to wait for and return the response.
        :return: Response from the device, or None if wait_for_response is False.
        """
        if self.serial_conn is None:
            raise ConnectionError("Serial connection is not open.")

        try:
            # Send the command followed by a carriage return
            full_command = command + '\r'
            self.serial_conn.write(full_command.encode())
            time.sleep(0.1)  # Small delay after sending the command
            
            if wait_for_response:
                return self.read_response()
            return None

        except serial.SerialTimeoutException:
            print("Error: Write timeout occurred.")
        except serial.SerialException as e:
            print(f"Serial communication error: {e}")

    def read_response(self):
        """
        Read the response from the serial device.
        
        :return: The response from the device as a string.
        """
        if self.serial_conn is None:
            raise ConnectionError("Serial connection is not open.")
        
        try:
            response = self.serial_conn.read_until(b'\r\n').decode('utf-8').strip()
            return response
        except serial.SerialException as e:
            print(f"Error reading from the serial device: {e}")
            return None

    def flush(self):
        """
        Flush input and output buffers.
        """
        if self.serial_conn:
            self.serial_conn.reset_input_buffer()
            self.serial_conn.reset_output_buffer()

    def send_at_command(self, command):
        """
        Helper function to send an AT command and return the response.
        
        :param command: The AT command to send.
        :return: The response from the device.
        """
        print(f"Sending AT command: {command}")
        response = self.send_command(command)
        print(f"Response: {response}")
        return response

    ### MQTT Commands ###
    
    def mqtt_config(self, connection_id, client_name, broker_url, port=1883, ip_type=0, keep_alive=1200, clean_session=1):
        """
        Configure MQTT connection parameters.
        
        :param connection_id: MQTT connection ID.
        :param client_name: Client identifier for MQTT.
        :param broker_url: MQTT broker URL.
        :param port: MQTT broker port (default is 1883).
        :param ip_type: Preferred IP type (0 for IPv4).
        :param keep_alive: Keep-alive time in seconds (default is 1200).
        :param clean_session: 1 for clean session (default is 1).
        """
        # Clear existing configuration
        self.send_at_command(f'AT%MQTTCFG="clear",{connection_id}')
        
        # Set client name and broker URL
        self.send_at_command(f'AT%MQTTCFG="nodes",{connection_id},"{client_name}","{broker_url}"')
        
        # Set IP type and port
        self.send_at_command(f'AT%MQTTCFG="IP",{connection_id},,{ip_type},{port}')
        
        # Set protocol, keep-alive, and clean session
        self.send_at_command(f'AT%MQTTCFG="PROTOCOL",{connection_id},0,{keep_alive},{clean_session}')
        
        # Enable all MQTT events
        self.send_at_command(f'AT%MQTTEV="all",{connection_id}')

    def mqtt_connect(self, connection_id):
        """
        Connect to the MQTT broker.
        
        :param connection_id: MQTT connection ID.
        :return: The connection confirmation status.
        """
        response = self.send_at_command(f'AT%MQTTCMD="connect",{connection_id}')
        if "%MQTTEVU:\"CONCONF\"" in response:
            print("Connected to MQTT broker successfully.")
        return response

    def mqtt_disconnect(self, connection_id):
        """
        Disconnect from the MQTT broker.
        
        :param connection_id: MQTT connection ID.
        :return: The disconnect confirmation status.
        """
        response = self.send_at_command(f'AT%MQTTCMD="disconnect",{connection_id}')
        if "%MQTTEVU:\"DISCONF\"" in response:
            print("Disconnected from MQTT broker.")
        return response

    def mqtt_subscribe(self, connection_id, topic, qos=0):
        """
        Subscribe to a topic on the MQTT broker.
        
        :param connection_id: MQTT connection ID.
        :param topic: The topic to subscribe to.
        :param qos:
[truncated — 2633 more characters]
```

### super_node/chat.py

```python
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding as sym_padding
import spec_pb2
import uuid
import time
import base64
import threading
import os
import random
from skylo import SerialWrapper
from rylr998 import RYLR998

NODE_ID = uuid.uuid4().hex[:8]  # Generate a UUID4 for the node ID
AES_KEY = b'password'.ljust(16, b'\0')[:16]  # Ensure the key is 16 bytes

serial_port = '/dev/ttyUSB0'  # Replace with your serial port
wrapper = SerialWrapper(serial_port, baudrate=115200, timeout=1)
wrapper.mqtt_config(connection_id=1, client_name="testclient_12458_sk", broker_url="test.mosquitto.org")
wrapper.mqtt_connect(1)

# Set to keep track of received packet UUIDs
received_packets = set()
# Dictionary to keep track of sent packets and their acknowledgment status
acknowledgments = {}
# Set to keep track of discovered nodes
discovered_nodes = set()


def aes_encrypt(message):
    if isinstance(message, str):
        message = message.encode()

    # Generate a random 16-byte IV for CBC mode
    iv = os.urandom(16)

    # Create AES cipher with the key and IV in CBC mode
    cipher = Cipher(algorithms.AES(AES_KEY), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()

    # Add padding to the message (AES requires the input to be a multiple of the block size)
    padder = sym_padding.PKCS7(algorithms.AES.block_size).padder()
    padded_message = padder.update(message) + padder.finalize()

    # Encrypt the message
    encrypted_message = encryptor.update(padded_message) + encryptor.finalize()

    # Return the IV and the encrypted message
    return iv + encrypted_message

def aes_decrypt(ciphertext):
    # Extract the IV (first 16 bytes) and the actual encrypted message
    iv = ciphertext[:16]
    encrypted_message = ciphertext[16:]

    # Create AES cipher with the key and IV in CBC mode
    cipher = Cipher(algorithms.AES(AES_KEY), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()

    # Decrypt the message
    decrypted_padded_message = decryptor.update(encrypted_message) + decryptor.finalize()

    # Remove padding from the decrypted message
    unpadder = sym_padding.PKCS7(algorithms.AES.block_size).unpadder()
    decrypted_message = unpadder.update(decrypted_padded_message) + unpadder.finalize()

    return decrypted_message.decode()


def initialize_lora(address, network_id):
    # Initialize the LoRa module
    lora = RYLR998(port='/dev/ttyAMA0')
    lora.set_address(address)  # Set this node's address
    lora.set_network_id(network_id)  # Set the network ID
    lora.set_rf_parameters(11,9,4,12)
    lora.set_band(902687500)

    return lora

# AES Encryption and Decryption functions remain unchanged

def send_message(lora, destination, message_content):
    # Encrypt the message
    encrypted_message = aes_encrypt(message_content.encode())

    # Create a NetworkMessage
    network_message = spec_pb2.NetworkMessage()
    network_message.node_id = NODE_ID
    network_message.timestamp = int(time.time())
    network_message.message_content = encrypted_message
    network_message.destination = destination

    # Create a Packet and assign the NetworkMessage to it
    packet = spec_pb2.Packet()
    packet.packet_uuid = uuid.uuid4().hex[:8]  # Generate a UUID4 for the packet
    packet.packet_type = spec_pb2.NETWORK_MESSAGE
    packet.network_message.CopyFrom(network_message)

    # Serialize and send the packet
    send_packet(lora, packet)

    # Add the packet to the acknowledgment dictionary
    acknowledgments[packet.packet_uuid] = False

def send_packet(lora, packet):
    serialized_packet = packet.SerializeToString()
    b64_packet = base64.b64encode(serialized_packet)
    lora.send_data(0, str(b64_packet.decode()))
    print(f"Sent packet: {b64_packet} / {packet}")

def listen_for_data(lora):
    print("Listening for incoming data...")
    while True:
        incoming_data = lora.receive_data()
        if incoming_data:
            print(f"Received raw data: {incoming_data}")
            data_parts = incoming_data.split(",")
            if len(data_parts) >= 3:
                encoded_packet = data_parts[2]
                serialized_packet = base64.b64decode(encoded_packet)
                received_packet = spec_pb2.Packet()
                received_packet.ParseFromString(serialized_packet)

                if received_packet.packet_uuid in received_packets:
                    print(f"Duplicate packet {received_packet.packet_uuid} received, skipping processing.")
                    continue

                received_packets.add(received_packet.packet_uuid)
                print(f"Received Packet: {received_packet}")

                if received_packet.packet_type == spec_pb2.NETWORK_MESSAGE:
                    process_network_message(lora, received_packet)
                elif received_packet.packet_type == spec_pb2.ACK_MESSAGE:
                    process_ack_message(received_packet)
                elif received_packet.packet_type == spec_pb2.DISCOVER_MESSAGE:
                    process_discover_message(lora, received_packet)
                elif received_packet.packet_type == spec_pb2.ANNOUNCE_MESSAGE:
                    process_announce_message(lora, received_packet)

def process_network_message(lora, received_packet):
    if received_packet.network_message.destination != NODE_ID and not received_packet.network_message.destination.startswith("+"):
        # Retransmit the packet if it's not for us
        print(f"Retransmitting packet {received_packet.packet_uuid}")
        # send_packet(lora, received_packet)
    else:
        # Process the message if it's for us or it's a relay SMS
        decrypted_message = aes_decrypt(received_packet.network_message.message_content)
        print(f"Decrypted message: {decrypted_message}")
        send_ack(lora, received_packet)
        
        if receive
[truncated — 3751 more characters]
```

[3 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]