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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d47c9c4b19c40337cb6b58f380e9a3f07554144a14d23ef4c2589060fd25942e
4
- data.tar.gz: 66f58f8fab19fb73ab5124e4036313459ae032282bdd07613dc6156ef86a1379
3
+ metadata.gz: 3a68f4a339bebc034ffe78e990086ff91d28164962c2877e5d88edb095cbca90
4
+ data.tar.gz: 7a96c22ccf82cae952123d733805c2efe31dd482ba9bf5d5a66d29cb14572822
5
5
  SHA512:
6
- metadata.gz: a55c719faf6f813ff4aef9004b4b32065297773498bf92291019002520b43c940e304fa5a0244635f42c9152ab3729cbeaa8534d809b2790878ebc9ef9a00244
7
- data.tar.gz: '014168fa0419b78aa966c4d1d9a415c2e8e70650323df2c1a4328354099ffacf038cd52ef1c1fc371334e312a91538e985b7ec98727863739aa70c88e449d4d3'
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). If you work with non-standard frames or CAN FD (64-byte data), you’ll need to customize the parsing/sending logic.
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
- with_socket do |socket|
43
- frame = build_can_frame(id: id, data: data, extended_id: extended_id)
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
- with_socket do |socket|
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, big-endian ID, 1-byte DLC, up to 8 data bytes, and 3 padding bytes.
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 bytes
117
- # @return [String] a 16-byte string representing a classic CAN frame
118
- def build_can_frame(id:, data:, extended_id: false)
119
- raise ArgumentError, "CAN data cannot exceed 8 bytes" if data.size > 8
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 of padding
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
- # Up to 8 data bytes, pad with 0 if fewer
134
- payload = data.pack("C*").ljust(8, "\x00")
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
- frame = socket.recv(FRAME_SIZE)
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
- # DLC is the lower 4 bits of byte 4
189
- data_length = frame[4].ord & 0x0F
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CanMessenger
4
- VERSION = "1.2.1"
4
+ VERSION = "1.3.0"
5
5
  end
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.2.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-06-05 00:00:00.000000000 Z
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.