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

Added new p1_capture utility, and simplified TCP/UDP/serial example apps. #347

Merged
merged 17 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
16 changes: 10 additions & 6 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ FusionEngine message specification.

### Applications

- [p1_capture](fusion_engine_client/applications/p1_capture.py) - Connect to a FusionEngine device in real time over
serial, TCP, UDP, or UNIX domain socket, and display incoming FusionEngine contents and/or log the incoming data to
disk
- [p1_display](fusion_engine_client/applications/p1_display.py) - Generate plots of vehicle trajectory, GNSS signal
status, wheel speed measurements, etc. from a file of logged FusionEngine messages
- [p1_extract](fusion_engine_client/applications/p1_extract.py) - Extract FusionEngine messages from a binary file
containing multiple data streams (e.g., interleaved RTCM and FusionEngine messages)
- [p1_filter](fusion_engine_client/applications/p1_filter.py) - Filter an incoming FusionEngine data stream, outputting
a new FusionEngine stream containing only the requested messages
- [p1_lband_extract](fusion_engine_client/applications/p1_lband_extract.py) - Extract L-band data bits contained from a
log of FusionEngine `LBandFrameMessage` messages
- [p1_print](fusion_engine_client/applications/p1_print.py) - Print the contents of FusionEngine messages found in a
Expand Down Expand Up @@ -59,13 +64,12 @@ FusionEngine message specification.
optionally mixed with other binary data, and decode the contents using the `FusionEngineDecoder` helper class
- [send_command.py](examples/send_command.py) - Send a command to a device over serial or TCP, and wait for a
response
- [serial_client.py](examples/serial_client.py) - Connect to a device over a local serial port and decode messages
in real time to be displayed and/or logged to disk using the `FusionEngineDecoder` helper class
- [tcp_client.py](examples/tcp_client.py) - Connect to a device over TCP and decode messages in real time to be
displayed and/or logged to disk using the `FusionEngineDecoder` helper class
- [serial_client.py](examples/serial_client.py) - Connect to a device over a local serial port and decode/print
incoming FusionEngine messages
- [tcp_client.py](examples/tcp_client.py) - Connect to a device over TCP and decode messages in real time and
decode/print incoming FusionEngine messages
- [udp_client.py](examples/udp_client.py) - Connect to a device over UDP and decode/display messages in real time
- Unlike [tcp_client.py](examples/tcp_client.py), currently assumes all incoming UDP packets contain
FusionEngine messages and does not use the `FusionEngineDecoder` helper class
and decode/print incoming FusionEngine messages
- `fusion_engine_client` - Top-level Python package directory
- `analysis`
- [analyzer.py](fusion_engine_client/analysis/analyzer.py) - `Analyzer` class, used by
Expand Down
125 changes: 25 additions & 100 deletions python/examples/serial_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env python3

from datetime import datetime
import os
import sys

Expand All @@ -15,115 +14,41 @@
root_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, root_dir)

from fusion_engine_client.applications.p1_print import add_print_format_argument, print_message
from fusion_engine_client.parsers import FusionEngineDecoder
from fusion_engine_client.utils import trace as logging
from fusion_engine_client.utils.argument_parser import ArgumentParser
from fusion_engine_client.messages import MessagePayload
from fusion_engine_client.parsers import FusionEngineDecoder


if __name__ == "__main__":
# Parse command-line arguments.
parser = ArgumentParser(description="""\
Connect to an Point One device over serial and print out the incoming message
contents and/or log the messages to disk.
Connect to a Point One device over serial and print out the incoming message
contents.
""")

parser.add_argument('-b', '--baud', type=int, default=460800,
help="The serial baud rate to be used.")
add_print_format_argument(parser, '--display-format')
parser.add_argument('-f', '--format', default='p1log', choices=('p1log', 'raw'),
help="The format of the file to be generated when --output is enabled."
"If 'p1log' (default), create a *.p1log file containing only FusionEngine messages."
"If 'raw', create a generic binary file containing all incoming data.")
parser.add_argument('-n', '--no-display', dest='display', action='store_false',
help="Do not display the incoming message contents.")
parser.add_argument('-o', '--output', type=str,
help="The path to a file where incoming data will be stored.")
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true',
help="Do not print anything to the console.")

