228 lines
5.6 KiB
Go
228 lines
5.6 KiB
Go
package device
|
|
|
|
import (
|
|
"automation/integration/google"
|
|
"automation/integration/kasa"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/kr/pretty"
|
|
"google.golang.org/api/homegraph/v1"
|
|
"google.golang.org/api/option"
|
|
|
|
paho "github.com/eclipse/paho.mqtt.golang"
|
|
)
|
|
|
|
type BaseDevice interface {
|
|
GetName() string
|
|
}
|
|
|
|
type Devices struct {
|
|
Devices map[string]interface{}
|
|
}
|
|
|
|
func NewDevices() *Devices {
|
|
return &Devices{Devices: make(map[string]interface{})}
|
|
}
|
|
|
|
func (d *Devices) GetGoogleDevices() map[string]google.DeviceInterface {
|
|
devices := make(map[string]google.DeviceInterface)
|
|
|
|
for _, device := range d.Devices {
|
|
if gd, ok := device.(google.DeviceInterface); ok {
|
|
// Instead of using name we use the internal ID for google, that way devices can freely be renamed without causing issues with google home
|
|
devices[gd.GetID()] = gd
|
|
}
|
|
}
|
|
|
|
return devices
|
|
}
|
|
|
|
func (d *Devices) GetGoogleDevice(name string) (google.DeviceInterface, error) {
|
|
device, ok := d.GetGoogleDevices()[name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Device does not exist")
|
|
}
|
|
|
|
return device, nil
|
|
}
|
|
|
|
func (d *Devices) GetZigbeeDevices() map[string]ZigbeeDevice {
|
|
devices := make(map[string]ZigbeeDevice)
|
|
|
|
for name, device := range d.Devices {
|
|
if zd, ok := device.(ZigbeeDevice); ok {
|
|
devices[name] = zd
|
|
}
|
|
}
|
|
|
|
return devices
|
|
}
|
|
|
|
func (d *Devices) GetKasaDevices() map[string]*kasa.Kasa {
|
|
devices := make(map[string]*kasa.Kasa)
|
|
|
|
for _, device := range d.Devices {
|
|
if gd, ok := device.(*kasa.Kasa); ok {
|
|
// Instead of using name we use the internal ID for google, that way devices can freely be renamed without causing issues with google home
|
|
devices[gd.GetName()] = gd
|
|
}
|
|
}
|
|
|
|
return devices
|
|
}
|
|
|
|
func (d *Devices) GetKasaDevice(name string) (*kasa.Kasa, error) {
|
|
device, ok := d.GetKasaDevices()[name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Device does not exist")
|
|
}
|
|
|
|
return device, nil
|
|
}
|
|
|
|
type DeviceInfo struct {
|
|
IEEEAdress string `json:"ieee_address"`
|
|
FriendlyName string `json:"friendly_name"`
|
|
Description string `json:"description"`
|
|
Manufacturer string `json:"manufacturer"`
|
|
ModelID string `json:"model_id"`
|
|
SoftwareBuildID string `json:"software_build_id"`
|
|
}
|
|
|
|
type ZigbeeDevice interface {
|
|
GetDeviceInfo() DeviceInfo
|
|
SetState(state bool)
|
|
}
|
|
|
|
type DeviceInterface interface {
|
|
google.DeviceInterface
|
|
SetState(state bool)
|
|
}
|
|
|
|
type Provider struct {
|
|
Service *google.Service
|
|
userID string
|
|
|
|
Devices *Devices
|
|
}
|
|
|
|
type credentials []byte
|
|
type Config struct {
|
|
Credentials credentials `yaml:"credentials" envconfig:"GOOGLE_CREDENTIALS"`
|
|
}
|
|
|
|
func (c *credentials) Decode(value string) error {
|
|
b, err := base64.StdEncoding.DecodeString(value)
|
|
*c = b
|
|
|
|
return err
|
|
}
|
|
|
|
// Auto populate and update the device list
|
|
func (p *Provider) devicesHandler(client paho.Client, msg paho.Message) {
|
|
var devices []DeviceInfo
|
|
json.Unmarshal(msg.Payload(), &devices)
|
|
|
|
log.Println("zigbee2mqtt devices:")
|
|
pretty.Logln(devices)
|
|
|
|
for name := range p.Devices.GetZigbeeDevices() {
|
|
// Delete all zigbee devices from the device list
|
|
delete(p.Devices.Devices, name)
|
|
}
|
|
|
|
for _, device := range devices {
|
|
switch device.Description {
|
|
case "Kettle":
|
|
kettle := NewKettle(device, client, p.Service)
|
|
p.Devices.Devices[kettle.GetDeviceInfo().FriendlyName] = kettle
|
|
log.Printf("Added Kettle (%s) %s\n", kettle.GetDeviceInfo().IEEEAdress, kettle.GetDeviceInfo().FriendlyName)
|
|
}
|
|
}
|
|
|
|
// Send sync request
|
|
p.Service.RequestSync(context.Background(), p.userID)
|
|
}
|
|
|
|
func NewProvider(config Config, client paho.Client) *Provider {
|
|
provider := &Provider{userID: "Dreaded_X", Devices: NewDevices()}
|
|
|
|
homegraphService, err := homegraph.NewService(context.Background(), option.WithCredentialsJSON(config.Credentials))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
provider.Service = google.NewService(provider, homegraphService)
|
|
|
|
if token := client.Subscribe("zigbee2mqtt/bridge/devices", 1, provider.devicesHandler); token.Wait() && token.Error() != nil {
|
|
log.Println(token.Error())
|
|
}
|
|
|
|
return provider
|
|
}
|
|
|
|
func (p *Provider) AddDevice(device BaseDevice) {
|
|
p.Devices.Devices[device.GetName()] = device
|
|
}
|
|
|
|
func (p *Provider) Sync(_ context.Context, _ string) ([]*google.Device, error) {
|
|
var devices []*google.Device
|
|
|
|
for _, device := range p.Devices.GetGoogleDevices() {
|
|
devices = append(devices, device.Sync())
|
|
}
|
|
|
|
return devices, nil
|
|
}
|
|
|
|
func (p *Provider) Query(_ context.Context, _ string, handles []google.DeviceHandle) (map[string]google.DeviceState, error) {
|
|
states := make(map[string]google.DeviceState)
|
|
|
|
for _, handle := range handles {
|
|
if device, err := p.Devices.GetGoogleDevice(handle.ID); err == nil {
|
|
states[handle.ID] = device.Query()
|
|
} else {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
|
|
return states, nil
|
|
}
|
|
|
|
func (p *Provider) Execute(_ context.Context, _ string, commands []google.Command) (*google.ExecuteResponse, error) {
|
|
resp := &google.ExecuteResponse{
|
|
UpdatedState: google.NewDeviceState(true),
|
|
FailedDevices: make(map[string]struct{ Devices []string }),
|
|
}
|
|
|
|
for _, command := range commands {
|
|
for _, execution := range command.Execution {
|
|
for _, handle := range command.Devices {
|
|
if device, err := p.Devices.GetGoogleDevice(handle.ID); err == nil {
|
|
errCode, online := device.Execute(execution, &resp.UpdatedState)
|
|
|
|
// Update the state
|
|
p.Devices.Devices[handle.ID] = device
|
|
if !online {
|
|
resp.OfflineDevices = append(resp.OfflineDevices, handle.ID)
|
|
} else if len(errCode) == 0 {
|
|
resp.UpdatedDevices = append(resp.UpdatedDevices, handle.ID)
|
|
} else {
|
|
e := resp.FailedDevices[errCode]
|
|
e.Devices = append(e.Devices, handle.ID)
|
|
resp.FailedDevices[errCode] = e
|
|
}
|
|
} else {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return resp, nil
|
|
}
|