Tuesday, August 2, 2016

Learn music for programmers with Sonci Pi - 03 - Rythm

Theory


Beat

One beat is basic time unit. One beat equals (most often) 1/4 note (because it's most common time measure in music).

Measure or Bar(line)

Measure or sometimes referred as bar is musical time unit which divides the song into larger units

Time signature

Time signature actually is telling us how many notes (the blue number) of and how long (the red number) we shall play. In example below it 3 quarter notes. if it would be 4/4 it would be 4 quarter notes, 2/2 would be 2 half notes, 4/8 would be 4 eights notes, etc.
Now as you can see in example below you don't have to play quarter notes, but if you summarize you shall get time corresponding to the time signature.
Take example of part between green lines(the end part). It's six 1/8 notes hence 6/8 = 3/4.

Rhythm

Rhythm is event repeating over time. Main beat and unstressed off beat or Accented back beat, repeating in pattern. To distinct them we use different drums, high and low, long and short beats.

Except using the traditional notation to understand and follow the rhythm, instead of linear display we can use circle, so one circle equals to one measure. Hence the measure with 3/4 time signature we can divide into 3 parts which each correspond to 1/4 note. (If tempo is 60 bpm then the full circle is 3 seconds).

In below section I show you few examples of rhythms, how they look on the "rhythm circle" and how we can write that in Sonic PI

Sonic PI


Waltz

Waltz use 3/4 time signature. Below we can see the basic simple waltz rhythm the first tone higher(main beat) next two lower(off and secondary beat).



It divides the rhythm circle below in to 3 equal parts each is for one 1/4 note.
In sonic code example below it corresponds to F and two D


Sonic Pi code:
# Welcome to Sonic Pi v2.10
use_bpm 90
use_synth :piano

#Let first define basic tones
D = 63
F = 66

#Basic rhythm
loop do
  play F
  sleep 1
  play D
  sleep 1
  play D
  sleep 1
end

Polka

Polka use 2/4 time signature. Below we can see it on rhythm circle. It's pretty easy one note (let it be E) starts on "12 o'clock" and last 1/4 as we have 2/4 = full circle, then 1/4 is one half of the circle so second note(let it be F) starts on "6 o'clock"


And here is the same in Sonic Pi

loop do
  play E
  sleep 1
  play F
  sleep 1
end

Let's try something little more interesting.


It's not hard we only need to play more things at once so lets do threads. It's easy syntax:
in_thread do
  commands
end

Complete Sonic PI code for polka rhythm above, the main rhythm(2 quarter F notes) are played in thread 1/8ths in "main" part:

# Welcome to Sonic Pi v2.10
use_bpm 90
use_synth :piano

#Let first define basic tones
C = 61
F = 66

#Basic rhythm
in_thread do
  loop do
    2.times do 
      play F
      sleep 1
    end
 end
end

loop do
  sleep 0.5
  play C+12
  sleep 0.5
  sleep 0.5
  play C+12
  sleep 0.5
end

Tango

Tango is in use 4/4 time signature. If we little simplify it's long, short, short, long. If you learn tango in dancing lesson you might remember the step pattern, slow, slow, quick, quick.

So here we go this one is pretty simple two long C to short E. (The dot behind the C means 1/8 longer)

Here it's on the rhythm circle



And here is the Sonic Pi code:
# Welcome to Sonic Pi v2.10
use_bpm 78
use_synth :piano
loop do
  play 61
  sleep 1
  sleep 0.5
  play 65
  sleep 0.5
  play 65
  sleep 1
  play 61
  sleep 1
end


Here is one little more complected as it's more changing the pitches but the timing is important (not the pitch)


You can try to write Sonic Pi code your self and then compare with my on github


Tuesday, July 26, 2016

Learn music for programmers with Sonci Pi - 02 - Random Chinese music generator

If you play just black keys on the piano in randomly you'll generate a Chinese music.

Pentatonic scale


Now this time it makes sense Penta tonic = 5 tones. Which is exactly what it is.
There are several pentatonic scales, but as suggested lets start with black piano keys which are:
C#, D#, F#, G#, A#, c#, d#, f#, g#, a#
However most important is the difference between them if we take the number for those tones from Sonic Pi it's:
62, 64, 67, 69, 71, (74, 76, 79, 81, 83)
Hence differences are:
2, 3, 2, 2, (3, 2, 3, 2, 2)
I guess you can see the pattern there, so if you choose any tone and just do the differences, you got pentatonic scale.

Sonic Pi

OK so musical theory we have, what we will need to create simple generator. Endless loop and randomly choose tone and duration in each go run through in the loop.


Loop

Loop just do commands in endless loop.

loop do
  commands
end

Field

Field is the list of items in square brackets separated by comma(s)
[62, 64, 67, 69, 71]
pentatonic_scale = [62, 64, 67, 69, 71]

Choose

Randomly choose one of the item in the give field
choose(field)
choose([62, 64, 67, 69, 71])

One_in

This returns true with probability of 1/X
one_in(X)
For example gives true with probability of 1/5
one_in(5)

Function/Procedure

Just use define :name do |prametr1, parametr2| to define procedure or function, if you want it to return some value then before the end use name = value

define :gchoose_note do
  note = 67
  note = 64 if one_in(5)
  note = 69 if one_in(5)
  note = 62 if one_in(10)
  note = 71 if one_in(10)
  gchoose_note = note
end

Random Chines music generator v1


loop do
  play choose([62, 64, 67, 69, 71])
  sleep choose([0.125, 0.25, 0.5, 1])
end

The problem with this is that it's too random and it doesn't sound as much as Chines music as if you really play the black keys your self randomly.
The reason is that people often misunderstand the randomness and if you ask group of the people to stay in the room randomly they will distribute them self's more or less evenly.
So they do when the play black keys, they tend to play one tone (in the middle often) and going around back and forth.

Random Chines music generator v2


So let's try a Gaussian distribution of probability with which we choose the tone to play.

# Welcome to Sonic Pi v2.10
use_bpm 60
use_synth :dull_bell

define :gchoose_note do
  note = 67
  note = 64 if one_in(5)
  note = 69 if one_in(5)
  note = 62 if one_in(10)
  note = 71 if one_in(10)
  gchoose_note = note
end

define :gchoose_duration do
  duration = 0.25
  duration = 0.5 if one_in(3)
  duration = 1 if one_in(6)
  duration = 0.125 if one_in(6)
  gchoose_duration = duration
end

loop do
  play gchoose_note
  sleep gchoose_duration
end

Saturday, July 16, 2016

Learn music for programmers with Sonic Pi - 01 - Brother John

I my self don't have any musical education and they try to teach me in school it was completely nonsense for me. The reason is that it's basically substitution cipher for basic physics. So let's start with few basic substitutions.

Tones/Notes

There is 12 half-tones which musicians call octave(Latin name for 8)
It's actually 7 tones (C,D,E,F,G,A,B) + 5 Sharp tones(C#, D#, F#, G#, A#). - To be more complicated German notation use H instead of B.
Reference tone is A = 440Hz, A octave higher have frequency 2*A (880Hz) and octave lower is 1/2*A (220Hz)

Now if we check the notation we can find where it's C, D, E etc. based on the position, as you can see on the we start with C on the line below "standard lines". D is under first line, E on the first line F between 1st and 2nd, etc. If you would like to ask where are the # (Sharps), there are not there if the note is sharp would see symbol # in front  of the note. (If you try to find logic in it I'm not able to help sorry, it's a cipher, really it is Bb read as B flat = A# read as A Sharp or Ais, 4 names for one frequency, see not only simple substitution it's almost like enigma)


