├── .gitignore ├── Makefile ├── README.md ├── bus.go ├── bus_test.go ├── examples └── usbcananalyzer │ └── main.go ├── frame.go ├── go.mod ├── go.sum ├── transport.go └── transports ├── socketcan.go ├── usbcananalyzer.go └── usbcananalyzer_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/go,vim,osx 3 | # Edit at https://www.gitignore.io/?templates=go,vim,osx 4 | 5 | ### Go ### 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | ### Go Patch ### 23 | /vendor/ 24 | /Godeps/ 25 | 26 | ### OSX ### 27 | # General 28 | .DS_Store 29 | .AppleDouble 30 | .LSOverride 31 | 32 | # Icon must end with two \r 33 | Icon 34 | 35 | # Thumbnails 36 | ._* 37 | 38 | # Files that might appear in the root of a volume 39 | .DocumentRevisions-V100 40 | .fseventsd 41 | .Spotlight-V100 42 | .TemporaryItems 43 | .Trashes 44 | .VolumeIcon.icns 45 | .com.apple.timemachine.donotpresent 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | Network Trash Folder 51 | Temporary Items 52 | .apdisk 53 | 54 | ### Vim ### 55 | # Swap 56 | [._]*.s[a-v][a-z] 57 | [._]*.sw[a-p] 58 | [._]s[a-rt-v][a-z] 59 | [._]ss[a-gi-z] 60 | [._]sw[a-p] 61 | 62 | # Session 63 | Session.vim 64 | Sessionx.vim 65 | 66 | # Temporary 67 | .netrwhist 68 | *~ 69 | # Auto-generated tag files 70 | tags 71 | # Persistent undo 72 | [._]*.un~ 73 | 74 | # End of https://www.gitignore.io/api/go,vim,osx 75 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOBIN=go 2 | 3 | # Lint code with golint 4 | .PHONY: lint 5 | lint: 6 | go vet ./... 7 | golangci-lint run ./... 8 | 9 | # Clean 10 | .PHONY: clean 11 | clean: 12 | rm ./coverage.out 13 | 14 | .PHONY: test 15 | test: 16 | go test -v -count 1 -race --coverprofile=coverage.out ./... 17 | go tool cover -func=coverage.out 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-can 2 | 3 | [github.com/angelodlfrtr/go-can](https://github.com/angelodlfrtr/go-can) is a canbus golang library supporting multiple transports (serial adapater, socketcan, etc). 4 | 5 | **Does not support extended frames, feel free to create a PR** 6 | 7 | ## Installation 8 | 9 | ```bash 10 | go get github.com/angelodlfrtr/go-can 11 | ``` 12 | 13 | ## Basic usage 14 | 15 | ```go 16 | package main 17 | 18 | import ( 19 | "log" 20 | "time" 21 | 22 | "github.com/angelodlfrtr/go-can" 23 | "github.com/angelodlfrtr/go-can/transports" 24 | ) 25 | 26 | const TestPort string = "/dev/tty.usbserial-14140" 27 | 28 | func main() { 29 | // Configure transport 30 | tr := &transports.USBCanAnalyzer{ 31 | Port: TestPort, 32 | BaudRate: 2000000, 33 | } 34 | 35 | // Open bus 36 | bus := can.NewBus(tr) 37 | 38 | if err := bus.Open(); err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | // Write some frames 43 | 44 | log.Println("Write 10 frames") 45 | 46 | for i := 0; i < 9; i++ { 47 | frm := &can.Frame{ 48 | ArbitrationID: uint32(i), 49 | Data: [8]byte{0x00, 0X01, uint8(i)}, 50 | } 51 | 52 | if err := bus.Write(frm); err != nil { 53 | log.Fatal(err) 54 | } 55 | 56 | log.Printf("Frame %v writed", frm) 57 | } 58 | 59 | // Read frames during 60 | 61 | log.Println("Wait a frame (10s timeout)") 62 | timer := time.NewTimer(10 * time.Second) 63 | 64 | select { 65 | case frm := <-bus.ReadChan(): 66 | log.Println(frm) 67 | case <-timer.C: 68 | log.Println("Timeout") 69 | } 70 | 71 | if err := bus.Close(); err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | log.Println("done") 76 | } 77 | ``` 78 | 79 | ## License 80 | 81 | Copyright (c) 2019 The contributors 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining a copy 84 | of this software and associated documentation files (the "Software"), to deal 85 | in the Software without restriction, including without limitation the rights 86 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 87 | copies of the Software, and to permit persons to whom the Software is 88 | furnished to do so, subject to the following conditions: 89 | 90 | The above copyright notice and this permission notice shall be included in all 91 | copies or substantial portions of the Software. 92 | 93 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 94 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 95 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 96 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 97 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 98 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 99 | SOFTWARE. 100 | -------------------------------------------------------------------------------- /bus.go: -------------------------------------------------------------------------------- 1 | // Package can provide utils to connect to a can bus using hardware adapters system 2 | package can 3 | 4 | // Bus is the main interface to interact with the Transport 5 | type Bus struct { 6 | // Transport represent the "logical" communication layer 7 | // which can be socketcan on linux, a serial adapater, or your custom transport 8 | Transport Transport 9 | } 10 | 11 | // NewBus create a new Bus with given transport 12 | func NewBus(transport Transport) *Bus { 13 | return &Bus{Transport: transport} 14 | } 15 | 16 | // Open call Transport#Open 17 | func (bus *Bus) Open() error { 18 | return bus.Transport.Open() 19 | } 20 | 21 | // Close call Transport#Close 22 | func (bus *Bus) Close() error { 23 | return bus.Transport.Close() 24 | } 25 | 26 | // Write call Transport#Write 27 | func (bus *Bus) Write(frm *Frame) error { 28 | return bus.Transport.Write(frm) 29 | } 30 | 31 | // ReadChan call Transport#ReadChan 32 | func (bus *Bus) ReadChan() chan *Frame { 33 | return bus.Transport.ReadChan() 34 | } 35 | -------------------------------------------------------------------------------- /bus_test.go: -------------------------------------------------------------------------------- 1 | package can 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | // FakeTransport 9 | type FakeTransport struct { 10 | readChan chan *Frame 11 | } 12 | 13 | func (t *FakeTransport) Open() error { 14 | return nil 15 | } 16 | 17 | // Close a serial connection 18 | func (t *FakeTransport) Close() error { 19 | return nil 20 | } 21 | 22 | // Write a frame to serial connection 23 | func (t *FakeTransport) Write(frm *Frame) error { 24 | return nil 25 | } 26 | 27 | // ReadChan returns the read chan 28 | func (t *FakeTransport) ReadChan() chan *Frame { 29 | return t.readChan 30 | } 31 | 32 | func TestNewBus(t *testing.T) { 33 | tr := &FakeTransport{} 34 | bus := NewBus(tr) 35 | 36 | t.Log(*bus) 37 | } 38 | 39 | func TestOpen(t *testing.T) { 40 | tr := &FakeTransport{} 41 | bus := NewBus(tr) 42 | 43 | if err := bus.Open(); err != nil { 44 | t.Fatal(err) 45 | } 46 | } 47 | 48 | func TestWrite(t *testing.T) { 49 | tr := &FakeTransport{} 50 | bus := NewBus(tr) 51 | 52 | if err := bus.Open(); err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | frm := &Frame{ 57 | ArbitrationID: uint32(0x45), 58 | DLC: 6, 59 | Data: [8]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, 60 | } 61 | 62 | if err := bus.Write(frm); err != nil { 63 | t.Fatal(err) 64 | } 65 | } 66 | 67 | func TestRead(t *testing.T) { 68 | tr := &FakeTransport{} 69 | bus := NewBus(tr) 70 | 71 | if err := bus.Open(); err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | start := time.Now() 76 | timeout := 1 * time.Second 77 | ticker := time.NewTicker(timeout) 78 | 79 | for { 80 | select { 81 | case frm := <-bus.ReadChan(): 82 | t.Log(time.Since(start)) 83 | t.Log(frm) 84 | t.Log("") 85 | case <-ticker.C: 86 | t.Log("Timeout") 87 | return 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /examples/usbcananalyzer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/angelodlfrtr/go-can" 8 | "github.com/angelodlfrtr/go-can/transports" 9 | ) 10 | 11 | // TestPort contain serial path to test port 12 | const TestPort string = "/dev/tty.usbserial-14140" 13 | 14 | func main() { 15 | // Configure transport 16 | tr := &transports.USBCanAnalyzer{ 17 | Port: TestPort, 18 | BaudRate: 2000000, 19 | } 20 | 21 | // Open bus 22 | bus := can.NewBus(tr) 23 | 24 | if err := bus.Open(); err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | // Write some frames 29 | 30 | log.Println("Write 10 frames") 31 | 32 | for i := 0; i < 9; i++ { 33 | frm := &can.Frame{ 34 | ArbitrationID: uint32(i), 35 | Data: [8]byte{0x00, 0x01, uint8(i)}, 36 | } 37 | 38 | if err := bus.Write(frm); err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | log.Printf("Frame %v writed", frm) 43 | } 44 | 45 | // Read frames during 46 | 47 | log.Println("Wait a frame (10s timeout)") 48 | timer := time.NewTimer(10 * time.Second) 49 | 50 | select { 51 | case frm := <-bus.ReadChan(): 52 | log.Println(frm) 53 | case <-timer.C: 54 | log.Println("Timeout") 55 | } 56 | 57 | if err := bus.Close(); err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | log.Println("done") 62 | } 63 | -------------------------------------------------------------------------------- /frame.go: -------------------------------------------------------------------------------- 1 | package can 2 | 3 | // Frame represent a can frame 4 | type Frame struct { 5 | // ArbitrationID is the frame identifier 6 | ArbitrationID uint32 7 | 8 | // DLC represent the size of the data field 9 | DLC uint8 10 | 11 | // Data is the data to transmit in the frame 12 | Data [8]byte 13 | } 14 | 15 | // GetData read frame.DLC data from frame.Data 16 | func (frame *Frame) GetData() []byte { 17 | return frame.Data[0:frame.DLC] 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/angelodlfrtr/go-can 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/angelodlfrtr/serial v0.0.0-20190912094943-d028474db63c 7 | github.com/brutella/can v0.0.2 8 | ) 9 | 10 | require golang.org/x/sys v0.3.0 // indirect 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/angelodlfrtr/serial v0.0.0-20190912094943-d028474db63c h1:Q5NYSNKJn3QdfE4q7yG6m+r0OLFrsC94KDQPGqj5OkE= 2 | github.com/angelodlfrtr/serial v0.0.0-20190912094943-d028474db63c/go.mod h1:kGJNzwu4M6uVHZPXuE7m6+f3BvYrhFH76a90n69CxEk= 3 | github.com/brutella/can v0.0.2 h1:8TyjZrBZSwQwSr5x3U9KtKzGW8HNE/NpUgsNcYDAVIM= 4 | github.com/brutella/can v0.0.2/go.mod h1:NYDxbQito3w4+4DcjWs/fpQ3xyaFdpXw/KYqtZFU98k= 5 | golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 6 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= 7 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 8 | -------------------------------------------------------------------------------- /transport.go: -------------------------------------------------------------------------------- 1 | package can 2 | 3 | // Transport interface can be socketcan, an serial adapter, custom implementation, etc 4 | type Transport interface { 5 | // Open a connection 6 | Open() error 7 | 8 | // Close a connection 9 | Close() error 10 | 11 | // Write a frame to connection 12 | Write(*Frame) error 13 | 14 | // ReadChan return the channel for reading frames 15 | ReadChan() chan *Frame 16 | } 17 | -------------------------------------------------------------------------------- /transports/socketcan.go: -------------------------------------------------------------------------------- 1 | // Package transports contain code to load a bus interface from different hardware 2 | package transports 3 | 4 | import ( 5 | "github.com/angelodlfrtr/go-can" 6 | brutCan "github.com/brutella/can" 7 | ) 8 | 9 | // SocketCan define a socketcan connection to canbus 10 | type SocketCan struct { 11 | // Interface is the socket can interface to connect toa. eg : can0, vcan1, etc 12 | Interface string 13 | 14 | // bus is the can.Bus socket can connection 15 | bus *brutCan.Bus 16 | 17 | // busHandler handle can frame received 18 | busHandler brutCan.Handler 19 | 20 | // readChan 21 | readChan chan *can.Frame 22 | } 23 | 24 | // Open a socketcan connection 25 | func (t *SocketCan) Open() error { 26 | // Open socketcan connection 27 | bus, err := brutCan.NewBusForInterfaceWithName(t.Interface) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | go func() { 33 | if err := bus.ConnectAndPublish(); err != nil { 34 | panic(err) 35 | } 36 | }() 37 | 38 | t.readChan = make(chan *can.Frame) 39 | t.bus = bus 40 | 41 | // Create handler 42 | t.busHandler = brutCan.NewHandler(t.handleFrame) 43 | 44 | // Subcribe to incoming frames 45 | t.bus.Subscribe(t.busHandler) 46 | 47 | return nil 48 | } 49 | 50 | // Close a socketcan connection 51 | func (t *SocketCan) Close() error { 52 | // Unsubscribe for frames 53 | t.bus.Unsubscribe(t.busHandler) 54 | 55 | // Close read chan 56 | close(t.readChan) 57 | 58 | // Close connectino 59 | return t.bus.Disconnect() 60 | } 61 | 62 | // Write data to socketcan interface 63 | func (t *SocketCan) Write(frm *can.Frame) error { 64 | brutCanFrm := brutCan.Frame{ 65 | ID: frm.ArbitrationID, 66 | Length: frm.DLC, 67 | Flags: 0, 68 | Res0: 0, 69 | Res1: 0, 70 | Data: frm.Data, 71 | } 72 | 73 | return t.bus.Publish(brutCanFrm) 74 | } 75 | 76 | // ReadChan returns channel for reading frames 77 | func (t *SocketCan) ReadChan() chan *can.Frame { 78 | return t.readChan 79 | } 80 | 81 | // handleFrame handle incoming frames from socketcan interface 82 | // and send it to readChan 83 | func (t *SocketCan) handleFrame(brutFrm brutCan.Frame) { 84 | frm := &can.Frame{} 85 | 86 | frm.ArbitrationID = brutFrm.ID 87 | frm.DLC = brutFrm.Length 88 | frm.Data = brutFrm.Data 89 | 90 | t.readChan <- frm 91 | } 92 | -------------------------------------------------------------------------------- /transports/usbcananalyzer.go: -------------------------------------------------------------------------------- 1 | package transports 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "sync" 8 | "time" 9 | 10 | "github.com/angelodlfrtr/go-can" 11 | "github.com/angelodlfrtr/serial" 12 | ) 13 | 14 | // USBCanAnalyzer define a USBCanAnalyzer connection to canbus via serial connection on USB 15 | type USBCanAnalyzer struct { 16 | // Port is the serial port eg : COM0 on windows, /dev/ttytest on posix, etc 17 | Port string 18 | 19 | // BaudRate is the serial connection baud rate 20 | BaudRate int 21 | 22 | // client is the serial.Port instance 23 | client *serial.Port 24 | 25 | // dataBuf contain data received by serial connection 26 | dataBuf []byte 27 | 28 | // mutex to access dataBuf 29 | mutex sync.Mutex 30 | 31 | // readErr is set if listen encounter an error during the read, readErr is set 32 | readErr error 33 | 34 | // running is read goroutine running 35 | running bool 36 | 37 | // readChan is a chan for reading frames 38 | readChan chan *can.Frame 39 | } 40 | 41 | func (t *USBCanAnalyzer) run() { 42 | t.running = true 43 | t.readChan = make(chan *can.Frame) 44 | 45 | go func() { 46 | for { 47 | // Stop goroutine if t.running == false 48 | t.mutex.Lock() 49 | running := t.running 50 | t.mutex.Unlock() 51 | 52 | if !running { 53 | break 54 | } 55 | 56 | // 64 byte read buffer 57 | data := make([]byte, 64) 58 | 59 | // Read data (in a blocking way) 60 | n, err := t.client.Read(data) 61 | 62 | if errors.Is(err, io.EOF) { 63 | continue 64 | } 65 | 66 | t.readErr = err 67 | if err != nil { 68 | continue 69 | } 70 | 71 | // Append to global data buf 72 | t.mutex.Lock() 73 | t.dataBuf = append(t.dataBuf, data[:n]...) 74 | t.mutex.Unlock() 75 | 76 | // Publish frames on channel 77 | for { 78 | if ok := t.publishFrames(); !ok { 79 | break 80 | } 81 | } 82 | } 83 | }() 84 | } 85 | 86 | func (t *USBCanAnalyzer) publishFrames() bool { 87 | t.mutex.Lock() 88 | defer t.mutex.Unlock() 89 | 90 | // Find adapter start of frame 91 | for { 92 | // Stop if buffer is empty 93 | if len(t.dataBuf) == 0 { 94 | break 95 | } 96 | 97 | // Stop if found SOF 98 | if t.dataBuf[0] == 0xAA { 99 | break 100 | } 101 | 102 | // Remove one element from dataBuf and loop again 103 | t.dataBuf = t.dataBuf[1:] 104 | } 105 | 106 | // Check if data can contain an entire frame (min frame size is 5 in case of 0 data) 107 | // Else read serial 108 | // (SOF + 2 + DLC + EOF) = 5 109 | if len(t.dataBuf) < 5 { 110 | return false 111 | } 112 | 113 | // Create new frame 114 | frm := &can.Frame{} 115 | 116 | // DLC 117 | frm.DLC = t.dataBuf[1] - 0xC0 118 | 119 | // Check buffer len can contain a frame 120 | // else read serial 121 | if len(t.dataBuf) < 5+int(frm.DLC) { 122 | return false 123 | } 124 | 125 | // Validate frame 126 | // Check frame end with 0x55 127 | // The USB cananalyzer have bug and soemtimes returns wrong data fields 128 | if t.dataBuf[4+int(frm.DLC)] != 0x55 { 129 | // Ignore frame by juste removing the frame SOF 130 | // The frame will be ignored at next iteration 131 | t.dataBuf = t.dataBuf[1:] 132 | 133 | // @TODO: Maybe return an error here ? 134 | return false 135 | } 136 | 137 | // Arbitration ID 138 | frm.ArbitrationID = uint32(binary.LittleEndian.Uint16(t.dataBuf[2:])) 139 | 140 | // Data 141 | for i := 0; i < int(frm.DLC); i++ { 142 | frm.Data[i] = t.dataBuf[i+4] 143 | } 144 | 145 | // Resize t.dataBuf 146 | lastMsgLen := 1 + 1 + 2 + frm.DLC + 1 // 0xAA (SOF) + DLC + arbId + data + 0x55 (EOF) 147 | t.dataBuf = t.dataBuf[lastMsgLen:] 148 | 149 | // Publish frame 150 | t.readChan <- frm 151 | 152 | return true 153 | } 154 | 155 | // Open a serial connection 156 | // Show https://github.com/kobolt/usb-can/blob/master/canusb.c for protocol definition 157 | func (t *USBCanAnalyzer) Open() error { 158 | serialConfig := &serial.Config{ 159 | // Name of the serial port 160 | Name: t.Port, 161 | 162 | // Baud rate should normally be 2 000 000 163 | Baud: t.BaudRate, 164 | 165 | // ReadTimeout for the connection. If zero, the Read() operation is blocking 166 | // ReadTimeout: 100 * time.Millisecond, 167 | ReadTimeout: 0, 168 | 169 | // Size is 8 databytes for USBCanAnalyzer 170 | Size: 8, 171 | 172 | // StopBits is 1 for usbCanAnalyzer 173 | StopBits: 1, 174 | 175 | // Parity none for usbCanAnalyzer 176 | Parity: serial.ParityNone, 177 | } 178 | 179 | port, err := serial.OpenPort(serialConfig) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | t.client = port 185 | 186 | // Send initialization sequence (configure adapter) 187 | seq := []byte{ 188 | 0xAA, 189 | 0x55, 190 | 0x12, 191 | 0x07, 192 | 0x01, 193 | 0x00, 194 | 0x00, 195 | 0x00, 196 | 0x00, 197 | 0x00, 198 | 0x00, 199 | 0x00, 200 | 0x00, 201 | 0x00, 202 | 0x01, 203 | 0x00, 204 | 0x00, 205 | 0x00, 206 | 0x00, 207 | 0x1B, 208 | } 209 | 210 | if _, err := t.client.Write(seq); err != nil { 211 | return err 212 | } 213 | 214 | // Wait 500ms (else adapater crash) 215 | time.Sleep(500 * time.Millisecond) 216 | 217 | // Run reads from serial 218 | t.run() 219 | 220 | return nil 221 | } 222 | 223 | // Close a serial connection 224 | func (t *USBCanAnalyzer) Close() error { 225 | if t.client == nil { 226 | return nil 227 | } 228 | 229 | // Stop reading serial port 230 | t.mutex.Lock() 231 | t.running = false 232 | t.mutex.Unlock() 233 | 234 | close(t.readChan) 235 | 236 | return t.client.Close() 237 | } 238 | 239 | // Write a frame to serial connection 240 | func (t *USBCanAnalyzer) Write(frm *can.Frame) error { 241 | frmFullLen := 4 + int(frm.DLC) + 1 242 | data := make([]byte, frmFullLen) 243 | 244 | // 0xAA : adapter start of frame 245 | data[0] = 0xAA 246 | 247 | // DLC 248 | data[1] = 0xC0 | frm.DLC 249 | 250 | // Write arbitration id 251 | binary.LittleEndian.PutUint16(data[2:], uint16(frm.ArbitrationID)) 252 | 253 | // Append data 254 | for i := 0; i < int(frm.DLC); i++ { 255 | data[i+4] = frm.Data[i] 256 | } 257 | 258 | // Adapater end of frame 259 | data[frmFullLen-1] = 0x55 260 | 261 | _, err := t.client.Write(data) 262 | return err 263 | } 264 | 265 | // ReadChan returns the read chan 266 | func (t *USBCanAnalyzer) ReadChan() chan *can.Frame { 267 | return t.readChan 268 | } 269 | -------------------------------------------------------------------------------- /transports/usbcananalyzer_test.go: -------------------------------------------------------------------------------- 1 | package transports 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestOpen(t *testing.T) { 8 | // Configure connection 9 | tr := &USBCanAnalyzer{ 10 | Port: "/dev/tty.usbserial-14220", 11 | BaudRate: 2000000, 12 | } 13 | 14 | // Try to open connection 15 | if err := tr.Open(); err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | t.Log("Connection opened") 20 | } 21 | 22 | func TestClose(t *testing.T) { 23 | // Configure connection 24 | tr := &USBCanAnalyzer{ 25 | Port: "/dev/tty.usbserial-14220", 26 | BaudRate: 2000000, 27 | } 28 | 29 | // Try to open connection 30 | if err := tr.Open(); err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | if err := tr.Close(); err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | t.Log("Connection closed") 39 | } 40 | --------------------------------------------------------------------------------