Skip to content

notnil/canbus

Repository files navigation

canbus

Go Reference

Gopher CAN

An idiomatic, dependency-free Go library for working with Controller Area Network (CAN). The top-level module provides core CAN types and I/O, plus a small, composable canopen subpackage for common CANopen tasks.

  • Module import: github.com/notnil/canbus
  • CANopen helpers: github.com/notnil/canbus/canopen

What is CAN?

  • CAN (Controller Area Network) is a robust, real-time field bus used in automotive, robotics, and industrial control.
  • Frames carry up to 8 data bytes (classical CAN) with 11-bit (standard) or 29-bit (extended) identifiers.
  • Broadcast medium: every node can see all frames; filtering happens at the node.

What is CANopen?

  • CANopen is a higher-level protocol (CiA 301) standardized on top of CAN.
  • It defines services such as NMT (network management), Heartbeat (node status), EMCY (emergency), and SDO/PDO for configuration and process data.
  • This library implements practical, well-factored building blocks; it is not a full CANopen stack or object dictionary implementation.

Features

  • Core Frame type with validation, String() formatting, and binary marshal/unmarshal using Linux can_frame layout (16 bytes)
  • In-memory loopback bus for testing and simulation
  • Optional Linux SocketCAN driver (linux-only) implemented via raw syscalls
  • A lightweight Mux that fans-out frames to subscribers via filters
  • Zero external dependencies beyond the Go standard library
  • CANopen helpers:
    • COB-ID helpers and function code mapping
    • NMT build/parse utilities
    • Heartbeat (NMT error control) build/parse and subscription helper
    • EMCY encode/decode
    • SDO client supporting expedited (≤4 bytes) and segmented transfers, with typed read/write helpers

Requirements

  • Go 1.22+
  • Linux for SocketCAN (build tag is automatic on linux)

Install

go get github.com/notnil/canbus

Quick start (loopback)

package main

import (
    "fmt"

    "github.com/notnil/canbus"
)

func main() {
    bus := canbus.NewLoopbackBus()
    a := bus.Open()
    b := bus.Open()
    defer a.Close()
    defer b.Close()

    go func() { _ = a.Send(canbus.MustFrame(0x123, []byte("hi"))) }()

    f, err := b.Receive()
    if err != nil { panic(err) }
    fmt.Printf("%s\n", f.String()) // e.g., 123 [2] 68 69
}

Frames

  • canbus.Frame supports standard and extended identifiers, data/RTR, and length 0..8.
  • Binary helpers use Linux can_frame layout and are useful for capture or transport.
f := canbus.MustFrame(0x1ABCDEFF, []byte{0xDE, 0xAD})
fmt.Println(f.String()) // 1ABCDEFF [2] DE AD
b, _ := f.MarshalBinary()
var g canbus.Frame
_ = g.UnmarshalBinary(b)

Linux SocketCAN

  • Build tag: enabled automatically on linux (socketcan_linux.go).
  • Open a bus with an interface name (e.g., can0) using canbus.DialSocketCAN("can0").
  • Optionally configure loopback, own-message echo, and buffer sizes with DialSocketCANWithOptions.

Interface control (Linux)

  • The package includes small helpers to toggle a CAN interface up/down without external dependencies:
    • canbus.IsInterfaceUp("can0")
    • canbus.SetInterfaceUp("can0")
    • canbus.SetInterfaceDown("can0")
  • These call Linux ioctls (SIOCGIFFLAGS/SIOCSIFFLAGS) under the hood and require network admin privileges.

Configure CAN interface parameters (Linux)

  • You can set bitrate, restart-ms (auto bus-off recovery), and txqueuelen using a small helper that shells out to ip (iproute2):
// Bring interface down before changing bitrate/restart-ms on many drivers
_ = canbus.SetInterfaceDown("can0")

br := uint32(500000) // 500 kbit/s
rst := uint32(100)   // 100 ms auto-restart after bus-off
txq := 1024          // transmit queue length

err := canbus.ConfigureLinuxCANInterface("can0", canbus.LinuxCANInterfaceOptions{
    Bitrate:    &br,
    RestartMs:  &rst,
    TxQueueLen: &txq,
})
if err != nil { /* handle */ }

_ = canbus.SetInterfaceUp("can0")
  • Requires CAP_NET_ADMIN (or root). The helper invokes ip and returns combined stderr/stdout on failure.

Running unprivileged

  • Bringing interfaces up/down requires CAP_NET_ADMIN (or root). You can grant only this capability to your compiled binary:
sudo setcap cap_net_admin+ep /path/to/your-app
# verify
getcap /path/to/your-app
  • Alternatively, use sudo to run the program. Note that opening a CAN raw socket for I/O does not require elevated privileges, only interface state changes do.
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/notnil/canbus"
)