Duration

Tones have duration, whole, half, quarter, 1/8, 1/16 etc.



This is what every musician explains, but still you don't know anything. Half of what? Second, minute, year.... ?
The answer is it depends on tempo. Tempo is number of whole notes which you can play in the minute, BPM, beats per minute.
Hence if tempo is 60 bpm it 60 wholes per minute, hence whole note plays for 1 second,  or half note 0.5 second, etc.

Tones/Notes in Sonic Pi


One more substitution we need to learn this time easy for Sonic Pi. Sonic Pi don't use names of tones it use simple integers.
1 is lowest C
2 is C#
3 is D
4 is D#
etc.
This is however way too low, to get something which we can hear we will start at 61 so

CC#DD#EFF#GG#AA#B
616263646566676869707172

Let's play in Sonic Pi


It's easy we will use command play to play a note and sleep to say how long.
Hence

play 61
sleep 0.25

plays quarter C

Now we just need to define what we are going to play and how fast (tempo). We will use piano and tempo 60 BPM

use_bpm 60
use_synth :piano

And last for today we need to learn to repeat certain parts for that we will use X. times do cyckle it have easy syntax:

X. times do
  commands
end

Will do X times commands between do and end. In below song we use 2 times do.

Brother John

An finally let's play very easy song, which have mutation in various languages, Brother Jacob.

Sonic Pi code:

# Welcome to Sonic Pi v2.10
use_bpm 60
use_synth :piano

#Let first define basic tones
C = 61
CSharp = 62
D = 63
DSharp = 64
E = 65
F = 66
FSharp = 67
G = 68
GSharp = 69
A = 70
ASharp = 71
H = 72
B = 72

#Now lets play the song Brother Jacob

#1st Part 2 times
#Are you slee - pin'
2. times do
  play C
  sleep 0.50
  play D
  sleep 0.50
  play E
  sleep 0.50
  play C
  sleep 0.50
end

#Second part 2 times
#Bro-ther John
2. times do
  play E
  sleep 0.5
  play F
  sleep 0.5
  play G
  sleep 1
