IP controlled relay board

If you have a question or need help, this is the place to be.
Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Fri May 15, 2020 6:48 am

Once again sincere thanks.

When I started trying to control Sonos from Demopad I found some examples on the Demopad forum, and using those plus trapping the data in/out of the Sonos box with WireShark I was able to get pretty good control.

The Browse command that I'm using has three arguments of interest -

<Filter>dc:title,dc:creator</Filter>
<StartingIndex>5</StartingIndex>
<RequestedCount>10</RequestedCount>

The first requests the track title and artist, the second is the queue location of the first track to return, and the third is the number of tracks to return.

In Demopad I have a variable called "browse" which is the start location and this forms part of the event in EG. Assume browse = 5 as shown above. At first I thought the event would be udp.5 and I could use udp.* for the macro, but I figured this would interfere with the udp.* that I'm using for the mouse movement, so I decided to use udp.<StartingIndex>5</StartingIndex>. This is all my clumsy way of getting the start location from Demopad to EG.

Ultimately I'd like the data in the following format for passing back to Demopad -

T1=track1:A1=artist1:T2=track2:A2=artist2 ...... :T10=track10:A10=artist10

I expect the regex in Demopad will be able to cope with this OK.

Will try your code later tonight.

Cheers,
Peter

User avatar
kgschlosser
Site Admin
Posts: 5395
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: IP controlled relay board

Post by kgschlosser » Fri May 15, 2020 6:52 am

ok so here is the code to parse the response data. I used to data you posted in an earlier post to make this.
It should work with just about any response information you use it on with only minor changes needed.

I commented the hell out of the script so that you can understand what is happening. I am going to take a quick peek at the broadcaster plugin
for some reason I think there is a way to get it to produce nicer events.

Code: Select all

from xml.etree import ElementTree

# This is the example response you provided in your post.
data = """\
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:BrowseResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<Result>
&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/
&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;

&lt;item id=&quot;Q:0/1&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/01%20-%20Everything%20That%20Rises.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f01%2520-
%2520Everything%2520That%2520Rises.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Everything That
 Rises&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;1&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/2&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/02%20-%20A%20Case%20for%20Shame.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f02%2520-
%2520A%2520Case%2520for%2520Shame.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;A Case for
 Shame&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;2&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/3&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/03%20-%20Almost%20Home.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f03%2520-
%2520Almost%2520Home.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Almost
 Home&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;3&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/4&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/04%20-%20Going%20Wrong.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f04%2520-
%2520Going%2520Wrong.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Going
 Wrong&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;4&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/5&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/05%20-%20The%20Perfect%20Life.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f05%2520-
%2520The%2520Perfect%2520Life.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;The Perfect
 Life&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;5&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/6&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/06%20-%20The%20Last%20Day.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f06%2520-
%2520The%2520Last%2520Day.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;The Last
 Day&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;6&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;/DIDL-Lite&gt;
</Result>
<NumberReturned>6</NumberReturned>
<TotalMatches>28</TotalMatches>
<UpdateID>6</UpdateID>
</u:BrowseResponse>
</s:Body>
</s:Envelope>
"""

# because name spaces make the processes of dealing with
# XML a really large annoyance and add a bunch of complexity
# to the code needed I have opted to strip the namespace
# information out of the string xml because sending the
# string over to ElementTree to be parsed.
# this function strips that namespace information in a manner
# that does not cause problems with the parsing
def strip_namespaces(in_data):
    lines = []
    line = ''

    for char in list(in_data):
        if char == '>':
            line += char
            lines += [line]
            line = ''
            continue
        elif char == '<' and line:
            lines += [line]
            line = ''

        line += char

    if line:
        lines += [line]

    for i, line in enumerate(lines):
        if '<' not in line:
            continue

        while 'xmlns=' in line:
            beg, end = line.split('xmlns=', 1)
            end = end.split('"', 2)[-1].strip()
            line = beg + end

        while 'xmlns:' in line:
            beg, end = line.split('xmlns:', 1)
            xmlns, end = end.split('=', 1)
            end = end.split('"', 2)[-1].strip()
            line = beg + end

            while xmlns + ':encodingStyle=' in line:
                beg, end = line.split(xmlns + ':encodingStyle=', 1)
                end = end.split('"', 2)[-1].strip()
                line = beg + end

            lines[i] = line

            for j, lne in enumerate(lines[i:]):
                if '<' not in lne:
                    continue
                lne = lne.replace('<' + xmlns + ':', '<')
                lne = lne.replace('</' + xmlns + ':', '</')

                lines[i + j] = lne

    out_data = ''.join(lines)
    return ''.join(line for line in out_data.split('\n'))


