Skip to content

Commit

Permalink
feat: Build new USBCANChannel to go alongside renamed SocketCANChannel
Browse files Browse the repository at this point in the history
  • Loading branch information
deregtd committed Jun 15, 2024
1 parent b0fb00e commit 9a67e56
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 58 deletions.
15 changes: 15 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run USBCANTest",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "cmd/usbcantest/usbcantest.go"
}
]
}
26 changes: 26 additions & 0 deletions cmd/usbcantest/usbcantest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"context"
"fmt"

"github.com/boatkit-io/tugboat/pkg/canbus"
"github.com/brutella/can"
"github.com/sirupsen/logrus"
)

func main() {
ctx := context.Background()
options := canbus.USBCANChannelOptions{
SerialInterfaceName: "tty.usbserial-210",
SerialBaudRate: 2000000,
BitRate: 250000,
FrameHandler: func(frame can.Frame) {
fmt.Printf("handled frame: %+v\n", frame)
},
}
c := canbus.NewUSBCANChannel(logrus.StandardLogger(), options)
if err := c.Run(ctx); err != nil {
fmt.Printf("error: %v\n", err)
}
}
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/vishvananda/netlink v1.2.1-beta.2.0.20221214185949-378a404a26f0
go.bug.st/serial v1.6.2
)

