Added new device: computer
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
ad5b8f9d29
commit
bfeedece77
64
device/computer.go
Normal file
64
device/computer.go
Normal 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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package smarthome
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"automation/integration/mqtt"
|
"automation/integration/mqtt"
|
||||||
|
@ -13,7 +13,7 @@ import (
|
||||||
paho "github.com/eclipse/paho.mqtt.golang"
|
paho "github.com/eclipse/paho.mqtt.golang"
|
||||||
)
|
)
|
||||||
|
|
||||||
type outlet struct {
|
type kettle struct {
|
||||||
Info DeviceInfo
|
Info DeviceInfo
|
||||||
m *mqtt.MQTT
|
m *mqtt.MQTT
|
||||||
updated chan bool
|
updated chan bool
|
||||||
|
@ -22,12 +22,12 @@ type outlet struct {
|
||||||
online bool
|
online bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *outlet) getState() google.DeviceState {
|
func (k *kettle) getState() google.DeviceState {
|
||||||
return google.NewDeviceState(o.online).RecordOnOff(o.isOn)
|
return google.NewDeviceState(k.online).RecordOnOff(k.isOn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKettle(info DeviceInfo, m *mqtt.MQTT, s *google.Service) *outlet {
|
func NewKettle(info DeviceInfo, m *mqtt.MQTT, s *google.Service) *kettle {
|
||||||
o := &outlet{Info: info, m: m, updated: make(chan bool, 1)}
|
k := &kettle{Info: info, m: m, updated: make(chan bool, 1)}
|
||||||
|
|
||||||
const length = 5 * time.Minute
|
const length = 5 * time.Minute
|
||||||
timer := time.NewTimer(length)
|
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 {
|
var payload struct {
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
}
|
}
|
||||||
json.Unmarshal(msg.Payload(), &payload)
|
json.Unmarshal(msg.Payload(), &payload)
|
||||||
|
|
||||||
// Update the internal state
|
// Update the internal state
|
||||||
o.isOn = payload.State == "ON"
|
k.isOn = payload.State == "ON"
|
||||||
o.online = true
|
k.online = true
|
||||||
|
|
||||||
// Notify that the state has updated
|
// Notify that the state has updated
|
||||||
for len(o.updated) > 0 {
|
for len(k.updated) > 0 {
|
||||||
<- o.updated
|
<- k.updated
|
||||||
}
|
}
|
||||||
o.updated <- true
|
k.updated <- true
|
||||||
|
|
||||||
// Notify google of the updated state
|
// Notify google of the updated state
|
||||||
id := o.Info.IEEEAdress
|
id := k.GetID()
|
||||||
s.ReportState(context.Background(), id, map[string]google.DeviceState{
|
s.ReportState(context.Background(), id, map[string]google.DeviceState{
|
||||||
id: o.getState(),
|
id: k.getState(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if o.isOn {
|
if k.isOn {
|
||||||
timer.Reset(length)
|
timer.Reset(length)
|
||||||
} else {
|
} else {
|
||||||
timer.Stop()
|
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 {
|
func (k *kettle) Sync() *google.Device {
|
||||||
device := google.NewDevice(o.Info.IEEEAdress, google.TypeKettle)
|
device := google.NewDevice(k.GetID(), google.TypeKettle)
|
||||||
device.AddOnOffTrait(false, false)
|
device.AddOnOffTrait(false, false)
|
||||||
|
|
||||||
s := strings.Split(o.Info.FriendlyName, "/")
|
s := strings.Split(k.Info.FriendlyName, "/")
|
||||||
room := ""
|
room := ""
|
||||||
name := s[0]
|
name := s[0]
|
||||||
if len(s) > 1 {
|
if len(s) > 1 {
|
||||||
|
@ -102,21 +102,21 @@ func (o* outlet) Sync() *google.Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
device.DeviceInfo = google.DeviceInfo{
|
device.DeviceInfo = google.DeviceInfo{
|
||||||
Manufacturer: o.Info.Manufacturer,
|
Manufacturer: k.Info.Manufacturer,
|
||||||
Model: o.Info.ModelID,
|
Model: k.Info.ModelID,
|
||||||
SwVersion: o.Info.SoftwareBuildID,
|
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
|
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
|
// We just report out internal representation as it should always match the actual state
|
||||||
state := o.getState()
|
state := k.getState()
|
||||||
// No /get needed
|
// No /get needed
|
||||||
if o.online {
|
if k.online {
|
||||||
state.Status = google.StatusSuccess
|
state.Status = google.StatusSuccess
|
||||||
} else {
|
} else {
|
||||||
state.Status = google.StatusOffline
|
state.Status = google.StatusOffline
|
||||||
|
@ -125,7 +125,7 @@ func (o *outlet) Query() google.DeviceState {
|
||||||
return state
|
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 := ""
|
errCode := ""
|
||||||
|
|
||||||
switch execution.Name {
|
switch execution.Name {
|
||||||
|
@ -136,24 +136,24 @@ func (o *outlet) Execute(execution google.Execution, updatedState *google.Device
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the updated channel
|
// Clear the updated channel
|
||||||
for len(o.updated) > 0 {
|
for len(k.updated) > 0 {
|
||||||
<- o.updated
|
<- k.updated
|
||||||
}
|
}
|
||||||
// Update the state
|
// 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
|
// Start timeout timer
|
||||||
timer := time.NewTimer(time.Second)
|
timer := time.NewTimer(time.Second)
|
||||||
|
|
||||||
// Wait for the update or timeout
|
// Wait for the update or timeout
|
||||||
select {
|
select {
|
||||||
case <- o.updated:
|
case <- k.updated:
|
||||||
updatedState.RecordOnOff(o.isOn)
|
updatedState.RecordOnOff(k.isOn)
|
||||||
|
|
||||||
case <- timer.C:
|
case <- timer.C:
|
||||||
// If we do not get a response in time mark the device as offline
|
// If we do not get a response in time mark the device as offline
|
||||||
log.Println("Device did not respond, marking as offline")
|
log.Println("Device did not respond, marking as offline")
|
||||||
o.online = false
|
k.online = false
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -162,5 +162,9 @@ func (o *outlet) Execute(execution google.Execution, updatedState *google.Device
|
||||||
log.Printf("Command (%s) not supported\n", execution.Name)
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package smarthome
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"automation/integration/google"
|
"automation/integration/google"
|
||||||
|
@ -26,13 +26,14 @@ type DeviceInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
service *google.Service
|
Service *google.Service
|
||||||
userID string
|
userID string
|
||||||
|
|
||||||
devices map[string]google.DeviceInterface
|
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")
|
credentials64, _ := os.LookupEnv("GOOGLE_CREDENTIALS")
|
||||||
credentials, err := base64.StdEncoding.DecodeString(credentials64)
|
credentials, err := base64.StdEncoding.DecodeString(credentials64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -40,15 +41,16 @@ func NewService(m *mqtt.MQTT) *google.Service {
|
||||||
os.Exit(1)
|
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))
|
homegraphService, err := homegraph.NewService(context.Background(), option.WithCredentialsJSON(credentials))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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) {
|
m.AddHandler("zigbee2mqtt/bridge/devices", func(_ paho.Client, msg paho.Message) {
|
||||||
var devices []DeviceInfo
|
var devices []DeviceInfo
|
||||||
json.Unmarshal(msg.Payload(), &devices)
|
json.Unmarshal(msg.Payload(), &devices)
|
||||||
|
@ -56,22 +58,28 @@ func NewService(m *mqtt.MQTT) *google.Service {
|
||||||
log.Println("zigbee2mqtt devices:")
|
log.Println("zigbee2mqtt devices:")
|
||||||
pretty.Logln(devices)
|
pretty.Logln(devices)
|
||||||
|
|
||||||
// Clear the list of devices in order to update it
|
// Remove all automatically added devices
|
||||||
provider.devices = make(map[string]google.DeviceInterface)
|
provider.devices = provider.manualDevices
|
||||||
|
|
||||||
for _, device := range devices {
|
for _, device := range devices {
|
||||||
switch device.Description {
|
switch device.Description {
|
||||||
case "Kettle":
|
case "Kettle":
|
||||||
outlet := NewKettle(device, m, provider.service)
|
outlet := NewKettle(device, m, provider.Service)
|
||||||
provider.devices[device.IEEEAdress] = outlet
|
provider.devices[device.IEEEAdress] = outlet
|
||||||
log.Printf("Added Kettle (%s) %s\n", device.IEEEAdress, device.FriendlyName)
|
log.Printf("Added Kettle (%s) %s\n", device.IEEEAdress, device.FriendlyName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send sync request
|
// 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) {
|
func (p *Provider) Sync(_ context.Context, _ string) ([]*google.Device, error) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ type DeviceInterface interface {
|
||||||
Sync() *Device
|
Sync() *Device
|
||||||
Query() DeviceState
|
Query() DeviceState
|
||||||
Execute(execution Execution, updatedState *DeviceState) (errCode string, online bool)
|
Execute(execution Execution, updatedState *DeviceState) (errCode string, online bool)
|
||||||
|
GetID() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://developers.google.com/assistant/smarthome/reference/intent/sync
|
// https://developers.google.com/assistant/smarthome/reference/intent/sync
|
||||||
|
|
|
@ -5,4 +5,5 @@ type Type string
|
||||||
// https://developers.google.com/assistant/smarthome/guides
|
// https://developers.google.com/assistant/smarthome/guides
|
||||||
const (
|
const (
|
||||||
TypeKettle = "action.devices.types.KETTLE"
|
TypeKettle = "action.devices.types.KETTLE"
|
||||||
|
TypeScene = "action.devices.types.SCENE"
|
||||||
)
|
)
|
||||||
|
|
6
main.go
6
main.go
|
@ -36,10 +36,12 @@ func main() {
|
||||||
m.AddHandler("automation/presence/+", p.PresenceHandler)
|
m.AddHandler("automation/presence/+", p.PresenceHandler)
|
||||||
|
|
||||||
// Smart home
|
// 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 := mux.NewRouter()
|
||||||
r.HandleFunc("/assistant", service.FullfillmentHandler)
|
r.HandleFunc("/assistant", provider.Service.FullfillmentHandler)
|
||||||
|
|
||||||
// Event loop
|
// Event loop
|
||||||
go func() {
|
go func() {
|
||||||
|
|
Reference in New Issue
Block a user