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

Interface improvements #123

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 13 commits
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
115 changes: 59 additions & 56 deletions brping/pingmessage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import struct
from brping import definitions
payload_dict = definitions.payload_dict_all
asciiMsgs = [definitions.COMMON_NACK, definitions.COMMON_ASCII_TEXT]
variable_msgs = [definitions.PING1D_PROFILE, definitions.PING360_DEVICE_DATA, ]
asciiMsgs = [definitions.CommonMessage.NACK, definitions.CommonMessage.ASCII_TEXT]
variable_msgs = [definitions.Ping1dMessage.PROFILE, definitions.Ping360Message.DEVICE_DATA, ]


class PingMessage(object):
Expand Down Expand Up @@ -53,7 +53,7 @@ class PingMessage(object):
#
# @par Ex set:
# @code
# m = PingMessage(PING1D_SET_RANGE)
# m = PingMessage(Ping1dMessage.SET_RANGE)
# m.start_mm = 1000
# m.length_mm = 2000
# m.update_checksum()
Expand All @@ -63,64 +63,67 @@ class PingMessage(object):
# @par Ex receive:
# @code
# m = PingMessage(rxByteArray)
# if m.message_id == PING1D_RANGE
# if m.message_id == Ping1dMessage.RANGE
# start_mm = m.start_mm
# length_mm = m.length_mm
# @endcode
def __init__(self, msg_id=0, msg_data=None):
def __init__(self, msg_id=0, dst_device_id=0, src_device_id=0, **payload_fields):
## The message id
self.message_id = msg_id

## The request id for request messages
self.request_id = None

## The message destination
self.dst_device_id = 0
self.dst_device_id = dst_device_id
## The message source
self.src_device_id = 0
self.src_device_id = src_device_id
## The message checksum
self.checksum = 0

## The raw data buffer for this message
# update with pack_msg_data()
self.msg_data = None

# Constructor 1: make a pingmessage object from a binary data buffer
# (for receiving + unpacking)
if msg_data is not None:
if not self.unpack_msg_data(msg_data):
# Attempted to create an unknown message
return
# Constructor 2: make a pingmessage object cooresponding to a message
# id, with field members ready to access and populate
# (for packing + transmitting)
else:
try:
## The name of this message
self.name = payload_dict[self.message_id].name

try:
## The name of this message
self.name = payload_dict[self.message_id]["name"]
## The field names of this message
self.payload_field_names = payload_dict[self.message_id].field_names

## The field names of this message
self.payload_field_names = payload_dict[self.message_id]["field_names"]
# initialize payload field members
for attr in self.payload_field_names:
setattr(self, attr, payload_fields.get(attr, 0))

# initialize payload field members
for attr in self.payload_field_names:
setattr(self, attr, 0)
# initialize vector field if present in message
if self.message_id in variable_msgs:
last_field = self.payload_field_names[-1]
# only set if not already set by user
if getattr(self, last_field) == 0:
setattr(self, last_field, bytearray())

# initialize vector fields
if self.message_id in variable_msgs:
setattr(self, self.payload_field_names[-1], bytearray())
## Number of bytes in the message payload
self.update_payload_length()

## Number of bytes in the message payload
self.update_payload_length()
## The struct formatting string for the message payload
self.payload_format = self.get_payload_format()

## The struct formatting string for the message payload
self.payload_format = self.get_payload_format()
except KeyError as e:
message_id = self.message_id
raise Exception(f"{message_id = } not recognized\n{msg_data = }") from e
Copy link
Contributor Author

Choose a reason for hiding this comment

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

f-string f'{var=}' syntax requires Python >= 3.8
-> should specify in commit message
-> should update minimum version in setup.py

Copy link
Member

Choose a reason for hiding this comment

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

We can update to 1.* version, do all the necessary syntax corrections and move to python 3.9


# TODO handle better here, and catch Constructor 1 also
except KeyError as e:
print("message id not recognized: %d" % self.message_id, msg_data)
raise e
@classmethod
def from_buffer(cls, msg_data):
""" Alternate constructor - initialise from a binary data buffer. """
msg = cls()
if not msg.unpack_msg_data(msg_data):
# Attempted to create an unknown message
return
return msg

## Pack object attributes into self.msg_data (bytearray)
# @return self.msg_data
Expand All @@ -130,10 +133,10 @@ def pack_msg_data(self):
self.update_payload_length()

# Prepare struct packing format string
msg_format = PingMessage.endianess + PingMessage.header_format + self.get_payload_format()
msg_format = self.endianess + self.header_format + self.get_payload_format()

