Reorganized code, added google home intergrations and added zigbee2mqtt kettle
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
91
integration/google/command.go
Normal file
91
integration/google/command.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type CommandName string
|
||||
|
||||
type Execution struct {
|
||||
Name CommandName
|
||||
|
||||
OnOff *CommandOnOffData
|
||||
StartStop *CommandStartStopData
|
||||
GetCameraStream *CommandGetCameraStreamData
|
||||
ActivateScene *CommandActivateSceneData
|
||||
}
|
||||
|
||||
func (c *Execution) UnmarshalJSON(data []byte) error {
|
||||
var tmp struct {
|
||||
Name CommandName `json:"command"`
|
||||
Params json.RawMessage `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Name = tmp.Name
|
||||
|
||||
var details interface{}
|
||||
switch c.Name {
|
||||
case CommandOnOff:
|
||||
c.OnOff = &CommandOnOffData{}
|
||||
details = c.OnOff
|
||||
|
||||
case CommandStartStop:
|
||||
c.StartStop = &CommandStartStopData{}
|
||||
details = c.StartStop
|
||||
|
||||
case CommandGetCameraStream:
|
||||
c.GetCameraStream = &CommandGetCameraStreamData{}
|
||||
details = c.GetCameraStream
|
||||
|
||||
case CommandActivateScene:
|
||||
c.ActivateScene = &CommandActivateSceneData{}
|
||||
details = c.ActivateScene
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Command (%s) is not implemented", c.Name)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(tmp.Params, details)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/onoff
|
||||
const CommandOnOff CommandName = "action.devices.commands.OnOff"
|
||||
|
||||
type CommandOnOffData struct {
|
||||
On bool `json:"on"`
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/startstop
|
||||
const CommandStartStop CommandName = "action.devices.commands.StartStop"
|
||||
|
||||
type CommandStartStopData struct {
|
||||
Start bool `json:"start"`
|
||||
Zone string `json:"zone,omitempty"`
|
||||
MultipleZones []string `json:"multipleZones,omitempty"`
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/camerastream
|
||||
const CommandGetCameraStream CommandName = "action.devices.commands.GetCameraStream"
|
||||
|
||||
type CommandGetCameraStreamData struct {
|
||||
StreamToChromecast bool `json:"StreamToChromecast"`
|
||||
SupportedStreamProtocols []string `json:"SupportedStreamProtocols"`
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/scene
|
||||
const CommandActivateScene CommandName = "action.devices.commands.ActivateScene"
|
||||
|
||||
type CommandActivateSceneData struct {
|
||||
Deactivate bool `json:"deactivate"`
|
||||
}
|
||||
51
integration/google/device.go
Normal file
51
integration/google/device.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package google
|
||||
|
||||
type DeviceName struct {
|
||||
DefaultNames []string `json:"defaultNames,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Nicknames []string `json:"nicknames,omitempty"`
|
||||
}
|
||||
|
||||
type DeviceInfo struct {
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
HwVersion string `json:"hwVersion,omitempty"`
|
||||
SwVersion string `json:"swVersion,omitempty"`
|
||||
}
|
||||
|
||||
type OtherDeviceID struct {
|
||||
AgentID string `json:"agentId,omitempty"`
|
||||
DeviceID string `json:"deviceId,omitempty"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
ID string `json:"id"`
|
||||
Type Type `json:"type"`
|
||||
|
||||
Traits []Trait `json:"traits"`
|
||||
|
||||
Name DeviceName `json:"name"`
|
||||
|
||||
WillReportState bool `json:"willReportState"`
|
||||
|
||||
NotificationSupportedByAgent bool `json:"notificationSupportedByAgent,omitempty"`
|
||||
|
||||
RoomHint string `json:"roomHint,omitempty"`
|
||||
|
||||
DeviceInfo DeviceInfo `json:"deviceInfo,omitempty"`
|
||||
|
||||
Attributes map[string]interface{} `json:"attributes,omitempty"`
|
||||
|
||||
CustomData map[string]interface{} `json:"customDate,omitempty"`
|
||||
|
||||
OtherDeviceIDs []OtherDeviceID `json:"otherDeviceIds,omitempty"`
|
||||
}
|
||||
|
||||
func NewDevice(id string, typ Type) *Device {
|
||||
return &Device{
|
||||
ID: id,
|
||||
Type: typ,
|
||||
Attributes: make(map[string]interface{}),
|
||||
CustomData: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
198
integration/google/handler.go
Normal file
198
integration/google/handler.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DeviceInterface interface {
|
||||
Sync() *Device
|
||||
Query() DeviceState
|
||||
Execute(execution Execution, updatedState *DeviceState) (errCode string, online bool)
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/reference/intent/sync
|
||||
type syncResponse struct {
|
||||
RequestID string `json:"requestId"`
|
||||
Payload struct {
|
||||
UserID string `json:"agentUserId"`
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
DebugString string `json:"debugString,omitempty"`
|
||||
Devices []*Device `json:"devices"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/reference/intent/query
|
||||
type queryResponse struct {
|
||||
RequestID string `json:"requestId"`
|
||||
Payload struct {
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
DebugString string `json:"debugString,omitempty"`
|
||||
Devices map[string]DeviceState `json:"devices"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type executeRespPayload struct {
|
||||
IDs []string `json:"ids"`
|
||||
Status Status `json:"status"`
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
States DeviceState `json:"states,omitempty"`
|
||||
}
|
||||
|
||||
type executeResponse struct {
|
||||
RequestID string `json:"requestId"`
|
||||
Payload struct {
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
DebugString string `json:"debugString,omitempty"`
|
||||
Commands []executeRespPayload `json:"commands,omitempty"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
func (s *Service) FullfillmentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
// Check if we are logged in
|
||||
{
|
||||
req, err := http.NewRequest("GET", "https://login.huizinga.dev/api/oidc/userinfo", nil)
|
||||
if err != nil {
|
||||
log.Println("Failed to make request to to login server")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(r.Header["Authorization"]) > 0 {
|
||||
req.Header.Set("Authorization", r.Header["Authorization"][0])
|
||||
}
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
// If we get something other than 200, error out
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Println("Not logged in...")
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO Make sure we receive content type json
|
||||
// @TODO Get this from userinfo, currently the scope is not set up properly to actually receive the username
|
||||
userID := "Dreaded_X"
|
||||
|
||||
fullfimentReq := &FullfillmentRequest{}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&fullfimentReq)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("JSON Deserialization failed"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(fullfimentReq.Inputs) != 1 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Unsupported number of inputs"))
|
||||
return
|
||||
}
|
||||
|
||||
switch fullfimentReq.Inputs[0].Intent {
|
||||
case IntentSync:
|
||||
devices, err := s.provider.Sync(r.Context(), userID)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
w.Write([]byte("Failed to sync"))
|
||||
}
|
||||
|
||||
syncResp := &syncResponse{
|
||||
RequestID: fullfimentReq.RequestID,
|
||||
}
|
||||
syncResp.Payload.UserID = userID
|
||||
syncResp.Payload.Devices = devices
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(w).Encode(syncResp)
|
||||
if err != nil {
|
||||
log.Println("Error serializing", err)
|
||||
}
|
||||
|
||||
case IntentQuery:
|
||||
states, err := s.provider.Query(r.Context(), userID, fullfimentReq.Inputs[0].Query.Devices)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
w.Write([]byte("Failed to sync"))
|
||||
}
|
||||
|
||||
queryResp := &queryResponse{
|
||||
RequestID: fullfimentReq.RequestID,
|
||||
}
|
||||
queryResp.Payload.Devices = states
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(w).Encode(queryResp)
|
||||
if err != nil {
|
||||
log.Println("Error serializing", err)
|
||||
}
|
||||
|
||||
case IntentExecute:
|
||||
response, err := s.provider.Execute(r.Context(), userID, fullfimentReq.Inputs[0].Execute.Commands)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
w.Write([]byte("Failed to sync"))
|
||||
}
|
||||
|
||||
executeResp := &executeResponse{
|
||||
RequestID: fullfimentReq.RequestID,
|
||||
}
|
||||
|
||||
if len(response.UpdatedDevices) > 0 {
|
||||
c := executeRespPayload{
|
||||
Status: StatusSuccess,
|
||||
States: response.UpdatedState,
|
||||
}
|
||||
|
||||
for _, id := range response.UpdatedDevices {
|
||||
c.IDs = append(c.IDs, id)
|
||||
}
|
||||
|
||||
executeResp.Payload.Commands = append(executeResp.Payload.Commands, c)
|
||||
}
|
||||
|
||||
if len(response.OfflineDevices) > 0 {
|
||||
c := executeRespPayload{
|
||||
Status: StatusOffline,
|
||||
}
|
||||
|
||||
for _, id := range response.UpdatedDevices {
|
||||
c.IDs = append(c.IDs, id)
|
||||
}
|
||||
|
||||
executeResp.Payload.Commands = append(executeResp.Payload.Commands, c)
|
||||
}
|
||||
|
||||
for errCode, details := range response.FailedDevices {
|
||||
c := executeRespPayload{
|
||||
Status: StatusError,
|
||||
ErrorCode: errCode,
|
||||
}
|
||||
|
||||
for _, id := range details.Devices {
|
||||
c.IDs = append(c.IDs, id)
|
||||
}
|
||||
|
||||
executeResp.Payload.Commands = append(executeResp.Payload.Commands, c)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(w).Encode(executeResp)
|
||||
if err != nil {
|
||||
log.Println("Error serializing", err)
|
||||
}
|
||||
|
||||
default:
|
||||
log.Println("Intent is not implemented")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Not implemented for now"))
|
||||
}
|
||||
}
|
||||
77
integration/google/intent.go
Normal file
77
integration/google/intent.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Intent string
|
||||
|
||||
const (
|
||||
IntentSync Intent = "action.devices.SYNC"
|
||||
IntentQuery = "action.devices.QUERY"
|
||||
IntentExecute = "action.devices.EXECUTE"
|
||||
)
|
||||
|
||||
type DeviceHandle struct {
|
||||
ID string `json:"id"`
|
||||
|
||||
CustomData map[string]interface{} `json:"customData,omitempty"`
|
||||
}
|
||||
|
||||
type queryPayload struct {
|
||||
Devices []DeviceHandle `json:"devices"`
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Devices []DeviceHandle `json:"devices"`
|
||||
Execution []Execution `json:"execution"`
|
||||
}
|
||||
|
||||
type executePayload struct {
|
||||
Commands []Command `json:"commands"`
|
||||
}
|
||||
|
||||
type fullfilmentInput struct {
|
||||
Intent Intent
|
||||
|
||||
Query *queryPayload
|
||||
Execute *executePayload
|
||||
}
|
||||
|
||||
type FullfillmentRequest struct {
|
||||
RequestID string `json:"requestId"`
|
||||
Inputs []fullfilmentInput `json:"inputs"`
|
||||
}
|
||||
|
||||
func (i *fullfilmentInput) UnmarshalJSON(data []byte) error {
|
||||
var tmp struct {
|
||||
Intent Intent `json:"intent"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.Intent = tmp.Intent
|
||||
switch i.Intent {
|
||||
case IntentQuery:
|
||||
payload := &queryPayload{}
|
||||
err = json.Unmarshal(tmp.Payload, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Query = payload
|
||||
|
||||
case IntentExecute:
|
||||
payload := &executePayload{}
|
||||
err = json.Unmarshal(tmp.Payload, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Execute = payload
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
88
integration/google/service.go
Normal file
88
integration/google/service.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/api/homegraph/v1"
|
||||
)
|
||||
|
||||
type ExecuteResponse struct {
|
||||
UpdatedState DeviceState
|
||||
UpdatedDevices []string
|
||||
OfflineDevices []string
|
||||
// The key is the errorCode that is associated with the devices
|
||||
FailedDevices map[string]struct {
|
||||
Devices []string
|
||||
}
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
Sync(context.Context, string) ([]*Device, error)
|
||||
Query(context.Context, string, []DeviceHandle) (map[string]DeviceState, error)
|
||||
Execute(context.Context, string, []Command) (*ExecuteResponse, error)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
provider Provider
|
||||
deviceService *homegraph.DevicesService
|
||||
}
|
||||
|
||||
func NewService(provider Provider, service *homegraph.Service) *Service {
|
||||
return &Service{
|
||||
provider: provider,
|
||||
deviceService: homegraph.NewDevicesService(service),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) RequestSync(ctx context.Context, userID string) error {
|
||||
call := s.deviceService.RequestSync(&homegraph.RequestSyncDevicesRequest{
|
||||
AgentUserId: userID,
|
||||
})
|
||||
|
||||
call.Context(ctx)
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.ServerResponse.HTTPStatusCode != http.StatusOK {
|
||||
return errors.New(fmt.Sprintf("sync failed: %d", resp.ServerResponse.HTTPStatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ReportState(ctx context.Context, userID string, states map[string]DeviceState) error {
|
||||
j, err := json.Marshal(states)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
call := s.deviceService.ReportStateAndNotification(&homegraph.ReportStateAndNotificationRequest{
|
||||
AgentUserId: userID,
|
||||
EventId: uuid.New().String(),
|
||||
RequestId: uuid.New().String(),
|
||||
Payload: &homegraph.StateAndNotificationPayload{
|
||||
Devices: &homegraph.ReportStateAndNotificationDevice{
|
||||
States: j,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
call.Context(ctx)
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.ServerResponse.HTTPStatusCode != http.StatusOK {
|
||||
return errors.New(fmt.Sprintf("report failed: %d", resp.ServerResponse.HTTPStatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
79
integration/google/state.go
Normal file
79
integration/google/state.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type DeviceState struct {
|
||||
Online bool
|
||||
Status Status
|
||||
|
||||
state map[string]interface{}
|
||||
}
|
||||
|
||||
func (ds DeviceState) MarshalJSON() ([]byte, error) {
|
||||
payload := make(map[string]interface{})
|
||||
|
||||
payload["online"] = ds.Online
|
||||
if len(ds.Status) > 0 {
|
||||
payload["status"] = ds.Status
|
||||
}
|
||||
|
||||
for k, v := range ds.state {
|
||||
payload[k] = v
|
||||
}
|
||||
|
||||
return json.Marshal(payload)
|
||||
}
|
||||
|
||||
func NewDeviceState(online bool) DeviceState {
|
||||
return DeviceState{
|
||||
Online: online,
|
||||
state: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/onoff
|
||||
func (ds DeviceState) RecordOnOff(on bool) DeviceState {
|
||||
ds.state["on"] = on
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/runcycle
|
||||
func (ds DeviceState) RecordRunCycle(state int) DeviceState {
|
||||
if state == 0 {
|
||||
} else if state == 1 {
|
||||
ds.state["currentRunCycle"] = []struct{
|
||||
CurrentCycle string `json:"currentCycle"`
|
||||
Lang string `json:"lang"`
|
||||
}{
|
||||
{
|
||||
CurrentCycle: "Wash",
|
||||
Lang: "en",
|
||||
},
|
||||
}
|
||||
} else if state == 2 {
|
||||
ds.state["currentTotalRemainingTime"] = 0
|
||||
}
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/startstop
|
||||
func (ds DeviceState) RecordStartStop(running bool, paused ...bool) DeviceState {
|
||||
ds.state["isRunning"] = running
|
||||
if len(paused) > 0 {
|
||||
ds.state["isPaused"] = paused[0]
|
||||
}
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/camerastream
|
||||
func (ds DeviceState) RecordCameraStream(url string) DeviceState {
|
||||
ds.state["cameraStreamProtocol"] = "progressive_mp4"
|
||||
ds.state["cameraStreamAccessUrl"] = url
|
||||
|
||||
return ds
|
||||
}
|
||||
10
integration/google/status.go
Normal file
10
integration/google/status.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package google
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusSuccess Status = "SUCCESS"
|
||||
StatusOffline = "OFFLINE"
|
||||
StatusException = "EXCEPTIONS"
|
||||
StatusError = "ERROR"
|
||||
)
|
||||
72
integration/google/trait.go
Normal file
72
integration/google/trait.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package google
|
||||
|
||||
import "github.com/kr/pretty"
|
||||
|
||||
type Trait string
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/onoff
|
||||
const TraitOnOff Trait = "action.devices.traits.OnOff"
|
||||
|
||||
func (d *Device) AddOnOffTrait(onlyCommand bool, onlyQuery bool) *Device {
|
||||
d.Traits = append(d.Traits, TraitOnOff)
|
||||
if onlyCommand {
|
||||
d.Attributes["commandOnlyOnOff"] = true
|
||||
}
|
||||
if onlyQuery {
|
||||
d.Attributes["queryOnlyOnOff"] = true
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/startstop
|
||||
const TraitStartStop = "action.devices.traits.StartStop"
|
||||
|
||||
func (d *Device) AddStartStopTrait(pausable bool) *Device {
|
||||
d.Traits = append(d.Traits, TraitStartStop)
|
||||
|
||||
if pausable {
|
||||
d.Attributes["pausable"] = true
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/onoff
|
||||
const TraitRunCycle = "action.devices.traits.RunCycle"
|
||||
|
||||
func (d *Device) AddRunCycleTrait() *Device {
|
||||
d.Traits = append(d.Traits, TraitRunCycle)
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/camerastream
|
||||
const TraitCameraStream = "action.devices.traits.CameraStream"
|
||||
|
||||
func (d *Device) AddCameraStreamTrait(authTokenNeeded bool, supportedProtocols ...string) *Device {
|
||||
d.Traits = append(d.Traits, TraitCameraStream)
|
||||
|
||||
if len(supportedProtocols) > 0 {
|
||||
d.Attributes["cameraStreamSupportedProtocols"] = supportedProtocols
|
||||
}
|
||||
|
||||
d.Attributes["cameraStreamNeedAuthToken"] = authTokenNeeded
|
||||
|
||||
pretty.Logln(d)
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/traits/scene
|
||||
const TraitScene = "action.devices.traits.Scene"
|
||||
|
||||
func (d *Device) AddSceneTrait(reversible bool) *Device {
|
||||
d.Traits = append(d.Traits, TraitScene)
|
||||
|
||||
if reversible {
|
||||
d.Attributes["sceneReversible"] = true
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
8
integration/google/type.go
Normal file
8
integration/google/type.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package google
|
||||
|
||||
type Type string
|
||||
|
||||
// https://developers.google.com/assistant/smarthome/guides
|
||||
const (
|
||||
TypeKettle = "action.devices.types.KETTLE"
|
||||
)
|
||||
Reference in New Issue
Block a user