2024-05-08 22:02:07 +08:00
|
|
|
import cherrypy
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
from pubsub import pub
|
|
|
|
import meshtastic
|
|
|
|
import meshtastic.tcp_interface
|
|
|
|
import json
|
|
|
|
import datetime
|
2024-05-08 23:39:51 +08:00
|
|
|
import hashlib
|
|
|
|
import random
|
2024-05-08 22:02:07 +08:00
|
|
|
|
|
|
|
class MeshAPI(object):
|
|
|
|
def __init__(self, hostname='meshtastic.local'):
|
|
|
|
"""
|
|
|
|
Initialize the MeshAPI.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
hostname (str): The hostname or IP address of the Meshtastic device.
|
|
|
|
"""
|
|
|
|
self.hostname = hostname
|
|
|
|
self.iface = None
|
2024-05-08 23:39:51 +08:00
|
|
|
self.device_telemetry_cache = {} # Cache to store device telemetry data
|
|
|
|
self.environment_telemetry_cache = {} # Cache to store environment telemetry data
|
|
|
|
self.message_cache = {} # Cache to store messages
|
|
|
|
self.seen_nodes = {} # Dictionary to store information about seen nodes
|
|
|
|
self.connection_attempts = 0 # Number of connection attempts made
|
|
|
|
self.last_connection_time = None # Time of the last successful connection
|
2024-05-08 22:02:07 +08:00
|
|
|
self.connection_thread = threading.Thread(target=self.connect_to_mesh, daemon=True)
|
|
|
|
|
|
|
|
def on_receive(self, packet, interface):
|
|
|
|
"""
|
|
|
|
Callback function called when a packet arrives.
|
|
|
|
"""
|
|
|
|
# Debug
|
|
|
|
print(f"Received: from={packet.get('from')} to={packet.get('to')} portnum={packet['decoded'].get('portnum', 'UNKNOWN')}")
|
|
|
|
|
|
|
|
# Check if the received packet contains telemetry data
|
|
|
|
if 'decoded' in packet and 'telemetry' in packet['decoded']:
|
|
|
|
node_id = packet['from']
|
|
|
|
telemetry_data = packet['decoded']['telemetry']
|
|
|
|
|
|
|
|
# Check if telemetry data contains deviceMetrics
|
|
|
|
if 'deviceMetrics' in telemetry_data:
|
|
|
|
self.device_telemetry_cache[node_id] = {
|
|
|
|
"time": telemetry_data["time"],
|
|
|
|
"deviceMetrics": {
|
|
|
|
"batteryLevel": telemetry_data["deviceMetrics"].get("batteryLevel"),
|
|
|
|
"voltage": telemetry_data["deviceMetrics"].get("voltage"),
|
|
|
|
"channelUtilization": telemetry_data["deviceMetrics"].get("channelUtilization"),
|
|
|
|
"airUtilTx": telemetry_data["deviceMetrics"].get("airUtilTx")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check if telemetry data contains environmentMetrics
|
|
|
|
if 'environmentMetrics' in telemetry_data:
|
|
|
|
self.environment_telemetry_cache[node_id] = {
|
|
|
|
"time": telemetry_data["time"],
|
|
|
|
"environmentMetrics": {
|
|
|
|
"temperature": telemetry_data["environmentMetrics"].get("temperature"),
|
|
|
|
"relativeHumidity": telemetry_data["environmentMetrics"].get("relativeHumidity"),
|
|
|
|
"barometricPressure": telemetry_data["environmentMetrics"].get("barometricPressure")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check if the received packet is a message
|
|
|
|
if 'decoded' in packet and 'text' in packet['decoded']:
|
|
|
|
node_id = packet['from']
|
|
|
|
message_data = packet['decoded']['text']
|
2024-05-08 23:39:51 +08:00
|
|
|
internal_message_id = self.generate_internal_message_id(node_id, message_data)
|
|
|
|
self.message_cache[internal_message_id] = {"node_id": node_id, "message": message_data}
|
2024-05-08 22:02:07 +08:00
|
|
|
if len(self.message_cache) > 100:
|
|
|
|
self.message_cache.pop(0)
|
|
|
|
|
|
|
|
# Update seen nodes dictionary
|
|
|
|
if 'fromId' in packet:
|
|
|
|
node_id = packet['from']
|
|
|
|
long_id = packet['fromId']
|
|
|
|
if node_id not in self.seen_nodes:
|
|
|
|
self.seen_nodes[node_id] = {"long_id": long_id}
|
|
|
|
|
|
|
|
def on_connection(self, interface, topic=pub.AUTO_TOPIC):
|
|
|
|
"""
|
|
|
|
Callback function called when we (re)connect to the radio.
|
|
|
|
"""
|
|
|
|
self.last_connection_time = time.time()
|
|
|
|
self.connection_attempts += 1
|
|
|
|
print(f"Connected to Meshtastic device at {self.hostname}")
|
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
def index(self):
|
|
|
|
"""
|
2024-05-08 23:39:51 +08:00
|
|
|
Exposed endpoint for the index page.
|
2024-05-08 22:02:07 +08:00
|
|
|
"""
|
|
|
|
html = """
|
|
|
|
<html>
|
|
|
|
<head><title>Meshttpd Poor Man's Swagger</title></head>
|
|
|
|
<style>
|
|
|
|
body {
|
|
|
|
font-family: Arial, sans-serif;
|
|
|
|
background-image: url('data:image/png;base64,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
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
background-position: right;
|
|
|
|
background-position-y: center;
|
|
|
|
background-position-y: 1px;
|
|
|
|
}
|
|
|
|
h1 {
|
|
|
|
color: #333;
|
|
|
|
}
|
|
|
|
ul {
|
|
|
|
list-style-type: none;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
li {
|
|
|
|
margin-bottom: 10px;
|
|
|
|
}
|
|
|
|
li a {
|
|
|
|
color: #007bff;
|
|
|
|
text-decoration: none;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
<body>
|
|
|
|
<h1>Meshttpd Poor Man's Swagger</h1>
|
|
|
|
<h2>Endpoints:</h2>
|
|
|
|
<ul>
|
|
|
|
<li>
|
|
|
|
<a href="/api/mesh/send_message">/api/mesh/send_message</a>: POST endpoint to send a message
|
|
|
|
<ul>
|
|
|
|
<li>Parameters:
|
|
|
|
<ul>
|
|
|
|
<li><b>message</b> (str): The message to be sent.</li>
|
|
|
|
<li><b>node_id</b> (optional, str): The ID of the node to which the message should be sent.</li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<a href="/api/mesh/get_device_telemetry">/api/mesh/get_device_telemetry</a>: GET endpoint to retrieve device telemetry data
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<a href="/api/mesh/get_environment_telemetry">/api/mesh/get_environment_telemetry</a>: GET endpoint to retrieve environment telemetry data
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<a href="/api/mesh/get_last_messages">/api/mesh/get_last_messages</a>: GET endpoint to retrieve the last cached messages
|
|
|
|
</li>
|
2024-05-08 23:39:51 +08:00
|
|
|
<li>
|
|
|
|
<a href="/api/mesh/delete_message">/api/mesh/delete_message</a>: POST endpoint to delete a message from the cache
|
|
|
|
<ul>
|
|
|
|
<li>Parameters:
|
|
|
|
<ul>
|
|
|
|
<li><b>message_id</b> (str): The ID of the message to be deleted.</li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
2024-05-08 22:02:07 +08:00
|
|
|
<li>
|
|
|
|
<a href="/api/mesh/nodes">/api/mesh/nodes</a>: GET endpoint to list all seen nodes
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<a href="/api/mesh/status">/api/mesh/status</a>: GET endpoint to check connection status
|
|
|
|
</li>
|
|
|
|
</ul>
|
2024-05-08 23:39:51 +08:00
|
|
|
<p>Made by luhf for <a href="https://monocul.us">Monocul.us Mesh</a></p>
|
2024-05-08 22:02:07 +08:00
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
"""
|
|
|
|
return html
|
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def send_message(self, message=None, node_id=None):
|
|
|
|
"""
|
|
|
|
Exposed endpoint for sending a message.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
message (str): The message to be sent.
|
|
|
|
node_id (optional, str): The ID of the node to which the message should be sent.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
dict: A JSON object containing the status of the operation.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
404 (Missing parameters): If the `message` parameter is missing.
|
|
|
|
400 (Invalid node ID): If the specified node ID is invalid.
|
|
|
|
"""
|
|
|
|
if message is None:
|
|
|
|
cherrypy.response.status = 404
|
|
|
|
return {"error": "Missing parameters: message"}
|
|
|
|
try:
|
|
|
|
if self.iface:
|
|
|
|
if node_id:
|
|
|
|
try:
|
|
|
|
self.iface.sendText(message, node_id)
|
|
|
|
except meshtastic.node_manager.NodeIdNotFound:
|
|
|
|
raise cherrypy.HTTPError(400, "Invalid node ID")
|
|
|
|
else:
|
|
|
|
self.iface.sendText(message)
|
|
|
|
return {"status": "success", "message": "Message sent successfully"}
|
|
|
|
else:
|
|
|
|
return {"status": "error", "message": "Mesh interface not initialized"}
|
|
|
|
except Exception as ex:
|
|
|
|
return {"status": "error", "message": str(ex)}
|
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def get_device_telemetry(self):
|
|
|
|
"""
|
|
|
|
Exposed endpoint for retrieving device telemetry data from connected nodes.
|
|
|
|
"""
|
|
|
|
return self.device_telemetry_cache
|
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def get_environment_telemetry(self):
|
|
|
|
"""
|
|
|
|
Exposed endpoint for retrieving environment telemetry data from connected nodes.
|
|
|
|
"""
|
|
|
|
return self.environment_telemetry_cache
|
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def get_last_messages(self):
|
|
|
|
"""
|
|
|
|
Exposed endpoint for retrieving the last X cached messages.
|
|
|
|
"""
|
|
|
|
return self.message_cache
|
|
|
|
|
2024-05-08 23:39:51 +08:00
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def delete_message(self, message_id=None):
|
|
|
|
"""
|
|
|
|
Exposed endpoint for deleting a message from the cache.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
message_id (str): The ID of the message to be deleted.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
dict: A JSON object containing the status of the operation.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
404 (Missing parameters): If the `message_id` parameter is missing.
|
|
|
|
400 (Invalid message ID): If the specified message ID is invalid.
|
|
|
|
"""
|
|
|
|
if message_id is None:
|
|
|
|
cherrypy.response.status = 404
|
|
|
|
return {"error": "Missing parameters: message_id"}
|
|
|
|
if message_id not in self.message_cache:
|
|
|
|
raise cherrypy.HTTPError(400, "Invalid message ID")
|
|
|
|
del self.message_cache[message_id]
|
|
|
|
return {"status": "success", "message": "Message deleted successfully"}
|
|
|
|
|
2024-05-08 22:02:07 +08:00
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def nodes(self):
|
|
|
|
"""
|
|
|
|
Exposed endpoint for listing all seen nodes.
|
|
|
|
"""
|
|
|
|
return self.seen_nodes
|
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def status(self):
|
|
|
|
"""
|
|
|
|
Exposed endpoint for checking connection status.
|
|
|
|
"""
|
|
|
|
return {
|
|
|
|
"connected": bool(self.iface),
|
|
|
|
"last_connection_time": self.last_connection_time,
|
|
|
|
"total_connection_attempts": self.connection_attempts
|
|
|
|
}
|
|
|
|
|
|
|
|
def connect_to_mesh(self):
|
|
|
|
"""
|
|
|
|
Function to connect to the Meshtastic device.
|
|
|
|
"""
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
self.iface = meshtastic.tcp_interface.TCPInterface(hostname=self.hostname)
|
|
|
|
return
|
|
|
|
except Exception as ex:
|
|
|
|
print(f"Error: Could not connect to {self.hostname} {ex}")
|
|
|
|
time.sleep(1) # Wait for 1 second before attempting to reconnect
|
|
|
|
|
2024-05-08 23:39:51 +08:00
|
|
|
def generate_internal_message_id(self, node_id, message):
|
|
|
|
"""
|
|
|
|
Function to generate an internal message ID using MD5 hashing of random data, node ID, and message.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
node_id (str): The ID of the node.
|
|
|
|
message (str): The message.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
str: The generated internal message ID.
|
|
|
|
"""
|
|
|
|
random_data = str(random.random()).encode()
|
|
|
|
hash_input = random_data + node_id.encode() + message.encode()
|
|
|
|
hash_object = hashlib.md5(hash_input)
|
|
|
|
return hash_object.hexdigest()[:10]
|
|
|
|
|
2024-05-08 22:02:07 +08:00
|
|
|
def start(self):
|
|
|
|
"""
|
|
|
|
Function to start the connection thread.
|
|
|
|
"""
|
|
|
|
self.connection_thread.start()
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
# Change the IP address of the meshtastic device here.
|
|
|
|
# With some networks the local domain will not work, if that is your case use the IP address of the device.
|
|
|
|
mesh_api = MeshAPI(hostname='meshtastic.local')
|
|
|
|
|
|
|
|
# Start the connection thread
|
|
|
|
mesh_api.start()
|
|
|
|
|
|
|
|
# Subscribe to receive events
|
|
|
|
pub.subscribe(mesh_api.on_receive, "meshtastic.receive")
|
|
|
|
pub.subscribe(mesh_api.on_connection, "meshtastic.connection.established")
|
|
|
|
|
|
|
|
|
|
|
|
cherrypy.tree.mount(mesh_api, '/api/mesh')
|
|
|
|
#Please use a reverse proxy if you are planning to expose this to the internet!
|
|
|
|
#Set server.socket_host to 127.0.0.1
|
|
|
|
cherrypy.config.update({'server.socket_host': '0.0.0.0'})
|
|
|
|
cherrypy.engine.start()
|
|
|
|
cherrypy.engine.block()
|