can_messenger 1.2.0 → 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 +17 -4
- data/lib/can_messenger/messenger.rb +55 -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:
|
@@ -51,13 +55,22 @@ messenger.send_can_message(id: 0x123, data: [0xDE, 0xAD, 0xBE, 0xEF])
|
|
51
55
|
|
52
56
|
> **Note:** Under the hood, the gem now writes CAN frames to a raw socket instead of calling `cansend`. No external dependencies are required beyond raw-socket permissions.
|
53
57
|
|
54
|
-
If you need to send an extended CAN frame (29-bit ID), set extended_id: true. The gem then sets the Extended Frame Format (EFF) bit automatically
|
58
|
+
If you need to send an extended CAN frame (29-bit ID), set extended_id: true. The gem then sets the Extended Frame Format (EFF) bit automatically:
|
55
59
|
|
56
60
|
```ruby
|
57
61
|
messenger.send_can_message(id: 0x123456, data: [0x01, 0x02, 0x03], extended_id: true)
|
58
62
|
```
|
59
63
|
|
60
|
-
|
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
|
+
|
73
|
+
### Receiving CAN Messages
|
61
74
|
|
62
75
|
To listen for incoming messages, set up a listener:
|
63
76
|
|
@@ -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,11 +41,15 @@ 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
|
51
|
+
rescue ArgumentError
|
52
|
+
raise
|
46
53
|
rescue StandardError => e
|
47
54
|
@logger.error("Error sending CAN message (ID: #{id}): #{e}")
|
48
55
|
end
|
@@ -60,12 +67,16 @@ module CanMessenger
|
|
60
67
|
# - `:id` [Integer] the CAN message ID
|
61
68
|
# - `:data` [Array<Integer>] the message data bytes
|
62
69
|
# @return [void]
|
63
|
-
def start_listening(filter: nil, &block)
|
70
|
+
def start_listening(filter: nil, can_fd: nil, &block)
|
64
71
|
return @logger.error("No block provided to handle messages.") unless block_given?
|
65
72
|
|
66
|
-
|
73
|
+
@listening = true
|
74
|
+
|
75
|
+
use_fd = can_fd.nil? ? @can_fd : can_fd
|
76
|
+
|
77
|
+
with_socket(can_fd: use_fd) do |socket|
|
67
78
|
@logger.info("Started listening on #{@interface_name}")
|
68
|
-
process_message(socket, filter, &block) while @listening
|
79
|
+
process_message(socket, filter, use_fd, &block) while @listening
|
69
80
|
end
|
70
81
|
end
|
71
82
|
|
@@ -84,8 +95,8 @@ module CanMessenger
|
|
84
95
|
#
|
85
96
|
# @yield [socket] An open CAN socket.
|
86
97
|
# @return [void]
|
87
|
-
def with_socket
|
88
|
-
socket = open_can_socket
|
98
|
+
def with_socket(can_fd: @can_fd)
|
99
|
+
socket = open_can_socket(can_fd: can_fd)
|
89
100
|
return @logger.error("Failed to open socket, cannot continue operation.") if socket.nil?
|
90
101
|
|
91
102
|
yield socket
|
@@ -96,23 +107,32 @@ module CanMessenger
|
|
96
107
|
# Creates and configures a CAN socket bound to @interface_name.
|
97
108
|
#
|
98
109
|
# @return [Socket, nil] The configured CAN socket, or nil if the socket cannot be opened.
|
99
|
-
def open_can_socket
|
110
|
+
def open_can_socket(can_fd: @can_fd) # rubocop:disable Metrics/MethodLength
|
100
111
|
socket = Socket.open(Socket::PF_CAN, Socket::SOCK_RAW, Socket::CAN_RAW)
|
101
112
|
socket.bind(Socket.pack_sockaddr_can(@interface_name))
|
102
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
|
103
118
|
socket
|
104
119
|
rescue StandardError => e
|
105
120
|
@logger.error("Error creating CAN socket on interface #{@interface_name}: #{e}")
|
106
121
|
nil
|
107
122
|
end
|
108
123
|
|
109
|
-
# Builds a raw CAN frame for SocketCAN
|
124
|
+
# Builds a raw CAN or CAN FD frame for SocketCAN.
|
110
125
|
#
|
111
126
|
# @param id [Integer] the CAN ID
|
112
|
-
# @param data [Array<Integer>] up to 8
|
113
|
-
# @
|
114
|
-
|
115
|
-
|
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
|
116
136
|
|
117
137
|
# Mask the ID to 29 bits
|
118
138
|
can_id = id & 0x1FFFFFFF
|
@@ -123,13 +143,15 @@ module CanMessenger
|
|
123
143
|
# Pack the 4‐byte ID (big-endian or little-endian)
|
124
144
|
id_bytes = @endianness == :big ? [can_id].pack("L>") : [can_id].pack("V")
|
125
145
|
|
126
|
-
# 1 byte for DLC, then 3 bytes
|
146
|
+
# 1 byte for DLC/length, then 3 bytes for flags/reserved
|
127
147
|
dlc_and_pad = [data.size, 0, 0, 0].pack("C*")
|
128
148
|
|
129
|
-
|
130
|
-
|
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
|
131
154
|
|
132
|
-
# Total 16 bytes (4 for ID, 1 for DLC, 3 padding, 8 data)
|
133
155
|
id_bytes + dlc_and_pad + payload
|
134
156
|
end
|
135
157
|
|
@@ -139,8 +161,8 @@ module CanMessenger
|
|
139
161
|
# @param filter [Integer, Range, Array<Integer>, nil] Optional filter for CAN IDs.
|
140
162
|
# @yield [message] Yields the message if it passes filtering.
|
141
163
|
# @return [void]
|
142
|
-
def process_message(socket, filter)
|
143
|
-
message = receive_message(socket: socket)
|
164
|
+
def process_message(socket, filter, can_fd)
|
165
|
+
message = receive_message(socket: socket, can_fd: can_fd)
|
144
166
|
return if message.nil?
|
145
167
|
return if filter && !matches_filter?(message_id: message[:id], filter: filter)
|
146
168
|
|
@@ -153,11 +175,12 @@ module CanMessenger
|
|
153
175
|
#
|
154
176
|
# @param socket [Socket]
|
155
177
|
# @return [Hash, nil]
|
156
|
-
def receive_message(socket:)
|
157
|
-
|
178
|
+
def receive_message(socket:, can_fd: false)
|
179
|
+
frame_size = can_fd ? CANFD_FRAME_SIZE : FRAME_SIZE
|
180
|
+
frame = socket.recv(frame_size)
|
158
181
|
return nil if frame.nil? || frame.size < MIN_FRAME_SIZE
|
159
182
|
|
160
|
-
parse_frame(frame: frame)
|
183
|
+
parse_frame(frame: frame, can_fd: can_fd)
|
161
184
|
rescue IO::WaitReadable
|
162
185
|
nil
|
163
186
|
rescue StandardError => e
|
@@ -169,9 +192,11 @@ module CanMessenger
|
|
169
192
|
#
|
170
193
|
# @param [String] frame
|
171
194
|
# @return [Hash, nil]
|
172
|
-
def parse_frame(frame:) # rubocop:disable Metrics/MethodLength
|
195
|
+
def parse_frame(frame:, can_fd: nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity
|
173
196
|
return nil unless frame && frame.size >= MIN_FRAME_SIZE
|
174
197
|
|
198
|
+
use_fd = can_fd.nil? ? frame.size >= CANFD_FRAME_SIZE : can_fd
|
199
|
+
|
175
200
|
raw_id = unpack_frame_id(frame: frame)
|
176
201
|
|
177
202
|
# Determine if EFF bit is set
|
@@ -181,8 +206,11 @@ module CanMessenger
|
|
181
206
|
# Now mask off everything except the lower 29 bits
|
182
207
|
id = raw_id & 0x1FFFFFFF
|
183
208
|
|
184
|
-
|
185
|
-
|
209
|
+
data_length = if use_fd
|
210
|
+
frame[4].ord
|
211
|
+
else
|
212
|
+
frame[4].ord & 0x0F
|
213
|
+
end
|
186
214
|
|
187
215
|
# Extract data
|
188
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.
|