mirror of
https://github.com/Xevion/go-ha.git
synced 2025-12-10 02:07:21 -06:00
event listeners working
This commit is contained in:
36
app.go
36
app.go
@@ -27,6 +27,7 @@ type app struct {
|
|||||||
schedules pq.PriorityQueue
|
schedules pq.PriorityQueue
|
||||||
entityListeners map[string][]*entityListener
|
entityListeners map[string][]*entityListener
|
||||||
entityListenersId int64
|
entityListenersId int64
|
||||||
|
eventListeners map[string][]*eventListener
|
||||||
}
|
}
|
||||||
|
|
||||||
type TimeString string
|
type TimeString string
|
||||||
@@ -53,6 +54,7 @@ func NewApp(connString string) app {
|
|||||||
state: state,
|
state: state,
|
||||||
schedules: pq.New(),
|
schedules: pq.New(),
|
||||||
entityListeners: map[string][]*entityListener{},
|
entityListeners: map[string][]*entityListener{},
|
||||||
|
eventListeners: map[string][]*eventListener{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,12 +85,23 @@ func (a *app) RegisterSchedule(s schedule) {
|
|||||||
a.schedules.Insert(s, float64(startTime.Unix()))
|
a.schedules.Insert(s, float64(startTime.Unix()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) RegisterEntityListener(el entityListener) {
|
func (a *app) RegisterEntityListener(etl entityListener) {
|
||||||
for _, entity := range el.entityIds {
|
for _, entity := range etl.entityIds {
|
||||||
if elList, ok := a.entityListeners[entity]; ok {
|
if elList, ok := a.entityListeners[entity]; ok {
|
||||||
a.entityListeners[entity] = append(elList, &el)
|
a.entityListeners[entity] = append(elList, &etl)
|
||||||
} else {
|
} else {
|
||||||
a.entityListeners[entity] = []*entityListener{&el}
|
a.entityListeners[entity] = []*entityListener{&etl}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) RegisterEventListener(evl eventListener) {
|
||||||
|
for _, eventType := range evl.eventTypes {
|
||||||
|
if elList, ok := a.eventListeners[eventType]; ok {
|
||||||
|
a.eventListeners[eventType] = append(elList, &evl)
|
||||||
|
} else {
|
||||||
|
ws.SubscribeToEventType(eventType, a.conn, a.ctx)
|
||||||
|
a.eventListeners[eventType] = []*eventListener{&evl}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,24 +157,13 @@ func carbon2TimeString(c carbon.Carbon) string {
|
|||||||
return fmt.Sprintf("%02d:%02d", c.Hour(), c.Minute())
|
return fmt.Sprintf("%02d:%02d", c.Hour(), c.Minute())
|
||||||
}
|
}
|
||||||
|
|
||||||
type subEvent struct {
|
|
||||||
Id int64 `json:"id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
EventType string `json:"event_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) Start() {
|
func (a *app) Start() {
|
||||||
// schedules
|
// schedules
|
||||||
go RunSchedules(a)
|
go RunSchedules(a)
|
||||||
|
|
||||||
// subscribe to state_changed events
|
// subscribe to state_changed events
|
||||||
id := internal.GetId()
|
id := internal.GetId()
|
||||||
e := subEvent{
|
ws.SubscribeToStateChangedEvents(id, a.conn, a.ctx)
|
||||||
Id: id,
|
|
||||||
Type: "subscribe_events",
|
|
||||||
EventType: "state_changed",
|
|
||||||
}
|
|
||||||
ws.WriteMessage(e, a.conn, a.ctx)
|
|
||||||
a.entityListenersId = id
|
a.entityListenersId = id
|
||||||
|
|
||||||
// entity listeners
|
// entity listeners
|
||||||
@@ -173,6 +175,8 @@ func (a *app) Start() {
|
|||||||
msg = <-elChan
|
msg = <-elChan
|
||||||
if a.entityListenersId == msg.Id {
|
if a.entityListenersId == msg.Id {
|
||||||
go callEntityListeners(a, msg.Raw)
|
go callEntityListeners(a, msg.Raw)
|
||||||
|
} else {
|
||||||
|
go callEventListeners(a, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package gomeassistant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-module/carbon"
|
"github.com/golang-module/carbon"
|
||||||
i "github.com/saml-dev/gome-assistant/internal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type entityListener struct {
|
type entityListener struct {
|
||||||
@@ -19,7 +17,6 @@ type entityListener struct {
|
|||||||
betweenEnd string
|
betweenEnd string
|
||||||
throttle time.Duration
|
throttle time.Duration
|
||||||
lastRan carbon.Carbon
|
lastRan carbon.Carbon
|
||||||
err error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type entityListenerCallback func(*Service, EntityData)
|
type entityListenerCallback func(*Service, EntityData)
|
||||||
@@ -68,7 +65,7 @@ type elBuilder1 struct {
|
|||||||
|
|
||||||
func (b elBuilder1) EntityIds(entityIds ...string) elBuilder2 {
|
func (b elBuilder1) EntityIds(entityIds ...string) elBuilder2 {
|
||||||
if len(entityIds) == 0 {
|
if len(entityIds) == 0 {
|
||||||
b.err = errors.New("must pass at least one entityId to EntityIds()")
|
log.Fatalln("must pass at least one entityId to EntityIds()")
|
||||||
} else {
|
} else {
|
||||||
b.entityListener.entityIds = entityIds
|
b.entityListener.entityIds = entityIds
|
||||||
}
|
}
|
||||||
@@ -80,9 +77,7 @@ type elBuilder2 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b elBuilder2) Call(callback entityListenerCallback) elBuilder3 {
|
func (b elBuilder2) Call(callback entityListenerCallback) elBuilder3 {
|
||||||
if b.err == nil {
|
b.entityListener.callback = callback
|
||||||
b.entityListener.callback = callback
|
|
||||||
}
|
|
||||||
return elBuilder3(b)
|
return elBuilder3(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,44 +137,17 @@ func callEntityListeners(app *app, msgBytes []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, l := range listeners {
|
for _, l := range listeners {
|
||||||
// if betweenStart and betweenEnd both set, first account for midnight
|
// Check conditions
|
||||||
// overlap, then only run if between those times.
|
if c := CheckWithinTimeRange(l.betweenStart, l.betweenEnd); c.fail {
|
||||||
if l.betweenStart != "" && l.betweenEnd != "" {
|
|
||||||
start := i.ParseTime(l.betweenStart)
|
|
||||||
end := i.ParseTime(l.betweenEnd)
|
|
||||||
|
|
||||||
// check for midnight overlap
|
|
||||||
if end.Lt(start) { // example turn on night lights when motion from 23:00 to 07:00
|
|
||||||
if end.IsPast() { // such as at 15:00, 22:00
|
|
||||||
end = end.AddDay()
|
|
||||||
} else {
|
|
||||||
start = start.SubDay() // such as at 03:00, 05:00
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip callback if not inside the range
|
|
||||||
if !carbon.Now().BetweenIncludedStart(start, end) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise just check individual before/after
|
|
||||||
} else if l.betweenStart != "" && i.ParseTime(l.betweenStart).IsFuture() {
|
|
||||||
return
|
|
||||||
} else if l.betweenEnd != "" && i.ParseTime(l.betweenEnd).IsPast() {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if c := CheckStatesMatch(l.fromState, data.OldState.State); c.fail {
|
||||||
// don't run callback if fromState or toState are set and don't match
|
|
||||||
if l.fromState != "" && l.fromState != data.OldState.State {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if l.toState != "" && l.toState != data.NewState.State {
|
if c := CheckStatesMatch(l.toState, data.NewState.State); c.fail {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if c := CheckThrottle(l.throttle, l.lastRan); c.fail {
|
||||||
// don't run callback if Throttle is set and that duration hasn't passed since lastRan
|
|
||||||
if l.throttle.Seconds() > 0 &&
|
|
||||||
l.lastRan.DiffAbsInSeconds(carbon.Now()) < int64(l.throttle.Seconds()) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
120
eventListener.go
120
eventListener.go
@@ -1,4 +1,122 @@
|
|||||||
package gomeassistant
|
package gomeassistant
|
||||||
|
|
||||||
// TODO: impl eventListener. could probably create generic listener struct for
|
// TODO: impl eventListener. could probably create generic listener struct for
|
||||||
// code reuse between eventListener and entityListener
|
// code reuse between eventListener and eventListener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-module/carbon"
|
||||||
|
ws "github.com/saml-dev/gome-assistant/internal/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventListener struct {
|
||||||
|
eventTypes []string
|
||||||
|
callback eventListenerCallback
|
||||||
|
betweenStart string
|
||||||
|
betweenEnd string
|
||||||
|
throttle time.Duration
|
||||||
|
lastRan carbon.Carbon
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventListenerCallback func(*Service, EventData)
|
||||||
|
|
||||||
|
type EventData struct {
|
||||||
|
Type string
|
||||||
|
RawEventJSON []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Methods */
|
||||||
|
|
||||||
|
func EventListenerBuilder() eventListenerBuilder1 {
|
||||||
|
return eventListenerBuilder1{eventListener{
|
||||||
|
lastRan: carbon.Now().StartOfCentury(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventListenerBuilder1 struct {
|
||||||
|
eventListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b eventListenerBuilder1) EventType(ets ...string) eventListenerBuilder2 {
|
||||||
|
b.eventTypes = ets
|
||||||
|
return eventListenerBuilder2(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventListenerBuilder2 struct {
|
||||||
|
eventListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b eventListenerBuilder2) Call(callback eventListenerCallback) eventListenerBuilder3 {
|
||||||
|
b.eventListener.callback = callback
|
||||||
|
return eventListenerBuilder3(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventListenerBuilder3 struct {
|
||||||
|
eventListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b eventListenerBuilder3) OnlyBetween(start string, end string) eventListenerBuilder3 {
|
||||||
|
b.eventListener.betweenStart = start
|
||||||
|
b.eventListener.betweenEnd = end
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b eventListenerBuilder3) OnlyAfter(start string) eventListenerBuilder3 {
|
||||||
|
b.eventListener.betweenStart = start
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b eventListenerBuilder3) OnlyBefore(end string) eventListenerBuilder3 {
|
||||||
|
b.eventListener.betweenEnd = end
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b eventListenerBuilder3) Throttle(s TimeString) eventListenerBuilder3 {
|
||||||
|
d, err := time.ParseDuration(string(s))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Couldn't parse string duration passed to Throttle(): \"%s\" see https://pkg.go.dev/time#ParseDuration for valid time units", s)
|
||||||
|
}
|
||||||
|
b.eventListener.throttle = d
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b eventListenerBuilder3) Build() eventListener {
|
||||||
|
return b.eventListener
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseEventMsg struct {
|
||||||
|
Event struct {
|
||||||
|
EventType string `json:"event_type"`
|
||||||
|
} `json:"event"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
func callEventListeners(app *app, msg ws.ChanMsg) {
|
||||||
|
baseEventMsg := BaseEventMsg{}
|
||||||
|
json.Unmarshal(msg.Raw, &baseEventMsg)
|
||||||
|
listeners, ok := app.eventListeners[baseEventMsg.Event.EventType]
|
||||||
|
if !ok {
|
||||||
|
// no listeners registered for this event type
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range listeners {
|
||||||
|
// Check conditions
|
||||||
|
if c := CheckWithinTimeRange(l.betweenStart, l.betweenEnd); c.fail {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c := CheckThrottle(l.throttle, l.lastRan); c.fail {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eventData := EventData{
|
||||||
|
Type: baseEventMsg.Event.EventType,
|
||||||
|
RawEventJSON: msg.Raw,
|
||||||
|
}
|
||||||
|
go l.callback(app.service, eventData)
|
||||||
|
l.lastRan = carbon.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,23 +14,34 @@ func main() {
|
|||||||
EntityIds("binary_sensor.pantry_door").
|
EntityIds("binary_sensor.pantry_door").
|
||||||
Call(pantryLights).
|
Call(pantryLights).
|
||||||
Build()
|
Build()
|
||||||
|
zwaveEventListener := ga.
|
||||||
|
EventListenerBuilder().
|
||||||
|
EventType("zwave_js_value_notification").
|
||||||
|
Call(onEvent).
|
||||||
|
Build()
|
||||||
app.RegisterEntityListener(pantryDoor)
|
app.RegisterEntityListener(pantryDoor)
|
||||||
app.RegisterSchedule(ga.ScheduleBuilder().Call(cool).Every("5s").Build())
|
app.RegisterSchedule(ga.ScheduleBuilder().Call(cool).Every("5s").Build())
|
||||||
|
app.RegisterEventListener(zwaveEventListener)
|
||||||
|
|
||||||
app.Start()
|
app.Start()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pantryLights(service *ga.Service, data ga.EntityData) {
|
func pantryLights(service *ga.Service, data ga.EntityData) {
|
||||||
|
l := "group.kitchen_ceiling_lights"
|
||||||
// service.HomeAssistant.Toggle("group.living_room_lamps", map[string]any{"brightness_pct": 100})
|
// service.HomeAssistant.Toggle("group.living_room_lamps", map[string]any{"brightness_pct": 100})
|
||||||
// service.Light.Toggle("light.entryway_lamp", map[string]any{"brightness_pct": 100})
|
// service.Light.Toggle("light.entryway_lamp", map[string]any{"brightness_pct": 100})
|
||||||
if data.ToState == "on" {
|
if data.ToState == "on" {
|
||||||
service.HomeAssistant.TurnOn("switch.pantry_light_2")
|
service.HomeAssistant.TurnOn(l)
|
||||||
} else {
|
} else {
|
||||||
service.HomeAssistant.TurnOff("switch.pantry_light_2")
|
service.HomeAssistant.TurnOff(l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func onEvent(service *ga.Service, data ga.EventData) {
|
||||||
|
service.HomeAssistant.Toggle("light.el_gato_key_lights")
|
||||||
|
}
|
||||||
|
|
||||||
func cool(service *ga.Service, state *ga.State) {
|
func cool(service *ga.Service, state *ga.State) {
|
||||||
// service.InputDatetime.Set("input_datetime.garage_last_triggered_ts", time.Now())
|
// service.InputDatetime.Set("input_datetime.garage_last_triggered_ts", time.Now())
|
||||||
// service.Light.TurnOn("light.entryway_lamp")
|
// service.Light.TurnOn("light.entryway_lamp")
|
||||||
@@ -42,5 +53,5 @@ func c(service *ga.Service, state *ga.State) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func listenerCB(service *ga.Service, data ga.EntityData) {
|
func listenerCB(service *ga.Service, data ga.EntityData) {
|
||||||
log.Default().Println("hi katie")
|
log.Default().Println("hi")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ func ListenWebsocket(conn *websocket.Conn, ctx context.Context, c chan ChanMsg)
|
|||||||
bytes, _ := ReadMessage(conn, ctx)
|
bytes, _ := ReadMessage(conn, ctx)
|
||||||
base := BaseMessage{}
|
base := BaseMessage{}
|
||||||
json.Unmarshal(bytes, &base)
|
json.Unmarshal(bytes, &base)
|
||||||
|
|
||||||
chanMsg := ChanMsg{
|
chanMsg := ChanMsg{
|
||||||
Type: base.Type,
|
Type: base.Type,
|
||||||
Id: base.Id,
|
Id: base.Id,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
i "github.com/saml-dev/gome-assistant/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthMessage struct {
|
type AuthMessage struct {
|
||||||
@@ -106,3 +107,33 @@ func VerifyAuthResponse(conn *websocket.Conn, ctx context.Context) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SubEvent struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
EventType string `json:"event_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubscribeToStateChangedEvents(id int64, conn *websocket.Conn, ctx context.Context) {
|
||||||
|
SubscribeToEventType("state_changed", conn, ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubscribeToEventType(eventType string, conn *websocket.Conn, ctx context.Context, id ...int64) {
|
||||||
|
var finalId int64
|
||||||
|
if len(id) == 0 {
|
||||||
|
finalId = i.GetId()
|
||||||
|
} else {
|
||||||
|
finalId = id[0]
|
||||||
|
}
|
||||||
|
e := SubEvent{
|
||||||
|
Id: finalId,
|
||||||
|
Type: "subscribe_events",
|
||||||
|
EventType: eventType,
|
||||||
|
}
|
||||||
|
err := WriteMessage(e, conn, ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error writing to websocket: ", err)
|
||||||
|
}
|
||||||
|
// m, _ := ReadMessage(conn, ctx)
|
||||||
|
// log.Default().Println(string(m))
|
||||||
|
}
|
||||||
|
|||||||
62
listeners.go
Normal file
62
listeners.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package gomeassistant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-module/carbon"
|
||||||
|
i "github.com/saml-dev/gome-assistant/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type conditionCheck struct {
|
||||||
|
fail bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckWithinTimeRange(startTime, endTime string) conditionCheck {
|
||||||
|
cc := conditionCheck{fail: false}
|
||||||
|
// if betweenStart and betweenEnd both set, first account for midnight
|
||||||
|
// overlap, then check if between those times.
|
||||||
|
if startTime != "" && endTime != "" {
|
||||||
|
parsedStart := i.ParseTime(startTime)
|
||||||
|
parsedEnd := i.ParseTime(endTime)
|
||||||
|
|
||||||
|
// check for midnight overlap
|
||||||
|
if parsedEnd.Lt(parsedStart) { // example turn on night lights when motion from 23:00 to 07:00
|
||||||
|
if parsedEnd.IsPast() { // such as at 15:00, 22:00
|
||||||
|
parsedEnd = parsedEnd.AddDay()
|
||||||
|
} else {
|
||||||
|
parsedStart = parsedStart.SubDay() // such as at 03:00, 05:00
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip callback if not inside the range
|
||||||
|
if !carbon.Now().BetweenIncludedStart(parsedStart, parsedEnd) {
|
||||||
|
cc.fail = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise just check individual before/after
|
||||||
|
} else if startTime != "" && i.ParseTime(startTime).IsFuture() {
|
||||||
|
cc.fail = true
|
||||||
|
} else if endTime != "" && i.ParseTime(endTime).IsPast() {
|
||||||
|
cc.fail = true
|
||||||
|
}
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckStatesMatch(listenerState, s string) conditionCheck {
|
||||||
|
cc := conditionCheck{fail: false}
|
||||||
|
// check if fromState or toState are set and don't match
|
||||||
|
if listenerState != "" && listenerState != s {
|
||||||
|
cc.fail = true
|
||||||
|
}
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckThrottle(throttle time.Duration, lastRan carbon.Carbon) conditionCheck {
|
||||||
|
cc := conditionCheck{fail: false}
|
||||||
|
// check if Throttle is set and that duration hasn't passed since lastRan
|
||||||
|
if throttle.Seconds() > 0 &&
|
||||||
|
lastRan.DiffAbsInSeconds(carbon.Now()) < int64(throttle.Seconds()) {
|
||||||
|
cc.fail = true
|
||||||
|
}
|
||||||
|
return cc
|
||||||
|
}
|
||||||
25
types.go
Normal file
25
types.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package gomeassistant
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type EventZWaveJSValueNotification struct {
|
||||||
|
EventType string `yaml:"event_type"`
|
||||||
|
Data struct {
|
||||||
|
Domain string `yaml:"domain"`
|
||||||
|
NodeID int `yaml:"node_id"`
|
||||||
|
HomeID int64 `yaml:"home_id"`
|
||||||
|
Endpoint int `yaml:"endpoint"`
|
||||||
|
DeviceID string `yaml:"device_id"`
|
||||||
|
CommandClass int `yaml:"command_class"`
|
||||||
|
CommandClassName string `yaml:"command_class_name"`
|
||||||
|
Label string `yaml:"label"`
|
||||||
|
Property string `yaml:"property"`
|
||||||
|
PropertyName string `yaml:"property_name"`
|
||||||
|
PropertyKey string `yaml:"property_key"`
|
||||||
|
PropertyKeyName string `yaml:"property_key_name"`
|
||||||
|
Value string `yaml:"value"`
|
||||||
|
ValueRaw int `yaml:"value_raw"`
|
||||||
|
} `yaml:"data"`
|
||||||
|
Origin string `yaml:"origin"`
|
||||||
|
TimeFired time.Time `yaml:"time_fired"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user