Files
hapz2m/mapping_translator.go
Darell Tan 5e24287d8c Reworked translation mapping between Characteristic and Exposed values.
- Broke the code out into its own file.

- Added chaining and flipped translators to assist with re-using
  existing translators.  Also simplified logic when translating between
  HomeKit & Z2M values by removing special cases like the "binary"
  exposes.  Since everything can be expressed with translators &
  translator chains now, the process is streamlined.

- Wired up the defaultTranslator during the setup phase, so when mapping
  is called, there's no nil checks necessary; just a direct call to the
  mapping.Translator.

- Also added more documentation for the translation part since I forgot
  most of it after a year.
2024-11-22 01:27:53 +08:00

124 lines
3.6 KiB
Go

package hapz2m
import (
"fmt"
"reflect"
)
// Implements a translator between the exposed property and Characteristic.
// Generally translators should be flexible to translate in either direction,
// e.g. a percentage to 0-255 translator should be able to apply the percentage
// to either the exposed property side, or the Characteristic side, but the
// MappingTranslator is a fixed direction for simplicity.
type MappingTranslator interface {
ToCharacteristicValue(exposedValue any) (cValue any, err error)
ToExposedValue(cValue any) (exposedValue any, err error)
}
// Default pass-through "translator", where both exposed and Characteristic
// values are of the same or similar types.
type PassthruTranslator struct{}
var defaultTranslator = &PassthruTranslator{}
func (p *PassthruTranslator) ToExposedValue(v any) (any, error) { return v, nil }
func (p *PassthruTranslator) ToCharacteristicValue(v any) (any, error) { return v, nil }
// Chains another Translator to transform values further.
// You can chain another Translator on the ExposedSide or the CharacteristicSide:
//
// Exposed -- ToCharacteristicValue() --> Characteristic
// Value <--- ToExposedValue() --- Value
type ChainedTranslator struct{ ExposedSide, CharacteristicSide MappingTranslator }
func (t *ChainedTranslator) ToExposedValue(cVal any) (any, error) {
v, err := t.CharacteristicSide.ToExposedValue(cVal)
if err != nil {
return v, err
}
return t.ExposedSide.ToExposedValue(v)
}
func (t *ChainedTranslator) ToCharacteristicValue(eVal any) (any, error) {
v, err := t.ExposedSide.ToCharacteristicValue(eVal)
if err != nil {
return v, err
}
return t.CharacteristicSide.ToCharacteristicValue(v)
}
// Wraps a Translator and flips the translation direction.
// This allows a translator to work for either an exposed value or
// Characteristic value.
type FlippedTranslator struct{ T MappingTranslator }
func (t *FlippedTranslator) ToExposedValue(cVal any) (any, error) {
return t.T.ToCharacteristicValue(cVal)
}
func (t *FlippedTranslator) ToCharacteristicValue(eVal any) (any, error) {
return t.T.ToExposedValue(eVal)
}
var ErrTranslationError = fmt.Errorf("cannot translate value")
// Translates a binary type exposed value to specified Characteristic T/F values
type BoolTranslator struct{ TrueValue, FalseValue any }
func (t *BoolTranslator) ToExposedValue(cVal any) (any, error) {
switch cVal {
case t.TrueValue:
return true, nil
case t.FalseValue:
return false, nil
}
return nil, ErrTranslationError
}
func (t *BoolTranslator) ToCharacteristicValue(eVal any) (any, error) {
bVal, ok := eVal.(bool)
if !ok {
return nil, ErrTranslationError
} else if bVal {
return t.TrueValue, 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
}