| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 |
- package main
- import (
- "context"
- "errors"
- "runtime/debug"
- "time"
- "github.com/go-redis/redis/v7"
- "github.com/sirupsen/logrus"
- "lc/common/mqtt"
- "lc/common/protocol"
- "lc/common/util"
- "lc/edge/ipole/ym485"
- "strings"
- "sync"
- )
- var YmProtocol = "yuming485"
- var mutex sync.Mutex
- var oldstate uint8
- type YmLampController struct {
- devinfo *protocol.DevInfo
- model *protocol.IotModel
- downQueue *util.MlQueue
- mapTopicHandle map[string]func(m mqtt.Message)
- mapLamps2OOT []protocol.CHZB_OnOffTime //时控策略
- mapTempLampsOOT *LampTimeRange //临时手动控时间段,手动控制开关灯
- mapLampAlarm *LampAlarmInfo //告警数据
- State []ym485.DeviceState //当前灯控状态 开关灯,电流,电压,电度
- ctx context.Context
- cancel context.CancelFunc
- chanDevInfo chan *protocol.DevInfo //设备管理更新
- chanModelInfo chan *ModelInfo //设备管理更新
- tswitch time.Time //手动控执行时间
- }
- func NewYmLampController(info *protocol.DevInfo) Device {
- ctx, cancel := context.WithCancel(context.Background())
- dev := &YmLampController{
- devinfo: info,
- downQueue: util.NewQueue(200),
- mapTopicHandle: make(map[string]func(m mqtt.Message)),
- ctx: ctx,
- cancel: cancel,
- chanDevInfo: make(chan *protocol.DevInfo),
- chanModelInfo: make(chan *ModelInfo),
- }
- iot, err := loadModel(info.TID)
- if err != nil {
- logrus.Errorf("NewYmLampController:加载模型[tid=%d]文件发生错误:%s", info.TID, err.Error())
- } else {
- if iot.Protocol == YmProtocol {
- dev.model = iot
- } else {
- logrus.Error("NewYmLampController:物模型错误,非YmProtocol协议")
- }
- }
- dev.SetTopicHandle()
- return dev
- }
- func (o *YmLampController) SetTopicHandle() {
- o.mapTopicHandle[GetTopic(protocol.DT_LAMPCONTROLLER, o.devinfo.DevCode, protocol.TP_YM_SET_SWITCH)] = o.HandleTpYmSetSwitch
- o.mapTopicHandle[GetTopic(protocol.DT_LAMPCONTROLLER, o.devinfo.DevCode, protocol.TP_YM_SET_ONOFFTIME)] = o.HandleTpYmSetOnofftime
- }
- func (o *YmLampController) MQTTSubscribe() {
- GetMQTTMgr().Subscribe(GetTopic(protocol.DT_LAMPCONTROLLER, o.devinfo.DevCode, protocol.TP_YM_SET_SWITCH), mqtt.ExactlyOnce, o.HandleCache, ToAll) //开关灯
- GetMQTTMgr().Subscribe(GetTopic(protocol.DT_LAMPCONTROLLER, o.devinfo.DevCode, protocol.TP_YM_SET_ONOFFTIME), mqtt.ExactlyOnce, o.HandleCache, ToCloud) //设置时间
- }
- func (o *YmLampController) HandleCache(m mqtt.Message) {
- logrus.Infof("Topic:%v,message:%v\n", m.Topic(), m.PayloadString())
- o.downQueue.Put(m)
- }
- func (o *YmLampController) Start() {
- o.MQTTSubscribe()
- retry := 3
- sleep := time.Duration(2)
- for i := 0; i < retry; i++ {
- if err := o.ReloadOOTFromRedis(); err == nil {
- break
- }
- time.Sleep(sleep * time.Second)
- }
- for i := 0; i < retry; i++ {
- if err := o.ReloadSwitchOOTFromRedis(); err == nil {
- break
- }
- time.Sleep(sleep * time.Second)
- }
- for i := 0; i < retry; i++ {
- if err := o.ReloadLampAlarmFromRedis(); err == nil {
- break
- }
- time.Sleep(sleep * time.Second)
- }
- go o.HandleData()
- }
- func (o *YmLampController) Stop() {
- //停止采集和处理
- o.cancel()
- }
- func (o *YmLampController) UpdateInfo(devinfo protocol.DevInfo) {
- o.chanDevInfo <- &devinfo
- }
- func (o *YmLampController) GetDevInfo() *protocol.DevInfo {
- return o.devinfo
- }
- func (o *YmLampController) UpdateModel(tid uint16, flag int) {
- if tid > 0 {
- mi := ModelInfo{
- TID: tid,
- Flag: flag,
- }
- o.chanModelInfo <- &mi
- }
- }
- func (o *YmLampController) UpdateModel2(mi *ModelInfo) {
- if o.devinfo.TID != mi.TID {
- return
- }
- if mi.Flag == 0 {
- logrus.Errorf("设备[%s]的物模型[tid=%d]模型文件被删除,下次启动即将生效。", o.devinfo.DevCode, mi.TID)
- return
- }
- logrus.Debugf("YmLampController.UpdateModel2:更新设备[%s]的物模型[%d]", o.devinfo.DevCode, mi.TID)
- iot, err := loadModel(mi.TID)
- if err != nil {
- logrus.Errorf("YmLampController.UpdateModel2:加载模型[%d]文件错误:%s", mi.TID, err.Error())
- return
- }
- if iot.Protocol == ModbusRtuProtocol { //合法的物模型
- o.model = iot
- logrus.Infof("YmLampController.UpdateModel2:更新设备[%s]的物模型[%d]成功", o.devinfo.DevCode, mi.TID)
- } else {
- logrus.Error("YmLampController.UpdateModel2:物模型错误,TID和文件名tid不一致或协议非ModbusRTU协议")
- }
- }
- func (o *YmLampController) GetDevType() string {
- if o.devinfo.DevType == 3 {
- return protocol.DT_LAMPCONTROLLER
- }
- return "unknown"
- }
- // HandleData 数据处理协程
- func (o *YmLampController) HandleData() {
- defer func() {
- if err := recover(); err != nil {
- logrus.Error("YmLampController.HandleData:panic:", err)
- logrus.Error("stack:", string(debug.Stack()))
- go o.HandleData()
- }
- }()
- o.QueryLampControllerAddr() //线上挂了两个灯控,则不能
- logrus.Infof("lamp state:%d", oldstate)
- nextFillTime := time.Time{}
- for {
- select {
- case <-o.ctx.Done():
- logrus.Errorf("设备[%s]的HandleData退出,原因:%v", o.devinfo.DevCode, o.ctx.Err())
- return
- case devinfo_ := <-o.chanDevInfo:
- o.devinfo = devinfo_
- case mi := <-o.chanModelInfo:
- o.UpdateModel2(mi)
- default:
- //从队列钟获取指令执行
- if m, ok, _ := o.downQueue.Get(); ok {
- if mm, ok := m.(mqtt.Message); ok {
- if fn, ok := o.mapTopicHandle[mm.Topic()]; ok {
- fn(mm)
- } else {
- logrus.Errorf("YmLampController.Handle:不支持的主题:%s", mm.Topic())
- }
- }
- } else {
- if nextFillTime.IsZero() || nextFillTime.Before(util.MlNow()) { //定时读取和发送
- t := util.MlNow()
- o.QueryDeviceState() //获取灯的数据
- o.ConfirmState(t) //获取设置灯的开/关
- nextFillTime = util.MlNow().Add(time.Duration(o.devinfo.SendCloud) * time.Millisecond)
- }
- time.Sleep(time.Millisecond * 1000)
- }
- }
- }
- }
- func (o *YmLampController) SendRecvData(aduRequest []byte, retry int) (aduResponse []byte, err error) {
- defer mutex.Unlock()
- mutex.Lock()
- // time.Sleep(time.Millisecond * 10)
- serial := GetSerialMgr().GetSerialPort(o.devinfo.Code)
- if serial == nil {
- return nil, ErrClosedConnection
- }
- if retry <= 0 {
- retry = 1
- }
- for ; retry > 0; retry-- {
- aduResponse, err = serial.SendRecvData(aduRequest, FlagYm485, o.devinfo.WaitTime)
- if err == nil {
- break
- }
- }
- return aduResponse, err
- }
- func (o *YmLampController) SendData(aduRequest []byte, retry int) (err error) {
- serial := GetSerialMgr().GetSerialPort(o.devinfo.Code)
- if serial == nil {
- return ErrClosedConnection
- }
- if retry <= 0 {
- retry = 1
- }
- for ; retry > 0; retry-- {
- if err = serial.SendData(aduRequest, FlagYm485, o.devinfo.WaitTime); err == nil {
- break
- }
- }
- return err
- }
- func (o *YmLampController) QueryLampControllerAddr() {
- var obj ym485.QueryAddr
- buf, err := obj.EnCode()
- defer ym485.ReleaseByteBuffer(buf)
- if err != nil {
- return
- }
- recvbuf, err := o.SendRecvData(buf.Bytes(), 2)
- if err != nil {
- return
- }
- var ret ym485.QueryAddrACK
- if err = ret.DeCode(recvbuf); err == nil {
- logrus.Infof("灯控地址:%s", ret.Addr)
- }
- if ret.Addr != o.devinfo.DevCode {
- logrus.Errorf("请将DevCode配置为灯控地址!")
- }
- }
- func (o *YmLampController) QuerySoftVer() {
- var obj ym485.PackingMark
- buf, err := obj.EnCode(o.devinfo.DevCode, 0x1E, [4]byte{0x00, 0x00, 0x60, 0x12})
- defer ym485.ReleaseByteBuffer(buf)
- if err != nil {
- return
- }
- recvbuf, err := o.SendRecvData(buf.Bytes(), 1)
- if err != nil {
- return
- }
- var ret ym485.QuerySoftVerAck
- if err = ret.DeCode(recvbuf); err == nil {
- logrus.Infof("灯控型号:%s,软件版本:%s", ret.Model, ret.Version)
- }
- }
- func (o *YmLampController) QueryBasicsettings() {
- var obj ym485.PackingMark
- buf, err := obj.EnCode(o.devinfo.DevCode, 0x1E, [4]byte{0x00, 0x00, 0x60, 0x02})
- defer ym485.ReleaseByteBuffer(buf)
- if err != nil {
- return
- }
- recvbuf, err := o.SendRecvData(buf.Bytes(), 1)
- if err != nil {
- return
- }
- var ret ym485.PackingBasic
- if err = ret.DeCode(recvbuf); err == nil {
- logrus.Infof("灯控使能状态:%d,是否上电开灯:%d", ret.Enabled, ret.OnOff)
- }
- }
- // QueryDeviceState 查询设备状态
- func (o *YmLampController) QueryDeviceState() {
- var obj ym485.DataPack //串口协议
- buf, err := obj.EnCode(o.devinfo.DevCode, 0x1E, []byte{0x00, 0x00, 0x60, 0x13, 0x01})
- defer ym485.ReleaseByteBuffer(buf)
- if err != nil {
- return
- }
- recvbuf, err := o.SendRecvData(buf.Bytes(), 1) //根据地址得到数据
- if err != nil {
- logrus.Error("单灯状态数据查询错误")
- return
- }
- var ret ym485.QueryDeviceStateAck
- if err = ret.DeCode(recvbuf); err == nil { //解码串口返回的数据
- // if ret.Dp.Addr != o.devInfo.DevCode { //不是本灯数据
- if !strings.Contains(o.devinfo.DevCode, ret.Dp.Addr) { //不是本灯数据
- o.State = nil
- return
- }
- if len(ret.State) == 0 {
- o.State = nil
- return
- }
- if len(o.State) != len(ret.State) {
- o.State = make([]ym485.DeviceState, len(ret.State))
- }
- copy(o.State, ret.State)
- //状态改变记录日志
- if oldstate != o.State[0].State {
- oldstate = o.State[0].State
- logrus.Infof("lamp state:%d", oldstate)
- }
- }
- mapData := make(map[string]*protocol.CHZB_LampData)
- var data protocol.CHZB_LampData
- data.Data = make(map[uint16]float64)
- data.SetStateErrorData(err)
- if len(o.State) > 0 { //只要第一路
- if o.State[0].State == 1 { //开灯
- data.Data[1] = float64(o.State[0].BrightnessState)
- } else {
- data.Data[1] = 0.0
- }
- data.Data[2] = float64(0)
- data.Data[3] = o.State[0].Voltage
- data.Data[4] = 0.0
- data.Data[5] = 0.0
- data.Data[6] = o.State[0].Current
- data.Data[7] = 0.0
- data.Data[8] = o.State[0].Degree //by hxz 1,2,3,6,8为有效
- data.Data[9] = 0.0
- }
- mapData[o.devinfo.DevCode] = &data
- var ret1 protocol.Pack_CHZB_UploadData
- if str, err := ret1.EnCode(o.devinfo.DevCode, appConfig.GID, GetNextUint64(), o.devinfo.TID, mapData); err == nil {
- GetMQTTMgr().Publish(GetTopic(protocol.DT_LAMPCONTROLLER, o.devinfo.DevCode, protocol.TP_YM_DATA), str, mqtt.AtMostOnce, ToCloud)
- }
- }
- func (o *YmLampController) TurnOnOff(flag uint8) error {
- kenabled := uint8(0x00)
- if flag > 0 {
- kenabled = 0xff
- }
- var obj ym485.PackingTurnOnOff
- buf, err := obj.EnCode(o.devinfo.DevCode, [4]byte{0x00, 0x00, 0x60, 0x07}, 0xff, kenabled)
- defer ym485.ReleaseByteBuffer(buf)
- if err != nil {
- return err
- }
- recvbuf, err := o.SendRecvData(buf.Bytes(), 3)
- if err != nil {
- return err
- }
- var ret ym485.PackingResult
- err = ret.DeCode(recvbuf)
- if err == nil {
- if ret.Result == 89 { //字母'Y'
- return nil
- }
- }
- return errors.New(protocol.FAILED_STR)
- }
- func (o *YmLampController) SetBrightness(brightness uint8) error {
- var obj ym485.PackingBrightnessColor
- buf, err := obj.EnCode(o.devinfo.DevCode, [4]byte{0x00, 0x00, 0x60, 0x09}, 0xff, []byte{brightness})
- defer ym485.ReleaseByteBuffer(buf)
- if err != nil {
- return err
- }
- recvbuf, err := o.SendRecvData(buf.Bytes(), 1)
- if err != nil {
- return err
- }
- var ret ym485.PackingResult
- err = ret.DeCode(recvbuf)
- if err == nil {
- if ret.Result == 89 { //字母'Y'
- return nil
- }
- }
- return errors.New(protocol.FAILED_STR)
- }
- func (o *YmLampController) SetBasicsettings(enabled, on uint8) {
- var obj ym485.PackingBasic
- buf, err := obj.EnCode(o.devinfo.DevCode, 0x1E, [4]byte{0x00, 0x00, 0x60, 0x01}, enabled, on, nil)
- defer ym485.ReleaseByteBuffer(buf)
- if err != nil {
- return
- }
- recvbuf, err := o.SendRecvData(buf.Bytes(), 1)
- if err != nil {
- return
- }
- var ret ym485.PackingResult
- if err = ret.DeCode(recvbuf); err == nil {
- if ret.Result == 89 { //字母'Y'
- logrus.Infoln("设置灯控基本信息执行成功")
- } else {
- logrus.Errorln("设置灯控基本信息执行失败")
- }
- }
- }
- func (o *YmLampController) Switch(Switch, Brightness uint8) error {
- if Switch == 0 {
- return o.TurnOnOff(0)
- } else {
- err := o.SetBrightness(Brightness)
- if err != nil {
- logrus.Errorf("调节亮度失败:%s", err.Error())
- }
- return o.TurnOnOff(Switch)
- }
- }
- func (o *YmLampController) ConfirmState(t time.Time) {
- //刚开/关灯,则不判断,避免采集数据误差导致误判
- if !o.tswitch.IsZero() {
- if t.After(o.tswitch) && t.Sub(o.tswitch).Seconds() < float64(o.devinfo.SendCloud/1000.0) {
- return
- }
- if t.Before(o.tswitch) && o.tswitch.Sub(t).Seconds() < float64(o.devinfo.SendCloud/1000.0) {
- return
- }
- }
- if len(o.State) == 0 {
- return
- }
- s := o.State[0]
- if o.mapTempLampsOOT != nil { //优先手动控制
- if t.After(time.Time(o.mapTempLampsOOT.End)) { //过时,则清除
- if err := redisEdgeData.Del(LampSwitchPrefix + o.devinfo.DevCode).Err(); err == nil {
- o.mapTempLampsOOT = nil
- o.tswitch = time.Time{}
- }
- } else if o.mapTempLampsOOT.isInTimeRange(t) {
- if o.mapTempLampsOOT.Brightness > 0 && s.State == 0 { //应开未开
- o.Switch(1, o.mapTempLampsOOT.Brightness) //开灯
- } else if o.mapTempLampsOOT.Brightness == 0 && s.State == 1 { //应关闭未关
- o.Switch(0, o.mapTempLampsOOT.Brightness) //关灯
- }
- return
- }
- }
- isInRange := false
- for _, v := range o.mapLamps2OOT { //其次时间策略
- if v.InTimeRange(t) { //在开灯区间
- if v.Brightness > 0 && s.State == 0 { //应开未开
- o.Switch(1, v.Brightness) //开灯
- } else if v.Brightness == 0 && s.State == 1 { //应关闭未关
- o.Switch(0, v.Brightness) //关灯
- }
- isInRange = true
- break
- }
- }
- if isInRange {
- return
- }
- if s.State == 1 {
- o.Switch(0, 0)
- }
- }
- func (o *YmLampController) HandleTpYmSetSwitch(m mqtt.Message) {
- var obj protocol.Pack_CHZB_Switch
- if err := obj.DeCode(m.PayloadString()); err != nil {
- logrus.Errorf("协议解析错误:%s,协议主题:%s,协议内容:%s", err.Error(), m.Topic(), m.PayloadString())
- return
- }
- if obj.Id != o.devinfo.DevCode {
- return
- }
- brightness := uint8(0)
- if obj.Data.Switch == 1 {
- brightness = obj.Data.Brightness
- }
- err := o.Switch(obj.Data.Switch, obj.Data.Brightness)
- if err == nil {
- o.tswitch = util.MlNow()
- mapRedisTempLampsOOT := make(map[string]interface{}) //临时开关灯记录,用于排除异常亮灯正常亮灯的情况
- ltr := LampTimeRange{
- Start: util.MLTime(o.tswitch),
- End: util.MLTime(o.tswitch.Add(time.Duration(obj.Data.Recovery) * time.Second)), //延迟2分钟,以防指令在队列中未及时执行
- Brightness: brightness,
- }
- ltrstr, _ := json.MarshalToString(ltr)
- o.mapTempLampsOOT = <r //内存
- mapRedisTempLampsOOT[o.devinfo.DevCode] = ltrstr //redis
- if err := redisEdgeData.HSet(LampSwitchPrefix+o.devinfo.DevCode, mapRedisTempLampsOOT).Err(); err != nil {
- logrus.Errorf("手动开关灯时间设置[内容:%v]缓存到redis失败:%s", mapRedisTempLampsOOT, err.Error())
- }
- }
- var ret protocol.Pack_Ack
- if str, err := ret.EnCode(o.devinfo.DevCode, appConfig.GID, obj.Seq, err); err == nil {
- GetMQTTMgr().Publish(GetTopic(protocol.DT_LAMPCONTROLLER, o.devinfo.DevCode, protocol.TP_YM_SET_SWITCH_ACK), str, mqtt.AtMostOnce, ToAll)
- }
- }
- func (o *YmLampController) HandleTpYmSetOnofftime(m mqtt.Message) {
- var obj protocol.Pack_SetOnOffTime
- if err := obj.DeCode(m.PayloadString()); err != nil {
- logrus.Errorf("协议解析错误:%s,协议主题:%s,协议内容:%s", err.Error(), m.Topic(), m.PayloadString())
- return
- }
- if obj.Id != o.devinfo.DevCode {
- return
- }
- if len(obj.Data.OnOffTime) == 0 {
- logrus.Errorf("Handle_TP_YM_SET_ONOFFTIME:错误,灯控编号[%v],时间段个数:%v", obj.Id, obj.Data.OnOffTime)
- return
- }
- mapRedisOOT := make(map[string]interface{})
- datastr, _ := json.MarshalToString(obj.Data.OnOffTime)
- mapRedisOOT[obj.Id] = datastr //缓存到redis
- o.mapLamps2OOT = obj.Data.OnOffTime //缓存在内存中
- //持久缓存到redis,以便于重启后读取进内存中
- err := redisEdgeData.HSet(LampOotPrefix+o.devinfo.DevCode, mapRedisOOT).Err()
- if err != nil {
- logrus.Errorf("灯控时间设置[内容:%v]缓存到redis失败:%s", mapRedisOOT, err.Error())
- }
- var ret protocol.Pack_Ack
- if str, err := ret.EnCode(o.devinfo.DevCode, appConfig.GID, obj.Seq, err); err == nil {
- GetMQTTMgr().Publish(GetTopic(protocol.DT_LAMPCONTROLLER, o.devinfo.DevCode, protocol.TP_YM_SET_ONOFFTIME_ACK), str, mqtt.AtMostOnce, ToCloud)
- }
- }
- func (o *YmLampController) ReloadOOTFromRedis() error {
- mapdata, err := redisEdgeData.HGetAll(LampOotPrefix + o.devinfo.DevCode).Result()
- if err != nil {
- if err == redis.Nil {
- return nil
- }
- logrus.Errorf("YmLampController.ReloadOOTFromRedis设备[%s]从redis加载时间策略失败:%s", o.devinfo.DevCode, err.Error())
- return err
- }
- for k, v := range mapdata {
- if k == o.devinfo.DevCode {
- var oot []protocol.CHZB_OnOffTime
- if err := json.UnmarshalFromString(v, &oot); err == nil {
- o.mapLamps2OOT = oot
- }
- break
- }
- }
- return nil
- }
- func (o *YmLampController) ReloadSwitchOOTFromRedis() error {
- mapdata, err := redisEdgeData.HGetAll(LampSwitchPrefix + o.devinfo.DevCode).Result()
- if err != nil {
- if err == redis.Nil {
- return nil
- }
- logrus.Errorf("YmLampController.ReloadSwitchOOTFromRedis设备[%s]从redis加载时间策略失败:%s", o.devinfo.DevCode, err.Error())
- return err
- }
- for k, v := range mapdata {
- if k == o.devinfo.DevCode {
- var ltr LampTimeRange
- if err := json.UnmarshalFromString(v, <r); err == nil {
- o.mapTempLampsOOT = <r
- }
- break
- }
- }
- return nil
- }
- func (o *YmLampController) ReloadLampAlarmFromRedis() error {
- mapAlarm, err := redisEdgeData.HGetAll(LampAlarmPrefix + o.devinfo.DevCode).Result()
- if err != nil {
- if err == redis.Nil {
- return nil
- }
- logrus.Errorf("YmLampController.ReloadLampAlarmFromRedis设备[%s]从redis加载广播恢复截止时间失败:%s", o.devinfo.DevCode, err.Error())
- return err
- }
- for k, v := range mapAlarm {
- if k == o.devinfo.DevCode {
- var lai LampAlarmInfo
- if err := json.UnmarshalFromString(v, &lai); err == nil {
- o.mapLampAlarm = &lai
- }
- break
- }
- }
- return nil
- }
|