# ok so we strip the namespace data here
data = strip_namespaces(data)

# then we create an xml object from the stripped data
xml = ElementTree.fromstring(data)

# then we locate the elements that we are looking for.
body = xml.find('Body')
browse_response = body.find('BrowseResponse')
result = browse_response.find('Result')

# The data we are looking for is stored in the text area of
# the Result field. The data automatically gets unescaped when
# we collect the data so we do not have a need to do any of
# the replacements I mentioned in a previous post it gets done
# for us.
data = result.text

# now we want to strip the namespaces from the result data
data = strip_namespaces(data)
# then we turn the result data into an xml object
xml = ElementTree.fromstring(data)


# we can do as we did above and target specific elements
# using the find method. I created a function that will
# iterate over all of the elements recursively and print
# out the elements tag (name) and also the information
# stored in that elements text field.
print '------------------- Iterating over all of the data --------------------'
print
def iter_nodes(parent, indent=0):
    if parent.text:
        print ' ' * indent, parent.tag + ':', parent.text
    else:
        print ' ' * indent, parent.tag

    for child in parent:
        iter_nodes(child, indent + 3)

iter_nodes(xml)

print '\n\n'
print '--------------------- Accessing the data directly ---------------------'
print
# here is an alternate way to iterate over the results.
# the root or lowest element element comes "pre selected"
# in this case it's the DIDL-Lite element.
for item in xml:
    print 'res:', item.find('res').text
    print 'albumArtURI:', item.find('albumArtURI').text
    print 'title:', item.find('title').text
    print 'class:', item.find('class').text
    print 'creator:', item.find('creator').text
    print 'album:', item.find('album').text
    print 'narrator:', item.find('narrator').text
    print '\n'
If you like the work I have been doing then feel free to Image

User avatar
kgschlosser
Site Admin
Posts: 5395
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: IP controlled relay board

Post by kgschlosser » Fri May 15, 2020 7:13 am

ahh see i knew there was a way.

in the broadcaster plugin there is a payload delimiter.
it is defaulted to &&

so this is the code that is used in the broadcaster plugin when it generated the events.

Code: Select all

bits = data.split(self.payDelim);

            commandSize=len(bits)
            if commandSize==1:
                self.plugin.TriggerEvent(bits[0])
            if commandSize==2:
                self.plugin.TriggerEvent(bits[0],bits[1])
            if commandSize>2:
                self.plugin.TriggerEvent(bits[0],bits[1:])
so you have control over what the suffix is and what the payload data is as well.

so if you send data that looks like this over UDP

some_event_suffix&&some_payload_data

you are going to get an event of udp.some_event_suffix and the payload is going to be 'some_payload_data'

Now this is where you can change the mouse up some. I can alter the script I made for you to accommodate this in an easy fashion.
In the broadcaster plugin you have the ability to change the event prefix. You should change this to DemoPad

on the demo pad change the udp broadcast to the following for the mouse movements.
Mouse.Movement&&{x}&&{y}
replacing {x} and {y} with whatever is used on the demopad to get the coordinates.

this is going to create an event
DemoPad.Mouse.Movement

with the payload of [x, y]
where x and y are the coordinates.

