Skip to content

Commit

Permalink
Revamp yaml structure slightly (#126)
Browse files Browse the repository at this point in the history
* add debug msgs

* fix typo

* fix typo

* add accel and brake pedals

* working rev of canfield inheritance

* switch to canpoint, add send and topic_append flags

* add documentation

* test signed

* further signed

* test little endianess

* improve endianness converter, wrap negative?

* fix from/to be

* why

* try hardcoding i32

* what

* try little endian hack

* try initial bitstream io

* introduce skip, add signed

* fix skip

* add little endian as read_as_to

* cleanup

* just use 32 bit

* Update README.md

* Update RustSynth.py

get rid of comment of removed field
  • Loading branch information
jr1221 authored May 21, 2024
1 parent 41cbd05 commit 22b868d
Show file tree
Hide file tree
Showing 12 changed files with 913 additions and 535 deletions.
48 changes: 17 additions & 31 deletions cangen/CANField.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
from __future__ import annotations
from ruamel.yaml import Optional
from typing import Optional
from dataclasses import dataclass

@dataclass
class CANField:
@dataclass
class CANPoint:
'''
Represents a field in a CAN message. Has an id, a name, a unit, a size,
and an optional Format and encodings. Also knows its own
index within its parent CANMsg, which is assigned at load from YAML.
Think 1 MQTT Topic per 1 CAN Field
Represents one set of bits in a CAN message.
Seperates CAN decoding logic from MQTT encoding information.
'''

name: str
unit: str
size: int # in bits
signed : bool = False
endianness : str = "little"
field_type: str = "*"*42
endianness : str = "big"
final_type: str = "f32"
format: Optional[str] = None

Expand All @@ -31,24 +24,17 @@ def get_size_bytes(self):

def get_size_bits(self):
return self.size

@dataclass
class DiscreteField(CANField):
"""
Represents a discrete field inside a CAN message in the sense that
it will make sense if it is published to MQTT and timestamped on its own
(think State of Charge or Temperature)
"""

field_type = "discrete"
class NetField:
'''
Represents a field in a CAN message. Contains a MQTT name and unit.
@dataclass
class CompositeField(CANField):
"""
Represents a composite field inside a CAN message in the sense that
it will only make sense with other data points in the same message and
can't be timestamped on its own (think XYZ acceleration or Roll, Pitch, Yaw)
"""
Think 1 MQTT Topic per 1 Net Field
'''

num_points: int = 0
field_type = "composite"
name: str
unit: str
points: list[CANPoint]
send: bool = True
topic_append: bool = False
5 changes: 2 additions & 3 deletions cangen/CANMsg.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations
from dataclasses import dataclass
from ruamel.yaml import Optional
from .CANField import CANField
from .CANField import NetField

@dataclass
class CANMsg:
Expand All @@ -10,7 +9,7 @@ class CANMsg:
'''
id: str # Hex value of CAN ID, i.e. `0x88`
desc: str # Brief name of CAN message, used for generating function names
fields: list[CANField] # List of CAN fields in the message (Optional to allow for only networkEncoding)
fields: list[NetField] # List of CAN fields in the message

def __setstate__(self, state):
self.__init__(**state)
47 changes: 8 additions & 39 deletions cangen/Format.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,18 @@ class Format:
repr: str = ""

@dataclass
class Temperature(Format):
repr: str = "temperature"
class Divide10(Format):
repr: str = "divide10"

@dataclass
class LowVoltage(Format):
repr: str = "low_voltage"
class Divide100(Format):
repr: str = "divide100"

@dataclass
class Torque(Format):
repr: str = "torque"
class Divide10000(Format):
repr: str = "divide10000"

@dataclass
class HighVoltage(Format):
repr: str = "high_voltage"
class Acceleration(Format):
repr: str = "acceleration"

@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"
46 changes: 44 additions & 2 deletions cangen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,54 @@
Here is a custom Python module built to generate embedded code for encoding and decoding CAN messages.

## Adding Messages

Messages should follow these rules:
1. Most significant bit should be the leftmost bit in each byte of data
2. Messages <=8 bits endianness should not be specified and will not do anything
3. Wherever possible, bit-wise decoding and byte-wise decoding should happen in seperate bytes to avoid confusion.
Ex. If there are 5 messages of size one (booleans), add a 3 bit filler before adding a 16 bit number
4. Message totals should be byte aligned, meaning total bit size of a message should be a power of 2
5. **Signed messages must be 8,16,or 32 bits!**
6. **Little endian messages must be 8,16, or 32 bits!**
7. Maximum size of a sent message (default, aka send=True), is 32 bits

Message guide:
1. Use previous examples for most things
2. Use an existing formatter if possible. To create a new one, add it to Format.py and RustSynth.py. Name it what it does if it could be reused by unrelated functins (ex. divide by 100 --> divide100) or if its very obscure use whats its used for (ex. multiply by 0029 in IMU datasheet --> acceleration).

Note: Single bit messages are memcpy-ed wrong by default, you may need to use `reverse_bits` in `c_utils.h`
Note: Please use big endian whenever possible, as that is the standard at which our MC, Charger Box, etc. expect it. Use `endian_swap` in `c_utils.h`

YAML info.
```
# all files start with this yaml
!Messages
msgs:
- !CANMsg
id: "0x80" # hexadecimal string of the CAN ID (extended CAN supported)
desc: "a quick description goes here"
fields: # list of MQTT messages being sent from this CANMsg
- !NetField
name: "The/Topic/Name" # the message topic name, going 3 levels of slashes is usally prefered, dont put trailing slashes
unit: "unit_here" # the unit of the data, ex. mph
send: true # (optional) whether this message should be sent over the network, default is true
topic_append: false # (optional) whether to append the topic name with the value of the first CANPoint in points
points: # list of CAN bits to be used for this message
- !CANPoint:
size: 8 # the integer size to be read, in bits
signed: false # (optional) whether the number is in signed twos complement form, default is false
endianness: "big" # (optional) the byte endianness of the bits being read, "big" or "false", "big" is default
format: "formatter_name" # (optional) the name of the formatter to use, default is no formatting, see above for formatter info
final_type: "f32" # (optional, not recommended) the final type of the data
```

### Directory Structure
```
|
|───CANField.py:
| |───DiscreteField # Single data point to stand alone
| └───CompositeField # Group of related data points that won't make sense alone
| |───NetField # a class which describes the topic and unit of one or more can points
| └──────CANPoint # a class which describes the decoding operations done to bits of a can message
|
|───CANMsg.py:
| └───CANMsg # Represents a full CAN message
Expand Down
Loading

0 comments on commit 22b868d

Please sign in to comment.