Files
cloud-services/pkg/can-go/pkg/canrunner/run.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
}
}
}
}