You can do something similiar for the pressing of hard buttons for mouse clicks.
Mouse.Button.{button name}

where {button name} would be RightClick pr LeftClick or something along those lines.


Now for the purposes of this script we are working on now you would use
DemoPad.Browse.Query&&StartingIndex={index}
where {index} is the starting index

you will get an event of
DemoPad.Browse.Query
and a payload of
StartingIndex={index}

we can access the payload of an event from a script using eg.event.payload.

so with the mouse movements we would use

Code: Select all

x, y = eg.event.payload
and for this script we would use

Code: Select all

starting_index = eg.event.payload.split('=')[-1]
There would need to be a change made to the test script i posted above to make the query to your media server.
The code is below that will facilitate that.

Code: Select all

import requests

data = '''\
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
            <ObjectID>Q:0</ObjectID>
            <BrowseFlag>BrowseDirectChildren</BrowseFlag>
            <Filter>dc:title,dc:creator</Filter>
             <StartingIndex>{starting_index}</StartingIndex>
            <RequestedCount>10</RequestedCount>
            <SortCriteria></SortCriteria>
        </u:Browse>
    </s:Body>
</s:Envelope>
'''

starting_index = eg.event.payload.split('=')[-1]
data = data.format(starting_index=starting_index )


headers = {
    'Content-Type': 'text/html; charset=UTF-8',
    'SOAPACTION': 'urn:schemas-upnp-org:service:ContentDirectory:1#Browse',
    'Content-Length': len(data)
}

response = requests.post('http://192.168.0.32:1400/MediaServer/ContentDirectory/Control', data=data, headers=headers)

print repr(response.content)
If you like the work I have been doing then feel free to Image

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Fri May 15, 2020 8:30 am

OK. Here's the response to the Browse command copied and pasted out of the EG log -

