github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/zigbee2mqtt/bridge.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package zigbee2mqtt 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/e154/smart-home/common/apperr" 30 31 "github.com/e154/smart-home/adaptors" 32 "github.com/e154/smart-home/common" 33 m "github.com/e154/smart-home/models" 34 "github.com/e154/smart-home/system/mqtt" 35 ) 36 37 // Bridge ... 38 type Bridge struct { 39 adaptors *adaptors.Adaptors 40 mqtt mqtt.MqttServ 41 mqttClient mqtt.MqttCli 42 isStarted bool 43 settingsLock sync.Mutex 44 state string // online|offline 45 config BridgeConfig 46 devicesLock sync.Mutex 47 devices map[string]*Device 48 modelLock sync.Mutex 49 model *m.Zigbee2mqtt 50 networkmapLock sync.Mutex 51 scanInProcess bool 52 lastScan *time.Time 53 networkmap string 54 } 55 56 // NewBridge ... 57 func NewBridge(mqtt mqtt.MqttServ, 58 adaptors *adaptors.Adaptors, 59 model *m.Zigbee2mqtt) *Bridge { 60 return &Bridge{ 61 adaptors: adaptors, 62 devices: make(map[string]*Device), 63 model: model, 64 mqtt: mqtt, 65 } 66 } 67 68 // Start ... 69 func (g *Bridge) Start() { 70 71 if g.isStarted { 72 return 73 } 74 g.isStarted = true 75 76 log.Infof("bridge id %v, base topic: %v", g.model.Id, g.model.BaseTopic) 77 78 //todo add metric ... 79 80 if g.mqttClient == nil { 81 g.mqttClient = g.mqtt.NewClient(fmt.Sprintf("bridge_%v", g.model.Name)) 82 } 83 84 // /zigbee2mqtt/bridge/# 85 _ = g.mqttClient.Subscribe(fmt.Sprintf("%s/bridge/#", g.model.BaseTopic), g.onBridgePublish) 86 87 if err := g.safeGetDeviceList(); err != nil { 88 log.Error(err.Error()) 89 90 } 91 92 g.configPermitJoin(g.model.PermitJoin) 93 } 94 95 // Stop ... 96 func (g *Bridge) Stop(ctx context.Context) { 97 if !g.isStarted { 98 return 99 } 100 g.isStarted = false 101 g.mqtt.RemoveClient(fmt.Sprintf("bridge_%v", g.model.Name)) 102 g.mqttClient.UnsubscribeAll() 103 } 104 105 func (g *Bridge) onBridgePublish(client mqtt.MqttCli, message mqtt.Message) { 106 107 var topic = strings.Split(message.Topic, "/") 108 109 switch topic[2] { 110 case "state": 111 g.onBridgeStatePublish(client, message) 112 case "log", "logging": 113 g.onLogPublish(client, message) 114 case "config": 115 g.onConfigPublish(client, message) 116 case "networkmap": 117 g.onNetworkmapPublish(client, message) 118 case "devices": 119 g.onDevices(client, message) 120 case "event": 121 g.onEvent(client, message) 122 default: 123 log.Warnf("unknown topic %v", message.Topic) 124 } 125 } 126 127 func (g *Bridge) onBridgeStatePublish(client mqtt.MqttCli, message mqtt.Message) { 128 g.settingsLock.Lock() 129 g.state = string(message.Payload) 130 g.settingsLock.Unlock() 131 } 132 133 func (g *Bridge) onNetworkmapPublish(client mqtt.MqttCli, message mqtt.Message) { 134 135 var topic = strings.Split(message.Topic, "/") 136 137 if len(topic) < 4 { 138 return 139 } 140 141 switch topic[3] { 142 case "raw": 143 log.Info("method not implemented") 144 case "graphviz": 145 g.networkmapLock.Lock() 146 g.scanInProcess = false 147 g.lastScan = common.Time(time.Now()) 148 g.networkmap = string(message.Payload) 149 g.networkmapLock.Unlock() 150 } 151 } 152 153 func (g *Bridge) onConfigPublish(client mqtt.MqttCli, message mqtt.Message) { 154 155 var topic = strings.Split(message.Topic, "/") 156 157 if len(topic) > 3 { 158 switch topic[3] { 159 case "devices": 160 g.onConfigDevicesPublish(client, message) 161 return 162 } 163 } 164 165 config := BridgeConfig{} 166 _ = json.Unmarshal(message.Payload, &config) 167 g.settingsLock.Lock() 168 g.config = config 169 g.settingsLock.Unlock() 170 } 171 172 func (g *Bridge) onConfigDevicesPublish(client mqtt.MqttCli, message mqtt.Message) {} 173 174 func (g *Bridge) safeGetDevice(friendlyName string) (device *Device, err error) { 175 176 g.devicesLock.Lock() 177 defer g.devicesLock.Unlock() 178 179 //log.Infof("Get device %v", friendlyName) 180 181 var ok bool 182 if device, ok = g.devices[friendlyName]; !ok { 183 err = apperr.ErrNotFound 184 return 185 } 186 187 return 188 } 189 190 func (g *Bridge) safeUpdateDevice(device *Device) (err error) { 191 g.devicesLock.Lock() 192 defer g.devicesLock.Unlock() 193 194 model := device.GetModel() 195 196 if _, err = g.adaptors.Zigbee2mqttDevice.GetById(context.Background(), device.friendlyName); err == nil { 197 log.Infof("update device %v ...", model.Id) 198 if err = g.adaptors.Zigbee2mqttDevice.Update(context.Background(), &model); err != nil { 199 log.Error(err.Error()) 200 return 201 } 202 device.GetImage() 203 204 } else { 205 log.Infof("add device %v ...", model.Id) 206 if err = g.adaptors.Zigbee2mqttDevice.Add(context.Background(), &model); err != nil { 207 return 208 } 209 //todo add metric ... 210 } 211 212 g.devices[device.friendlyName] = device 213 214 //TODO optimize 215 g.modelLock.Lock() 216 g.model.Devices = make([]*m.Zigbee2mqttDevice, len(g.devices)) 217 i := 0 218 for _, device := range g.devices { 219 g.model.Devices[i] = &device.model 220 i++ 221 } 222 g.modelLock.Unlock() 223 224 //todo add metric ... 225 226 return 227 } 228 229 func (g *Bridge) safeGetDeviceList() (err error) { 230 231 g.devicesLock.Lock() 232 defer g.devicesLock.Unlock() 233 234 for _, model := range g.model.Devices { 235 log.Infof("add device %v ...", model.Id) 236 g.devices[model.Id] = NewDevice(model.Id, model) 237 } 238 239 return 240 } 241 242 func (g *Bridge) onLogPublish(client mqtt.MqttCli, message mqtt.Message) { 243 var lm BridgeLog 244 _ = json.Unmarshal(message.Payload, &lm) 245 log.Infof("%s, %v, %s", lm.Message, lm.Meta, lm.Type) 246 } 247 248 func (g *Bridge) onEvent(client mqtt.MqttCli, message mqtt.Message) { 249 event := Event{} 250 _ = json.Unmarshal(message.Payload, &event) 251 switch event.Type { 252 case EventDeviceAnnounce: 253 case EventDeviceLeave: 254 g.deviceLeave(event.Data.FriendlyName) 255 case EventDeviceJoined: 256 g.deviceJoined(event.Data.FriendlyName) 257 case EventDeviceInterview: 258 g.deviceInterview(event) 259 default: 260 log.Warnf("unknown event type \"%s\"", event.Type) 261 262 } 263 } 264 265 func (g *Bridge) getState() string { 266 g.settingsLock.Lock() 267 defer g.settingsLock.Unlock() 268 return g.state 269 } 270 271 // get config 272 func (g *Bridge) getConfig() { 273 _ = g.mqttClient.Publish(g.topic("/bridge/config/get"), []byte{}) 274 } 275 276 // get device list 277 func (g *Bridge) getDevices() { 278 _ = g.mqttClient.Publish(g.topic("/bridge/config/devices/get"), []byte{}) 279 } 280 281 func (g *Bridge) configPermitJoin(tr bool) { 282 var permitJoin = "true" 283 if !tr { 284 permitJoin = "false" 285 } 286 _ = g.mqttClient.Publish(g.topic("/bridge/config/permit_join"), []byte(permitJoin)) 287 } 288 289 func (g *Bridge) configLastSeen() {} 290 func (g *Bridge) configElapsed() {} 291 292 // Resets the ZNP (CC2530/CC2531). 293 func (g *Bridge) configReset() { 294 _ = g.mqttClient.Publish(g.topic("/bridge/config/reset"), []byte{}) 295 } 296 297 // ConfigReset ... 298 func (g *Bridge) ConfigReset() { 299 g.configReset() 300 time.Sleep(time.Second * 5) 301 } 302 303 func (g *Bridge) configLogLevel() {} 304 305 // DeviceOptions ... 306 func (g *Bridge) DeviceOptions() {} 307 308 // Remove ... 309 func (g *Bridge) Remove(friendlyName string) { 310 _ = g.mqttClient.Publish(g.topic("/bridge/config/remove"), []byte(friendlyName)) 311 } 312 313 // Ban ... 314 func (g *Bridge) Ban(friendlyName string) { 315 _ = g.mqttClient.Publish(g.topic("/bridge/config/force_remove"), []byte(friendlyName)) 316 } 317 318 // Whitelist ... 319 func (g *Bridge) Whitelist(friendlyName string) { 320 _ = g.mqttClient.Publish(g.topic("/bridge/config/whitelist"), []byte(friendlyName)) 321 } 322 323 // RenameDevice ... 324 func (g *Bridge) RenameDevice(friendlyName, name string) (err error) { 325 326 var device *Device 327 if device, err = g.safeGetDevice(friendlyName); err != nil { 328 return 329 } 330 331 device.SetName(name) 332 333 err = g.safeUpdateDevice(device) 334 335 return 336 } 337 338 // RenameLast ... 339 func (g *Bridge) RenameLast() {} 340 341 // AddGroup ... 342 func (g *Bridge) AddGroup() {} 343 344 // RemoveGroup ... 345 func (g *Bridge) RemoveGroup() {} 346 347 // UpdateNetworkmap ... 348 func (g *Bridge) UpdateNetworkmap() { 349 g.networkmapLock.Lock() 350 defer g.networkmapLock.Unlock() 351 352 if g.scanInProcess { 353 return 354 } 355 g.scanInProcess = true 356 357 _ = g.mqttClient.Publish(g.topic("/bridge/networkmap"), []byte("graphviz")) 358 } 359 360 // Networkmap ... 361 func (g *Bridge) Networkmap() string { 362 g.networkmapLock.Lock() 363 defer g.networkmapLock.Unlock() 364 return g.networkmap 365 } 366 367 func (g *Bridge) topic(s string) string { 368 return fmt.Sprintf("%s%s", g.model.BaseTopic, s) 369 } 370 371 // GetDeviceTopic ... 372 func (g *Bridge) GetDeviceTopic(friendlyName string) string { 373 return g.topic("/" + friendlyName) 374 } 375 376 func (g *Bridge) deviceLeave(friendlyName string) { 377 device, err := g.safeGetDevice(friendlyName) 378 if err != nil { 379 return 380 } 381 device.SetStatus(removed) 382 if err = g.safeUpdateDevice(device); err != nil { 383 log.Error(err.Error()) 384 } 385 } 386 387 func (g *Bridge) deviceJoined(friendlyName string) { 388 device, err := g.safeGetDevice(friendlyName) 389 if err != nil { 390 return 391 } 392 device.SetStatus(active) 393 if err = g.safeUpdateDevice(device); err != nil { 394 log.Error(err.Error()) 395 } 396 } 397 398 func (g *Bridge) deviceInterview(event Event) { 399 400 log.Infof("device interview %s, status: %s", event.Data.FriendlyName, event.Data.Status) 401 402 if event.Data.Status != "successful" { 403 return 404 } 405 406 g.updateDevice(event.Data) 407 } 408 409 func (g *Bridge) onDevices(client mqtt.MqttCli, message mqtt.Message) { 410 411 devices := make([]DeviceInfo, 0) 412 _ = json.Unmarshal(message.Payload, &devices) 413 414 for _, device := range devices { 415 if device.Type == Coordinator || !device.InterviewCompleted { 416 continue 417 } 418 g.updateDevice(device) 419 420 go g.UpdateNetworkmap() 421 } 422 } 423 424 func (g *Bridge) deviceForceRemoved(friendlyName string) { 425 device, err := g.safeGetDevice(friendlyName) 426 if err != nil { 427 log.Error(err.Error()) 428 return 429 } 430 device.SetStatus(banned) 431 if err = g.safeUpdateDevice(device); err != nil { 432 log.Error(err.Error()) 433 } 434 } 435 436 func (g *Bridge) updateDevice(params DeviceInfo) { 437 438 payload, _ := json.Marshal(params) 439 model := &m.Zigbee2mqttDevice{ 440 Id: params.FriendlyName, 441 Zigbee2mqttId: g.model.Id, 442 Name: params.FriendlyName, 443 Type: params.Type, 444 Model: params.Definition.Model, 445 Description: params.Definition.Description, 446 Manufacturer: params.Definition.Vendor, 447 Status: active, 448 Payload: payload, 449 } 450 451 device := NewDevice(params.FriendlyName, model) 452 if err := g.safeUpdateDevice(device); err != nil { 453 log.Error(err.Error()) 454 } 455 } 456 457 // PermitJoin ... 458 func (g *Bridge) PermitJoin(permitJoin bool) { 459 g.modelLock.Lock() 460 defer g.modelLock.Unlock() 461 462 g.model.PermitJoin = permitJoin 463 if err := g.adaptors.Zigbee2mqtt.Update(context.Background(), g.model); err != nil { 464 return 465 } 466 g.configPermitJoin(g.model.PermitJoin) 467 } 468 469 // UpdateModel ... 470 func (g *Bridge) UpdateModel(model *m.Zigbee2mqtt) { 471 g.modelLock.Lock() 472 defer g.modelLock.Unlock() 473 474 g.model.Name = model.Name 475 g.model.BaseTopic = model.BaseTopic 476 g.model.Login = model.Login 477 g.model.EncryptedPassword = model.EncryptedPassword 478 g.model.PermitJoin = model.PermitJoin 479 480 g.configPermitJoin(g.model.PermitJoin) 481 } 482 483 // Info ... 484 func (g *Bridge) Info() (info *m.Zigbee2mqttInfo) { 485 486 g.networkmapLock.Lock() 487 g.settingsLock.Lock() 488 g.devicesLock.Lock() 489 490 defer func() { 491 g.networkmapLock.Unlock() 492 g.settingsLock.Unlock() 493 g.devicesLock.Unlock() 494 }() 495 496 model := m.Zigbee2mqtt{} 497 498 _ = common.Copy(&model, g.model, common.JsonEngine) 499 500 info = &m.Zigbee2mqttInfo{ 501 ScanInProcess: g.scanInProcess, 502 LastScan: g.lastScan, 503 Networkmap: g.networkmap, 504 Status: g.state, 505 } 506 507 return 508 } 509 510 // GetModel ... 511 func (g *Bridge) GetModel() (model m.Zigbee2mqtt) { 512 g.modelLock.Lock() 513 defer g.modelLock.Unlock() 514 515 model = m.Zigbee2mqtt{} 516 _ = common.Copy(&model, &g.model, common.JsonEngine) 517 518 return 519 }