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  }