'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encodin ... seResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1"><Result>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;&lt;item id=&quot;Q:0/6&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Ana%20Christensen/Brave%20New%20World/04%20-%20Isolate%20Your%20Heart.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fAna%2520Christensen%2fBrave%2520New%2520World%2f04%2520-%2520Isolate%2520Your%2520Heart.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Isolate Your Heart&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Ana Christensen&lt;/dc:creator&gt;&lt;upnp:album&gt;Brave New World&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;4&lt;/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Ana Christensen&lt;/r:narrator&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/7&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Anastacia/Not%20That%20Kind/01%20-%20Not%20That%20Kind.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fAnastacia%2fNot%2520That%2520Kind%2f01%2520-%2520Not%2520That%2520Kind.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Not That Kind&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Anastacia&lt;/dc:creator&gt;&lt;upnp:album&gt;Not That Kind&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;1&lt;/upnp:originalTrackNumber&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/8&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Anastacia/Not%20That%20Kind/02%20-%20I&amp;apos;m%20Outta%20Love.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fAnastacia%2fNot%2520That%2520Kind%2f02%2520-%2520I&amp;apos;m%2520Outta%2520Love.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;I&amp;apos;m Outta Love&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Anastacia&lt;/dc:creator&gt;&lt;upnp:album&gt;Not That Kind&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;2&lt;/upnp:originalTrackNumber&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/9&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Audio%20Shaman/Cityzen/05%20-%20Goenkaji.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fAudio%2520Shaman%2fCityzen%2f05%2520-%2520Goenkaji.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Goenkaji&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Audio Shaman&lt;/dc:creator&gt;&lt;upnp:album&gt;Cityzen&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;5&lt;/upnp:originalTrackNumber&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/10&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Australian%20Crawl/Crawl%20File/12%20-%20Reckless%20(Don&amp;apos;t%20You%20Be%20So...).flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fAustralian%2520Crawl%2fCrawl%2520File%2f12%2520-%2520Reckless%2520(Don&amp;apos;t%2520You%2520Be%2520So...).flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Reckless (Don&amp;apos;t You Be So...)&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Australian Crawl&lt;/dc:creator&gt;&lt;upnp:album&gt;Crawl File&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;12&lt;/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;James Reyne&lt;/r:narrator&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/11&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Bill%20Withers/Lovely%20Days/01%20-%20Lovely%20Day.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fBill%2520Withers%2fLovely%2520Days%2f01%2520-%2520Lovely%2520Day.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Lovely Day&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Bill Withers&lt;/dc:creator&gt;&lt;upnp:album&gt;Lovely Days&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;1&lt;/upnp:originalTrackNumber&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/12&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Billy%20Joel/52nd%20Street/08%20-%20Until%20the%20Night.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fBilly%2520Joel%2f52nd%2520Street%2f08%2520-%2520Until%2520the%2520Night.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Until the Night&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Billy Joel&lt;/dc:creator&gt;&lt;upnp:album&gt;52nd Street&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;8&lt;/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Billy Joel&lt;/r:narrator&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/13&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Boston/Boston/01%20-%20More%20Than%20a%20Feeling.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fBoston%2fBoston%2f01%2520-%2520More%2520Than%2520a%2520Feeling.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;More Than a Feeling&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Boston&lt;/dc:creator&gt;&lt;upnp:album&gt;Boston&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;1&lt;/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Tom Scholz&lt;/r:narrator&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/14&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Boy%20Meets%20Girl/Reel%20Life/02%20-%20Waiting%20for%20a%20Star%20to%20Fall.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fBoy%2520Meets%2520Girl%2fReel%2520Life%2f02%2520-%2520Waiting%2520for%2520a%2520Star%2520to%2520Fall.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Waiting for a Star to Fall&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Boy Meets Girl&lt;/dc:creator&gt;&lt;upnp:album&gt;Reel Life&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;2&lt;/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Shannon Rubicam&lt;/r:narrator&gt;&lt;/item&gt;&lt;item id=&quot;Q:0/15&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://XXXXNAS-1/Public/music/Boz%20Scaggs/Silk%20Degrees/06%20-%20Lowdown.flac&lt;/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fXXXXNAS-1%2fPublic%2fmusic%2fBoz%2520Scaggs%2fSilk%2520Degrees%2f06%2520-%2520Lowdown.flac&amp;amp;v=49&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Lowdown&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Boz Scaggs&lt;/dc:creator&gt;&lt;upnp:album&gt;Silk Degrees&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;6&lt;/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;David Paich&lt;/r:narrator&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</Result><NumberReturned>10</NumberReturned><TotalMatches>147</TotalMatches><UpdateID>7</UpdateID></u:BrowseResponse></s:Body></s:Envelope>'

Interestingly it appears to have returned more fields for each track than I've been getting with Demopad and I have no idea why.

Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Fri May 15, 2020 9:09 am

Brilliant !!!

So far I've done the following -

1. Added the parsing code, deleted the sample response and added in

Code: Select all

data=response.content
to link the two sections of code.

2. Made the changes in Demopad so that the event and payload are sent correctly.

Both of these methods are printing all the required fields; Iterating over all of the data; Accessing the data directly.

I should be able to figure out how to format and Broadcast out to Demopad so will have a go at that now.

Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Fri May 15, 2020 12:07 pm

All was working really well and I had the info going back to Demopad and then suddenly it was throwing up these errors -

Traceback (most recent call last):
Python script "31", line 118, in <module>
xml = ElementTree.fromstring(data)
File "xml\etree\ElementTree.pyc", line 963, in XML
File "xml\etree\ElementTree.pyc", line 1245, in feed
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 536: ordinal not in range(128)

Of course I assumed it was me until I searched on u'\xe9' and realised that it's an accented lower case e that was in an artists name !

Any way around this ?

Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Fri May 15, 2020 12:23 pm

This is what I've ended up with -

Code: Select all

import requests

