Files
pmomusic/upnp/devices/services/statevariables/statevariable.go

420 lines
11 KiB
Go
Raw Normal View History

2025-07-13 07:02:26 +02:00
package statevariables
2025-06-21 17:22:56 +02:00
import (
"fmt"
2025-09-06 17:45:28 +02:00
"maps"
2025-06-21 17:22:56 +02:00
"reflect"
"slices"
"strings"
"time"
log "github.com/sirupsen/logrus"
)
type EventType string
2025-07-13 06:59:56 +02:00
type StateConditionFunc func(instance *StateVarInstance) bool
2025-07-12 12:28:13 +02:00
type StringValueParser func(value string) (interface{}, error)
type ValueSerializer func(value interface{}) (string, error)
2025-06-21 17:22:56 +02:00
2025-07-13 06:59:56 +02:00
type StateVariable struct {
2025-07-12 12:28:13 +02:00
name string // Name of the state value (e.g., "Volume", "Brightness")
valueType StateVarType // Type of state value, see the upnp/statevaluetype package for more information on this.
step interface{} // Step size for incremental state values (e.g., "10")
2025-06-21 17:22:56 +02:00
modifiable bool
2025-07-12 12:28:13 +02:00
eventConditions map[string]StateConditionFunc
2025-06-21 17:22:56 +02:00
description string
defaultValue interface{}
valueRange *ValueRange
allowedValues []interface{}
sendEvents bool
2025-07-12 12:28:13 +02:00
parse StringValueParser
marshal ValueSerializer
2025-06-21 17:22:56 +02:00
}
2025-07-12 16:19:20 +02:00
// BitSize returns the number of bits that will be used to represent values
// when this type is used in an UPnP State Variable. The returned value can be
// either 8, 16, 24, 32, or 64, depending on whether t is Byte, Boolean, I2, Ui2,
// I4, Ui4 respectively. If none of these types match, it will return -1.
2025-07-13 06:59:56 +02:00
func (sv StateVariable) BitSize() int {
2025-07-12 16:19:20 +02:00
return sv.valueType.BitSize()
}
2025-06-21 17:22:56 +02:00
// Name returns the state variable's name (e.g., "Volume", "Brightness").
2025-07-13 06:59:56 +02:00
func (sv StateVariable) Name() string {
2025-07-12 16:19:20 +02:00
return sv.name
2025-06-21 17:22:56 +02:00
}
2025-08-01 12:45:42 +02:00
func (sv StateVariable) TypeID() string {
return "StateVariable"
}
2025-06-21 17:22:56 +02:00
// Type returns the UPnP data type of the state variable.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) Type() StateVarType {
2025-07-12 12:28:13 +02:00
return state.valueType
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) AddEventCondition(name string, condition StateConditionFunc) {
2025-07-12 12:28:13 +02:00
state.eventConditions[name] = condition
2025-06-21 17:22:56 +02:00
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) DeleteEventConditions(name string) error {
2025-07-12 12:28:13 +02:00
if _, ok := state.eventConditions[name]; !ok {
return fmt.Errorf("%s: no such event condition (%s)", state.name, name)
}
delete(state.eventConditions, name)
return nil
2025-06-21 17:22:56 +02:00
}
// ClearEventConditions réinitialise toutes les conditions
2025-07-13 06:59:56 +02:00
func (state *StateVariable) ClearEventConditions() {
2025-07-12 12:28:13 +02:00
state.eventConditions = make(map[string]StateConditionFunc)
}
2025-07-13 06:59:56 +02:00
func (sv *StateVariable) SetMinDelta(minDelta interface{}) error {
2025-07-12 12:28:13 +02:00
if minDelta == nil {
return fmt.Errorf("%s: nil is an invalid minimum delta value", sv.name)
}
minDelta, err := sv.Cast(minDelta)
if err != nil {
2025-07-12 16:19:20 +02:00
return fmt.Errorf("%s: invalid minimum delta value %v : %v", sv.name, minDelta, err)
2025-07-12 12:28:13 +02:00
}
2025-07-12 16:19:20 +02:00
// mdf := func(instance *StateValueInstance) bool {
// o := instance.previousValue
// n := instance.value
2025-07-12 12:28:13 +02:00
2025-07-12 16:19:20 +02:00
// r,err := instance.model.valueType.ValueRange(o, n)
2025-07-12 12:28:13 +02:00
2025-07-12 16:19:20 +02:00
// if err != nil {
// return false
// }
2025-07-12 12:28:13 +02:00
2025-07-12 16:19:20 +02:00
// return instance.model.valueType.InRange()
// }
2025-07-12 12:28:13 +02:00
2025-07-12 16:19:20 +02:00
// sv.eventConditions["MinDelta"] = mdf
2025-07-12 12:28:13 +02:00
return nil
2025-06-21 17:22:56 +02:00
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) SetDefault(value interface{}) error {
2025-07-12 16:19:20 +02:00
var err error
var valid bool
if valid, err = state.IsValidValue(value); valid && err == nil {
2025-07-12 12:28:13 +02:00
cvalue, _ := state.valueType.Cast(value)
2025-06-21 17:22:56 +02:00
state.defaultValue = cvalue
log.Debugf("🐞 Setting default value for %v to %v", state.name, cvalue)
return nil
}
2025-07-12 16:19:20 +02:00
return fmt.Errorf("invalid default value for %v (%v) : %v", state.name, value, err)
2025-06-21 17:22:56 +02:00
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) HasDefault() bool {
2025-06-21 17:22:56 +02:00
return state.defaultValue != nil
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) DefaultValue() interface{} {
2025-06-21 17:22:56 +02:00
if !state.HasDefault() {
2025-07-12 12:28:13 +02:00
return state.valueType.DefaultValue()
2025-06-21 17:22:56 +02:00
}
2025-09-06 17:45:28 +02:00
return state.defaultValue
2025-06-21 17:22:56 +02:00
}
// HasRange indicates if a value range constraint is defined.
// Returns true if min/max boundaries are set.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) HasRange() bool {
2025-06-21 17:22:56 +02:00
return state.valueRange != nil
}
// Maximum returns the upper bound of the value range.
// Returns nil if no range is defined.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) Maximum() interface{} {
2025-06-21 17:22:56 +02:00
if state.valueRange == nil {
return nil
}
return state.valueRange.max
}
// Minimum returns the lower bound of the value range.
// Returns nil if no range is defined.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) Minimum() interface{} {
2025-06-21 17:22:56 +02:00
if state.valueRange == nil {
return nil
}
return state.valueRange.min
}
// SetRange defines the inclusive value range [min, max].
// Validates and casts values to the state variable's type.
//
// Parameters:
//
// min: Lower boundary value
// max: Upper boundary value
//
// Returns:
//
// error: If values can't be cast to the type or are nil
//
// Example:
//
// err := volumeState.SetRange(0, 100) // 0-100 range for volume
2025-07-13 06:59:56 +02:00
func (state *StateVariable) SetRange(min, max interface{}) error {
2025-06-21 17:22:56 +02:00
if min == nil || max == nil {
return fmt.Errorf("min and max must not be nil")
}
2025-07-12 12:28:13 +02:00
limits, err := state.valueType.ValueRange(min, max)
2025-06-21 17:22:56 +02:00
if err != nil {
return fmt.Errorf("setting range: %v", err)
}
state.valueRange = limits
log.Debugf("🐞 Setting range of %s to [%v, %v]", state.name, min, max)
return nil
}
// UpdateMinimalValue dynamically updates the lower range boundary.
// Requires an existing range to be set.
//
// Parameters:
//
// value: New minimum value
//
// Returns:
//
// error: If no range exists or value can't be cast
2025-07-13 06:59:56 +02:00
func (state *StateVariable) UpdateMinimalValue(value interface{}) error {
2025-06-21 17:22:56 +02:00
if state.valueRange == nil {
return fmt.Errorf("no range set for value %v", state.name)
}
2025-07-12 12:28:13 +02:00
cvalue, err := state.valueType.Cast(value)
2025-06-21 17:22:56 +02:00
if err != nil {
return fmt.Errorf("casting value: %v", err)
}
state.valueRange.min = cvalue
log.Debugf("🐞 Updating minimal value of %s to %v", state.name, cvalue)
return nil
}
// UpdateMaximalValue dynamically updates the upper range boundary.
// Requires an existing range to be set.
//
// Parameters:
//
// value: New maximum value
//
// Returns:
//
// error: If no range exists or value can't be cast
2025-07-13 06:59:56 +02:00
func (state *StateVariable) UpdateMaximalValue(value interface{}) error {
2025-06-21 17:22:56 +02:00
if state.valueRange == nil {
return fmt.Errorf("no range set for value %v", state.name)
}
2025-07-12 12:28:13 +02:00
cvalue, err := state.valueType.Cast(value)
2025-06-21 17:22:56 +02:00
if err != nil {
return fmt.Errorf("casting value: %v", err)
}
state.valueRange.max = cvalue
log.Debugf("🐞 Updating maximal value of %s to %v", state.name, cvalue)
return nil
}
// IsSendingEvents indicates if state changes trigger UPnP events.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) IsSendingEvents() bool {
2025-06-21 17:22:56 +02:00
return state.sendEvents
}
// SetSendingEvents enables event notifications for state changes.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) SetSendingEvents() {
2025-06-21 17:22:56 +02:00
state.sendEvents = true
log.Debugf("🐞 Enabling event sending for %s", state.name)
}
// UnsetSendingEvents disables event notifications for state changes.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) UnsetSendingEvents() {
2025-06-21 17:22:56 +02:00
state.sendEvents = false
log.Debugf("🐞 Disabling event sending for %s", state.name)
}
// HasAllowedValues indicates if an allowed value list is defined.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) HasAllowedValues() bool {
2025-06-21 17:22:56 +02:00
return len(state.allowedValues) > 0
}
// AllowedValues returns the list of permitted values.
// Returns an empty slice if no values are defined.
2025-07-13 06:59:56 +02:00
func (state *StateVariable) AllowedValues() []interface{} {
2025-06-21 17:22:56 +02:00
return state.allowedValues
}
// AppendAllowedValue adds values to the permitted value list.
// Values are cast to the state variable's type before adding.
//
// Parameters:
//
// value: One or more values to add
//
// Returns:
//
// error: If any value can't be cast to the type
//
// Example:
//
// err := state.AppendAllowedValue("PLAYING", "PAUSED", "STOPPED")
2025-07-13 06:59:56 +02:00
func (state *StateVariable) AppendAllowedValue(value ...interface{}) error {
2025-06-21 17:22:56 +02:00
state.allowedValues = slices.Grow(state.allowedValues, len(value))
for _, v := range value {
2025-07-12 12:28:13 +02:00
cv, err := state.valueType.Cast(v)
2025-06-21 17:22:56 +02:00
if err != nil {
return fmt.Errorf("casting allowed value: %v", err)
}
state.allowedValues = append(state.allowedValues, cv)
}
log.Debugf("🐞 Added allowed values to %s: %v", state.name, value)
return nil
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) HasDescription() bool {
2025-06-21 17:22:56 +02:00
return len(state.description) > 0
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) Description() string {
2025-06-21 17:22:56 +02:00
return state.description
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) SetDescription(desc string) {
2025-06-21 17:22:56 +02:00
state.description = strings.TrimSpace(desc)
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) IsConstant() bool {
2025-06-21 17:22:56 +02:00
return !state.modifiable
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) SetConstant() {
2025-06-21 17:22:56 +02:00
state.modifiable = false
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) SetModifiable() {
2025-06-21 17:22:56 +02:00
state.modifiable = true
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) SetStep(step interface{}) error {
2025-06-21 17:22:56 +02:00
// Validation que le step correspond au type de la variable
2025-07-12 12:28:13 +02:00
if _, err := state.valueType.Cast(step); err != nil {
2025-06-21 17:22:56 +02:00
return fmt.Errorf("invalid step type: %v", err)
}
state.step = step
return nil
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) UnsetStep() {
2025-06-21 17:22:56 +02:00
state.step = nil
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) HasStep() bool {
2025-06-21 17:22:56 +02:00
return state.step != nil
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) Step() interface{} {
2025-06-21 17:22:56 +02:00
return state.step
}
2025-07-13 06:59:56 +02:00
func (state *StateVariable) SetAllowedValues(allowed ...interface{}) {
state.ClearAllowedValues()
state.AppendAllowedValues(allowed...)
}
func (state *StateVariable) AppendAllowedValues(allowed ...interface{}) error {
state.allowedValues = slices.Grow(state.allowedValues, len(allowed))
var err error
for _, val := range allowed {
val, err = state.valueType.Cast(val)
if err != nil {
return err
}
state.allowedValues = append(state.allowedValues, val)
}
return nil
}
func (state *StateVariable) ClearAllowedValues() {
state.allowedValues = make([]interface{}, 0)
}
2025-09-06 17:45:28 +02:00
// bool: True if within range or no range defined
func (state *StateVariable) IsValueInRange(value interface{}) (bool, error) {
return state.valueType.InRange(value, state.valueRange)
2025-06-21 17:22:56 +02:00
}
2025-07-12 12:28:13 +02:00
2025-09-06 17:45:28 +02:00
func (state *StateVariable) IsValueAllowed(value interface{}) (bool, error) {
if !state.HasAllowedValues() {
return true, nil // No list = any value valid
2025-07-12 12:28:13 +02:00
}
2025-09-06 17:45:28 +02:00
cvalue, err := state.Cast(value)
if err != nil {
return false, err
2025-07-12 12:28:13 +02:00
}
2025-09-06 17:45:28 +02:00
for _, allowed := range state.allowedValues {
if reflect.DeepEqual(cvalue, allowed) {
return true, nil
2025-07-12 12:28:13 +02:00
}
}
2025-09-06 17:45:28 +02:00
return false, nil
2025-07-12 12:28:13 +02:00
}
2025-09-06 17:45:28 +02:00
func (state *StateVariable) IsValidValue(value interface{}) (bool, error) {
cvalue, err := state.Cast(value)
if err != nil {
return false, err
2025-07-12 12:28:13 +02:00
}
2025-09-06 17:45:28 +02:00
inrange, err1 := state.IsValueInRange(cvalue)
allowed, err2 := state.IsValueAllowed(cvalue)
if err1 != nil || err2 != nil {
if err1 != nil {
err = err1
} else {
err = err2
2025-07-12 12:28:13 +02:00
}
2025-09-06 17:45:28 +02:00
}
return inrange && allowed, err
}
2025-07-12 12:28:13 +02:00
2025-09-06 17:45:28 +02:00
func (state *StateVariable) NewInstance() *StateVarInstance {
instance := &StateVarInstance{
model: state,
name: state.name,
modifiable: state.modifiable,
description: state.description,
step: state.step,
defaultValue: state.defaultValue,
eventConditions: maps.Clone(state.eventConditions),
allowedValues: slices.Clone(state.allowedValues),
sendEvents: state.sendEvents,
parse: state.parse,
marshal: state.marshal,
2025-07-12 12:28:13 +02:00
2025-09-06 17:45:28 +02:00
value: state.DefaultValue(),
lastChange: time.Now(),
lastEvent: time.Unix(int64(1718985600), 0).UTC(),
}
2025-07-12 12:28:13 +02:00
2025-09-06 17:45:28 +02:00
if state.HasRange() {
instance.valueRange = &ValueRange{
min: state.valueRange.min,
max: state.valueRange.max,
2025-07-12 12:28:13 +02:00
}
}
2025-09-06 17:45:28 +02:00
return instance
2025-07-12 12:28:13 +02:00
}