Added scribbles
This commit is contained in:
@@ -12,4 +12,9 @@
|
|||||||
"simple-log"
|
"simple-log"
|
||||||
))
|
))
|
||||||
(define build-deps
|
(define build-deps
|
||||||
'("rackunit-lib"))
|
'("racket-doc"
|
||||||
|
"rackunit-lib"
|
||||||
|
"scribble-lib"))
|
||||||
|
|
||||||
|
(define scribblings
|
||||||
|
'(("scribblings/racket-sonos.scrbl" () (library))))
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
#lang scribble/manual
|
||||||
|
|
||||||
|
@(require
|
||||||
|
(for-label
|
||||||
|
racket/base
|
||||||
|
racket/contract
|
||||||
|
racket/list
|
||||||
|
racket-sonos
|
||||||
|
racket-upnp))
|
||||||
|
|
||||||
|
@title{racket-sonos}
|
||||||
|
@author{Hans Dijkema}
|
||||||
|
|
||||||
|
@defmodule[racket-sonos]
|
||||||
|
|
||||||
|
The @racketmodname[racket-sonos] library adds Sonos topology support to
|
||||||
|
@racketmodname[racket-upnp]. It recognises Sonos and IKEA SYMFONISK devices
|
||||||
|
and turns the vendor-specific zone-group topology into logical media
|
||||||
|
renderers. A logical renderer can represent a single speaker, a stereo pair,
|
||||||
|
a bonded home-theatre setup, or a group of rooms.
|
||||||
|
|
||||||
|
The library does not perform device discovery itself. Use
|
||||||
|
@racket[query-upnp-devices] from @racketmodname[racket-upnp], then pass the
|
||||||
|
complete discovery result to @racket[sonos-groups].
|
||||||
|
|
||||||
|
@section{Device helpers}
|
||||||
|
|
||||||
|
@defproc[(sonos-device? [device any/c]) boolean?]{
|
||||||
|
|
||||||
|
Returns a true value when @racket[device] is a UPnP device that advertises a
|
||||||
|
@tt{ZoneGroupTopology} service, identifies its manufacturer as Sonos, or has a
|
||||||
|
model name containing @litchar{SYMFONISK}. The textual comparisons are
|
||||||
|
case-insensitive.
|
||||||
|
|
||||||
|
Returns @racket[#f] for values that are not UPnP devices.
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(sonos-device-name [device sonos-device?]) string?]{
|
||||||
|
|
||||||
|
Returns the friendly room name of @racket[device]. For the common names
|
||||||
|
@litchar{Living Room - Sonos One} and
|
||||||
|
@litchar{Kitchen - SYMFONISK Bookshelf}, this function returns
|
||||||
|
@litchar{Living Room} and @litchar{Kitchen}, respectively. Other friendly
|
||||||
|
names are returned unchanged.
|
||||||
|
|
||||||
|
Raises @racket[exn:fail:contract?] when @racket[device] is not recognised by
|
||||||
|
@racket[sonos-device?].
|
||||||
|
}
|
||||||
|
|
||||||
|
@section{Reading the Sonos topology}
|
||||||
|
|
||||||
|
@defproc[(sonos-groups [devices (listof upnp-device?)])
|
||||||
|
(listof sonos-group?)]{
|
||||||
|
|
||||||
|
Finds the first @tt{ZoneGroupTopology} service among @racket[devices], invokes
|
||||||
|
its @tt{GetZoneGroupState} action, and returns the logical Sonos groups in the
|
||||||
|
response.
|
||||||
|
|
||||||
|
Pass the complete result of @racket[(query-upnp-devices 'all)]. In particular,
|
||||||
|
@racket[devices] must contain the UPnP media-renderer device for every group
|
||||||
|
coordinator that should be returned. A topology group whose coordinator cannot
|
||||||
|
be matched to a media renderer is omitted and a warning is logged.
|
||||||
|
|
||||||
|
Returns the empty list when none of the supplied devices provides the topology
|
||||||
|
service or when the service returns an empty state. Network and SOAP failures
|
||||||
|
from @racket[upnp-service-call], and malformed topology XML, are reported as
|
||||||
|
exceptions.
|
||||||
|
}
|
||||||
|
|
||||||
|
@section{Group values}
|
||||||
|
|
||||||
|
@defstruct[sonos-group
|
||||||
|
([id (or/c #f string?)]
|
||||||
|
[name string?]
|
||||||
|
[coordinator-id (or/c #f string?)]
|
||||||
|
[member-ids (listof string?)]
|
||||||
|
[bonded? boolean?]
|
||||||
|
[stereo? boolean?]
|
||||||
|
[grouped? boolean?]
|
||||||
|
[renderer media-renderer?])
|
||||||
|
#:transparent]{
|
||||||
|
|
||||||
|
Represents one logical renderer from the Sonos zone-group topology. Values
|
||||||
|
returned by @racket[sonos-groups] have the following fields:
|
||||||
|
|
||||||
|
@itemlist[
|
||||||
|
@item{@racket[id] is the topology's group identifier. It falls back to the
|
||||||
|
coordinator identifier when the topology does not supply one.}
|
||||||
|
@item{@racket[name] combines the distinct visible room names with
|
||||||
|
@litchar{ + }. The suffix @litchar{ (stereo)} or
|
||||||
|
@litchar{ (bonded)} describes bonded configurations.}
|
||||||
|
@item{@racket[coordinator-id] is the normalised UDN of the device that
|
||||||
|
coordinates playback. A leading @litchar{uuid:} and any suffix after a
|
||||||
|
Sonos @litchar{RINCON_} identifier are removed.}
|
||||||
|
@item{@racket[member-ids] contains the normalised identifiers of all members,
|
||||||
|
including invisible bonded members.}
|
||||||
|
@item{@racket[bonded?] is true for stereo pairs and other bonded arrangements,
|
||||||
|
such as a soundbar with satellites.}
|
||||||
|
@item{@racket[stereo?] is true when the channel map contains both the left and
|
||||||
|
right front channels.}
|
||||||
|
@item{@racket[grouped?] is true when more than one visible member belongs to
|
||||||
|
the group. A bonded setup with only one visible room is therefore not
|
||||||
|
necessarily grouped.}
|
||||||
|
@item{@racket[renderer] is the coordinator's @racket[media-renderer?] value.
|
||||||
|
Supply this value to the playback operations provided by
|
||||||
|
@racketmodname[racket-upnp].}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
@section{Example}
|
||||||
|
|
||||||
|
This example discovers all UPnP devices, reads the Sonos topology, and prints
|
||||||
|
the logical renderers:
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
(require racket-sonos
|
||||||
|
racket-upnp)
|
||||||
|
|
||||||
|
(define devices
|
||||||
|
(query-upnp-devices 'all))
|
||||||
|
|
||||||
|
(for ([group (in-list (sonos-groups devices))])
|
||||||
|
(printf "~a: ~a member(s)~a~n"
|
||||||
|
(sonos-group-name group)
|
||||||
|
(length (sonos-group-member-ids group))
|
||||||
|
(if (sonos-group-grouped? group)
|
||||||
|
" (grouped)"
|
||||||
|
"")))
|
||||||
|
]
|
||||||
|
|
||||||
|
To control a group, use its coordinator renderer. For example:
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
(define groups
|
||||||
|
(sonos-groups devices))
|
||||||
|
|
||||||
|
(unless (null? groups)
|
||||||
|
(media-renderer-play-uri!
|
||||||
|
(sonos-group-renderer (first groups))
|
||||||
|
"http://192.0.2.10:8080/audio/example.mp3"))
|
||||||
|
]
|
||||||
|
|
||||||
|
@index-section[]
|
||||||
Reference in New Issue
Block a user