Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Waypoint creation/move/deletion #714

Merged
merged 3 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions examples/waypoint.py
Original file line number Diff line number Diff line change
@@ -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()
94 changes: 94 additions & 0 deletions meshtastic/mesh_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import collections
import json
import logging
import math
import random
import secrets
import sys
import threading
import time
Expand Down Expand Up @@ -700,6 +702,98 @@ def onResponseTelemetry(self, p: dict):
"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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the source of this ID generation formula? Looking at the other clients, android seems to just use whatever would be the next packet ID and apple seems to generate a random one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have done similar has in javascript API.
Note that it is not a packet ID but a waypoint ID.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I'm not sure why restricting it to 9 digits is useful, but it's probably fine in any case for this applicaiton.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function doesn't appear to exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I forgot those indeed.
Latest commit add them but I am not sure how they should really behave from a protocol perspective. So please guide me if it is wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are what'd normally be used by the CLI if it supported this stuff, but of course this PR doesn't include that. For now, I think they can just serve as placeholders, so what you have should be fine, I think.

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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to exist either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see commit 7cc18e9

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,
Expand Down