mirror of
https://github.com/nikdoof/hapz2m.git
synced 2026-01-30 13:58:17 +00:00
Add support for dimmers and dimmable bulbs
The exposes entry looks similar to a switch, except the type is a "light" and it has `state` and `brightness`. Tested only on single channel dimmers. Also added a PercentageTranslator to translate between the HomeKit `brightness`, which is a percentage, to/from an arbitrary numeric value in Z2M.
This commit is contained in:
@@ -15,6 +15,7 @@ Currently only supports the types of Zigbee devices I have:
|
|||||||
- Contact sensors
|
- Contact sensors
|
||||||
- Motion sensors
|
- Motion sensors
|
||||||
- Wall switch
|
- Wall switch
|
||||||
|
- Dimmer, or dimmable light bulbs
|
||||||
|
|
||||||
If you do use this software, note that it's in development and may contains bugs,
|
If you do use this software, note that it's in development and may contains bugs,
|
||||||
or may even burn your house down. I offer no warranty, but you are welcome to file bugs.
|
or may even burn your house down. I offer no warranty, but you are welcome to file bugs.
|
||||||
|
|||||||
50
s_light.go
Normal file
50
s_light.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package hapz2m
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/brutella/hap/accessory"
|
||||||
|
"github.com/brutella/hap/characteristic"
|
||||||
|
"github.com/brutella/hap/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createLightServices(dev *Device) (byte, []*service.S, []*ExposeMapping, error) {
|
||||||
|
var svcs []*service.S
|
||||||
|
var exposes []*ExposeMapping
|
||||||
|
|
||||||
|
for _, exp := range dev.Definition.Exposes {
|
||||||
|
if exp.Ignored() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case exp.Type == "light" && len(exp.Features) > 0:
|
||||||
|
exp := exp // create a copy
|
||||||
|
|
||||||
|
light := service.NewLightbulb()
|
||||||
|
|
||||||
|
for _, feat := range exp.Features {
|
||||||
|
if !feat.IsStateSetGet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
feat := feat
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case feat.Name == "state" && feat.Type == "binary":
|
||||||
|
svcs = append(svcs, light.S)
|
||||||
|
exposes = append(exposes, &ExposeMapping{&feat, light.On.C, nil})
|
||||||
|
|
||||||
|
case feat.Name == "brightness" && feat.Type == "numeric":
|
||||||
|
brightness := characteristic.NewBrightness()
|
||||||
|
light.AddC(brightness.C)
|
||||||
|
exposes = append(exposes, &ExposeMapping{&feat, brightness.C, nil})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessory.TypeLightbulb, svcs, exposes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterCreateServiceHandler(createLightServices)
|
||||||
|
}
|
||||||
47
z2m.go
47
z2m.go
@@ -124,6 +124,17 @@ func createAccessory(dev *Device) (*accessory.A, []*ExposeMapping, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if it's a percentage, then don't copy
|
||||||
|
if e.Characteristic.Unit == characteristic.UnitPercentage {
|
||||||
|
// assign a PercentageTranslator here, if there wasn't already
|
||||||
|
if e.Translator == nil && e.ExposesEntry.IsSettable() &&
|
||||||
|
e.ExposesEntry.ValueMin != nil && e.ExposesEntry.ValueMax != nil {
|
||||||
|
|
||||||
|
e.Translator = &PercentageTranslator{*e.ExposesEntry.ValueMin, *e.ExposesEntry.ValueMax}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
err := e.ExposesEntry.CopyValueRanges(e.Characteristic)
|
err := e.ExposesEntry.CopyValueRanges(e.Characteristic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("cant copy value ranges for %s to cfmt %s: %s",
|
return nil, nil, fmt.Errorf("cant copy value ranges for %s to cfmt %s: %s",
|
||||||
@@ -223,6 +234,42 @@ func (t *BoolTranslator) ToCharacteristicValue(eVal any) (any, error) {
|
|||||||
return t.FalseValue, nil
|
return t.FalseValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translates a numeric type exposed value to percentage Characteristic values
|
||||||
|
type PercentageTranslator struct{ Min, Max float64 }
|
||||||
|
|
||||||
|
func (t *PercentageTranslator) ToExposedValue(cVal any) (any, error) {
|
||||||
|
cVal2, ok := valToFloat64(cVal)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrTranslationError
|
||||||
|
}
|
||||||
|
v := t.Min + (cVal2 / 100. * (t.Max - t.Min))
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PercentageTranslator) ToCharacteristicValue(eVal any) (any, error) {
|
||||||
|
eVal2, ok := valToFloat64(eVal)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrTranslationError
|
||||||
|
}
|
||||||
|
v := (eVal2 - t.Min) * 100. / (t.Max - t.Min)
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts numeric values to float64, if possible
|
||||||
|
// Returns the converted float64 value and a bool indicating if it was successful.
|
||||||
|
func valToFloat64(v any) (float64, bool) {
|
||||||
|
val := reflect.ValueOf(v)
|
||||||
|
switch {
|
||||||
|
case val.CanInt():
|
||||||
|
return float64(val.Int()), true
|
||||||
|
case val.CanUint():
|
||||||
|
return float64(val.Uint()), true
|
||||||
|
case val.CanFloat():
|
||||||
|
return val.Float(), true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
|
||||||
// Maps a zigbee2mqtt device property into a HAP characteristic.
|
// Maps a zigbee2mqtt device property into a HAP characteristic.
|
||||||
|
|||||||
Reference in New Issue
Block a user