From 9284a848f2ed78bf5e732882cdbf0ec1482a6627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fejoz?= Date: Wed, 18 Dec 2024 17:36:58 +0100 Subject: [PATCH] feat(waypoint): Waypoint creation/deletion Add methods to send (create or move), delete waypoint. Add an example script to create, move, delete waypoint. --- examples/waypoint.py | 56 +++++++++++++++++++++ meshtastic/mesh_interface.py | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 examples/waypoint.py diff --git a/examples/waypoint.py b/examples/waypoint.py new file mode 100644 index 0000000..57d2e90 --- /dev/null +++ b/examples/waypoint.py @@ -0,0 +1,56 @@ +"""Program to create and delete waypoint + To run: + python3 examples/waypoint.py --port /dev/ttyUSB0 create 45 test the_desc_2 '2024-12-18T23:05:23' 48.74 7.35 + python examples/waypoint.py delete 45 +""" + +import argparse +import datetime +import sys +import time + +import meshtastic +import meshtastic.serial_interface + +parser = argparse.ArgumentParser( + prog='waypoint', + description='Create and delete Meshtastic waypoint') +parser.add_argument('--port', default=None) +parser.add_argument('--debug', default=False, action='store_true') + +subparsers = parser.add_subparsers(dest='cmd') +parser_delete = subparsers.add_parser('delete', help='Delete a waypoint') +parser_delete.add_argument('id', help="id of the waypoint") + +parser_create = subparsers.add_parser('create', help='Create a new waypoint') +parser_create.add_argument('id', help="id of the waypoint") +parser_create.add_argument('name', help="name of the waypoint") +parser_create.add_argument('description', help="description of the waypoint") +parser_create.add_argument('expire', help="expiration date of the waypoint as interpreted by datetime.fromisoformat") +parser_create.add_argument('latitude', help="latitude of the waypoint") +parser_create.add_argument('longitude', help="longitude of the waypoint") + +args = parser.parse_args() +print(args) + +# By default will try to find a meshtastic device, +# otherwise provide a device path like /dev/ttyUSB0 +if args.debug: + d = sys.stderr +else: + d = None +with meshtastic.serial_interface.SerialInterface(args.port, debugOut=d) as iface: + if args.cmd == 'create': + p = iface.sendWaypoint( + id=int(args.id), + name=args.name, + description=args.description, + expire=int(datetime.datetime.fromisoformat(args.expire).timestamp()), + latitude=float(args.latitude), + longitude=float(args.longitude), + ) + else: + p = iface.deleteWaypoint(int(args.id)) + print(p) + +# iface.close() diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 2da04ce..3f5a1b1 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -5,7 +5,9 @@ import collections import json import logging +import math import random +import secrets import sys import threading import time @@ -700,6 +702,98 @@ class MeshInterface: # pylint: disable=R0902 "No response from node. At least firmware 2.1.22 is required on the destination node." ) + def sendWaypoint( + self, + name, + description, + expire: int, + id: Optional[int] = None, + latitude: float = 0.0, + longitude: float = 0.0, + destinationId: Union[int, str] = BROADCAST_ADDR, + wantAck: bool = True, + wantResponse: bool = False, + channelIndex: int = 0, + ): + """ + Send a waypoint packet to some other node (normally a broadcast) + + Returns the sent packet. The id field will be populated in this packet and + can be used to track future message acks/naks. + """ + w = mesh_pb2.Waypoint() + w.name = name + w.description = description + w.expire = expire + if id is None: + seed = secrets.randbits(32) + w.id = math.floor(seed * math.pow(2, -32) * 1e9) + logging.debug(f"w.id:{w.id}") + else: + w.id = id + if latitude != 0.0: + w.latitude_i = int(latitude * 1e7) + logging.debug(f"w.latitude_i:{w.latitude_i}") + if longitude != 0.0: + w.longitude_i = int(longitude * 1e7) + logging.debug(f"w.longitude_i:{w.longitude_i}") + + if wantResponse: + onResponse = self.onResponseWaypoint + else: + onResponse = None + + d = self.sendData( + w, + destinationId, + portNum=portnums_pb2.PortNum.WAYPOINT_APP, + wantAck=wantAck, + wantResponse=wantResponse, + onResponse=onResponse, + channelIndex=channelIndex, + ) + if wantResponse: + self.waitForWaypoint() + return d + + def deleteWaypoint( + self, + id: int, + destinationId: Union[int, str] = BROADCAST_ADDR, + wantAck: bool = True, + wantResponse: bool = False, + channelIndex: int = 0, + ): + """ + Send a waypoint deletion packet to some other node (normally a broadcast) + + NB: The id must be the waypoint's id and not the id of the packet creation. + + Returns the sent packet. The id field will be populated in this packet and + can be used to track future message acks/naks. + """ + p = mesh_pb2.Waypoint() + p.id = id + p.expire = 0 + + if wantResponse: + onResponse = self.onResponseWaypoint + else: + onResponse = None + + d = self.sendData( + p, + destinationId, + portNum=portnums_pb2.PortNum.WAYPOINT_APP, + wantAck=wantAck, + wantResponse=wantResponse, + onResponse=onResponse, + channelIndex=channelIndex, + ) + if wantResponse: + self.waitForWaypoint() + return d + def _addResponseHandler( self, requestId: int,