on progresse

This commit is contained in:
2025-08-01 12:45:42 +02:00
parent f063aadcd8
commit c2930b3232
43 changed files with 624 additions and 2 deletions

16
upnp/devices/device.go Normal file
View File

@@ -0,0 +1,16 @@
package devices
type Device struct {
deviceType string
friendlyName string
manufacturer string
manufacturerURL string
modelDescription string
modelName string
modelNumber string
modelURL string
serialNumber string
specVersion string
presentationURL string
UDN string
}

View File

@@ -0,0 +1,17 @@
package mediarenderer
import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services"
var AVTransport = func() *services.Service {
svc := services.NewService("AVTransport")
svc.AddVariable(AVTransportURI)
svc.AddVariable(AVTransportURIMetaData)
svc.AddVariable(CurrentTrackDuration)
svc.AddVariable(SeekMode)
svc.AddVariable(TransportPlaySpeed)
svc.AddVariable(TransportState)
svc.AddVariable(TransportStatus)
return svc
}()

View File

@@ -0,0 +1,10 @@
package mediarenderer
import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
var AVTransportURI = func() *sv.StateVariable {
ts := sv.StateType_String.NewStateValue("AVTransportURI")
return ts
}()

View File

@@ -0,0 +1,10 @@
package mediarenderer
import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
var AVTransportURIMetaData = func() *sv.StateVariable {
ts := sv.StateType_String.NewStateValue("AVTransportURIMetaData")
return ts
}()

View File

@@ -0,0 +1,10 @@
package mediarenderer
import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
var CurrentTrackDuration = func() *sv.StateVariable {
ts := sv.StateType_String.NewStateValue("CurrentTrackDuration")
return ts
}()

View File

@@ -0,0 +1,10 @@
package mediarenderer
import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
var SeekMode = func() *sv.StateVariable {
ts := sv.StateType_String.NewStateValue("SeekMode")
return ts
}()

View File

@@ -0,0 +1,10 @@
package mediarenderer
import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
var TransportPlaySpeed = func() *sv.StateVariable {
ts := sv.StateType_String.NewStateValue("TransportPlaySpeed")
return ts
}()

View File

@@ -0,0 +1,20 @@
package mediarenderer
import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
var TransportState = func() *sv.StateVariable {
ts := sv.StateType_String.NewStateValue("TransportState")
ts.SetAllowedValues(
"STOPPED",
"PLAYING",
"PAUSED_PLAYBACK",
"TRANSITIONING",
"NO_MEDIA_PRESENT",
)
ts.SetSendingEvents()
return ts
}()

View File

@@ -0,0 +1,17 @@
package mediarenderer
import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
var TransportStatus = func() *sv.StateVariable {
ts := sv.StateType_String.NewStateValue("TransportStatus")
ts.SetAllowedValues(
"OK",
"ERROR_OCCURRED",
)
ts.SetSendingEvents()
return ts
}()

View File

@@ -0,0 +1 @@
package connectionmanager

View File

@@ -0,0 +1,39 @@
package actions
import "github.com/beevik/etree"
type Action struct {
name string
arguments ArgumentSet
}
func NewAction(name string) *Action {
ac := &Action{
name: name,
}
return ac
}
func (a *Action) Name() string {
return a.name
}
func (a *Action) TypeID() string {
return "Action"
}
func (a *Action) AddArgument(arg *Argument) {
a.arguments.Insert(arg)
}
func (a *Action) ToXMLElement() *etree.Element {
elem := etree.NewElement("action")
name := elem.CreateElement("name")
name.SetText(a.Name())
elem.AddChild(a.arguments.ToXMLElement())
return elem
}

View File

@@ -0,0 +1,32 @@
package actions
import (
"iter"
"gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore"
"github.com/beevik/etree"
)
type ActionSet objectstore.ObjectSet[*Action]
func (m *ActionSet) Insert(obj *Action) {
(*objectstore.ObjectSet[*Action])(m).Insert(obj)
}
func (set *ActionSet) Contains(obj *Action) bool {
return (*objectstore.ObjectSet[*Action])(set).Contains(obj)
}
func (m *ActionSet) All() iter.Seq[*Action] {
return (*objectstore.ObjectSet[*Action])(m).All()
}
func (m *ActionSet) ToXMLElement() *etree.Element {
elem := etree.NewElement("ActionList")
for sv := range m.All() {
elem.AddChild(sv.ToXMLElement())
}
return elem
}

View File

