237 lines
6.3 KiB
Go
237 lines
6.3 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"src.dualinventive.com/go/dinet"
|
|
"src.dualinventive.com/go/dinet/rpc"
|
|
"src.dualinventive.com/go/lib/dilog"
|
|
)
|
|
|
|
// HeartbeatInterval is the heartbeat interval for the services
|
|
const HeartbeatInterval = time.Minute
|
|
|
|
// Compile-time interface validation
|
|
var _ dinet.Device = &Device{}
|
|
var _ dinet.ParentDevice = &Device{}
|
|
|
|
// Device is a service device
|
|
type Device struct {
|
|
uid string
|
|
conn dinet.DeviceConn
|
|
router *dinet.Router
|
|
logger dilog.Logger
|
|
endpoint string
|
|
appVersion string
|
|
|
|
childrenLock sync.RWMutex
|
|
children map[string]dinet.Device
|
|
}
|
|
|
|
// NewDevice creates a new device which is a service. BackendURI is the URI of SMP to connect to
|
|
func NewDevice(logger dilog.Logger, backendURI string, devPrefix string, appVersion string) (*Device, error) {
|
|
con, err := dinet.NewDeviceConn(dinet.TransportLowlevel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = con.Connect(backendURI); err != nil {
|
|
return nil, err
|
|
}
|
|
uid, err := GenerateUIDFromMac(devPrefix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dLogger := logger.WithField("device:uid", uid)
|
|
dLogger.Info("authenticating at SMP")
|
|
if err = con.DeviceHandshake(uid); err != nil {
|
|
dLogger.WithError(err).Error("handshake failed")
|
|
if err2 := con.Close(); err2 != nil {
|
|
dLogger.WithError(err2).Error("failed to close the connection")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
rtr, err := dinet.NewRouter(logger, con)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dev := &Device{
|
|
uid: uid,
|
|
conn: con,
|
|
router: rtr,
|
|
logger: dLogger,
|
|
endpoint: backendURI,
|
|
appVersion: appVersion,
|
|
children: make(map[string]dinet.Device),
|
|
}
|
|
rtr.Subscribe(uid, rpc.ClassMethodDeviceData, rpc.MsgTypeRequest, dev.requestDeviceData)
|
|
rtr.Subscribe(uid, rpc.ClassMethodDeviceInfo, rpc.MsgTypeRequest, dev.deviceInfo)
|
|
rtr.Subscribe(uid, rpc.ClassMethodConnectionInfo, rpc.MsgTypeRequest, dev.connectionInfo)
|
|
dev.startBackgroundTasks()
|
|
return dev, nil
|
|
}
|
|
|
|
func (d *Device) startBackgroundTasks() {
|
|
ticker := time.NewTicker(HeartbeatInterval)
|
|
go func() {
|
|
for range ticker.C {
|
|
err := d.sendDeviceData()
|
|
if err != nil {
|
|
d.logger.Fatal("device ping failed")
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
go func() {
|
|
if err := d.router.ListenAndServe(); err != nil {
|
|
d.logger.WithError(err).Error("service: listenAndServe failed")
|
|
}
|
|
err := d.Close()
|
|
if err != nil {
|
|
d.logger.WithError(err).Error("service: cannot close device")
|
|
}
|
|
}()
|
|
|
|
}
|
|
|
|
// RegisterUID registers the uid on the connection (dinetlowlevel message type Register)
|
|
// DONT use this function, this is for internal use only. Use childdevice.Handshake()
|
|
func (d *Device) RegisterUID(uid string) error {
|
|
return d.conn.DeviceRegister(uid)
|
|
}
|
|
|
|
// UnregisterUID unregisters the uid on the connection (dinetlowlevel message type Unregister)
|
|
// DONT use this function, this is for internal use only. Use childdevice.Close()
|
|
func (d *Device) UnregisterUID(uid string) error {
|
|
d.router.UnsubscribeDevice(uid)
|
|
return d.conn.DeviceUnregister(uid)
|
|
}
|
|
|
|
// RegisterDevice registers a new device on behalf of the current device. The children will be updated with the given
|
|
// device
|
|
// DONT use this function, this is for internal use only. Use childdevice.Handshake()
|
|
func (d *Device) RegisterDevice(device dinet.Device) error {
|
|
d.childrenLock.Lock()
|
|
d.children[device.UID()] = device
|
|
d.childrenLock.Unlock()
|
|
return d.conn.DeviceRegister(device.UID())
|
|
}
|
|
|
|
// UnregisterDevice unregisters a device on behalf of the current device. The children will be updated with the given
|
|
// device
|
|
// DONT use this function, this is for internal use only. Use childdevice.Close()
|
|
func (d *Device) UnregisterDevice(uid string) error {
|
|
d.childrenLock.Lock()
|
|
delete(d.children, uid)
|
|
d.childrenLock.Unlock()
|
|
return d.conn.DeviceUnregister(uid)
|
|
}
|
|
|
|
// Logger returns the logger
|
|
func (d *Device) Logger() dilog.Logger {
|
|
return d.logger
|
|
}
|
|
|
|
// send transfers a message for this device to the remote host
|
|
func (d *Device) send(m *rpc.Msg) error {
|
|
m.DeviceUID = d.uid
|
|
return d.router.Send(m)
|
|
}
|
|
|
|
// Router returns the router
|
|
func (d *Device) Router() *dinet.Router {
|
|
return d.router
|
|
}
|
|
|
|
// requestDeviceData is the callback for device:data
|
|
func (d *Device) requestDeviceData(req *rpc.Msg) *rpc.Msg {
|
|
rep := req.CreateReply()
|
|
rep.Result = rpc.NewResult([]rpc.DeviceData{{
|
|
State: rpc.DeviceStateIdle,
|
|
Error: false,
|
|
}})
|
|
return rep
|
|
}
|
|
|
|
// deviceInfo is the callback for device:info
|
|
func (d *Device) deviceInfo(req *rpc.Msg) *rpc.Msg {
|
|
rep := req.CreateReply()
|
|
rep.Result = rpc.NewResult([]rpc.DeviceInfo{{
|
|
Type: rpc.DeviceTypeService,
|
|
Version: rpc.VersionMap{rpc.VersionKeyFwService: d.appVersion},
|
|
}})
|
|
return rep
|
|
}
|
|
|
|
// connectionInfo is the callback for connection:info
|
|
func (d *Device) connectionInfo(req *rpc.Msg) *rpc.Msg {
|
|
rep := req.CreateReply()
|
|
rep.Result = rpc.NewResult([]rpc.ConnectionInfo{{
|
|
Timeout: uint32(HeartbeatInterval.Seconds()),
|
|
Transport: rpc.ConnectionTransportService,
|
|
Service: &rpc.ConnectionInfoService{Endpoint: d.endpoint},
|
|
}})
|
|
return rep
|
|
}
|
|
|
|
// sendDeviceData publishes the device:data
|
|
func (d *Device) sendDeviceData() error {
|
|
return d.send(&rpc.Msg{
|
|
ClassMethod: rpc.ClassMethodDeviceData,
|
|
Type: rpc.MsgTypePublish,
|
|
Result: rpc.NewResult([]rpc.DeviceData{{
|
|
State: rpc.DeviceStateIdle,
|
|
Error: false,
|
|
}}),
|
|
})
|
|
}
|
|
|
|
// UID returns the UID of this service device
|
|
func (d *Device) UID() string {
|
|
return d.uid
|
|
}
|
|
|
|
// Search will search for the UID in the tree below this device
|
|
func (d *Device) Search(uid string) (dinet.Device, error) {
|
|
if uid == d.UID() {
|
|
return d, nil
|
|
}
|
|
|
|
d.childrenLock.RLock()
|
|
defer d.childrenLock.RUnlock()
|
|
// First find via the direct children (fastest solution)
|
|
for pUID, dev := range d.children {
|
|
if pUID == uid {
|
|
return dev, nil
|
|
}
|
|
}
|
|
// Not found als direct child. Now find in the sub children
|
|
for _, dev := range d.children {
|
|
pDev, ok := dev.(dinet.ParentDevice)
|
|
if !ok {
|
|
continue
|
|
}
|
|
fDev, err := pDev.Search(uid)
|
|
if err == nil {
|
|
return fDev, nil
|
|
}
|
|
}
|
|
return nil, errors.New("not found")
|
|
}
|
|
|
|
// Close closes all the children and closes the connection to SMP
|
|
func (d *Device) Close() error {
|
|
d.childrenLock.RLock()
|
|
for _, c := range d.children {
|
|
if err := c.Close(); err != nil {
|
|
d.logger.WithError(err).WithField("child", c.UID()).Error("cannot close child")
|
|
}
|
|
}
|
|
d.childrenLock.RUnlock()
|
|
return d.conn.Close()
|
|
}
|