Maintenant modifier les instance de state variable pour qu'elles notifient leurs changements
This commit is contained in:
24
pmolog/prettyxml.go
Normal file
24
pmolog/prettyxml.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package pmolog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
func PrettyPrintXML(raw string) string {
|
||||
var out bytes.Buffer
|
||||
dec := xml.NewDecoder(bytes.NewReader([]byte(raw)))
|
||||
enc := xml.NewEncoder(&out)
|
||||
enc.Indent("", " ")
|
||||
for {
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if err := enc.EncodeToken(t); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
enc.Flush()
|
||||
return out.String()
|
||||
}
|
||||
@@ -48,24 +48,6 @@ type Fault struct {
|
||||
|
||||
// ----- Utils -----
|
||||
|
||||
func prettyPrintXML(raw string) string {
|
||||
var out bytes.Buffer
|
||||
dec := xml.NewDecoder(bytes.NewReader([]byte(raw)))
|
||||
enc := xml.NewEncoder(&out)
|
||||
enc.Indent("", " ")
|
||||
for {
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if err := enc.EncodeToken(t); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
enc.Flush()
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// ----- Parseurs -----
|
||||
|
||||
func ParseSOAPEnvelope(body []byte) (*Envelope, error) {
|
||||
|
||||
@@ -12,8 +12,14 @@ import (
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type notifiable interface {
|
||||
Name() string
|
||||
EventToBeSent(name string, value interface{})
|
||||
}
|
||||
|
||||
type StateVarInstance struct {
|
||||
model *StateVariable
|
||||
name string
|
||||
@@ -27,12 +33,12 @@ type StateVarInstance struct {
|
||||
sendEvents bool
|
||||
parse StringValueParser
|
||||
marshal ValueSerializer
|
||||
|
||||
value interface{}
|
||||
previousValue interface{}
|
||||
lastChange time.Time
|
||||
lastEvent time.Time
|
||||
mu sync.RWMutex
|
||||
service notifiable
|
||||
value interface{}
|
||||
previousValue interface{}
|
||||
lastChange time.Time
|
||||
lastEvent time.Time
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (instance *StateVarInstance) Name() string {
|
||||
@@ -102,13 +108,11 @@ func (instance *StateVarInstance) ParseValue(value string) (interface{}, error)
|
||||
|
||||
// IsValueInRange checks if a value falls within the defined range.
|
||||
// Always returns true if no range is set.
|
||||
|
||||
// Parameters:
|
||||
|
||||
// value: Value to check
|
||||
|
||||
//
|
||||
// value: Value to check
|
||||
//
|
||||
// Returns:
|
||||
|
||||
// bool: True if within range or no range defined
|
||||
func (instance *StateVarInstance) IsValueInRange(value interface{}) (bool, error) {
|
||||
return instance.model.valueType.InRange(value, instance.valueRange)
|
||||
@@ -186,27 +190,73 @@ func (instance *StateVarInstance) SetValue(val interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok, err := instance.IsValidValue(cval); !ok || err != nil {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("Not valid value %v for variable %s", cval, instance.Name())
|
||||
}
|
||||
|
||||
instance.mu.Lock()
|
||||
defer instance.mu.Unlock()
|
||||
instance.previousValue = instance.value
|
||||
instance.value = cval
|
||||
|
||||
if instance.ShouldTriggerEvent() {
|
||||
instance.service.EventToBeSent(instance.Name(), instance.Value())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (instance *StateVarInstance) Incr() {
|
||||
instance.mu.Lock()
|
||||
defer instance.mu.Unlock()
|
||||
func (instance *StateVarInstance) Incr() error {
|
||||
if instance.HasStep() {
|
||||
value, err := instance.model.valueType.Add(instance.Value(), instance.Step())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return instance.SetValue(value)
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"no step for variable %s:%s",
|
||||
instance.service.Name(),
|
||||
instance.Name(),
|
||||
)
|
||||
}
|
||||
|
||||
func (instance *StateVarInstance) Decr() error {
|
||||
if instance.HasStep() {
|
||||
value, err := instance.model.valueType.Sub(instance.Value(), instance.Step())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return instance.SetValue(value)
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"no step for variable %s:%s",
|
||||
instance.service.Name(),
|
||||
instance.Name(),
|
||||
)
|
||||
}
|
||||
|
||||
// ShouldTriggerEvent vérifie toutes les conditions
|
||||
func (instance *StateVarInstance) ShouldTriggerEvent() bool {
|
||||
for _, condition := range instance.model.eventConditions {
|
||||
if !condition(instance) {
|
||||
return false
|
||||
if instance.IsSendingEvents() {
|
||||
for name, condition := range instance.model.eventConditions {
|
||||
if !condition(instance) {
|
||||
log.Debugf(
|
||||
"State variable %s:%s event condition %s not true",
|
||||
instance.service.Name(),
|
||||
instance.Name(),
|
||||
name,
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
func (sv *StateVarInstance) GenerateEvent() *etree.Element {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gargoton.petite-maison-orange.fr/eric/pmomusic/pmolog"
|
||||
"gargoton.petite-maison-orange.fr/eric/pmomusic/soap"
|
||||
"gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions"
|
||||
"gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
|
||||
@@ -162,38 +163,48 @@ func (svc *ServiceInstance) SendInitialEvent(sid string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ici tu peux construire un SOAP Event XML minimal
|
||||
body := `<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0"><e:property><Status>Initial</Status></e:property></e:propertyset>`
|
||||
changed := make(map[string]interface{})
|
||||
for name, sv := range svc.statevariables {
|
||||
if sv.IsSendingEvents() { // sendEvents="yes"
|
||||
changed[name] = sv.Value()
|
||||
}
|
||||
}
|
||||
|
||||
callback = strings.TrimSpace(callback)
|
||||
callback = strings.Trim(callback, "<>") // retire les < et >
|
||||
|
||||
// parser pour valider
|
||||
u, err := url.Parse(callback)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid callback URL: %v", err)
|
||||
if len(changed) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("NOTIFY", u.String(), strings.NewReader(body))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create NOTIFY request: %v", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
callback = strings.TrimSpace(callback)
|
||||
callback = strings.Trim(callback, "<>")
|
||||
|
||||
req.Header.Set("Content-Type", "text/xml; charset=\"utf-8\"")
|
||||
req.Header.Set("NT", "upnp:event")
|
||||
req.Header.Set("NTS", "upnp:propchange")
|
||||
req.Header.Set("SID", sid)
|
||||
req.Header.Set("SEQ", "0")
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to send initial event to %s: %v", callback, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
log.Infof("✅ Initial event sent to %s, status=%s", callback, resp.Status)
|
||||
body := `<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">`
|
||||
for name, val := range changed {
|
||||
body += fmt.Sprintf("<e:property><%s>%v</%s></e:property>", name, val, name)
|
||||
}
|
||||
body += "</e:propertyset>"
|
||||
|
||||
req, _ := http.NewRequest("NOTIFY", callback, strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", `text/xml; charset="utf-8"`)
|
||||
req.Header.Set("NT", "upnp:event")
|
||||
req.Header.Set("NTS", "upnp:propchange")
|
||||
req.Header.Set("SID", sid)
|
||||
req.Header.Set("SEQ", "0") // initial event
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to send initial event to %s: %v", callback, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
log.Infof(
|
||||
"✅ Initial event sent to %s, status=%s\n<details>\n\n```xml\n%s\n```\n</details>\n",
|
||||
callback,
|
||||
resp.Status,
|
||||
pmolog.PrettyPrintXML(body),
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
||||
func (svc *ServiceInstance) ToXMLElement() *etree.Element {
|
||||
|
||||
Reference in New Issue
Block a user