-
KERDREUX Jerome authored
- Added motion sensors - Added Luxmeter
KERDREUX Jerome authored- Added motion sensors - Added Luxmeter
xaal.go 13.66 KiB
package main
import (
"fmt"
"log/slog"
"github.com/lucasb-eyer/go-colorful"
"gitlab.imt-atlantique.fr/xaal/code/go/core/schemas"
"gitlab.imt-atlantique.fr/xaal/code/go/core/uuid"
"gitlab.imt-atlantique.fr/xaal/code/go/core/xaal"
)
// This is a xaal.Device w/ Z2MDevice and Expose
type XAALDevice struct {
*xaal.Device
Z2MDevice *Z2MDevice
Expose *Expose
}
type Contact struct {
XAALDevice
}
type Thermometer struct {
XAALDevice
}
type Hygrometer struct {
XAALDevice
}
type LinkQuality struct {
XAALDevice
}
type Battery struct {
XAALDevice
}
type PowerRelay struct {
XAALDevice
}
type PowerMeter struct {
XAALDevice
}
type Lamp struct {
XAALDevice
}
type ButtonRemote struct {
XAALDevice
}
type Motion struct {
XAALDevice
}
type LuxMeter struct {
XAALDevice
}
type XAALDeviceInterface interface {
update(map[string]interface{})
GetXAALDevice() *xaal.Device
}
func (dev *XAALDevice) setup() {
zDev := dev.Z2MDevice
dev.VendorID = zDev.Definition.Vendor
dev.ProductID = zDev.Definition.Model
dev.HWID = zDev.IeeeAddress
dev.Version = zDev.SwBuildID
dev.Info = "z2m:" + zDev.Type + ":" + zDev.FriendlyName
}
func (dev *XAALDevice) GetXAALDevice() *xaal.Device {
return dev.Device
}
// =============================================================================
// Contact
// =============================================================================
func NewContact(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &Contact{XAALDevice{schemas.NewContact(addr), zDev, exp}}
dev.setup()
return dev
}
func (dev *Contact) update(payload map[string]interface{}) {
value, exists := payload[dev.Expose.Name].(bool)
if exists {
dev.GetAttribute("detected").SetValue(!value)
}
}
// =============================================================================
// Thermometer
// =============================================================================
func NewThermometer(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &Thermometer{XAALDevice{schemas.NewThermometer(addr), zDev, exp}}
dev.setup()
return dev
}
func (dev *Thermometer) update(payload map[string]interface{}) {
value, exists := payload[dev.Expose.Name].(float64)
if exists {
dev.GetAttribute("temperature").SetValue(value)
}
}
// =============================================================================
// Hygrometer
// =============================================================================
func NewHygrometer(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &Hygrometer{XAALDevice{schemas.NewHygrometer(addr), zDev, exp}}
dev.setup()
return dev
}
func (dev *Hygrometer) update(payload map[string]interface{}) {
value, exists := payload[dev.Expose.Name].(float64)
if exists {
dev.GetAttribute("humidity").SetValue(value)
}
}
// =============================================================================
// LinkQuality
// =============================================================================
func NewLinkQuality(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &LinkQuality{XAALDevice{schemas.NewLinkquality(addr), zDev, exp}}
dev.GetAttribute("level").Value = 0 // override type to int
dev.setup()
dev.RemoveAttribute("devices")
dev.UnsupportedAttributes = []string{"devices"}
return dev
}
func (dev *LinkQuality) update(payload map[string]interface{}) {
value, err := convertToInt(payload[dev.Expose.Name])
if err == nil {
value = value * 100 / 255
oldValue := dev.GetAttribute("level").Value.(int)
if value > oldValue+5 || value < oldValue-5 {
dev.GetAttribute("level").SetValue(value)
}
}
}
// =============================================================================
// Battery
// =============================================================================
func NewBattery(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &Battery{XAALDevice{schemas.NewBattery(addr), zDev, exp}}
dev.setup()
dev.RemoveAttribute("devices")
dev.UnsupportedAttributes = []string{"devices"}
return dev
}
func (dev *Battery) update(payload map[string]interface{}) {
value, exists := payload[dev.Expose.Name].(float64)
if exists {
dev.GetAttribute("level").SetValue(value)
}
}
// =============================================================================
// PowerRelay
// =============================================================================
func NewPowerRelay(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &PowerRelay{XAALDevice{schemas.NewPowerrelayToggle(addr), zDev, exp}}
dev.SetMethod("turn_on", dev.turnOn)
dev.SetMethod("turn_off", dev.turnOff)
dev.SetMethod("toggle", dev.toggle)
dev.setup()
return dev
}
func (dev *PowerRelay) update(payload map[string]interface{}) {
// TODO: Handle missing state property, this can be done in constructor
state := dev.Expose.GetFeature("state")
value, exists := payload[state.Property].(string)
if exists {
power := dev.GetAttribute("power")
switch value {
case "ON":
power.SetValue(true)
case "OFF":
power.SetValue(false)
}
}
}
func (dev *PowerRelay) SetState(value string) {
state := dev.Expose.GetFeature("state")
body := fmt.Sprintf(`{"%s": "%s"}`, state.Property, value)
dev.Z2MDevice.Set(body)
}
func (dev *PowerRelay) turnOn(xaal.MessageBody) *xaal.MessageBody {
dev.SetState("ON")
return nil
}
func (dev *PowerRelay) turnOff(xaal.MessageBody) *xaal.MessageBody {
dev.SetState("OFF")
return nil
}
func (dev *PowerRelay) toggle(xaal.MessageBody) *xaal.MessageBody {
dev.SetState("TOGGLE")
return nil
}
// =============================================================================
// PowerMeter
// =============================================================================
func NewPowerMeter(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &PowerMeter{XAALDevice{schemas.NewPowermeter(addr), zDev, exp}}
dev.setup()
dev.RemoveAttribute("devices")
dev.UnsupportedAttributes = []string{"devices"}
return dev
}
func (dev *PowerMeter) update(payload map[string]interface{}) {
// power
value, exists := payload[dev.Expose.Name].(float64)
if exists {
dev.GetAttribute("power").SetValue(value)
}
// energy
value, exists = payload["energy"].(float64)
if exists {
dev.GetAttribute("energy").SetValue(value)
}
}
// =============================================================================
// Lamp
// =============================================================================
// The Lamp don't use the Expose.Names right now. Perhaps in future, if we find
// some lamps w/ different exposes. This will make the code more complex.
func NewLamp(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
// This is the default lamp device, we will change it's devType if needed
dev := &Lamp{XAALDevice{schemas.NewLamp(addr), zDev, exp}}
dev.SetMethod("turn_on", dev.turnOn)
dev.SetMethod("turn_off", dev.turnOff)
dev.SetMethod("toggle", dev.toggle)
if exp.GetFeature("brightness") != nil {
dev.DevType = "lamp.dimmer"
dev.AddAttribute("brightness", 0)
dev.AddMethod("set_brightness", dev.setBrightness)
}
if exp.GetFeature("color_temp") != nil {
dev.DevType = "lamp.dimmer"
dev.AddAttribute("white_temperature", 0)
dev.AddMethod("set_white_temperature", dev.setWhiteTemperature)
}
if exp.GetFeature("color_xy") != nil {
dev.DevType = "lamp.color"
dev.AddAttribute("hsv", []float64{0, 0, 0})
dev.AddAttribute("mode", nil)
dev.UnsupportedAttributes = []string{"scene"}
dev.AddMethod("set_hsv", dev.setHSV)
dev.AddMethod("set_mode", dev.setMode)
}
dev.setup()
return dev
}
func (dev *Lamp) getMode() string {
mode := dev.GetAttribute("mode")
if mode != nil {
return mode.Value.(string)
}
return "white"
}
func (dev *Lamp) update(payload map[string]interface{}) {
// state
state, exists := payload["state"].(string)
if exists {
light := dev.GetAttribute("light")
switch state {
case "ON":
light.SetValue(true)
case "OFF":
light.SetValue(false)
}
}
// color_mode
color_mode, exists := payload["color_mode"].(string)
if exists {
mode := dev.GetAttribute("mode")
// only color lamp have a mode
if mode != nil {
switch color_mode {
case "xy":
mode.SetValue("color")
case "color_temp":
mode.SetValue("white")
}
}
}
// brightness
brightness, err := convertToInt(payload["brightness"])
if err == nil {
brightness = brightness * 100 / 255
dev.GetAttribute("brightness").SetValue(int(brightness))
}
// color_temp
if dev.getMode() == "white" {
// color_temp change when we are in color mode (looks like a z2m bug)
// so we have to only update when we are in white mode. Without this check
// the color_temp is wrong when you change the mode w/ setMode
color_temp, err := convertToInt(payload["color_temp"])
if err == nil {
dev.GetAttribute("white_temperature").SetValue(convertMired(color_temp))
}
}
// colors
if dev.getMode() == "color" {
color, exists := payload["color"].(map[string]interface{})
if exists {
x, _ := convertToFloat64(color["x"])
y, _ := convertToFloat64(color["y"])
brigthness, _ := convertToFloat64(payload["brightness"])
value := xyToColor(x, y, brigthness)
slog.Debug("color", "color", value.Hex(), "x", x, "y", y, "tmp", brigthness)
hue, sat, val := value.Hsv()
hue = roundToDecimal(hue, 1)
sat = roundToDecimal(sat, 3)
val = roundToDecimal(val, 3)
dev.GetAttribute("hsv").SetValue([]float64{hue, sat, val})
}
}
}
func (dev *Lamp) setState(value string) {
state := dev.Expose.GetFeature("state")
body := fmt.Sprintf(`{"%s": "%s"}`, state.Property, value)
dev.Z2MDevice.Set(body)
}
func (dev *Lamp) turnOn(xaal.MessageBody) *xaal.MessageBody {
dev.setState("ON")
return nil
}
func (dev *Lamp) turnOff(xaal.MessageBody) *xaal.MessageBody {
dev.setState("OFF")
return nil
}
func (dev *Lamp) toggle(xaal.MessageBody) *xaal.MessageBody {
dev.setState("TOGGLE")
return nil
}
func (dev *Lamp) setBrightness(body xaal.MessageBody) *xaal.MessageBody {
value, exists := body["brightness"]
if exists {
target, _ := convertToInt(value)
dev.Z2MDevice.Set(fmt.Sprintf(`{"brightness": %d}`, target*255/100))
}
return nil
}
func (dev *Lamp) setWhiteTemperature(body xaal.MessageBody) *xaal.MessageBody {
value, exists := body["white_temperature"]
if exists {
target, _ := convertToInt(value)
target = convertMired(target)
dev.Z2MDevice.Set(fmt.Sprintf(`{"color_temp": %d}`, target))
}
return nil
}
func (dev *Lamp) setHSV(body xaal.MessageBody) *xaal.MessageBody {
value, exist := body["hsv"].([]interface{})
if exist {
hue, _ := convertToFloat64(value[0])
sat, _ := convertToFloat64(value[1])
val, _ := convertToFloat64(value[2])
color := colorful.Hsv(hue, sat, val)
slog.Debug("setHSV", "color", color.Hex(), "hue", hue, "sat", sat, "val", val)
dev.Z2MDevice.Set(fmt.Sprintf(`{"color": { "hex": "%s" }}`, color.Hex()))
}
return nil
}
func (dev *Lamp) setMode(body xaal.MessageBody) *xaal.MessageBody {
value, exist := body["mode"].(string)
if exist {
// for a unknown reason, we are unable to set the color_mode
// z2m doesn't give an error, but the color_mode doesn't change
// so we set old color or temperature
//
// dev.Z2MDevice.Set(`{"color_mode": "color_temp"}`)
// dev.Z2MDevice.Set(`{"color_mode": "xy"}`)
switch value {
case "color":
hsv := dev.GetAttribute("hsv").Value.([]float64)
color := colorful.Hsv(hsv[0], hsv[1], hsv[2])
dev.Z2MDevice.Set(fmt.Sprintf(`{"color": { "hex": "%s" }}`, color.Hex()))
case "white":
value := dev.GetAttribute("white_temperature").Value.(int)
// if booted in color mode, while_temperature is 0 => divide crash
if value == 0 {
value = 3000
}
value = convertMired(value)
dev.Z2MDevice.Set(fmt.Sprintf(`{"color_temp": %d}`, value))
}
}
return nil
}
// =============================================================================
// NetButtonRemote
// =============================================================================
func NewButtonRemote(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &ButtonRemote{XAALDevice{schemas.NewButtonRemote(addr), zDev, exp}}
dev.setup()
dev.SetMethod("get_buttons", dev.getButtons)
return dev
}
func (dev *ButtonRemote) update(payload map[string]interface{}) {
value, exists := payload[dev.Expose.Name].(string)
if exists {
body := make(xaal.MessageBody)
body["action"] = 0
body["button"] = value
dev.SendNotification("click", body)
}
}
func (dev *ButtonRemote) getButtons(xaal.MessageBody) *xaal.MessageBody {
body := make(xaal.MessageBody)
action := dev.Z2MDevice.GetExpose(dev.Expose.Name)
if action != nil {
body["buttons"] = action.Values
}
return &body
}
// =============================================================================
// Motion
// =============================================================================
func NewMotion(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &Motion{XAALDevice{schemas.NewMotion(addr), zDev, exp}}
dev.setup()
return dev
}
func (dev *Motion) update(payload map[string]interface{}) {
value, exists := payload[dev.Expose.Name].(bool)
if exists {
dev.GetAttribute("presence").SetValue(value)
}
}
// =============================================================================
// LuxMeter
// =============================================================================
func NewLuxMeter(addr uuid.UUID, zDev *Z2MDevice, exp *Expose) XAALDeviceInterface {
dev := &LuxMeter{XAALDevice{schemas.NewLuxmeter(addr), zDev, exp}}
dev.GetAttribute("illuminance").Value = 0 // override
dev.setup()
return dev
}
func (dev *LuxMeter) update(payload map[string]interface{}) {
lux, err := convertToInt(payload[dev.Expose.Name])
if err == nil {
dev.GetAttribute("illuminance").SetValue(lux)
}
}