github.com/simpleiot/simpleiot@v0.18.3/client/can.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "net" 8 "os/exec" 9 "time" 10 11 "github.com/nats-io/nats.go" 12 "github.com/pkg/errors" 13 "github.com/simpleiot/canparse" 14 "github.com/simpleiot/simpleiot/data" 15 "go.einride.tech/can" 16 "go.einride.tech/can/pkg/socketcan" 17 ) 18 19 // CanBus represents a CAN socket config. The name matches the front-end node type "canBus" to link the two so 20 // that when a canBus node is created on the frontend the client manager knows to start a CanBus client. 21 type CanBus struct { 22 ID string `node:"id"` 23 Parent string `node:"parent"` 24 Description string `point:"description"` 25 Device string `point:"device"` 26 BitRate string `point:"bitRate"` 27 MsgsInDb int `point:"msgsInDb"` 28 SignalsInDb int `point:"signalsInDb"` 29 MsgsRecvdDb int `point:"msgsRecvdDb"` 30 MsgsRecvdDbReset bool `point:"msgsRecvdDbReset"` 31 MsgsRecvdOther int `point:"msgsRecvdOther"` 32 MsgsRecvdOtherReset bool `point:"msgsRecvdOtherReset"` 33 Databases []File `child:"file"` 34 } 35 36 // CanBusClient is a SIOT client used to communicate on a CAN bus 37 type CanBusClient struct { 38 nc *nats.Conn 39 config CanBus 40 stop chan struct{} 41 newPoints chan NewPoints 42 newEdgePoints chan NewPoints 43 wrSeq byte 44 lastSendStats time.Time 45 natsSub string 46 } 47 48 // NewCanBusClient returns a new CanBusClient with a NATS connection and a config 49 func NewCanBusClient(nc *nats.Conn, config CanBus) Client { 50 return &CanBusClient{ 51 nc: nc, 52 config: config, 53 stop: make(chan struct{}), 54 newPoints: make(chan NewPoints), 55 newEdgePoints: make(chan NewPoints), 56 wrSeq: 0, 57 lastSendStats: time.Time{}, 58 natsSub: SubjectNodePoints(config.ID), 59 } 60 } 61 62 // Run the main logic for this client and blocks until stopped 63 // There are several main aspects of the CAN bus client 64 // 65 // - the listener function is a process that recieves CAN bus frames from the 66 // Linux SocketCAN socket and sends the frames out on the canMsgRx channel 67 // 68 // - when a frame is recieved on the canMsgRx channel in the main loop, it is 69 // decoded and a point is sent out for each canparse.Signal in the frame. 70 // The key of each point contains the message name, signal name, and signal 71 // units 72 func (cb *CanBusClient) Run() error { 73 log.Println("CanBusClient: Starting CAN bus client:", cb.config.Description) 74 75 var db *canparse.Database = &canparse.Database{} 76 77 sendDbStats := func(msgs, signals int) { 78 points := data.Points{ 79 data.Point{ 80 Time: time.Now(), 81 Type: data.PointTypeMsgsInDb, 82 Value: float64(msgs), 83 }, 84 data.Point{ 85 Time: time.Now(), 86 Type: data.PointTypeSignalsInDb, 87 Value: float64(signals), 88 }, 89 } 90 91 err := SendPoints(cb.nc, cb.natsSub, points, false) 92 if err != nil { 93 log.Println(errors.Wrap(err, "CanBusClient: error CAN db stats: ")) 94 } 95 } 96 97 readDb := func() { 98 cb.config.MsgsInDb = 0 99 cb.config.SignalsInDb = 0 100 db.Clean() 101 for _, dbFile := range cb.config.Databases { 102 err := db.ReadBytes([]byte(dbFile.Data), dbFile.Name) 103 if err != nil { 104 log.Println(errors.Wrap(err, "CanBusClient: Error parsing database file")) 105 sendDbStats(0, 0) 106 return 107 } 108 for _, b := range db.Busses { 109 cb.config.MsgsInDb += len(b.Messages) 110 for _, m := range b.Messages { 111 cb.config.SignalsInDb += len(m.Signals) 112 /* 113 for _, s := range m.Signals { 114 log.Printf("CanBusClient: read msg %X sig %v: start=%v len=%v scale=%v offset=%v unit=%v", 115 m.Id, s.Name, s.Start, s.Length, s.Scale, s.Offset, s.Unit) 116 } 117 */ 118 } 119 } 120 } 121 sendDbStats(cb.config.MsgsInDb, cb.config.SignalsInDb) 122 } 123 124 readDb() 125 126 canMsgRx := make(chan can.Frame) 127 128 var ctx context.Context 129 var cancelContext context.CancelFunc 130 131 // setupDev bringDownDev must be called before every call of setupDev // 132 // except for the first call 133 setupDev := func() { 134 135 // Set up the socketCan interface 136 iface, err := net.InterfaceByName(cb.config.Device) 137 if err != nil { 138 log.Println(errors.Wrap(err, 139 "CanBusClient: socketCan interface not found")) 140 141 return 142 } 143 if iface.Flags&net.FlagUp == 0 { 144 err = exec.Command( 145 "ip", "link", "set", cb.config.Device, "up", "type", 146 "can", "bitrate", cb.config.BitRate).Run() 147 if err != nil { 148 log.Println( 149 errors.Wrap(err, fmt.Sprintf("CanBusClient: error bringing up socketCan interface with: device=%v, bitrate=%v", 150 cb.config.Device, cb.config.BitRate))) 151 152 } else { 153 log.Println( 154 "CanBusClient: bringing up socketCan interface with:", 155 cb.config.Device, cb.config.BitRate) 156 } 157 } 158 159 // Connect to the socketCan interface 160 ctx, cancelContext = context.WithCancel(context.Background()) 161 _ = cancelContext 162 conn, err := socketcan.DialContext(ctx, "can", cb.config.Device) 163 if err != nil { 164 log.Println(errors.Wrap(err, "CanBusClient: error dialing socketcan context")) 165 return 166 } 167 recv := socketcan.NewReceiver(conn) 168 169 // Listen on the socketCan interface 170 listener := func() { 171 for recv.Receive() { 172 frame := recv.Frame() 173 canMsgRx <- frame 174 } 175 } 176 go listener() 177 } 178 179 setupDev() 180 181 bringDownDev := func() { 182 if cancelContext != nil { 183 cancelContext() 184 } 185 } 186 187 for { 188 select { 189 case <-cb.stop: 190 log.Println("CanBusClient: stopping CAN bus client:", cb.config.Description) 191 bringDownDev() 192 return nil 193 194 case frame := <-canMsgRx: 195 196 // Decode the can message based on database 197 msg, err := canparse.DecodeMessage(frame, db) 198 if err != nil { 199 cb.config.MsgsRecvdOther++ 200 } else { 201 cb.config.MsgsRecvdDb++ 202 } 203 204 // Populate points representing the decoded CAN data 205 points := make(data.Points, len(msg.Signals)) 206 for i, sig := range msg.Signals { 207 points[i].Type = data.PointTypeValue 208 points[i].Key = fmt.Sprintf("%v.%v[%v]", 209 msg.Name, sig.Name, sig.Unit) 210 points[i].Time = time.Now() 211 points[i].Value = float64(sig.Value) 212 } 213 214 // Populate points to update CAN client stats 215 points = append(points, 216 data.Point{ 217 Time: time.Now(), 218 Type: data.PointTypeMsgsRecvdDb, 219 Value: float64(cb.config.MsgsRecvdDb), 220 }) 221 points = append(points, 222 data.Point{ 223 Time: time.Now(), 224 Type: data.PointTypeMsgsRecvdOther, 225 Value: float64(cb.config.MsgsRecvdOther), 226 }) 227 228 // Send the points 229 if len(points) > 0 { 230 err = SendPoints(cb.nc, cb.natsSub, points, false) 231 if err != nil { 232 log.Println(errors.Wrap(err, "CanBusClient: error sending points received from CAN bus: ")) 233 } 234 } 235 236 case pts := <-cb.newPoints: 237 err := data.MergePoints(pts.ID, pts.Points, &cb.config) 238 if err != nil { 239 log.Println("CanBusClient: error merging new points:", err) 240 } 241 242 // Update CAN devices and databases with new information 243 for _, p := range pts.Points { 244 switch p.Type { 245 case data.PointTypeDevice: 246 bringDownDev() 247 setupDev() 248 case data.PointTypeData: 249 readDb() 250 case data.PointTypeDisabled: 251 if p.Value == 0 { 252 bringDownDev() 253 } 254 } 255 } 256 257 // Reset db msgs received counter 258 if cb.config.MsgsRecvdDbReset { 259 points := data.Points{ 260 {Time: time.Now(), Type: data.PointTypeMsgsRecvdDb, Value: 0}, 261 {Time: time.Now(), Type: data.PointTypeMsgsRecvdDbReset, Value: 0}, 262 } 263 err = SendPoints(cb.nc, cb.natsSub, points, false) 264 if err != nil { 265 log.Println("Error resetting CAN message received count:", err) 266 } 267 268 cb.config.MsgsRecvdDbReset = false 269 cb.config.MsgsRecvdDb = 0 270 } 271 272 // Reset other msgs received counter 273 if cb.config.MsgsRecvdOtherReset { 274 points := data.Points{ 275 {Time: time.Now(), Type: data.PointTypeMsgsRecvdOther, Value: 0}, 276 {Time: time.Now(), Type: data.PointTypeMsgsRecvdOtherReset, Value: 0}, 277 } 278 err = SendPoints(cb.nc, cb.natsSub, points, false) 279 if err != nil { 280 log.Println("Error resetting CAN message received count:", err) 281 } 282 283 cb.config.MsgsRecvdOtherReset = false 284 cb.config.MsgsRecvdOther = 0 285 } 286 287 case pts := <-cb.newEdgePoints: 288 err := data.MergeEdgePoints(pts.ID, pts.Parent, pts.Points, &cb.config) 289 if err != nil { 290 log.Println("CanBusClient: error merging new points:", err) 291 } 292 293 // TODO need to send edge points to CAN bus, not implemented yet 294 } 295 } 296 } 297 298 // Stop sends a signal to the Run function to exit 299 func (cb *CanBusClient) Stop(_ error) { 300 close(cb.stop) 301 } 302 303 // Points is called by the Manager when new points for this 304 // node are received. 305 func (cb *CanBusClient) Points(nodeID string, points []data.Point) { 306 cb.newPoints <- NewPoints{nodeID, "", points} 307 } 308 309 // EdgePoints is called by the Manager when new edge points for this 310 // node are received. 311 func (cb *CanBusClient) EdgePoints(nodeID, parentID string, points []data.Point) { 312 cb.newEdgePoints <- NewPoints{nodeID, parentID, points} 313 }