Class: CanMessenger::DBC
- Inherits:
-
Object
- Object
- CanMessenger::DBC
- Defined in:
- lib/can_messenger/dbc.rb
Overview
DBC (Database CAN) Parser and Encoder/Decoder
This class provides functionality to parse DBC files and encode/decode CAN messages according to the signal definitions. DBC files are a standard way to describe CAN network communication.
Constant Summary collapse
- IGNORED_LINE_PREFIXES =
%w[ BO_TX_BU_ VERSION NS_ BS_ BU_ CM_ VAL_ BA_ BA_DEF_ BA_DEF_DEF_ SIG_VALTYPE_ ].freeze
- MESSAGE_LINE_PATTERN =
/^BO_\s+(\d+)\s+(\w+)\s*:\s*(\d+)\s+\S.*$/- SIGNAL_LINE_PATTERN =
/^SG_\s+(\w+)\s*:\s*(\d+)\|(\d+)@(\d)([+-])\s*\(([^,]+),([^)]+)\)/
Instance Attribute Summary collapse
-
#messages ⇒ Object
readonly
Returns the value of attribute messages.
Class Method Summary collapse
-
.load(path) ⇒ DBC
Loads a DBC file from disk and parses its contents.
Instance Method Summary collapse
-
#decode_can(id, data) ⇒ Hash?
Decodes a CAN message frame into signal values.
-
#encode_can(name, values) ⇒ Hash
Encodes signal values into a CAN message frame.
-
#initialize(content = "") ⇒ DBC
constructor
Initializes a new DBC instance.
-
#parse(content) ⇒ void
Parses DBC content and populates the messages hash.
-
#parse_message_line(line) ⇒ Message?
Parses a message definition line from DBC content.
-
#parse_signal_line(line, _current) ⇒ Signal?
Parses a signal definition line from DBC content.
Constructor Details
#initialize(content = "") ⇒ DBC
Initializes a new DBC instance.
53 54 55 56 |
# File 'lib/can_messenger/dbc.rb', line 53 def initialize(content = "") @messages = {} parse(content) unless content.empty? end |
Instance Attribute Details
#messages ⇒ Object (readonly)
Returns the value of attribute messages.
38 39 40 |
# File 'lib/can_messenger/dbc.rb', line 38 def @messages end |
Class Method Details
.load(path) ⇒ DBC
Loads a DBC file from disk and parses its contents.
46 47 48 |
# File 'lib/can_messenger/dbc.rb', line 46 def self.load(path) new(File.read(path)) end |
Instance Method Details
#decode_can(id, data) ⇒ Hash?
Decodes a CAN message frame into signal values.
Takes a CAN ID and data bytes, finds the matching message definition, and decodes the data into individual signal values according to the DBC.
185 186 187 188 189 190 |
# File 'lib/can_messenger/dbc.rb', line 185 def decode_can(id, data) msg = @messages.values.find { |m| m.id == id } return nil unless msg { name: msg.name, signals: msg.decode(data) } end |
#encode_can(name, values) ⇒ Hash
Encodes signal values into a CAN message frame.
Takes a message name and a hash of signal values, then encodes them into the appropriate byte array according to the DBC signal definitions.
166 167 168 169 170 171 |
# File 'lib/can_messenger/dbc.rb', line 166 def encode_can(name, values) msg = @messages[name] raise ArgumentError, "Unknown message #{name}" unless msg { id: msg.id, data: msg.encode(values) } end |
#parse(content) ⇒ void
This method returns an undefined value.
Parses DBC content and populates the messages hash.
This method processes each line of the DBC content, identifying message definitions (BO_) and signal definitions (SG_). It builds a complete message structure with all associated signals.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/can_messenger/dbc.rb', line 66 def parse(content) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity current = nil in_namespace_section = false # The parser is intentionally strict so malformed DBC files fail early with line context. # We still ignore a small allowlist of standard metadata directives that do not affect encode/decode. content.each_line.with_index(1) do |line, line_number| raw_line = line.rstrip stripped_line = raw_line.strip if in_namespace_section next if raw_line.match?(/^\s+/) in_namespace_section = false end next if stripped_line.empty? if ignored_line?(stripped_line) in_namespace_section = stripped_line.start_with?("NS_") next end if stripped_line.start_with?("BO_") msg = (stripped_line) raise_parse_error("Invalid message definition", line_number, stripped_line) unless msg current = msg @messages[msg.name] = msg elsif stripped_line.start_with?("SG_") raise_parse_error("Signal definition without a current message", line_number, stripped_line) unless current sig = parse_signal_line(stripped_line, current) raise_parse_error("Invalid signal definition", line_number, stripped_line) unless sig current.signals << sig else raise_parse_error("Unsupported or malformed DBC line", line_number, stripped_line) end end end |
#parse_message_line(line) ⇒ Message?
Parses a message definition line from DBC content.
Message lines follow the format: BO_ <ID> <Name>: <DLC> <Node>
114 115 116 117 118 119 120 121 |
# File 'lib/can_messenger/dbc.rb', line 114 def (line) return unless (m = line.match(MESSAGE_LINE_PATTERN)) id = m[1].to_i name = m[2] dlc = m[3].to_i Message.new(id, name, dlc) end |
#parse_signal_line(line, _current) ⇒ Signal?
Parses a signal definition line from DBC content.
Signal lines follow the format: SG_ <SignalName> : <StartBit>|<Length>@<Endianness><Sign> (<Factor>,<Offset>) [<Min>|<Max>] “<Unit>” <Receivers>
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/can_messenger/dbc.rb', line 131 def parse_signal_line(line, _current) # rubocop:disable Metrics/MethodLength return unless (m = line.match(SIGNAL_LINE_PATTERN)) sig_name = m[1] start_bit = m[2].to_i length = m[3].to_i endian = m[4] == "1" ? :little : :big sign = m[5] == "-" ? :signed : :unsigned factor = m[6].to_f offset = m[7].to_f Signal.new( sig_name, start_bit: start_bit, length: length, endianness: endian, sign: sign, factor: factor, offset: offset ) end |