can_messenger 1.2.1 → 1.3.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/README.md +15 -2
- data/lib/can_messenger/messenger.rb +51 -27
- data/lib/can_messenger/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a68f4a339bebc034ffe78e990086ff91d28164962c2877e5d88edb095cbca90
|
4
|
+
data.tar.gz: 7a96c22ccf82cae952123d733805c2efe31dd482ba9bf5d5a66d29cb14572822
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8302565cc5e889a290a446167bdec8eedd29ba077c63c003f544138c078a94498b258f465387b3c92f8b9b9ffc18ed65810fa70c5dc97e3ba977e62bbc394c4
|
7
|
+
data.tar.gz: a606c71e8606bbccd98e1917bc4c2205037156e3994f152f6b05c3a52fc1037c21f8a38a91814abddb6f4a9bf5a0eb8682a2acaf93e3da0cdb818a2d8d03131f
|
data/README.md
CHANGED
@@ -9,6 +9,10 @@
|
|
9
9
|
|
10
10
|
`can_messenger` is a Ruby gem that provides an interface for communicating over the CAN bus, allowing users to send and receive CAN messages `via raw SocketCAN sockets`. This gem is designed for developers who need an easy way to interact with CAN-enabled devices on Linux.
|
11
11
|
|
12
|
+
## Requirements
|
13
|
+
|
14
|
+
- Ruby 3.0 or higher.
|
15
|
+
|
12
16
|
## Installation
|
13
17
|
|
14
18
|
To install `can_messenger`, add it to your application's Gemfile:
|
@@ -57,6 +61,15 @@ If you need to send an extended CAN frame (29-bit ID), set extended_id: true. Th
|
|
57
61
|
messenger.send_can_message(id: 0x123456, data: [0x01, 0x02, 0x03], extended_id: true)
|
58
62
|
```
|
59
63
|
|
64
|
+
If you need to work with **CAN FD** frames (up to 64 data bytes), enable the mode per call or when initializing the messenger:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
messenger_fd = CanMessenger::Messenger.new(interface_name: 'can0', can_fd: true)
|
68
|
+
messenger_fd.send_can_message(id: 0x123, data: Array.new(12, 0xFF))
|
69
|
+
# Or on demand
|
70
|
+
messenger.send_can_message(id: 0x123, data: Array.new(12, 0xFF), can_fd: true)
|
71
|
+
```
|
72
|
+
|
60
73
|
### Receiving CAN Messages
|
61
74
|
|
62
75
|
To listen for incoming messages, set up a listener:
|
@@ -147,11 +160,11 @@ Before using `can_messenger`, please note the following:
|
|
147
160
|
|
148
161
|
- **CAN Frame Format Assumptions:**
|
149
162
|
- By default, the gem uses **big-endian** packing for CAN IDs. If you integrate with a system using little-endian, you may need to adjust or specify an endianness in the code.
|
150
|
-
- The gem expects a standard CAN frame layout (16 bytes total, with the first 4 for the ID, followed by 1 byte for DLC, 3 bytes of padding, and up to 8 bytes of data).
|
163
|
+
- The gem expects a standard CAN frame layout (16 bytes total, with the first 4 for the ID, followed by 1 byte for DLC, 3 bytes of padding, and up to 8 bytes of data). **CAN FD** frames (up to 64 bytes) are supported when enabled.
|
151
164
|
|
152
165
|
## Features
|
153
166
|
|
154
|
-
- **Send CAN Messages**: Send CAN messages (up to 8 data bytes).
|
167
|
+
- **Send CAN Messages**: Send CAN messages (up to 8 data bytes, or 64 bytes with CAN FD enabled).
|
155
168
|
- **Receive CAN Messages**: Continuously listen for messages on a CAN interface.
|
156
169
|
- **Filtering**: Optional ID filters for incoming messages (single ID, range, or array).
|
157
170
|
- **Logging**: Logs errors and events for debugging/troubleshooting.
|
@@ -17,7 +17,9 @@ module CanMessenger
|
|
17
17
|
# end
|
18
18
|
class Messenger # rubocop:disable Metrics/ClassLength
|
19
19
|
FRAME_SIZE = 16
|
20
|
+
CANFD_FRAME_SIZE = 72
|
20
21
|
MIN_FRAME_SIZE = 8
|
22
|
+
MAX_FD_DATA = 64
|
21
23
|
TIMEOUT = [1, 0].pack("l_2")
|
22
24
|
|
23
25
|
# Initializes a new Messenger instance.
|
@@ -26,11 +28,12 @@ module CanMessenger
|
|
26
28
|
# @param [Logger, nil] logger Optional logger for error handling and debug information.
|
27
29
|
# @param [Symbol] endianness The endianness of the CAN ID (default: :big) can be :big or :little.
|
28
30
|
# @return [void]
|
29
|
-
def initialize(interface_name:, logger: nil, endianness: :big)
|
31
|
+
def initialize(interface_name:, logger: nil, endianness: :big, can_fd: false)
|
30
32
|
@interface_name = interface_name
|
31
33
|
@logger = logger || Logger.new($stdout)
|
32
34
|
@listening = true # Control flag for listening loop
|
33
35
|
@endianness = endianness # :big or :little
|
36
|
+
@can_fd = can_fd
|
34
37
|
end
|
35
38
|
|
36
39
|
# Sends a CAN message by writing directly to a raw CAN socket
|
@@ -38,9 +41,11 @@ module CanMessenger
|
|
38
41
|
# @param [Integer] id The CAN ID of the message (up to 29 bits for extended IDs).
|
39
42
|
# @param [Array<Integer>] data The data bytes of the CAN message (0 to 8 bytes).
|
40
43
|
# @return [void]
|
41
|
-
def send_can_message(id:, data:, extended_id: false)
|
42
|
-
|
43
|
-
|
44
|
+
def send_can_message(id:, data:, extended_id: false, can_fd: nil)
|
45
|
+
use_fd = can_fd.nil? ? @can_fd : can_fd
|
46
|
+
|
47
|
+
with_socket(can_fd: use_fd) do |socket|
|
48
|
+
frame = build_can_frame(id: id, data: data, extended_id: extended_id, can_fd: use_fd)
|
44
49
|
socket.write(frame)
|
45
50
|
end
|
46
51
|
rescue ArgumentError
|
@@ -62,14 +67,16 @@ module CanMessenger
|
|
62
67
|
# - `:id` [Integer] the CAN message ID
|
63
68
|
# - `:data` [Array<Integer>] the message data bytes
|
64
69
|
# @return [void]
|
65
|
-
def start_listening(filter: nil, &block)
|
70
|
+
def start_listening(filter: nil, can_fd: nil, &block)
|
66
71
|
return @logger.error("No block provided to handle messages.") unless block_given?
|
67
72
|
|
68
73
|
@listening = true
|
69
74
|
|
70
|
-
|
75
|
+
use_fd = can_fd.nil? ? @can_fd : can_fd
|
76
|
+
|
77
|
+
with_socket(can_fd: use_fd) do |socket|
|
71
78
|
@logger.info("Started listening on #{@interface_name}")
|
72
|
-
process_message(socket, filter, &block) while @listening
|
79
|
+
process_message(socket, filter, use_fd, &block) while @listening
|
73
80
|
end
|
74
81
|
end
|
75
82
|
|
@@ -88,8 +95,8 @@ module CanMessenger
|
|
88
95
|
#
|
89
96
|
# @yield [socket] An open CAN socket.
|
90
97
|
# @return [void]
|
91
|
-
def with_socket
|
92
|
-
socket = open_can_socket
|
98
|
+
def with_socket(can_fd: @can_fd)
|
99
|
+
socket = open_can_socket(can_fd: can_fd)
|
93
100
|
return @logger.error("Failed to open socket, cannot continue operation.") if socket.nil?
|
94
101
|
|
95
102
|
yield socket
|
@@ -100,23 +107,32 @@ module CanMessenger
|
|
100
107
|
# Creates and configures a CAN socket bound to @interface_name.
|
101
108
|
#
|
102
109
|
# @return [Socket, nil] The configured CAN socket, or nil if the socket cannot be opened.
|
103
|
-
def open_can_socket
|
110
|
+
def open_can_socket(can_fd: @can_fd) # rubocop:disable Metrics/MethodLength
|
104
111
|
socket = Socket.open(Socket::PF_CAN, Socket::SOCK_RAW, Socket::CAN_RAW)
|
105
112
|
socket.bind(Socket.pack_sockaddr_can(@interface_name))
|
106
113
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, TIMEOUT)
|
114
|
+
if can_fd && Socket.const_defined?(:CAN_RAW_FD_FRAMES)
|
115
|
+
socket.setsockopt(Socket.const_defined?(:SOL_CAN_RAW) ? Socket::SOL_CAN_RAW : Socket::CAN_RAW,
|
116
|
+
Socket::CAN_RAW_FD_FRAMES, 1)
|
117
|
+
end
|
107
118
|
socket
|
108
119
|
rescue StandardError => e
|
109
120
|
@logger.error("Error creating CAN socket on interface #{@interface_name}: #{e}")
|
110
121
|
nil
|
111
122
|
end
|
112
123
|
|
113
|
-
# Builds a raw CAN frame for SocketCAN
|
124
|
+
# Builds a raw CAN or CAN FD frame for SocketCAN.
|
114
125
|
#
|
115
126
|
# @param id [Integer] the CAN ID
|
116
|
-
# @param data [Array<Integer>] up to 8
|
117
|
-
# @
|
118
|
-
|
119
|
-
|
127
|
+
# @param data [Array<Integer>] data bytes (up to 8 for classic, 64 for CAN FD)
|
128
|
+
# @param can_fd [Boolean] whether to build a CAN FD frame
|
129
|
+
# @return [String] the packed CAN frame
|
130
|
+
def build_can_frame(id:, data:, extended_id: false, can_fd: false) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity
|
131
|
+
if can_fd
|
132
|
+
raise ArgumentError, "CAN FD data cannot exceed #{MAX_FD_DATA} bytes" if data.size > MAX_FD_DATA
|
133
|
+
elsif data.size > 8
|
134
|
+
raise ArgumentError, "CAN data cannot exceed 8 bytes"
|
135
|
+
end
|
120
136
|
|
121
137
|
# Mask the ID to 29 bits
|
122
138
|
can_id = id & 0x1FFFFFFF
|
@@ -127,13 +143,15 @@ module CanMessenger
|
|
127
143
|
# Pack the 4‐byte ID (big-endian or little-endian)
|
128
144
|
id_bytes = @endianness == :big ? [can_id].pack("L>") : [can_id].pack("V")
|
129
145
|
|
130
|
-
# 1 byte for DLC, then 3 bytes
|
146
|
+
# 1 byte for DLC/length, then 3 bytes for flags/reserved
|
131
147
|
dlc_and_pad = [data.size, 0, 0, 0].pack("C*")
|
132
148
|
|
133
|
-
|
134
|
-
|
149
|
+
payload = if can_fd
|
150
|
+
data.pack("C*").ljust(MAX_FD_DATA, "\x00")
|
151
|
+
else
|
152
|
+
data.pack("C*").ljust(8, "\x00")
|
153
|
+
end
|
135
154
|
|
136
|
-
# Total 16 bytes (4 for ID, 1 for DLC, 3 padding, 8 data)
|
137
155
|
id_bytes + dlc_and_pad + payload
|
138
156
|
end
|
139
157
|
|
@@ -143,8 +161,8 @@ module CanMessenger
|
|
143
161
|
# @param filter [Integer, Range, Array<Integer>, nil] Optional filter for CAN IDs.
|
144
162
|
# @yield [message] Yields the message if it passes filtering.
|
145
163
|
# @return [void]
|
146
|
-
def process_message(socket, filter)
|
147
|
-
message = receive_message(socket: socket)
|
164
|
+
def process_message(socket, filter, can_fd)
|
165
|
+
message = receive_message(socket: socket, can_fd: can_fd)
|
148
166
|
return if message.nil?
|
149
167
|
return if filter && !matches_filter?(message_id: message[:id], filter: filter)
|
150
168
|
|
@@ -157,11 +175,12 @@ module CanMessenger
|
|
157
175
|
#
|
158
176
|
# @param socket [Socket]
|
159
177
|
# @return [Hash, nil]
|
160
|
-
def receive_message(socket:)
|
161
|
-
|
178
|
+
def receive_message(socket:, can_fd: false)
|
179
|
+
frame_size = can_fd ? CANFD_FRAME_SIZE : FRAME_SIZE
|
180
|
+
frame = socket.recv(frame_size)
|
162
181
|
return nil if frame.nil? || frame.size < MIN_FRAME_SIZE
|
163
182
|
|
164
|
-
parse_frame(frame: frame)
|
183
|
+
parse_frame(frame: frame, can_fd: can_fd)
|
165
184
|
rescue IO::WaitReadable
|
166
185
|
nil
|
167
186
|
rescue StandardError => e
|
@@ -173,9 +192,11 @@ module CanMessenger
|
|
173
192
|
#
|
174
193
|
# @param [String] frame
|
175
194
|
# @return [Hash, nil]
|
176
|
-
def parse_frame(frame:) # rubocop:disable Metrics/MethodLength
|
195
|
+
def parse_frame(frame:, can_fd: nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity
|
177
196
|
return nil unless frame && frame.size >= MIN_FRAME_SIZE
|
178
197
|
|
198
|
+
use_fd = can_fd.nil? ? frame.size >= CANFD_FRAME_SIZE : can_fd
|
199
|
+
|
179
200
|
raw_id = unpack_frame_id(frame: frame)
|
180
201
|
|
181
202
|
# Determine if EFF bit is set
|
@@ -185,8 +206,11 @@ module CanMessenger
|
|
185
206
|
# Now mask off everything except the lower 29 bits
|
186
207
|
id = raw_id & 0x1FFFFFFF
|
187
208
|
|
188
|
-
|
189
|
-
|
209
|
+
data_length = if use_fd
|
210
|
+
frame[4].ord
|
211
|
+
else
|
212
|
+
frame[4].ord & 0x0F
|
213
|
+
end
|
190
214
|
|
191
215
|
# Extract data
|
192
216
|
data = if frame.size >= MIN_FRAME_SIZE + data_length
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: can_messenger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fk1018
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: CanMessenger provides an interface to send and receive messages over
|
14
14
|
the CAN bus, useful for applications requiring CAN communication in Ruby.
|