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 install
Or install it yourself with:
gem install can_messenger
RubyGems 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.(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. The SocketCAN adapter builds the Linux socket address itself, so it does not depend on Ruby exposingCAN_RAWorpack_sockaddr_can.
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.(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.(id: 0x123, data: Array.new(12, 0xFF))
# Or on demand
messenger.(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]}"
end
Listening with Filters
The start_listening method supports filtering incoming messages based on CAN ID:
Unsupported filter values now raise ArgumentError. Accepted values are nil, a single Integer, an Integer Range, or an Array<Integer>.
- Single CAN ID:
messenger.start_listening(filter: 0x123) do ||
puts "Received filtered message: #{}"
end
- Range of CAN IDs:
messenger.start_listening(filter: 0x100..0x200) do ||
puts "Received filtered message: #{}"
end
- Array of CAN IDs:
messenger.start_listening(filter: [0x123, 0x456, 0x789]) do ||
puts "Received filtered 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.(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]}"
elsif msg[:decode_error]
warn "DBC decode failed: #{msg[:decode_error][:class]} - #{msg[:decode_error][:message]}"
end
end
If 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.
If DBC decoding raises because the payload does not match the signal definition, the raw frame is still yielded and includes decode_error: { class:, message: }.
Malformed DBC files now fail fast with ArgumentError that includes the line number and offending line text. Standard metadata directives such as VERSION, NS_, BU_, CM_, VAL_, and BA_* are still ignored.
Stopping the Listener
To stop listening, use:
messenger.stop_listening
Adapters
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.(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:ruby 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.
- DBC Decode Errors: When
start_listening(dbc: ...)cannot decode a frame, the raw frame is still yielded and a structureddecode_errorhash is attached.
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 app
The 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 lint
Run the test suite:
docker compose run --rm test
Build the gem:
docker compose run --rm build
Warm docs dependency cache (optional):
docker compose run --rm docs npm ci
Build the docs site:
docker compose run --rm docs sh -lc "npm ci && npm run build"
Preview docs locally:
docker compose up docs
Local Ruby workflow
If you already have Ruby installed locally, you can still use:
bin/setup
bundle exec rake test:rspec
Contributing
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.