chzbconcentrator.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. package main
  2. import (
  3. "context"
  4. "runtime/debug"
  5. "strconv"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/sirupsen/logrus"
  10. "lc/common/models"
  11. "lc/common/mqtt"
  12. "lc/common/protocol"
  13. "lc/common/util"
  14. )
  15. // 长河Zigbee 集控器
  16. type ChZigbeeConcentrator struct {
  17. ctx context.Context
  18. cancel context.CancelFunc
  19. lock sync.Mutex
  20. tenant string //租户
  21. gid string //基础数据,网关ID
  22. did string //基础数据,集控器设备编码
  23. tid uint16 //基础数据,集控器物模型ID
  24. mapLamp map[string]*ChZigbeeLampController //单灯数据,编号唯一
  25. state uint8 //实时数据,0在线,1离线
  26. lastStateTime time.Time //实时数据,最新状态时间
  27. lastDataTime time.Time //最新数据时间
  28. nextHourTime time.Time //实时数据
  29. queue *util.MlQueue //数据队列
  30. errCnt uint //错误计数
  31. mapTopicHandle map[string]func(m *mqtt.Message)
  32. }
  33. func NewChZigbeeConcentrator(tenant, did string) *ChZigbeeConcentrator {
  34. ctx, cancel := context.WithCancel(context.Background())
  35. chc := ChZigbeeConcentrator{
  36. ctx: ctx,
  37. cancel: cancel,
  38. tenant: tenant,
  39. did: did,
  40. mapLamp: make(map[string]*ChZigbeeLampController),
  41. queue: util.NewQueue(1000),
  42. mapTopicHandle: make(map[string]func(m *mqtt.Message)),
  43. }
  44. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_DATA)] = chc.handleTpChZigbeeData
  45. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_QUERY_LAMP)] = chc.handleTpChZigbeeQLamp
  46. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_SET_WAITTIME_ACK)] = chc.handleTpChZigbeeSetWaittimeAck
  47. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_SET_SWITCH_ACK)] = chc.handleTpChZigbeeSetSwitchAck
  48. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_SET_RECOVERY_AUTO_ACK)] = chc.handleTpChZigbeeSetRecoveryAutoAck
  49. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_SET_ONOFFTIME_ACK)] = chc.handleTpChZigbeeSetOnofftimeAck
  50. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_QUERY_ONOFFTIME_ACK)] = chc.handleTpChZigbeeQueryOnofftimeAck
  51. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_SET_UPDATE_LAMP_ACK)] = chc.handleTpChZigbeeSetUpdateLampAck
  52. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_QUERY_TIME_ACK)] = chc.handleTpChZigbeeQueryTimeAck
  53. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_SET_BROADCASTTIME_ACK)] = chc.handleTpChZigbeeSetBroadcastTimeAck
  54. chc.mapTopicHandle[GetTopic(tenant, protocol.DT_CONCENTRATOR, did, protocol.TP_CHZB_ALARM)] = chc.handleTpChZigbeeAlarm
  55. return &chc
  56. }
  57. func (o *ChZigbeeConcentrator) PutMessage(m *mqtt.Message) {
  58. o.queue.Put(m)
  59. }
  60. func (o *ChZigbeeConcentrator) Start() {
  61. go o.HandleQueue()
  62. }
  63. func (o *ChZigbeeConcentrator) Stop() {
  64. o.cancel()
  65. }
  66. func (o *ChZigbeeConcentrator) HandleQueue() {
  67. defer func() {
  68. if err := recover(); err != nil {
  69. logrus.Error("ChZigbeeConcentrator.HandleQueue发生异常:", string(debug.Stack()))
  70. time.Sleep(5 * time.Second)
  71. go o.HandleQueue()
  72. }
  73. }()
  74. o.UpdateDID()
  75. var exit = false
  76. timer := time.NewTicker(1 * time.Minute)
  77. timer5 := time.NewTicker(5 * time.Minute)
  78. //每天15点半同步日出日落时间
  79. var SyncSunset = util.New(util.MlNow()).BeginningOfDay().Add(14*time.Hour + 30*time.Minute)
  80. for {
  81. select {
  82. case <-o.ctx.Done():
  83. logrus.Error("ChZigbeeConcentrator.HandleQueue即将退出,原因:", o.ctx.Err())
  84. exit = true
  85. case <-timer.C: //每隔1分钟执行一次
  86. o.UpdateState() //更新集控器状态,防止无数据状态不更新
  87. o.UpdateLampControllerState() //更新灯控状态,防止无数据状态不更新
  88. case <-timer5.C: //每隔5分钟执行一次
  89. o.UpdateDID() //更新灯控设备编码
  90. //同步日出日落时间
  91. if util.MlNow().After(SyncSunset) {
  92. if err := o.SyncSunset(); err == nil {
  93. SyncSunset = SyncSunset.AddDate(0, 0, 1)
  94. }
  95. }
  96. default:
  97. //从队列钟获取指令执行
  98. if m, ok, quantity := o.queue.Get(); ok {
  99. if mm, ok := m.(*mqtt.Message); ok {
  100. if f, ok := o.mapTopicHandle[mm.Topic()]; ok {
  101. f(mm)
  102. } else {
  103. logrus.Error("ChZigbeeConcentrator.HandleQueue:不支持的主题:", mm.Topic())
  104. }
  105. }
  106. } else if quantity == 0 {
  107. if exit {
  108. return
  109. }
  110. time.Sleep(100 * time.Millisecond)
  111. }
  112. }
  113. }
  114. }
  115. func (o *ChZigbeeConcentrator) SyncSunset() error {
  116. arr, err := models.GetZigbeeLampstrategyByconcentrator(o.did)
  117. if err != nil {
  118. logrus.Errorf("集控器[%s]从数据库读取日出日落时间错误:%s", o.did, err.Error())
  119. return err
  120. }
  121. if len(arr) == 0 {
  122. return nil
  123. }
  124. //Strategy相同的一起发送,不同的分批发送
  125. mapTime := make(map[string]*protocol.CHZB_OnOffTime) //策略时间
  126. mapNumber := make(map[string][]uint32) //策略灯控编号,注意非编码
  127. for _, v := range arr {
  128. mapNumber[v.ID] = append(mapNumber[v.ID], uint32(v.Number))
  129. if _, ok := mapTime[v.Strategy]; ok {
  130. //已计算日出日落时间的,不再重复计算
  131. continue
  132. }
  133. var oot protocol.CHZB_OnOffTime
  134. var ls []models.LampStrategy
  135. if err := json.UnmarshalFromString(v.TimeInfo, &ls); err == nil && len(ls) > 0 {
  136. oot.Brightness = uint8(ls[0].Brightness)
  137. }
  138. //计算时间
  139. if rise, set, err := util.SunriseSunsetForChina(v.Latitude, v.Longitude); err == nil {
  140. onhour, _ := strconv.Atoi(strings.Split(set, ":")[0])
  141. onminite, _ := strconv.Atoi(strings.Split(set, ":")[1])
  142. offhour, _ := strconv.Atoi(strings.Split(rise, ":")[0])
  143. offminite, _ := strconv.Atoi(strings.Split(rise, ":")[1])
  144. oot.OnHour = uint8(onhour)
  145. oot.OnMinite = uint8(onminite)
  146. oot.OffHour = uint8(offhour)
  147. oot.OffMinite = uint8(offminite)
  148. }
  149. mapTime[v.Strategy] = &oot
  150. }
  151. for k, v := range mapNumber {
  152. if oot, ok := mapTime[k]; ok {
  153. var obj protocol.Pack_SetOnOffTime
  154. seq := GetNextSeq()
  155. if str, err := obj.EnCode(o.did, o.gid, seq, v, []protocol.CHZB_OnOffTime{*oot}); err == nil {
  156. topic := GetTopic(o.tenant, protocol.DT_CONCENTRATOR, o.did, protocol.TP_CHZB_SET_ONOFFTIME)
  157. err = GetMQTTMgr().Publish(topic, str, mqtt.AtLeastOnce)
  158. if err != nil {
  159. logrus.Errorf("SyncSunset:集控器[%s]对灯控[%v]发布日出日落消息错误:%s", o.did, v, err.Error())
  160. }
  161. var msg string
  162. if msg0, errmsg := json.MarshalIndent(obj, "", " "); errmsg == nil {
  163. msg = string(msg0)
  164. } else {
  165. msg = str
  166. }
  167. odb := models.DeviceCmdRecord{
  168. ID: seq,
  169. GID: o.gid,
  170. DID: o.did,
  171. Topic: topic,
  172. Message: msg,
  173. State: 0,
  174. }
  175. if err := models.G_db.Create(&odb).Error; err != nil {
  176. logrus.Errorf("集控器[%s]对灯控[%v]发布日出日落时间时指令入库错误:%s", o.did, v, err.Error())
  177. } else {
  178. logrus.Errorf("集控器[%s]对灯控[%v]发布日出日落时间时指令入库成功", o.did, v)
  179. }
  180. }
  181. }
  182. }
  183. return nil
  184. }
  185. func (o *ChZigbeeConcentrator) UpdateDID() {
  186. if arr, err := models.GetLampControllerByConcentrator(o.did); err == nil {
  187. maplamps := make(map[uint32]string, len(arr))
  188. for _, v := range arr {
  189. if ld, ok := o.mapLamp[v.ID]; ok {
  190. ld.SetDID(v.ID, uint32(v.Number))
  191. } else {
  192. var ld ChZigbeeLampController
  193. ld.SetDID(v.ID, uint32(v.Number))
  194. o.mapLamp[v.ID] = &ld
  195. }
  196. if v.Number != 0xFEFE { //0xFEFE为广播地址,禁止使用
  197. maplamps[uint32(v.Number)] = v.ID
  198. }
  199. o.gid = v.GID
  200. }
  201. if len(maplamps) > 0 {
  202. var ret protocol.Pack_CHZB_LampIDs
  203. if str, err := ret.EnCode(o.did, o.gid, GetNextSeq(), maplamps); err == nil {
  204. topic := GetTopic(o.tenant, protocol.DT_CONCENTRATOR, o.did, protocol.TP_CHZB_SET_UPDATE_LAMP)
  205. if err := GetMQTTMgr().Publish(topic, str, mqtt.AtLeastOnce); err != nil {
  206. logrus.Errorf("UpdateDID:发布消息错误:%s", err.Error())
  207. }
  208. }
  209. }
  210. }
  211. //更新长和zigbee灯控物模型
  212. if o.tid > 0 {
  213. if err := models.UpdateTID(o.did, int(o.tid)); err != nil {
  214. logrus.Errorf("集控器[%s]更新灯控物模型失败:%s", o.did, err.Error())
  215. }
  216. }
  217. }
  218. func (o *ChZigbeeConcentrator) handleStateChange(t time.Time) {
  219. //最新状态
  220. state := uint8(0)
  221. if o.errCnt >= 10 {
  222. state = 1
  223. } else if o.errCnt == 0 {
  224. state = 0
  225. } else {
  226. return
  227. }
  228. //状态处理
  229. if o.lastStateTime.IsZero() || o.state == 0xff {
  230. t0, s0, err := getState(o.did)
  231. if err != nil {
  232. o.state = state
  233. o.lastStateTime = t
  234. return
  235. }
  236. o.state = s0
  237. o.lastStateTime = t0
  238. }
  239. if o.state == 0 && state == 1 { //在线->离线
  240. GetEventMgr().PushEvent(&EventObject{ID: o.did, EventType: models.ET_OFFLINE, Time: t})
  241. } else if o.state == 1 && state == 0 { //离线->在线
  242. GetEventMgr().PushEvent(&EventObject{ID: o.did, EventType: models.ET_ONLINE, Time: t})
  243. }
  244. o.state = state
  245. o.lastStateTime = t
  246. }
  247. func (o *ChZigbeeConcentrator) UpdateState() {
  248. if o.lastStateTime.IsZero() {
  249. t0, s0, err := getState(o.did)
  250. if err == nil {
  251. o.state = s0
  252. o.lastStateTime = t0
  253. }
  254. }
  255. if o.state == protocol.FAILED ||
  256. (!o.lastDataTime.IsZero() && util.MlNow().Sub(o.lastDataTime).Minutes() < OfflineInterval) {
  257. return
  258. }
  259. //如果之前一直是在线状态的,则置为离线;若之前是离线状态的,则不修改状态
  260. if o.state == protocol.SUCCESS {
  261. o.state = protocol.FAILED
  262. o.lastStateTime = util.MlNow()
  263. GetEventMgr().PushEvent(&EventObject{ID: o.did, EventType: models.ET_OFFLINE, Time: o.lastStateTime})
  264. cacheState(o.did, o.lastStateTime.Format("2006-01-02 15:04:05"), o.state)
  265. }
  266. }
  267. func (o *ChZigbeeConcentrator) UpdateLampControllerState() {
  268. for _, v := range o.mapLamp {
  269. v.UpdateState()
  270. }
  271. }
  272. func (o *ChZigbeeConcentrator) handleTpChZigbeeData(m *mqtt.Message) {
  273. var obj protocol.Pack_CHZB_UploadData
  274. if err := obj.DeCode(m.PayloadString()); err != nil {
  275. return
  276. }
  277. t, err := util.MlParseTime(obj.Time)
  278. if err != nil {
  279. logrus.Errorf("时间[%s]解析错误:%s", obj.Time, err.Error())
  280. return
  281. }
  282. o.tid = obj.Data.TID //更新物模型TID
  283. o.gid = obj.Gid
  284. //处理集控器定时上报的单灯数据
  285. var errCnt_ uint = 0
  286. for k, v := range obj.Data.Data {
  287. //未找到的等待下次更新灯控信息
  288. if ld, ok := o.mapLamp[k]; ok {
  289. ld.HandleData(o.tenant, obj.Gid, o.did, o.tid, t, v)
  290. }
  291. if v.State == protocol.FAILED {
  292. errCnt_++
  293. }
  294. }
  295. if errCnt_ == uint(len(obj.Data.Data)) {
  296. o.errCnt++
  297. } else {
  298. o.errCnt = 0
  299. }
  300. //先处理状态变化,再存入最新状态
  301. o.handleStateChange(t)
  302. cacheState(o.did, obj.Time, o.state)
  303. o.lastDataTime = t
  304. }
  305. func (o *ChZigbeeConcentrator) handleTpChZigbeeAlarm(m *mqtt.Message) {
  306. var obj protocol.Pack_CHZB_LampAlarm
  307. if err := obj.DeCode(m.PayloadString()); err != nil {
  308. return
  309. }
  310. if ld, ok := o.mapLamp[obj.Data.DID]; ok {
  311. ld.HandleAlarm(obj.Data)
  312. } else {
  313. logrus.Errorf("未找到灯控:%s", obj.Data.DID)
  314. }
  315. }
  316. func (o *ChZigbeeConcentrator) handleTpChZigbeeQLamp(m *mqtt.Message) {
  317. var obj protocol.Pack_CHZB_EmptyObject
  318. if err := obj.DeCode(m.PayloadString()); err != nil {
  319. return
  320. }
  321. arr, err := models.GetLampControllerByConcentrator(obj.Id)
  322. if err != nil || len(arr) == 0 {
  323. return
  324. }
  325. lamps := make(map[uint32]string, len(arr))
  326. for _, v := range arr {
  327. if v.Number != 0xFEFE { //0xFEFE为广播地址,禁止使用
  328. lamps[uint32(v.Number)] = v.ID
  329. }
  330. //顺带更新
  331. if ld, ok := o.mapLamp[v.ID]; ok {
  332. ld.SetDID(v.ID, uint32(v.Number))
  333. } else {
  334. var ld ChZigbeeLampController
  335. ld.SetDID(v.ID, uint32(v.Number))
  336. o.mapLamp[v.ID] = &ld
  337. }
  338. }
  339. var ret protocol.Pack_CHZB_LampIDs
  340. if str, err := ret.EnCode(o.did, o.gid, GetNextSeq(), lamps); err == nil {
  341. topic := GetTopic(o.tenant, protocol.DT_CONCENTRATOR, o.did, protocol.TP_CHZB_SET_UPDATE_LAMP)
  342. if err := GetMQTTMgr().Publish(topic, str, mqtt.AtMostOnce); err != nil {
  343. logrus.Errorf("handle_TP_CHZB_Q_LAMP:发布消息错误:%s", err.Error())
  344. }
  345. }
  346. }
  347. func (o *ChZigbeeConcentrator) handleTpChZigbeeSetWaittimeAck(m *mqtt.Message) {
  348. var obj protocol.Pack_Ack
  349. if err := obj.DeCode(m.PayloadString()); err != nil {
  350. return
  351. }
  352. oo := models.DeviceCmdRecord{
  353. ID: obj.Seq,
  354. State: uint(obj.Data.State),
  355. Resp: obj.Data.Error,
  356. }
  357. if err := oo.Update(); err != nil {
  358. logrus.Errorf("收到网关[%s]的响应[seq:%d],主题:%s,但更新数据库失败[%s]",
  359. obj.Id, obj.Seq, m.Topic(), err.Error())
  360. }
  361. }
  362. func (o *ChZigbeeConcentrator) handleTpChZigbeeSetSwitchAck(m *mqtt.Message) {
  363. var obj protocol.Pack_CHZB_SeqLampAck
  364. if err := obj.DeCode(m.PayloadString()); err != nil {
  365. return
  366. }
  367. resq, _ := json.MarshalIndent(obj.Data.MapLamp, "", " ")
  368. oo := models.DeviceCmdRecord{
  369. ID: obj.Seq,
  370. State: 1,
  371. Resp: string(resq),
  372. }
  373. if err := oo.Update(); err != nil {
  374. logrus.Errorf("收到网关[%s]的响应[seq:%d],主题:%s,但更新数据库失败[%s]",
  375. obj.Id, obj.Seq, m.Topic(), err.Error())
  376. }
  377. }
  378. func (o *ChZigbeeConcentrator) handleTpChZigbeeSetRecoveryAutoAck(m *mqtt.Message) {
  379. var obj protocol.Pack_CHZB_SeqLampAck
  380. if err := obj.DeCode(m.PayloadString()); err != nil {
  381. return
  382. }
  383. resq, _ := json.MarshalIndent(obj.Data.MapLamp, "", " ")
  384. oo := models.DeviceCmdRecord{
  385. ID: obj.Seq,
  386. State: 1,
  387. Resp: string(resq),
  388. }
  389. if err := oo.Update(); err != nil {
  390. logrus.Errorf("收到网关[%s]的响应[seq:%d],主题:%s,但更新数据库失败[%s]",
  391. obj.Id, obj.Seq, m.Topic(), err.Error())
  392. }
  393. }
  394. func (o *ChZigbeeConcentrator) handleTpChZigbeeSetOnofftimeAck(m *mqtt.Message) {
  395. var obj protocol.Pack_CHZB_SeqLampAck
  396. if err := obj.DeCode(m.PayloadString()); err != nil {
  397. return
  398. }
  399. resq, _ := json.MarshalIndent(obj.Data.MapLamp, "", " ")
  400. oo := models.DeviceCmdRecord{
  401. ID: obj.Seq,
  402. State: 1,
  403. Resp: string(resq),
  404. }
  405. if err := oo.Update(); err != nil {
  406. logrus.Errorf("收到网关[%s]的响应[seq:%d],主题:%s,但更新数据库失败[%s]",
  407. obj.Id, obj.Seq, m.Topic(), err.Error())
  408. }
  409. }
  410. func (o *ChZigbeeConcentrator) handleTpChZigbeeQueryOnofftimeAck(m *mqtt.Message) {
  411. var obj protocol.Pack_CHZB_QueryOnOffTimeAck
  412. if err := obj.DeCode(m.PayloadString()); err != nil {
  413. return
  414. }
  415. oo := models.DeviceCmdRecord{
  416. ID: obj.Seq,
  417. State: 1,
  418. Resp: obj.Data.Error,
  419. }
  420. if err := oo.Update(); err != nil {
  421. logrus.Errorf("收到网关[%s]的响应[seq:%d],主题:%s,但更新数据库失败[%s]",
  422. obj.Id, obj.Seq, m.Topic(), err.Error())
  423. }
  424. }
  425. func (o *ChZigbeeConcentrator) handleTpChZigbeeSetUpdateLampAck(m *mqtt.Message) {
  426. var obj protocol.Pack_Ack
  427. if err := obj.DeCode(m.PayloadString()); err != nil {
  428. return
  429. }
  430. oo := models.DeviceCmdRecord{
  431. ID: obj.Seq,
  432. State: 1,
  433. Resp: obj.Data.Error,
  434. }
  435. if err := oo.Update(); err != nil {
  436. logrus.Errorf("收到网关[%s]的响应[seq:%d],主题:%s,但更新数据库失败[%s]",
  437. obj.Id, obj.Seq, m.Topic(), err.Error())
  438. }
  439. }
  440. func (o *ChZigbeeConcentrator) handleTpChZigbeeQueryTimeAck(m *mqtt.Message) {
  441. var obj protocol.Pack_CHZB_QueryTimeAck
  442. if err := obj.DeCode(m.PayloadString()); err != nil {
  443. return
  444. }
  445. oo := models.DeviceCmdRecord{
  446. ID: obj.Seq,
  447. State: 1,
  448. Resp: obj.Data.Error,
  449. }
  450. if err := oo.Update(); err != nil {
  451. logrus.Errorf("收到网关[%s]的响应[seq:%d],主题:%s,但更新数据库失败[%s]",
  452. obj.Id, obj.Seq, m.Topic(), err.Error())
  453. }
  454. if obj.Data.LampTime != nil {
  455. logrus.Errorf("集控器[编码为:%s]的灯控[编号为:%d]当前时间为:%02d:%02d:%02d",
  456. o.did, obj.Data.LampID, obj.Data.LampTime.Hour, obj.Data.LampTime.Minite, obj.Data.LampTime.Second)
  457. }
  458. }
  459. func (o *ChZigbeeConcentrator) handleTpChZigbeeSetBroadcastTimeAck(m *mqtt.Message) {
  460. var obj protocol.Pack_Ack
  461. if err := obj.DeCode(m.PayloadString()); err != nil {
  462. return
  463. }
  464. oo := models.DeviceCmdRecord{
  465. ID: obj.Seq,
  466. State: 1,
  467. Resp: obj.Data.Error,
  468. }
  469. if err := oo.Update(); err != nil {
  470. logrus.Errorf("收到网关[%s]的响应[seq:%d],主题:%s,但更新数据库失败[%s]",
  471. obj.Id, obj.Seq, m.Topic(), err.Error())
  472. }
  473. }