can_messenger 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +59 -1
- data/lib/can_messenger/adapter/socketcan.rb +21 -8
- data/lib/can_messenger/constants.rb +9 -0
- data/lib/can_messenger/dbc.rb +36 -20
- data/lib/can_messenger/messenger.rb +26 -3
- data/lib/can_messenger/version.rb +1 -1
- data/lib/can_messenger.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51737c6d0bee83617a3cb2c68b634f63a65ae5b1c711a4720d7a5a37a08a0612
|
|
4
|
+
data.tar.gz: aebcbd5b0904849633ce3326f60bf9fd28ca47f8c1982601d25161ec383f3a05
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6d8a36f95f8afcc859d25bd48b5c49b878b7fd1e2e51245ebe142e83dc3e05bc0e2572233da563ec470b0e0b52b3b59ee55c50c7165dc46b7d13302d2df465c
|
|
7
|
+
data.tar.gz: 88bca2b12015fa26df773080f8ad540ec30967b486e6a878afe4319c93b743eb758c6dcef9fe302b2af3ee5776e162129a49236368f31ca1343cc30a117096fc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [2.2.0] - 2026-03-11
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Make the Ruby Docker workflow install gems against the checked-in lockfile and self-heal stale bundle cache volumes.
|
|
8
|
+
- Promote the docs site stable snapshot to `2.2.0` and add global RubyGems navigation links.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Respect DBC-defined extended CAN IDs when sending messages and when decoding received extended frames.
|
|
13
|
+
- Reject invalid outbound CAN IDs instead of silently coercing them to a different wire value.
|
|
14
|
+
|
|
15
|
+
## [2.1.0] - 2026-02-23
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Promote the first stable docs snapshot version to `2.1.0`.
|
|
20
|
+
- Tighten DBC signal bounds validation and report exact out-of-range bit positions.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fix DBC big-endian (`@0`) bit mapping for multi-byte signals.
|
|
25
|
+
- Reject unsigned DBC signal values that exceed the field's bit-length range instead of silently wrapping.
|
|
26
|
+
- Align `Messenger` RBS signatures with runtime behavior (`adapter:` support and correct private method surface).
|
|
27
|
+
- Update DBC specs to cover the corrected big-endian behavior and unsigned overflow errors.
|
|
28
|
+
|
|
3
29
|
## [2.0.0] - 2026-02-02
|
|
4
30
|
|
|
5
31
|
### Changed
|
|
@@ -150,6 +176,9 @@
|
|
|
150
176
|
## [0.1.0] - 2024-11-10
|
|
151
177
|
|
|
152
178
|
- Initial release
|
|
179
|
+
[Unreleased]: https://github.com/fk1018/can_messenger/compare/v2.2.0...HEAD
|
|
180
|
+
[2.2.0]: https://github.com/fk1018/can_messenger/compare/v2.1.0...v2.2.0
|
|
181
|
+
[2.1.0]: https://github.com/fk1018/can_messenger/compare/v2.0.0...v2.1.0
|
|
153
182
|
[2.0.0]: https://github.com/fk1018/can_messenger/compare/v1.3.0...v2.0.0
|
|
154
183
|
[1.3.0]: https://github.com/fk1018/can_messenger/compare/v1.2.1...v1.3.0
|
|
155
184
|
[1.2.1]: https://github.com/fk1018/can_messenger/compare/v1.2.0...v1.2.1
|
data/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
## Requirements
|
|
14
14
|
|
|
15
15
|
- Ruby 4.0.1 or higher.
|
|
16
|
+
- Docker (optional, for containerized development without installing Ruby locally).
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
@@ -34,6 +35,8 @@ Or install it yourself with:
|
|
|
34
35
|
gem install can_messenger
|
|
35
36
|
```
|
|
36
37
|
|
|
38
|
+
RubyGems page: [https://rubygems.org/gems/can_messenger](https://rubygems.org/gems/can_messenger)
|
|
39
|
+
|
|
37
40
|
## Usage
|
|
38
41
|
|
|
39
42
|
### Initializing the Messenger
|
|
@@ -127,6 +130,8 @@ messenger.start_listening(dbc: dbc) do |msg|
|
|
|
127
130
|
end
|
|
128
131
|
```
|
|
129
132
|
|
|
133
|
+
If the DBC message definition uses an extended CAN ID, `send_dbc_message` automatically sends it as an extended frame and `start_listening(dbc: ...)` decodes received extended frames back through the same DBC definition.
|
|
134
|
+
|
|
130
135
|
### Stopping the Listener
|
|
131
136
|
|
|
132
137
|
To stop listening, use:
|
|
@@ -206,7 +211,60 @@ Before using `can_messenger`, please note the following:
|
|
|
206
211
|
|
|
207
212
|
## Development
|
|
208
213
|
|
|
209
|
-
|
|
214
|
+
### Docker-first workflow
|
|
215
|
+
|
|
216
|
+
Build the development image:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
docker compose build app
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The Ruby services automatically run `bundle check || bundle install` inside the container, so an existing bundle cache volume stays usable after dependency changes.
|
|
223
|
+
|
|
224
|
+
Run RuboCop:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
docker compose run --rm lint
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Run the test suite:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
docker compose run --rm test
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Build the gem:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
docker compose run --rm build
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Install docs dependencies (Docusaurus):
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
docker compose run --rm docs npm ci
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Build the docs site:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
docker compose run --rm docs npm run build
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Preview docs locally:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
docker compose run --rm --service-ports docs npm run start
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Local Ruby workflow
|
|
261
|
+
|
|
262
|
+
If you already have Ruby installed locally, you can still use:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
bin/setup
|
|
266
|
+
bundle exec rake test:rspec
|
|
267
|
+
```
|
|
210
268
|
|
|
211
269
|
## Contributing
|
|
212
270
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "socket"
|
|
4
4
|
require_relative "base"
|
|
5
|
+
require_relative "../constants"
|
|
5
6
|
|
|
6
7
|
module CanMessenger
|
|
7
8
|
module Adapter
|
|
@@ -11,10 +12,11 @@ module CanMessenger
|
|
|
11
12
|
CANFD_FRAME_SIZE = 72
|
|
12
13
|
MIN_FRAME_SIZE = 8
|
|
13
14
|
MAX_FD_DATA = 64
|
|
15
|
+
MAX_STANDARD_ID = 0x7FF
|
|
14
16
|
TIMEOUT = [1, 0].pack("l_2")
|
|
15
17
|
|
|
16
18
|
# Creates and configures a CAN socket bound to the interface.
|
|
17
|
-
def open_socket(can_fd:
|
|
19
|
+
def open_socket(can_fd: nil)
|
|
18
20
|
socket = Socket.open(Socket::PF_CAN, Socket::SOCK_RAW, Socket::CAN_RAW)
|
|
19
21
|
configure_socket(socket, can_fd: can_fd)
|
|
20
22
|
socket
|
|
@@ -25,17 +27,18 @@ module CanMessenger
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
# Builds a raw CAN or CAN FD frame for SocketCAN.
|
|
28
|
-
def build_can_frame(id:, data:, extended_id: false, can_fd:
|
|
30
|
+
def build_can_frame(id:, data:, extended_id: false, can_fd: nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
29
31
|
if can_fd
|
|
30
32
|
raise ArgumentError, "CAN FD data cannot exceed #{MAX_FD_DATA} bytes" if data.size > MAX_FD_DATA
|
|
31
33
|
elsif data.size > 8
|
|
32
34
|
raise ArgumentError, "CAN data cannot exceed 8 bytes"
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
validate_can_id!(id, extended_id: extended_id)
|
|
38
|
+
|
|
39
|
+
can_id = id
|
|
37
40
|
# Set bit 31 for extended frames
|
|
38
|
-
can_id |=
|
|
41
|
+
can_id |= Constants::EXTENDED_ID_FLAG if extended_id
|
|
39
42
|
|
|
40
43
|
# Pack the ID based on endianness
|
|
41
44
|
id_bytes = endianness == :big ? [can_id].pack("L>") : [can_id].pack("V")
|
|
@@ -52,7 +55,7 @@ module CanMessenger
|
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
# Reads a frame from the socket and parses it into a hash.
|
|
55
|
-
def receive_message(socket:, can_fd:
|
|
58
|
+
def receive_message(socket:, can_fd: nil)
|
|
56
59
|
frame_size = can_fd ? CANFD_FRAME_SIZE : FRAME_SIZE
|
|
57
60
|
frame = socket.recv(frame_size)
|
|
58
61
|
return nil if frame.nil? || frame.size < MIN_FRAME_SIZE
|
|
@@ -72,8 +75,8 @@ module CanMessenger
|
|
|
72
75
|
use_fd = can_fd.nil? ? frame.size >= CANFD_FRAME_SIZE : can_fd
|
|
73
76
|
|
|
74
77
|
raw_id = unpack_frame_id(frame: frame)
|
|
75
|
-
extended = raw_id.anybits?(
|
|
76
|
-
id = raw_id &
|
|
78
|
+
extended = raw_id.anybits?(Constants::EXTENDED_ID_FLAG)
|
|
79
|
+
id = raw_id & Constants::MAX_EXTENDED_ID
|
|
77
80
|
|
|
78
81
|
data_length = if use_fd
|
|
79
82
|
frame[4].ord
|
|
@@ -120,6 +123,16 @@ module CanMessenger
|
|
|
120
123
|
rescue StandardError
|
|
121
124
|
# Ignore close errors so we can report the original failure.
|
|
122
125
|
end
|
|
126
|
+
|
|
127
|
+
def validate_can_id!(id, extended_id:)
|
|
128
|
+
raise ArgumentError, "id must be an Integer" unless id.is_a?(Integer)
|
|
129
|
+
raise ArgumentError, "CAN id cannot be negative" if id.negative?
|
|
130
|
+
|
|
131
|
+
max_id = extended_id ? Constants::MAX_EXTENDED_ID : MAX_STANDARD_ID
|
|
132
|
+
return if id <= max_id
|
|
133
|
+
|
|
134
|
+
raise ArgumentError, "#{extended_id ? "Extended" : "Standard"} CAN id cannot exceed 0x#{max_id.to_s(16).upcase}"
|
|
135
|
+
end
|
|
123
136
|
end
|
|
124
137
|
end
|
|
125
138
|
end
|
data/lib/can_messenger/dbc.rb
CHANGED
|
@@ -283,15 +283,15 @@ module CanMessenger
|
|
|
283
283
|
# @return [void]
|
|
284
284
|
# @raise [ArgumentError] If signal bits exceed message boundaries or start_bit is negative
|
|
285
285
|
def validate_signal_bounds(message_size_bytes)
|
|
286
|
-
max_bit = start_bit + length - 1
|
|
287
|
-
max_allowed_bit = (message_size_bytes * 8) - 1
|
|
288
|
-
|
|
289
286
|
raise ArgumentError, "Signal #{name}: start_bit (#{start_bit}) cannot be negative" if start_bit.negative?
|
|
287
|
+
raise ArgumentError, "Signal #{name}: length (#{length}) must be positive" if length <= 0
|
|
290
288
|
|
|
291
|
-
|
|
289
|
+
max_allowed_bit = max_allowed_bit_for(message_size_bytes)
|
|
290
|
+
invalid_position = invalid_bit_position(max_allowed_bit)
|
|
291
|
+
return unless invalid_position
|
|
292
292
|
|
|
293
293
|
raise ArgumentError,
|
|
294
|
-
"Signal #{name}:
|
|
294
|
+
"Signal #{name}: bit position #{invalid_position} exceeds message size " \
|
|
295
295
|
"(#{message_size_bytes} bytes = #{max_allowed_bit + 1} bits)"
|
|
296
296
|
end
|
|
297
297
|
|
|
@@ -328,9 +328,15 @@ module CanMessenger
|
|
|
328
328
|
# @return [void]
|
|
329
329
|
# @raise [ArgumentError] If an unsigned value is negative
|
|
330
330
|
def validate_unsigned_value(raw)
|
|
331
|
-
return unless sign == :unsigned
|
|
331
|
+
return unless sign == :unsigned
|
|
332
|
+
|
|
333
|
+
raise ArgumentError, "Unsigned value cannot be negative: #{raw}" if raw.negative?
|
|
332
334
|
|
|
333
|
-
|
|
335
|
+
max_val = (1 << length) - 1
|
|
336
|
+
return if raw <= max_val
|
|
337
|
+
|
|
338
|
+
raise ArgumentError,
|
|
339
|
+
"Unsigned value #{raw} out of range [0..#{max_val}] for #{length}-bit field"
|
|
334
340
|
end
|
|
335
341
|
|
|
336
342
|
# Validates signed values to ensure they fit in the signal's bit range.
|
|
@@ -396,19 +402,12 @@ module CanMessenger
|
|
|
396
402
|
if endianness == :little
|
|
397
403
|
start_bit + bit_offset
|
|
398
404
|
else
|
|
399
|
-
#
|
|
400
|
-
#
|
|
401
|
-
#
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
# position (aligned to the nearest multiple of 8). Adding (7 - (start_bit % 8))
|
|
406
|
-
# adjusts this base position to point to the MSB of the starting byte.
|
|
407
|
-
#
|
|
408
|
-
# Finally, we subtract the bit offset to account for the signal's length and
|
|
409
|
-
# position within the message.
|
|
410
|
-
base = ((start_bit / 8) * 8) + (7 - (start_bit % 8))
|
|
411
|
-
base - bit_offset
|
|
405
|
+
# DBC big-endian (`@0`) start_bit is in sawtooth numbering and points at the
|
|
406
|
+
# signal MSB. Convert to sequential network numbering, move toward the LSB,
|
|
407
|
+
# then convert back to the little-endian bit index used by byte writes.
|
|
408
|
+
network_start = sawtooth_to_network_bitnum(start_bit)
|
|
409
|
+
network_bit = network_start + (length - 1 - bit_offset)
|
|
410
|
+
sawtooth_to_network_bitnum(network_bit)
|
|
412
411
|
end
|
|
413
412
|
end
|
|
414
413
|
|
|
@@ -511,5 +510,22 @@ module CanMessenger
|
|
|
511
510
|
msb_set = (value >> (length - 1)).allbits?(1)
|
|
512
511
|
msb_set ? value - (1 << length) : value
|
|
513
512
|
end
|
|
513
|
+
|
|
514
|
+
def sawtooth_to_network_bitnum(bitnum)
|
|
515
|
+
(8 * (bitnum / 8)) + (7 - (bitnum % 8))
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def invalid_bit_position(max_allowed_bit)
|
|
519
|
+
length.times do |i|
|
|
520
|
+
bit_pos = calculate_bit_position(i)
|
|
521
|
+
return bit_pos if bit_pos.negative? || bit_pos > max_allowed_bit
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
nil
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def max_allowed_bit_for(message_size_bytes)
|
|
528
|
+
(message_size_bytes * 8) - 1
|
|
529
|
+
end
|
|
514
530
|
end
|
|
515
531
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "logger"
|
|
4
4
|
require_relative "adapter/socketcan"
|
|
5
|
+
require_relative "constants"
|
|
5
6
|
|
|
6
7
|
module CanMessenger
|
|
7
8
|
# Messenger
|
|
@@ -64,8 +65,8 @@ module CanMessenger
|
|
|
64
65
|
def send_dbc_message(message_name:, signals:, dbc: @dbc, extended_id: false, can_fd: nil)
|
|
65
66
|
raise ArgumentError, "dbc is required" if dbc.nil?
|
|
66
67
|
|
|
67
|
-
encoded = dbc.encode_can(message_name, signals)
|
|
68
|
-
send_can_message(id: encoded[:id], data: encoded[:data], extended_id: extended_id, can_fd: can_fd)
|
|
68
|
+
encoded = normalized_dbc_message(dbc.encode_can(message_name, signals), extended_id: extended_id)
|
|
69
|
+
send_can_message(id: encoded[:id], data: encoded[:data], extended_id: encoded[:extended_id], can_fd: can_fd)
|
|
69
70
|
rescue ArgumentError
|
|
70
71
|
raise
|
|
71
72
|
rescue StandardError => e
|
|
@@ -134,7 +135,7 @@ module CanMessenger
|
|
|
134
135
|
return if filter && !matches_filter?(message_id: message[:id], filter: filter)
|
|
135
136
|
|
|
136
137
|
if dbc
|
|
137
|
-
decoded = dbc.decode_can(message
|
|
138
|
+
decoded = dbc.decode_can(dbc_decode_id(message), message[:data])
|
|
138
139
|
message[:decoded] = decoded if decoded
|
|
139
140
|
end
|
|
140
141
|
|
|
@@ -156,5 +157,27 @@ module CanMessenger
|
|
|
156
157
|
else true
|
|
157
158
|
end
|
|
158
159
|
end
|
|
160
|
+
|
|
161
|
+
def normalize_can_id(id)
|
|
162
|
+
id & Constants::CAN_ID_MASK
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def dbc_extended_id?(id)
|
|
166
|
+
id.anybits?(Constants::EXTENDED_ID_FLAG)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def normalized_dbc_message(encoded, extended_id:)
|
|
170
|
+
{
|
|
171
|
+
id: normalize_can_id(encoded[:id]),
|
|
172
|
+
data: encoded[:data],
|
|
173
|
+
extended_id: extended_id || dbc_extended_id?(encoded[:id])
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def dbc_decode_id(message)
|
|
178
|
+
return message[:id] unless message[:extended]
|
|
179
|
+
|
|
180
|
+
message[:id] | Constants::EXTENDED_ID_FLAG
|
|
181
|
+
end
|
|
159
182
|
end
|
|
160
183
|
end
|
data/lib/can_messenger.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: can_messenger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fk1018
|
|
@@ -23,15 +23,16 @@ files:
|
|
|
23
23
|
- lib/can_messenger.rb
|
|
24
24
|
- lib/can_messenger/adapter/base.rb
|
|
25
25
|
- lib/can_messenger/adapter/socketcan.rb
|
|
26
|
+
- lib/can_messenger/constants.rb
|
|
26
27
|
- lib/can_messenger/dbc.rb
|
|
27
28
|
- lib/can_messenger/messenger.rb
|
|
28
29
|
- lib/can_messenger/version.rb
|
|
29
|
-
homepage: https://github.
|
|
30
|
+
homepage: https://can-messenger.github.io/
|
|
30
31
|
licenses:
|
|
31
32
|
- MIT
|
|
32
33
|
metadata:
|
|
33
34
|
allowed_push_host: https://rubygems.org
|
|
34
|
-
homepage_uri: https://github.
|
|
35
|
+
homepage_uri: https://can-messenger.github.io/
|
|
35
36
|
source_code_uri: https://github.com/fk1018/can_messenger
|
|
36
37
|
changelog_uri: https://github.com/fk1018/can_messenger/blob/main/CHANGELOG.md
|
|
37
38
|
rubygems_mfa_required: 'true'
|