Files
pmomusic/internal/upnp/statevalue.go
2025-06-21 17:22:56 +02:00

385 lines
9.8 KiB
Go

package upnp
import (
"fmt"
"reflect"
"slices"
"strings"
"time"
log "github.com/sirupsen/logrus"
)
type EventType string
const (
EventEnabled EventType = "yes"
EventDisabled EventType = "no"
EventConditional EventType = "conditional"
)
type StateConditionFunc func(instance *StateValueInstance) bool
type TypeModifier int
const (
ModifierAtomic TypeModifier = iota // Valeur simple (par défaut)
ModifierList // Liste d'éléments
ModifierMap // Tableau associatif
ModifierStruct // Structure nommée
)
// StateValue represents a UPnP state variable with value constraints and eventing capabilities.
// It encapsulates:
// - Value type (StateVarType)
// - Value range constraints (min/max)
// - Allowed value list
// - Eventing configuration
// graph TD
// A[StateValue] --> B[baseType: Type sérialisé]
// A --> C[modifier: Structure des données]
// C --> D[Liste]
// C --> E[Map]
// C --> F[Structure]
// D --> G[elementType: Type des éléments]
// E --> H[keyType: Type des clés]
// E --> I[elementType: Type des valeurs]
//
// Usage:
//
// state := StateValue{
// name: "Volume",
// baseType: StateType_UI2,
// }
// state.SetRange(0, 100) // Set 0-100 range
// state.AppendAllowedValue(25, 50, 75) // Add specific allowed values
type StateValue struct {
name string
baseType StateVarType // Renommé pour plus de clarté
modifier TypeModifier
step interface{}
minDelta interface{}
modifiable bool
eventConditions []StateConditionFunc
description string
defaultValue interface{}
valueRange *ValueRange
allowedValues []interface{}
sendEvents bool
elementType StateVarType // Pour listes et valeurs de maps
keyType StateVarType // Type des clés (pour les maps)
structFields map[string]StateVarType // Champs (pour les structures)
}
// Name returns the state variable's name (e.g., "Volume", "Brightness").
func (state *StateValue) Name() string {
return state.name
}
// Type returns the UPnP data type of the state variable.
func (state *StateValue) Type() StateVarType {
return state.baseType
}
func (state *StateValue) AddEventCondition(condition StateConditionFunc) {
state.eventConditions = append(state.eventConditions, condition)
}
// ClearEventConditions réinitialise toutes les conditions
func (state *StateValue) ClearEventConditions() {
state.eventConditions = nil
}
func (state *StateValue) SetDefault(value interface{}) error {
if state.IsValidValue(value) {
cvalue, _ := state.baseType.Cast(value)
state.defaultValue = cvalue
log.Debugf("🐞 Setting default value for %v to %v", state.name, cvalue)
return nil
}
return fmt.Errorf("invalid default value for %v (%v)", state.name, value)
}
func (state *StateValue) HasDefault() bool {
return state.defaultValue != nil
}
func (state *StateValue) DefaultValue() interface{} {
if !state.HasDefault() {
return state.baseType.DefaultValue()
}
return state.DefaultValue()
}
// HasRange indicates if a value range constraint is defined.
// Returns true if min/max boundaries are set.
func (state *StateValue) HasRange() bool {
return state.valueRange != nil
}
// Maximum returns the upper bound of the value range.
// Returns nil if no range is defined.
func (state *StateValue) Maximum() interface{} {
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.
func (state *StateValue) Minimum() interface{} {
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
func (state *StateValue) SetRange(min, max interface{}) error {
if min == nil || max == nil {
return fmt.Errorf("min and max must not be nil")
}
limits, err := state.baseType.ValueRange(min, max)
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
func (state *StateValue) UpdateMinimalValue(value interface{}) error {
if state.valueRange == nil {
return fmt.Errorf("no range set for value %v", state.name)
}
cvalue, err := state.baseType.Cast(value)
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
func (state *StateValue) UpdateMaximalValue(value interface{}) error {
if state.valueRange == nil {
return fmt.Errorf("no range set for value %v", state.name)
}
cvalue, err := state.baseType.Cast(value)
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
}
// IsValueInRange checks if a value falls within the defined range.
// Always returns true if no range is set.
//
// Parameters:
//
// value: Value to check
//
// Returns:
//
// bool: True if within range or no range defined
func (state *StateValue) IsValueInRange(value interface{}) bool {
return state.baseType.InRange(value, state.valueRange)
}
// IsSendingEvents indicates if state changes trigger UPnP events.
func (state *StateValue) IsSendingEvents() bool {
return state.sendEvents
}
// SetSendingEvents enables event notifications for state changes.
func (state *StateValue) SetSendingEvents() {
state.sendEvents = true
log.Debugf("🐞 Enabling event sending for %s", state.name)
}
// UnsetSendingEvents disables event notifications for state changes.
func (state *StateValue) UnsetSendingEvents() {
state.sendEvents = false
log.Debugf("🐞 Disabling event sending for %s", state.name)
}
// HasAllowedValues indicates if an allowed value list is defined.
func (state *StateValue) HasAllowedValues() bool {
return len(state.allowedValues) > 0
}
// AllowedValues returns the list of permitted values.
// Returns an empty slice if no values are defined.
func (state *StateValue) AllowedValues() []interface{} {
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")
func (state *StateValue) AppendAllowedValue(value ...interface{}) error {
state.allowedValues = slices.Grow(state.allowedValues, len(value))
for _, v := range value {
cv, err := state.baseType.Cast(v)
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
}
// IsValueAllowed checks if a value exists in the allowed value list.
// Always returns true if no allowed values are defined.
//
// Parameters:
//
// value: Value to check
//
// Returns:
//
// bool: True if value is permitted or no list defined
func (state *StateValue) IsValueAllowed(value interface{}) bool {
if !state.HasAllowedValues() {
return true // No list = any value valid
}
cvalue, err := state.baseType.Cast(value)
if err != nil {
return false
}
for _, allowed := range state.allowedValues {
if reflect.DeepEqual(cvalue, allowed) {
return true
}
}
return false
}
// IsValidValue performs full validation against all constraints.
// Checks (in order):
// 1. Value can be cast to the type
// 2. Value is within range (if defined)
// 3. Value is in allowed list (if defined)
//
// Returns:
//
// bool: True if value passes all applicable constraints
func (state *StateValue) IsValidValue(value interface{}) bool {
cvalue, err := state.baseType.Cast(value)
if err != nil {
return false
}
return state.IsValueInRange(cvalue) && state.IsValueAllowed(cvalue)
}
func (state *StateValue) HasDescription() bool {
return len(state.description) > 0
}
func (state *StateValue) Description() string {
return state.description
}
func (state *StateValue) SetDescription(desc string) {
state.description = strings.TrimSpace(desc)
}
func (state *StateValue) IsConstant() bool {
return !state.modifiable
}
func (state *StateValue) SetConstant() {
state.modifiable = false
}
func (state *StateValue) SetModifiable() {
state.modifiable = true
}
func (state *StateValue) SetStep(step interface{}) error {
// Validation que le step correspond au type de la variable
if _, err := state.baseType.Cast(step); err != nil {
return fmt.Errorf("invalid step type: %v", err)
}
state.step = step
return nil
}
func (state *StateValue) UnsetStep() {
state.step = nil
}
func (state *StateValue) HasStep() bool {
return state.step != nil
}
func (state *StateValue) Step() interface{} {
return state.step
}
func (state *StateValue) NewInstance() *StateValueInstance {
return &StateValueInstance{
model: state,
value: state.DefaultValue(),
lastChange: time.Now(),
lastEvent: time.Unix(int64(1718985600), 0).UTC(),
}
}