CanMessenger
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.
Requirements
- Ruby 4.0.1 or higher.
- Docker (optional, for containerized development without installing Ruby locally).
Installation
To install can_messenger, add it to your application's Gemfile:
gem 'can_messenger'Then execute:
bundle installOr install it yourself with:
gem install can_messengerRubyGems page: https://rubygems.org/gems/can_messenger
Usage
Initializing the Messenger
To create a new instance of CanMessenger and start sending messages:
require 'can_messenger'
messenger = CanMessenger::Messenger.new(interface_name: 'can0')Sending CAN Messages
To send a message:
messenger.send_can_message(id: 0x123, data: [0xDE, 0xAD, 0xBE, 0xEF])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.
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:
messenger.send_can_message(id: 0x123456, data: [0x01, 0x02, 0x03], extended_id: true)If you need to work with CAN FD frames (up to 64 data bytes), enable the mode per call or when initializing the messenger:
messenger_fd = CanMessenger::Messenger.new(interface_name: 'can0', can_fd: true)
messenger_fd.send_can_message(id: 0x123, data: Array.new(12, 0xFF))
# Or on demand
messenger.send_can_message(id: 0x123, data: Array.new(12, 0xFF), can_fd: true)Receiving CAN Messages
To listen for incoming messages, set up a listener:
messenger.start_listening do |msg|
puts "Received ID=0x#{msg[:id].to_s(16)}, Extended=#{msg[:extended]}, Data=#{msg[:data]}"
endListening with Filters
The start_listening method supports filtering incoming messages based on CAN ID:
-
Single CAN ID:
messenger.start_listening(filter: 0x123) do |message| puts "Received filtered message: #{message}" end
-
Range of CAN IDs:
messenger.start_listening(filter: 0x100..0x200) do |message| puts "Received filtered message: #{message}" end
-
Array of CAN IDs:
messenger.start_listening(filter: [0x123, 0x456, 0x789]) do |message| puts "Received filtered message: #{message}" end
Working with DBC Files
Parse a DBC file and let the messenger encode and decode messages automatically:
dbc = CanMessenger::DBC.load('example.dbc')
# Encode using signal values
messenger.send_dbc_message(dbc: dbc, message_name: 'Example', signals: { Speed: 100 })
# Decode received frames
messenger.start_listening(dbc: dbc) do |msg|
if msg[:decoded]
puts "#{msg[:decoded][:name]} => #{msg[:decoded][:signals]}"
end
endIf the DBC message definition uses an extended CAN ID, send_dbc_message automatically sends it as an extended frame and start_listening(dbc: ...) decodes received extended frames back through the same DBC definition.
Stopping the Listener
To stop listening, use:
messenger.stop_listeningAdapters
CanMessenger::Messenger delegates low-level CAN bus operations to an adapter. By default it uses the
SocketCAN adapter which communicates with Linux CAN interfaces using raw sockets:
messenger = CanMessenger::Messenger.new(interface_name: "can0")You can provide a custom adapter via the adapter: option:
my_adapter = MyCustomAdapter.new(interface_name: "can0", logger: Logger.new($stdout))
messenger = CanMessenger::Messenger.new(interface_name: "can0", adapter: my_adapter)To build your own adapter, subclass CanMessenger::Adapter::Base and implement the required methods
open_socket, build_can_frame, receive_message, and parse_frame.
Important Considerations
Before using can_messenger, please note the following:
-
Environment Requirements:
- SocketCAN must be available on your Linux system.
- 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.
-
API Changes (v1.0.0 and later):
-
Keyword Arguments: The Messenger API now requires keyword arguments. For example, when initializing the Messenger:
messenger = CanMessenger::Messenger.new(interface_name: 'can0')
Similarly, methods like
send_can_messageuse named parameters:messenger.send_can_message(id: 0x123, data: [0xDE, 0xAD, 0xBE, 0xEF])
If upgrading from an earlier version, update your code accordingly.
-
Block Requirement for
start_listening:
Thestart_listeningmethod requires a block. If no block is provided, the method logs an error and exits without processing messages:messenger.start_listening do |message| puts "Received: #{message}" end
-
-
Threading & Socket Management:
-
Blocking Behavior: The gem uses blocking socket calls and continuously listens for messages. Manage the listener’s lifecycle appropriately, especially in multi-threaded environments. Always call
stop_listeningto gracefully shut down the listener. - Resource Cleanup: The socket is automatically closed when the listening loop terminates. Stop the listener to avoid resource leaks.
-
Blocking Behavior: The gem uses blocking socket calls and continuously listens for messages. Manage the listener’s lifecycle appropriately, especially in multi-threaded environments. Always call
-
Logging:
- Default Logger: If no logger is provided, logs go to standard output. Provide a custom logger if you want more control.
-
CAN Frame Format Assumptions:
- By default, the gem uses native endianness for CAN IDs (little-endian on most x86/ARM systems). Changed in v2.0.0: this default was previously
:big. You can override this by passingendianness: :bigorendianness: :little. - 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.
- By default, the gem uses native endianness for CAN IDs (little-endian on most x86/ARM systems). Changed in v2.0.0: this default was previously
Features
- Send CAN Messages: Send CAN messages (up to 8 data bytes, or 64 bytes with CAN FD enabled).
- Receive CAN Messages: Continuously listen for messages on a CAN interface.
- Filtering: Optional ID filters for incoming messages (single ID, range, or array).
- Logging: Logs errors and events for debugging/troubleshooting.
- DBC Parsing: Parse DBC files to encode messages by name and decode incoming frames.
Development
Docker-first workflow
Build the development image:
docker compose build appThe Ruby services automatically run bundle check || bundle install inside the container, so an existing bundle cache volume stays usable after dependency changes.
Run RuboCop:
docker compose run --rm lintRun the test suite:
docker compose run --rm testBuild the gem:
docker compose run --rm buildInstall docs dependencies (Docusaurus):
docker compose run --rm docs npm ciBuild the docs site:
docker compose run --rm docs npm run buildPreview docs locally:
docker compose run --rm --service-ports docs npm run startLocal Ruby workflow
If you already have Ruby installed locally, you can still use:
bin/setup
bundle exec rake test:rspecContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/fk1018/can_messenger.
License
The gem is available as open-source under the terms of the MIT License.
Author
Developed by fk1018.