|
@@ -2,6 +2,7 @@ package drviers
|
|
|
|
|
|
import (
|
|
|
"encoding/hex"
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
|
"reflect"
|
|
|
"strconv"
|
|
@@ -14,31 +15,43 @@ import (
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
- CMD_ENERGY_TOTAL = "00000000"
|
|
|
- DEF_SAMPLE_PERIOD = 5 * time.Minute
|
|
|
- DEF_SAMPLE_PEER_DURATION = 30 * time.Second
|
|
|
+ DEF_SAMPLE_PERIOD = 30 * time.Second // time.Minute
|
|
|
+ DEF_SAMPLE_PEER_DURATION = 10 * time.Second
|
|
|
DEF_TCP_READ_TIMEOUT = 1 * time.Minute
|
|
|
POSTPONE_PERSIST_TIME = 1 * time.Minute
|
|
|
)
|
|
|
|
|
|
+//amList map[string]*Ammeter
|
|
|
+//amList : key=[code + address], each meter id must be unique, otherwise ammeter would not work correctly!
|
|
|
+//Careful! address from cloud may not real address. software varAddress functon would change it to real address
|
|
|
+
|
|
|
type AmmeterModel struct {
|
|
|
- Id string `json:"id" gorm:"-"`
|
|
|
- DevName string `json:"devName" gorm:"primarykey"`
|
|
|
- Code string `json:"code" gorm:"-"`
|
|
|
- DutSn string `json:"dutSn" gorm:"-"`
|
|
|
- Address string `json:"address" gorm:"-"`
|
|
|
- Protocol string `json:"protocol" gorm:"-"`
|
|
|
- GwDevId string `json:"gwDevId" gorm:"-"`
|
|
|
- TransRadio string `json:"transformerRatio" gorm:"-"`
|
|
|
+ Id string `json:"id"`
|
|
|
+ DevName string `json:"devName" gorm:"primary_key; unique"`
|
|
|
+ Code string `json:"code" `
|
|
|
+ DutSn string `json:"dutSn" `
|
|
|
+ Address string `json:"address" `
|
|
|
+ Protocol string `json:"protocol" `
|
|
|
+ GwDevId string `json:"gwDevId" `
|
|
|
+ TransRadio string `json:"transformerRatio" `
|
|
|
+ Type string `json:"-" `
|
|
|
}
|
|
|
|
|
|
+type meterPersist struct {
|
|
|
+ DevName string `json:"devName" gorm:"primary_key; unique"`
|
|
|
+ TsSample int64 `gorm:"autoUpdateTime"`
|
|
|
+ TotalEnergy float64
|
|
|
+ DBStatus int32
|
|
|
+}
|
|
|
type Ammeter struct {
|
|
|
AmmeterModel
|
|
|
TsSample int64
|
|
|
TotalEnergy float64
|
|
|
- TransDeno float64 `gorm:"-"`
|
|
|
- TransDiv float64 `gorm:"-"`
|
|
|
- DBStatus int `json:"-"`
|
|
|
+ TransDeno float64
|
|
|
+ TransDiv float64
|
|
|
+ varAddr string
|
|
|
+ DBStatus int32
|
|
|
+ TsUpdate int64
|
|
|
}
|
|
|
|
|
|
type AmMeterHub struct {
|
|
@@ -49,8 +62,8 @@ type AmMeterHub struct {
|
|
|
chnExit chan bool
|
|
|
loopserver bool
|
|
|
sampleTmr *time.Timer
|
|
|
- amList map[string]*Ammeter
|
|
|
- persitList map[string]*Ammeter
|
|
|
+ amList map[string]*Ammeter //key=[code + address], each meter id must be unique
|
|
|
+ persitList map[string]*meterPersist
|
|
|
tmrPersist *time.Timer
|
|
|
|
|
|
queryIdx int
|
|
@@ -82,16 +95,9 @@ func (dev *AmMeterHub) ChannelDispatch(stream []byte, args interface{}) bus.ChnD
|
|
|
if stream[0] == 0x68 && stream[7] == 0x68 && stream[8] == 0x93 {
|
|
|
devname := ""
|
|
|
mret := dev.Route[1].iproto.ParsePacket(stream, &devname)
|
|
|
- if mret != nil && devname == dev.DeviceName {
|
|
|
+ if mret != nil && devname == dev.DutSn {
|
|
|
return bus.DispatchSingle
|
|
|
}
|
|
|
- // for i := 0; i < 6; i++ {
|
|
|
- // if stream[i+1] != stream[i+10] {
|
|
|
- // return bus.DispatchNone
|
|
|
- // }
|
|
|
- // }
|
|
|
- // log.Infof("MOUNT meter:0000[%x]", stream[1:7])
|
|
|
- // return bus.DispatchSingle
|
|
|
}
|
|
|
return bus.DispatchNone
|
|
|
}
|
|
@@ -112,6 +118,9 @@ func (dev *AmMeterHub) ChannelDispatch(stream []byte, args interface{}) bus.ChnD
|
|
|
func (dev *AmMeterHub) IssueSampleCmd(tmrTx *time.Timer) (pam *Ammeter) {
|
|
|
i := 0
|
|
|
for _, am := range dev.amList {
|
|
|
+ if i == 0 {
|
|
|
+ pam = am
|
|
|
+ }
|
|
|
if i == dev.queryIdx {
|
|
|
pam = am
|
|
|
break
|
|
@@ -121,7 +130,7 @@ func (dev *AmMeterHub) IssueSampleCmd(tmrTx *time.Timer) (pam *Ammeter) {
|
|
|
if pam == nil {
|
|
|
return nil
|
|
|
}
|
|
|
- pack := dev.Route[1].iproto.PackageCmd(CMD_ENERGY_TOTAL, pam.Code+pam.Address)
|
|
|
+ pack := dev.Route[1].iproto.PackageCmd("TotalActivePower", pam.Code+pam.Address)
|
|
|
dev.Route[1].ibus.Send(dev.Route[1].iChn, pack)
|
|
|
tmrTx.Reset(1 * time.Second)
|
|
|
return pam
|
|
@@ -130,6 +139,7 @@ func (dev *AmMeterHub) IssueSampleCmd(tmrTx *time.Timer) (pam *Ammeter) {
|
|
|
func (dev *AmMeterHub) SchedulNextSample(tmrTx *time.Timer) {
|
|
|
tmrTx.Stop()
|
|
|
dev.queryIdx++
|
|
|
+ log.Info("Schedul next:(%d/%d)", dev.queryIdx, len(dev.amList))
|
|
|
if dev.queryIdx >= len(dev.amList) {
|
|
|
dev.queryIdx = 0
|
|
|
dev.sampleTmr.Reset(DEF_SAMPLE_PERIOD)
|
|
@@ -138,30 +148,33 @@ func (dev *AmMeterHub) SchedulNextSample(tmrTx *time.Timer) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (dev *AmMeterHub) AdjustTelemetry(mval interface{}, devname string) {
|
|
|
+func (dev *AmMeterHub) AdjustTelemetry(mval interface{}, am *Ammeter) {
|
|
|
vlist := mval.(map[string]interface{})
|
|
|
- if am, has := dev.amList[devname]; has {
|
|
|
- for k, v := range vlist {
|
|
|
- if reflect.TypeOf(v).Kind() == reflect.Float64 {
|
|
|
- valItem := v.(float64)
|
|
|
- vlist[k] = (valItem * am.TransDeno) / am.TransDiv
|
|
|
- }
|
|
|
+ for k, v := range vlist {
|
|
|
+ if reflect.TypeOf(v).Kind() == reflect.Float64 {
|
|
|
+ valItem := v.(float64)
|
|
|
+ vlist[k] = (valItem * am.TransDeno) / am.TransDiv
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if val, has := vlist["TotalActivePower"]; has {
|
|
|
- diff := val.(float64) - am.TotalEnergy
|
|
|
- if am.DBStatus == 0 { //数据库清0
|
|
|
- diff = 0
|
|
|
- am.DBStatus = 1
|
|
|
- }
|
|
|
- am.TotalEnergy = val.(float64)
|
|
|
- vlist["ActivePowerIncrement"] = diff
|
|
|
- if len(dev.persitList) == 0 {
|
|
|
- dev.tmrPersist.Reset(POSTPONE_PERSIST_TIME)
|
|
|
- }
|
|
|
-
|
|
|
- dev.persitList[am.DevName] = am
|
|
|
+ if val, has := vlist["TotalActivePower"]; has {
|
|
|
+ diff := val.(float64) - am.TotalEnergy
|
|
|
+ if am.DBStatus == 0 { //数据库清0
|
|
|
+ diff = 0
|
|
|
+ am.DBStatus = 1
|
|
|
}
|
|
|
+ am.TotalEnergy = val.(float64)
|
|
|
+ vlist["ActivePowerIncrement"] = diff
|
|
|
+ if len(dev.persitList) == 0 {
|
|
|
+ dev.tmrPersist.Reset(POSTPONE_PERSIST_TIME)
|
|
|
+ }
|
|
|
+ energe := &meterPersist{
|
|
|
+ TotalEnergy: am.TotalEnergy,
|
|
|
+ TsSample: am.TsSample,
|
|
|
+ DBStatus: am.DBStatus,
|
|
|
+ DevName: am.DevName,
|
|
|
+ }
|
|
|
+ dev.persitList[am.DevName] = energe
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -176,17 +189,16 @@ func (dev *AmMeterHub) Run() error {
|
|
|
case msg := <-dev.Route[1].router[0]:
|
|
|
if reflect.TypeOf(msg).Kind() == reflect.Slice {
|
|
|
b := msg.([]byte)
|
|
|
- log.Info("[", dev.DutSn, "]:", hex.EncodeToString(b))
|
|
|
- devname := "" //pam.DevName
|
|
|
- mret := dev.Route[1].iproto.ParsePacket(b, &devname)
|
|
|
- if mret != nil && reflect.TypeOf(mret).Kind() == reflect.Map && devname != "" {
|
|
|
- log.Info(devname, mret)
|
|
|
- devname = "AM10-" + devname + dev.DevCompCode[:4]
|
|
|
- dev.AdjustTelemetry(mret, devname)
|
|
|
- telemetry := dev.Route[0].iproto.PackageCmd(devname, mret)
|
|
|
- dev.Route[0].ibus.Send(dev.Route[0].iChn, telemetry)
|
|
|
- if meter, has := dev.amList[devname]; has {
|
|
|
- meter.TsSample = time.Now().Unix()
|
|
|
+ log.Info("[", dev.DutSn, "]:", hex.EncodeToString(b)) //[9521003697]:fefefefe68040042050821689108333333338b694c331c16
|
|
|
+ devid := ""
|
|
|
+ mret := dev.Route[1].iproto.ParsePacket(b, &devid)
|
|
|
+ if mret != nil && reflect.TypeOf(mret).Kind() == reflect.Map && devid != "" {
|
|
|
+ log.Info(devid, mret) //210805420004 map[TotalActivePower:1936.58]
|
|
|
+ if am, has := dev.amList[devid]; has {
|
|
|
+ dev.AdjustTelemetry(mret, am)
|
|
|
+ telemetry := dev.Route[0].iproto.PackageCmd(am.DevName, mret)
|
|
|
+ dev.Route[0].ibus.Send(dev.Route[0].iChn, telemetry)
|
|
|
+ am.TsSample = time.Now().Unix()
|
|
|
}
|
|
|
dev.SchedulNextSample(tmrTxTimout)
|
|
|
}
|
|
@@ -198,11 +210,8 @@ func (dev *AmMeterHub) Run() error {
|
|
|
case <-dev.chnExit:
|
|
|
dev.loopserver = false
|
|
|
case <-dev.tmrPersist.C:
|
|
|
- for k, am := range dev.persitList {
|
|
|
- if am.DBStatus == 0 {
|
|
|
-
|
|
|
- }
|
|
|
- config.GetDB().Save(am)
|
|
|
+ for k, eng := range dev.persitList {
|
|
|
+ config.GetDB().Save(eng)
|
|
|
delete(dev.persitList, k)
|
|
|
}
|
|
|
}
|
|
@@ -221,8 +230,13 @@ func DutchanDispatch(rxin interface{}, param interface{}) interface{} {
|
|
|
|
|
|
func (dev *AmMeterHub) Open(param ...interface{}) error {
|
|
|
if dev.DutSn != "" {
|
|
|
- dev.SetRoute(dev, dev.Protocol, "dtuFtpServer", dev.DutSn, 0)
|
|
|
+ if dev.SetRoute(dev, dev.Protocol, "dtuFtpServer", dev.DutSn, 0) == 0 {
|
|
|
+ return errors.New("can not set default rout!")
|
|
|
+ }
|
|
|
+ r := dev.Route[len(dev.Route)-1]
|
|
|
+ r.ibus.ApplyPatch("PassiveAddress", r.iproto.PackageCmd("ReadAddress"))
|
|
|
}
|
|
|
+
|
|
|
dev.sampleTmr = time.NewTimer(DEF_SAMPLE_PERIOD)
|
|
|
dev.sampleTmr.Stop()
|
|
|
|
|
@@ -231,8 +245,10 @@ func (dev *AmMeterHub) Open(param ...interface{}) error {
|
|
|
}
|
|
|
|
|
|
func (dev *AmMeterHub) GetDevice(devname string) interface{} {
|
|
|
- if dev, has := dev.amList[devname]; has {
|
|
|
- return dev
|
|
|
+ for _, am := range dev.amList {
|
|
|
+ if am.DevName == devname {
|
|
|
+ return dev
|
|
|
+ }
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
@@ -241,14 +257,13 @@ func (dev *AmMeterHub) ListDevices() interface{} {
|
|
|
mdev := make(map[string]interface{})
|
|
|
mdev["State"] = dev.Status
|
|
|
dinfo := make(map[string]interface{})
|
|
|
- for sname, subdev := range dev.amList {
|
|
|
-
|
|
|
+ for _, subdev := range dev.amList {
|
|
|
meterval := make(map[string]string)
|
|
|
meterval["TotalEnergy"] = fmt.Sprintf("%f", subdev.TotalEnergy)
|
|
|
if subdev.TsSample != 0 {
|
|
|
meterval["TsSample"] = time.Unix(subdev.TsSample, 0).Format("2006-01-02 15:04:05")
|
|
|
}
|
|
|
- dinfo[sname] = meterval
|
|
|
+ dinfo[subdev.DevName] = meterval
|
|
|
}
|
|
|
mdev["Sub Meters"] = dinfo
|
|
|
return mdev
|
|
@@ -272,55 +287,88 @@ func (drv *AmmeterDrv) ParseTransRatio(dev *Ammeter) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (drv *AmmeterDrv) CreateDevice(model interface{}, filterGwId string) (int, []IDevice) {
|
|
|
+func (drv *AmmeterDrv) varyAdress(m *AmmeterModel, addr string, subid string) {
|
|
|
+ defer func() {
|
|
|
+ recover()
|
|
|
+ }()
|
|
|
+ iaddr, _ := strconv.Atoi(addr)
|
|
|
+ isub, _ := strconv.Atoi(subid)
|
|
|
+ if iaddr != 0 && isub > 0 {
|
|
|
+ iaddr = iaddr + (isub-1)*3
|
|
|
+ m.Address = strconv.Itoa(iaddr)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (drv *AmmeterDrv) mountHub(m *AmmeterModel, list *[]IDevice) IDevice {
|
|
|
+ devCompID := m.DutSn
|
|
|
+ if m.DutSn == "" || m.DutSn == "0000" || len(m.DutSn) < 10 {
|
|
|
+ addr := m.Address
|
|
|
+ ss := strings.Split(addr, "-")
|
|
|
+ m.DutSn = m.Code + ss[0]
|
|
|
+ devCompID = "0000"
|
|
|
+ if len(ss) > 1 {
|
|
|
+ drv.varyAdress(m, ss[0], ss[1])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if hub, has := drv.DevList[m.DutSn]; has {
|
|
|
+ return hub
|
|
|
+ }
|
|
|
+ hub := &AmMeterHub{
|
|
|
+ DutSn: m.DutSn,
|
|
|
+ Protocol: m.Protocol,
|
|
|
+ chnExit: make(chan bool),
|
|
|
+ loopserver: false,
|
|
|
+ sampleTmr: nil,
|
|
|
+ amList: make(map[string]*Ammeter),
|
|
|
+ persitList: make(map[string]*meterPersist),
|
|
|
+ queryIdx: 0,
|
|
|
+ DevCompCode: devCompID,
|
|
|
+ }
|
|
|
+ hub.Probe(m.DutSn, drv)
|
|
|
+ drv.DevList[m.DutSn] = hub
|
|
|
+ *list = append(*list, hub)
|
|
|
+ return hub
|
|
|
+}
|
|
|
+
|
|
|
+func (drv *AmmeterDrv) CreateDevice(model interface{}) (int, []IDevice) {
|
|
|
+ ts := time.Now().UnixMilli()
|
|
|
+ devlist := make([]IDevice, 0)
|
|
|
if model != nil {
|
|
|
- models := model.(*[]AmmeterModel)
|
|
|
- for _, m := range *models {
|
|
|
- var hub IDevice = nil
|
|
|
- var has bool
|
|
|
- if m.GwDevId != filterGwId {
|
|
|
- continue
|
|
|
- }
|
|
|
- if len(m.Code) < 4 || len(m.Address) < 8 {
|
|
|
+ devModels := model.(*[]AmmeterModel)
|
|
|
+ for _, mod := range *devModels {
|
|
|
+ if len(mod.Code) < 4 || len(mod.Address) < 8 {
|
|
|
continue
|
|
|
}
|
|
|
- devCompID := m.DutSn
|
|
|
- if m.DutSn == "" {
|
|
|
- m.DutSn = m.Code + m.Address
|
|
|
- devCompID = "0000"
|
|
|
- }
|
|
|
- if hub, has = drv.DevList[m.DutSn]; !has {
|
|
|
- hub = &AmMeterHub{
|
|
|
- DutSn: m.DutSn,
|
|
|
- Protocol: m.Protocol,
|
|
|
- chnExit: make(chan bool),
|
|
|
- loopserver: false,
|
|
|
- sampleTmr: nil,
|
|
|
- amList: make(map[string]*Ammeter),
|
|
|
- persitList: make(map[string]*Ammeter),
|
|
|
- queryIdx: 0,
|
|
|
- DevCompCode: devCompID,
|
|
|
- }
|
|
|
- hub.Probe(m.DutSn, drv)
|
|
|
- drv.DevList[m.DutSn] = hub
|
|
|
- }
|
|
|
-
|
|
|
- if _, has := hub.(*AmMeterHub).amList[m.DevName]; !has {
|
|
|
- dev := &Ammeter{
|
|
|
- TotalEnergy: 0,
|
|
|
- TsSample: 0,
|
|
|
+ hub := drv.mountHub(&mod, &devlist)
|
|
|
+ id := mod.Code + mod.Address
|
|
|
+ am, has := hub.(*AmMeterHub).amList[id]
|
|
|
+ if !has {
|
|
|
+ eng := &meterPersist{}
|
|
|
+ config.GetDB().Find(eng, "dev_name='"+mod.DevName+"'")
|
|
|
+ am = &Ammeter{
|
|
|
+ TotalEnergy: eng.TotalEnergy,
|
|
|
+ DBStatus: eng.DBStatus,
|
|
|
+ TsSample: eng.TsSample,
|
|
|
TransDiv: 1,
|
|
|
TransDeno: 1,
|
|
|
- DBStatus: 0,
|
|
|
}
|
|
|
- config.GetDB().Find(dev, "dev_name='"+m.DevName+"'")
|
|
|
- dev.AmmeterModel = m
|
|
|
- drv.ParseTransRatio(dev)
|
|
|
- hub.(*AmMeterHub).amList[dev.DevName] = dev
|
|
|
+ am.AmmeterModel = mod
|
|
|
+ drv.ParseTransRatio(am)
|
|
|
+ hub.(*AmMeterHub).amList[id] = am
|
|
|
}
|
|
|
+ am.TsUpdate = ts
|
|
|
}
|
|
|
}
|
|
|
- return drv.baseDriver.CreateDevice(model, filterGwId)
|
|
|
+
|
|
|
+ for _, hub := range drv.DevList {
|
|
|
+ for id, am := range hub.(*AmMeterHub).amList {
|
|
|
+ if am.TsUpdate != ts {
|
|
|
+ delete(hub.(*AmMeterHub).amList, id)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return int(ts), devlist
|
|
|
}
|
|
|
|
|
|
func (drv *AmmeterDrv) GetModel() interface{} {
|
|
@@ -334,5 +382,6 @@ func NewAmMeter(param interface{}) IDriver {
|
|
|
|
|
|
func init() {
|
|
|
driverReg["ammeter"] = NewAmMeter
|
|
|
- config.GetDB().CreateTbl(&Ammeter{})
|
|
|
+ config.GetDB().CreateTbl(&AmmeterModel{})
|
|
|
+ config.GetDB().CreateTbl(&meterPersist{})
|
|
|
}
|