end

#3rd part 2 times
#Mor- nin bells are ring-ing
2. times do
  play G
  sleep 0.25
  play A
  sleep 0.25
  play G
  sleep 0.25
  play F
  sleep 0.25
  play E
  sleep 0.5
  play C
  sleep 0.5
end

#4th and last part 2 times
# Bim bam boom
2. times do
  play C
  sleep 0.5
  play G
  sleep 0.5
  play C
  sleep 1
end

Saturday, July 9, 2016

False alerts - Dropped packets

Most of the monitoring is based on same fact it does some kind of probe, ICMP, SNMP, SSH, WinRM, WMI, or some kind of API. But most importantly it because it needs to pull all data from all devices it cannot afford more then few tries. So it's understandable that from time to time something is missed, not reply. It would be fine if it doesn't occur often and ideally on the random bases.

Usually the problem lies in TCP/IP implementation. Each network interface (no matter if virtual, physical, on switch, server, printer) have incoming and outgoing queue (buffer) which have some size. Packets goes first to this buffer and then they are processed one by one by TCP/IP layers.

When there is lot of traffic in very short period of time, typically when you start big file transfer, this buffer fills and newly incoming packets are dropped. For ongoing TCP connection it's no problem, because dropped packets as they are not acknowledged are resend. But new connections and state less protocols are not able recover and it "timeouts".

In monitoring we usually consider such device as down because it's not responding.
So if you have irregularly flapping devices or more specifically packet loss and your problem might be dropped packets and you shall consider extending those buffers.
This is known issue of VMWare by the way
https://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2039495
If you want to prove this the problem you just need to get the dropped packet counters so here are tips how you can get it.

For Windows


From Zenoss


winrm -r http://hotsname:5985 -u service-acount@example.net -a kerberos --dcip 10.10.10.10 -f "select * from Win32_PerfRawData_Tcpip_NetworkInterface" -d

From Windows Powershell


Get-WmiObject -computername hostname -Query "select Name,PacketsOutboundDiscarded,PacketsReceivedDiscarded from Win32_PerfRawData_Tcpip_NetworkInterface"

