can_messenger 0.2.2 → 1.0.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 +4 -10
- data/lib/can_messenger/messenger.rb +97 -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: 7d26615bdfc66cb0379649692d151278aea8056338510b2d73f144f9d0fe0fe4
|
4
|
+
data.tar.gz: 1259bc700f8adf69e1de5f73cdd1dcbef783612b081be9757a31e918855e1b2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a79e6a61508e0915d1b52e2e31c1b7104597c46db9d21c838952acac87060efe88fb4914d9ad64e4ac7c63287ef32a8b0a927f4b494c0212aed95cdd198ff693
|
7
|
+
data.tar.gz: 61904213c26ef9b468a4083af58cbf00c95102b5ee841a943b3e559504d324234d3b57d3a1b99a829141a0f629705c6b3240e7d02f8f0081d004b86684088459
|
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
|
@@ -105,19 +105,13 @@ messenger.stop_listening
|
|
105
105
|
|
106
106
|
## Features
|
107
107
|
|
108
|
-
- **Send CAN Messages**: Send
|
108
|
+
- **Send CAN Messages**: Send CAN messages with a specified ID.
|
109
109
|
- **Receive CAN Messages**: Continuously listen for messages on a CAN interface.
|
110
110
|
- **Logging**: Logs errors and events for debugging and troubleshooting.
|
111
111
|
|
112
112
|
## Development
|
113
113
|
|
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
|
-
```
|
114
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test:rspec` to run the tests.
|
121
115
|
|
122
116
|
## Contributing
|
123
117
|
|
@@ -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,26 +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
|
-
socket = create_socket
|
57
|
-
while @listening
|
58
|
-
message = receive_message(socket)
|
59
|
-
break unless message # Exit loop if no message
|
60
|
-
|
61
|
-
next if filter && !matches_filter?(message[:id], filter) # Apply filter if specified
|
61
|
+
def start_listening(filter: nil, &block)
|
62
|
+
return @logger.error("No block provided to handle messages.") unless block_given?
|
62
63
|
|
63
|
-
|
64
|
+
with_socket do |socket|
|
65
|
+
@logger.info("Started listening on #{@can_interface}")
|
66
|
+
process_message(socket, filter, &block) while @listening
|
64
67
|
end
|
65
|
-
ensure
|
66
|
-
socket&.close
|
67
68
|
end
|
68
69
|
|
69
70
|
# Stops the listening loop by setting @listening to false.
|
@@ -77,28 +78,68 @@ module CanMessenger
|
|
77
78
|
|
78
79
|
private
|
79
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
|
+
|
80
116
|
# Creates and configures a CAN socket.
|
81
117
|
#
|
82
|
-
# @return [Socket] The configured CAN socket.
|
83
|
-
def
|
118
|
+
# @return [Socket, nil] The configured CAN socket, or nil if the socket cannot be opened.
|
119
|
+
def open_can_socket
|
84
120
|
socket = Socket.open(Socket::PF_CAN, Socket::SOCK_RAW, Socket::CAN_RAW)
|
85
121
|
socket.bind(Socket.pack_sockaddr_can(@can_interface))
|
86
|
-
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO,
|
122
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, TIMEOUT)
|
87
123
|
socket
|
88
124
|
rescue StandardError => e
|
89
125
|
@logger.error("Error creating CAN socket on interface #{@can_interface}: #{e}")
|
126
|
+
nil
|
90
127
|
end
|
91
128
|
|
92
|
-
# 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`.
|
93
134
|
#
|
94
|
-
# @param [Socket]
|
95
|
-
# @return [
|
96
|
-
#
|
97
|
-
def receive_message(socket)
|
98
|
-
frame = socket.recv(
|
99
|
-
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
|
100
141
|
|
101
|
-
parse_frame(frame)
|
142
|
+
parse_frame(frame: frame)
|
102
143
|
rescue IO::WaitReadable
|
103
144
|
nil
|
104
145
|
rescue StandardError => e
|
@@ -109,41 +150,38 @@ module CanMessenger
|
|
109
150
|
# Parses a raw CAN frame into a message hash.
|
110
151
|
#
|
111
152
|
# @param [String] frame The raw CAN frame.
|
112
|
-
# @return [
|
113
|
-
|
114
|
-
|
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
|
115
157
|
|
116
158
|
id = frame[0..3].unpack1("L>") & 0x1FFFFFFF
|
117
159
|
data_length = frame[4].ord & 0x0F
|
118
|
-
data = (frame[
|
119
|
-
|
160
|
+
data = (frame[MIN_FRAME_SIZE, data_length].unpack("C*") if frame.size >= MIN_FRAME_SIZE + data_length)
|
120
161
|
{ id: id, data: data }
|
121
162
|
rescue StandardError => e
|
122
163
|
@logger.error("Error parsing CAN frame: #{e}")
|
123
164
|
nil
|
124
165
|
end
|
125
166
|
|
126
|
-
# 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.
|
127
173
|
#
|
128
|
-
#
|
129
|
-
# - A single ID (e.g., 0x123)
|
130
|
-
# - A range of IDs (e.g., 0x100..0x200)
|
131
|
-
# - An array of IDs (e.g., [0x123, 0x200, 0x300])
|
174
|
+
# If the filter is nil or unrecognized, the method returns true.
|
132
175
|
#
|
133
|
-
# @param [Integer]
|
134
|
-
# @param [Integer, Range, Array<Integer>, nil]
|
135
|
-
#
|
136
|
-
|
137
|
-
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:)
|
138
180
|
case filter
|
139
|
-
when Integer
|
140
|
-
|
141
|
-
when
|
142
|
-
|
143
|
-
when Array
|
144
|
-
filter.include?(message_id)
|
145
|
-
else
|
146
|
-
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
|
147
185
|
end
|
148
186
|
end
|
149
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.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-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:
|