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:
Darell Tan
2023-06-25 02:20:01 +08:00
parent 7fbbec79d9
commit c61da984c9
3 changed files with 98 additions and 0 deletions

View File

@@ -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
View 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
View File

@@ -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.