parser.add_argument('port',
help="The serial device to use (e.g., /dev/ttyUSB0, COM1)")
options = parser.parse_args()

if options.quiet:
options.display = False

logging.basicConfig(format='%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s', stream=sys.stdout)
logger = logging.getLogger('point_one.fusion_engine')
logger.setLevel(logging.INFO)

# Open the output file if logging was requested.
if options.output is not None:
if options.format == 'p1log':
p1i_path = os.path.splitext(options.output)[0] + '.p1i'
if os.path.exists(p1i_path):
os.remove(p1i_path)

output_file = open(options.output, 'wb')
else:
output_file = None

generating_raw_log = (output_file is not None and options.format == 'raw')
generating_p1log = (output_file is not None and options.format == 'p1log')

# Connect to the device.
port = serial.Serial(port=options.port, baudrate=options.baud)

# Listen for incoming data.
decoder = FusionEngineDecoder(warn_on_unrecognized=not options.quiet, return_bytes=True)
bytes_received = 0
messages_received = 0
start_time = datetime.now()
last_print_time = start_time
while True:
# Read some data.
try:
received_data = port.read(1024)
bytes_received += len(received_data)

if not options.quiet:
now = datetime.now()
if (now - last_print_time).total_seconds() > 5.0:
print('Status: [bytes_received=%d, messages_received=%d elapsed_time=%d sec]' %
(bytes_received, messages_received, (now - start_time).total_seconds()))
last_print_time = now
except serial.SerialException as e:
print('Unexpected error reading from device:\r%s' % str(e))
break
except KeyboardInterrupt:
break

# If logging in raw format, write the data to disk as is.
if generating_raw_log:
output_file.write(received_data)

# Decode the incoming data and print the contents of any complete messages.
#
# Note that we pass the data to the decoder, even if --no-display was requested, for three reasons:
# - So that we get a count of the number of incoming messages
# - So we print warnings if the CRC fails on any of the incoming data
# - If we are logging in *.p1log format, so the decoder can extract the FusionEngine data from any
# non-FusionEngine data in the stream
messages = decoder.on_data(received_data)
messages_received += len(messages)

if options.display or generating_p1log:
for (header, message, raw_data) in messages:
if generating_p1log:
output_file.write(raw_data)

if options.display:
print_message(header, message, format=options.display_format)

# Close the serial port.
port.close()

# Close the output file.
if output_file is not None:
output_file.close()

if not options.quiet:
now = datetime.now()
elapsed_sec = (now - last_print_time).total_seconds() if last_print_time else 0.0
print('Status: [bytes_received=%d, messages_received=%d elapsed_time=%d sec]' %
(bytes_received, messages_received, elapsed_sec))
transport = serial.Serial(port=options.port, baudrate=options.baud)

# Listen for incoming data and parse FusionEngine messages.
try:
decoder = FusionEngineDecoder()
while True:
received_data = transport.read(1024)
messages = decoder.on_data(received_data)
for header, message in messages:
if isinstance(message, MessagePayload):
print(str(message))
else:
print(f'{header.message_type} message (not supported)')
except KeyboardInterrupt:
pass
except serial.SerialException as e:
print('Unexpected error reading from device:\r%s' % str(e))

# Close the transport when finished.
transport.close()
117 changes: 24 additions & 93 deletions python/examples/tcp_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env python3

from datetime import datetime
import os
import socket
import sys
Expand All @@ -10,108 +9,40 @@
root_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, root_dir)

from fusion_engine_client.applications.p1_print import add_print_format_argument, print_message
from fusion_engine_client.parsers import FusionEngineDecoder
from fusion_engine_client.utils.argument_parser import ArgumentParser
from fusion_engine_client.messages import MessagePayload
from fusion_engine_client.parsers import FusionEngineDecoder


