diff --git a/automation/automation.go b/automation/automation.go index 4a74e41..58357a3 100644 --- a/automation/automation.go +++ b/automation/automation.go @@ -39,4 +39,5 @@ func RegisterAutomations(client paho.Client, prefix string, hue *hue.Hue, notify presenceAutomation(client, hue, notify, home) mixerAutomation(client, home) kettleAutomation(client, prefix, home) + darknessAutomation(client, hue) } diff --git a/automation/dark.go b/automation/dark.go new file mode 100644 index 0000000..8afb300 --- /dev/null +++ b/automation/dark.go @@ -0,0 +1,14 @@ +package automation + +import ( + "automation/integration/hue" + "automation/integration/zigbee" + + paho "github.com/eclipse/paho.mqtt.golang" +) + +func darknessAutomation(client paho.Client, hue *hue.Hue) { + on(client, "automation/darkness/living", func(message zigbee.DarknessPayload) { + // hue.SetFlag(43, message.IsDark) + }) +} diff --git a/device/internal_name.go b/device/internal_name.go index fb5d0a1..43f3913 100644 --- a/device/internal_name.go +++ b/device/internal_name.go @@ -11,7 +11,6 @@ func (n InternalName) Room() string { room = s[0] } room = strings.ReplaceAll(room, "_", " ") - room = strings.Title(room) return room } @@ -22,7 +21,6 @@ func (n InternalName) Name() string { if len(s) > 1 { name = s[1] } - name = strings.Title(name) return name } diff --git a/integration/wol/computer.go b/integration/wol/computer.go index fabbd62..45d7636 100644 --- a/integration/wol/computer.go +++ b/integration/wol/computer.go @@ -5,6 +5,7 @@ import ( "automation/integration/google" "log" "net/http" + "strings" ) type computer struct { @@ -49,9 +50,12 @@ func (c *computer) Sync() *google.Device { DefaultNames: []string{ "Computer", }, - Name: c.GetID().Name(), + Name: strings.Title(c.GetID().Name()), + } + room := strings.Title(c.GetID().Room()) + if len(room) > 1 { + device.RoomHint = room } - device.RoomHint = c.GetID().Room() return device } diff --git a/integration/zigbee/devices.go b/integration/zigbee/devices.go index 1494756..51e250c 100644 --- a/integration/zigbee/devices.go +++ b/integration/zigbee/devices.go @@ -17,17 +17,21 @@ func DevicesHandler(client paho.Client, prefix string, home *home.Home) { json.Unmarshal(msg.Payload(), &devices) for name, d := range device.GetDevices[Device](&home.Devices) { - d.Delete() + d.Delete(client) // Delete all zigbee devices from the device list delete(home.Devices, name) } for _, d := range devices { + d.MQTTAddress = fmt.Sprintf("%s/%s", prefix, d.FriendlyName.String()) + switch d.Description { case "Kettle": - d.MQTTAddress = fmt.Sprintf("%s/%s", prefix, d.FriendlyName.String()) kettle := NewKettle(d, client, home.Service) home.AddDevice(kettle) + case "LightSensor": + lightSensor := NewLightSensor(d, client) + home.AddDevice(lightSensor) } } diff --git a/integration/zigbee/kettle.go b/integration/zigbee/kettle.go index 0687d91..7be70b7 100644 --- a/integration/zigbee/kettle.go +++ b/integration/zigbee/kettle.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "log" + "strings" "time" paho "github.com/eclipse/paho.mqtt.golang" @@ -36,7 +37,10 @@ func NewKettle(info Info, client paho.Client, service *google.Service) *kettle { func (k *kettle) stateHandler(client paho.Client, msg paho.Message) { var payload OnOffState - json.Unmarshal(msg.Payload(), &payload) + if err := json.Unmarshal(msg.Payload(), &payload); err != nil { + log.Println(err) + return + } // Update the internal state k.isOn = payload.State @@ -65,8 +69,8 @@ var _ Device = (*kettle)(nil) func (k *kettle) IsZigbeeDevice() {} // zigbee.Device -func (k *kettle) Delete() { - if token := k.client.Unsubscribe(k.info.MQTTAddress); token.Wait() && token.Error() != nil { +func (k *kettle) Delete(client paho.Client) { + if token := client.Unsubscribe(k.info.MQTTAddress); token.Wait() && token.Error() != nil { log.Println(token.Error()) } } @@ -84,11 +88,11 @@ func (k *kettle) Sync() *google.Device { DefaultNames: []string{ "Kettle", }, - Name: k.GetID().Name(), + Name: strings.Title(k.GetID().Name()), } device.WillReportState = true - room := k.GetID().Room() + room := strings.Title(k.GetID().Room()) if len(room) > 1 { device.RoomHint = room } diff --git a/integration/zigbee/light_sensor.go b/integration/zigbee/light_sensor.go new file mode 100644 index 0000000..f8e18a3 --- /dev/null +++ b/integration/zigbee/light_sensor.go @@ -0,0 +1,140 @@ +package zigbee + +import ( + "automation/device" + "encoding/json" + "fmt" + "log" + "time" + + paho "github.com/eclipse/paho.mqtt.golang" +) + +type lightSensor struct { + info Info + + minValue int + maxValue int + timeout time.Duration + + couldBeDark bool + isDark bool + initialized bool + timer *time.Timer +} + +type DarknessPayload struct { + IsDark bool `json:"is_dark"` + Updated int64 `json:"updated"` +} + +func NewLightSensor(info Info, client paho.Client) *lightSensor { + l := &lightSensor{info: info} + + // @TODO Two completely random values for now + l.minValue = 8000 + l.maxValue = 16000 + + l.timeout = time.Minute + l.timer = time.NewTimer(l.timeout) + l.timer.Stop() + + if token := client.Subscribe(l.info.MQTTAddress, 1, l.stateHandler); token.Wait() && token.Error() != nil { + log.Println(token.Error()) + } + + go func() { + for { + <-l.timer.C + l.isDark = l.couldBeDark + + log.Println("Is dark:", l.isDark) + + payload, err := json.Marshal(DarknessPayload{ + IsDark: l.isDark, + Updated: time.Now().UnixMilli(), + }) + if err != nil { + log.Println(err) + } + + if token := client.Publish(l.darknessTopic(), 1, true, payload); token.Wait() && token.Error() != nil { + log.Println(token.Error()) + } + } + }() + + return l +} + +func (l *lightSensor) darknessTopic() string { + return fmt.Sprintf("automation/darkness/%s", l.info.FriendlyName.Room()) +} + +func (l *lightSensor) stateHandler(client paho.Client, msg paho.Message) { + var message LightSensorState + if err := json.Unmarshal(msg.Payload(), &message); err != nil { + log.Println(err) + return + } + + fmt.Println(l.isDark, l.couldBeDark, message.Illuminance, l.maxValue, l.minValue) + + if !l.initialized { + if message.Illuminance > l.maxValue { + l.couldBeDark = false + } else { + l.couldBeDark = true + } + + l.initialized = true + l.timer.Reset(time.Millisecond) + + return + } + + if message.Illuminance > l.maxValue { + if l.isDark && l.couldBeDark { + log.Println("Could be light, starting timer") + l.couldBeDark = false + l.timer.Reset(l.timeout) + } else if !l.isDark { + log.Println("Is not dark, canceling timer") + l.couldBeDark = false + l.timer.Stop() + } + } else if message.Illuminance < l.minValue { + if !l.isDark && !l.couldBeDark { + log.Println("Could be dark, starting timer") + l.couldBeDark = true + l.timer.Reset(l.timeout) + } else if l.isDark { + log.Println("Is dark, canceling timer") + l.couldBeDark = true + l.timer.Stop() + } + } else { + // log.Println("In between the threshold, canceling timer for now keeping the current state") + l.couldBeDark = l.isDark + l.timer.Stop() + } + +} + +// zigbee.Device +var _ Device = (*lightSensor)(nil) + +func (l *lightSensor) IsZigbeeDevice() {} + +func (l *lightSensor) Delete(client paho.Client) { + if token := client.Unsubscribe(l.darknessTopic()); token.Wait() && token.Error() != nil { + log.Println(token.Error()) + } +} + +// device.Base +var _ device.Basic = (*lightSensor)(nil) + +func (l *lightSensor) GetID() device.InternalName { + return l.info.FriendlyName +} diff --git a/integration/zigbee/payload.go b/integration/zigbee/payload.go index fdf5576..baca6ec 100644 --- a/integration/zigbee/payload.go +++ b/integration/zigbee/payload.go @@ -32,3 +32,7 @@ const ( type RemoteState struct { Action RemoteAction `json:"action"` } + +type LightSensorState struct { + Illuminance int `json:"illuminance"` +} diff --git a/integration/zigbee/zigbee.go b/integration/zigbee/zigbee.go index e26d08f..5c8405e 100644 --- a/integration/zigbee/zigbee.go +++ b/integration/zigbee/zigbee.go @@ -1,14 +1,18 @@ package zigbee -import "automation/device" +import ( + "automation/device" + + paho "github.com/eclipse/paho.mqtt.golang" +) type Info struct { - IEEEAdress string `json:"ieee_address"` + IEEEAdress string `json:"ieee_address"` FriendlyName device.InternalName `json:"friendly_name"` - Description string `json:"description"` - Manufacturer string `json:"manufacturer"` - ModelID string `json:"model_id"` - SoftwareBuildID string `json:"software_build_id"` + Description string `json:"description"` + Manufacturer string `json:"manufacturer"` + ModelID string `json:"model_id"` + SoftwareBuildID string `json:"software_build_id"` MQTTAddress string `json:"-"` } @@ -17,5 +21,5 @@ type Device interface { device.Basic IsZigbeeDevice() - Delete() + Delete(client paho.Client) } diff --git a/presence/presence.go b/presence/presence.go index db1da1d..96619ff 100644 --- a/presence/presence.go +++ b/presence/presence.go @@ -53,16 +53,15 @@ func (p *Presence) devicePresenceHandler(client paho.Client, msg paho.Message) { if p.presence != present { p.presence = present - msg, err := json.Marshal(Message{ + payload, err := json.Marshal(Message{ State: present, Updated: time.Now().UnixMilli(), }) - if err != nil { log.Println(err) } - token := client.Publish("automation/presence", 1, true, msg) + token := client.Publish("automation/presence", 1, true, payload) if token.Wait() && token.Error() != nil { log.Println(token.Error()) }