@@ -0,0 +1,100 @@
package actions
import (
"gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
"github.com/beevik/etree"
)
type Argument struct {
name string
statevariables *statevariables.StateVariable
in bool
out bool
}
func newArgument(name string, statevariable *statevariables.StateVariable) *Argument {
arg := &Argument{
name: name,
statevariables: statevariable,
}
return arg
}
func NewInArgument(name string, statevariable *statevariables.StateVariable) *Argument {
arg := newArgument(name, statevariable)
arg.in = true
return arg
}
func NewOutArgument(name string, statevariable *statevariables.StateVariable) *Argument {
arg := newArgument(name, statevariable)
arg.out = true
return arg
}
func NewInOutArgument(name string, statevariable *statevariables.StateVariable) *Argument {
arg := newArgument(name, statevariable)
arg.in = true
arg.out = true
return arg
}
func (a *Argument) Name() string {
return a.name
}
func (sv Argument) TypeID() string {
return "Argument"
}
func (a *Argument) StateVariable() *statevariables.StateVariable {
return a.statevariables
}
func (a *Argument) IsIn() bool {
return a.in
}
func (a *Argument) IsOut() bool {
return a.out
}
func (a *Argument) ToXMLElement() *etree.Element {
var elem, arg *etree.Element
if a.IsIn() && a.IsOut() {
elem = etree.NewElement("")
arg = elem.CreateElement("argument")
} else {
elem = etree.NewElement("argument")
arg = elem
}
if a.IsIn() {
name := arg.CreateElement("name")
name.SetText(a.Name())
direction := arg.CreateElement("direction")
direction.SetText("in")
relatedStateVariable := arg.CreateElement("relatedStateVariable")
relatedStateVariable.SetText(a.StateVariable().Name())
}
if a.IsIn() && a.IsOut() {
arg = elem.CreateElement("argument")
}
if a.IsOut() {
name := arg.CreateElement("name")
name.SetText(a.Name())
direction := arg.CreateElement("direction")
direction.SetText("out")
relatedStateVariable := arg.CreateElement("relatedStateVariable")
relatedStateVariable.SetText(a.StateVariable().Name())
}
return elem
}

View File

@@ -0,0 +1,32 @@
package actions
import (
"iter"
"gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore"
"github.com/beevik/etree"
)
type ArgumentSet objectstore.ObjectSet[*Argument]
func (m *ArgumentSet) Insert(obj *Argument) {
(*objectstore.ObjectSet[*Argument])(m).Insert(obj)
}
func (set *ArgumentSet) Contains(obj *Argument) bool {
return (*objectstore.ObjectSet[*Argument])(set).Contains(obj)
}
func (m *ArgumentSet) All() iter.Seq[*Argument] {
return (*objectstore.ObjectSet[*Argument])(m).All()
}
func (m *ArgumentSet) ToXMLElement() *etree.Element {
elem := etree.NewElement("argumentList")
for sv := range m.All() {
elem.AddChild(sv.ToXMLElement())
}
return elem
}

View File

@@ -0,0 +1,129 @@
package services
import (
"fmt"
"iter"
"github.com/beevik/etree"
"gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions"
sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
)
type Service struct {
name string
identifier string
version int
controlURL string
eventSubURL string
scpdURL string
actions actions.ActionSet
stateTable sv.StateVariableSet
}
func NewService(name string) *Service {
svc := &Service{
name: name,
identifier: name,
controlURL: "/service/" + name + "/control",
eventSubURL: "/service/" + name + "/event",
scpdURL: "/service/" + name + "/desc.xml",
version: 1,
}
return svc
}
func (svc *Service) Name() string {
return svc.name
}
func (svc *Service) TypeID() string {
return "Service"
}
func (svc *Service) ServiceType() string {
return fmt.Sprintf("urn:schemas-upnp-org:service:%s:%d", svc.name, svc.version)
}
func (svc *Service) ServiceId() string {
return fmt.Sprintf("urn:upnp-org:serviceId:%s", svc.identifier)
}
func (svc *Service) SetIdentifier(id string) {
svc.identifier = id
}
func (svc *Service) ControlURL() string {
return svc.controlURL
}
func (svc *Service) SetControlURL(url string) {
svc.controlURL = url
}
func (svc *Service) EventSubURL() string {
return svc.eventSubURL
}
func (svc *Service) SetEventSubURL(url string) {
svc.eventSubURL = url
}
func (svc *Service) SCPDURL() string {
return svc.scpdURL
}
func (svc *Service) SetSCPDURL(url string) {
svc.scpdURL = url
}
func (svc *Service) SetVersion(version int) error {
if version < 1 {
return fmt.Errorf("%s", "version must be greater than or equal to 1")
}
svc.version = version
return nil
}
func (svc *Service) Version() int {
return svc.version
}
func (svc *Service) AddVariable(sv *sv.StateVariable) {
svc.stateTable.Insert(sv)
}
func (svc *Service) ContaintsVariable(sv *sv.StateVariable) bool {
return svc.stateTable.Contains(sv)
}
func (svc *Service) Variables() iter.Seq[*sv.StateVariable] {
return svc.stateTable.All()
}
func (svc *Service) AddAction(ac *actions.Action) {
}
func (svc *Service) ToXMLElement() *etree.Element {
elem := etree.NewElement("service")
st := elem.CreateElement("serviceType")
st.SetText(svc.ServiceType())
sid := elem.CreateElement("serviceId")
sid.SetText(svc.ServiceId())
spcd := elem.CreateElement("SCPDURL")
spcd.SetText(svc.SCPDURL())
ctrl := elem.CreateElement("controlURL")
ctrl.SetText(svc.ControlURL())
event := elem.CreateElement("eventSubURL")
event.SetText(svc.EventSubURL())
return elem
}