func main() {
    bus, err := canbus.DialSocketCAN("can0")
    if err != nil { log.Fatal(err) }
    defer bus.Close()

    // Send a frame
    if err := bus.Send(canbus.MustFrame(0x123, []byte{0xDE, 0xAD, 0xBE, 0xEF})); err != nil {
        log.Fatal(err)
    }

    // Receive frames (blocks until a frame is available)
    go func() {
        for {
            f, err := bus.Receive()
            if err != nil { return }
            fmt.Println(f.String())
        }
    }()

    time.Sleep(2 * time.Second)
}

SocketCAN options

opts := &canbus.SocketCANOptions{}

// Enable local loopback so tools like candump see TX frames (kernel default is on).
enable := true
opts.Loopback = &enable

// If you want your own socket to receive its transmitted frames (off by default):
opts.ReceiveOwnMessages = &enable

// Optionally enlarge buffers
opts.SendBufferBytes = 1 << 20
opts.ReceiveBufferBytes = 1 << 20

bus, err := canbus.DialSocketCANWithOptions("can0", opts)
if err != nil { log.Fatal(err) }
defer bus.Close()

Mux and filters

  • Mux owns a Bus for receiving, reads frames in a single goroutine, and fans out to subscribers using FrameFilters without blocking each other.
  • Use canbus filter helpers or your own FrameFilter functions.
bus := canbus.NewLoopbackBus()
rx := bus.Open()            // for Mux receive
sender := bus.Open()        // keep original for Send
mux := canbus.NewMux(rx)

a, cancel := mux.Subscribe(canbus.ByID(0x123), 8)
defer cancel()

_ = sender.Send(canbus.MustFrame(0x123, []byte{1,2,3}))
fmt.Println((<-a).String())

mux.Close()

Common filters

  • canbus.ByID, ByIDs, ByRange, ByMask
  • canbus.StandardOnly, ExtendedOnly, DataOnly, RTROnly
  • canbus.And, Or, Not for composition

CANopen

The canopen subpackage provides focused helpers for NMT, heartbeat, EMCY, and SDO (expedited and segmented). It also includes a synchronous SDO client designed to work with any canbus.Bus and a Mux.

Example: Heartbeat subscribe via mux

bus := canbus.NewLoopbackBus()
rx := bus.Open()
mux := canbus.NewMux(rx)

events, cancel := canopen.SubscribeHeartbeats(mux, nil, 8)
defer cancel()

// Simulate a heartbeat from node 0x05
hb := canopen.Heartbeat{Node: 0x05, State: canopen.StateOperational}
f, _ := hb.MarshalCANFrame()
_ = bus.Open().Send(f)

fmt.Printf("Heartbeat: %+v\n", <-events)

Example: SDO client (expedited and segmented)

// Assumes a CANopen server on node 0x22 present on the bus.
bus := canbus.NewLoopbackBus() // or a real bus (e.g., SocketCAN)
tx := bus.Open()               // client transmit endpoint
rx := bus.Open()               // client receive endpoint (owned by mux)
mux := canbus.NewMux(rx)
client := canopen.NewSDOClient(tx, 0x22, mux) // default: wait indefinitely, spec mode

// Write then read using expedited transfer (≤ 4 bytes)
_ = client.WriteU16(0x2000, 0x01, 0xBEEF)
v, err := client.ReadU16(0x2000, 0x01)
if err != nil { /* handle */ }
fmt.Printf("0x2000:01 = 0x%04X\n", v)

// Generic API
_ = client.Download(0x2001, 0x01, []byte("hello world")) // segmented if > 4 bytes
b, err := client.Upload(0x2001, 0x01)
_ = b; _ = err

Notes

  • The SDO client requires a non-nil Mux and uses it to wait for responses without racing other receivers.
  • Timeouts: pass WithTimeout(d) to NewSDOClient for bounded waits.
  • Classic expedited writes: use WithExpeditedMode(canopen.ExpeditedModeClassic) if your device expects 0x23/0x27/0x2B/0x2F command bytes.
  • Heartbeat and EMCY include marshal/unmarshal helpers and idiomatic types.

API reference

  • pkg.go.dev: https://pkg.go.dev/github.com/notnil/canbus and https://pkg.go.dev/github.com/notnil/canbus/canopen

Gopher artwork

  • The Go gopher was designed by Renée French. The design is licensed under the Creative Commons Attribution 3.0 License. See the Go gopher page and CC BY 3.0.

License MIT

About

A lightweight Golang library for working with CAN bus — send, receive, and parse CAN frames with ease.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages