From 3c300d4d53785eb04816b9b0065c38ccd8952599 Mon Sep 17 00:00:00 2001 From: Nicholas DePatie <80368116+nwdepatie@users.noreply.github.com> Date: Thu, 29 Feb 2024 23:08:25 -0500 Subject: [PATCH] Got Rust autogeneration working with Rust prebuild hook! (#33) * Got Rust autogeneration working with Rust prebuild hook! * Adding in git admin for adding in autogen * Adding in a more verbose failure for autogen * Add automatic reconnect --------- Co-authored-by: Peyton-McKee --- .github/workflows/rust-ci.yml | 8 +- .gitignore | 2 + .gitmodules | 3 + Embedded-Base | 1 + build.rs | 20 +++ calypsogen.py | 35 +++++ oxy/RustSynth.py | 158 -------------------- oxy/YAMLParser.py | 26 ---- oxy/can-messages/bms.yaml | 255 --------------------------------- oxy/can-messages/mpu.yaml | 57 -------- oxy/can-messages/wheel.yaml | 16 --- oxy/structs/CANField.py | 19 --- oxy/structs/CANMsg.py | 22 --- oxy/structs/Decoding.py | 32 ----- oxy/structs/Format.py | 56 -------- oxy/structs/Messages.py | 9 -- oxy/structs/NetworkEncoding.py | 23 --- oxy/structs/Result.py | 13 -- oxy/typedpoc.py | 21 --- src/decode_data.rs | 237 ------------------------------ src/main.rs | 2 +- src/master_mapping.rs | 27 ---- src/mqtt.rs | 1 + 23 files changed, 70 insertions(+), 973 deletions(-) create mode 100644 .gitmodules create mode 160000 Embedded-Base create mode 100644 build.rs create mode 100644 calypsogen.py delete mode 100644 oxy/RustSynth.py delete mode 100644 oxy/YAMLParser.py delete mode 100644 oxy/can-messages/bms.yaml delete mode 100644 oxy/can-messages/mpu.yaml delete mode 100644 oxy/can-messages/wheel.yaml delete mode 100644 oxy/structs/CANField.py delete mode 100644 oxy/structs/CANMsg.py delete mode 100644 oxy/structs/Decoding.py delete mode 100644 oxy/structs/Format.py delete mode 100644 oxy/structs/Messages.py delete mode 100644 oxy/structs/NetworkEncoding.py delete mode 100644 oxy/structs/Result.py delete mode 100644 oxy/typedpoc.py delete mode 100644 src/decode_data.rs delete mode 100644 src/master_mapping.rs diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 2726935..2308ffd 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -16,7 +16,13 @@ jobs: steps: - name: Setup Rust - uses: actions/checkout@v2 + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install Python and Pip + run: sudo apt-get install python3 python3-pip + - name: Install ruamel.yaml + run: pip install "ruamel.yaml<0.18.0" - name: Install cargo-audit run: cargo install cargo-audit - name: Build diff --git a/.gitignore b/.gitignore index e22ff85..1fcceda 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ __pycache__/ build/ dist/ logs/ +master_mapping.rs +decode_data.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..672744f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Embedded-Base"] + path = Embedded-Base + url = git@github.com:Northeastern-Electric-Racing/Embedded-Base.git diff --git a/Embedded-Base b/Embedded-Base new file mode 160000 index 0000000..0c828dc --- /dev/null +++ b/Embedded-Base @@ -0,0 +1 @@ +Subproject commit 0c828dc580b36d8a8eb5f881307b1cfef78b4f9e diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..588a383 --- /dev/null +++ b/build.rs @@ -0,0 +1,20 @@ +use std::process::Command; + +/* Prebuild script */ +fn main() { + println!("cargo:rerun-if-env-changed=ALWAYS_RUN"); + + match Command::new("python3").arg("./calypsogen.py").status() { + Ok(status) if status.success() => { + println!("Python script executed successfully"); + }, + Ok(status) => { + eprintln!("Python script exited with status: {}", status); + std::process::exit(1); + }, + Err(e) => { + eprintln!("Failed to execute Python script: {}", e); + std::process::exit(1); + }, + } +} \ No newline at end of file diff --git a/calypsogen.py b/calypsogen.py new file mode 100644 index 0000000..07d6c09 --- /dev/null +++ b/calypsogen.py @@ -0,0 +1,35 @@ +import importlib.util +import sys + +# Full path to the directory containing the cangen module +EMBEDDED_BASE_PATH = "./Embedded-Base" +module_name = "cangen" + +# Full path to the cangen module file +module_path = f"{EMBEDDED_BASE_PATH}/{module_name}/__init__.py" + +# Add the cangen directory to the system's path +sys.path.append(EMBEDDED_BASE_PATH) + +# Load the module +spec = importlib.util.spec_from_file_location(module_name, module_path) +cangen = importlib.util.module_from_spec(spec) +spec.loader.exec_module(cangen) + +decode_data = open("./src/decode_data.rs", "w") +master_mapping = open("./src/master_mapping.rs", "w") + +bms_messages = cangen.YAMLParser().parse(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/bms.yaml", "r")) +mpu_messages = cangen.YAMLParser().parse(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/mpu.yaml", "r")) +wheel_messages = cangen.YAMLParser().parse(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/wheel.yaml", "r")) + +bms_messages.msgs.extend(mpu_messages.msgs) +bms_messages.msgs.extend(wheel_messages.msgs) + +result = cangen.RustSynth().parse_messages(bms_messages.msgs) + +decode_data.write(result.decode_data) +decode_data.close() + +master_mapping.write(result.master_mapping) +master_mapping.close() diff --git a/oxy/RustSynth.py b/oxy/RustSynth.py deleted file mode 100644 index 227eb81..0000000 --- a/oxy/RustSynth.py +++ /dev/null @@ -1,158 +0,0 @@ -from structs.CANField import CANField -from structs.CANMsg import CANMsg -from structs.Result import Result -from typing import List - - -class RustSynth: - """ - A class to synthesize Rust from a given CANMsg spec. - """ - - ignore_clippy: str = ( - "#![allow(clippy::all)]\n" # Ignoring clippy for decode_data because it's autogenerated and has some unnecessary type casting to ensure correct types - ) - decode_data_import: str = ( - "use super::data::{Data,FormatData as fd, ProcessData as pd}; \n" # Importing the Data struct and the FormatData and ProcessData traits - ) - - decode_return_type: str = "Vec::" # The return type of any decode function - decode_return_value: str = ( - f" let result = vec![" # Initializing the result vector - ) - decode_close: str = ( - " ]; \n result\n}\n" # Returning the result vector and closing the function - ) - - decode_mock: str = """ -pub fn decode_mock(_data: &[u8]) -> Vec:: { - let result = vec![ - Data::new(vec![0.0], "Mock", "") - ]; - result -} -""" # A mock decode function that is used for messages that don't have a decode function - - master_mapping_import: str = ( - "use super::decode_data::*; \nuse super::data::Data; \n" # Importing all the functions in decode_data.rs file and the Data struct - ) - - master_mapping_signature: str = ( - "pub fn get_message_info(id: &u32) -> MessageInfo { \n match id {" # The signature of the master_mapping function - ) - - master_mapping_closing: str = ( - " _ => MessageInfo::new(decode_mock), \n }\n}" # The closing of the master_mapping function and the default case for the match statement that returns the mock decode function - ) - - message_info = """ -pub struct MessageInfo { - pub decoder: fn(data: &[u8]) -> Vec, -} - -impl MessageInfo { - pub fn new(decoder: fn(data: &[u8]) -> Vec) -> Self { - Self { - decoder - } - } -} -""" # The MessageInfo struct that is used to store the decode function for a given message - - # The main function of the RustSynth class. Takes a list of CANMsgs and returns a Result object that contains the synthesized Rust code for the decode_data.rs and master_mapping.rs files - def parse_messages(self, msgs: List[CANMsg]) -> Result: - result = Result("", "") - result.decode_data += self.ignore_clippy - result.decode_data += self.decode_data_import - result.decode_data += self.decode_mock - - result.master_mapping += self.master_mapping_import - result.master_mapping += self.message_info - result.master_mapping += self.master_mapping_signature - - for msg in msgs: - result.decode_data += self.synthesize(msg) + "\n" - result.master_mapping += self.map_msg_to_decoder(msg) - - result.master_mapping += self.master_mapping_closing - return result - - # Helper function that maps a given CANMsg to its decode function - def map_msg_to_decoder(self, msg: CANMsg) -> str: - return f" {msg.id} => MessageInfo::new({self.function_name(msg.desc)}),\n" - - # Helper function that synthesizes the decode function for a given CANMsg - def synthesize(self, msg: CANMsg) -> str: - signature: str = self.signature(msg.desc) - length_check: str = self.add_length_check(msg.networkEncoding[0].fields) - generated_lines: list[str] = [] - # Generate a line for each field in the message - generated_lines += self.parse_network_encoding(msg) - total_list: list[str] = ( - [signature, length_check, self.decode_return_value] - + generated_lines - + [self.decode_close] - ) - return "\n".join(total_list) - - def parse_network_encoding( - self, msg: CANMsg - ) -> list[str]: # Change return type to list[str] - result = [] - networkEncoding = msg.networkEncoding[0] - if networkEncoding.id == "csv": - result.append(f" {networkEncoding.start}") - result.append( - f" {','.join(self.decode_field_value(field) for field in networkEncoding.fields)}" - ) - result.append(f" {networkEncoding.closing}") - result.append( - f' , "{networkEncoding.topic}", "{networkEncoding.unit}")' - ) - elif networkEncoding.id == "single_point": - for field in networkEncoding.fields: - result.append(f" {networkEncoding.start}") - result.append(f" {self.decode_field_value(field)}") - result.append(f" {networkEncoding.closing}") - result.append(f' , "{field.name}", "{field.unit}"), ') - return result - - def add_length_check(self, fields: List[CANField]) -> str: - fieldSize = sum(field.size for field in fields) - return f"if data.len() < {fieldSize} {{ return vec![]; }}" - - def decode_field_value(self, field: CANField) -> str: - return f"{self.format_data(field, self.parse_decoders(field))}" - - # Helper function that generates the name of a decode function for a given CANMsg based off the can message description - def function_name(self, desc: str) -> str: - return f"decode_{desc.replace(' ', '_').lower()}" - - # Helper function that generates the signature of a decode function for a given CANMsg based off the can message description - def signature(self, desc: str) -> str: - return f"pub fn {self.function_name(desc)}(data: &[u8]) -> {self.decode_return_type} {{" - - # Helper function that generates a line the data struct for a given CANField value - def finalize_line(self, topic: str, unit: str, val: str) -> str: - return f' Data::new({val}, "{topic}", "{unit}"),' - - # Helper function that parses the decoders for a given CANField by applying the decoders to the data and casting the result to the final type of the CANField. - def parse_decoders(self, field: CANField) -> str: - if isinstance(field.decodings, type(None)): - return f"data[{field.index}] as {field.final_type}" - else: - base: str - if field.size == 1: - base = f"data[{field.index}]" - else: - base = f"&data[{field.index}..{field.index + field.size}]" - for decoder in field.decodings: - base = f"pd::{decoder.repr}({base} as {decoder.entry_type}, {decoder.bits})" - return f"{base} as {field.final_type}" - - # Helper function that formats the data for a given CANField based off the format of the CANField if it exists, returns the decoded data otherwise - def format_data(self, field: CANField, decoded_data: str) -> str: - cf = decoded_data - if field.format: - cf = f"fd::{field.format}({decoded_data})" - return cf diff --git a/oxy/YAMLParser.py b/oxy/YAMLParser.py deleted file mode 100644 index 49d2363..0000000 --- a/oxy/YAMLParser.py +++ /dev/null @@ -1,26 +0,0 @@ -from ruamel.yaml import YAML, Any -from structs.CANMsg import CANMsg -from structs.CANField import CANField -from structs.Format import Format -from structs.Decoding import Decoding -from structs.Messages import Messages -from structs.NetworkEncoding import NetworkEncoding - -class YAMLParser: - ''' - A class to parse a given YAML string or file. Most of the heavy lifting - is done by the internals of ruamel.yaml. - ''' - - def __init__(self): - self.yaml = YAML() - self.yaml.register_class(Messages) - self.yaml.register_class(CANMsg) - self.yaml.register_class(CANField) - for encoding in NetworkEncoding.__subclasses__(): - self.yaml.register_class(encoding) - for decoding in Decoding.__subclasses__(): - self.yaml.register_class(decoding) - - def parse(self, file: Any) -> Messages: - return self.yaml.load(file) diff --git a/oxy/can-messages/bms.yaml b/oxy/can-messages/bms.yaml deleted file mode 100644 index b906a3c..0000000 --- a/oxy/can-messages/bms.yaml +++ /dev/null @@ -1,255 +0,0 @@ -!Messages -msgs: -#BMS BROADCAST -- !CANMsg - id: "0x80" - desc: "accumulator status" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "BMS/Pack/Voltage" - unit: "V" - size: 2 - decodings: - - !BigEndian - bits: 8 - format: "high_voltage" - - !CANField - name: "BMS/Pack/Current" - unit: "A" - size: 2 - decodings: - - !BigEndian - bits: 8 - - !TwosComplement - bits: 16 - format: "current" - - !CANField - name: "BMS/Pack/Amp-hours" - unit: "Ah" - size: 2 - decodings: - - !BigEndian - bits: 8 - - !CANField - name: "BMS/Pack/SOC" - unit: "%" - size: 1 - - !CANField - name: "BMS/Pack/Health" - unit: "%" - size: 1 - -- !CANMsg - id: "0x81" - desc: "BMS Status" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "BMS/State" - unit: "" - size: 1 - - !CANField - name: "BMS/Faults" - unit: "" - size: 4 - decodings: - - !LittleEndian - bits: 8 - - !CANField - name: "BMS/Temps/Average" - unit: "C" - size: 1 - decodings: - - !TwosComplement - bits: 8 - - !CANField - name: "BMS/Temps/Internal" - size: 1 - unit: "C" - decodings: - - !TwosComplement - bits: 8 - - !CANField - name: "BMS/Cells/BurningStatus" - size: 1 - unit: "" - -- !CANMsg - id: "0x82" - desc: "Shutdown Control" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "BMS/Shutdown/MPE" - size: 1 - unit: "" - -- !CANMsg - id: "0x83" - desc: "Cell Data" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "BMS/Cells/Volts/High/Value" - size: 2 - unit: "V" - decodings: - - !LittleEndian - bits: 8 - format: "cell_voltage" - - !CANField - name: "BMS/Cells/Volts/High/Chip" - size: 1 - unit: "" - decodings: - - !Half - bits: 4 - - !CANField - name: "BMS/Cells/Volts/High/Cell" - index: 2 - size: 1 - unit: "" - decodings: - - !Half - bits: 0 - - !CANField - name: "BMS/Cells/Volts/Low/Value" - size: 2 - index: 3 - unit: "V" - decodings: - - !LittleEndian - bits: 8 - format: "cell_voltage" - - !CANField - name: "BMS/Cells/Volts/Low/Chip" - index: 5 - size: 1 - unit: "" - decodings: - - !Half - bits: 4 - - !CANField - name: "BMS/Cells/Volts/Low/Cell" - index: 5 - size: 1 - unit: "" - decodings: - - !Half - bits: 0 - - !CANField - name: "BMS/Cells/Volts/Avg/Value" - size: 2 - index: 6 - unit: "V" - decodings: - - !LittleEndian - bits: 8 - format: "cell_voltage" - -- !CANMsg - id: "0x84" - desc: "Cell Temperatures" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "BMS/Cells/Temp/High/Value" - unit: "C" - size: 2 - decodings: - - !LittleEndian - bits: 8 - - !TwosComplement - bits: 16 - - !CANField - name: "BMS/Cells/Temp/High/Cell" - unit: "" - size: 1 - decodings: - - !Half - bits: 4 - - !CANField - name: "BMS/Cells/Temp/High/Chip" - unit: "" - size: 1 - index: 2 - decodings: - - !Half - bits: 0 - - !CANField - name: "BMS/Cells/Temp/Low/Value" - unit: "C" - size: 2 - index: 3 - decodings: - - !LittleEndian - bits: 8 - - !TwosComplement - bits: 16 - - !CANField - name: "BMS/Cells/Temp/Low/Cell" - unit: "" - size: 1 - index: 5 - decodings: - - !Half - bits: 4 - - !CANField - name: "BMS/Cells/Temp/Low/Chip" - unit: "" - size: 1 - index: 5 - decodings: - - !Half - bits: 0 - - !CANField - name: "BMS/Cells/Temp/Avg/Value" - unit: "C" - size: 2 - index: 6 - decodings: - - !LittleEndian - bits: 8 - - !TwosComplement - bits: 16 - -- !CANMsg - id: "0x85" - desc: "Segment Temperatures" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "BMS/Segment/Temp/1" - unit: "C" - size: 1 - decodings: - - !TwosComplement - bits: 8 - - !CANField - name: "BMS/Segment/Temp/2" - unit: "C" - size: 1 - decodings: - - !TwosComplement - bits: 8 - - !CANField - name: "BMS/Segment/Temp/3" - unit: "C" - size: 1 - decodings: - - !TwosComplement - bits: 8 - - !CANField - name: "BMS/Segment/Temp/4" - unit: "C" - size: 1 - decodings: - - !TwosComplement - bits: 8 diff --git a/oxy/can-messages/mpu.yaml b/oxy/can-messages/mpu.yaml deleted file mode 100644 index b998c1e..0000000 --- a/oxy/can-messages/mpu.yaml +++ /dev/null @@ -1,57 +0,0 @@ -!Messages -msgs: -- !CANMsg - id: "0x500" - desc: "MPU Acceleromter" - networkEncoding: - - !CSV - topic: "MPU/Accel" - unit: "g" - fields: - - !CANField - name: "MPU/Accel/X" - unit: "g" - size: 2 - decodings: - - !BigEndian - bits: 8 - format: "acceleration" - - !CANField - name: "MPU/Accel/Y" - unit: "g" - size: 2 - decodings: - - !BigEndian - bits: 8 - format: "acceleration" - - !CANField - name: "MPU/Accel/Z" - unit: "g" - size: 2 - decodings: - - !BigEndian - bits: 8 - format: "acceleration" - -- !CANMsg - id: "0x501" - desc: "MPU Status" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "MPU/State/Mode" - unit: "" - size: 1 - - !CANField - name: "MPU/State/Torque_Limit_Percentage" - unit: "" - size: 1 - - !CANField - name: "MPU/State/Regen_Strength" - unit: "" - size: 1 - - !CANField - name: "MPU/State/Traction_Control" - unit: "" - size: 1 \ No newline at end of file diff --git a/oxy/can-messages/wheel.yaml b/oxy/can-messages/wheel.yaml deleted file mode 100644 index 0ccb60e..0000000 --- a/oxy/can-messages/wheel.yaml +++ /dev/null @@ -1,16 +0,0 @@ -!Messages -msgs: -- !CANMsg - id: "0x680" - desc: "Wheel State" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "WHEEL/Buttons/1" - unit: "" - size: 1 - - !CANField - name: "WHEEL/Buttons/2" - unit: "" - size: 1 \ No newline at end of file diff --git a/oxy/structs/CANField.py b/oxy/structs/CANField.py deleted file mode 100644 index dc6bdbc..0000000 --- a/oxy/structs/CANField.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import annotations -from .Decoding import * -from ruamel.yaml import Optional -from dataclasses import dataclass - -@dataclass -class CANField: - ''' - Represents a field in a CAN message. Has an id, a name, a unit, a size, - and an optional Format and Decodings. Also knows its own - index within its parent CANMsg, which is assigned at load from YAML. - ''' - name: str - unit: str - size: int - index: int = -1 - final_type: str = "f32" - decodings: Optional[list[Decoding]] = None - format: Optional[str] = None diff --git a/oxy/structs/CANMsg.py b/oxy/structs/CANMsg.py deleted file mode 100644 index ac50c8b..0000000 --- a/oxy/structs/CANMsg.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass -from .NetworkEncoding import NetworkEncoding - -@dataclass -class CANMsg: - ''' - Represents a CAN message. Has an id, a description, and a number of individual fields. - ''' - id: str - desc: str - networkEncoding: list[NetworkEncoding] - - def __post_init__(self) -> None: - idx: int = 0 - for field in self.networkEncoding[0].fields: - if (field.index is not None): - field.index = idx - idx += field.size - - def __setstate__(self, state): - self.__init__(**state) diff --git a/oxy/structs/Decoding.py b/oxy/structs/Decoding.py deleted file mode 100644 index bf05872..0000000 --- a/oxy/structs/Decoding.py +++ /dev/null @@ -1,32 +0,0 @@ -from dataclasses import dataclass - -@dataclass -class Decoding: - ''' - This is an abstract class (well, what passes for one in Python) - that represents a decoding to be applied to a slice of data. - ''' - bits: int - entry_type: str - repr: str = "*"*42 - -@dataclass -class BigEndian(Decoding): - repr: str = "big_endian" - entry_type = "&[u8]" - -@dataclass -class LittleEndian(Decoding): - repr: str = "little_endian" - entry_type = "&[u8]" - -@dataclass -class TwosComplement(Decoding): - repr: str = "twos_comp" - entry_type = "u32" - - -@dataclass -class Half(Decoding): - repr: str = "half" - entry_type = "u8" diff --git a/oxy/structs/Format.py b/oxy/structs/Format.py deleted file mode 100644 index bb923dd..0000000 --- a/oxy/structs/Format.py +++ /dev/null @@ -1,56 +0,0 @@ -from dataclasses import dataclass - -@dataclass -class Format: - ''' - Represents a format to be applied to data after decoding. - ''' - repr: str = "" - -@dataclass -class Temperature(Format): - repr: str = "temperature" - -@dataclass -class LowVoltage(Format): - repr: str = "low_voltage" - -@dataclass -class Torque(Format): - repr: str = "torque" - -@dataclass -class HighVoltage(Format): - repr: str = "high_voltage" - -@dataclass -class Current(Format): - repr: str = "current" - -@dataclass -class Angle(Format): - repr: str = "angle" - -@dataclass -class AngularVelocity(Format): - repr: str = "angular_velocity" - -@dataclass -class Frequency(Format): - repr: str = "frequency" - -@dataclass -class Power(Format): - repr: str = "power" - -@dataclass -class Timer(Format): - repr: str = "timer" - -@dataclass -class Flux(Format): - repr: str = "flux" - -@dataclass -class CellVoltage(Format): - repr: str = "cell_voltage" diff --git a/oxy/structs/Messages.py b/oxy/structs/Messages.py deleted file mode 100644 index eeac46e..0000000 --- a/oxy/structs/Messages.py +++ /dev/null @@ -1,9 +0,0 @@ -from dataclasses import dataclass -from .CANMsg import CANMsg - -@dataclass -class Messages: - ''' - Represents a list of CAN messages. Has a list of CANMsgs. - ''' - msgs: list[CANMsg] diff --git a/oxy/structs/NetworkEncoding.py b/oxy/structs/NetworkEncoding.py deleted file mode 100644 index e7ea217..0000000 --- a/oxy/structs/NetworkEncoding.py +++ /dev/null @@ -1,23 +0,0 @@ -from dataclasses import dataclass -from .CANField import CANField -from ruamel.yaml import Optional - -@dataclass -class NetworkEncoding: - ''' - Determines the format of the data to be sent over the network. - ''' - id: str - fields: list[CANField] - topic: Optional[str] = None - unit: Optional[str] = None - start: str = "Data::new(vec![" - closing: str = "]" - -@dataclass -class CSV(NetworkEncoding): - id = "csv" - -@dataclass -class SinglePoint(NetworkEncoding): - id = "single_point" diff --git a/oxy/structs/Result.py b/oxy/structs/Result.py deleted file mode 100644 index 9b1d317..0000000 --- a/oxy/structs/Result.py +++ /dev/null @@ -1,13 +0,0 @@ - -class Result: - """ - This class is used to store the results of the RustSynth.py script. - decode_data is the synthesized Rust code for the decode_data.rs file. - master_mapping is the synthesized Rust code for the master_mapping.rs file. - """ - decode_data: str - master_mapping: str - - def __init__(self, decode_data: str, master_mapping: str): - self.decode_data = decode_data - self.master_mapping = master_mapping diff --git a/oxy/typedpoc.py b/oxy/typedpoc.py deleted file mode 100644 index 052ab37..0000000 --- a/oxy/typedpoc.py +++ /dev/null @@ -1,21 +0,0 @@ -from YAMLParser import YAMLParser -from RustSynth import RustSynth - -decode_data = open("../src/decode_data.rs", "w") -master_mapping = open("../src/master_mapping.rs", "w") - -bms_messages = YAMLParser().parse(open("can-messages/bms.yaml", "r")) -mpu_messages = YAMLParser().parse(open("can-messages/mpu.yaml", "r")) -wheel_messages = YAMLParser().parse(open("can-messages/wheel.yaml", "r")) - - -bms_messages.msgs.extend(mpu_messages.msgs) -bms_messages.msgs.extend(wheel_messages.msgs) - -result = RustSynth().parse_messages(bms_messages.msgs) - -decode_data.write(result.decode_data) -decode_data.close() - -master_mapping.write(result.master_mapping) -master_mapping.close() \ No newline at end of file diff --git a/src/decode_data.rs b/src/decode_data.rs deleted file mode 100644 index 2f9ac86..0000000 --- a/src/decode_data.rs +++ /dev/null @@ -1,237 +0,0 @@ -#![allow(clippy::all)] -use super::data::{Data, FormatData as fd, ProcessData as pd}; - -pub fn decode_mock(_data: &[u8]) -> Vec { - let result = vec![Data::new(vec![0.0], "Mock", "")]; - result -} -pub fn decode_accumulator_status(data: &[u8]) -> Vec { - if data.len() < 8 { - return vec![]; - } - let result = vec![ - Data::new( - vec![fd::high_voltage( - pd::big_endian(&data[0..2] as &[u8], 8) as f32 - )], - "BMS/Pack/Voltage", - "V", - ), - Data::new( - vec![fd::current( - pd::twos_comp(pd::big_endian(&data[2..4] as &[u8], 8) as u32, 16) as f32, - )], - "BMS/Pack/Current", - "A", - ), - Data::new( - vec![pd::big_endian(&data[4..6] as &[u8], 8) as f32], - "BMS/Pack/Amp-hours", - "Ah", - ), - Data::new(vec![data[6] as f32], "BMS/Pack/SOC", "%"), - Data::new(vec![data[7] as f32], "BMS/Pack/Health", "%"), - ]; - result -} - -pub fn decode_bms_status(data: &[u8]) -> Vec { - if data.len() < 8 { - return vec![]; - } - let result = vec![ - Data::new(vec![data[0] as f32], "BMS/State", ""), - Data::new( - vec![pd::little_endian(&data[1..5] as &[u8], 8) as f32], - "BMS/Faults", - "", - ), - Data::new( - vec![pd::twos_comp(data[5] as u32, 8) as f32], - "BMS/Temps/Average", - "C", - ), - Data::new( - vec![pd::twos_comp(data[6] as u32, 8) as f32], - "BMS/Temps/Internal", - "C", - ), - Data::new(vec![data[7] as f32], "BMS/Cells/BurningStatus", ""), - ]; - result -} - -pub fn decode_shutdown_control(data: &[u8]) -> Vec { - if data.len() < 1 { - return vec![]; - } - let result = vec![Data::new(vec![data[0] as f32], "BMS/Shutdown/MPE", "")]; - result -} - -pub fn decode_cell_data(data: &[u8]) -> Vec { - if data.len() < 10 { - return vec![]; - } - let result = vec![ - Data::new( - vec![fd::cell_voltage( - pd::little_endian(&data[0..2] as &[u8], 8) as f32 - )], - "BMS/Cells/Volts/High/Value", - "V", - ), - Data::new( - vec![pd::half(data[2] as u8, 4) as f32], - "BMS/Cells/Volts/High/Chip", - "", - ), - Data::new( - vec![pd::half(data[3] as u8, 0) as f32], - "BMS/Cells/Volts/High/Cell", - "", - ), - Data::new( - vec![fd::cell_voltage( - pd::little_endian(&data[4..6] as &[u8], 8) as f32 - )], - "BMS/Cells/Volts/Low/Value", - "V", - ), - Data::new( - vec![pd::half(data[6] as u8, 4) as f32], - "BMS/Cells/Volts/Low/Chip", - "", - ), - Data::new( - vec![pd::half(data[7] as u8, 0) as f32], - "BMS/Cells/Volts/Low/Cell", - "", - ), - Data::new( - vec![fd::cell_voltage( - pd::little_endian(&data[8..10] as &[u8], 8) as f32, - )], - "BMS/Cells/Volts/Avg/Value", - "V", - ), - ]; - result -} - -pub fn decode_cell_temperatures(data: &[u8]) -> Vec { - if data.len() < 10 { - return vec![]; - } - let result = vec![ - Data::new( - vec![pd::twos_comp(pd::little_endian(&data[0..2] as &[u8], 8) as u32, 16) as f32], - "BMS/Cells/Temp/High/Value", - "C", - ), - Data::new( - vec![pd::half(data[2] as u8, 4) as f32], - "BMS/Cells/Temp/High/Cell", - "", - ), - Data::new( - vec![pd::half(data[3] as u8, 0) as f32], - "BMS/Cells/Temp/High/Chip", - "", - ), - Data::new( - vec![pd::twos_comp(pd::little_endian(&data[4..6] as &[u8], 8) as u32, 16) as f32], - "BMS/Cells/Temp/Low/Value", - "C", - ), - Data::new( - vec![pd::half(data[6] as u8, 4) as f32], - "BMS/Cells/Temp/Low/Cell", - "", - ), - Data::new( - vec![pd::half(data[7] as u8, 0) as f32], - "BMS/Cells/Temp/Low/Chip", - "", - ), - Data::new( - vec![pd::twos_comp(pd::little_endian(&data[8..10] as &[u8], 8) as u32, 16) as f32], - "BMS/Cells/Temp/Avg/Value", - "C", - ), - ]; - result -} - -pub fn decode_segment_temperatures(data: &[u8]) -> Vec { - if data.len() < 4 { - return vec![]; - } - let result = vec![ - Data::new( - vec![pd::twos_comp(data[0] as u32, 8) as f32], - "BMS/Segment/Temp/1", - "C", - ), - Data::new( - vec![pd::twos_comp(data[1] as u32, 8) as f32], - "BMS/Segment/Temp/2", - "C", - ), - Data::new( - vec![pd::twos_comp(data[2] as u32, 8) as f32], - "BMS/Segment/Temp/3", - "C", - ), - Data::new( - vec![pd::twos_comp(data[3] as u32, 8) as f32], - "BMS/Segment/Temp/4", - "C", - ), - ]; - result -} - -pub fn decode_mpu_acceleromter(data: &[u8]) -> Vec { - if data.len() < 6 { - return vec![]; - } - let result = vec![Data::new( - vec![ - fd::acceleration(pd::big_endian(&data[0..2] as &[u8], 8) as f32), - fd::acceleration(pd::big_endian(&data[2..4] as &[u8], 8) as f32), - fd::acceleration(pd::big_endian(&data[4..6] as &[u8], 8) as f32), - ], - "MPU/Accel", - "g", - )]; - result -} - -pub fn decode_mpu_status(data: &[u8]) -> Vec { - if data.len() < 4 { - return vec![]; - } - let result = vec![ - Data::new(vec![data[0] as f32], "MPU/State/Mode", ""), - Data::new( - vec![data[1] as f32], - "MPU/State/Torque_Limit_Percentage", - "", - ), - Data::new(vec![data[2] as f32], "MPU/State/Regen_Strength", ""), - Data::new(vec![data[3] as f32], "MPU/State/Traction_Control", ""), - ]; - result -} - -pub fn decode_wheel_state(data: &[u8]) -> Vec { - if data.len() < 2 { - return vec![]; - } - let result = vec![ - Data::new(vec![data[0] as f32], "WHEEL/Buttons/1", ""), - Data::new(vec![data[1] as f32], "WHEEL/Buttons/2", ""), - ]; - result -} diff --git a/src/main.rs b/src/main.rs index d776278..1322d74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ fn read_can(mut publisher: Box, can_interface: &str) { } }; loop { - let msg = match { socket.read_frame() } { + let msg = match socket.read_frame() { Ok(msg) => msg, Err(err) => { println!("Failed to read CAN frame: {}", err); diff --git a/src/master_mapping.rs b/src/master_mapping.rs deleted file mode 100644 index 8cb2016..0000000 --- a/src/master_mapping.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::decode_data::*; -use super::data::Data; - -pub struct MessageInfo { - pub decoder: fn(data: &[u8]) -> Vec, -} - -impl MessageInfo { - pub fn new(decoder: fn(data: &[u8]) -> Vec) -> Self { - Self { - decoder - } - } -} -pub fn get_message_info(id: &u32) -> MessageInfo { - match id { 0x80 => MessageInfo::new(decode_accumulator_status), - 0x81 => MessageInfo::new(decode_bms_status), - 0x82 => MessageInfo::new(decode_shutdown_control), - 0x83 => MessageInfo::new(decode_cell_data), - 0x84 => MessageInfo::new(decode_cell_temperatures), - 0x85 => MessageInfo::new(decode_segment_temperatures), - 0x500 => MessageInfo::new(decode_mpu_acceleromter), - 0x501 => MessageInfo::new(decode_mpu_status), - 0x680 => MessageInfo::new(decode_wheel_state), - _ => MessageInfo::new(decode_mock), - } -} \ No newline at end of file diff --git a/src/mqtt.rs b/src/mqtt.rs index 38d2266..b20be6f 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -113,6 +113,7 @@ impl MqttClient { .keep_alive_interval(Duration::from_secs(20)) .clean_session(false) .will_message(lastwilltestatment) + .automatic_reconnect(Duration::from_secs(1), Duration::from_secs(30)) .finalize(); if let Err(e) = client.connect(conn_opts) { println!("Unable to connect:\n\t{:?}", e);