View File

@@ -0,0 +1,32 @@
package services
import (
"iter"
"gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore"
"github.com/beevik/etree"
)
type ServiceSet objectstore.ObjectSet[*Service]
func (m *ServiceSet) Insert(obj *Service) {
(*objectstore.ObjectSet[*Service])(m).Insert(obj)
}
func (set *ServiceSet) Contains(obj *Service) bool {
return (*objectstore.ObjectSet[*Service])(set).Contains(obj)
}
func (m *ServiceSet) All() iter.Seq[*Service] {
return (*objectstore.ObjectSet[*Service])(m).All()
}
func (m *ServiceSet) ToXMLElement() *etree.Element {
elem := etree.NewElement("ServiceList")
for sv := range m.All() {
elem.AddChild(sv.ToXMLElement())
}
return elem
}

View File

@@ -49,6 +49,10 @@ func (sv StateVariable) Name() string {
return sv.name
}
func (sv StateVariable) TypeID() string {
return "StateVariable"
}
// Type returns the UPnP data type of the state variable.
func (state *StateVariable) Type() StateVarType {
return state.valueType
@@ -431,7 +435,6 @@ func (state *StateVariable) NewInstance() *StateVarInstance {
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 {
@@ -440,6 +443,9 @@ func (sv *StateVariable) ToXMLElement() *etree.Element {
elem.CreateAttr("sendEvents", "no") // Disable event notifications
}
name := elem.CreateElement("name")
name.SetText(sv.Name())
// Add data type element
dataType := elem.CreateElement("dataType")
dataType.SetText(sv.valueType.String()) // Set UPnP type name (e.g., "ui1", "boolean")

View File

@@ -0,0 +1,32 @@
package statevariables
import (
"iter"
"gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore"
"github.com/beevik/etree"
)
type StateVariableSet objectstore.ObjectSet[*StateVariable]
func (m *StateVariableSet) Insert(obj *StateVariable) {
(*objectstore.ObjectSet[*StateVariable])(m).Insert(obj)
}
func (set *StateVariableSet) Contains(obj *StateVariable) bool {
return (*objectstore.ObjectSet[*StateVariable])(set).Contains(obj)
}
func (m *StateVariableSet) All() iter.Seq[*StateVariable] {
return (*objectstore.ObjectSet[*StateVariable])(m).All()
}
func (m *StateVariableSet) ToXMLElement() *etree.Element {
elem := etree.NewElement("serviceStateTable")
for sv := range m.All() {
elem.AddChild(sv.ToXMLElement())
}
return elem
}

View File

@@ -55,7 +55,7 @@ func toTime(v interface{}) (time.Time, error) {
func parseUPnPTime(t StateVarType, s string) (time.Time, error) {
s = strings.TrimSpace(s)
layouts := []string{}
var layouts []string = nil
switch t {
case StateType_Date:
@@ -86,6 +86,7 @@ func parseUPnPTime(t StateVarType, s string) (time.Time, error) {
return ts, nil
}
}
return time.Time{}, fmt.Errorf("invalid %v value: %q", t, s)
}

View File

@@ -0,0 +1,69 @@
package objectstore
import (
"iter"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)
type ObjectIdSet map[uuid.UUID]struct{}
func (set ObjectIdSet) Contains(id uuid.UUID) bool {
_, ok := set[id]
return ok
}
func (set ObjectIdSet) Add(id uuid.UUID) {
set[id] = struct{}{}
}
func (set ObjectIdSet) Remove(id uuid.UUID) {
delete(set, id)
}
func (set ObjectIdSet) Clear() {
for k := range set {
delete(set, k)
}
}
func (set ObjectIdSet) Len() int {
return len(set)
}
func (set ObjectIdSet) All() iter.Seq[uuid.UUID] {
return func(yield func(uuid.UUID) bool) {
for uuid := range set {
if !yield(uuid) {
return
}
}
}
}
var objectstore = make(map[uuid.UUID]Object)
func RegisterObject(o Object) uuid.UUID {
id := uuid.New()
objectstore[id] = o
return id
}
func GetObject(id uuid.UUID) (Object, bool) {
o, ok := objectstore[id]
return o, ok
}
func RemoveObject(id uuid.UUID) {
if _, ok := objectstore[id]; !ok {
delete(objectstore, id)
return
}
log.Warnf("Object %s not found in objectstore", id)
}
func CountOfStoredObjects() int {
return len(objectstore)
}

View File

@@ -0,0 +1,29 @@
package objectstore
import "iter"
type Object interface {
Name() string
TypeID() string
}
type ObjectSet[T Object] map[string]T
func (m *ObjectSet[T]) Insert(obj T) {
(*m)[obj.Name()] = obj
}
func (set *ObjectSet[T]) Contains(obj T) bool {
_, ok := (*set)[obj.Name()]
return ok
}
func (m *ObjectSet[T]) All() iter.Seq[T] {
return func(yield func(T) bool) {
for _, sv := range *m {
if !yield(sv) {
return
}
}
}
}