if __name__ == "__main__":
# Parse command-line arguments.
parser = ArgumentParser(description="""\
Connect to an Point One device over TCP and print out the incoming message
contents and/or log the messages to disk.
Connect to a Point One device over TCP and print out the incoming message
contents.
""")

add_print_format_argument(parser, '--display-format')
parser.add_argument('-f', '--format', default='p1log', choices=('p1log', 'raw'),
help="The format of the file to be generated when --output is enabled."
"If 'p1log' (default), create a *.p1log file containing only FusionEngine messages."
"If 'raw', create a generic binary file containing all incoming data.")
parser.add_argument('-n', '--no-display', dest='display', action='store_false',
help="Do not display the incoming message contents.")
parser.add_argument('-o', '--output', type=str,
help="The path to a file where incoming data will be stored.")
parser.add_argument('-p', '--port', type=int, default=30201,
help="The FusionEngine TCP port on the data source.")
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true',
help="Do not print anything to the console.")

parser.add_argument('hostname',
help="The IP address or hostname of the data source.")
options = parser.parse_args()

if options.quiet:
options.display = False

# Open the output file if logging was requested.
if options.output is not None:
if options.format == 'p1log':
p1i_path = os.path.splitext(options.output)[0] + '.p1i'
if os.path.exists(p1i_path):
os.remove(p1i_path)

output_file = open(options.output, 'wb')
else:
output_file = None

generating_raw_log = (output_file is not None and options.format == 'raw')
generating_p1log = (output_file is not None and options.format == 'p1log')

# Connect to the device.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((socket.gethostbyname(options.hostname), options.port))

# Listen for incoming data.
decoder = FusionEngineDecoder(warn_on_unrecognized=not options.quiet, return_bytes=True)
bytes_received = 0
messages_received = 0
start_time = datetime.now()
last_print_time = start_time
while True:
# Read some data.
try:
received_data = sock.recv(1024)
bytes_received += len(received_data)

if not options.quiet:
now = datetime.now()
if (now - last_print_time).total_seconds() > 5.0:
print('Status: [bytes_received=%d, messages_received=%d elapsed_time=%d sec]' %
(bytes_received, messages_received, (now - start_time).total_seconds()))
last_print_time = now
except KeyboardInterrupt:
break

# If logging in raw format, write the data to disk as is.
if generating_raw_log:
output_file.write(received_data)

# Decode the incoming data and print the contents of any complete messages.
#
# Note that we pass the data to the decoder, even if --no-display was requested, for three reasons:
# - So that we get a count of the number of incoming messages
# - So we print warnings if the CRC fails on any of the incoming data
# - If we are logging in *.p1log format, so the decoder can extract the FusionEngine data from any
# non-FusionEngine data in the stream
messages = decoder.on_data(received_data)
messages_received += len(messages)

if options.display or generating_p1log:
for (header, message, raw_data) in messages:
if generating_p1log:
output_file.write(raw_data)

if options.display:
print_message(header, message, format=options.display_format)

# Close the socket.
sock.close()

# Close the output file.
if output_file is not None:
output_file.close()

if not options.quiet:
now = datetime.now()
elapsed_sec = (now - last_print_time).total_seconds() if last_print_time else 0.0
print('Status: [bytes_received=%d, messages_received=%d elapsed_time=%d sec]' %
(bytes_received, messages_received, elapsed_sec))
transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
transport.connect((socket.gethostbyname(options.hostname), options.port))

# Listen for incoming data and parse FusionEngine messages.
try:
decoder = FusionEngineDecoder()
while True:
received_data = transport.recv(1024)
messages = decoder.on_data(received_data)
for header, message in messages:
if isinstance(message, MessagePayload):
print(str(message))
else:
print(f'{header.message_type} message (not supported)')
except KeyboardInterrupt:
pass

# Close the transport when finished.
transport.close()
Loading
Loading