# Racket UPnP module This directory contains a small, layered UPnP control-point implementation. The public interfaces deliberately hide SSDP, XML, SCPD and SOAP details unless the generic service escape hatch is used. ## Module layout ```text upnp/main.rkt generic public interface upnp/didl-lite.rkt DIDL-Lite metadata generation upnp/media-renderer.rkt high-level renderer operations upnp/media-server.rkt high-level MediaServer browser upnp/services/av-transport.rkt playback transport upnp/services/rendering-control.rkt volume and mute upnp/services/connection-manager.rkt protocol and connection information upnp/services/content-directory.rkt media-server browse and search upnp/ssdp.rkt low-level SSDP discovery upnp/device.rkt device-description parsing upnp/service.rkt SCPD inspection and SOAP calls ``` The collection can be required as `upnp` when the package root is on Racket's collection path. ## Generic device discovery ```racket #lang racket/base (require upnp) (define devices (query-upnp-devices '(media-renderer scanner) #:interface "10.7.3.118" #:dns? #t)) (for ([device (in-list devices)]) (printf "~a: ~a\n" (upnp-device-kind device) (upnp-device-name device))) ``` Known friendly device kinds are returned by: ```racket (upnp-device-kinds) ``` Without a kind, all described UPnP devices are returned: ```racket (query-upnp-devices) ``` Unknown vendor-specific device types are retained and classified as `'unknown`; their original URN remains available through `upnp-device-type`. ## Service inspection ```racket (define renderer (car (query-upnp-devices 'media-renderer))) (for ([service (in-list (upnp-device-services renderer))]) (printf "~a: ~a\n" (upnp-service-kind service) (upnp-service-actions service))) ``` `upnp-service-call` is the generic escape hatch for standard services that do not yet have a typed module and for manufacturer-specific services. ## One module per known service ```racket (require upnp/services/av-transport upnp/services/rendering-control) (define transport (device-av-transport renderer)) (define rendering (device-rendering-control renderer)) (av-transport-set-uri! transport "http://10.7.3.118:8080/media/test.flac") (av-transport-play! transport) (rendering-control-set-volume! rendering 30) ``` The currently implemented typed service modules are: - `upnp/services/av-transport` - `upnp/services/rendering-control` - `upnp/services/connection-manager` - `upnp/services/content-directory` A typed service value is still the generic `upnp-service` value. No extra wrapper objects or public constructors are needed. ## Media-server browser The higher media-server layer parses ContentDirectory DIDL-Lite into ordinary containers, items and resources: ```racket (require upnp/media-server) (define synology (car (query-media-servers #:interface "10.7.3.118"))) (define root (media-server-root synology)) (define music (findf (lambda (entry) (and (media-container? entry) (string=? (media-entry-title entry) "Muziek"))) root)) (define entries (media-container-children synology music)) (for ([entry (in-list entries)]) (printf "~a: ~a\n" (if (media-container? entry) 'container 'item) (media-entry-title entry))) ``` A media item can advertise several resources. Their URLs are available with `media-item-resources` and `media-resource-uri`. Containers are followed one level at a time; the published hierarchy can be a logical artist/album/genre view rather than the physical filesystem. ## Media-renderer convenience layer Normal playback code need not mention AVTransport or SOAP: ```racket (require upnp upnp/media-renderer) (define files (start-media-file-server "http://10.7.3.118:8080/media/" #:listen-ip "10.7.3.118")) (define uri (media-file-server-publish! files "C:/muziek/test.flac" "test.flac")) (define denon (car (query-media-renderers #:interface "10.7.3.118" #:dns? #t))) (media-renderer-play-uri! denon uri #:metadata (media-file-server-didl-lite files uri #:title "Test track" #:duration 639)) (media-renderer-seek! denon 120) (media-renderer-set-volume! denon 30) ``` `query-media-renderers` returns all UPnP MediaRenderers by default. Add `#:dlna-only? #t` to retain only devices advertising `X_DLNADOC`. For local files, `media-file-server-publish!` supplies a byte-range-capable HTTP URL. `media-file-server-didl-lite` generates matching DIDL-Lite metadata, including MIME type, file size, duration, and `DLNA.ORG_OP=01`. Some renderers, including certain Denon models, only advertise `Seek` after receiving this metadata. Generate and pass matching metadata for both the current URI and a preloaded next URI. ## Documentation and tests The Scribble manual is in `upnp/scribblings/upnp.scrbl`. The test file `upnp/tests/upnp-test.rkt` uses a local mock HTTP/SOAP server and covers service discovery from SCPD, SOAP calls, SOAP faults, AVTransport, RenderingControl, ConnectionManager, ContentDirectory and DIDL-Lite media server browsing.