rename package
This commit is contained in:
75
upnp/statevariables/statevalueinstance.go
Normal file
75
upnp/statevariables/statevalueinstance.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
type StateVarInstance struct {
|
||||
model *StateVariable
|
||||
value interface{}
|
||||
previousValue interface{}
|
||||
lastChange time.Time
|
||||
lastEvent time.Time
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (instance *StateVarInstance) Cast(val interface{}) (interface{}, error) {
|
||||
return instance.model.Cast(val)
|
||||
}
|
||||
|
||||
func (instance *StateVarInstance) Model() *StateVariable {
|
||||
return instance.model
|
||||
}
|
||||
|
||||
func (instance *StateVarInstance) Value() interface{} {
|
||||
instance.mu.RLock()
|
||||
defer instance.mu.RUnlock()
|
||||
return instance.value
|
||||
}
|
||||
|
||||
func (instance *StateVarInstance) SetValue(val interface{}) error {
|
||||
cval, err := instance.Cast(val)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instance.mu.Lock()
|
||||
defer instance.mu.Unlock()
|
||||
instance.previousValue = instance.value
|
||||
instance.value = cval
|
||||
return nil
|
||||
}
|
||||
|
||||
func (instance *StateVarInstance) Incr() {
|
||||
instance.mu.Lock()
|
||||
defer instance.mu.Unlock()
|
||||
|
||||
}
|
||||
|
||||
// ShouldTriggerEvent vérifie toutes les conditions
|
||||
func (instance *StateVarInstance) ShouldTriggerEvent() bool {
|
||||
for _, condition := range instance.model.eventConditions {
|
||||
if !condition(instance) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (sv *StateVarInstance) GenerateEvent() *etree.Element {
|
||||
|
||||
// Construire le XML d'événement
|
||||
propSet := etree.NewElement("e:propertyset")
|
||||
propSet.CreateAttr("xmlns:e", "urn:schemas-upnp-org:event-1-0")
|
||||
|
||||
prop := propSet.CreateElement("e:property")
|
||||
elem := prop.CreateElement(sv.model.Name())
|
||||
elem.SetText(sv.model.valueToString(sv.Value()))
|
||||
|
||||
return propSet
|
||||
|
||||
}
|
||||
553
upnp/statevariables/statevariable.go
Normal file
553
upnp/statevariables/statevariable.go
Normal file
@@ -0,0 +1,553 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type EventType string
|
||||
|
||||
type StateConditionFunc func(instance *StateVarInstance) bool
|
||||
type StringValueParser func(value string) (interface{}, error)
|
||||
type ValueSerializer func(value interface{}) (string, error)
|
||||
|
||||
type StateVariable struct {
|
||||
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")
|
||||
modifiable bool
|
||||
eventConditions map[string]StateConditionFunc
|
||||
description string
|
||||
defaultValue interface{}
|
||||
valueRange *ValueRange
|
||||
allowedValues []interface{}
|
||||
sendEvents bool
|
||||
parse StringValueParser
|
||||
marshal ValueSerializer
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (sv StateVariable) BitSize() int {
|
||||
return sv.valueType.BitSize()
|
||||
}
|
||||
|
||||
// Name returns the state variable's name (e.g., "Volume", "Brightness").
|
||||
func (sv StateVariable) Name() string {
|
||||
return sv.name
|
||||
}
|
||||
|
||||
// Type returns the UPnP data type of the state variable.
|
||||
func (state *StateVariable) Type() StateVarType {
|
||||
return state.valueType
|
||||
}
|
||||
|
||||
func (state *StateVariable) AddEventCondition(name string, condition StateConditionFunc) {
|
||||
state.eventConditions[name] = condition
|
||||
}
|
||||
|
||||
func (state *StateVariable) DeleteEventConditions(name string) error {
|
||||
if _, ok := state.eventConditions[name]; !ok {
|
||||
return fmt.Errorf("%s: no such event condition (%s)", state.name, name)
|
||||
}
|
||||
delete(state.eventConditions, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearEventConditions réinitialise toutes les conditions
|
||||
func (state *StateVariable) ClearEventConditions() {
|
||||
state.eventConditions = make(map[string]StateConditionFunc)
|
||||
}
|
||||
|
||||
func (sv *StateVariable) SetMinDelta(minDelta interface{}) error {
|
||||
if minDelta == nil {
|
||||
return fmt.Errorf("%s: nil is an invalid minimum delta value", sv.name)
|
||||
}
|
||||
|
||||
minDelta, err := sv.Cast(minDelta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: invalid minimum delta value %v : %v", sv.name, minDelta, err)
|
||||
}
|
||||
|
||||
// mdf := func(instance *StateValueInstance) bool {
|
||||
// o := instance.previousValue
|
||||
// n := instance.value
|
||||
|
||||
// r,err := instance.model.valueType.ValueRange(o, n)
|
||||
|
||||
// if err != nil {
|
||||
// return false
|
||||
// }
|
||||
|
||||
// return instance.model.valueType.InRange()
|
||||
// }
|
||||
|
||||
// sv.eventConditions["MinDelta"] = mdf
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *StateVariable) SetDefault(value interface{}) error {
|
||||
var err error
|
||||
var valid bool
|
||||
|
||||
if valid, err = state.IsValidValue(value); valid && err == nil {
|
||||
cvalue, _ := state.valueType.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) : %v", state.name, value, err)
|
||||
}
|
||||
|
||||
func (state *StateVariable) HasDefault() bool {
|
||||
return state.defaultValue != nil
|
||||
}
|
||||
|
||||
func (state *StateVariable) DefaultValue() interface{} {
|
||||
if !state.HasDefault() {
|
||||
return state.valueType.DefaultValue()
|
||||
}
|
||||
|
||||
return state.DefaultValue()
|
||||
}
|
||||
|
||||
// HasRange indicates if a value range constraint is defined.
|
||||
// Returns true if min/max boundaries are set.
|
||||
func (state *StateVariable) HasRange() bool {
|
||||
return state.valueRange != nil
|
||||
}
|
||||
|
||||
// Maximum returns the upper bound of the value range.
|
||||
// Returns nil if no range is defined.
|
||||
func (state *StateVariable) 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 *StateVariable) 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 *StateVariable) SetRange(min, max interface{}) error {
|
||||
if min == nil || max == nil {
|
||||
return fmt.Errorf("min and max must not be nil")
|
||||
}
|
||||
limits, err := state.valueType.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 *StateVariable) UpdateMinimalValue(value interface{}) error {
|
||||
if state.valueRange == nil {
|
||||
return fmt.Errorf("no range set for value %v", state.name)
|
||||
}
|
||||
cvalue, err := state.valueType.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 *StateVariable) UpdateMaximalValue(value interface{}) error {
|
||||
if state.valueRange == nil {
|
||||
return fmt.Errorf("no range set for value %v", state.name)
|
||||
}
|
||||
cvalue, err := state.valueType.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 *StateVariable) IsValueInRange(value interface{}) (bool, error) {
|
||||
return state.valueType.InRange(value, state.valueRange)
|
||||
}
|
||||
|
||||
// IsSendingEvents indicates if state changes trigger UPnP events.
|
||||
func (state *StateVariable) IsSendingEvents() bool {
|
||||
return state.sendEvents
|
||||
}
|
||||
|
||||
// SetSendingEvents enables event notifications for state changes.
|
||||
func (state *StateVariable) SetSendingEvents() {
|
||||
state.sendEvents = true
|
||||
log.Debugf("🐞 Enabling event sending for %s", state.name)
|
||||
}
|
||||
|
||||
// UnsetSendingEvents disables event notifications for state changes.
|
||||
func (state *StateVariable) UnsetSendingEvents() {
|
||||
state.sendEvents = false
|
||||
log.Debugf("🐞 Disabling event sending for %s", state.name)
|
||||
}
|
||||
|
||||
// HasAllowedValues indicates if an allowed value list is defined.
|
||||
func (state *StateVariable) 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 *StateVariable) 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 *StateVariable) AppendAllowedValue(value ...interface{}) error {
|
||||
state.allowedValues = slices.Grow(state.allowedValues, len(value))
|
||||
for _, v := range value {
|
||||
cv, err := state.valueType.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 *StateVariable) IsValueAllowed(value interface{}) (bool, error) {
|
||||
if !state.HasAllowedValues() {
|
||||
return true, nil // No list = any value valid
|
||||
}
|
||||
|
||||
cvalue, err := state.valueType.Cast(value)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, allowed := range state.allowedValues {
|
||||
if reflect.DeepEqual(cvalue, allowed) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 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 *StateVariable) IsValidValue(value interface{}) (bool, error) {
|
||||
cvalue, err := state.valueType.Cast(value)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
inrange, err1 := state.IsValueInRange(cvalue)
|
||||
allowed, err2 := state.IsValueAllowed(cvalue)
|
||||
if err1 != nil || err2 != nil {
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
} else {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
return inrange && allowed, err
|
||||
}
|
||||
|
||||
func (state *StateVariable) HasDescription() bool {
|
||||
return len(state.description) > 0
|
||||
}
|
||||
|
||||
func (state *StateVariable) Description() string {
|
||||
return state.description
|
||||
}
|
||||
|
||||
func (state *StateVariable) SetDescription(desc string) {
|
||||
state.description = strings.TrimSpace(desc)
|
||||
}
|
||||
|
||||
func (state *StateVariable) IsConstant() bool {
|
||||
return !state.modifiable
|
||||
}
|
||||
|
||||
func (state *StateVariable) SetConstant() {
|
||||
state.modifiable = false
|
||||
}
|
||||
|
||||
func (state *StateVariable) SetModifiable() {
|
||||
state.modifiable = true
|
||||
}
|
||||
|
||||
func (state *StateVariable) SetStep(step interface{}) error {
|
||||
// Validation que le step correspond au type de la variable
|
||||
if _, err := state.valueType.Cast(step); err != nil {
|
||||
return fmt.Errorf("invalid step type: %v", err)
|
||||
}
|
||||
state.step = step
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *StateVariable) UnsetStep() {
|
||||
state.step = nil
|
||||
}
|
||||
|
||||
func (state *StateVariable) HasStep() bool {
|
||||
return state.step != nil
|
||||
}
|
||||
|
||||
func (state *StateVariable) Step() interface{} {
|
||||
return state.step
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (state *StateVariable) NewInstance() *StateVarInstance {
|
||||
return &StateVarInstance{
|
||||
model: state,
|
||||
value: state.DefaultValue(),
|
||||
lastChange: time.Now(),
|
||||
lastEvent: time.Unix(int64(1718985600), 0).UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
// ToXMLElement generates the complete XML representation of the state variable
|
||||
// Returns an etree.Element that can be serialized to XML
|
||||
func (sv *StateVariable) ToXMLElement() *etree.Element {
|
||||
// Create root <stateVariable> element
|
||||
elem := etree.NewElement("stateVariable")
|
||||
elem.CreateAttr("name", sv.name)
|
||||
|
||||
// Add sendEvents attribute (UPnP eventing capability)
|
||||
if sv.sendEvents {
|
||||
elem.CreateAttr("sendEvents", "yes") // Enable event notifications
|
||||
} else {
|
||||
elem.CreateAttr("sendEvents", "no") // Disable event notifications
|
||||
}
|
||||
|
||||
// Add data type element
|
||||
dataType := elem.CreateElement("dataType")
|
||||
dataType.SetText(sv.valueType.String()) // Set UPnP type name (e.g., "ui1", "boolean")
|
||||
|
||||
// Add default value if specified
|
||||
if sv.defaultValue != nil {
|
||||
defaultValue := elem.CreateElement("defaultValue")
|
||||
// Convert value to UPnP-compatible string representation
|
||||
defaultValue.SetText(sv.valueToString(sv.defaultValue))
|
||||
}
|
||||
|
||||
// Add value range constraints if defined
|
||||
if sv.valueRange != nil {
|
||||
rangeElem := elem.CreateElement("allowedValueRange")
|
||||
|
||||
// Minimum boundary value
|
||||
min := rangeElem.CreateElement("minimum")
|
||||
min.SetText(sv.valueToString(sv.valueRange.min))
|
||||
|
||||
// Maximum boundary value
|
||||
max := rangeElem.CreateElement("maximum")
|
||||
max.SetText(sv.valueToString(sv.valueRange.max))
|
||||
|
||||
// Add step value if defined (for incremental controls)
|
||||
if sv.step != nil {
|
||||
step := rangeElem.CreateElement("step")
|
||||
step.SetText(sv.valueToString(sv.step))
|
||||
}
|
||||
}
|
||||
|
||||
// Add allowed value list if defined
|
||||
if len(sv.allowedValues) > 0 {
|
||||
allowedList := elem.CreateElement("allowedValueList")
|
||||
for _, value := range sv.allowedValues {
|
||||
// Create individual <allowedValue> elements
|
||||
allowed := allowedList.CreateElement("allowedValue")
|
||||
allowed.SetText(sv.valueToString(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Add description if available
|
||||
if sv.description != "" {
|
||||
desc := elem.CreateElement("description")
|
||||
desc.SetText(sv.description) // Human-readable description
|
||||
}
|
||||
|
||||
return elem
|
||||
}
|
||||
|
||||
// valueToString converts a value to its UPnP-compatible string representation
|
||||
// Handles type-specific formatting for proper XML serialization
|
||||
func (sv *StateVariable) valueToString(val interface{}) string {
|
||||
if val == nil {
|
||||
return "" // Safeguard against nil values
|
||||
}
|
||||
|
||||
// Type-specific formatting for UPnP compliance
|
||||
switch sv.valueType {
|
||||
case StateType_Boolean:
|
||||
// Boolean: "1" for true, "0" for false (UPnP standard)
|
||||
if b, ok := val.(bool); ok && b {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
|
||||
case StateType_Date:
|
||||
// Date: YYYY-MM-DD format
|
||||
if t, ok := val.(time.Time); ok {
|
||||
return t.Format("2006-01-02")
|
||||
}
|
||||
|
||||
case StateType_DateTime, StateType_DateTimeTZ:
|
||||
// DateTime: ISO 8601 format with timezone
|
||||
if t, ok := val.(time.Time); ok {
|
||||
return t.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
case StateType_Time, StateType_TimeTZ:
|
||||
// Time: HH:MM:SS format
|
||||
if t, ok := val.(time.Time); ok {
|
||||
return t.Format("15:04:05")
|
||||
}
|
||||
|
||||
case StateType_BinBase64:
|
||||
// Binary: Base64 encoding
|
||||
if b, ok := val.([]byte); ok {
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
case StateType_BinHex:
|
||||
// Binary: Hex encoding
|
||||
if b, ok := val.([]byte); ok {
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
case StateType_URI:
|
||||
// URI: Full URL string
|
||||
if u, ok := val.(*url.URL); ok {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
case StateType_UUID:
|
||||
// UUID: Canonical string representation
|
||||
if u, ok := val.(uuid.UUID); ok {
|
||||
return u.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Default conversion for unsupported types or fallback
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
17
upnp/statevariables/statevariable_TransferStatus.go
Normal file
17
upnp/statevariables/statevariable_TransferStatus.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package statevariables
|
||||
|
||||
var TransferStatus = func() *StateVariable {
|
||||
|
||||
ts := StateType_String.NewStateValue("TransferStatus")
|
||||
|
||||
ts.SetAllowedValues(
|
||||
"COMPLETED",
|
||||
"ERROR",
|
||||
"IN_PROGRESS",
|
||||
"NONE",
|
||||
)
|
||||
|
||||
ts.SetSendingEvents()
|
||||
|
||||
return ts
|
||||
}()
|
||||
13
upnp/statevariables/statevariable_Volume.go
Normal file
13
upnp/statevariables/statevariable_Volume.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package statevariables
|
||||
|
||||
var Volume = func() *StateVariable {
|
||||
|
||||
vol := StateType_UI2.NewStateValue("Volume")
|
||||
|
||||
vol.SetRange(0, 100)
|
||||
vol.SetStep(1)
|
||||
|
||||
vol.SetSendingEvents()
|
||||
|
||||
return vol
|
||||
}()
|
||||
39
upnp/statevariables/statevariable_arithm.go
Normal file
39
upnp/statevariables/statevariable_arithm.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package statevariables
|
||||
|
||||
// Add performs addition operation on the given parameters 'a' and 'b'. It calls the
|
||||
// corresponding method from valueType which is assumed to be an interface that
|
||||
// provides methods for arithmetic operations. This function returns the result of
|
||||
// the operation or an error if any occurs during computation. Side effects might
|
||||
// include modifying the state of the system, but this depends on the actual implementation
|
||||
// of the valueType's Add method. Errors might occur due to invalid input parameters or
|
||||
// failures in the addition operation itself. The function does not handle edge cases
|
||||
// and therefore should be used with caution. Here is an example usage:
|
||||
//
|
||||
// result, err := sv.Add(5, 3)
|
||||
// if err != nil {
|
||||
// // Handle error
|
||||
// } else {
|
||||
// // Use result
|
||||
// }
|
||||
func (sv StateVariable) Add(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Add(a, b)
|
||||
}
|
||||
|
||||
// Sub performs subtraction operation on the given parameters 'a' and 'b'. It follows
|
||||
// similar semantics as in the Add method, but for subtraction instead of addition.
|
||||
func (sv StateVariable) Sub(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Sub(a, b)
|
||||
}
|
||||
|
||||
// Mul performs multiplication operation on the given parameters 'a' and 'b'. It follows
|
||||
// similar semantics as in the Add method, but for multiplication instead of addition.
|
||||
func (sv StateVariable) Mul(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Mul(a, b)
|
||||
}
|
||||
|
||||
// Div performs division operation on the given parameters 'a' and 'b'. It follows similar
|
||||
// semantics as in the Add method, but for division instead of addition. Please note that
|
||||
// division by zero is undefined and will result in an error being returned from valueType.Div call.
|
||||
func (sv StateVariable) Div(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Div(a, b)
|
||||
}
|
||||
29
upnp/statevariables/statevariable_cast.go
Normal file
29
upnp/statevariables/statevariable_cast.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package statevariables
|
||||
|
||||
// Cast transforms any given value into an interface suitable for use in a StateValue object, using
|
||||
// the underlying type's specific casting rules. It will return an error if the transformation fails or
|
||||
// if the provided interface is not supported by the ValueType of the StateValue.
|
||||
// This function does NOT mutate the original value it receives as input.
|
||||
//
|
||||
// The parameter 'val' is an interface that needs to be cast into a form compatible with the internal
|
||||
// state representation used by the StateValue object.
|
||||
//
|
||||
// It returns an interface and an error: if the casting operation was successful, the first return value will
|
||||
// be the casted version of 'val', and the second one (an error) will be nil. If the casting fails, it will
|
||||
// return a nil for the first value and an appropriate error for the second one.
|
||||
//
|
||||
// This function should not modify its input value. It is idempotent and always returns consistent results given
|
||||
// the same inputs. However, if the provided interface 'val' does not match with the ValueType of StateValue, it
|
||||
// will return an error.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// state := upnp.NewStateValue(upnp.NewTime())
|
||||
// castedVal, err := state.Cast("2022-12-31")
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
func (sv *StateVariable) Cast(val interface{}) (interface{}, error) {
|
||||
return sv.valueType.Cast(val)
|
||||
}
|
||||
13
upnp/statevariables/statevariable_cmp.go
Normal file
13
upnp/statevariables/statevariable_cmp.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package statevariables
|
||||
|
||||
func (sv StateVariable) Cmp(a, b interface{}) (int, error) {
|
||||
return sv.valueType.Cmp(a, b)
|
||||
}
|
||||
|
||||
func (sv StateVariable) Equal(a, b interface{}) (int, error) {
|
||||
return sv.valueType.Cmp(a, b)
|
||||
}
|
||||
|
||||
func (sv StateVariable) InRange(val interface{}, interval *ValueRange) (bool, error) {
|
||||
return sv.valueType.InRange(val, interval)
|
||||
}
|
||||
158
upnp/statevariables/statevariable_typechecking.go
Normal file
158
upnp/statevariables/statevariable_typechecking.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package statevariables
|
||||
|
||||
// IsNumeric checks whether a given StateValue model represents a numeric value or
|
||||
// not. Numeric types are defined as those that can be used to store number-like
|
||||
// values. The following types are considered numeric: UI1, UI2, UI4, I1, I2,
|
||||
// I4, Int, R4, R8, Number and Fixed14_4.
|
||||
//
|
||||
// t: StateVarType to check if it's a numeric type or not.
|
||||
//
|
||||
// Returns true if the given StateValue represents a numeric value; false
|
||||
// otherwise.
|
||||
func (sv StateVariable) IsNumeric() bool {
|
||||
return sv.valueType.IsNumeric()
|
||||
}
|
||||
|
||||
// IsInteger checks if the state variable type is integer or not.
|
||||
//
|
||||
// It returns a boolean value indicating whether the provided StateValue
|
||||
// is an integer type or not.
|
||||
//
|
||||
// Returns: bool: If the state variable type is any of the defined integer types
|
||||
// (UI1, UI2, UI4, I1, I2, I4, Int), it returns true. Otherwise, it returns false.
|
||||
func (sv StateVariable) IsInteger() bool {
|
||||
return sv.valueType.IsInteger()
|
||||
}
|
||||
|
||||
// IsSignedInt checks if the state value type is a signed int.
|
||||
//
|
||||
// The return value will be a boolean indicating whether the state value type is
|
||||
// a signed integer (true) or not (false).
|
||||
func (sv StateVariable) IsSignedInt() bool {
|
||||
return sv.valueType.IsSignedInt()
|
||||
}
|
||||
|
||||
// IsUnsignedInt checks if the state value type represents an unsigned integer.
|
||||
// The method returns a boolean indicating whether the state value type is an
|
||||
// unsigned integer.
|
||||
func (sv StateVariable) IsUnsignedInt() bool {
|
||||
return sv.valueType.IsUnsignedInt()
|
||||
}
|
||||
|
||||
// IsFloat returns true if the StateValue's value type represents a floating point
|
||||
// number; false otherwise.
|
||||
func (sv StateVariable) IsFloat() bool {
|
||||
return sv.valueType.IsFloat()
|
||||
}
|
||||
|
||||
// IsBool checks if the state value is of boolean type.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// None
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (bool) : Indicates whether the StateValue is of boolean type or not.
|
||||
func (sv StateVariable) IsBool() bool {
|
||||
return sv.valueType.IsBool()
|
||||
}
|
||||
|
||||
// IsString checks if the underlying value type of a StateValue object
|
||||
// represents a string.
|
||||
//
|
||||
// Parameters:
|
||||
// - None.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// bool: Indicates whether the underlying value type is a string or not.
|
||||
//
|
||||
// Side Effects:
|
||||
// - This function does not modify any state. It only reads and returns a boolean value.
|
||||
//
|
||||
// Errors:
|
||||
// - This function does not return an error, so you don't have to check for errors.
|
||||
//
|
||||
// Edge Cases:
|
||||
// - If the underlying type of the StateValue is not TypeString,
|
||||
// this function will return false as expected.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// state := upnp.StateValue{valueType: upnp.TypeInt}
|
||||
// fmt.Println(state.IsString()) // Outputs: false
|
||||
func (sv StateVariable) IsString() bool {
|
||||
return sv.valueType.IsString()
|
||||
}
|
||||
|
||||
// IsTime reports whether this state value represents a time instance.
|
||||
//
|
||||
// The function returns true if and only if the underlying type of the
|
||||
// StateValue is TypeTime, else false. This method does not check for other
|
||||
// types that are convertible to Time as it's assumed that these would be
|
||||
// handled by the ConvertToType method beforehand.
|
||||
//
|
||||
// No side effects: this function is pure and doesn't change any state.
|
||||
//
|
||||
// Errors: This function does not return an error, so you don't have to check
|
||||
// for errors. However, note that TypeTime conversion might fail if the
|
||||
// StateValue isn't convertible to Time; use the ConvertToType method in such
|
||||
// cases.
|
||||
//
|
||||
// Edge cases: If the underlying type of the StateValue is not TypeTime or is a
|
||||
// non-convertible time, this function will return false as expected.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// state := upnp.StateValue{valueType: upnp.TypeInt}
|
||||
// fmt.Println(state.IsTime())
|
||||
//
|
||||
// Outputs: false
|
||||
func (sv StateVariable) IsTime() bool {
|
||||
return sv.valueType.IsTime()
|
||||
}
|
||||
|
||||
// IsUUID checks if the state value type is a UUID (Universally Unique
|
||||
// Identifier).
|
||||
//
|
||||
// It returns true if the underlying type of the StateValue object represents a
|
||||
// UUID; false otherwise.
|
||||
//
|
||||
// Returns: bool: Indicates whether the StateValue is of UUID type or not.
|
||||
func (sv StateVariable) IsUUID() bool {
|
||||
return sv.valueType.IsUUID()
|
||||
}
|
||||
|
||||
// IsURI checks if the state value type is a URI.
|
||||
// The method returns a boolean indicating whether the state value type represents
|
||||
// a Uniform Resource Identifier (URI) or not.
|
||||
//
|
||||
// Returns: bool: Indicates whether the StateValue is of URI type or not.
|
||||
func (sv StateVariable) IsURI() bool {
|
||||
return sv.valueType.IsURI()
|
||||
}
|
||||
|
||||
// IsBinary checks if the state value type is binary or not.
|
||||
//
|
||||
// The function returns a boolean indicating whether the provided StateValue's
|
||||
// value type is of binary format (bin.base64 or bin.hex).
|
||||
//
|
||||
// Returns: bool: If the underlying value type of the StateValue object is
|
||||
// either TypeBinBase64 or TypeBinHex, this method will return true; otherwise,
|
||||
// it returns false.
|
||||
func (sv StateVariable) IsBinary() bool {
|
||||
return sv.valueType.IsBinary()
|
||||
}
|
||||
|
||||
// IsComparable function checks if a state variable is comparable or not.
|
||||
//
|
||||
// It returns false for binary types (bin.base64 and bin.hex)
|
||||
// as they are non-comparable. For all other types, it returns true indicating
|
||||
// that these types can be compared.
|
||||
// //
|
||||
// Returns: bool: A boolean value indicating whether the given StateValue is
|
||||
// comparable or not. True means it's comparable, False means it isn't.
|
||||
func (sv StateVariable) IsComparable() bool {
|
||||
return sv.valueType.IsComparable()
|
||||
}
|
||||
194
upnp/statevariables/statevartype.go
Normal file
194
upnp/statevariables/statevartype.go
Normal file
@@ -0,0 +1,194 @@
|
||||
// package stateVariables provides comprehensive handling of UPnP state variable types.
|
||||
// It includes type identification, value casting, comparison, and range validation
|
||||
// for all standard UPnP state variable types.
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StateVarType represents UPnP state variable types with corresponding Go type mappings.
|
||||
type StateVarType int
|
||||
|
||||
// Constants defining all supported UPnP state variable types
|
||||
const (
|
||||
StateType_Unknown StateVarType = iota
|
||||
StateType_UI1 // Unsigned 8-bit integer (Go: uint8)
|
||||
StateType_UI2 // Unsigned 16-bit integer (Go: uint16)
|
||||
StateType_UI4 // Unsigned 32-bit integer (Go: uint32)
|
||||
StateType_I1 // Signed 8-bit integer (Go: int8)
|
||||
StateType_I2 // Signed 16-bit integer (Go: int16)
|
||||
StateType_I4 // Signed 32-bit integer (Go: int32)
|
||||
StateType_Int // Synonymous with i4 (Go: int32)
|
||||
StateType_R4 // 32-bit floating point (Go: float32)
|
||||
StateType_R8 // 64-bit floating point (Go: float64)
|
||||
StateType_Number // Synonymous with r8 (Go: float64)
|
||||
StateType_Fixed14_4 // Fixed-point decimal (Go: float64)
|
||||
StateType_Char // Single Unicode character (Go: rune)
|
||||
StateType_String // Character string (Go: string)
|
||||
StateType_Boolean // Boolean value (Go: bool)
|
||||
StateType_BinBase64 // Base64-encoded binary (Go: []byte)
|
||||
StateType_BinHex // Hex-encoded binary (Go: []byte)
|
||||
StateType_Date // Date (YYYY-MM-DD) (Go: time.Time)
|
||||
StateType_DateTime // DateTime without timezone (Go: time.Time)
|
||||
StateType_DateTimeTZ // DateTime with timezone (Go: time.Time)
|
||||
StateType_Time // Time without timezone (Go: time.Time)
|
||||
StateType_TimeTZ // Time with timezone (Go: time.Time)
|
||||
StateType_UUID // Universally unique identifier (Go: uuid.UUID)
|
||||
StateType_URI // Uniform Resource Identifier (Go: *url.URL)
|
||||
)
|
||||
|
||||
// typeNames maps UPnP XML type names to StateVarType constants
|
||||
var typeNames = map[string]StateVarType{
|
||||
"ui1": StateType_UI1,
|
||||
"ui2": StateType_UI2,
|
||||
"ui4": StateType_UI4,
|
||||
"i1": StateType_I1,
|
||||
"i2": StateType_I2,
|
||||
"i4": StateType_I4,
|
||||
"int": StateType_Int,
|
||||
"r4": StateType_R4,
|
||||
"r8": StateType_R8,
|
||||
"number": StateType_Number,
|
||||
"fixed.14.4": StateType_Fixed14_4,
|
||||
"char": StateType_Char,
|
||||
"string": StateType_String,
|
||||
"boolean": StateType_Boolean,
|
||||
"bin.base64": StateType_BinBase64,
|
||||
"bin.hex": StateType_BinHex,
|
||||
"date": StateType_Date,
|
||||
"dateTime": StateType_DateTime,
|
||||
"dateTime.tz": StateType_DateTimeTZ,
|
||||
"time": StateType_Time,
|
||||
"time.tz": StateType_TimeTZ,
|
||||
"uuid": StateType_UUID,
|
||||
"uri": StateType_URI,
|
||||
}
|
||||
|
||||
// typeStrings provides string representations for StateVarType constants
|
||||
var typeStrings = [...]string{
|
||||
"unknown",
|
||||
"ui1",
|
||||
"ui2",
|
||||
"ui4",
|
||||
"i1",
|
||||
"i2",
|
||||
"i4",
|
||||
"int",
|
||||
"r4",
|
||||
"r8",
|
||||
"number",
|
||||
"fixed.14.4",
|
||||
"char",
|
||||
"string",
|
||||
"boolean",
|
||||
"bin.base64",
|
||||
"bin.hex",
|
||||
"date",
|
||||
"dateTime",
|
||||
"dateTime.tz",
|
||||
"time",
|
||||
"time.tz",
|
||||
"uuid",
|
||||
"uri",
|
||||
}
|
||||
|
||||
// StateVarTypeFactory takes a string and attempts to build from it a valid
|
||||
// StateVarType. The input string is cleaned before processing -
|
||||
// leading/trailing spaces are trimmed, the case is lowered for comparison with
|
||||
// known types, and if no match is found 'StateType_Unknown' is returned.
|
||||
func StateVarTypeFactory(s string) StateVarType {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
if val, ok := typeNames[s]; ok {
|
||||
return val
|
||||
}
|
||||
return StateType_Unknown
|
||||
}
|
||||
|
||||
// String returns a string representation of the StateVarType. It defaults to
|
||||
// "unknown" if the type is not recognized.
|
||||
func (t StateVarType) String() string {
|
||||
if int(t) >= 0 && int(t) < len(typeStrings) {
|
||||
return typeStrings[t]
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// BitSize returns the bit size of the StateVarType value, or -1 if not numeric.
|
||||
// The possible return values are 8 for StateTypes I1, UI1, and 64 for others.
|
||||
func (t StateVarType) BitSize() int {
|
||||
// If t isn't numeric, return -1
|
||||
if !t.IsNumeric() {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Check the value of t and return the appropriate bit size
|
||||
switch t {
|
||||
case StateType_I1, StateType_UI1:
|
||||
return 8
|
||||
case StateType_I2, StateType_UI2:
|
||||
return 16
|
||||
case StateType_I4, StateType_UI4, StateType_Int, StateType_R4:
|
||||
return 32
|
||||
case StateType_R8, StateType_Number, StateType_Fixed14_4:
|
||||
return 64
|
||||
default:
|
||||
return 64
|
||||
}
|
||||
}
|
||||
|
||||
// NewStateValue creates and returns a new StateValue struct instance with the given name
|
||||
// and the receiver's state variable type. The created StateValue is initialized with an
|
||||
// empty map for event conditions. If name is an empty string, it will cause panic in later
|
||||
// usage. Name is typically used to identify or represent a specific value or condition
|
||||
// associated with the variable type 't'.
|
||||
func (t StateVarType) NewStateValue(name string) *StateVariable {
|
||||
return &StateVariable{
|
||||
name: name,
|
||||
valueType: t,
|
||||
eventConditions: make(map[string]StateConditionFunc),
|
||||
}
|
||||
}
|
||||
|
||||
func (t StateVarType) DefaultValue() interface{} {
|
||||
switch t {
|
||||
case StateType_Unknown:
|
||||
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4:
|
||||
return uint64(0)
|
||||
|
||||
case StateType_I1, StateType_I2, StateType_I4, StateType_Int:
|
||||
return int64(0)
|
||||
|
||||
case StateType_R4, StateType_R8, StateType_Number, StateType_Fixed14_4:
|
||||
return float64(0)
|
||||
|
||||
case StateType_Char, StateType_String:
|
||||
return ""
|
||||
case StateType_Boolean:
|
||||
return false
|
||||
|
||||
case StateType_BinBase64:
|
||||
return ""
|
||||
case StateType_BinHex:
|
||||
return ""
|
||||
case StateType_Date:
|
||||
return time.Unix(int64(1718985600), 0).UTC()
|
||||
case StateType_DateTime:
|
||||
return time.Unix(int64(1718985600), 0).UTC()
|
||||
case StateType_DateTimeTZ:
|
||||
return time.Unix(int64(1718985600), 0).UTC()
|
||||
case StateType_Time:
|
||||
return time.Unix(int64(1718985600), 0).UTC()
|
||||
case StateType_TimeTZ:
|
||||
return time.Unix(int64(1718985600), 0).UTC()
|
||||
case StateType_UUID:
|
||||
return ""
|
||||
case StateType_URI:
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
73
upnp/statevariables/statevartype_arithm.go
Normal file
73
upnp/statevariables/statevartype_arithm.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package statevariables
|
||||
|
||||
// Add performs addition operation on two interfaces if both are of numeric
|
||||
// type, otherwise it returns an error. If the types are not numeric, it checks
|
||||
// and converts them into float64 before performing the addition. The function
|
||||
// then casts the result back to its original type using Cast method from
|
||||
// StateVarType t and returns this value or any encountered error.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// a (interface{}): First operand for addition operation. Can be of any type.
|
||||
// b (interface{}): Second operand for addition operation. Can be of any type.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// interface{}: Result of the addition, casted back to its original type using StateVarType t if no error encountered.
|
||||
// error: Encountered error in case any conversion or casting fails. This includes non-numeric types for this operation.
|
||||
func (t StateVarType) Add(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af + bf)
|
||||
}
|
||||
|
||||
// Sub subtracts 'b' from 'a'. It converts both values to numeric types
|
||||
// and then performs a subtraction operation, casting the result back to its
|
||||
// original type. If either conversion fails or an unsupported type is used,
|
||||
// it returns an error.
|
||||
func (t StateVarType) Sub(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af - bf)
|
||||
}
|
||||
|
||||
// Mul takes in two interface types 'a' and 'b', multiplies them together
|
||||
// and returns the result along with any error encountered during this
|
||||
// process. If either of the inputs is not compatible with numeric values, an
|
||||
// error will be returned. The multiplication operation is performed between two
|
||||
// numbers represented as 'float64' types (since Go does not support generic
|
||||
// types on its own). The resulting value will be cast to the type represented
|
||||
// by the receiver of this method 't'. If a casting error occurs, it will also
|
||||
// be returned along with nil for the result.
|
||||
func (t StateVarType) Mul(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af * bf)
|
||||
}
|
||||
|
||||
// Div performs division between the provided arguments 'a' and 'b'. The function casts both operands to their numeric equivalents using valuesToNumericOperands() before performing the division.
|
||||
//
|
||||
// Parameters:
|
||||
// - a: first operand of type interface{}, can be of any type, will be converted if necessary
|
||||
// - b: second operand of type interface{}, can be of any type, will be converted if necessary
|
||||
//
|
||||
// Returns:
|
||||
// - result: the division result in numeric form after casting it with function t.Cast()
|
||||
// - err: error that might occur during the conversion or division operation
|
||||
func (t StateVarType) Div(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af / bf)
|
||||
}
|
||||
153
upnp/statevariables/statevartype_cast.go
Normal file
153
upnp/statevariables/statevartype_cast.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Cast converts a value to the Go type corresponding to the UPnP type.
|
||||
// Supports conversion from various primitive types and strings.
|
||||
// Returns an error for unsupported conversions or invalid values.
|
||||
//
|
||||
// Examples:
|
||||
// - StateType_UI2.Cast(42) // uint16(42), nil
|
||||
// - StateType_Boolean.Cast("true") // true, nil
|
||||
// - StateType_UI1.Cast(300) // nil, error (overflow)
|
||||
func (t StateVarType) Cast(val interface{}) (interface{}, error) {
|
||||
switch t {
|
||||
case StateType_UI1:
|
||||
v, err := toUint(val, 8)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI1", val, val)
|
||||
}
|
||||
return uint8(v), nil
|
||||
|
||||
case StateType_UI2:
|
||||
v, err := toUint(val, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI2", val, val)
|
||||
}
|
||||
return uint16(v), nil
|
||||
|
||||
case StateType_UI4:
|
||||
v, err := toUint(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI4", val, val)
|
||||
}
|
||||
return uint32(v), nil
|
||||
|
||||
case StateType_I1:
|
||||
v, err := toInt(val, 8)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I1", val, val)
|
||||
}
|
||||
return int8(v), nil
|
||||
|
||||
case StateType_I2:
|
||||
v, err := toInt(val, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I2", val, val)
|
||||
}
|
||||
return int16(v), nil
|
||||
|
||||
case StateType_I4, StateType_Int:
|
||||
v, err := toInt(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I4", val, val)
|
||||
}
|
||||
return int32(v), nil
|
||||
|
||||
case StateType_R4:
|
||||
v, err := toFloat(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to R4", val, val)
|
||||
}
|
||||
return float32(v), nil
|
||||
|
||||
case StateType_R8, StateType_Number, StateType_Fixed14_4:
|
||||
v, err := toFloat(val, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to R8", val, val)
|
||||
}
|
||||
return v, nil
|
||||
|
||||
case StateType_Boolean:
|
||||
b, err := toBool(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to Boolean", val, val)
|
||||
}
|
||||
return b, nil
|
||||
|
||||
case StateType_Char:
|
||||
switch s := val.(type) {
|
||||
case string:
|
||||
if len(s) != 1 {
|
||||
return nil, fmt.Errorf("invalid Char: string too long %q", s)
|
||||
}
|
||||
return rune(s[0]), nil
|
||||
case rune:
|
||||
return s, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to Char", val, val)
|
||||
}
|
||||
|
||||
case StateType_String:
|
||||
return fmt.Sprint(val), nil
|
||||
|
||||
case StateType_UUID:
|
||||
switch val := val.(type) {
|
||||
case uuid.UUID:
|
||||
return val, nil
|
||||
case string:
|
||||
u, err := uuid.Parse(strings.TrimSpace(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid UUID %v: %v", val, err)
|
||||
}
|
||||
return u, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UUID", val, val)
|
||||
}
|
||||
|
||||
case StateType_URI:
|
||||
switch val := val.(type) {
|
||||
case *url.URL:
|
||||
return val, nil
|
||||
case string:
|
||||
u, err := url.Parse(strings.TrimSpace(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URI %v: %v", val, err)
|
||||
}
|
||||
return u, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to URI", val, val)
|
||||
}
|
||||
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
switch v := val.(type) {
|
||||
case []byte:
|
||||
return v, nil
|
||||
case string:
|
||||
return decodeBinary(t, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to binary", val, val)
|
||||
}
|
||||
|
||||
case StateType_Date, StateType_DateTime, StateType_DateTimeTZ,
|
||||
StateType_Time, StateType_TimeTZ:
|
||||
switch v := val.(type) {
|
||||
case time.Time:
|
||||
return v, nil
|
||||
case string:
|
||||
return parseUPnPTime(t, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to time", val, val)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %v", t)
|
||||
}
|
||||
}
|
||||
205
upnp/statevariables/statevartype_cmp.go
Normal file
205
upnp/statevariables/statevartype_cmp.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Cmp compares two values of a given type 'StateVarType'. The comparison is
|
||||
// made based on the specific type of 'a' and 'b'. It returns an integer
|
||||
// indicating whether 'a' is less than, equal to, or greater than 'b'. If 'a' is
|
||||
// less than 'b', it returns -1. If they are equal, it returns 0. If 'a' is
|
||||
// greater than 'b', it returns 1. It also returns an error if any of the values
|
||||
// can't be cast to the required type or if comparison isn't supported for the
|
||||
// given type 'StateVarType'.
|
||||
//
|
||||
// - t: StateVarType representing the specific type to use for comparison.
|
||||
// - a, b: The values to compare. They should be of type interface{} as they could be
|
||||
// any valid Go type.
|
||||
//
|
||||
// Returns: An integer indicating the result of the comparison (-1 if 'a' is
|
||||
// less than 'b', 0 if they are equal, and 1 if 'a' is greater than 'b'). It
|
||||
// also returns an error if any values can't be cast or if comparison isn't
|
||||
// supported for the given type.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result, err := t.Cmp(int32(5), int32(7)) // Returns -1 and nil error as 5 is less than 7.
|
||||
func (t StateVarType) Cmp(a, b interface{}) (int, error) {
|
||||
a, err1 := t.Cast(a)
|
||||
b, err2 := t.Cast(b)
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
log.Fatalf("Failed to cast for comparison: %v vs %v (errors: %v, %v)", a, b, err1, err2)
|
||||
}
|
||||
|
||||
switch {
|
||||
case t.IsInteger():
|
||||
ai, err := toInt(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid int value for a: %w", err)
|
||||
}
|
||||
bi, err := toInt(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid int value for b: %w", err)
|
||||
}
|
||||
return cmpInt(ai, bi), nil
|
||||
|
||||
case t.IsUnsignedInt():
|
||||
ai, err := toUint(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid uint value for a: %w", err)
|
||||
}
|
||||
bi, err := toUint(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid uint value for b: %w", err)
|
||||
}
|
||||
return cmpUint(ai, bi), nil
|
||||
|
||||
case t.IsFloat():
|
||||
af, err := toFloat(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid float value for a: %w", err)
|
||||
}
|
||||
bf, err := toFloat(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid float value for b: %w", err)
|
||||
}
|
||||
return cmpFloat64(af, bf), nil
|
||||
|
||||
case t == StateType_Boolean:
|
||||
ab, err := toBool(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid bool value for a: %w", err)
|
||||
}
|
||||
bb, err := toBool(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid bool value for b: %w", err)
|
||||
}
|
||||
return cmpBool(ab, bb), nil
|
||||
|
||||
case t == StateType_String || t == StateType_Char:
|
||||
as, err := toString(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid string value for a")
|
||||
}
|
||||
bs, err := toString(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid string value for b")
|
||||
}
|
||||
return strings.Compare(as, bs), nil
|
||||
|
||||
case t.IsTime():
|
||||
at, err := toTime(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid time value for a")
|
||||
}
|
||||
bt, err := toTime(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid time value for b")
|
||||
}
|
||||
return cmpTime(at, bt), nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("comparison not supported for type %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (t StateVarType) Equal(a, b interface{}) (bool, error) {
|
||||
switch {
|
||||
case t.IsInteger():
|
||||
ai, err1 := toInt(a, 64)
|
||||
bi, err2 := toInt(b, 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid integer value for type %s", t.String())
|
||||
}
|
||||
return ai == bi, nil
|
||||
|
||||
case t.IsFloat():
|
||||
af, err := toFloat(a, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid float value for type %s: %v", t.String(), err)
|
||||
}
|
||||
bf, err := toFloat(b, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid float value for type %s: %v", t.String(), err)
|
||||
}
|
||||
return af == bf, nil
|
||||
|
||||
case t.IsString():
|
||||
as, ok1 := a.(string)
|
||||
bs, ok2 := b.(string)
|
||||
if !ok1 || !ok2 {
|
||||
return false, fmt.Errorf("invalid string value for type %s", t.String())
|
||||
}
|
||||
return as == bs, nil
|
||||
|
||||
case t.IsBool():
|
||||
ab, err1 := toBool(a)
|
||||
bb, err2 := toBool(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid boolean value for type %s", t.String())
|
||||
}
|
||||
return ab == bb, nil
|
||||
|
||||
case t.IsTime():
|
||||
at, err1 := toTime(a)
|
||||
bt, err2 := toTime(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid time.Time value for type %s", t.String())
|
||||
}
|
||||
return at.Equal(bt), nil
|
||||
|
||||
case t.IsUUID():
|
||||
au, err1 := toUUID(a)
|
||||
bu, err2 := toUUID(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid uuid.UUID value for type %s", t.String())
|
||||
}
|
||||
return au == bu, nil
|
||||
|
||||
case t.IsURI():
|
||||
au, err1 := toURI(a)
|
||||
bu, err2 := toURI(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid *url.URL value for type %s", t.String())
|
||||
}
|
||||
return au.String() == bu.String(), nil
|
||||
|
||||
case t.IsBinary():
|
||||
ab, err1 := toBinary(a)
|
||||
bb, err2 := toBinary(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid []byte value for type %s", t.String())
|
||||
}
|
||||
return bytes.Equal(ab, bb), nil
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("equality not supported for type %s", t.String())
|
||||
}
|
||||
}
|
||||
|
||||
// InRange checks if a value falls within an inclusive range [min, max].
|
||||
// Uses the type's comparison logic. Returns true if val is between min and max (inclusive).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// range := ValueRange{min: uint16(10), max: uint16(100)}
|
||||
// StateType_UI2.InRange(uint16(50), range) // true
|
||||
func (t StateVarType) InRange(val interface{}, interval *ValueRange) (bool, error) {
|
||||
if interval == nil {
|
||||
return true, nil
|
||||
}
|
||||
cmp1, err1 := t.Cmp(val, interval.min)
|
||||
cmp2, err2 := t.Cmp(val, interval.max)
|
||||
if err1 != nil || err2 != nil {
|
||||
err := err1
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return cmp1 >= 0 && cmp2 <= 0, nil
|
||||
}
|
||||
171
upnp/statevariables/statevartype_typechecking.go
Normal file
171
upnp/statevariables/statevartype_typechecking.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package statevariables
|
||||
|
||||
// IsNumeric checks whether a given StateVarType represents a numeric type or
|
||||
// not. Numeric types are defined as those that can be used to store number-like
|
||||
// values. The following types are considered numeric: UI1, UI2, UI4, I1, I2,
|
||||
// I4, Int, R4, R8, Number and Fixed14_4.
|
||||
//
|
||||
// t: StateVarType to check if it's a numeric type or not.
|
||||
//
|
||||
// Returns true if the given StateVarType represents a numeric type; false
|
||||
// otherwise.
|
||||
func (t StateVarType) IsNumeric() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4,
|
||||
StateType_I1, StateType_I2, StateType_I4,
|
||||
StateType_Int,
|
||||
StateType_R4, StateType_R8,
|
||||
StateType_Number,
|
||||
StateType_Fixed14_4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsInteger checks if the state variable type is integer or not.
|
||||
//
|
||||
// It returns a boolean value indicating whether the provided StateVarType (t)
|
||||
// is an integer type or not. The function takes one parameter, t of type
|
||||
// StateVarType, which represents the state variable type to be checked.
|
||||
//
|
||||
// Parameters: - t (StateVarType): The StateVarType to check for comparability.
|
||||
//
|
||||
// Returns: bool: If the state variable type is any of the defined integer types
|
||||
// (StateType_UI1, StateType_UI2, StateType_UI4, StateType_I1, StateType_I2,
|
||||
// StateType_I4, StateType_Int), it returns true. Otherwise, it returns false.
|
||||
func (t StateVarType) IsInteger() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4,
|
||||
StateType_I1, StateType_I2, StateType_I4,
|
||||
StateType_Int:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsSignedInt checks if the state variable type is a signed integer type.
|
||||
// The function returns true for StateType_I1, StateType_I2, StateType_I4, and
|
||||
// StateType_Int, otherwise it will return false. This method is part of the
|
||||
// StateVarType enumeration in statevaluetype package. It takes no parameters
|
||||
// but operates on the receiver 't' of type StateVarType.
|
||||
//
|
||||
// The returned value is a boolean.
|
||||
func (t StateVarType) IsSignedInt() bool {
|
||||
switch t {
|
||||
case StateType_I1, StateType_I2, StateType_I4, StateType_Int:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsUnsignedInt checks if the state variable type is an unsigned integer. It
|
||||
// returns a boolean indicating whether or not the current StateVarType
|
||||
// represents an unsigned integer type, namely: StateType_UI1, StateType_UI2,
|
||||
// and StateType_UI4.
|
||||
func (t StateVarType) IsUnsignedInt() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsFloat returns a boolean indicating whether the given state variable type
|
||||
// represents a float number. If the state variable type is one of R4, R8, Number or
|
||||
// Fixed14_4 it returns true; otherwise, it returns false.
|
||||
func (t StateVarType) IsFloat() bool {
|
||||
switch t {
|
||||
case StateType_R4, StateType_R8, StateType_Number, StateType_Fixed14_4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsBool checks if a StateVarType is of type Boolean. It returns true if the
|
||||
// StateVarType equals to StateType_Boolean, false otherwise.
|
||||
func (t StateVarType) IsBool() bool {
|
||||
return t == StateType_Boolean
|
||||
}
|
||||
|
||||
// IsString reports whether or not the state variable type represents a string
|
||||
// value.
|
||||
//
|
||||
// This method returns true if the StateVarType is either StateType_String or
|
||||
// StateType_Char, otherwise it returns false.
|
||||
func (t StateVarType) IsString() bool {
|
||||
switch t {
|
||||
case StateType_String, StateType_Char:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsTime checks whether a given StateVarType is of time type or not. It accepts
|
||||
// a StateVarType parameter 't' and returns a boolean value based on the check.
|
||||
//
|
||||
// The possible values for 't' are: StateType_Date, StateType_DateTime,
|
||||
// StateType_DateTimeTZ, StateType_Time, StateType_TimeTZ. If 't' is any of
|
||||
// these types, the function returns true; otherwise, it returns false.
|
||||
func (t StateVarType) IsTime() bool {
|
||||
switch t {
|
||||
case StateType_Date, StateType_DateTime, StateType_DateTimeTZ,
|
||||
StateType_Time, StateType_TimeTZ:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsUUID reports whether the receiver represents a UUID (Universally Unique
|
||||
// Identifier). The StateVarType should be of type StateType_UUID to return
|
||||
// true. Otherwise, it returns false.
|
||||
func (t StateVarType) IsUUID() bool {
|
||||
return t == StateType_UUID
|
||||
}
|
||||
|
||||
// IsURI checks if the given state variable type is a URI.
|
||||
//
|
||||
// This function returns true if and only if the receiver (StateVarType t)
|
||||
// equals StateType_URI, which represents URIs in UPnP protocol. Otherwise, it
|
||||
// returns false.
|
||||
func (t StateVarType) IsURI() bool {
|
||||
return t == StateType_URI
|
||||
}
|
||||
|
||||
// IsBinary checks if the given StateVarType is binary type or not. It returns
|
||||
// true for types StateType_BinBase64 and StateType_BinHex, otherwise it returns
|
||||
// false.
|
||||
func (t StateVarType) IsBinary() bool {
|
||||
switch t {
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsComparable function checks if a StateVarType is comparable or not.
|
||||
//
|
||||
// It returns false for binary types (StateType_BinBase64 and StateType_BinHex)
|
||||
// as they are non-comparable. For all other types, it returns true indicating
|
||||
// that these types can be compared.
|
||||
//
|
||||
// Parameters: - t (StateVarType): The StateVarType to check for comparability.
|
||||
//
|
||||
// Returns: bool: A boolean value indicating whether the given StateVarType is
|
||||
// comparable or not. True means it's comparable, False means it isn't.
|
||||
func (t StateVarType) IsComparable() bool {
|
||||
// Tous les types sauf les binaires sont comparables
|
||||
switch t {
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
62
upnp/statevariables/utils_binary.go
Normal file
62
upnp/statevariables/utils_binary.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// toBinary tries to convert v into a []byte.
|
||||
// - if v is []byte, returns it directly
|
||||
// - if v is string, attempts base64 decode, then hex decode
|
||||
func toBinary(v interface{}) ([]byte, error) {
|
||||
switch val := v.(type) {
|
||||
case []byte:
|
||||
return val, nil
|
||||
|
||||
case string:
|
||||
// Try base64 first
|
||||
if data, err := base64.StdEncoding.DecodeString(val); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Try hex next
|
||||
if data, err := hex.DecodeString(val); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot parse string as base64 or hex: %q", val)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot convert type %T to binary", v)
|
||||
}
|
||||
}
|
||||
|
||||
// decodeBinary decodes Base64 or Hex-encoded binary strings to byte slices
|
||||
func decodeBinary(t StateVarType, val string) ([]byte, error) {
|
||||
switch t {
|
||||
case StateType_BinBase64:
|
||||
data, err := base64.StdEncoding.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base64: %v", err)
|
||||
}
|
||||
return data, nil
|
||||
|
||||
case StateType_BinHex:
|
||||
// Accept even-length hex string
|
||||
val = strings.TrimSpace(val)
|
||||
if len(val)%2 != 0 {
|
||||
return nil, fmt.Errorf("invalid hex: odd-length string")
|
||||
}
|
||||
data := make([]byte, len(val)/2)
|
||||
_, err := hex.Decode(data, []byte(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hex: %v", err)
|
||||
}
|
||||
return data, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("decodeBinary: unsupported binary type %v", t)
|
||||
}
|
||||
}
|
||||
61
upnp/statevariables/utils_bool.go
Normal file
61
upnp/statevariables/utils_bool.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseUPnPBoolean parses a string like "true", "false", "1", "0" into a boolean.
|
||||
func parseUPnPBoolean(s string) (bool, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(s)) {
|
||||
case "1", "true":
|
||||
return true, nil
|
||||
case "0", "false":
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.New("invalid string for UPnP boolean")
|
||||
}
|
||||
}
|
||||
|
||||
// toBool converts various types to boolean following UPnP rules:
|
||||
// true: 1, "true"; false: 0, "false"
|
||||
func toBool(val interface{}) (bool, error) {
|
||||
if val == nil {
|
||||
return false, errors.New("cannot convert nil to bool")
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case bool:
|
||||
return v, nil
|
||||
|
||||
case string:
|
||||
return parseUPnPBoolean(v)
|
||||
|
||||
default:
|
||||
// try to convert numerics to float
|
||||
f, err := toFloat(v, 64)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if f == 1.0 {
|
||||
return true, nil
|
||||
}
|
||||
if f == 0.0 {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.New("numeric value cannot be converted to bool unless 0 or 1")
|
||||
}
|
||||
}
|
||||
|
||||
// cmpBool compares two boolean values 'a' and 'b'. If they are equal it returns 0, if 'a' is false and 'b' is true it returns -1 else it returns 1.
|
||||
func cmpBool(a, b bool) int {
|
||||
if a == b { // if both booleans are the same, return 0
|
||||
return 0
|
||||
}
|
||||
if !a && b { // if 'a' is false and 'b' is true, return -1
|
||||
return -1
|
||||
}
|
||||
// otherwise, return 1
|
||||
return 1 // if 'a' is true or 'a' and 'b' are not the same
|
||||
}
|
||||
95
upnp/statevariables/utils_float.go
Normal file
95
upnp/statevariables/utils_float.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// maxFloat returns the maximum value for floating point numbers given number of bits.
|
||||
// If bits is neither 32 nor 64, it defaults to returning the maximum float64 value.
|
||||
func maxFloat(bits int) float64 {
|
||||
switch bits {
|
||||
case 32:
|
||||
return float64(math.MaxFloat32)
|
||||
case 64:
|
||||
return math.MaxFloat64
|
||||
default:
|
||||
return math.MaxFloat64 // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// minFloat returns the minimum float value for the given number of bits.
|
||||
// If bits are not recognized as either 32 or 64, it will default to the maximum
|
||||
// possible float64 value.
|
||||
func minFloat(bits int) float64 {
|
||||
switch bits {
|
||||
case 32:
|
||||
return -float64(math.MaxFloat32)
|
||||
case 64:
|
||||
return -math.MaxFloat64
|
||||
default:
|
||||
return -math.MaxFloat64 // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// toFloat converts various types to float (32 or 64 bits).
|
||||
// Checks float32 boundaries when converting to 32-bit float.
|
||||
// toFloat converts v to a float64, ensuring it fits within the range of the requested float size.
|
||||
func toFloat(v interface{}, bits int) (float64, error) {
|
||||
var f float64
|
||||
|
||||
switch val := v.(type) {
|
||||
case float32:
|
||||
f = float64(val)
|
||||
case float64:
|
||||
f = val
|
||||
case int:
|
||||
f = float64(val)
|
||||
case int8:
|
||||
f = float64(val)
|
||||
case int16:
|
||||
f = float64(val)
|
||||
case int32:
|
||||
f = float64(val)
|
||||
case int64:
|
||||
f = float64(val)
|
||||
case uint:
|
||||
f = float64(val)
|
||||
case uint8:
|
||||
f = float64(val)
|
||||
case uint16:
|
||||
f = float64(val)
|
||||
case uint32:
|
||||
f = float64(val)
|
||||
case uint64:
|
||||
f = float64(val)
|
||||
case string:
|
||||
var err error
|
||||
f, err = strconv.ParseFloat(val, bits)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("%T,unsupported type in toFloat", v)
|
||||
}
|
||||
|
||||
if f < minFloat(bits) || f > maxFloat(bits) {
|
||||
return 0, fmt.Errorf("value %v overflows float%d range", v, bits)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// cmpFloat64 compares two float64 values and returns an integer indicating their relation.
|
||||
// If a is less than b, it returns -1; if a is greater than b, it returns 1; otherwise, it returns 0.
|
||||
func cmpFloat64(a, b float64) int {
|
||||
switch {
|
||||
case a < b:
|
||||
return -1
|
||||
case a > b:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
143
upnp/statevariables/utils_int.go
Normal file
143
upnp/statevariables/utils_int.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// minInt returns minimum signed integer value for specified bit size
|
||||
func minInt(bits int) int64 {
|
||||
switch bits {
|
||||
case 8:
|
||||
return math.MinInt8
|
||||
case 16:
|
||||
return math.MinInt16
|
||||
case 32:
|
||||
return math.MinInt32
|
||||
case 64:
|
||||
return math.MinInt64
|
||||
default:
|
||||
return math.MinInt64 // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// maxInt returns maximum signed integer value for specified bit size
|
||||
func maxInt(bits int) int64 {
|
||||
switch bits {
|
||||
case 8:
|
||||
return math.MaxInt8
|
||||
case 16:
|
||||
return math.MaxInt16
|
||||
case 32:
|
||||
return math.MaxInt32
|
||||
case 64:
|
||||
return math.MaxInt64
|
||||
default:
|
||||
return math.MaxInt64 // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// toInt converts the given interface value into an int64. The function accepts
|
||||
// a parameter v of any type and an integer bits representing the size of the
|
||||
// desired integer type (8, 16, 32 or 64). If successful, it returns the
|
||||
// converted integer and nil for error. Otherwise, it returns zero for int64 and
|
||||
// an appropriate error message.
|
||||
//
|
||||
// The function checks whether v is nil. If true, it returns an error stating
|
||||
// "cannot convert nil to int".
|
||||
//
|
||||
// It then identifies the type of v using a type switch statement. Depending on
|
||||
// the type, the function performs different actions:
|
||||
// - For integer types (int, int8, int16, int32 and int64), it uses checkIntBounds() to ensure the value fits within the specified bits range and returns the result.
|
||||
// - For unsigned types (uint, uint8, uint16, uint32 and uint64), it checks for overflow before converting to an int64 and calls checkIntBounds().
|
||||
// - For float types (float32 and float64), it converts them to int64 directly.
|
||||
// - For string type, it attempts to parse the string as a base-10 integer using strconv.ParseInt() with specified bits range.
|
||||
//
|
||||
// If no match is found in these cases or v is of unsupported type, it returns
|
||||
// an error stating "unsupported type for toInt".
|
||||
func toInt(v interface{}, bits int) (int64, error) {
|
||||
if v == nil {
|
||||
return 0, errors.New("cannot convert nil to int")
|
||||
}
|
||||
|
||||
switch val := v.(type) {
|
||||
case int:
|
||||
return checkIntBounds(int64(val), bits)
|
||||
case int8:
|
||||
return checkIntBounds(int64(val), bits)
|
||||
case int16:
|
||||
return checkIntBounds(int64(val), bits)
|
||||
case int32:
|
||||
return checkIntBounds(int64(val), bits)
|
||||
case int64:
|
||||
return checkIntBounds(val, bits)
|
||||
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
u := reflect.ValueOf(val).Uint()
|
||||
if u > uint64(math.MaxInt64) {
|
||||
return 0, errors.New("unsigned value overflows int64")
|
||||
}
|
||||
return checkIntBounds(int64(u), bits)
|
||||
|
||||
case float32:
|
||||
return checkIntBounds(int64(val), bits)
|
||||
case float64:
|
||||
return checkIntBounds(int64(val), bits)
|
||||
|
||||
case string:
|
||||
i, err := strconv.ParseInt(val, 10, bits)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return checkIntBounds(i, bits)
|
||||
|
||||
default:
|
||||
return 0, errors.New("unsupported type for toInt")
|
||||
}
|
||||
}
|
||||
|
||||
// CheckIntBounds checks if a given int64 value is within the valid range for a specific number of bits.
|
||||
// It returns an error and 0 if the value is out of bounds, else it returns the input value and nil.
|
||||
//
|
||||
// Parameters:
|
||||
// - v: The integer value to check.
|
||||
// - bits: The number of bits that determine the valid range for 'v'.
|
||||
//
|
||||
// Returns:
|
||||
// - int64: The original input value if within bounds, else 0.
|
||||
// - error: An error object if the value is out of bounds; nil otherwise.
|
||||
func checkIntBounds(v int64, bits int) (int64, error) {
|
||||
if v < minInt(bits) || v > maxInt(bits) {
|
||||
return 0, errors.New("integer value out of bounds")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// cmpInt compares two integers 'a' and 'b'. If 'a' is less than 'b', it returns -1, if 'a' is greater than 'b' it returns 1, else it returns 0.
|
||||
func cmpInt(a, b int64) int {
|
||||
switch {
|
||||
case a < b:
|
||||
return -1
|
||||
case a > b:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// cmpUint compares two unsigned integers, a and b.
|
||||
// It returns -1 if a < b.
|
||||
// It returns 1 if a > b.
|
||||
// It returns 0 if a == b.
|
||||
func cmpUint(a, b uint64) int {
|
||||
switch {
|
||||
case a < b:
|
||||
return -1
|
||||
case a > b:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
32
upnp/statevariables/utils_numeric_operandes.go
Normal file
32
upnp/statevariables/utils_numeric_operandes.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package statevariables
|
||||
|
||||
import "fmt"
|
||||
|
||||
// valuesToNumericOperands takes two interface{} values, casts them to a numeric type based on a given StateVarType, and returns their float64 equivalents.
|
||||
// If any error occurs during casting or conversion to float64, it is returned along with zero values for the operands.
|
||||
func valuesToNumericOperands(t StateVarType, a interface{}, b interface{}) (float64, float64, error) {
|
||||
var err error
|
||||
if !t.IsNumeric() {
|
||||
return 0, 0, fmt.Errorf("type %v is not numeric", t)
|
||||
}
|
||||
|
||||
a, err = t.Cast(a)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
b, err = t.Cast(b)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
af, err := toFloat(a, 64)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
bf, err := toFloat(b, 64)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return af, bf, nil
|
||||
}
|
||||
37
upnp/statevariables/utils_string.go
Normal file
37
upnp/statevariables/utils_string.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// toString converts v to string if possible.
|
||||
func toString(v interface{}) (string, error) {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return val, nil
|
||||
|
||||
case []byte:
|
||||
return string(val), nil
|
||||
|
||||
case fmt.Stringer:
|
||||
return val.String(), nil
|
||||
|
||||
case int, int8, int16, int32, int64:
|
||||
return fmt.Sprintf("%d", val), nil
|
||||
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
return fmt.Sprintf("%d", val), nil
|
||||
|
||||
case float32, float64:
|
||||
return fmt.Sprintf("%v", val), nil
|
||||
|
||||
case bool:
|
||||
if val {
|
||||
return "true", nil
|
||||
}
|
||||
return "false", nil
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("cannot convert type %T to string", v)
|
||||
}
|
||||
}
|
||||
103
upnp/statevariables/utils_time.go
Normal file
103
upnp/statevariables/utils_time.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// toTime converts the given value to a time.Time type if possible, otherwise it returns an error.
|
||||
// The supported types for conversion are: time.Time, string, int64 and float64.
|
||||
func toTime(v interface{}) (time.Time, error) {
|
||||
switch val := v.(type) {
|
||||
case time.Time:
|
||||
return val, nil
|
||||
|
||||
case string:
|
||||
// Liste de layouts communs à UPnP / ISO-8601 / RFC
|
||||
layouts := []string{
|
||||
time.RFC3339Nano, // 2006-01-02T15:04:05.999999999Z07:00
|
||||
time.RFC3339, // 2006-01-02T15:04:05Z07:00
|
||||
"2006-01-02", // date only
|
||||
"15:04:05", // time only
|
||||
"15:04:05Z07:00", // time with TZ
|
||||
"2006-01-02T15:04:05", // no TZ
|
||||
"2006-01-02T15:04:05Z", // UTC
|
||||
"2006-01-02 15:04:05", // space-separated
|
||||
}
|
||||
|
||||
for _, layout := range layouts {
|
||||
if t, err := time.Parse(layout, val); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("cannot parse time string: %q", val)
|
||||
|
||||
case int64:
|
||||
return time.Unix(val, 0), nil
|
||||
|
||||
case float64:
|
||||
sec := int64(val)
|
||||
nsec := int64((val - float64(sec)) * 1e9)
|
||||
return time.Unix(sec, nsec), nil
|
||||
|
||||
default:
|
||||
return time.Time{}, fmt.Errorf("unsupported type for time conversion: %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
// parseUPnPTime parses time values using UPnP-specific formats:
|
||||
// - Date: "2006-01-02"
|
||||
// - Time: "15:04:05"
|
||||
// - DateTime: "2006-01-02T15:04:05"
|
||||
// - TimeTZ: "15:04:05-07:00"
|
||||
// - DateTimeTZ: "2006-01-02T15:04:05-07:00"
|
||||
func parseUPnPTime(t StateVarType, s string) (time.Time, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
layouts := []string{}
|
||||
|
||||
switch t {
|
||||
case StateType_Date:
|
||||
layouts = []string{"2006-01-02"}
|
||||
|
||||
case StateType_Time:
|
||||
layouts = []string{"15:04:05"} // HH:MM:SS
|
||||
|
||||
case StateType_TimeTZ:
|
||||
layouts = []string{"15:04:05Z07:00"} // HH:MM:SS+TZ
|
||||
|
||||
case StateType_DateTime:
|
||||
layouts = []string{"2006-01-02T15:04:05"} // ISO8601 sans TZ
|
||||
|
||||
case StateType_DateTimeTZ:
|
||||
layouts = []string{
|
||||
"2006-01-02T15:04:05Z07:00", // full
|
||||
"2006-01-02T15:04:05-0700", // fallback no colon
|
||||
"2006-01-02T15:04:05Z", // Zulu
|
||||
}
|
||||
|
||||
default:
|
||||
return time.Time{}, fmt.Errorf("unsupported date/time type: %v", t)
|
||||
}
|
||||
|
||||
for _, layout := range layouts {
|
||||
if ts, err := time.Parse(layout, s); err == nil {
|
||||
return ts, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("invalid %v value: %q", t, s)
|
||||
}
|
||||
|
||||
// cmpTime compares two time.Times, returning -1 if the first is before the second,
|
||||
// 1 if the first is after the second, and 0 if they're equal.
|
||||
func cmpTime(a, b time.Time) int {
|
||||
switch {
|
||||
case a.Before(b):
|
||||
return -1
|
||||
case a.After(b):
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
100
upnp/statevariables/utils_uint.go
Normal file
100
upnp/statevariables/utils_uint.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// maxUint returns maximum unsigned integer value for specified bit size
|
||||
func maxUint(bits int) uint64 {
|
||||
switch bits {
|
||||
case 8:
|
||||
return math.MaxUint8
|
||||
case 16:
|
||||
return math.MaxUint16
|
||||
case 32:
|
||||
return math.MaxUint32
|
||||
case 64:
|
||||
return math.MaxUint64
|
||||
default:
|
||||
return math.MaxUint64 // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// toUint converts various types of numeric values into a uint64 type. It
|
||||
// supports conversions from signed and unsigned integers, floating-point
|
||||
// numbers, and string representations of integers.
|
||||
//
|
||||
// Parameters:
|
||||
// - v interface{}: input value that can be converted to uint64. The
|
||||
// function will return an error if the input is not one of these types.
|
||||
// - bits int: number of bits that should fit within the returned uint64.
|
||||
// An error will be returned if the conversion would exceed this limit.
|
||||
//
|
||||
// Returns:
|
||||
// - uint64: converted value from input 'v'. If the input 'v' is a string,
|
||||
// it must represent an integer in base 10 and can fit into an uint64
|
||||
// type with given number of bits.
|
||||
// - error: if the conversion fails due to unsupported input type, overflow
|
||||
// or underflow conditions, or invalid string representation, this will
|
||||
// contain an appropriate error message. If 'v' is nil,
|
||||
// it returns an "cannot convert nil to uint" error.
|
||||
func toUint(v interface{}, bits int) (uint64, error) {
|
||||
if v == nil {
|
||||
return 0, errors.New("cannot convert nil to uint")
|
||||
}
|
||||
|
||||
switch val := v.(type) {
|
||||
case uint:
|
||||
return checkUintBounds(uint64(val), bits)
|
||||
case uint8:
|
||||
return checkUintBounds(uint64(val), bits)
|
||||
case uint16:
|
||||
return checkUintBounds(uint64(val), bits)
|
||||
case uint32:
|
||||
return checkUintBounds(uint64(val), bits)
|
||||
case uint64:
|
||||
return checkUintBounds(val, bits)
|
||||
|
||||
case int, int8, int16, int32, int64:
|
||||
i := reflect.ValueOf(val).Int()
|
||||
if i < 0 {
|
||||
return 0, errors.New("negative value cannot be converted to uint")
|
||||
}
|
||||
return checkUintBounds(uint64(i), bits)
|
||||
|
||||
case float32:
|
||||
if val < 0 {
|
||||
return 0, errors.New("negative float cannot be converted to uint")
|
||||
}
|
||||
return checkUintBounds(uint64(val), bits)
|
||||
case float64:
|
||||
if val < 0 {
|
||||
return 0, errors.New("negative float cannot be converted to uint")
|
||||
}
|
||||
return checkUintBounds(uint64(val), bits)
|
||||
|
||||
case string:
|
||||
u, err := strconv.ParseUint(val, 10, bits)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return checkUintBounds(u, bits)
|
||||
|
||||
default:
|
||||
return 0, errors.New("unsupported type for toUint")
|
||||
}
|
||||
}
|
||||
|
||||
// checkUintBounds checks whether the given unsigned integer 'v' is within the
|
||||
// acceptable range defined by the number of bits 'bits'. If 'v' is out of
|
||||
// bounds, it returns an error with a message "unsigned integer value out of
|
||||
// bounds". Otherwise, it returns 'v' and nil.
|
||||
func checkUintBounds(v uint64, bits int) (uint64, error) {
|
||||
if v > maxUint(bits) {
|
||||
return 0, errors.New("unsigned integer value out of bounds")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
25
upnp/statevariables/utils_uri.go
Normal file
25
upnp/statevariables/utils_uri.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// toURI converts a value to *url.URL if possible.
|
||||
func toURI(v interface{}) (*url.URL, error) {
|
||||
switch val := v.(type) {
|
||||
case *url.URL:
|
||||
// Already a URL
|
||||
return val, nil
|
||||
|
||||
case string:
|
||||
u, err := url.Parse(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URI string %q: %v", val, err)
|
||||
}
|
||||
return u, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot convert type %T to URI", v)
|
||||
}
|
||||
}
|
||||
32
upnp/statevariables/utils_uuid.go
Normal file
32
upnp/statevariables/utils_uuid.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package statevariables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// toUUID converts a value to uuid.UUID if possible.
|
||||
func toUUID(v interface{}) (uuid.UUID, error) {
|
||||
switch val := v.(type) {
|
||||
case uuid.UUID:
|
||||
return val, nil
|
||||
|
||||
case string:
|
||||
u, err := uuid.Parse(val)
|
||||
if err != nil {
|
||||
return uuid.UUID{}, fmt.Errorf("invalid UUID string %q: %v", val, err)
|
||||
}
|
||||
return u, nil
|
||||
|
||||
case []byte:
|
||||
u, err := uuid.FromBytes(val)
|
||||
if err != nil {
|
||||
return uuid.UUID{}, fmt.Errorf("invalid UUID bytes: %v", err)
|
||||
}
|
||||
return u, nil
|
||||
|
||||
default:
|
||||
return uuid.UUID{}, fmt.Errorf("cannot convert type %T to UUID", v)
|
||||
}
|
||||
}
|
||||
61
upnp/statevariables/valuerange.go
Normal file
61
upnp/statevariables/valuerange.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package statevariables
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ValueRange represents an inclusive range constraint for a state variable value.
|
||||
// It defines the minimum and maximum allowable values for a given UPnP type.
|
||||
//
|
||||
// Usage:
|
||||
// - For numeric types: min/max must be numeric types
|
||||
// - For time types: min/max must be time.Time values
|
||||
// - For strings/UUIDs: min/max must be string values
|
||||
//
|
||||
// Use with StateVarType.InRange() to check if values fall within the range.
|
||||
type ValueRange struct {
|
||||
min interface{}
|
||||
max interface{}
|
||||
}
|
||||
|
||||
// ValueRange creates a valid value range for the UPnP type.
|
||||
//
|
||||
// This method casts the provided min and max values to the UPnP type and returns
|
||||
// a ValueRange struct suitable for range validation. If either value cannot be
|
||||
// cast to the type, it returns an error.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// min: Minimum value of the range (inclusive)
|
||||
// max: Maximum value of the range (inclusive)
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// ValueRange: Valid range structure if cast succeeds
|
||||
// error: If min or max cannot be cast to the type
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Create a range for UI2 (uint16) values
|
||||
// r, err := StateType_UI2.ValueRange(10, 100)
|
||||
// if err != nil { /* handle error */ }
|
||||
// valid := StateType_UI2.InRange(50, r) // true
|
||||
//
|
||||
// Notes:
|
||||
// - The range is inclusive: [min, max]
|
||||
// - Values must be comparable using the type's comparison logic
|
||||
// - For time types, min and max must be valid time values
|
||||
func (t StateVarType) ValueRange(min, max interface{}) (*ValueRange, error) {
|
||||
cmin, error := t.Cast(min)
|
||||
if error != nil {
|
||||
return nil, fmt.Errorf("min value %v is not castable to type %s", min, t.String())
|
||||
}
|
||||
cmax, error := t.Cast(max)
|
||||
if error != nil {
|
||||
return nil, fmt.Errorf("max value %v is not castable to type %s", min, t.String())
|
||||
}
|
||||
|
||||
if cmp, err := t.Cmp(cmin, cmax); err != nil && cmp > 0 {
|
||||
cmax, cmin = cmin, cmax
|
||||
}
|
||||
|
||||
return &ValueRange{min: cmin, max: cmax}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user