mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-28 13:34:17 +00:00
Initial commit: standalone Pyxis T-Deck firmware
Split T-Deck firmware from microReticulum examples/lxmf_tdeck/ into its own repo. microReticulum is consumed as a git submodule dependency pinned to feat/t-deck. All include paths updated from relative symlinks to bare includes resolved via library build flags. Both tdeck (NimBLE) and tdeck-bluedroid environments compile successfully. Licensed under AGPLv3. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
|
||||
#include "Interface.h"
|
||||
#include "Identity.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
#include "AutoInterfacePeer.h"
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <IPv6Address.h>
|
||||
#include <lwip/ip6_addr.h>
|
||||
#include <lwip/netdb.h>
|
||||
#include <lwip/sockets.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
// AutoInterface - automatic peer discovery via IPv6 multicast
|
||||
// Matches Python RNS AutoInterface behavior for interoperability
|
||||
class AutoInterface : public RNS::InterfaceImpl {
|
||||
|
||||
public:
|
||||
// Protocol constants (match Python RNS)
|
||||
static const uint16_t DEFAULT_DISCOVERY_PORT = 29716;
|
||||
static const uint16_t DEFAULT_DATA_PORT = 42671;
|
||||
static constexpr const char* DEFAULT_GROUP_ID = "reticulum";
|
||||
static constexpr double PEERING_TIMEOUT = 22.0; // seconds (matches Python RNS)
|
||||
static constexpr double ANNOUNCE_INTERVAL = 1.6; // seconds (matches Python RNS)
|
||||
static constexpr double MCAST_ECHO_TIMEOUT = 6.5; // seconds (matches Python RNS)
|
||||
static constexpr double REVERSE_PEERING_INTERVAL = ANNOUNCE_INTERVAL * 3.25; // ~5.2 seconds
|
||||
static constexpr double PEER_JOB_INTERVAL = 4.0; // seconds (matches Python RNS)
|
||||
static const size_t DEQUE_SIZE = 48; // packet dedup window
|
||||
static constexpr double DEQUE_TTL = 0.75; // seconds
|
||||
static const uint32_t BITRATE_GUESS = 10 * 1000 * 1000;
|
||||
static const uint16_t HW_MTU = 1196;
|
||||
|
||||
// Discovery token is full_hash(group_id + link_local_address) = 32 bytes
|
||||
// Python RNS sends and expects the full 32-byte hash (HASHLENGTH//8 = 256//8 = 32)
|
||||
static const size_t TOKEN_SIZE = 32;
|
||||
|
||||
public:
|
||||
AutoInterface(const char* name = "AutoInterface");
|
||||
virtual ~AutoInterface();
|
||||
|
||||
// Configuration (call before start())
|
||||
void set_group_id(const std::string& group_id) { _group_id = group_id; }
|
||||
void set_discovery_port(uint16_t port) { _discovery_port = port; }
|
||||
void set_data_port(uint16_t port) { _data_port = port; }
|
||||
void set_interface_name(const std::string& ifname) { _ifname = ifname; }
|
||||
|
||||
// InterfaceImpl overrides
|
||||
virtual bool start() override;
|
||||
virtual void stop() override;
|
||||
virtual void loop() override;
|
||||
|
||||
virtual inline std::string toString() const override {
|
||||
return "AutoInterface[" + _name + "/" + _group_id + "]";
|
||||
}
|
||||
|
||||
// Getters for testing
|
||||
const RNS::Bytes& get_discovery_token() const { return _discovery_token; }
|
||||
const RNS::Bytes& get_multicast_address() const { return _multicast_address_bytes; }
|
||||
size_t peer_count() const { return _peers.size(); }
|
||||
|
||||
// Carrier state tracking (matches Python RNS)
|
||||
bool carrier_changed() {
|
||||
bool changed = _carrier_changed;
|
||||
_carrier_changed = false; // Clear flag on read
|
||||
return changed;
|
||||
}
|
||||
void clear_carrier_changed() { _carrier_changed = false; }
|
||||
bool is_timed_out() const { return _timed_out; }
|
||||
|
||||
protected:
|
||||
virtual void send_outgoing(const RNS::Bytes& data) override;
|
||||
|
||||
private:
|
||||
// Discovery and addressing
|
||||
void calculate_multicast_address();
|
||||
void calculate_discovery_token();
|
||||
bool get_link_local_address();
|
||||
|
||||
// Socket operations
|
||||
bool setup_discovery_socket();
|
||||
bool setup_unicast_discovery_socket();
|
||||
bool setup_data_socket();
|
||||
bool join_multicast_group();
|
||||
|
||||
// Main loop operations
|
||||
void send_announce();
|
||||
void process_discovery();
|
||||
void process_unicast_discovery();
|
||||
void send_reverse_peering();
|
||||
void reverse_announce(AutoInterfacePeer& peer);
|
||||
void process_data();
|
||||
void check_echo_timeout();
|
||||
void check_link_local_address();
|
||||
|
||||
// Peer management
|
||||
#ifdef ARDUINO
|
||||
void add_or_refresh_peer(const IPv6Address& addr, double timestamp);
|
||||
#else
|
||||
void add_or_refresh_peer(const struct in6_addr& addr, double timestamp);
|
||||
#endif
|
||||
void expire_stale_peers();
|
||||
|
||||
// Deduplication
|
||||
bool is_duplicate(const RNS::Bytes& packet);
|
||||
void add_to_deque(const RNS::Bytes& packet);
|
||||
void expire_deque_entries();
|
||||
|
||||
// Configuration
|
||||
std::string _group_id = DEFAULT_GROUP_ID;
|
||||
uint16_t _discovery_port = DEFAULT_DISCOVERY_PORT;
|
||||
uint16_t _unicast_discovery_port = DEFAULT_DISCOVERY_PORT + 1; // 29717
|
||||
uint16_t _data_port = DEFAULT_DATA_PORT;
|
||||
std::string _ifname; // Network interface name (e.g., "eth0", "wlan0")
|
||||
|
||||
// Computed values
|
||||
RNS::Bytes _discovery_token; // 16 bytes
|
||||
RNS::Bytes _multicast_address_bytes; // 16 bytes (IPv6)
|
||||
struct in6_addr _multicast_address;
|
||||
struct in6_addr _link_local_address;
|
||||
std::string _link_local_address_str;
|
||||
std::string _multicast_address_str; // For logging
|
||||
bool _data_socket_ok = false; // Data socket initialized successfully
|
||||
#ifdef ARDUINO
|
||||
IPv6Address _link_local_ip; // ESP32: link-local as IPv6Address
|
||||
IPv6Address _multicast_ip; // ESP32: multicast as IPv6Address
|
||||
#endif
|
||||
|
||||
// Sockets
|
||||
#ifdef ARDUINO
|
||||
int _discovery_socket = -1; // Raw socket for IPv6 multicast discovery
|
||||
int _unicast_discovery_socket = -1; // Raw socket for unicast discovery (reverse peering)
|
||||
int _data_socket = -1; // Raw socket for IPv6 unicast data (WiFiUDP doesn't support IPv6)
|
||||
unsigned int _if_index = 0; // Interface index for scope_id
|
||||
#else
|
||||
int _discovery_socket = -1;
|
||||
int _unicast_discovery_socket = -1; // Socket for unicast discovery (reverse peering)
|
||||
int _data_socket = -1;
|
||||
unsigned int _if_index = 0; // Interface index for multicast
|
||||
#endif
|
||||
|
||||
// Peers and state
|
||||
std::vector<AutoInterfacePeer> _peers;
|
||||
double _last_announce = 0;
|
||||
double _last_peer_job = 0; // Timestamp of last peer job check
|
||||
|
||||
// Echo tracking (matches Python RNS multicast_echoes / initial_echoes)
|
||||
double _last_multicast_echo = 0.0; // Timestamp of last own echo received
|
||||
bool _initial_echo_received = false; // True once first echo received
|
||||
bool _timed_out = false; // Current timeout state
|
||||
bool _carrier_changed = false; // Flag for Transport layer notification
|
||||
bool _firewall_warning_logged = false; // Track firewall warning (log once)
|
||||
|
||||
// Deduplication: pairs of (packet_hash, timestamp)
|
||||
struct DequeEntry {
|
||||
RNS::Bytes hash;
|
||||
double timestamp;
|
||||
};
|
||||
std::deque<DequeEntry> _packet_deque;
|
||||
|
||||
// Receive buffer
|
||||
RNS::Bytes _buffer;
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <WiFi.h>
|
||||
#include <IPv6Address.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
// Lightweight peer info holder for AutoInterface
|
||||
// Not a full InterfaceImpl - just holds address and timing info
|
||||
struct AutoInterfacePeer {
|
||||
// IPv6 address storage
|
||||
#ifdef ARDUINO
|
||||
IPv6Address address; // Must use IPv6Address, not IPAddress (which is IPv4-only!)
|
||||
#else
|
||||
struct in6_addr address;
|
||||
#endif
|
||||
uint16_t data_port;
|
||||
double last_heard; // Timestamp of last activity
|
||||
double last_outbound; // Timestamp of last reverse peering sent
|
||||
bool is_local; // True if this is our own announcement (to ignore)
|
||||
|
||||
AutoInterfacePeer() : data_port(0), last_heard(0), last_outbound(0), is_local(false) {
|
||||
#ifndef ARDUINO
|
||||
memset(&address, 0, sizeof(address));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARDUINO
|
||||
AutoInterfacePeer(const IPv6Address& addr, uint16_t port, double time, bool local = false)
|
||||
: address(addr), data_port(port), last_heard(time), last_outbound(0), is_local(local) {}
|
||||
#else
|
||||
AutoInterfacePeer(const struct in6_addr& addr, uint16_t port, double time, bool local = false)
|
||||
: address(addr), data_port(port), last_heard(time), last_outbound(0), is_local(local) {}
|
||||
#endif
|
||||
|
||||
// Get string representation of address for logging
|
||||
std::string address_string() const {
|
||||
#ifdef ARDUINO
|
||||
// ESP32 IPAddress.toString() is IPv4-only, need manual IPv6 formatting
|
||||
char buf[64];
|
||||
// Read 16 bytes from IPAddress
|
||||
uint8_t addr[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
addr[i] = address[i];
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
|
||||
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7],
|
||||
addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]);
|
||||
return std::string(buf);
|
||||
#else
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET6, &address, buf, sizeof(buf));
|
||||
return std::string(buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Check if two peers have the same address
|
||||
#ifdef ARDUINO
|
||||
bool same_address(const IPv6Address& other) const {
|
||||
// Use IPv6Address == operator (properly compares all 16 bytes)
|
||||
return address == other;
|
||||
}
|
||||
#else
|
||||
bool same_address(const struct in6_addr& other) const {
|
||||
return memcmp(&address, &other, sizeof(address)) == 0;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// AutoInterface configuration - DO NOT COMMIT auto_config.h
|
||||
// Copy auto_config.h.example to auto_config.h and fill in your values
|
||||
|
||||
// WiFi credentials (for ESP32)
|
||||
#define AUTO_WIFI_SSID "your_wifi_ssid"
|
||||
#define AUTO_WIFI_PASSWORD "your_wifi_password"
|
||||
|
||||
// Network interface to use (optional - leave empty for auto-detect)
|
||||
// Examples: "eth0", "wlan0", "enp0s3"
|
||||
#define AUTO_INTERFACE_NAME ""
|
||||
|
||||
// Group ID (default: "reticulum" - must match Python RNS config)
|
||||
#define AUTO_GROUP_ID "reticulum"
|
||||
|
||||
// Ports (default values match Python RNS)
|
||||
#define AUTO_DISCOVERY_PORT 29716
|
||||
#define AUTO_DATA_PORT 42671
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "auto_interface",
|
||||
"version": "0.0.1",
|
||||
"description": "AutoInterface implementation for peer discovery via IPv6 multicast",
|
||||
"keywords": "reticulum, autointerface, ipv6, multicast",
|
||||
"license": "MIT",
|
||||
"frameworks": ["arduino"],
|
||||
"platforms": ["espressif32"],
|
||||
"dependencies": {
|
||||
"microReticulum": "*"
|
||||
},
|
||||
"build": {
|
||||
"flags": "-std=gnu++11 -I../../../../deps/microReticulum/src"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user