require (
Expand All @@ -20,4 +21,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require golang.org/x/sys v0.21.0 // indirect
require (
github.com/creack/goselect v0.1.2 // indirect
golang.org/x/sys v0.21.0 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/brutella/can v0.0.2 h1:8TyjZrBZSwQwSr5x3U9KtKzGW8HNE/NpUgsNcYDAVIM=
github.com/brutella/can v0.0.2/go.mod h1:NYDxbQito3w4+4DcjWs/fpQ3xyaFdpXw/KYqtZFU98k=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -20,6 +22,8 @@ github.com/vishvananda/netlink v1.2.1-beta.2.0.20221214185949-378a404a26f0/go.mo
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
14 changes: 14 additions & 0 deletions pkg/canbus/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package canbus

import (
"context"

"github.com/brutella/can"
)

// Interface is a basic interface for a CANbus implementation
type Interface interface {
Run(ctx context.Context) error
Close() error
WriteFrame(frame can.Frame) error
}
23 changes: 0 additions & 23 deletions pkg/canbus/options.go

This file was deleted.

67 changes: 33 additions & 34 deletions pkg/canbus/channel.go → pkg/canbus/socketcan.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,43 @@ import (
"github.com/vishvananda/netlink"
)

// Channel represents a single canbus channel for sending/receiving CAN frames
type Channel struct {
bitRate int32
ChannelOptions ChannelOptions
// SocketCANChannelOptions is a type that contains required options on a SocketCANChannel.
type SocketCANChannelOptions struct {
InterfaceName string
BitRate int
ForceBounceInterface bool
MessageHandler can.HandlerFunc
}

// SocketCANChannel represents a single canbus channel for sending/receiving CAN frames
type SocketCANChannel struct {
options SocketCANChannelOptions

bus *can.Bus
busHandler can.Handler

log *logrus.Logger
}

// NewChannel returns a Channel object based on the given options. ChannelOptions are required settings, and then you can optionally add
// more ChannelOption objects for various optional options.
func NewChannel(log *logrus.Logger, ChannelOptions ChannelOptions, opts ...ChannelOption) *Channel {
c := Channel{
ChannelOptions: ChannelOptions,
log: log,
}

// Apply defaults
c.bitRate = DefaultBitRate

// Apply functional options.
for i := range opts {
opts[i](&c)
// NewSocketCANChannel returns a Channel object based on SocketCAN and the given options. ChannelOptions are required settings.
func NewSocketCANChannel(log *logrus.Logger, options SocketCANChannelOptions) *SocketCANChannel {
c := SocketCANChannel{
options: options,
log: log,
}

return &c
}

// Run opens the canbus channel and starts listening. This will also, as needed, use netlink to actually call into the OS
// to start the channel and/or set the bitrate, as needed.
func (c *Channel) Run(ctx context.Context) error {
func (c *SocketCANChannel) Run(ctx context.Context) error {
// Referencing https://github.com/angelodlfrtr/go-can/blob/master/transports/socketcan.go

// Use netlink to make sure the interface is up
link, err := netlink.LinkByName(c.ChannelOptions.CanInterfaceName)
link, err := netlink.LinkByName(c.options.InterfaceName)
if err != nil {
return fmt.Errorf("no link found for %v: %v", c.ChannelOptions.CanInterfaceName, err)
return fmt.Errorf("no link found for %v: %v", c.options.InterfaceName, err)
}

if link.Type() != "can" {
Expand All @@ -62,16 +60,16 @@ func (c *Channel) Run(ctx context.Context) error {

if canLink.Attrs().OperState == netlink.OperUp {
bounce := false
if canLink.BitRate != uint32(c.bitRate) {
if canLink.BitRate != uint32(c.options.BitRate) {
c.log.WithField("bitRate", canLink.BitRate).Info("Channel currently has wrong bitrate, bringing down")
bounce = true
} else if c.ChannelOptions.ForceBounceInterface {
} else if c.options.ForceBounceInterface {
c.log.Info("Bouncing channel")
bounce = true
}

if bounce {
cmd := exec.CommandContext(ctx, "ip", "link", "set", c.ChannelOptions.CanInterfaceName, "down")
cmd := exec.CommandContext(ctx, "ip", "link", "set", c.options.InterfaceName, "down")
if output, err := cmd.Output(); err != nil {
logBase := c.log.WithField("cmd", strings.Join(cmd.Args, " ")).WithField("output", string(output))
if errCast, worked := err.(*exec.ExitError); worked {
Expand All @@ -82,20 +80,21 @@ func (c *Channel) Run(ctx context.Context) error {
}

// Re-fetch info
link, err = netlink.LinkByName(c.ChannelOptions.CanInterfaceName)
link, err = netlink.LinkByName(c.options.InterfaceName)
if err != nil {
return fmt.Errorf("no link found for %v: %v", c.ChannelOptions.CanInterfaceName, err)
return fmt.Errorf("no link found for %v: %v", c.options.InterfaceName, err)
}

canLink = link.(*netlink.Can)
}
}

if canLink.Attrs().OperState == netlink.OperDown {
c.log.WithField("canName", c.ChannelOptions.CanInterfaceName).WithField("bitRate", c.bitRate).Info("Link is down, bringing up link")
c.log.WithField("canName", c.options.InterfaceName).WithField("bitRate", c.options.BitRate).Info("Link is down, bringing up link")

// ip link set can1 up type can bitrate 250000
cmd := exec.CommandContext(ctx, "ip", "link", "set", c.ChannelOptions.CanInterfaceName, "up", "type", "can", "bitrate", strconv.Itoa(int(c.bitRate)))
cmd := exec.CommandContext(ctx, "ip", "link", "set", c.options.InterfaceName, "up", "type", "can", "bitrate",
strconv.Itoa(int(c.options.BitRate)))
if output, err := cmd.Output(); err != nil {
logBase := c.log.WithField("cmd", strings.Join(cmd.Args, " ")).WithField("output", string(output))
if errCast, worked := err.(*exec.ExitError); worked {
Expand All @@ -107,24 +106,24 @@ func (c *Channel) Run(ctx context.Context) error {
}

// Open the brutella can bus
bus, err := can.NewBusForInterfaceWithName(c.ChannelOptions.CanInterfaceName)
bus, err := can.NewBusForInterfaceWithName(c.options.InterfaceName)
if err != nil {
return err
}

c.bus = bus
c.busHandler = can.NewHandler(c.ChannelOptions.MessageHandler)
c.busHandler = can.NewHandler(c.options.MessageHandler)
c.bus.Subscribe(c.busHandler)

c.log.WithField("canName", c.ChannelOptions.CanInterfaceName).
Info("opened connection and listening")
c.log.WithField("interfaceName", c.options.InterfaceName).
Info("Opened SocketCAN and listening")

// Start listening for messages
return bus.ConnectAndPublish()
}

// Close shuts down the channel
func (c *Channel) Close() error {
func (c *SocketCANChannel) Close() error {
if c.bus == nil {
return nil
}
Expand All @@ -138,6 +137,6 @@ func (c *Channel) Close() error {
}

// WriteFrame will send a CAN frame to the channel
func (c *Channel) WriteFrame(frame can.Frame) error {
func (c *SocketCANChannel) WriteFrame(frame can.Frame) error {
return c.bus.Publish(frame)
}
Loading

0 comments on commit 9a67e56

Please sign in to comment.