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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acc375538208d463f34aa89a1947ab24791b3f1341e2283236a2a8759c0502ea
4
- data.tar.gz: 935132d1c3d9478974a56fc9ebee44c3f4773db9a18da7be4502486a47606935
3
+ metadata.gz: 7d26615bdfc66cb0379649692d151278aea8056338510b2d73f144f9d0fe0fe4
4
+ data.tar.gz: 1259bc700f8adf69e1de5f73cdd1dcbef783612b081be9757a31e918855e1b2a
5
5
  SHA512:
6
- metadata.gz: a3dcba75678d1968cb3cfeb81cf06c33fc42aa7fe84172c35d5ec70e955d05ff765060cbf10de94170262a23d9728880ed1c98a7bafcae69b4f35895d723972d
7
- data.tar.gz: 82e3a9f4ac0aa23aad20f6ada51bf54acc308b66b2cfbff6571c8aa66ba0d32aa4ad8d28d05027e63bb51ca38bdc89bebeb732ed9d2202b8ecb0f22e8018f620
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 data to specified CAN IDs.
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
- # socket = CanMessenger::Messenger.new('can0')
15
- # socket.send_can_message(0x123, [0xDE, 0xAD, 0xBE, 0xEF])
16
- # socket.start_listening do |message|
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] can_interface The CAN interface to use (e.g., 'can0').
23
- # @param [Logger] logger Optional logger for error handling and debug information.
24
- def initialize(can_interface, logger = nil)
25
- @can_interface = can_interface
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, data)
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 or a range of IDs. Only messages that match
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. Pass a single ID (e.g., 0x123)
51
- # or a range (e.g., 0x100..0x200). If no filter is provided, all messages are processed.
52
- # @yield [Hash] Each received message in the format { id: Integer, data: Array<Integer> }.
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.info("Started listening on #{@can_interface}")
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
- yield(message)
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 create_socket
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, [1, 0].pack("l_2"))
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] socket The CAN socket to read from.
95
- # @return [Hash, nil] Parsed message in the format { id: Integer, data: Array<Integer> },
96
- # or nil if no message is received or an error occurs.
97
- def receive_message(socket)
98
- frame = socket.recv(16)
99
- return nil if frame.nil? || frame.size < 8
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 [Hash, nil] Parsed message with :id and :data keys, or nil if the frame is incomplete.
113
- def parse_frame(frame)
114
- return nil unless frame && frame.size >= 8
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[8, data_length].unpack("C*") if frame.size >= 8 + data_length)
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 if a message ID matches the given filter.
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
- # Supported filters:
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] message_id The ID of the incoming CAN message.
134
- # @param [Integer, Range, Array<Integer>, nil] filter The filter to apply.
135
- # If nil, the method always returns true.
136
- # @return [Boolean] True if the message ID matches the filter, false otherwise.
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
- message_id == filter
141
- when Range
142
- filter.cover?(message_id)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CanMessenger
4
- VERSION = "0.2.2"
4
+ VERSION = "1.0.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: 0.2.2
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-01-17 00:00:00.000000000 Z
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: