Added new device: computer
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dreaded_X 2022-11-15 03:16:08 +01:00
parent ad5b8f9d29
commit bfeedece77
Signed by: Dreaded_X
GPG Key ID: 76BDEC4E165D8AD9
6 changed files with 128 additions and 48 deletions

64
device/computer.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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

View File

@ -5,4 +5,5 @@ type Type string
// https://developers.google.com/assistant/smarthome/guides
const (
TypeKettle = "action.devices.types.KETTLE"
TypeScene = "action.devices.types.SCENE"
)

View File

@ -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() {