201 lines
5.3 KiB
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
|
|
}
|