Output example:
__GENUS                  : 2
__CLASS                  : Win32_PerfRawData_Tcpip_NetworkInterface
__SUPERCLASS             :
__DYNASTY                :
__RELPATH                : Win32_PerfRawData_Tcpip_NetworkInterface.Name="vmxnet3 Ethernet Adapter _2"
__PROPERTY_COUNT         : 3
__DERIVATION             : {}
__SERVER                 :
__NAMESPACE              :
__PATH                   :
Name                     : vmxnet3 Ethernet Adapter _2
PacketsOutboundDiscarded : 0
PacketsReceivedDiscarded : 50
PacketsReceivedDiscarded ( https://msdn.microsoft.com/en-us/library/aa394340(v=vs.85).aspx )
Data type: uint32
Access type: Read-only
Qualifiers: DisplayName ("Packets Received Discarded") ,
CounterType (65536) ,
DefaultScale (0) ,
PerfDetail(200)
Number of inbound packets that were chosen to be discarded even though no errors had been detected to prevent delivery to a higher-layer protocol. One possible reason for discarding such a packet could be to free up buffer space.
  

For SNMP Devices

Snmpwalk/get for below OIDs
ifInDiscards .1.3.6.1.2.1.2.2.1.13 
ifOutDiscards .1.3.6.1.2.1.2.2.1.19
The number of in/out bound packets which were chosen to be discarded even though no errors had been detected to prevent their being deliverable to a higher-layer protocol. One possible reason for discarding such a packet could be to free up buffer space

root@raspberrypi:/home/pi# snmpwalk -v 2c -c public 10.0.0.40 .1.3.6.1.2.1.2.2.1.2
iso.3.6.1.2.1.2.2.1.2.1 = STRING: "eth0"
iso.3.6.1.2.1.2.2.1.2.2 = STRING: "LOOPBACK"
root@raspberrypi:/home/pi# snmpwalk -v 2c -c public 10.0.0.40 .1.3.6.1.2.1.2.2.1.13
iso.3.6.1.2.1.2.2.1.13.1 = Counter32: 0
iso.3.6.1.2.1.2.2.1.13.2 = Counter32: 0
root@raspberrypi:/home/pi# snmpwalk -v 2c -c public 10.0.0.40 .1.3.6.1.2.1.2.2.1.19
iso.3.6.1.2.1.2.2.1.19.1 = Counter32: 0
iso.3.6.1.2.1.2.2.1.19.2 = Counter32: 0

For Linux base devices

On linux based devices you can use ifconfig command nad check for dropped counters
root@raspberrypi:/var/www# ifconfig
eth0      Link encap:Ethernet  HWaddr b8:27:eb:1d:89:fe
          inet addr:10.0.0.37  Bcast:10.0.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:19462418 errors:0 dropped:43030 overruns:0 frame:0
          TX packets:12891339 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000

          RX bytes:4260766110 (3.9 GiB)  TX bytes:2835848497 (2.6 GiB)

Thursday, October 15, 2015

CSS to Display UPnP Media Info

I was playing with UPnP and my goal interest was to display on webpage what is currently playing on XBOX360. (But would work on most of UPnP devices.)
So first we need python script which will ask for information. For this we need service AVTransport and action GetMediaInfo, if you are not familiar with UPnP you can find more here
I have few others monitoring scripts which are storing info about devices in my network to sqlite3 DB. Following script use this DB to find current uri for of my XBOX 360 UPnP service and send the request to get the data.
#! /usr/bin/python

import sqlite3
import sys
import cgi
import httplib, urllib
import re
import suds

#As UPnP ports are dynamic I'm getting info from my DB, info is stored by monitoring script.
select_query = "SELECT ip,upnp_port FROM devices WHERE name like 'XBOX 360';"
judodb = sqlite3.connect('/var/www/judo.db')
cur = judodb.cursor()
rows = cur.execute(select_query)
for row in rows:
  ip = row[0]
  upnp_port = row[1]

judodb.commit()
judodb.close()


url = ip + ":" + str(upnp_port)
controlUrl = "/Control/AVTransport"
service = "urn:schemas-upnp-org:service:AVTransport:1"
soap_args = "0"

actions = ["GetTransportInfo", "GetMediaInfo"]

for action in actions:
  conn = httplib.HTTPConnection(url)
  soap_data = ''+soap_args+''

  #FOR DEBUG
  #print soap_data

  params = urllib.urlencode({'q': 'set'})
  headers = { "Content-Type": "text/xml", "Content-Length": "%d" % len(soap_data), "SOAPACTION": '"'+service+'#'+action+'"' }

  conn.request("POST", controlUrl , "", headers)
  conn.send(soap_data)

  response = conn.getresponse()

  #FOR DEBUG
  #print response.status, response.reason

  #In UPnP there is sometime nested xml inside xml as string with < and > instead of < and >
  #lines below fix this to male it all one xml
  responsestr = response.read()
  responsestr = responsestr.replace(">",">")
  responsestr = responsestr.replace("<","<")

  print responsestr

I use javascript XMLHttpRequest to call the script and write the data I get to webpage. Below is eaxmple of such data.
<div id="UPnPPlayInfo" class="list"><!--?xml version="1.0"?-->
<s:envelope s:encodingstyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
 <s:body>
  <u:gettransportinforesponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
    <currenttransportstate>STOPPED</currenttransportstate>
    <currenttransportstatus>OK</currenttransportstatus>
    <currentspeed>1</currentspeed>
  </u:gettransportinforesponse>
 </s:body>
</s:envelope>


<!--?xml version="1.0"?-->
<s:envelope s:encodingstyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
 <s:body>
  <u:getmediainforesponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
           <nrtracks>1</nrtracks>
           <mediaduration>0:01:51.856</mediaduration>
           <currenturi>http://10.0.0.33:9001/disk/DLNA-PNMP3-OP01-FLAGS01700000/O0$1$8I566796.mp3</currenturi>
           <currenturimetadata>
             <item id="0$1$12$4477R566796" parentid="0$1$12$4477" refid="0$1$8I566796" restricted="1">
               <dc:title>Ja zadam</dc:title><dc:date>2009-01-01</dc:date>
               <upnp:genre>Folk</upnp:genre
               ><upnp:album>Virtualky</upnp:album>
               <upnp:originaltracknumber>4</upnp:originaltracknumber>
               <dc:creator>Jaromir Nohavica</dc:creator>
               <upnp:albumarturi dlna:profileid="JPEG_TN">http://10.0.0.33:9001/disk/DLNA-PNJPEG_TN-OP01-CI1-FLAGS00d00000/defaultalbumart/a_u_d_i_o.jpg/O0$1$8I566796.jpg?scale=160x160</upnp:albumarturi>
               <upnp:artist>Jaromir Nohavica</upnp:artist>
               <pv:extension>mp3</pv:extension>
               <upnp:albumartist>Jaromir Nohavica</upnp:albumartist>
               <pv:modificationtime>1255889570</pv:modificationtime>
               <pv:addedtime>1401613856</pv:addedtime>
               <pv:lastupdated>1255889570</pv:lastupdated>
               <pv:album_crosslink>0$1$12$4153</pv:album_crosslink>
               <pv:artist_crosslink>0$1$11$3960</pv:artist_crosslink>
               <pv:genre_crosslink>0$1$10$2756</pv:genre_crosslink>
               <pv:numberofthisdisc>1</pv:numberofthisdisc>
               <upnp:author role="Composer">Jaromir Nohavica</upnp:author>
               <upnp:artist role="Composer">Jaromir Nohavica</upnp:artist>
               <pv:bookmark>uuid:55076f6e-6b79-4d65-64a7-5067f0a1791f,-L21udC9tZWRpYVNoYXJlcy9tdXNpYy9tdXNpYy9KYXJvbWlyX05vaGF2aWNhL1ZpcnR1YWxreS9qYV96YWRhbS5tcDM=</pv:bookmark>
            </item>
          </currenturimetadata>
    </u:getmediainforesponse>
  </s:body>
</s:envelope>

Last thing I need to do is to use CSS to display only what I want and add some formatting and additional text.
/* LIST OF TAGS TO BE NOT DISPLAYED  */
currenttransportstatus, currentspeed, currenturi, playmedium, recordmedium, writestatus, res, mediaduration, nrtracks, desc {display: none}
upnp\:class, upnp\:albumarturi, dc\:creator, upnp\:albumartist, upnp\:author  {display: none}
pv\:extension, pv\:modificationtime, pv\:addedtime, pv\:lastupdated, pv\:album_crosslink, pv\:artist_crosslink, pv\:genre_crosslink, pv\:bookmark, pv\:numberofdiscs, pv\:numberofthisdisc {display: none}

DIDL-Lite {display: block}

Meta_Info:before {content: "Currently Playing:"; font-weight: bold}
Play_Mode:before {content: "Play mode:"; font-weight: bold}

/* LIST OF TAGS TO BE DISPLAYED ONE PER LINE */
upnp\:genre, upnp\:originaltracknumber, upnp\:album, upnp\:artist, dc\:title, dc\:date, dc\:publisher {display: list-item; list-style-type: none}


upnp\:artist:before {content: "Artist: "; margin-left: 10px; font-weight: bold}
upnp\:album:before {content: "Album: "; margin-left: 10px; font-weight: bold}
upnp\:genre:before {content: "Genre: "; margin-left: 10px; font-weight: bold}
upnp\:originaltracknumber:before {content: "Track No.: "; margin-left: 10px; font-weight: bold}
dc\:title:before {content: "Song: "; margin-left: 10px; font-weight: bold}
dc\:date:before {content: "Date: "; margin-left: 10px; font-weight: bold}
dc\:publisher:before {content: "Publisher: "; margin-left: 10px; font-weight: bold}

Monday, May 4, 2015

Home entertainment system monitoring and auto switch off

I have few devices which are creating my home entertainment system:

  • NEC monitor (sometimes good monitor is better then TV)
  • Yamaha HTR-4065 
  • PC Zotac
  • Evolveo DaulCoder DVB-T
  • XBOX 360

All those are connected to network via ethernet and there is some way to monitor or even to remotely control, lets start one by one:

DVB-T and PC

As you might know one of the easiest way to check if device is up or down would be to ping it, however in case of audio/video systems it's bit hard because if they are in Standby mode they will answer to the ping.
In some cases I don't even give the device permanent IP(DVB-T and PC). However there is the way to discover them via UPnP.

There is linux utility gssdp-discovery which use ssdp to discover UPnP capable device and as far as each device have unique identificator uuid we can check if device is running by checking if device is responding to ssdp discovery.

Below is small script which check based on uuid if device is running, which IP it was assigned and on which port UPnP is listening and record this to sqlite database.

#! /bin/bash

#test if device can be discovered using SSDP
#IMPORTANT return true if device is down
testUPnP()
{
  return `gssdp-discover -n 10 -t uuid:$1 | grep "resource available" | wc -l`
}

HFILE='/var/www/monitoring/device_upnp.list'
DBFILE='/var/www/judo.db'

if [[ -z $HFILE ]]
then
  echo "$HFILE doesn't exist"
  exit 1
fi

while read line
do
  uuid=$(echo "$line" | cut -d, -f5)
  mac=$(echo "$line" | cut -d, -f4)
  new_polls=1
  prev_status_polls=$(sqlite3 $DBFILE "SELECT cur_state,polls_in_state FROM dev_status WHERE mac = '$mac';")
  prev_status=$(echo "$prev_status_polls" | cut -d\| -f1)
  prev_polls=$(echo "$prev_status_polls" | cut -d\| -f2)

  #remeber testUPnP returns true if device is down
  if `testUPnP $uuid`
  then
    new_status="Down"
    if [ "$prev_status" == "Up" ]
    then
      sqlite3 $DBFILE "UPDATE devices SET ip = NULL, upnp_port = NULL WHERE mac = '$mac';"
    fi
  else
    new_status="Up"
    if [ "$prev_status" == "Down" ]
    then
      location=`gssdp-discover -n 10 -t uuid:$uuid | grep "Location:" | sed 's/ *Location: http:\/\///g'| cut -d/ -f1 | sed 's/\///g'`
      echo "$location"
      ip=$(echo "$location" | cut -d: -f1)
      upnp_port=$(echo "$location" | cut -d: -f2)
      echo "$ip:$upnp_port"
      sqlite3 $DBFILE "UPDATE devices SET ip = '$ip', upnp_port = '$upnp_port' WHERE mac = '$mac';"
    fi
  fi

  if [ "$prev_status" == "$new_status" ]
  then
    new_polls=$((prev_polls + 1))
  fi
  sqlite3 $DBFILE "UPDATE dev_status SET cur_state = '$new_status', polls_in_state = '$new_polls' WHERE mac = '$mac';"
  echo "$uuid,$mac,$new_status,$prev_status,$new_polls"
done < ${HFILE}

Yamaha HTR-4065


Even though Yamaha have UPnP we will rather use YNC to get the status, because we also want to know which input is actually active. Below is python script which finds the status and input and store that in sqlite database.
#! /usr/bin/python

import sqlite3
import sys
import httplib, urllib
import re
from xml.dom import minidom
from xml.parsers.expat import ExpatError

conn = httplib.HTTPConnection("10.0.0.36:80")
xml_req_power = '<?xml version="1.0" encoding="utf-8"?><YAMAHA_AV cmd="GET"><Main_Zone><Power_Control><Power>GetParam</Power></Power_Control></Main_Zone></YAMAHA_AV>'

xml_req_input = '<?xml version="1.0" encoding="utf-8"?><YAMAHA_AV cmd="GET"><Main_Zone><Input><Input_Sel>GetParam</Input_Sel></Input></Main_Zone></YAMAHA_AV>'

params = urllib.urlencode({'q': 'set'})
headers = { 'Content-Type': 'application/xml', "Content-Length": "%d" % len(xml_req_power)}

conn.request("POST", "/YamahaRemoteControl/ctrl", "", headers)
conn.send(xml_req_power)

xmldoc = minidom.parseString(conn.getresponse().read())
xml_input_element = xmldoc.getElementsByTagName('Power')
#print xml_input_element[0]
for node in xml_input_element:
  power_status = node.firstChild.nodeValue

conn.close()

#=================================
#Geting input

headers = { 'Content-Type': 'application/xml', "Content-Length": "%d" % len(xml_req_input)}

conn.request("POST", "/YamahaRemoteControl/ctrl", "", headers)
conn.send(xml_req_input)
#response = conn.getresponse()

#print response.status, response.reason
#response_data = response.read()

#print response_data

xmldoc = minidom.parse(conn.getresponse())
xml_input_element = xmldoc.getElementsByTagName('Input_Sel')
for node in xml_input_element:
  av_input = node.firstChild.nodeValue

conn.close()

print "Status\t\tAV Input"
print power_status + "\t\t" + av_input

select_query = "SELECT cur_state,polls_in_state,input FROM dev_status WHERE mac = '00:a0:de:92:6b:78';"
#print update_query
new_polls=1
judodb = sqlite3.connect('/var/www/judo.db')
cur = judodb.cursor()
rows = cur.execute(select_query)
for row in rows:
  prev_state = row[0]
  prev_polls = row[1]
  prev_input = row[2]
if prev_state == power_status and prev_input == av_input:
  new_polls = prev_polls + 1
print prev_state + "\t\t" + str(new_polls)
update_query = "UPDATE dev_status SET input = \'" + av_input + "\', cur_state = \'" + power_status + "\', polls_in_state = " + str(new_polls)  + " WHERE mac = '00:a0:de:92:6b:78'"
cur.execute(update_query)
judodb.commit()
judodb.close()

NEC

NEC responds to ping in Standby and of course don't have UPnP, however the NEC remote control over ethernet let us find out the actual status.
Below python script check the status and store it in sqlite database.
#! /usr/bin/python

import sys
import socket
import sqlite3

monitor_ip = '10.0.0.35'
port = 7142
buffer_size = 2048
data_on = '\x01\x30\x41\x30\x41\x30\x36\x02\x30\x31\x44\x36\x03\x74\x0d'


new = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
new.connect((monitor_ip, port))
new.send(data_on)

data = ''

#message is split over several packets so need to get all content, message we expect have 25bytes
packet = new.recv(buffer_size)
data += packet
while len(data) < 25 or not packet:
   packet = new.recv(buffer_size)
   data += packet

new.close()

#Reponse OFF
#00AB120200D60000040004q
#Response ON
#00AB120200D60000040001t

off_response = b'\x30\x30\x41\x42\x31\x32\x30\x32\x30\x30\x44\x36\x30\x30\x30\x30\x30\x34\x30\x30\x30\x34'
on_response = bytes("\x30\x30\x41\x42\x31\x32\x30\x32\x30\x30\x44\x36\x30\x30\x30\x30\x30\x34\x30\x30\x30\x31")


#there seems to be some not printable chars because of packet fragmentation
#using index is dirty hack but don't care
if data[23] == off_response[21]:
  power_status = "Off"
elif data[23] == on_response[21]:
  power_status = "On"
else:
  power_status = "Unknown"

#Now we need to update sqlite with status

select_query = "SELECT cur_state,polls_in_state FROM dev_status WHERE mac = '00:25:5c:2e:36:39';"
#print update_query
new_polls=1

judodb = sqlite3.connect('/var/www/judo.db')
cur = judodb.cursor()
rows = cur.execute(select_query)
for row in rows:
  prev_state = row[0]
  prev_polls = row[1]
if prev_state == power_status:
  new_polls = prev_polls + 1
print prev_state + "\t\t" + str(new_polls)
update_query = "UPDATE dev_status SET cur_state = \'" + power_status + "\', polls_in_state = " + str(new_polls)  + " WHERE mac = '00:25:5c:2e:36:39'"
cur.execute(update_query)
judodb.commit()
judodb.close()


XBOX 360

For monitoring of XBOX 360 I've have to use arping and UPnP, because of limitation of XBOX 360 OS more can be find here including the script.

Automatic Switch Off


Now as we have ways of getting status we just use crontab to run the scripts each 10 minutes and to get the status and store it in to sqlite database. We are storing also how many time we see it consecutively in current status. (polls_in_state column in table below)

sqlite> .schema dev_status
CREATE TABLE dev_status(mac TEXT, cur_state TEXT, polls_in_state NUM, input TEXT);
sqlite> select * from dev_status;
00:25:5c:2e:36:39|On|3|unknown
00:a0:de:92:6b:78|On|3|HDMI1
00:ce:39:b5:48:9e|Up|4|unknown
7c:1e:52:c2:6c:a7|Up|3|unknown
00:01:2e:3a:a3:b2|Down|1214|unknown
Last thing we have to do is set crontab to run script, which check status based on the status of devices finds what is running and if no device coresponding to AV receiver input is running, (means that AV receiver is running for nothing) it will switch it off. Same for NEC.
crontab -l

#Monitoring of home network
*/10 * * * * /var/www/monitoring/judo_icmp_monitoring &> /dev/null
*/10 * * * * /var/www/monitoring/judo_upnp_monitoring &> /dev/null
*/10 * * * * /var/www/monitoring/HTR-4065_State.py &> /dev/null
*/10 * * * * /var/www/monitoring/NECStatus.py &> /dev/null
*/10 * * * * /var/www/monitoring/XBOX360Status.sh &> /dev/null

5,15,25,35,45,55 * * * * /var/www/monitoring/autostop >> /media/nas/public/autostop.log

autostop script
#! /bin/bash

HTR_MAC='00:a0:de:92:6b:78'
NEC_MAC='58:C2:32:97:3A:48'
Evolve_MAC='00:ce:39:b5:48:9e'
XBOX_MAC='7c:1e:52:c2:6c:a7'
Zotac_MAC='00:01:2e:3a:a3:b2'

CGIDIR='/var/www/cgi-bin'
DBFILE='/var/www/judo.db'

getDeviceStatus(){
  SQLQUERY="SELECT cur_state,polls_in_state,input FROM dev_status WHERE mac = '$1';"
  SQLRESULT=`sqlite3 $DBFILE "$SQLQUERY" | sed 's/|/ /g;'`
  echo "$SQLRESULT"
}


ACTION='No action'

echo -n ""

read HTR_STATUS HTR_POLLS_IN_STATUS HTR_INPUT <<< `getDeviceStatus $HTR_MAC`
echo -n "$HTR_STATUS $HTR_POLLS_IN_STATUS $HTR_INPUT"

#Example of output
#00:a0:de:92:6b:78|On|51|HDMI1

if [[ $HTR_STATUS == "On" && $HTR_POLLS_IN_STATUS -gt 3 ]]
then
  case $HTR_INPUT in
    "HDMI1")
       #echo "Check Evolve status"
       read Evolve_STATUS Evolve_POLLS_IN_STATUS Evolve_INPUT <<< `getDeviceStatus $Evolve_MAC`
       echo -n "$Evolve_STATUS $Evolve_POLLS_IN_STATUS $Evolve_INPUT"
       if [[ $Evolve_STATUS == "Down" && $Evolve_POLLS_IN_STATUS -gt 3 ]]
       then
         $CGIDIR/NECOff.py
         $CGIDIR/HTR-4065_Off.py
         ACTION="Switching HTR-4065 and NEC off"
       fi
       ;;
    "HDMI2")
       #echo "Check XBOX 360 status"
       read XBOX_STATUS XBOX_POLLS_IN_STATUS XBOX_INPUT <<< `getDeviceStatus $XBOX_MAC`
       echo -n "$XBOX_STATUS $XBOX_POLLS_IN_STATUS $XBOX_INPUT"
       if [[ $XBOX_STATUS == "Down" && $XBOX_POLLS_IN_STATUS -gt 3 ]]
       then
         $CGIDIR/NECOff.py
         $CGIDIR/HTR-4065_Off.py
         ACTION="Switching HTR-4065 and NEC off"
       fi
       ;;
    "HDMI3")
       #echo "Checking PC Zotac Status"
       read Zotac_STATUS Zotac_POLLS_IN_STATUS Zotac_INPUT <<< `getDeviceStatus $Zotac_MAC`
       echo -n "$Zotac_STATUS $Zotac_POLLS_IN_STATUS $Zotac_INPUT"
       if [[ $Zotac_STATUS == "Down" && $Zotac_POLLS_IN_STATUS -gt 3 ]]
       then
         $CGIDIR/NECOff.py
         $CGIDIR/HTR-4065_Off.py
         ACTION="Switching HTR-4065 and NEC off"
       fi
       ;;
  esac