data = '''\
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
            <ObjectID>Q:0</ObjectID>
            <BrowseFlag>BrowseDirectChildren</BrowseFlag>
            <Filter>dc:title,dc:creator</Filter>
             <StartingIndex>{starting_index}</StartingIndex>
            <RequestedCount>10</RequestedCount>
            <SortCriteria></SortCriteria>
        </u:Browse>
    </s:Body>
</s:Envelope>
'''

starting_index = eg.event.payload.split('=')[-1]
data = data.format(starting_index=starting_index )

headers = {
    'Content-Type': 'text/html; charset=UTF-8',
    'SOAPACTION': 'urn:schemas-upnp-org:service:ContentDirectory:1#Browse',
    'Content-Length': len(data)
}

response = requests.post('http://192.168.0.32:1400/MediaServer/ContentDirectory/Control', data=data, headers=headers)

from xml.etree import ElementTree

data = response.content

# this function strips namespace information
def strip_namespaces(in_data):
    lines = []
    line = ''

    for char in list(in_data):
        if char == '>':
            line += char
            lines += [line]
            line = ''
            continue
        elif char == '<' and line:
            lines += [line]
            line = ''

        line += char

    if line:
        lines += [line]

    for i, line in enumerate(lines):
        if '<' not in line:
            continue

        while 'xmlns=' in line:
            beg, end = line.split('xmlns=', 1)
            end = end.split('"', 2)[-1].strip()
            line = beg + end

        while 'xmlns:' in line:
            beg, end = line.split('xmlns:', 1)
            xmlns, end = end.split('=', 1)
            end = end.split('"', 2)[-1].strip()
            line = beg + end

            while xmlns + ':encodingStyle=' in line:
                beg, end = line.split(xmlns + ':encodingStyle=', 1)
                end = end.split('"', 2)[-1].strip()
                line = beg + end

            lines[i] = line

            for j, lne in enumerate(lines[i:]):
                if '<' not in lne:
                    continue
                lne = lne.replace('<' + xmlns + ':', '<')
                lne = lne.replace('</' + xmlns + ':', '</')

                lines[i + j] = lne

    out_data = ''.join(lines)
    return ''.join(line for line in out_data.split('\n'))

# strip the namespace data here
data = strip_namespaces(data)

# create an xml object from the stripped data
xml = ElementTree.fromstring(data)

# locate the elements that we are looking for
body = xml.find('Body')
browse_response = body.find('BrowseResponse')
result = browse_response.find('Result')

# data is stored in the text area of the result field
data = result.text

# strip the namespaces from the result data
data = strip_namespaces(data)

# turn the result data into an xml object
xml = ElementTree.fromstring(data)

track = 1
for item in xml:
#    print 'res:', item.find('res').text
#    print 'albumArtURI:', item.find('albumArtURI').text
#    print 'title',track,':', item.find('title').text
    message = 'title' + str(track) + ':' + item.find('title').text + ':'
#    print message
    eg.plugins.BroadcastListener.Broadcast(message, u'', 33339)
#    print 'class:', item.find('class').text
#    print 'artist',track,':', item.find('creator').text
    message = 'artist' + str(track) + ':' + item.find('creator').text + ':'
#    print message
    eg.plugins.BroadcastListener.Broadcast(message, u'', 33339)
#    print 'album:', item.find('album').text
#    print 'narrator:', item.find('narrator').text
#    print '\n'
    track = track + 1
I'm sure there's a more elegant way of inserting the queue numbers into the broadcast to Demopad, but this seems to be working OK.

Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sat May 16, 2020 6:04 am

A bit of searching on StackOverflow led me to this solution for the accented characters -

Code: Select all

xml = ElementTree.fromstring(data.encode('utf-8'))
All seems to be working now.

Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sat May 16, 2020 6:45 am

I've also got the changes to the mouse event and payload working.

The only addition was that after this -

Code: Select all

x, y = eg.event.payload
I needed this -

Code: Select all

x = int(x)
y = int(y)
Once again thanks very much for your help !

Cheers,
Peter

User avatar
kgschlosser
Site Admin
Posts: 5395
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: IP controlled relay board

