Skip to content
Snippets Groups Projects
xaal.go 9.56 KiB
package main

import (
	"fmt"
	"log/slog"
	"math"

	"github.com/davecgh/go-spew/spew"
	"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"
)

type XAALDevice struct {
	*xaal.Device
}

type Contact struct {
	XAALDevice
}

type Thermometer struct {
	XAALDevice
}

type Hygrometer struct {
	XAALDevice
}

type LinkQuality struct {
	XAALDevice
}

type Battery struct {
	XAALDevice
}

type PowerRelay struct {
	XAALDevice
	Property  string
	Z2MDevice Z2MDevice
}

type PowerMeter struct {
	XAALDevice
}

type Lamp struct {
	XAALDevice
	Z2MDevice Z2MDevice
}

type LampDimmer struct {
	XAALDevice
	Z2MDevice Z2MDevice
}

type ButtonRemote struct {
	XAALDevice
	Z2MDevice Z2MDevice
}

type XAALDeviceInterface interface {
	update(map[string]interface{})
	getXAALDevice() *xaal.Device
}

func (dev *XAALDevice) setup(zDev *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) XAALDeviceInterface {
	dev := &Contact{XAALDevice{schemas.NewContact(addr)}}
	dev.setup(zDev)
	return dev
}

func (dev *Contact) update(payload map[string]interface{}) {
	value, exists := payload["contact"].(bool)
	if exists {
		dev.GetAttribute("detected").SetValue(!value)
	}
}

// =============================================================================
// Thermometer
// =============================================================================
func NewThermometer(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &Thermometer{XAALDevice{schemas.NewThermometer(addr)}}
	dev.setup(zDev)
	return dev
}

func (dev *Thermometer) update(payload map[string]interface{}) {
	value, exists := payload["temperature"].(float64)
	if exists {
		dev.GetAttribute("temperature").SetValue(value)
	}
}

// =============================================================================
// Hygrometer
// =============================================================================
func NewHygrometer(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &Hygrometer{XAALDevice{schemas.NewHygrometer(addr)}}
	dev.setup(zDev)
	return dev
}

func (dev *Hygrometer) update(payload map[string]interface{}) {
	value, exists := payload["humidity"].(float64)
	if exists {
		dev.GetAttribute("humidity").SetValue(value)
	}
}

// =============================================================================
// LinkQuality
// =============================================================================
func NewLinkQuality(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &LinkQuality{XAALDevice{schemas.NewLinkquality(addr)}}
	dev.setup(zDev)
	dev.RemoveAttribute("devices")
	dev.UnsupportedAttributes = []string{"devices"}
	return dev
}

func (dev *LinkQuality) update(payload map[string]interface{}) {
	value, exists := payload["linkquality"].(float64)
	if exists {
		value = math.Round(value / 255 * 100)
		dev.GetAttribute("level").SetValue(value)
	}
}

// =============================================================================
// Battery
// =============================================================================
func NewBattery(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &Battery{XAALDevice{schemas.NewBattery(addr)}}
	dev.setup(zDev)
	dev.RemoveAttribute("devices")
	dev.UnsupportedAttributes = []string{"devices"}
	return dev
}

func (dev *Battery) update(payload map[string]interface{}) {
	value, exists := payload["battery"].(float64)
	if exists {
		dev.GetAttribute("level").SetValue(value)
	}
}

// =============================================================================
// PowerRelay
// =============================================================================
func NewPowerRelay(addr uuid.UUID, zDev *Z2MDevice, property string) XAALDeviceInterface {
	dev := &PowerRelay{XAALDevice{schemas.NewPowerrelayToggle(addr)}, property, *zDev}
	dev.SetMethod("turn_on", dev.On)
	dev.SetMethod("turn_off", dev.Off)
	dev.SetMethod("toggle", dev.Toggle)
	dev.setup(zDev)
	return dev
}

func (dev *PowerRelay) update(payload map[string]interface{}) {
	value, exists := payload[dev.Property].(string)
	if exists {
		power := dev.GetAttribute("power")
		switch value {
		case "ON":
			power.SetValue(true)
		case "OFF":
			power.SetValue(false)
		}
	}
}

func (dev *PowerRelay) SetProperty(value string) {
	body := fmt.Sprintf(`{"%s": "%s"}`, dev.Property, value)
	dev.Z2MDevice.Set(body)
}

func (dev *PowerRelay) On(xaal.MessageBody) *xaal.MessageBody {
	dev.SetProperty("ON")
	return nil
}

func (dev *PowerRelay) Off(xaal.MessageBody) *xaal.MessageBody {
	dev.SetProperty("OFF")
	return nil
}

func (dev *PowerRelay) Toggle(xaal.MessageBody) *xaal.MessageBody {
	dev.SetProperty("TOGGLE")
	return nil
}

