Skip to content

Commit

Permalink
Translator (#26)
Browse files Browse the repository at this point in the history
* cleanup from old branch

* added little structs

* added big structs

* added overarching objects

* reworked mapping to use tags

* added new PoC

* updated module paths to make mypy slightly happier
  • Loading branch information
qubitter authored Jan 10, 2024
1 parent 8746a47 commit 20ae544
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 10 deletions.
20 changes: 14 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
build/
dist/
__pycache__/
logs/
# editor things
.idea/
output.csv
.zed/
.vscode/

# Added by cargo
# misc things
.DS_Store
.gitignore

# python things
pyrightconfig.json
__pycache__/

# Added by cargo (rust things)
/target
build/
dist/
logs/
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

42 changes: 42 additions & 0 deletions oxy/RustSynth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from structs.CANField import CANField
from structs.CANMsg import CANMsg
from structs.Decoding import Decoding

from typing import Optional

class RustSynth:
'''
A class to synthesize Rust from a given CANMsg spec.
'''

inst_hashmap: str = " let mut result = HashMap::new();"
closing: str = " result\n}"

def synthesize(self, msg: CANMsg) -> str:
signature: str = self.signature(msg.desc)
generated_lines: list[str] = []
for field in msg.fields:
generated_lines.append(self.finalize_line(field.id, f"({self.parse_decoders(field)}){self.correcting_factor(field)}"))
total_list: list[str] = [signature, self.inst_hashmap] + generated_lines + [self.closing]
return "\n".join(total_list)

def signature(self, to_decode: str) -> str:
return f"pub fn decode_{to_decode.replace(' ', '_')}(data: &[u8]) -> HashMap<u8, f32> {{"

def finalize_line(self, id: int, val: str) -> str:
return f" result.insert({id}, {val});"

def parse_decoders(self, field: CANField) -> str:
if isinstance(field.decodings, type(None)):
return f"data[{field.index}] as f32"
else:
base: str = f"&data[{field.index}..{field.index + field.size}]"
for decoder in field.decodings:
base = f"pd::{decoder.repr}({base}, {decoder.bits}) as {decoder.final_type}"
return base

def correcting_factor(self, field:CANField) -> str:
cf: str = ""
if field.correcting_factor:
cf = f" {field.correcting_factor.op} {field.correcting_factor.const}"
return cf
25 changes: 25 additions & 0 deletions oxy/YAMLParser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from io import TextIOWrapper
from ruamel.yaml import YAML, Any

from structs.CANMsg import CANMsg
from structs.CANField import CANField
from structs.CorrectingFactor import CorrectingFactor
import structs.Decoding

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(CANMsg)
self.yaml.register_class(CANField)
self.yaml.register_class(CorrectingFactor)
for decoding in structs.Decoding.Decoding.__subclasses__():
self.yaml.register_class(decoding)


def parse(self, file: Any) -> CANMsg:
return self.yaml.load(file)
54 changes: 54 additions & 0 deletions oxy/mapping.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
!CANMsg
id: 1
desc: "accumulator status"
fields:
- !CANField
id: 1
name: Pack Inst Voltage
units: "V"
size: 2
decodings:
- !BigEndian
bits: 8
final_type: "f32"
correcting_factor:
!CorrectingFactor
const: 10.0
op: "/"
- !CANField
id: 2
name: "Pack Current"
units: "A"
size: 2
decodings:
- !BigEndian
bits: 8
final_type: "u32"
- !TwosComplement
bits: 16
final_type: "f32"
correcting_factor:
!CorrectingFactor
const: 10.0
op: "/"
- !CANField
id: 3
name: "Pack Amp-hours"
units: "Ah"
size: 2
decodings:
- !BigEndian
bits: 8
final_type: "f32"
- !CANField
id: 4
name: "Pack SOC"
units: "%"
size: 1
final_type: "f32"
- !CANField
id: 5
name: "Pack Health"
units: "%"
size: 1
final_type: "f32"
48 changes: 48 additions & 0 deletions oxy/poc_translator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from ruamel.yaml import YAML, Any
from functools import reduce


yaml: YAML = YAML(typ="safe")

out_string: str = ""
data: dict[str, Any] = yaml.load(open("mapping.yaml"))
print(data)
print(type(data))

function_name: str = "decode" + "_" + "_".join(data['string'].split(" "))
args: str = "(data: &[u8])"
returnVal: str= " -> HashMap<u8, f32>"

signature: str = "pub fn " + function_name + args + returnVal + " {"
instantiate_hash_map: str = " let mut result = HashMap::new();"
conclusion: str = " result\n}"

decodings: list[str] = []
accumulated_size: int = 0
for field in data["fields"]: # result.insert(1, (pd::big_endian(&data[0..2], 8) as f32) / 10.0);
field: dict
decoded: str
id = field["field_id"]
if field["size"] > 1: # we need to do some decoding, then
to_decode: str = f"&data[{accumulated_size}..{accumulated_size+field['size']}]"
_cf: str = field.get("correcting_factor", "")
correcting_factor: str = f"{' ' + ('/' if '/' in _cf else '*') + ' ' if 'correcting_factor' in field.keys() else ''}{_cf.split('/')[-1]}"
for decodingsetup in field["decoding"]:
decodingsetup: dict[str, dict[str, str]] = {k: reduce(lambda x,y: x|y, v, {}) for k,v in decodingsetup.items()}
for decoder, params in decodingsetup.items():
match decoder:
case "big_endian":
to_decode = f"pd::big_endian({to_decode}, {params['bits']}) as {params['final_type']}"
case "twos_complement":
to_decode = f"pd::twos_comp({to_decode}, {params['bits']}) as {params['final_type']}"
decoded = f"{id}, {to_decode}{correcting_factor}"
else: # no decoding required!
decoded = f"{id}, data[{accumulated_size}] as {field['final_type']}"

decodings.append(decoded)
accumulated_size += field["size"]

formatted_decodings = [f" result.insert({i});" for i in decodings]

finals: list[str] = [signature, instantiate_hash_map] + formatted_decodings + [conclusion]
print("\n".join(finals))
20 changes: 20 additions & 0 deletions oxy/structs/CANField.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from __future__ import annotations
from .CorrectingFactor import CorrectingFactor
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, units, a size,
and an optional CorrectingFactor and Decodings. Also knows its own
index within its parent CANMsg, which is assigned at load from YAML.
'''
id: int
name: str
units: str
size: int
index: int = -1
correcting_factor: Optional[CorrectingFactor] = None
decodings: Optional[list[Decoding]] = None
24 changes: 24 additions & 0 deletions oxy/structs/CANmsg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations
from ruamel.yaml import Optional, MappingNode
from structs.CorrectingFactor import CorrectingFactor
from .CANField import CANField
from dataclasses import dataclass

@dataclass
class CANMsg:
'''
Represents a CAN message. Has an id, a description, and a number of individual fields.
'''
id: int
desc: str
fields: list[CANField]

def __post_init__(self) -> None:
idx: int = 0
for field in self.fields:
field.index = idx
idx += field.size


def __setstate__(self, state):
self.__init__(**state)
9 changes: 9 additions & 0 deletions oxy/structs/CorrectingFactor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from dataclasses import dataclass

@dataclass
class CorrectingFactor:
'''
Represents a correcting factor to be applied to data after decoding.
'''
const: float
op: str
23 changes: 23 additions & 0 deletions oxy/structs/Decoding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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
final_type: str
repr: str = "*"*42

@dataclass
class BigEndian(Decoding):
repr: str = "big_endian"

@dataclass
class LittleEndian(Decoding):
repr: str = "little_endian"

@dataclass
class TwosComplement(Decoding):
repr: str = "twos_comp"
4 changes: 4 additions & 0 deletions oxy/typedpoc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from YAMLParser import YAMLParser
from RustSynth import RustSynth

print(RustSynth().synthesize(YAMLParser().parse(open("mapping.yaml", "r"))))
2 changes: 1 addition & 1 deletion src/master_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ impl DataInfo {
}
}

// maps from data id to DataInfo containing the name of the data and its units
pub fn get_data_info(id: u8) -> DataInfo {
match id {
0 => return DataInfo::new("Mock Data".to_string(), "".to_string()),
Expand Down Expand Up @@ -242,6 +243,5 @@ pub fn get_data_info(id: u8) -> DataInfo {
145 => return DataInfo::new("Precharge State".to_string(), "".to_string()),
146 => return DataInfo::new("BMS Prefault Status".to_string(), "".to_string()),
_ => return DataInfo::new("".to_string(), "".to_string()),

}
}

0 comments on commit 20ae544

Please sign in to comment.