# Prepare complete list of field names (header + payload)
attrs = PingMessage.header_field_names + payload_dict[self.message_id]["field_names"]
attrs = self.header_field_names + payload_dict[self.message_id].field_names

# Prepare iterable ordered list of values to pack
values = []
Expand All @@ -148,7 +151,7 @@ def pack_msg_data(self):
self.msg_data = bytearray(struct.pack(msg_format, *values))

# Update and append checksum
self.msg_data += bytearray(struct.pack(PingMessage.endianess + PingMessage.checksum_format, self.update_checksum()))
self.msg_data += bytearray(struct.pack(self.endianess + self.checksum_format, self.update_checksum()))

return self.msg_data

Expand All @@ -158,32 +161,32 @@ def unpack_msg_data(self, msg_data):
self.msg_data = msg_data

# Extract header
header = struct.unpack(PingMessage.endianess + PingMessage.header_format, self.msg_data[0:PingMessage.headerLength])
header = struct.unpack(self.endianess + self.header_format, self.msg_data[0:self.headerLength])

for i, attr in enumerate(PingMessage.header_field_names):
for i, attr in enumerate(self.header_field_names):
setattr(self, attr, header[i])

## The name of this message
try:
self.name = payload_dict[self.message_id]["name"]
self.name = payload_dict[self.message_id].name
except KeyError:
print("Unknown message: ", self.message_id)
return False

## The field names of this message
self.payload_field_names = payload_dict[self.message_id]["field_names"]
self.payload_field_names = payload_dict[self.message_id].field_names

if self.payload_length > 0:
## The struct formatting string for the message payload
self.payload_format = self.get_payload_format()

# Extract payload
try:
payload = struct.unpack(PingMessage.endianess + self.payload_format, self.msg_data[PingMessage.headerLength:PingMessage.headerLength + self.payload_length])
payload = struct.unpack(self.endianess + self.payload_format, self.msg_data[self.headerLength:self.headerLength + self.payload_length])
except Exception as e:
print("error unpacking payload: %s" % e)
print("msg_data: %s, header: %s" % (msg_data, header))
print("format: %s, buf: %s" % (PingMessage.endianess + self.payload_format, self.msg_data[PingMessage.headerLength:PingMessage.headerLength + self.payload_length]))
print("format: %s, buf: %s" % (self.endianess + self.payload_format, self.msg_data[self.headerLength:self.headerLength + self.payload_length]))
print(self.payload_format)
else: # only use payload if didn't raise exception
for i, attr in enumerate(self.payload_field_names):
Expand All @@ -196,12 +199,12 @@ def unpack_msg_data(self, msg_data):
pass

# Extract checksum
self.checksum = struct.unpack(PingMessage.endianess + PingMessage.checksum_format, self.msg_data[PingMessage.headerLength + self.payload_length: PingMessage.headerLength + self.payload_length + PingMessage.checksumLength])[0]
self.checksum = struct.unpack(self.endianess + self.checksum_format, self.msg_data[self.headerLength + self.payload_length: self.headerLength + self.payload_length + self.checksumLength])[0]
return True

## Calculate the checksum from the internal bytearray self.msg_data
def calculate_checksum(self):
return sum(self.msg_data[0:PingMessage.headerLength + self.payload_length]) & 0xffff
return sum(self.msg_data[0:self.headerLength + self.payload_length]) & 0xffff

## Update the object checksum value
# @return the object checksum value
Expand All @@ -217,28 +220,28 @@ def verify_checksum(self):
def update_payload_length(self):
if self.message_id in variable_msgs or self.message_id in asciiMsgs:
# The last field self.payload_field_names[-1] is always the single dynamic-length field
self.payload_length = payload_dict[self.message_id]["payload_length"] + len(getattr(self, self.payload_field_names[-1]))
self.payload_length = payload_dict[self.message_id].payload_length + len(getattr(self, self.payload_field_names[-1]))
else:
self.payload_length = payload_dict[self.message_id]["payload_length"]
self.payload_length = payload_dict[self.message_id].payload_length

## Get the python struct formatting string for the message payload
# @return the payload struct format string
def get_payload_format(self):
# messages with variable length fields
if self.message_id in variable_msgs or self.message_id in asciiMsgs:
var_length = self.payload_length - payload_dict[self.message_id]["payload_length"] # Subtract static length portion from payload length
var_length = self.payload_length - payload_dict[self.message_id].payload_length # Subtract static length portion from payload length
if var_length <= 0:
return payload_dict[self.message_id]["format"] # variable data portion is empty
return payload_dict[self.message_id].format # variable data portion is empty

return payload_dict[self.message_id]["format"] + str(var_length) + "s"
return payload_dict[self.message_id].format + str(var_length) + "s"
else: # messages with a static (constant) length
return payload_dict[self.message_id]["format"]
return payload_dict[self.message_id].format

## Dump object into string representation
# @return string representation of the object
def __repr__(self):
header_string = "Header:"
for attr in PingMessage.header_field_names:
for attr in self.header_field_names:
header_string += " " + attr + ": " + str(getattr(self, attr))

if self.payload_length == 0: # this is a hack/guard for empty body requests
Expand All @@ -250,17 +253,17 @@ def __repr__(self):
if self.message_id in variable_msgs:

# static fields are handled as usual
for attr in payload_dict[self.message_id]["field_names"][:-1]:
for attr in payload_dict[self.message_id].field_names[:-1]:
payload_string += "\n - " + attr + ": " + str(getattr(self, attr))

# the variable length field is always the last field
attr = payload_dict[self.message_id]["field_names"][-1:][0]
attr = payload_dict[self.message_id].field_names[-1:][0]

# format this field as a list of hex values (rather than a string if we did not perform this handling)
payload_string += "\n - " + attr + ": " + str([hex(item) for item in getattr(self, attr)])

else: # handling of static length messages and text messages
for attr in payload_dict[self.message_id]["field_names"]:
for attr in payload_dict[self.message_id].field_names:
payload_string += "\n - " + attr + ": " + str(getattr(self, attr))

representation = (
Expand Down Expand Up @@ -369,7 +372,7 @@ def wait_checksum_h(self, msg_byte):
self.message_id = 0

self.buf.append(msg_byte)
self.rx_msg = PingMessage(msg_data=self.buf)
self.rx_msg = PingMessage.from_buffer(self.buf)

if self.rx_msg.verify_checksum():
self.parsed += 1
Expand Down Expand Up @@ -419,7 +422,7 @@ def parse_byte(self, msg_byte):
0x52,
4,
0,
definitions.COMMON_PROTOCOL_VERSION,
definitions.CommonMessage.PROTOCOL_VERSION,
0,
77,
211,
Expand Down
48 changes: 48 additions & 0 deletions examples/simplePing360Example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python

#simplePing360Example.py
from brping import Ping360
import time
import argparse

##Parse Command line options
############################

parser = argparse.ArgumentParser(description="Ping python library example (Ping360).",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--device', help="Ping360 device port. E.g: /dev/ttyUSB0")
parser.add_argument('--baudrate', type=int, default=115200, help="Ping360 device baudrate. E.g: 115200")
parser.add_argument('--udp', help="Ping360 UDP server. E.g: 192.168.2.2:9092")
args = parser.parse_args()
if args.device is None and args.udp is None:
parser.print_help()
exit(1)

# Make a new Ping
device = Ping360()
if args.device is not None:
device.connect_serial(args.device, args.baudrate)
elif args.udp is not None:
(host, port) = args.udp.split(':')
device.connect_udp(host, int(port))

with device:
line = "-" * 40
print(line)
print("Starting Ping360...")
print("Press CTRL+C to exit")
print(line)

input("Press Enter to continue...")

# Read and print
while "user hasn't quit":

# Read and print distance measurements with confidence
while True:
data = myPing.get_distance()
if data:
print("Distance: %s\tConfidence: %s%%" % (data["distance"], data["confidence"]))
else:
print("Failed to get distance data")
time.sleep(0.1)
39 changes: 29 additions & 10 deletions generate/generate-python.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,43 @@
"i32": "i",
"char": "s"}

payload_setup = '''\
from typing import NamedTuple, Tuple
from enum import IntEnum

class Payload(NamedTuple):
name: str
format: str
field_names: Tuple[str]
payload_length: int

class MessageEnum(IntEnum): pass

'''

f = open("%s/definitions.py" % args.output_directory, "w")

f.write(payload_setup)

for definition in definitions:
definitionFile = "%s/%s.json" % (definitionPath, definition)
f.write(g.generate(definitionFile, templateFile, {"structToken": struct_token, "base": definition}))

#allString = "payload_dict_all = {}\n"
# add PINGMESSAGE_UNDEFINED for legacy request support
allString = '\
PINGMESSAGE_UNDEFINED = 0\n\
payload_dict_all = {\n\
PINGMESSAGE_UNDEFINED: {\n\
"name": "undefined",\n\
"format": "",\n\
"field_names": (),\n\
"payload_length": 0\n\
},\n\
}\n'
allString = '''\
class UndefinedMessage(MessageEnum):
UNDEFINED = 0

payload_dict_all = {
UndefinedMessage.UNDEFINED: Payload(
name = "undefined",
format = "",
field_names = (),
payload_length = 0
),
}
'''

f.write(allString)

Expand Down
Loading