can_messenger 0.2.3 → 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: 4fc9aa1a5061f03c93f08c98700ed0036218c2c6fe91f0a2c661522b3e86f36e
4
- data.tar.gz: 77dccf7d1b719254e253c86bfd9f31e193b7ae20c9902ec453dbb510ec6a8acb
3
+ metadata.gz: 7d26615bdfc66cb0379649692d151278aea8056338510b2d73f144f9d0fe0fe4
4
+ data.tar.gz: 1259bc700f8adf69e1de5f73cdd1dcbef783612b081be9757a31e918855e1b2a
5
5
  SHA512:
6
- metadata.gz: a52172045469c761c4dd7eac2d47d998e1cd236624cda4c1a75ed711499374e917259da1465d3b0323b18b99d800d0e53ce6578b9e86c582851d940933a969f3
7
- data.tar.gz: 76142086690c5542ee39afddfd3e54309bff19482ad1385b80092ce0c2998cba5181cf77ad101cc3614e029bcc79cc6ac0ef23e37bb95f44274b6d2274917108
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,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 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
- next if message.nil? # Skip if no message received
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 create_socket
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, [1, 0].pack("l_2"))
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] socket The CAN socket to read from.
94
- # @return [Hash, nil] Parsed message in the format { id: Integer, data: Array<Integer> },
95
- # or nil if no message is received or an error occurs.
96
- def receive_message(socket)
97
- frame = socket.recv(16)
98
- 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
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 [Hash, nil] Parsed message with :id and :data keys, or nil if the frame is incomplete.
112
- def parse_frame(frame)
113
- 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
114
157
 
115
158
  id = frame[0..3].unpack1("L>") & 0x1FFFFFFF
116
159
  data_length = frame[4].ord & 0x0F
117
- data = (frame[8, data_length].unpack("C*") if frame.size >= 8 + data_length)
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 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.
126
173
  #
127
- # Supported filters:
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] message_id The ID of the incoming CAN message.
133
- # @param [Integer, Range, Array<Integer>, nil] filter The filter to apply.
134
- # If nil, the method always returns true.
135
- # @return [Boolean] True if the message ID matches the filter, false otherwise.
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
- message_id == filter
140
- when Range
141
- filter.cover?(message_id)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CanMessenger
4
- VERSION = "0.2.3"
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.3
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-02-01 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: