mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
127 lines
5.5 KiB
Python
127 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# UDP Interface Listener
|
|
# credit to pdxlocations for all of this core work https://github.com/pdxlocations/
|
|
# depends on: pip install meshtastic protobuf zeroconf pubsub
|
|
# 2025 Kelly Keeton K7MHI
|
|
from pubsub import pub
|
|
from meshtastic.protobuf import mesh_pb2, portnums_pb2
|
|
from mudp import UDPPacketStream, node, conn, send_text_message, send_nodeinfo, send_device_telemetry, send_position, send_environment_metrics, send_power_metrics, send_waypoint, send_data
|
|
from mudp.encryption import generate_hash
|
|
import time
|
|
from zeroconf import Zeroconf, ServiceBrowser
|
|
import socket
|
|
|
|
MCAST_GRP, MCAST_PORT, CHANNEL_ID, KEY = "224.0.0.69", 4403, "LongFast", "1PG7OiApB1nwvP+rz05pAQ=="
|
|
PUBLIC_CHANNEL_IDS = ["LongFast", "ShortSlow", "MediumFast", "MediumSlow", "ShortFast", "ShortTurbo"]
|
|
mudpEnabled, mudpInterface = True, None
|
|
messages = []
|
|
|
|
class ZeroconfListner:
|
|
def add_service(self, zeroconf, type, name):
|
|
info = zeroconf.get_service_info(type, name)
|
|
if info:
|
|
txt = info.properties
|
|
ip = None
|
|
if info.addresses:
|
|
ip = socket.inet_ntoa(info.addresses[0])
|
|
print(f"Found Meshtastic node: id={txt.get(b'id', b'').decode()} shortname={txt.get(b'shortname', b'').decode()} longname={txt.get(b'longname', b'').decode()} ip={ip}")
|
|
|
|
def update_service(self, zeroconf, type, name):
|
|
# This method is required by zeroconf, but you can leave it empty if you don't need updates.
|
|
pass
|
|
|
|
def initalize_mudp():
|
|
global mudpInterface
|
|
if mudpEnabled and mudpInterface is None:
|
|
mudpInterface = UDPPacketStream(MCAST_GRP, MCAST_PORT, key=KEY)
|
|
print(f"MUDP Interface initialized with multicast group", MCAST_GRP, "port", MCAST_PORT)
|
|
node.node_id, node.long_name, node.short_name = "!deadbeef", "UDP Test", "UDP"
|
|
node.channel, node.key = "LongFast", KEY
|
|
conn.setup_multicast(MCAST_GRP, MCAST_PORT)
|
|
|
|
def on_recieve(packet: mesh_pb2.MeshPacket, addr=None):
|
|
print(f"\n[RECV] Packet received from {addr}")
|
|
print("from:", getattr(packet, "from", None))
|
|
print("to:", packet.to)
|
|
|
|
# Check against all public channels
|
|
matched_channel = None
|
|
for channel_name in PUBLIC_CHANNEL_IDS:
|
|
channel_hash = generate_hash(channel_name, KEY)
|
|
if packet.channel == channel_hash:
|
|
matched_channel = channel_name
|
|
break
|
|
|
|
if matched_channel:
|
|
channel_status = f"Match ({matched_channel})"
|
|
else:
|
|
channel_status = f"Hash: {packet.channel}"
|
|
|
|
print("channel:", channel_status)
|
|
|
|
if packet.HasField("decoded"):
|
|
port_name = portnums_pb2.PortNum.Name(packet.decoded.portnum) if packet.decoded.portnum else "N/A"
|
|
try:
|
|
payload_decoded = True
|
|
packet_payload = packet.decoded.payload.decode("utf-8", "ignore")
|
|
except Exception:
|
|
print(" payload (raw bytes):", packet.decoded.payload)
|
|
else:
|
|
print(f"encrypted: { {packet.encrypted} }")
|
|
|
|
|
|
print("id:", packet.id or None)
|
|
print("rx_time:", packet.rx_time or None)
|
|
print("rx_snr:", packet.rx_snr or None)
|
|
print("hop_limit:", packet.hop_limit or None)
|
|
priority_name = mesh_pb2.MeshPacket.Priority.Name(packet.priority) if packet.priority else "N/A"
|
|
print("priority:", priority_name or None)
|
|
print("rx_rssi:", packet.rx_rssi or None)
|
|
print("hop_start:", packet.hop_start or None)
|
|
print("next_hop:", packet.next_hop or None)
|
|
print("relay_node:", packet.relay_node or None)
|
|
|
|
print(f"decoded {{portnum: {port_name}, payload: {packet_payload if payload_decoded else 'N/A'}, bitfield: {packet.decoded.bitfield or None}}}" if packet.HasField("decoded") else "No decoded field")
|
|
|
|
pub.subscribe(on_recieve, "mesh.rx.packet")
|
|
# pub.subscribe(on_text_message, "mesh.rx.port.1")
|
|
# pub.subscribe(on_nodeinfo, "mesh.rx.port.4") # NODEINFO_APP
|
|
|
|
zeroconf = Zeroconf()
|
|
listener = ZeroconfListner()
|
|
browser = ServiceBrowser(zeroconf, "_meshtastic._tcp.local.", listener)
|
|
|
|
def main():
|
|
initalize_mudp()
|
|
mudpInterface.start()
|
|
try:
|
|
while True: time.sleep(0.05)
|
|
except KeyboardInterrupt: pass
|
|
finally: mudpInterface.stop()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|
|
|
|
# Meshtastic Port Numbers Reference:
|
|
# | Port Number | Name | Purpose |
|
|
# |-------------|------------------------|--------------------------------|
|
|
# | 1 | TEXT_MESSAGE_APP | Text messages |
|
|
# | 2 | POSITION_APP | Position updates (GPS) |
|
|
# | 3 | ROUTING_APP | Routing info |
|
|
# | 4 | NODEINFO_APP | Node info (name, id, etc) |
|
|
# | 5 | TELEMETRY_APP | Telemetry (battery, sensors) |
|
|
# | 6 | SERIAL_APP | Serial data |
|
|
# | 7 | ENVIRONMENTAL_APP | Environmental sensors |
|
|
# | 8 | REMOTE_HARDWARE_APP | Remote hardware control |
|
|
# | 9 | STORE_FORWARD_APP | Store and forward |
|
|
# | 10 | RANGE_TEST_APP | Range test |
|
|
# | 11 | ADMIN_APP | Admin/config |
|
|
# | 12 | WAYPOINT_APP | Waypoints |
|
|
# | 13 | CHANNEL_NODEINFO_APP | Channel node info |
|
|
# | 256 | PRIVATE_APP | Private app (custom use) |
|
|
# See: https://github.com/meshtastic/protobufs/blob/main/meshtastic/protobuf/portnums.proto
|
|
|
|
|