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 }