205 lines
5.5 KiB
Go
205 lines
5.5 KiB
Go
package canrunner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
|
"github.com/fiskerinc/cloud-services/pkg/can-go/internal/clock"
|
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
|
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/socketcan"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
// defaultSendTimeout is the send timeout used for messages without a cycle time.
|
|
const defaultSendTimeout = time.Second
|
|
|
|
// Node is an interface for a CAN node to be run by the runner.
|
|
type Node interface {
|
|
sync.Locker
|
|
Connect() (net.Conn, error)
|
|
Descriptor() *descriptor.Node
|
|
TransmittedMessages() []TransmittedMessage
|
|
ReceivedMessage(id uint32) (ReceivedMessage, bool)
|
|
}
|
|
|
|
// TransmittedMessage is an interface for a message to be transmitted by the runner.
|
|
type TransmittedMessage interface {
|
|
generated.Message
|
|
// SetTransmitTime sets the time the message was last transmitted.
|
|
SetTransmitTime(time.Time)
|
|
// IsCyclicTransmissionEnabled returns true when cyclic transmission is enabled.
|
|
IsCyclicTransmissionEnabled() bool
|
|
// WakeUpChan returns a channel for waking up and checking if cyclic transmission is enabled.
|
|
WakeUpChan() <-chan struct{}
|
|
// TransmitEventChan returns channel for event-based transmission of the message.
|
|
TransmitEventChan() <-chan struct{}
|
|
// BeforeTransmitHook returns a function to be called before the message is transmitted.
|
|
//
|
|
// If the hook returns an error, the transmitter halt.
|
|
BeforeTransmitHook() func(context.Context) error
|
|
}
|
|
|
|
// ReceivedMessage is an interface for a message to be received by the runner.
|
|
type ReceivedMessage interface {
|
|
generated.Message
|
|
// SetReceiveTime sets the time the message was last received.
|
|
SetReceiveTime(time.Time)
|
|
// AfterReceiveHook returns a function to be called after the message has been received.
|
|
//
|
|
// If the hook returns an error, the receiver will halt.
|
|
AfterReceiveHook() func(context.Context) error
|
|
}
|
|
|
|
// FrameTransmitter is an interface for the the CAN frame transmitter used by the runner.
|
|
type FrameTransmitter interface {
|
|
TransmitFrame(context.Context, can.Frame) error
|
|
}
|
|
|
|
// FrameReceiver is an interface for the CAN frame receiver used by the runner.
|
|
type FrameReceiver interface {
|
|
Receive() bool
|
|
Frame() can.Frame
|
|
Err() error
|
|
}
|
|
|
|
func Run(ctx context.Context, n Node) error {
|
|
conn, err := n.Connect()
|
|
if err != nil {
|
|
return fmt.Errorf("run %s node: %w", n.Descriptor().Name, err)
|
|
}
|
|
g, ctx := errgroup.WithContext(ctx)
|
|
g.Go(func() error {
|
|
<-ctx.Done()
|
|
return conn.Close()
|
|
})
|
|
g.Go(func() error {
|
|
rx := socketcan.NewReceiver(conn)
|
|
return RunMessageReceiver(ctx, rx, n, clock.System())
|
|
})
|
|
for _, m := range n.TransmittedMessages() {
|
|
m := m
|
|
g.Go(func() error {
|
|
tx := socketcan.NewTransmitter(conn)
|
|
return RunMessageTransmitter(ctx, tx, n, m, clock.System())
|
|
})
|
|
}
|
|
if err := g.Wait(); err != nil {
|
|
if strings.Contains(err.Error(), "closed") {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("run %s node: %w", n.Descriptor().Name, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func RunMessageReceiver(ctx context.Context, rx FrameReceiver, n Node, c clock.Clock) error {
|
|
for rx.Receive() {
|
|
f := rx.Frame()
|
|
m, ok := n.ReceivedMessage(f.ID)
|
|
if !ok {
|
|
continue
|
|
}
|
|
n.Lock()
|
|
hook := m.AfterReceiveHook()
|
|
m.SetReceiveTime(c.Now())
|
|
err := m.UnmarshalFrame(f)
|
|
n.Unlock()
|
|
if err != nil {
|
|
return fmt.Errorf("receiver: %w", err)
|
|
}
|
|
if err := hook(ctx); err != nil {
|
|
return fmt.Errorf("receiver: %w", err)
|
|
}
|
|
}
|
|
if err := rx.Err(); err != nil {
|
|
return fmt.Errorf("receiver: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func RunMessageTransmitter(
|
|
ctx context.Context,
|
|
tx FrameTransmitter,
|
|
l sync.Locker,
|
|
m TransmittedMessage,
|
|
c clock.Clock,
|
|
) error {
|
|
sendTimeout := m.Descriptor().CycleTime
|
|
if sendTimeout == 0 {
|
|
sendTimeout = defaultSendTimeout
|
|
}
|
|
var cyclicTransmissionTicker *time.Ticker
|
|
var cyclicTransmissionTickChan <-chan time.Time
|
|
enableCyclicTransmission := func() {
|
|
isCyclic := m.Descriptor().SendType == descriptor.SendTypeCyclic
|
|
hasCycleTime := m.Descriptor().CycleTime > 0
|
|
if !isCyclic || !hasCycleTime || cyclicTransmissionTicker != nil {
|
|
return
|
|
}
|
|
cyclicTransmissionTicker = time.NewTicker(m.Descriptor().CycleTime)
|
|
cyclicTransmissionTickChan = cyclicTransmissionTicker.C
|
|
}
|
|
disableCyclicTransmission := func() {
|
|
if cyclicTransmissionTicker == nil {
|
|
return
|
|
}
|
|
cyclicTransmissionTicker.Stop()
|
|
cyclicTransmissionTicker = nil
|
|
}
|
|
setCyclicTransmission := func() {
|
|
l.Lock()
|
|
isCyclicTransmissionEnabled := m.IsCyclicTransmissionEnabled()
|
|
l.Unlock()
|
|
if isCyclicTransmissionEnabled {
|
|
enableCyclicTransmission()
|
|
} else {
|
|
disableCyclicTransmission()
|
|
}
|
|
}
|
|
transmit := func() error {
|
|
l.Lock()
|
|
hook := m.BeforeTransmitHook()
|
|
m.SetTransmitTime(c.Now())
|
|
l.Unlock()
|
|
if err := hook(ctx); err != nil {
|
|
return fmt.Errorf("%s transmitter: %w", m.Descriptor().Name, err)
|
|
}
|
|
l.Lock()
|
|
f := m.Frame()
|
|
l.Unlock()
|
|
ctx, cancel := context.WithTimeout(ctx, sendTimeout)
|
|
err := tx.TransmitFrame(ctx, f)
|
|
cancel()
|
|
if err != nil {
|
|
return fmt.Errorf("%s transmitter: %w", m.Descriptor().Name, err)
|
|
}
|
|
return nil
|
|
}
|
|
ctxDone := ctx.Done()
|
|
transmitEventChan := m.TransmitEventChan()
|
|
setCyclicTransmission()
|
|
wakeUpChan := m.WakeUpChan()
|
|
for {
|
|
select {
|
|
case <-ctxDone:
|
|
return nil
|
|
case <-wakeUpChan:
|
|
setCyclicTransmission()
|
|
case <-transmitEventChan:
|
|
if err := transmit(); err != nil {
|
|
return err
|
|
}
|
|
case <-cyclicTransmissionTickChan:
|
|
if err := transmit(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|