else
  #AV reciever is Standby for more then hour and NEC is On for
  if [[ ! $HTR_STATUS == "On" ]]
  then
    read NEC_STATUS NEC_POLLS_IN_STATUS NEC_INPUT <<< `getDeviceStatus $NEC_MAC`
    echo -n "$NEC_STATUS $NEC_POLLS_IN_STATUS $NEC_INPUT"

    if [[ $HTR_POLLS_IN_STATE -gt 6 && $NEC_STATUS == "On" && $NEC_POLLS_IN_STATUS -gt 3 ]]
    then
      $CGIDIR/NECoff.py
      ACTION="Switching NEC Off"
    fi
  fi
fi

echo -n "$ACTION"
echo ""
#HTR-4065 HDMI1 : Evolve Down,3 => OFF: NEC, HTR-4065
#HTR-4065 HDMI2 : XBOX_360 Down,3 => OFF: NEC, HTR-4065
#HTR-4065 HDMI3 : PC_Zotac Down,3 => OFF: NEC, HTR-4065
#HTR-4065 HDMI4 : Not Assigned

Thursday, March 26, 2015

XBOX 360 monitoring

XBOX 360 is great even it's "old" already compared to XBOX One and other next gen consoles. Never the less I like XBOX 360 even though I'm missing few thinks (limited USB stack implementation, missing Wake On LAN).

