From bfeedece77de1de563e51ea2a116db8c2be9d884 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Tue, 15 Nov 2022 03:16:08 +0100 Subject: [PATCH] Added new device: computer --- device/computer.go | 64 ++++++++++++++++++++++++++++++ device/kettle.go | 74 ++++++++++++++++++----------------- device/provider.go | 28 ++++++++----- integration/google/handler.go | 1 + integration/google/type.go | 3 +- main.go | 6 ++- 6 files changed, 128 insertions(+), 48 deletions(-) create mode 100644 device/computer.go diff --git a/device/computer.go b/device/computer.go new file mode 100644 index 0000000..b3ba1f0 --- /dev/null +++ b/device/computer.go @@ -0,0 +1,64 @@ +package device + +import ( + "automation/integration/google" + "log" + "net/http" +) + +type computer struct { + macAddress string + name string + room string +} + +func NewComputer(macAddress string, name string, room string) *computer { + c := &computer{macAddress: macAddress, name: name, room: room} + + return c +} + +func (c *computer) Sync() *google.Device { + device := google.NewDevice(c.GetID(), google.TypeScene) + device.AddSceneTrait(false) + + device.Name = google.DeviceName{ + DefaultNames: []string{ + "Computer", + }, + Name: c.name, + } + device.RoomHint = c.room + + return device +} + +func (c *computer) Query() google.DeviceState { + state := google.NewDeviceState(true) + state.Status = google.StatusSuccess + + return state +} + +func (c *computer) Execute(execution google.Execution, updateState *google.DeviceState) (string, bool) { + errCode := "" + + switch execution.Name { + case google.CommandActivateScene: + if !execution.ActivateScene.Deactivate { + // @NOTE For now just call the webhook, that way we do not have to give this docker container network_mode: host + // @TODO Add wake on lan support + + http.Get("https://webhook.huizinga.dev/start-pc?token=7!$8bmjfZsT606Rmw5IrfIXhQWt6clTY") + } + default: + errCode = "actionNotAvailable" + log.Printf("Command (%s) not supported\n", execution.Name) + } + + return errCode, true +} + +func (c *computer) GetID() string { + return c.macAddress +} diff --git a/device/kettle.go b/device/kettle.go index 0e9275e..264fb65 100644 --- a/device/kettle.go +++ b/device/kettle.go @@ -1,4 +1,4 @@ -package smarthome +package device import ( "automation/integration/mqtt" @@ -13,7 +13,7 @@ import ( paho "github.com/eclipse/paho.mqtt.golang" ) -type outlet struct { +type kettle struct { Info DeviceInfo m *mqtt.MQTT updated chan bool @@ -22,12 +22,12 @@ type outlet struct { online bool } -func (o *outlet) getState() google.DeviceState { - return google.NewDeviceState(o.online).RecordOnOff(o.isOn) +func (k *kettle) getState() google.DeviceState { + return google.NewDeviceState(k.online).RecordOnOff(k.isOn) } -func NewKettle(info DeviceInfo, m *mqtt.MQTT, s *google.Service) *outlet { - o := &outlet{Info: info, m: m, updated: make(chan bool, 1)} +func NewKettle(info DeviceInfo, m *mqtt.MQTT, s *google.Service) *kettle { + k := &kettle{Info: info, m: m, updated: make(chan bool, 1)} const length = 5 * time.Minute timer := time.NewTimer(length) @@ -41,45 +41,45 @@ func NewKettle(info DeviceInfo, m *mqtt.MQTT, s *google.Service) *outlet { } }() - o.m.AddHandler(fmt.Sprintf("zigbee2mqtt/%s", o.Info.FriendlyName), func (_ paho.Client, msg paho.Message) { + k.m.AddHandler(fmt.Sprintf("zigbee2mqtt/%s", k.Info.FriendlyName), func (_ paho.Client, msg paho.Message) { var payload struct { State string `json:"state"` } json.Unmarshal(msg.Payload(), &payload) // Update the internal state - o.isOn = payload.State == "ON" - o.online = true + k.isOn = payload.State == "ON" + k.online = true // Notify that the state has updated - for len(o.updated) > 0 { - <- o.updated + for len(k.updated) > 0 { + <- k.updated } - o.updated <- true + k.updated <- true // Notify google of the updated state - id := o.Info.IEEEAdress + id := k.GetID() s.ReportState(context.Background(), id, map[string]google.DeviceState{ - id: o.getState(), + id: k.getState(), }) - if o.isOn { + if k.isOn { timer.Reset(length) } else { timer.Stop() } }) - o.m.Publish(fmt.Sprintf("zigbee2mqtt/%s/get", o.Info.FriendlyName), 1, false, `{ "state": "" }`) + k.m.Publish(fmt.Sprintf("zigbee2mqtt/%s/get", k.Info.FriendlyName), 1, false, `{ "state": "" }`) - return o + return k } -func (o* outlet) Sync() *google.Device { - device := google.NewDevice(o.Info.IEEEAdress, google.TypeKettle) +func (k *kettle) Sync() *google.Device { + device := google.NewDevice(k.GetID(), google.TypeKettle) device.AddOnOffTrait(false, false) - s := strings.Split(o.Info.FriendlyName, "/") + s := strings.Split(k.Info.FriendlyName, "/") room := "" name := s[0] if len(s) > 1 { @@ -102,21 +102,21 @@ func (o* outlet) Sync() *google.Device { } device.DeviceInfo = google.DeviceInfo{ - Manufacturer: o.Info.Manufacturer, - Model: o.Info.ModelID, - SwVersion: o.Info.SoftwareBuildID, + Manufacturer: k.Info.Manufacturer, + Model: k.Info.ModelID, + SwVersion: k.Info.SoftwareBuildID, } - o.m.Publish(fmt.Sprintf("zigbee2mqtt/%s/get", o.Info.FriendlyName), 1, false, `{ "state": "" }`) + k.m.Publish(fmt.Sprintf("zigbee2mqtt/%s/get", k.Info.FriendlyName), 1, false, `{ "state": "" }`) return device } -func (o *outlet) Query() google.DeviceState { +func (k *kettle) Query() google.DeviceState { // We just report out internal representation as it should always match the actual state - state := o.getState() + state := k.getState() // No /get needed - if o.online { + if k.online { state.Status = google.StatusSuccess } else { state.Status = google.StatusOffline @@ -125,7 +125,7 @@ func (o *outlet) Query() google.DeviceState { return state } -func (o *outlet) Execute(execution google.Execution, updatedState *google.DeviceState) (string, bool) { +func (k *kettle) Execute(execution google.Execution, updatedState *google.DeviceState) (string, bool) { errCode := "" switch execution.Name { @@ -136,24 +136,24 @@ func (o *outlet) Execute(execution google.Execution, updatedState *google.Device } // Clear the updated channel - for len(o.updated) > 0 { - <- o.updated + for len(k.updated) > 0 { + <- k.updated } // Update the state - o.m.Publish(fmt.Sprintf("zigbee2mqtt/%s/set", o.Info.FriendlyName), 1, false, fmt.Sprintf(`{ "state": "%s" }`, state)) + k.m.Publish(fmt.Sprintf("zigbee2mqtt/%s/set", k.Info.FriendlyName), 1, false, fmt.Sprintf(`{ "state": "%s" }`, state)) // Start timeout timer timer := time.NewTimer(time.Second) // Wait for the update or timeout select { - case <- o.updated: - updatedState.RecordOnOff(o.isOn) + case <- k.updated: + updatedState.RecordOnOff(k.isOn) case <- timer.C: // If we do not get a response in time mark the device as offline log.Println("Device did not respond, marking as offline") - o.online = false + k.online = false } default: @@ -162,5 +162,9 @@ func (o *outlet) Execute(execution google.Execution, updatedState *google.Device log.Printf("Command (%s) not supported\n", execution.Name) } - return errCode, o.online + return errCode, k.online +} + +func (k *kettle) GetID() string { + return k.Info.IEEEAdress } diff --git a/device/provider.go b/device/provider.go index 467e834..dd01009 100644 --- a/device/provider.go +++ b/device/provider.go @@ -1,4 +1,4 @@ -package smarthome +package device import ( "automation/integration/google" @@ -26,13 +26,14 @@ type DeviceInfo struct { } type Provider struct { - service *google.Service + Service *google.Service userID string devices map[string]google.DeviceInterface + manualDevices map[string]google.DeviceInterface } -func NewService(m *mqtt.MQTT) *google.Service { +func NewProvider(m *mqtt.MQTT) *Provider { credentials64, _ := os.LookupEnv("GOOGLE_CREDENTIALS") credentials, err := base64.StdEncoding.DecodeString(credentials64) if err != nil { @@ -40,15 +41,16 @@ func NewService(m *mqtt.MQTT) *google.Service { os.Exit(1) } - provider := &Provider{userID: "Dreaded_X", devices: make(map[string]google.DeviceInterface)} + provider := &Provider{userID: "Dreaded_X", devices: make(map[string]google.DeviceInterface), manualDevices: make(map[string]google.DeviceInterface)} homegraphService, err := homegraph.NewService(context.Background(), option.WithCredentialsJSON(credentials)) if err != nil { panic(err) } - provider.service = google.NewService(provider, homegraphService) + provider.Service = google.NewService(provider, homegraphService) + // Auto populate and update the device list m.AddHandler("zigbee2mqtt/bridge/devices", func(_ paho.Client, msg paho.Message) { var devices []DeviceInfo json.Unmarshal(msg.Payload(), &devices) @@ -56,22 +58,28 @@ func NewService(m *mqtt.MQTT) *google.Service { log.Println("zigbee2mqtt devices:") pretty.Logln(devices) - // Clear the list of devices in order to update it - provider.devices = make(map[string]google.DeviceInterface) + // Remove all automatically added devices + provider.devices = provider.manualDevices + for _, device := range devices { switch device.Description { case "Kettle": - outlet := NewKettle(device, m, provider.service) + outlet := NewKettle(device, m, provider.Service) provider.devices[device.IEEEAdress] = outlet log.Printf("Added Kettle (%s) %s\n", device.IEEEAdress, device.FriendlyName) } } // Send sync request - provider.service.RequestSync(context.Background(), provider.userID) + provider.Service.RequestSync(context.Background(), provider.userID) }) - return provider.service + return provider +} + +func (p *Provider) AddDevice(device google.DeviceInterface) { + p.devices[device.GetID()] = device + p.manualDevices[device.GetID()] = device } func (p *Provider) Sync(_ context.Context, _ string) ([]*google.Device, error) { diff --git a/integration/google/handler.go b/integration/google/handler.go index 419cadd..aca763e 100644 --- a/integration/google/handler.go +++ b/integration/google/handler.go @@ -10,6 +10,7 @@ type DeviceInterface interface { Sync() *Device Query() DeviceState Execute(execution Execution, updatedState *DeviceState) (errCode string, online bool) + GetID() string } // https://developers.google.com/assistant/smarthome/reference/intent/sync diff --git a/integration/google/type.go b/integration/google/type.go index f6d08e4..ebb49fb 100644 --- a/integration/google/type.go +++ b/integration/google/type.go @@ -4,5 +4,6 @@ type Type string // https://developers.google.com/assistant/smarthome/guides const ( - TypeKettle = "action.devices.types.KETTLE" + TypeKettle = "action.devices.types.KETTLE" + TypeScene = "action.devices.types.SCENE" ) diff --git a/main.go b/main.go index f3edd79..8b8005a 100644 --- a/main.go +++ b/main.go @@ -36,10 +36,12 @@ func main() { m.AddHandler("automation/presence/+", p.PresenceHandler) // Smart home - service := smarthome.NewService(&m) + provider := device.NewProvider(&m) + + provider.AddDevice(device.NewComputer("30:9c:23:60:9c:13", "Zeus", "Living Room")) r := mux.NewRouter() - r.HandleFunc("/assistant", service.FullfillmentHandler) + r.HandleFunc("/assistant", provider.Service.FullfillmentHandler) // Event loop go func() {