// =============================================================================
// PowerMeter
// =============================================================================
func NewPowerMeter(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &PowerMeter{XAALDevice{schemas.NewPowermeter(addr)}}
	dev.setup(zDev)
	dev.RemoveAttribute("devices")
	dev.UnsupportedAttributes = []string{"devices"}
	return dev
}

func (dev *PowerMeter) update(payload map[string]interface{}) {
	// power
	value, exists := payload["power"].(float64)
	if exists {
		dev.GetAttribute("power").SetValue(value)
	}
	// energy
	value, exists = payload["energy"].(float64)
	if exists {
		dev.GetAttribute("energy").SetValue(value)
	}
}

// =============================================================================
// Lamp
// =============================================================================
func NewLamp(addr uuid.UUID, zDev *Z2MDevice, expose *Expose) XAALDeviceInterface {
	// This is the default lamp device, we will change it's devType if needed
	dev := &Lamp{XAALDevice{schemas.NewLamp(addr)}, *zDev}
	dev.SetMethod("turn_on", dev.turnOn)
	dev.SetMethod("turn_off", dev.turnOff)
	dev.SetMethod("toggle", dev.toggle)
	if expose.GetFeature("brightness") != nil {
		dev.DevType = "lamp.dimmer"
		dev.AddAttribute("brightness", 0)
		dev.AddMethod("set_brightness", dev.setBrightness)
	}
	if expose.GetFeature("color_temp") != nil {
		// All bulb w/ color temp are dimmer
		dev.AddAttribute("white_temperature", 0)
		dev.AddMethod("set_white_temperature", dev.setWhiteTemperature)
	}
	if expose.GetFeature("color_xy") != nil {
		dev.DevType = "lamp.color"
		dev.AddAttribute("hsv", nil)
		dev.AddAttribute("mode", nil)
		dev.UnsupportedAttributes = []string{"scene"}
		dev.AddMethod("set_hsv", dev.setHSV)
		dev.AddMethod("set_mode", dev.setMode)
	}

	dev.setup(zDev)
	return dev
}

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)
		}
	}
	// brightness
	brightness, exists := payload["brightness"].(float64)
	if exists {
		brightness = brightness / 255 * 100
		dev.GetAttribute("brightness").SetValue(int(brightness))
	}
	// color_temp
	color_temp, exists := payload["color_temp"].(float64)
	if exists {
		// Mired = 1,000,000 / Temperature in Kelvin
		color_temp = 1000000 / color_temp
		dev.GetAttribute("white_temperature").SetValue(int(color_temp))
	}
}

func (dev *Lamp) turnOn(xaal.MessageBody) *xaal.MessageBody {
	dev.Z2MDevice.Set(`{"state": "ON"}`)
	return nil
}

func (dev *Lamp) turnOff(xaal.MessageBody) *xaal.MessageBody {
	dev.Z2MDevice.Set(`{"state": "OFF"}`)
	return nil
}

func (dev *Lamp) toggle(xaal.MessageBody) *xaal.MessageBody {
	dev.Z2MDevice.Set(`{"state": "TOGGLE"}`)
	return nil
}

func (dev *Lamp) setBrightness(body xaal.MessageBody) *xaal.MessageBody {
	// TODO: I don't know why we receive a uint64 instead of a float64
	value, exists := body["brightness"].(uint64)
	if exists {
		dev.Z2MDevice.Set(fmt.Sprintf(`{"brightness": %d}`, value*255/100))
	}
	return nil
}

func (dev *Lamp) setWhiteTemperature(body xaal.MessageBody) *xaal.MessageBody {
	value, exists := body["white_temperature"].(uint64)
	if exists {
		value = 1000000 / value
		dev.Z2MDevice.Set(fmt.Sprintf(`{"color_temp": %d}`, value))
	}
	return nil
}

func (dev *Lamp) setHSV(body xaal.MessageBody) *xaal.MessageBody {
	spew.Dump(body)
	return nil
}

func (dev *Lamp) setMode(body xaal.MessageBody) *xaal.MessageBody {
	spew.Dump(body)
	return nil
}

// =============================================================================
// NetButtonRemote
// =============================================================================
func NewButtonRemote(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &ButtonRemote{XAALDevice{schemas.NewButtonRemote(addr)}, *zDev}
	dev.setup(zDev)
	dev.SetMethod("get_buttons", dev.GetButtons)
	return dev
}

func (dev *ButtonRemote) update(payload map[string]interface{}) {
	value, exists := payload["action"].(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 {
	slog.Info("defaultGetButtons method: get_buttons")
	body := make(xaal.MessageBody)
	action := dev.Z2MDevice.GetExpose("action")
	if action != nil {
		body["buttons"] = action.Values
	}
	return &body
}