src.dualinventive.com/go/cp3000-interface/internal/device/base.go

201 lines
5.3 KiB
Go

package device
import (
"errors"
"fmt"
"strconv"
"sync"
"time"
"src.dualinventive.com/go/cp3000-interface/internal/statusupdate"
"src.dualinventive.com/go/cp3000-interface/internal/storage"
"src.dualinventive.com/go/dinet"
"src.dualinventive.com/go/dinet/rpc"
"src.dualinventive.com/go/lib/cp3000"
"src.dualinventive.com/go/lib/dilog"
)
// DefaultCommandTimeout for CP3000 request-reply
//nolint: gochecknoglobals
var DefaultCommandTimeout = time.Second * 10
// Base is a CP3000 base-device
type Base struct {
uid string
devType rpc.DeviceType
client cp3000.Router
dev *dinet.ChildDevice
decoder statusupdate.Decoder
hwVersion string
databaseID uint32
repo storage.Repository
commandTimeout time.Duration
clientMu sync.Mutex
}
func newBaseDevice(r cp3000.Router, repo storage.Repository, hwVersion string, databaseID int) (*Base, error) {
b := &Base{
client: r,
decoder: nil,
hwVersion: hwVersion,
databaseID: uint32(databaseID),
repo: repo,
commandTimeout: DefaultCommandTimeout,
}
uid, err := rpc.GenerateDeviceUID(rpc.DeviceUIDPrefixLegacy, strconv.Itoa(databaseID))
if err != nil {
return nil, err
}
b.uid = uid
return b, nil
}
func (z *Base) cp3000ReqRep(timeout time.Duration, cmd cp3000.Command, args ...string) (*cp3000.Msg, rpc.ErrorCode) {
z.clientMu.Lock()
defer z.clientMu.Unlock()
if err := z.client.Send(cmd, args...); err != nil {
z.dev.Logger().WithError(err).Error("Error sending to the device...")
return nil, rpc.EBackendCp3000CommError
}
rep, err := z.client.ReceiveReply(timeout)
if err != nil {
z.dev.Logger().WithError(err).Error("Error receiving from device...")
if err == cp3000.ErrTimeout {
return nil, rpc.ETimeout
}
return nil, rpc.EBackendCp3000CommError
}
return rep, rpc.Ok
}
func (z *Base) requestValue(field string) (string, rpc.ErrorCode) {
msg, rpcerr := z.cp3000ReqRep(z.commandTimeout, cp3000.CommandRetrieve, field)
if rpcerr != rpc.Ok {
z.dev.Logger().WithField("field", field).Error("cannot get field")
return "", rpcerr
}
if msg.Command != "00" {
z.dev.Logger().WithField("cp3000-code", msg.Command).Error("cp3000 error")
return "", rpc.EBackendCp3000CommError
}
return msg.Params[0], rpc.Ok
}
func (z *Base) requestVersion(field string) (string, rpc.ErrorCode) {
return z.requestValue(fmt.Sprintf("VERSION[%s]", field))
}
func (z *Base) updateError(active bool, errDescription string, rpcErr rpc.ErrorCode) bool {
var changed bool
if active {
changed = z.dev.SetError(rpcErr)
} else {
changed = z.dev.ClearError(rpcErr)
}
if changed {
z.dev.Logger().WithFields(dilog.Fields{
"active": active,
"error": errDescription,
}).Info("reported error")
}
return changed
}
// heartbeatCmd is the Watchdog callback (used as heartbeat)
// Suppress the linter because heartbeatCmd needs to satisfy the interface
//nolint: unparam
func (z *Base) heartbeatCmd(r cp3000.Router, args []string) error {
if err := z.dev.SendDeviceData(); err != nil {
return err
}
return z.client.Send("WD")
}
func (z *Base) syncCmd(r cp3000.Router, args []string) error {
return nil
}
func (z *Base) exitCmd(r cp3000.Router, args []string) error {
return errors.New("exiting connection")
}
// UID returns the device UID of this device
func (z *Base) UID() string {
return z.uid
}
// Type returns the device type of this device
func (z *Base) Type() rpc.DeviceType {
return z.devType
}
// Close unregisters the connection from SMP
func (z *Base) Close() error {
z.dev.Logger().Debug("close device")
if z.client != nil {
z.client.RemoveDevice(z.UID())
if err := z.client.Close(); err != nil {
return err
}
}
if err := z.dev.Close(); err != nil {
return err
}
return nil
}
// configSetEndpoint request
func (z *Base) configSetEndpoint(p rpc.ConfigParam) rpc.ErrorCode {
s, ok := p.Value.(string)
if !ok {
return rpc.EParam
}
if err := z.client.Send(cp3000.CommandStore, "DBSERVER", s); err != nil {
z.dev.Logger().WithError(err).Warning("setting tcpserver failed")
return rpc.EBackendCp3000CommError
}
srv, errcode := z.requestValue("DBSERVER")
if errcode != rpc.Ok {
return errcode
}
if srv != s {
z.dev.Logger().WithField("current", srv).Warning("tcpserver verification failed")
return rpc.EBackendCp3000CommError
}
// todo: reset the device
return rpc.Ok
}
func (z *Base) configGetEndpoint(rep *rpc.Msg) *rpc.Msg {
srv, err := z.requestValue("DBSERVER")
if err != rpc.Ok {
return rep.SetError(err)
}
rep.Result = rpc.NewResult([]*rpc.ResultConfigValueItem{rpc.ConfigEndpoint.Data(srv)})
return rep
}
func (z *Base) cp3000Transport() (interface{}, rpc.ErrorCode) {
var cp3000Info rpc.ConnectionInfoCP3000
var rpcerr rpc.ErrorCode
cp3000Info.DatabaseID = z.databaseID
if cp3000Info.Imsi, rpcerr = z.requestVersion("IMSI"); rpcerr != rpc.Ok {
return nil, rpcerr
}
if cp3000Info.Iccid, rpcerr = z.requestVersion("SIM"); rpcerr != rpc.Ok {
return nil, rpcerr
}
if cp3000Info.Imei, rpcerr = z.requestVersion("IMEI"); rpcerr != rpc.Ok {
return nil, rpcerr
}
if cp3000Info.Endpoint, rpcerr = z.requestValue("DBSERVER"); rpcerr != rpc.Ok {
return nil, rpcerr
}
if cp3000Info.GPRSApn, rpcerr = z.requestValue("GPRS"); rpcerr != rpc.Ok {
return nil, rpcerr
}
return &cp3000Info, rpc.Ok
}