From 379d3a75244bca6a598febaab586caa1a693fefc Mon Sep 17 00:00:00 2001 From: Ben Hall Date: Sat, 30 Dec 2023 01:48:03 -0500 Subject: [PATCH] updating py_data_acq and flake --- README.md | 14 ++-- broadcast.py | 44 ------------- flake.nix | 22 ++++++- py_data_acq/broadcast-test.py | 65 +++++++++++++++++++ .../py_data_acq/common_types/__init__.py | 0 .../py_data_acq/common_types/common.py | 4 ++ .../py_data_acq/foxglove_live/foxglove_ws.py | 19 +++--- py_data_acq/setup.py | 2 +- py_data_acq/test.py | 9 +-- 9 files changed, 112 insertions(+), 67 deletions(-) delete mode 100644 broadcast.py create mode 100644 py_data_acq/broadcast-test.py create mode 100644 py_data_acq/py_data_acq/common_types/__init__.py create mode 100644 py_data_acq/py_data_acq/common_types/common.py diff --git a/README.md b/README.md index 1a825bd..256262d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ usage: TODO: - -- [ ] write the data storage script for saving the received CAN messages locally in the mcap -- [ ] get nix-proto working with dbc input from url for creation of python lib -- [ ] get py_data_acq working in dev shell with nix-proto generated python lib for proto msg packing -- [ ] make the deserialization task for unpacking received data from CAN. +- [ ] write test script for creating a cantools constructed hytech CAN msg and sends it over a virtual CAN line +- [ ] make the deserialization task for unpacking received data from CAN in the data acq service script. +- [ ] make ability to start / stop / control in general the data recording via grpc calls for the mcap writer task +- [ ] make user script / interface for the grpc calls for ease of interaction with the service +- [ ] actually get current data from car into protobuf encoded CAN messages in an integration test +- [x] get nix-proto working with dbc input from url for creation of python lib +- [x] get py_data_acq working in dev shell with nix-proto generated python lib for proto msg packing - [x] make service script that creates an instance of the mcap writer and the foxglove websocket - [x] come up with a good way of associating the dbc file with the protobuf file @@ -16,8 +18,6 @@ TODO: - I know that I will be using cantools to create the DBC file so I might as well extend that creation script to create the proto at the same time. Additionally, I know that I will be using tim's auto-magic nix-proto for creation of the python auto-gen code. -- [ ] actually get current data from car into protobuf encoded CAN messages and send them from current TCU / SAB -- [ ] get the raspberry pi listening to CAN messages ## automation goals - [x] dbc and proto file generation using CI diff --git a/broadcast.py b/broadcast.py deleted file mode 100644 index 0f878a8..0000000 --- a/broadcast.py +++ /dev/null @@ -1,44 +0,0 @@ -import socket -import time -import py_data_acq.test.ht_data_pb2 as ht_data_pb2 # Import the generated Python code from your .proto file - - -# Define the IP and port for the UDP socket -UDP_IP = "127.0.0.1" -UDP_PORT = 12345 - -def main(): - # Create an instance of your message - my_message = ht_data_pb2.ht_data() - my_message.status = "ERROR" - my_message.shock_fr = 12.23 - my_message.shock_fl = 12.23 - my_message.shock_br = 12.23 - my_message.shock_bl = 12.23 - - # Serialize the message to bytes - serialized_message = my_message.SerializeToString() - - # Create a UDP socket - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - while(1): - time.sleep(0.2) - try: - # Send the serialized message over the UDP socket - my_message.shock_fr += 0.3 - serialized_message = my_message.SerializeToString() - sock.sendto(serialized_message, (UDP_IP, UDP_PORT)) - print(f"Message sent to {UDP_IP}:{UDP_PORT}") - except KeyboardInterrupt: - # Handle Ctrl+C to exit the loop gracefully - sock.close() - break - except Exception as e: - print(f"Error sending message: {e}") - # finally: - # sock.close() - -if __name__ == "__main__": - main() - \ No newline at end of file diff --git a/flake.nix b/flake.nix index 710c81b..94bb4d9 100644 --- a/flake.nix +++ b/flake.nix @@ -64,14 +64,30 @@ devShells.x86_64-linux.default = pkgs.mkShell rec { # Update the name to something that suites your project. name = "nix-devshell"; - packages = with pkgs; [ py_data_acq_pkg py_dbc_proto_gen_pkg proto_gen_pkg cmake ]; + packages = with pkgs; [ jq py_data_acq_pkg py_dbc_proto_gen_pkg proto_gen_pkg cmake ]; # Setting up the environment variables you need during # development. shellHook = let icon = "f121"; in '' path=${pkgs.proto_gen_pkg} - path+="/bin" - export BIN_PATH=$path + bin_path=path+"/bin" + dbc_path=path+"/dbc" + + export BIN_PATH=$bin_path + export DBC_PATH=$dbc_path + PYTHON_INTERPRETER_PATH=$(which python) + + # Path to the settings.json file in your VSCode workspace + SETTINGS_JSON_FILE=".vscode/settings.json" + + # Check if the settings.json file exists, if not, create it + if [ ! -f "$SETTINGS_JSON_FILE" ]; then + mkdir -p "$(dirname "$SETTINGS_JSON_FILE")" + echo "{}" > "$SETTINGS_JSON_FILE" + fi + + jq --arg pythonPath "$PYTHON_INTERPRETER_PATH" '. + { "python.pythonPath": $pythonPath }' "$SETTINGS_JSON_FILE" > "$SETTINGS_JSON_FILE.tmp" && mv "$SETTINGS_JSON_FILE.tmp" "$SETTINGS_JSON_FILE" + export PS1="$(echo -e '\u${icon}') {\[$(tput sgr0)\]\[\033[38;5;228m\]\w\[$(tput sgr0)\]\[\033[38;5;15m\]} (${name}) \\$ \[$(tput sgr0)\]" ''; }; diff --git a/py_data_acq/broadcast-test.py b/py_data_acq/broadcast-test.py new file mode 100644 index 0000000..4169fa9 --- /dev/null +++ b/py_data_acq/broadcast-test.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +import socket +import time + +from hytech_np_proto_py import hytech_pb2 + +# Define the IP and port for the UDP socket +UDP_IP = "127.0.0.1" +UDP_PORT = 12345 + + +def main(): + # Create an instance of your message + my_message = hytech_pb2.id_dashboard_status() + my_message.start_button = True + my_message.buzzer_active = False + my_message.ssok_above_threshold = True + my_message.shutdown_h_above_threshold = True + my_message.mark_button = True + my_message.mode_button = True + my_message.motor_controller_cycle_button = True + my_message.launch_ctrl_button_ = True + my_message.torque_mode_button = True + my_message.led_dimmer_button = True + + my_message.dial_state = "yo" + my_message.ams_led = "yo" + my_message.imd_led = "yo" + my_message.mode_led = "yo" + my_message.motor_controller_error_led = "yo" + my_message.start_status_led = "yo" + my_message.inertia_status_led = "yo" + my_message.mechanical_brake_led = "yo" + my_message.gen_purp_led = "yo" + my_message.bots_led = "yo" + my_message.cockpit_brb_led = "yo" + my_message.crit_charge_led = "yo" + my_message.glv_led = "yo" + my_message.launch_control_led = "yo" + + # Serialize the message to bytes + serialized_message = my_message.SerializeToString() + + # Create a UDP socket + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + while 1: + time.sleep(0.2) + try: + # Send the serialized message over the UDP socket + serialized_message = my_message.SerializeToString() + sock.sendto(serialized_message, (UDP_IP, UDP_PORT)) + print(f"Message sent to {UDP_IP}:{UDP_PORT}") + except KeyboardInterrupt: + # Handle Ctrl+C to exit the loop gracefully + sock.close() + break + except Exception as e: + print(f"Error sending message: {e}") + # finally: + # sock.close() + + +if __name__ == "__main__": + main() diff --git a/py_data_acq/py_data_acq/common_types/__init__.py b/py_data_acq/py_data_acq/common_types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py_data_acq/py_data_acq/common_types/common.py b/py_data_acq/py_data_acq/common_types/common.py new file mode 100644 index 0000000..61b78db --- /dev/null +++ b/py_data_acq/py_data_acq/common_types/common.py @@ -0,0 +1,4 @@ +class QueueData(): + def __init__(self, schema_name: str, data: bytes): + self.name = schema_name + self.data = data \ No newline at end of file diff --git a/py_data_acq/py_data_acq/foxglove_live/foxglove_ws.py b/py_data_acq/py_data_acq/foxglove_live/foxglove_ws.py index 1637f24..b08f212 100644 --- a/py_data_acq/py_data_acq/foxglove_live/foxglove_ws.py +++ b/py_data_acq/py_data_acq/foxglove_live/foxglove_ws.py @@ -1,4 +1,6 @@ import asyncio + +from py_data_acq.common_types.common import QueueData from typing import Any from foxglove_websocket import run_cancellable @@ -12,33 +14,34 @@ # what I want to do with this class is extend the foxglove server to make it where it creates a protobuf schema # based foxglove server that serves data from an asyncio queue. class HTProtobufFoxgloveServer(FoxgloveServer): - def __init__(self, host: str, port: int, name: str, pb_bin_file_path: str): + def __init__(self, host: str, port: int, name: str, pb_bin_file_path: str, schema_names: list[str]): super().__init__(host, port, name) self.path = pb_bin_file_path - + self.schema_names = schema_names self.schema = standard_b64encode(open(pb_bin_file_path, "rb").read()).decode("ascii") # this is run when we use this in a with statement for context management async def __aenter__(self): await super().__aenter__() - self.chan_id = await super().add_channel( + # TODO add channels for all of the msgs that are in the protobuf schema + for name in self.schema_names: + self.chan_id_dict[name] = await super().add_channel( { - "topic": "car data", + "topic": name +"_data", "encoding": "protobuf", - "schemaName": "ht_data", + "schemaName": name, "schema": self.schema, } ) - return self async def __aexit__(self, exc_type: Any, exc_val: Any, traceback: Any): return await super().__aexit__(exc_type, exc_val, traceback) - async def send_msgs_from_queue(self, queue): + async def send_msgs_from_queue(self, queue: asyncio.Queue[QueueData]): try: data = await queue.get() if data is not None: - await super().send_message(self.chan_id, time.time_ns(), data) + await super().send_message(self.chan_id_dict[data.name], time.time_ns(), data.data) except asyncio.CancelledError: pass diff --git a/py_data_acq/setup.py b/py_data_acq/setup.py index d117a9a..50c6808 100644 --- a/py_data_acq/setup.py +++ b/py_data_acq/setup.py @@ -5,5 +5,5 @@ name="py_data_acq", version="1.0", packages=find_packages(), - scripts=['test.py'] + scripts=['test.py', 'broadcast-test.py'] ) \ No newline at end of file diff --git a/py_data_acq/test.py b/py_data_acq/test.py index 2ccf00f..db52c77 100644 --- a/py_data_acq/test.py +++ b/py_data_acq/test.py @@ -30,7 +30,7 @@ async def write_data_to_mcap(queue, mcap_writer): while True: await mcw.write_data(queue) -async def consume_data(queue, foxglove_server): +async def fxglv_websocket_consume_data(queue, foxglove_server): async with foxglove_server as fz: while True: await fz.send_msgs_from_queue(queue) @@ -50,15 +50,16 @@ async def main(): receiver_task = asyncio.create_task(continuous_udp_receiver(queue, queue2)) - fx_task = asyncio.create_task(consume_data(queue, fx_s)) + fx_task = asyncio.create_task(fxglv_websocket_consume_data(queue, fx_s)) # in the mcap task I actually have to deserialize the any protobuf msg into the message ID and # the encoded message for the message id. I will need to handle the same association of message id # and schema in the foxglove websocket server. - mcap_task = asyncio.create_task(write_data_to_mcap(queue, mcap_writer)) + # mcap_task = asyncio.create_task(write_data_to_mcap(queue, mcap_writer)) # TODO the data consuming MCAP file task for writing MCAP files to specific directory - await asyncio.gather(receiver_task, fx_task, mcap_task) + await asyncio.gather(receiver_task, fx_task) + # await asyncio.gather(receiver_task, fx_task, mcap_task) # await asyncio.gather(receiver_task, mcap_task) if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file