Post by kgschlosser » Sat May 16, 2020 7:45 am

Cool deal. Nice that you figured out the decode problem. and also that you merged the 2 scripts together to get it working. I hope my detailed explanations and comments have helped you in your learning process.

Do you understand what is happening in the code you are using to get the browse results? I made sure that I commented a whole mess of it. The only part I didn't get into much detail on was the function that strips out the name spaces. It is far easier to do that then to have to deal with the name spaces using ElementTree.

I am glad to see that you have gotten this portion up and running. Most of the responses from your media server are going to be formatted in the same manner. You will have to change a single tag in order to access the returned data the BrowseResponse is the tag you will have to change
If you like the work I have been doing then feel free to Image

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sat May 16, 2020 8:29 am

Studying your code and trying to solve the little teething issues that arise is certainly helping me learn more about python. The hardest part is knowing where to start. Once I see you use a function or method, then Google provides a wealth of extra info. I feel confident that if I decide to extend my Sonos control the code I have now can form the foundation of new commands and feedback parsing.

Over time I can foresee more and more of my equipment control / feedback processing moving out of Demopad and into EG.

Cheers,
Peter

P.S. Welcome to AVS !

User avatar
kgschlosser
Site Admin
Posts: 5395
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: IP controlled relay board

Post by kgschlosser » Sat May 16, 2020 5:40 pm

Thanks. I was poking about and looking for information on the DemoPad. while it seems to a decent piece of software it's very limited in it's ability to handle incoming date. It needs to be paired with a control platform like what you are doing with EG. This gives the DemoPad the processing power that it needs. So you are on the right track with it. Ultimately web is the way to go for a UI. I am trying to simplify that whole process as well. Writing a framework is extremely complicated to do. I have a fairly decent framework written I need to write a GUI for it so that a user will have a drag and drop interface that will allow a user to add and remove components. This is the direction I am trying to go with EG and using this framework. The thing is it is going to really break API to use it.

Since you are starting to get more into writing python scripts I would suggest that you download and install PyCharm and also Python 2.7 This is going to make it far easier for you to understand what is happening. It offers code completion and intellisense which will provide you with type hinting and provides documentation on the different python builtin functions. it will speed up your learning process.
If you like the work I have been doing then feel free to Image

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sun May 17, 2020 4:44 am

FYI - this is my Sonos page from Demopad -
.
sonos page.png
.
Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Mon May 18, 2020 11:33 am

So I thought I'd fixed the problem with accented characters but not entirely it appears.

The original album that gave me problems had a composer name with accented e and my fix allowed the py code to run without errors. Note that I don't use the data in the composer field.

Now I've come across an artist with an accented character - Lesiëm

The code runs OK up to the point when it broadcasts to Demopad.

I added a print command to help debug and this is the contents of 'message' -

artist1:Lesiëm:

So the code is working OK up to the broadcast and then I get this -

Traceback (most recent call last):
Python script "53", line 110, in <module>
eg.plugins.BroadcastListener.Broadcast(message, u'', 33339)
File "C:\Program Files (x86)\EventGhost\plugins\Broadcaster\__init__.py", line 189, in __call__
res = self.bcastSend(mesg, payload,port)
File "C:\Program Files (x86)\EventGhost\plugins\Broadcaster\__init__.py", line 204, in bcastSend
UDPSock.sendto(eg.ParseString(eventString)+self.plugin.payDelim+eg.ParseString(payload),addr)
TypeError: sendto() takes exactly 3 arguments (2 given)

It appears that the accented character is creating problems within the Broadcaster plugin.

Cheers,
Peter

User avatar
kgschlosser
Site Admin
Posts: 5395
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: IP controlled relay board

Post by kgschlosser » Tue May 19, 2020 6:41 am

I need to see your whole script. I need to make a change to it to get it to cooperate and I do not know where that change is going to be exactly in your script.
If you like the work I have been doing then feel free to Image

Post Reply