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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67c064d4b8899c012642e83b9c348cf72da13eb136bc81283a2acb7116e79897
4
- data.tar.gz: ddb8c322325800be6b03813510e1831348e724967e4f05daa9ac0c2b5b32dad3
3
+ metadata.gz: 3a68f4a339bebc034ffe78e990086ff91d28164962c2877e5d88edb095cbca90
4
+ data.tar.gz: 7a96c22ccf82cae952123d733805c2efe31dd482ba9bf5d5a66d29cb14572822
5
5
  SHA512:
6
- metadata.gz: 3d248a72be8fb4b407fa4b79f42fc291658b39340db84c255440cd33b465c02ca7a2d6bf9274a1b52cae8b4cb3b4890d0d2562238040d2365649c1d7d7e0300e
7
- data.tar.gz: 7749d6a7544e43db00dfec4a44a967362b449b78bb657da2f7e3afefc04956a466a9348c9414f05c2b72669858e8cecc27f8ac0b32fa2d6c8b58cbaca492e041
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:### Receiving CAN Messages
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
- ### Listen to CAN Messages
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). 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,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
- 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
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
- with_socket do |socket|
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, 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.
110
125
  #
111
126
  # @param id [Integer] the CAN ID
112
- # @param data [Array<Integer>] up to 8 bytes
113
- # @return [String] a 16-byte string representing a classic CAN frame
114
- def build_can_frame(id:, data:, extended_id: false)
115
- 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
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 of padding
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
- # Up to 8 data bytes, pad with 0 if fewer
130
- 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
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
- 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)
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
- # DLC is the lower 4 bits of byte 4
185
- 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
186
214
 
187
215
  # Extract data
188
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.0"
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.0
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-02-28 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.