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  }