can_messenger 0.2.3 → 1.0.3
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 +46 -10
- data/lib/can_messenger/messenger.rb +98 -59
- data/lib/can_messenger/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3c1f6bf2353eb4b67b93f9aa9e167a4474b8c65cc1abe2327b520d04695cec2
|
4
|
+
data.tar.gz: 6afe7242348e786084de8c6d8afe25f18727ac57b2eeea727518ddd77d53330a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7cd537434d6c9f0d41a6a9ad6dd37229bb569057d9b9e259ac301792e71b629df1b66f2ece6c3d5606e7224410d4f42d1c2488894f5341faecb551e46b222140
|
7
|
+
data.tar.gz: 18620bf7cc07a7694438b0e3da277a3b96d4767cb87d36c5310920dbce5976cc21971e4c3e572869c99ee3a37a7eac7e9996f7948f2d5d2001875b9dead5f4c1
|
data/README.md
CHANGED
@@ -44,7 +44,7 @@ To create a new instance of `CanMessenger` and start sending messages:
|
|
44
44
|
```ruby
|
45
45
|
require 'can_messenger'
|
46
46
|
|
47
|
-
messenger = CanMessenger::Messenger.new('can0')
|
47
|
+
messenger = CanMessenger::Messenger.new(interface_name: 'can0')
|
48
48
|
```
|
49
49
|
|
50
50
|
### Sending CAN Messages
|
@@ -52,7 +52,7 @@ messenger = CanMessenger::Messenger.new('can0')
|
|
52
52
|
To send a message:
|
53
53
|
|
54
54
|
```ruby
|
55
|
-
messenger.send_can_message(0x123, [0xDE, 0xAD, 0xBE, 0xEF])
|
55
|
+
messenger.send_can_message(id: 0x123, data: [0xDE, 0xAD, 0xBE, 0xEF])
|
56
56
|
```
|
57
57
|
|
58
58
|
### Receiving CAN Messages
|
@@ -103,21 +103,57 @@ To stop listening, use:
|
|
103
103
|
messenger.stop_listening
|
104
104
|
```
|
105
105
|
|
106
|
+
## Important Considerations
|
107
|
+
|
108
|
+
Before using `can_messenger`, please note the following:
|
109
|
+
|
110
|
+
- **Environment Requirements:**
|
111
|
+
|
112
|
+
- **Permissions:** Working with raw sockets may require elevated privileges or membership in a specific group to open and bind to CAN interfaces without running as root.
|
113
|
+
|
114
|
+
- **API Changes (v1.0.0 and later):**
|
115
|
+
|
116
|
+
- **Keyword Arguments:** The Messenger API now requires keyword arguments. For example, when initializing the Messenger, use:
|
117
|
+
```ruby
|
118
|
+
messenger = CanMessenger::Messenger.new(interface_name: 'can0')
|
119
|
+
```
|
120
|
+
Similarly, methods like `send_can_message` now require named parameters:
|
121
|
+
```ruby
|
122
|
+
messenger.send_can_message(id: 0x123, data: [0xDE, 0xAD, 0xBE, 0xEF])
|
123
|
+
```
|
124
|
+
If you're upgrading from an earlier version, update your code accordingly.
|
125
|
+
- **Block Requirement for `start_listening`:**
|
126
|
+
The `start_listening` method now requires a block. If no block is provided, the method logs an error and exits without processing messages. Ensure you pass a block to handle incoming CAN messages:
|
127
|
+
```ruby
|
128
|
+
messenger.start_listening do |message|
|
129
|
+
# Process the message here
|
130
|
+
puts "Received: #{message}"
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
- **Threading & Socket Management:**
|
135
|
+
|
136
|
+
- **Blocking Behavior:** The gem uses blocking socket calls and continuously listens for messages. Be sure to manage the listener's lifecycle appropriately,especially if using it in a multi-threaded application. Always call `stop_listening` to gracefully shut down the listener.
|
137
|
+
- **Resource Cleanup:** The socket is automatically closed when the listening loop terminates. However, you should ensure that your application stops the listener to avoid resource leaks.
|
138
|
+
|
139
|
+
- **Logging:**
|
140
|
+
|
141
|
+
- **Default Logger:** By default, if no logger is provided, the gem logs to standard output. For more controlled logging, pass a custom logger when initializing the Messenger.
|
142
|
+
|
143
|
+
- **CAN Frame Format Assumptions:**
|
144
|
+
- The gem expects a standard CAN frame format with a minimum frame size and specific layout (e.g., the first 4 bytes for the CAN ID, followed by a byte indicating data length, etc.). If you work with non-standard frames, you may need to adjust the implementation.
|
145
|
+
|
146
|
+
By keeping these points in mind, you can avoid common pitfalls and ensure that `can_messenger` is integrated smoothly into your project.
|
147
|
+
|
106
148
|
## Features
|
107
149
|
|
108
|
-
- **Send CAN Messages**: Send
|
150
|
+
- **Send CAN Messages**: Send CAN messages with a specified ID.
|
109
151
|
- **Receive CAN Messages**: Continuously listen for messages on a CAN interface.
|
110
152
|
- **Logging**: Logs errors and events for debugging and troubleshooting.
|
111
153
|
|
112
154
|
## Development
|
113
155
|
|
114
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
115
|
-
|
116
|
-
To install this gem onto your local machine, run:
|
117
|
-
|
118
|
-
```bash
|
119
|
-
bundle exec rake install
|
120
|
-
```
|
156
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test:rspec` to run the tests.
|
121
157
|
|
122
158
|
## Contributing
|
123
159
|
|
@@ -11,18 +11,22 @@ module CanMessenger
|
|
11
11
|
# It supports sending messages with specific CAN IDs and listening for incoming messages.
|
12
12
|
#
|
13
13
|
# @example
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
14
|
+
# messenger = CanMessenger::Messenger.new(interface_name: 'can0')
|
15
|
+
# messenger.send_can_message(id: 0x123, data: [0xDE, 0xAD, 0xBE, 0xEF])
|
16
|
+
# messenger.start_listening do |message|
|
17
17
|
# puts "Received: ID=#{message[:id]}, Data=#{message[:data].map { |b| '0x%02X' % b }}"
|
18
18
|
# end
|
19
19
|
class Messenger
|
20
|
+
FRAME_SIZE = 16
|
21
|
+
MIN_FRAME_SIZE = 8
|
22
|
+
TIMEOUT = [1, 0].pack("l_2")
|
20
23
|
# Initializes a new Messenger instance.
|
21
24
|
#
|
22
|
-
# @param [String]
|
23
|
-
# @param [Logger] logger Optional logger for error handling and debug information.
|
24
|
-
|
25
|
-
|
25
|
+
# @param [String] interface_name The CAN interface to use (e.g., 'can0').
|
26
|
+
# @param [Logger, nil] logger Optional logger for error handling and debug information.
|
27
|
+
# @return [void]
|
28
|
+
def initialize(interface_name:, logger: nil)
|
29
|
+
@can_interface = interface_name
|
26
30
|
@logger = logger || Logger.new($stdout)
|
27
31
|
@listening = true # Control flag for listening loop
|
28
32
|
end
|
@@ -32,11 +36,11 @@ module CanMessenger
|
|
32
36
|
# @param [Integer] id The CAN ID of the message.
|
33
37
|
# @param [Array<Integer>] data The data bytes of the CAN message.
|
34
38
|
# @return [void]
|
35
|
-
def send_can_message(id
|
39
|
+
def send_can_message(id:, data:)
|
36
40
|
hex_id = format("%03X", id)
|
37
41
|
hex_data = data.map { |byte| format("%02X", byte) }.join
|
38
42
|
command = "cansend #{@can_interface} #{hex_id}##{hex_data}"
|
39
|
-
system(command)
|
43
|
+
system(command) # @todo validate command status
|
40
44
|
rescue StandardError => e
|
41
45
|
@logger.error("Error sending CAN message (ID: #{id}): #{e}")
|
42
46
|
end
|
@@ -44,25 +48,23 @@ module CanMessenger
|
|
44
48
|
# Continuously listens for CAN messages on the specified interface.
|
45
49
|
#
|
46
50
|
# This method listens for incoming CAN messages and applies an optional filter.
|
47
|
-
# The filter can be a specific CAN ID
|
48
|
-
# the filter are yielded to the provided block.
|
51
|
+
# The filter can be a specific CAN ID, a range of IDs, or an array of IDs.
|
52
|
+
# Only messages that match the filter are yielded to the provided block.
|
49
53
|
#
|
50
|
-
# @param [Integer, Range, nil] filter Optional filter for CAN IDs.
|
51
|
-
#
|
52
|
-
#
|
54
|
+
# @param [Integer, Range, Array<Integer>, nil] filter Optional filter for CAN IDs.
|
55
|
+
# Pass a single ID (e.g., 0x123), a range (e.g., 0x100..0x200), or an array of IDs.
|
56
|
+
# If no filter is provided, all messages are processed.
|
57
|
+
# @yield [message] Yields each received CAN message as a hash with keys:
|
58
|
+
# - `:id` [Integer] the CAN message ID
|
59
|
+
# - `:data` [Array<Integer>] the message data bytes
|
53
60
|
# @return [void]
|
54
|
-
def start_listening(filter: nil)
|
55
|
-
@logger.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
next if filter && !matches_filter?(message[:id], filter) # Apply filter if specified
|
61
|
-
|
62
|
-
yield(message)
|
61
|
+
def start_listening(filter: nil, &block)
|
62
|
+
return @logger.error("No block provided to handle messages.") unless block_given?
|
63
|
+
|
64
|
+
with_socket do |socket|
|
65
|
+
@logger.info("Started listening on #{@can_interface}")
|
66
|
+
process_message(socket, filter, &block) while @listening
|
63
67
|
end
|
64
|
-
ensure
|
65
|
-
socket&.close
|
66
68
|
end
|
67
69
|
|
68
70
|
# Stops the listening loop by setting @listening to false.
|
@@ -76,28 +78,68 @@ module CanMessenger
|
|
76
78
|
|
77
79
|
private
|
78
80
|
|
81
|
+
# Yields an open CAN socket to the given block.
|
82
|
+
#
|
83
|
+
# Opens a socket and, if successful, yields it to the block.
|
84
|
+
# If the socket cannot be opened, logs an error and returns.
|
85
|
+
#
|
86
|
+
# @yield [socket] An open CAN socket.
|
87
|
+
# @return [void]
|
88
|
+
def with_socket
|
89
|
+
socket = open_can_socket
|
90
|
+
return @logger.error("Failed to open socket, cannot continue listening.") if socket.nil?
|
91
|
+
|
92
|
+
yield socket
|
93
|
+
ensure
|
94
|
+
socket&.close
|
95
|
+
end
|
96
|
+
|
97
|
+
# Processes a single CAN message.
|
98
|
+
#
|
99
|
+
# Reads a message from the socket, applies the filter, and yields the message if appropriate.
|
100
|
+
# If an error occurs during processing, it logs the error.
|
101
|
+
#
|
102
|
+
# @param socket [Socket] The CAN socket.
|
103
|
+
# @param filter [Integer, Range, Array<Integer>, nil] Optional filter for CAN IDs.
|
104
|
+
# @yield [message] Yields the message if it passes filtering.
|
105
|
+
# @return [void]
|
106
|
+
def process_message(socket, filter)
|
107
|
+
message = receive_message(socket: socket)
|
108
|
+
return if message.nil?
|
109
|
+
return if filter && !matches_filter?(message_id: message[:id], filter: filter)
|
110
|
+
|
111
|
+
yield(message)
|
112
|
+
rescue StandardError => e
|
113
|
+
@logger.error("Unexpected error in listening loop: #{e.message}")
|
114
|
+
end
|
115
|
+
|
79
116
|
# Creates and configures a CAN socket.
|
80
117
|
#
|
81
|
-
# @return [Socket] The configured CAN socket.
|
82
|
-
def
|
118
|
+
# @return [Socket, nil] The configured CAN socket, or nil if the socket cannot be opened.
|
119
|
+
def open_can_socket
|
83
120
|
socket = Socket.open(Socket::PF_CAN, Socket::SOCK_RAW, Socket::CAN_RAW)
|
84
121
|
socket.bind(Socket.pack_sockaddr_can(@can_interface))
|
85
|
-
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO,
|
122
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, TIMEOUT)
|
86
123
|
socket
|
87
124
|
rescue StandardError => e
|
88
125
|
@logger.error("Error creating CAN socket on interface #{@can_interface}: #{e}")
|
126
|
+
nil
|
89
127
|
end
|
90
128
|
|
91
|
-
# Receives a CAN message from the socket and parses it.
|
129
|
+
# Receives a CAN message from the given socket and parses it.
|
130
|
+
#
|
131
|
+
# This method attempts to read a frame from the provided CAN socket. It returns a parsed
|
132
|
+
# message hash in the format `{ id: Integer, data: Array<Integer> }` if a valid frame is received.
|
133
|
+
# If no frame is received, or if an error occurs, the method returns `nil`.
|
92
134
|
#
|
93
|
-
# @param [Socket]
|
94
|
-
# @return [
|
95
|
-
#
|
96
|
-
def receive_message(socket)
|
97
|
-
frame = socket.recv(
|
98
|
-
return nil if frame.nil? || frame.size <
|
135
|
+
# @param socket [Socket] The CAN socket to read from.
|
136
|
+
# @return [{ id: Integer, data: Array<Integer> }, nil] A hash representing the CAN message, or `nil` if no message
|
137
|
+
# is received or an error occurs.
|
138
|
+
def receive_message(socket:)
|
139
|
+
frame = socket.recv(FRAME_SIZE)
|
140
|
+
return nil if frame.nil? || frame.size < MIN_FRAME_SIZE
|
99
141
|
|
100
|
-
parse_frame(frame)
|
142
|
+
parse_frame(frame: frame)
|
101
143
|
rescue IO::WaitReadable
|
102
144
|
nil
|
103
145
|
rescue StandardError => e
|
@@ -108,41 +150,38 @@ module CanMessenger
|
|
108
150
|
# Parses a raw CAN frame into a message hash.
|
109
151
|
#
|
110
152
|
# @param [String] frame The raw CAN frame.
|
111
|
-
# @return [
|
112
|
-
|
113
|
-
|
153
|
+
# @return [{ id: Integer, data: Array<Integer> }, nil] Parsed message with :id and :data keys,
|
154
|
+
# or nil if the frame is incomplete or an error occurs.
|
155
|
+
def parse_frame(frame:)
|
156
|
+
return nil unless frame && frame.size >= MIN_FRAME_SIZE
|
114
157
|
|
115
158
|
id = frame[0..3].unpack1("L>") & 0x1FFFFFFF
|
116
159
|
data_length = frame[4].ord & 0x0F
|
117
|
-
data = (frame[
|
118
|
-
|
160
|
+
data = (frame[MIN_FRAME_SIZE, data_length].unpack("C*") if frame.size >= MIN_FRAME_SIZE + data_length)
|
119
161
|
{ id: id, data: data }
|
120
162
|
rescue StandardError => e
|
121
163
|
@logger.error("Error parsing CAN frame: #{e}")
|
122
164
|
nil
|
123
165
|
end
|
124
166
|
|
125
|
-
# Checks
|
167
|
+
# Checks whether the given message ID matches the specified filter.
|
168
|
+
#
|
169
|
+
# The filter can be one of the following:
|
170
|
+
# - An Integer, which requires an exact match.
|
171
|
+
# - A Range of Integers, where the message ID must fall within the range.
|
172
|
+
# - An Array of Integers, where the message ID must be included in the array.
|
126
173
|
#
|
127
|
-
#
|
128
|
-
# - A single ID (e.g., 0x123)
|
129
|
-
# - A range of IDs (e.g., 0x100..0x200)
|
130
|
-
# - An array of IDs (e.g., [0x123, 0x200, 0x300])
|
174
|
+
# If the filter is nil or unrecognized, the method returns true.
|
131
175
|
#
|
132
|
-
# @param [Integer]
|
133
|
-
# @param [Integer, Range, Array<Integer>, nil]
|
134
|
-
#
|
135
|
-
|
136
|
-
def matches_filter?(message_id, filter)
|
176
|
+
# @param message_id [Integer] The ID of the incoming CAN message.
|
177
|
+
# @param filter [Integer, Range, Array<Integer>, nil] The filter to apply.
|
178
|
+
# @return [Boolean] Returns true if the message ID matches the filter otherwise false.
|
179
|
+
def matches_filter?(message_id:, filter:)
|
137
180
|
case filter
|
138
|
-
when Integer
|
139
|
-
|
140
|
-
when
|
141
|
-
|
142
|
-
when Array
|
143
|
-
filter.include?(message_id)
|
144
|
-
else
|
145
|
-
true # No filter or unrecognized filter
|
181
|
+
when Integer then message_id == filter
|
182
|
+
when Range then filter.cover?(message_id)
|
183
|
+
when Array then filter.include?(message_id)
|
184
|
+
else true
|
146
185
|
end
|
147
186
|
end
|
148
187
|
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: 0.
|
4
|
+
version: 1.0.3
|
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-
|
11
|
+
date: 2025-02-10 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.
|
@@ -30,6 +30,7 @@ metadata:
|
|
30
30
|
homepage_uri: https://github.com/fk1018/can_messenger
|
31
31
|
source_code_uri: https://github.com/fk1018/can_messenger
|
32
32
|
changelog_uri: https://github.com/fk1018/can_messenger/blob/main/CHANGELOG.md
|
33
|
+
rubygems_mfa_required: 'true'
|
33
34
|
post_install_message:
|
34
35
|
rdoc_options: []
|
35
36
|
require_paths:
|