However I've decided I shall monitor my entertaining systems to be able switch off AV reciever or Display if forget to. For this I need to know if my Xbox 360, PC or DVB-T is running.... I'll cover this "auto switch off system" in another post.

During my exploration I find following problems:
  1. ping is not the option, XBOX 360 doesn't reply to ICMP request
  2. UPnP is running only when not playing game
  3. arping works only with IP, if only MAC address is provide it will send request with broadcast IP which xbox will not respond
So the solution is to set for XBOX fixed IP and use:
  1. arping to verify if device is up
  2. gssdp-discovery to check if game is running (and get UPnP Port if not)

Below is script using arping to verify if device is up and if so then find what UPnP port is it listening(if game is not played), results are stored in sqlite db for purpose of further use

#! /bin/bash


#test ICMP connectivity with ping
testARPing()
{
  return `arping -c 4 $1 | grep "100% unanswered" | wc -l`
}

XBOXIP='10.0.0.41'
XBOXMAC='7c:1e:52:c2:6c:a7'
XBOXUUID='03166842-2743-2000-0000-7c1e52c26ca7'

DBFILE='/var/www/judo.db'

if `testARPing $XBOXIP`
  then
    new_status="Up"
    location=`gssdp-discover -n 10 -t uuid:$XBOXUUID | grep "Location:" | sed 's/ *Location: http:\/\///g'| cut -d/ -f1 | sed 's/\///g'`
    #echo "$location"
    #ip=$(echo "$location" | cut -d: -f1)
    upnp_port=$(echo "$location" | cut -d: -f2)
  else
    new_status="Down"
fi

new_polls=1
prev_status_polls=$(sqlite3 $DBFILE "SELECT cur_state,polls_in_state FROM dev_status WHERE mac = '$XBOXMAC';")
prev_status=$(echo "$prev_status_polls" | cut -d\| -f1)
prev_polls=$(echo "$prev_status_polls" | cut -d\| -f2)
if [ "$prev_status" == "$new_status" ]
then
  new_polls=$((prev_polls + 1))
fi
sqlite3 $DBFILE "UPDATE dev_status SET cur_state = '$new_status', polls_in_state = '$new_polls' WHERE mac = '$XBOXMAC';"
sqlite3 $DBFILE "UPDATE devices SET upnp_port = '$upnp_port' WHERE mac = '$XBOXMAC';"
echo "$XBOXIP,$XBOXMAC,$new_status,$prev_status,$new_polls"