Ankündigung

Einklappen
Keine Ankündigung bisher.

Russound C5/C7 Python Steuerung (RIO)

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

    #16
    Hi,

    inspiriert von 55er driver habe ich eine Schnittstelle für das RIO Protokoll in php geschrieben. Ich habe nicht alle Funktionen übernommen, aber eine Funktion announce eingebaut. Hier bei wird das Script zum Beispiel aufgerufen mit: http://<IP Webserver>/russound.php?Z=1&action=announce&text=Guten Morgen

    Es wird zuerst die aktuellen Paramenter der Zonen gelesen, dann wird über Google Translate der text in ein mp3 umgewandelt, in /var/spool/audio zwischengespeichert, der mp3 mit mpg123 auf dem Standard Audio interface ausgeben und die Einstellungen der Zonen wieder zurück geschrieben.

    Bedingung ist, dass auf dem Webserver bei korrekter, manueller Einstellung des Russound, eine mp3 Datei richtig ausgegeben wird.

    Bei mir läuft das Script auf einem Raspberry und der Audio-Ausgang ist mit einer Quelle (bei mir 6) aud dem Russound MCA-C5 verbunden.

    Vielleicht kann es jemand gebraucht. Das Script als russound.php abspeichert, und los geht es. Bei Bedarf kann ich auch zeigen, wie die Einbindung in den Homeserver erfolgt.

    -Muecke

    PHP-Code:
    <html>
    <body>
    <?php
    // Russound MCA RIO php interface Version 1.0.8
    // Example: http://<IP Webserver>/russound.php?debug=1&Z=1&action=announce&text=Have a nice day
    // Parameter:
    // Z or zone: Zone number e.g. 1 or 1,4,5 etc...
    // C: Controller number e.g. 1 ( default is 1)
    // volume: Volume for announcements and for fade-in (like in My Russound App 1..100)
    //    source: set zone Z to source number
    //    test: reads, print and restore MCA config for zone Z
    // action:
    //        On - zone power on
    //        Off - zone power off
    //        AllOn - power on all zones
    //        AllOff - power off all zones
    //        fadein - power Zone on, set source fade zone to volume
    //        fadeout - fade zones out
    //        intro - Plays on intro mp3 to get attention, an not existing name will disable the intro
    //        VolUp - zones VolumeUp
    //        VolDown - zones VolumeDown
    //
    // Requirements:
    // Apache with php enabled
    // mpg123 playing MP3
    // Apache user member of group audio
    // Changelog:
    // 1.0.4 bugfix announce volume
    // 1.0.5 Change get_zone_config to one network request, change function names
    // 1.0.6 Bugfixing
    // 1.0.7 Add Volume up and down
    // 1.0.8 Add parameter intro

    // Change the following variables to your needs
    //debug mode On=1 Off=0
    $debug=0;
    //name / ip Roussound
    $rs="mcac5";
    //number of zones
    $maxzone=6;
    //Controller number
    $controller=1;
    //Source number for announcements
    $announce_source=6;
    //Volume for announcements
    $announce_volume=30;
    //Directory for announcement cache
    $announce_dir="/var/spool/audio/";
    //Gong filename
    $intro="$announce_dir/gong1.mp3";
    //language of announcement
    $announce_lang="de";
    //Zonenames to allow using of Names instead only Numbers
    $zonenames=array( => 'Sauna'=> 'Arbeit'=> 'Schlafzimmer'=> 'Fitness'=> 'Wohnzimmer'=> 'Kind1');

    // Keep your hands off starting from here
    $count_zones=0;
    $action="";
    $text="";
    $source=0;
    $volume=0;

    // activate full error reporting
    error_reporting(E_ALL E_STRICT);

    if (isset(
    $_GET['debug'])) {
        
    $debug=filter_input(INPUT_GET'debug'FILTER_SANITIZE_STRING);
    }

    if (isset(
    $_GET['source'])) {
        
    $source=filter_input(INPUT_GET'source'FILTER_SANITIZE_STRING);
        if (
    $source || $source 6)
            die(
    "Illegal Source");
    }

    if (isset(
    $_GET['action'])) {
        
    $action=filter_input(INPUT_GET'action'FILTER_SANITIZE_STRING);
        if (
    $action == "1") {
            
    $action="on";
        }
        elseif (
    $action == "0") {
            
    $action="off";
        }
    }

    if (isset(
    $_GET['lang'])) {
        
    $announce_lang=filter_input(INPUT_GET'lang'FILTER_SANITIZE_STRING);
    }

    if (isset(
    $_GET['intro'])) {
        
    $intro=$announce_dir.filter_input(INPUT_GET'intro'FILTER_SANITIZE_STRING);
    }

    if (isset(
    $_GET['C'])) {
        
    $controller=filter_input(INPUT_GET'C'FILTER_SANITIZE_STRING);
    }

    if (isset(
    $_GET['text'])) {
        
    $text=filter_input(INPUT_GET'text'FILTER_SANITIZE_STRING);
    }

    if (isset(
    $_GET['volume'])) {
        
    $volume=filter_input(INPUT_GET'volume'FILTER_SANITIZE_STRING);
         if (
    $volume 0)
             
    $volume $volume 2;
    }

    //Zone either Z oder zone
    if (isset($_GET['Z']))
        
    $zone=explode(",",filter_input(INPUT_GET'Z'FILTER_SANITIZE_STRING));

    if (isset(
    $_GET['zone']))
        
    $zone=explode(",",filter_input(INPUT_GET'zone'FILTER_SANITIZE_STRING));

    for(
    $i=0$i count($zone); $i++) {
        
    $key=array_search($zone[$i],$zonenames);
        if (
    $key)
            
    $zone[$i]=$key;
            
        if (
    $zone[$i] < || $zone[$i] > $maxzone)
            die (
    "Illegal Zone:".$zone[$i]);
    }

    $socket socket_create(AF_INETSOCK_STREAMSOL_TCP);
    if (
    $socket == false ) {
        echo 
    "socket_create() failed: " socket_strerror(socket_last_error()) . "\n";
    }

    $result socket_connect($socketgethostbyname($rs), 9621);
    if (
    $result == false) {
        echo 
    "socket_connect() failed: ($result) " socket_strerror(socket_last_error($socket)) . "\n";
        
    socket_close($socket);
        die (
    "Unable to connect to Russound");
    }

    if (
    $debug) {
        
    socket_write($socket"VERSION\r"8);
        echo 
    "MAIN: " socket_read($socket1024)."<br>";
    }

    switch (
    strtolower($action)) {
        case 
    "on":
        case 
    "off":
            foreach (
    $zone as $z)
            {
                if (
    $source)
                    
    set_zone_source($socket$z$source);
                
    $debug ? print "Main: set Zone " $z " " $action "<br>" null;
                
    set_zone_onoff($socket$z$action);
            }
            break;

        case 
    "allon":
        case 
    "alloff":
            
    socket_write($socket"EVENT C[$controller].Z[1]!$action\r");
            
    $rc socket_read ($socket,1024);
            echo 
    $debug "Main: RC: $rc<br>" null;
            break;

        case 
    "source":
            foreach (
    $zone as $z)
                
    set_zone_source($socket$z$source);
            break;

        case 
    "announce":
            
    $file text2mp3($text);
                
            if (
    $volume)
                
    $announce_volume=$volume;
                
            foreach (
    $zone as $z) {
                
    get_zone_config($socket$z);
                
    set_zone_source($socket$z$announce_source);
                
    set_zone_onoff($socket$z"On");
                
    set_zone_volume($socket$z$announce_volume);
            }
        
            
    //wait 1 sec for the speakers to be ready
            
    sleep(1);
            
            if (
    file_exists($intro)) {
                echo 
    $debug "mpg123 $intro<br>" null;
                
    exec("mpg123 -n 100 -q ".$intro$output);
                if (
    $debug)
                    foreach (
    $output as $line)
                        echo 
    $line "<br>";
            }
            echo 
    $debug "mpg123 $file<br>" null;
            
    exec("mpg123 -q "$file$output);
            if(
    $debug)
                foreach (
    $output as $line)
                    echo 
    $line "<br>";

            foreach (
    $zone as $z) {
                
    set_zone_config($socket$z);
            }

            break;

        case 
    "volup":
        case 
    "voldown":
            foreach (
    $zone as $z) {
                
    set_zone_vol_UpDown($socket$zsubstr($action3));
            }
            break;

        case 
    "fadein":
            foreach (
    $zone as $z) {
                if (
    $source)
                    
    set_zone_source($socket$z$source);

                
    zone_fadein($socket$z$volume);
            }
            break;

        case 
    "fadeout":
            foreach (
    $zone as $z) {
                
    get_zone_config($socket$z);
                
    zone_fadeout($socket$z);
            }
            break;

         case 
    "test":
            
    $debug=1;
            foreach (
    $zone as $z) {
                
    get_zone_config($socket$z);
                
    set_zone_config($socket$z);
            }
            break;
    }
    socket_close($socket);
    // end of main

    // read zone config in an array to restore settings
    function get_zone_config($socket$zone)
    {
        global 
    $debug$config$controller;
        
        
    $str="GET ".
            
    "C[$controller].Z[$zone].name,".
            
    "C[$controller].Z[$zone].status,".
            
    "C[$controller].Z[$zone].partyMode,".
            
    "C[$controller].Z[$zone].volume,".
            
    "C[$controller].Z[$zone].turnOnVolume,".
            
    "C[$controller].Z[$zone].currentSource,".
            
    "C[$controller].Z[$zone].doNotDisturb";

        echo 
    $debug __FUNCTION__.": $str<br>" null;

        
    socket_write($socket$str."\r");
        
    $line socket_read($socket1024);

        if (
    $line[0] == "S") {
            
    $value explode(","substr($line ,2));
            foreach (
    $value as $v) {
                
    $details=explode("="$v);
                
    $attr=explode("."$details[0]);
                
    $config[$zone][$attr[2]] = trim(str_replace('"'""$details[1]));
                echo 
    $debug __FUNCTION__.": Zone[$zone].[$attr[2]]=" $config[$zone][$attr[2]] . "<br>" null;
            }
        }
        else {
            echo 
    $debug __FUNCTION__.": Result: $line.<br>" null;
            die (
    __FUNCTION__.": Error, reading from Russound");
        }
        return;
    }

    // Read a specific attribute of a zone
    function read_zone_attr($socket$zone$attr)
    {
        global 
    $debug$config$controller;

        echo 
    $debug __FUNCTION__.": GET C[$controller].Z[$zone].$attr<br>" null;
        
    socket_write($socket"GET C[".$controller."].Z[".$zone."].".$attr."\r");
        
    $line socket_read($socket1024);
        echo 
    $debug __FUNCTION__.": Line: $line<br>" null;
        
        if (
    $line[0] == "S") {
            
    $value explode("="$line);
            
    $config[$zone][$attr] = trim(str_replace('"'""$value[1]));
            echo 
    $debug ? print __FUNCTION__.": Z[$zone].[$attr]= "$config[$zone][$attr]."<br>" null;
        }
        else {
            echo 
    $debug __FUNCTION__.": Result: $line<br>" :null;
            die (
    __FUNCTION__.": Error, reading from Russound");
       }
        return 
    $config[$zone][$attr];
    }

    // Restore zone settings
    function set_zone_config($socket$zone)
    {
        global 
    $debug$config$controller;

        if (
    $config[$zone]["status"] == "ON") {
            echo 
    $debug __FUNCTION__.": Zone [$zone][volume]=".$config[$zone]["volume"]."<br>" null;
            
    set_zone_volume($socket$zone$config[$zone]["volume"]);
        }
        else
            
    set_zone_volume($socket$zone0);
        
        
    set_zone_source($socket$zone$config[$zone]["currentSource"]);
        
    set_zone_partymode($socket$zone$config[$zone]["partyMode"]);
        
    set_zone_onoff($socket$zone$config[$zone]["status"]);
    }

    function 
    set_zone_partymode($socket$zone$onoff)
    {
        global 
    $debug$controller;
        echo 
    $debug __FUNCTION__.": EVENT C[$controller].Z[$zone]!partyMode $onoff<br>" null;
        
    socket_write($socket"EVENT C[$controller].Z[$zone]!partyMode $onoff\r");
        
    $rc socket_read ($socket,1024);
        echo 
    $debug __FUNCTION__.": RC: $rc<br>" null;
        return 
    $rc;
    }

    function 
    set_zone_volume($socket$zone$volume)
    {
        global 
    $debug$controller;
        echo 
    $debug __FUNCTION__.": EVENT C[$controller].Z[$zone]!KeyPress Volume $volume<br>" null;
        
    socket_write($socket"EVENT C[".$controller."].Z[".$zone."]!KeyPress Volume ".$volume."\r");
        
    $rc socket_read ($socket,1024);
        echo 
    $debug __FUNCTION__.": RC: $rc<br>" null;
        return 
    $rc;
    }

    function 
    set_zone_source($socket$zone$source)
    {
        global 
    $debug$controller;
        echo 
    $debug __FUNCTION__.": EVENT C[$controller].Z[$zone]!KeyRelease SelectSource $source<br>" null;
        
    socket_write($socket"EVENT C[".$controller."].Z[".$zone."]!KeyRelease SelectSource ".$source."\r");
        
    $rc socket_read ($socket,1024);
        echo 
    $debug __FUNCTION__.": RC: $rc<br>" null;
        return 
    $rc;
    }

    // Power zone on and fade in volume
    function zone_fadein($socket$zone$volume)
    {
        global 
    $debug$controller$config;
        
        if (
    $volume == 0) {
            
    $volume=read_zone_attr($socket$zone"turnOnVolume");
        }

        
    set_zone_onoff($socket$zone"On");
        
    set_zone_volume($socket$zone0);
        echo 
    $debug __FUNCTION__.": Volume $volume<br>" null;

        for (
    $i 1$i <= $volume$i++) {
            
    set_zone_vol_UpDown($socket$zone"Up");
            
    usleep(200000);
        }
    }

    // Decrease zone volume slowly to zero
    function zone_fadeout($socket$zone)
    {
        global 
    $debug$controller$config;
        echo 
    $debug __FUNCTION__.": Z[$zone]["volume"]=".$config[$z]["volume"]."<br>" null;
        for (
    $i 1$i <= $config[$zone]["volume"]; $i++) {
            
    set_zone_vol_UpDown($socket$zone"Down");
            
    usleep(200000);
       }
    }

    function 
    set_zone_vol_UpDown ($socket$zone$UpDown)
    {
        global 
    $debug$controller$config;

        echo 
    $debug __FUNCTION__.": EVENT C[$controller].Z[$zone]!KeyPress Volume$UpDown<br>" null;
        
    socket_write($socket"EVENT C[".$controller."].Z[".$zone."]!KeyPress Volume".$UpDown."\r");
        
    $rc socket_read ($socket,1024);
        echo 
    $debug __FUNCTION__.": RC: $rc<br>" null;
    }

    function 
    set_zone_onoff($socket$zone$onoff)
    {
        global 
    $debug$controller;

        echo 
    $debug __FUNCTION__.": EVENT C[$controller].Z[$zone]!Zone$onoff<br>" null;
        
    socket_write($socket"EVENT C[".$controller."].Z[".$zone."]!Zone".$onoff."\r");
        
    $rc socket_read ($socket,1024);
        echo 
    $debug __FUNCTION__.": RC: $rc<br>" null;
        return 
    $rc;
    }


    function 
    text2mp3 ($text)
    {

        global 
    $debug$announce_dir$announce_lang;
    // Convert Words (text) to Speech (MP3)
    // ------------------------------------

    // Google Translate API cannot handle strings > 100 characters
        
    $text substr($text0100);

    // Replace the non-alphanumeric characters
    // The spaces in the sentence are replaced with the Plus symbol
        
    $text urlencode($text);
        
    $debug ? print "Text: ".$text."<br>" null;

    // Name of the MP3 file generated using the MD5 hash
        
    $md5  md5($text);

    // Save the MP3 file in this folder with the .mp3 extension
        
    $file $announce_dir $md5 ".mp3";


    // If the MP3 file exists, do not create a new request
        
    if (!file_exists($file)) {
            
    $debug ? print "URL: http://translate.google.de/translate_tts?&q=".$text."&tl=" $announce_lang "&client=t<br>" null;
            
    $mp3 file_get_contents("http://translate.google.de/translate_tts?&q=".$text."&tl=" $announce_lang "&client=t");
            if (
    strlen($mp3) > 3) {
                
    //mp3 stream received successfull, write mp3 and index
                
    file_put_contents($file$mp3);
                
    file_put_contents($announce_dir "index.log"$md5 "\t" $text "\n"FILE_APPEND|LOCK_EX);
            }
            else
                die (
    "Text to mp3 failed");
        }
        return 
    $file;
    }

    ?>
    </html>
    Zuletzt geändert von Muecke; 28.11.2015, 22:51.

    Kommentar


      #17
      Ich habe mal einen Proof of Concept zum Auslesen der RDS-Information in python (mein erstes Python-Programm, bitte seit nachsichtig....) geschrieben. Bisher gibt es nur einen Text zum groupswrite aus. Ich bin noch am Überlegen, wie ich die Werte am Besten in den Homeserver bekomme. Wahrscheinlich über UDP-Pakete über einen dedizierten port. Aktuell geht das Script von 2 Quellen aus.

      Aktuell macht das Script auch noch keinen Reconnect, falls die Verbindung irgendwann abbricht.

      - Muecke

      Code:
      #!/usr/bin/python
      
      import os
      import socket
      import sys
      
      #//// SETTINGS OF Russound Device ////
      host = '7.0.2.75'
      port = 9621
      macAddr = '00:21:c7:00:29:4c'
      controller = 1
      eibd_connect='/usr/local/bin/groupswrite ip:7.0.7.101 '
      #Define list of sources (only a single digit)
      sourcelist = (1, 2)
      #Connection expires every expMin Minutes and will be renewed
      expMin = 30
      GA = []
      GA.append('')
      
      # GAs for Source 1
      GA.append({'name':'7/1/1', 'channel':'7/1/1', 'radioText':'7/1/2'})
      # GAs for Source 2
      GA.append({'name':'7/2/1', 'channel':'7/2/1', 'radioText':'6/0/100'})
      
      #////// CONNECT //////////////////////////////////////////////////////
      try:
          s = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
          print ('connecting...')
          s.connect( (host, port))
      
      except socket.error:
          print ('error occured, try to wake device using WOL...')
          
          #////// try to wake the russound ////////////////////////////
          
          addr_byte = macAddr.split(':')
          hw_addr = struct.pack('BBBBBB', int(addr_byte[0], 16),
              int(addr_byte[1], 16),
              int(addr_byte[2], 16),
              int(addr_byte[3], 16),
              int(addr_byte[4], 16),
              int(addr_byte[5], 16))
      
          msg = '\xff' * 6 + hw_addr * 16
              
          # send magic packet
          dgramSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
          dgramSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
          dgramSocket.sendto(msg, ("255.255.255.255", 9))
      
          #/////// now try to connect again //////////////////////////
          s = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
          print ('reconnecting...')
          s.connect( (host, port))
      
      s.send('GET System.status\r\n')
      status = s.recv(1024)
      print ('status of device: ' + status)
      
      # Enable WATCH for all sources
      for l in sourcelist:
          print ('WATCH S[' + str(l) + '] ON EXPIRESIN ' + str(expMin) + '\r\n')
          s.send('WATCH S[' + str(l) + '] ON EXPIRESIN ' + str(expMin) + '\r\n')
      
      while True:
          result = s.recv(1024)
          
          #Split results in different lines
          for line in result.split('\r\n'):
              if len(line) > 0:
                  #print (line)
                  if line[0] is 'N':
                      words=line.split('=')
                      if words[0] == 'N EXPIRING':
                          # line is N EXPIRING=SOURCE-1, source is at pos 7
                          source=words[1][7]
                          print ('renewing ' + words[1])
                          print('WATCH S[' + source + '] ON EXPIRESIN  ' + str(expMin) + '\r\n')
                          s.send('WATCH S[' + source + '] ON EXPIRESIN  ' + str(expMin) + '\r\n')
                      else:
                          # line is something like N S[2].channel="SWR3", find source number and separate attribute
                          chars=words[0].split('.')
                          source=chars[0][4]
                          try:
                              print (eibd_connect + GA[int(source)][chars[1]] + ' ' + words[1])
      #                        retvalue = os.system(eibd_connect + GA[int(source)][chars[1]] + ' ' + words[1])
                              print (retvalue)
                          except:
                              print ('GA for "' + chars[1] + '" not found')
      
      s.close()

      Kommentar


        #18
        Hallo,

        ich habe mein Script mittlerweile weiterentwickelt und verbessert. Es läuft jetzt als multi-threading: Ein Prozess liest permanent den Status des Russouns aus und der zweite Prozess stellt alles mittels http Protokoll als json bereit.

        Aufruf mit rio.py -r <ip-adresse vom Russound> -w <Port für den Web-Service

        Bei mir läuft es ständig auf einem Raspberry Pi mit. Via http://<ip-adresses des Pi>/status bekommt man einige Status-Informationen, ansonsten gibt der Aufruf http://<ip-adresses des Pi> alle JSON Infos zurück.

        Damit könnte man auch relativ leicht die aktuelle RDS Information an den Homeserver, z.B. via UDP schicken.

        Vielleicht kann es jemand gebrauchen.

        Gruesse
        Matthias

        PHP-Code:
        #!/usr/bin/python3
        # -*- coding: iso-8859-15 -*-
        # Version 1.1 02.02.2019 - First release
        # Version 1.2 28.02.2019 - Added activeZone to Source array

        import os
        import socket
        import sys
        import optparse
        import time
        import re
        import datetime
        import threading
        import json
        import syslog
        import struct
        from collections import defaultdict

        class recursivedefaultdict(defaultdict):
            
        def __init__(self):
                
        self.default_factory type(self)
                
        ZoneConfig=recursivedefaultdict()
        SourceConfig=defaultdict(dict)
        ZoneCount=defaultdict(dict)
        ControllerType=defaultdict(dict)

        #//// SETTINGS oF Russound Device ////
        controllers = [1]
        ignorezones = [8]
        ignoresources = [6,7,8]
        debugLevel=0
        debugTarget
        =0
        ConvertErrorStr
        =""
        ConvertErrorHex=""
        ConvertErrorDateTime=datetime.datetime(1970,1,1)
        ConnectErrorDate=ConvertErrorDateTime
        LastReadDateTime
        =datetime.datetime.now()
        TimebetweenRead=LastReadDateTime-LastReadDateTime
        MaxTimeReadDiff
        =TimebetweenRead
        MaxTimeReadDiffDate
        =LastReadDateTime


        Channels 
        = { \
            
        'Antenne BY''94.00',\
            
        'Antenne FFM''106.20',\
            
        'AFN''105.15',\
            
        'Bayern1''91.20',\
            
        'Bayern2''92.15',\
            
        'Bayern3''92.45',\
            
        'BIG FM''100.95',\
            
        'FFH''90.75', \
            
        'harmony.fm''107.50',\
            
        'hr1''87.60',\
            
        'hr2''99.45',\
            
        'hr3''88.55',\
            
        'hr-info''88.55',\
            
        'SWR1''94.65',\
            
        'SWR2''94.95',\
            
        'SWR3''96.20',\
            
        'WDR2''101.25',\
            
        'WDR3''101.70',\
            
        'YouFM''89.80'
            
        }

        DefChannel 'SWR3'

        def debugFunction(levelmsg):
            global 
        debugLeveldebugTarget

            
        if debugTarget == 1:
                if 
        level <= debugLevel:
                    
        syslog.syslog(msg)
            if 
        debugTarget == 2:
                if 
        level <= debugLevel:
                    print(
        msg)
                
        def set_keepalive(sockafter_idle_sec=10interval_sec=3max_fails=3):
            
        # Set TCP keepalive on an open socket.

            # It activates after 10 second (after_idle_sec) of idleness,
            # then sends a keepalive ping once every 3 seconds (interval_sec),
            # and closes the connection after 5 failed ping (max_fails), or 15 seconds
            #
            
        sock.setsockopt(socket.SOL_SOCKETsocket.SO_KEEPALIVE1)
            
        sock.setsockopt(socket.IPPROTO_TCPsocket.TCP_KEEPIDLEafter_idle_sec)
            
        sock.setsockopt(socket.IPPROTO_TCPsocket.TCP_KEEPINTVLinterval_sec)
            
        sock.setsockopt(socket.IPPROTO_TCPsocket.TCP_KEEPCNTmax_fails)
            
        debugFunction(0'Socket Options defined: Idle (sec)=' str(after_idle_sec) + \
                
        ', Interval (sec)=' str(interval_sec) + ', MaxFail=' str(max_fails))
            
        def connectRussound():
            global 
        hostportslastconnectDeviceVersionZoneConfigZoneCountControllerTypeSourceCount
            
        #////// CONNECT //////////////////////////////////////////////////////
            
            
        connected=False
            
        while not connected:
                try:
                    
        socket.socketsocket.AF_INETsocket.SOCK_STREAM)
                    
        #print ('connecting...')
                    
        s.connect( (hostport))
                    
        debugFunction(0'Socket: ' str(s))
                    
        connected=True
                except socket
        .error:
                    
        debugFunction(0'error occured, try to wake device using WOL...')
                    
                    if 
        macAddr:
                        
        #////// try to wake the russound ////////////////////////////
                        
                        
        addr_byte macAddr.split(':')
                        
        hw_addr struct.pack('BBBBBB'int(addr_byte[0], 16),
                            
        int(addr_byte[1], 16),
                            
        int(addr_byte[2], 16),
                            
        int(addr_byte[3], 16),
                            
        int(addr_byte[4], 16),
                            
        int(addr_byte[5], 16))

                        
        msg b'\xff' hw_addr 16
                        debugFunction
        (0'try to send WOL packet.')
                
                        
        # send magic packet
                        
        dgramSocket socket.socket(socket.AF_INETsocket.SOCK_DGRAM)
                        
        dgramSocket.setsockopt(socket.SOL_SOCKETsocket.SO_BROADCAST1)
                        
        dgramSocket.sendto(msg, ("255.255.255.255"9))

                    
        debugFunction(0'Wait for 60 secs..')
                    
        time.sleep(60)

            
        s.send('VERSION\r'.encode())
            
        res s.recv(1024).decode().split('"')
            
        DeviceVersion res[1]
            
        debugFunction(0'Version of device: ' DeviceVersion)
            
            
        # Read config for each connected Controller
            
        for c in controllers:
                
        s.send(('GET C[' str(c) + '].type\r').encode())
                
        res s.recv(1024).decode().split('"')
                
        ControllerType[c] = res[1]
                
        debugFunction(0'Type of device: ' ControllerType[c])

                
        # Read zones of a Controller
                
        ZoneCount[c]=0
                
        while True:
                    
        s.send(('GET C[' str(c) + '].Z[' str(ZoneCount[c]+1) + '].name\r').encode())
                    
        res s.recv(1024).decode()
                    if 
        res[0] == 'S'#Success
                        
        ZoneCount[c] += 1
                    
        else:
                        
        debugFunction(2"ZoneCount=" str(ZoneCount[c]))
                        break

            
        # Read Number if Sources
            
        SourceCount=0
            
        while True:
                
        s.send(('GET S[' str(SourceCount+1) + '].name\r').encode())
                
        res s.recv(1024).decode()
                if 
        res[0] == 'S'#Success
                    
        SourceCount += 1
                
        else:
                    
        debugFunction(2"SourceCount=" str(SourceCount))
                    break
                    
            
        debugFunction(0'WATCH SYSTEM ON')
            
        s.send('WATCH SYSTEM ON\r'.encode())
            
        lastconnect=datetime.datetime.now()
                
            
        # Enable WATCH for all zones
            
        for c in controllers:
                for 
        l in range(1ZoneCount[c]+1):
                    try:
                        
        ignorezones.index(l# Test Zone in ignore list
                        
        debugFunction(1'Ignore Zone ' str(l))
                    
        except:
                        
        s.send(('WATCH C[' str(c) + '].Z[' str(l) + '] ON\r').encode())
                    
            
        # Enable WATCH for all sources
            
        for l in range(1SourceCount+1):
                try:
                    
        ignoresources.index(l# Test Source in ignore list
                    
        debugFunction(1'Ignore Source ' str(l))
                
        except:
                    
        s.send(('WATCH S[' str(l) + '] ON\r').encode())
            
            
        # Set TCP timeout to reconnect in case of a network outtage
            
        set_keepalive(s)


        # Convert Umlaut, no clue what the Charset is    
        def checkCharSet(byteStr):
            
        i=0
            
        while len(byteStr):
                if 
        byteStr[i] == 0x97:        # ö Ö -> \xd6
                    
        byteStr[i] = 0xf6
                elif byteStr
        [i] == 0x91:    # ä Ä -> \xc4
                    
        byteStr[i] = 0xe4
                elif byteStr
        [i] == 0x99:    # ü Ü -> \xdc  ß -> \xdf é=\u0083  Ī=\u0084
                    
        byteStr[i] = 0xfc
                
                i
        +=1
                
            
        return byteStr

        def countActiveSources
        ():
            global 
        ZoneConfigSourceConfig
            
            
        for s in SourceConfig:
                
        SourceConfig[s]["activeZones"]=0

            
        for controller in ZoneConfig:
                for 
        zone in ZoneConfig[controller]:
                    
        source=ZoneConfig[controller][zone]["currentSource"]

                    if 
        ZoneConfig[controller][zone]["status"] == "ON":
                        
        SourceConfig[source]["activeZones"] +=1
            
        def readRussound
        ():
            global 
        DeviceStatusZoneConfigSourceConfigLastReadLastReadDateTimeTimebetweenRead,\
                
        MaxTimeReadDiffMaxTimeReadDiffDateConvertErrorStrConvertErrorHexConvertErrorDateTimeConnectErrorDate

            
        # Initial connect
            
        connectRussound();

            
        # Main loop to read controller updates
            
        while True:
                try:
                    
        debugFunction(1'Before read')
                    
        result s.recv(1024)
                    
        debugFunction(1'after read' str (result))                                                          

                    
        TimebetweenRead=datetime.datetime.now() - LastReadDateTime
                    
                    
        if MaxTimeReadDiff TimebetweenRead:
                        
        MaxTimeReadDiff=TimebetweenRead
                        MaxTimeReadDiffDate
        =datetime.datetime.now()

                    
        LastReadDateTime=datetime.datetime.now()

                    for 
        line in result.split(b'\r\n'): #Split results in different lines
                        
        if len(line) > 0:
                            try:
                                
        line=checkCharSet(bytearray(line))
                                
        line line.decode('iso-8859-1')
                            
        except:
                                
        line line.decode('iso-8859-1''ignore')
                                
        ConvertErrorHex=''.join(hex(ord(x))[2:] for x in line)
                                
        debugFunction (0ConvertErrorHex)
                                
        debugFunction(0'Convert Error: ' line)
                                
        ConvertErrorStr=line
                                ConvertErrorDateTime
        =datetime.datetime.now()
                    
                            
        LastRead=line
                            
        if line[0is 'N':
                                if 
        re.search(r'N System.status\="(.*)"$'line):  #N System.status="OFF" | N System.status="ON"
                                    
        res=re.split(r'N System.status\="(.*)"$'line0);
                                    
        DeviceStatus=res[1]
                                    
        debugFunction(0"SYSTEM: " line)
                                    
                                
        elif re.search(r'N C\[(\d)\]\.Z\[(\d)\]\.(\w+)\="(.*)"$'line):  #N C[1].Z[5].name="Wohnzimmer"
                                    
        res=re.split(r'N C\[(\d)\]\.Z\[(\d)\]\.(\w+)\="(.*)"$'line0);
                                    
        ZoneConfig[res[1]][res[2]][res[3]]=res[4]
                                    
        debugFunction(0"ZONE: " line)

                                    if 
        res[3] == "status" and res[4] == "OFF":
                                        
        ZoneConfig[res[1]][res[2]]["volume"]=ZoneConfig[res[1]][res[2]]["turnOnVolume"]
                                        
        debugFunction(0"ZONE " res[2] + ": Set Volume to " ZoneConfig[res[1]][res[2]]["volume"])
                                    if 
        res[3] == "status" or res[3] == "currentSource"# Change of Sources
                                        
        countActiveSources();
                                    
                                
        elif re.search(r'N S\[(\d)\]\.(\w+)\="(.*)"$'line): #N S[5].type="DMS-3.1 Media Streamer"
                                    
        res=re.split(r'N S\[(\d)\]\.(\w+)\="(.*)"$'line0);
                                    
        SourceConfig[res[1]][res[2]]=res[3]
                                    
        debugFunction(1"SOURCE: " line)
                                    
                                else:
                                    
        debugFunction(0"ERROR: " line)

                
        except Exception as err:
                    
        ConnectErrorDate=datetime.datetime.now()
                    
        debugFunction(0"EXCEPTION: " str(err))
                    
        connectRussound()

            
        s.close()

        def ws():
            global 
        lastconnectZoneConfigSourceConfigDeviceVersionDeviceStatusSourceCountLastRead,\
            
        LastReadDateTimeMaxTimeReadDiffDateTimebetweenReadMaxTimeReadDiffConvertErrorStrConvertErrorHex,\
            
        ConvertErrorDateTimeConnectErrorDate
            HOST 
        ''

            
        listen_socket socket.socket(socket.AF_INETsocket.SOCK_STREAM)
            
        listen_socket.setsockopt(socket.SOL_SOCKETsocket.SO_REUSEADDR1)
            
        listen_socket.bind((HOSTwport))
            
        listen_socket.listen(1)
            
        debugFunction (2'Serving HTTP on port %s ...' wport)
            
        startdate=datetime.datetime.now()
            
            while 
        True:
                
        client_connectionclient_address listen_socket.accept()
                
        request client_connection.recv(1024).decode('utf-8''ignore')
                
        now datetime.datetime.now()
                
                
        result=""
                
        if re.search(r'^GET /(\w*) HTTP'request0):  
                    
        res=re.split(r'^GET /(\w*) HTTP'request0);
                    
        result=res[1].lower()
                
                
        http_response "HTTP/1.1 200 OK\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\nContent-Type: application/json\n\n"
                
        if result == 'zoneconfig':
                    
        http_response += json.dumps(ZoneConfig)

                
        elif result == 'sourceconfig':
                    
        http_response += json.dumps(SourceConfig)

                
        elif result == 'channels':
                    
        http_response += json.dumps(Channels)

                
        elif result == 'defaultchannels':
                    
        http_response += json.dumps(DefChannel)

                
        elif result == 'status':
                    
        http_response += \
                        
        '{ "StartDate": ' json.dumps(startdate.strftime("%d.%m.%Y %H:%M:%S")) + \
                        
        ', "LastReconnect": ' json.dumps(lastconnect.strftime("%d.%m.%Y %H:%M:%S")) + \
                        
        ', "ConnectErrorDate": ' json.dumps(ConnectErrorDate.strftime("%d.%m.%Y %H:%M:%S")) + \
                        
        ', "DeviceVersion": ' json.dumps(DeviceVersion) + \
                        
        ', "DeviceStatus": ' json.dumps(DeviceStatus) + \
                        
        ', "ZoneCount": ' json.dumps(ZoneCount) + \
                        
        ', "ControllerType": ' json.dumps(ControllerType) + \
                        
        ', "CountSource": ' json.dumps(SourceCount) + \
                        
        ', "ConvertErrorStr": ' json.dumps(ConvertErrorStr) + \
                        
        ', "ConvertErrorHex": ' json.dumps(ConvertErrorHex) + \
                        
        ', "ConvertErrorDateTime": ' json.dumps(ConvertErrorDateTime.strftime("%d.%m.%Y %H:%M:%S")) + \
                        
        ', "MaxDiffDate": ' json.dumps(MaxTimeReadDiffDate.strftime("%d.%m.%Y %H:%M:%S")) + \
                        
        ', "TimebetweenRead": ' json.dumps(str(TimebetweenRead)) + \
                        
        ', "MaxDiffTimebetweenRead": ' json.dumps(str(MaxTimeReadDiff)) + \
                        
        ', "LastRead": ' json.dumps(LastRead) + \
                        
        ', "LastReadDateTime": ' json.dumps(LastReadDateTime.strftime("%d.%m.%Y %H:%M:%S")) + \
                        
        '}'

                
        else:
                    
        http_response += \
                        
        '{ "ZoneConfig": ' json.dumps(ZoneConfig) + \
                        
        ', "SourceConfig": ' json.dumps(SourceConfig) + \
                        
        ', "Channels": ' json.dumps(Channels) + \
                        
        ', "DefaultChannel": ' json.dumps(DefChannel) + \
                        
        ', "StartDate": ' json.dumps(startdate.strftime("%d.%m.%Y %H:%M:%S")) + \
                        
        ', "LastReconnect": ' json.dumps(lastconnect.strftime("%d.%m.%Y %H:%M:%S")) + \
                        
        ', "DeviceVersion": ' json.dumps(DeviceVersion) + \
                        
        ', "DeviceStatus": ' json.dumps(DeviceStatus) + \
                        
        ', "CountSource": ' json.dumps(SourceCount) + \
                        
        '}'
                
        client_connection.sendall(http_response.encode())
                
        client_connection.close()
                
        def main(argv):
            global 
        hostwportportdebugTargetdebugLevelmacAddr
            
            parser 
        optparse.OptionParser()
            
        parser.add_option('-d''--debug',
                
        dest="debugLevel",
                default=
        0,
                
        action="store",
                
        type="int",
            )
            
        parser.add_option('-t''--target',
                
        dest="debugTarget",
                default=
        0,
                
        action="store",
                
        type="int",
            )
            
        parser.add_option('-r''--russound',
                
        dest="russound",
                
        action="store",
                
        type="string",
            )
            
        parser.add_option('-w''--wport',
                
        dest="wport",
                default=
        8080,
                
        action="store",
                
        type="int",
            )
            
        parser.add_option('-c''--port',
                
        dest="port",
                default=
        9621,
                
        action="store",
                
        type="int",
            )
            
        parser.add_option('-m''--mac',
                
        dest="mac",
                
        action="store",
                
        type="string",
            )

            
        optionsremainder parser.parse_args()
            if 
        options.russound is None:
                
        parser.error('Russound address not given')
                
        sys.exit(2)
            
            
        host=options.russound
            wport
        =options.wport
            port
        =options.port
            macAddr
        =options.mac
            debugLevel
        =options.debugLevel
            debugTarget
        =options.debugTarget
            
        if __name__ == "__main__":
           
        main(sys.argv[1:])
          
        t1 threading.Thread(target=ws)
        t2 threading.Thread(target=readRussound)
        t2.daemon True
        t1
        .daemon True  # thread dies when main thread (only non-daemon thread) exits.
        t1.start()
        t2.start()

        t1.join()
        t2.join() 
        Zuletzt geändert von maque; 03.03.2019, 12:17.

        Kommentar


          #19
          Top!!
          Schau ich mir definitiv demnächst genauer an!

          Danke!
          Grüße aus Leipzig

          Philipp

          Kommentar


            #20
            Läuft bei mir schon über mehrere Wochen stabil.... Melde Dich einfach, wenn es klemmt

            Kommentar


              #21
              Ich nochmal... läuft das standalone oder nur mit EibPC oder ähnlichem?

              Ich frage wegen der Zeile: eibd_connect='/usr/local/bin/groupswrite ip:7.0.7.101'




              Grüße aus Leipzig

              Philipp

              Kommentar


                #22
                Das Script läuft mittlerweile völlig autonom, die Integration in Eib war nur in der ersten Version drin, die ich gepostet hatte. Ich werde in wenige Tagen die Option eingebaut haben, einzelne Attribute mittels TCP oder UDP an bestimmte IP-Adressen zu schicken.

                Kommentar


                  #23
                  Zitat von maque Beitrag anzeigen
                  Das Script läuft mittlerweile völlig autonom, die Integration in Eib war nur in der ersten Version drin, die ich gepostet hatte. Ich werde in wenige Tagen die Option eingebaut haben, einzelne Attribute mittels TCP oder UDP an bestimmte IP-Adressen zu schicken.
                  Schonmal an ein NodeRed Plugin gedacht um mit anderen Systemen interagieren zu können?
                  Grüße aus Leipzig

                  Philipp

                  Kommentar


                    #24
                    Noch nicht, ich kenne es bisher noch nicht. Schau mir es aber mal an
                    Zuletzt geändert von maque; 04.03.2019, 10:34.

                    Kommentar


                      #25
                      Die Intergration des Scripts ist leicht möglich, siehe Screen shot. Ein komplettes Plugin dafür zu entwicken, ist sicher ziemlich aufwendig.
                      screenshot_11.png

                      Kommentar


                        #26
                        Ich hab zur Zeit eine Menge zu tun und komme nicht dazu irgendwas zu testen. Sobald sich das ändert teste ich das alles mal.
                        Was hast du in dem tcp Node stehen? Kannst den Flow bitte mal, gern auch per pn, schicken?
                        Grüße aus Leipzig

                        Philipp

                        Kommentar


                          #27
                          So, anbei die Funktion mit senden über UDP oder TCP. Beispiele für die Config stehen im riod.ini Der Code noch nicht sehr fehlertolerant für falsche Konfigurattion. Damit läßt sich der Status auch im Homeserver, NodeRed etc. leicht einbinden.

                          Gruesse
                          Matthias



                          Code:
                          #!/usr/bin/python3
                          # -*- coding: iso-8859-15 -*-
                          #
                          # This is the config file for riod.py script
                          # The purpose of riod.py to permanelty run as a daemon on a unix hosts, e.g. raspberry
                          # and provide the status of a Russound device MCA-C3, MCA-C5 or MCA-88
                          # Location of riod.ini could be: current dir, /etc or /usr/local/etc
                          #
                          # V1.1 02.02.2019 - First release
                          # V1.2 28.02.2019 - Added activeZone to Source array
                          # V1.3 09.03.2019 - Improve Config read
                          
                          
                          import os
                          import socket
                          import sys
                          import configparser
                          import optparse
                          import time
                          import re
                          import datetime
                          import threading
                          import json
                          import syslog
                          import struct
                          from collections import defaultdict
                          
                          class recursivedefaultdict(defaultdict):
                              def __init__(self):
                                  self.default_factory = type(self)
                                  
                          ZoneConfig=recursivedefaultdict()
                          SourceConfig=defaultdict(dict)
                          ZoneCount=defaultdict(dict)
                          ControllerType=defaultdict(dict)
                          
                          #//// SETTINGS oF Russound Device ////
                          debugLevel=0
                          debugTarget=0
                          ConvertErrorStr=""
                          ConvertErrorHex=""
                          ConvertErrorDateTime=datetime.datetime(1970,1,1)
                          ConnectErrorDate=ConvertErrorDateTime
                          LastReadDateTime=datetime.datetime.now()
                          TimebetweenRead=LastReadDateTime-LastReadDateTime
                          MaxTimeReadDiff=TimebetweenRead
                          MaxTimeReadDiffDate=LastReadDateTime
                          
                          DefChannel = 'SWR3'
                          
                          def debugFunction(level, msg):
                              global debugLevel, debugTarget
                          
                              if debugTarget == 1:
                                  if level <= debugLevel:
                                      syslog.syslog(msg)
                              if debugTarget == 2:
                                  if level <= debugLevel:
                                      print(msg)
                                  
                          def send2Network(options, msg):
                          
                              # netcat debug UDP: nc -kluv <port No>
                              # netcat debug TCP: nc -klv <port No>
                          
                              res=options.split(':') # tcp:127.0.0.1:5001
                              prot=res[0].lower()
                              host=res[1]
                              port=int(res[2])
                              msg=msg.strip()
                              
                              if msg:
                                  debugFunction(3, "Send Message :" + msg + " to Host " + host + " with Prot " + prot + " via Port " + str(port) )
                          
                                  if prot == "udp":
                                      s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
                          
                                      s.sendto(bytes(msg, "utf-8"), (host, port))
                          
                                  elif  prot == "tcp":
                                      try:
                                          s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                                          s.connect((host, port))
                                          s.sendall(bytes(msg, "utf-8"))
                                          s.close
                                      except Exception as err:
                                          debugFunction(0, "EXCEPTION - send2Network: " + str(err))
                                  else:
                                      debugFunction(2, 'Illegal Protocol ' + msg)
                              else:
                                  debugFunction(0, 'Received empty string!')
                                      
                                      
                          
                          def set_keepalive(sock, after_idle_sec=10, interval_sec=3, max_fails=3):
                              # Set TCP keepalive on an open socket.
                          
                              # It activates after 10 second (after_idle_sec) of idleness,
                              # then sends a keepalive ping once every 3 seconds (interval_sec),
                              # and closes the connection after 5 failed ping (max_fails), or 15 seconds
                              #
                              sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
                              sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, after_idle_sec)
                              sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec)
                              sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails)
                              debugFunction(0, 'Socket Options defined: Idle (sec)=' + str(after_idle_sec) + \
                                  ', Interval (sec)=' + str(interval_sec) + ', MaxFail=' + str(max_fails))
                              
                          def connectRussound(host, port):
                              global lastconnect, DeviceVersion, ZoneConfig, ZoneCount, ControllerType, SourceCount
                              #////// CONNECT //////////////////////////////////////////////////////
                              
                              connected=False
                              while not connected:
                                  try:
                                      s = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
                                      s.connect( (host, port))
                                      debugFunction(0, 'Socket: ' + str(s))
                                      connected=True
                                  except socket.error:
                                      debugFunction(0, 'error occured, try to wake device using WOL...')
                                      
                                      if macAddr:
                                          #////// try to wake the russound ////////////////////////////
                                          
                                          addr_byte = macAddr.split(':')
                                          hw_addr = struct.pack('BBBBBB', int(addr_byte[0], 16),
                                              int(addr_byte[1], 16),
                                              int(addr_byte[2], 16),
                                              int(addr_byte[3], 16),
                                              int(addr_byte[4], 16),
                                              int(addr_byte[5], 16))
                          
                                          msg = b'\xff' * 6 + hw_addr * 16
                                          debugFunction(0, 'try to send WOL packet.')
                                  
                                          # send magic packet
                                          dgramSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                                          dgramSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
                                          dgramSocket.sendto(msg, ("255.255.255.255", 9))
                          
                                      debugFunction(0, 'Wait for 60 secs..')
                                      time.sleep(60)
                          
                              s.send('VERSION\r'.encode())
                              res = s.recv(1024).decode().split('"')
                              DeviceVersion = res[1]
                              debugFunction(0, 'Version of device: ' + DeviceVersion)
                              
                              # Read config for each connected Controller
                              for c in controllers:
                                  s.send(('GET C[' + str(c) + '].type\r').encode())
                                  res = s.recv(1024).decode().split('"')
                                  ControllerType[c] = res[1]
                                  debugFunction(0, 'Type of device: ' + ControllerType[c])
                          
                                  # Read zones of a Controller
                                  ZoneCount[c]=0
                                  while True:
                                      s.send(('GET C[' + str(c) + '].Z[' + str(ZoneCount[c]+1) + '].name\r').encode())
                                      res = s.recv(1024).decode()
                                      if res[0] == 'S': #Success
                                          ZoneCount[c] += 1
                                      else:
                                          debugFunction(2, "ZoneCount=" + str(ZoneCount[c]))
                                          break
                          
                              # Read Number if Sources
                              SourceCount=0
                              while True:
                                  s.send(('GET S[' + str(SourceCount+1) + '].name\r').encode())
                                  res = s.recv(1024).decode()
                                  if res[0] == 'S': #Success
                                      SourceCount += 1
                                  else:
                                      debugFunction(2, "SourceCount=" + str(SourceCount))
                                      break
                                      
                              debugFunction(0, 'WATCH SYSTEM ON')
                              s.send('WATCH SYSTEM ON\r'.encode())
                              lastconnect=datetime.datetime.now()
                                  
                              # Enable WATCH for all zones
                              for c in controllers:
                                  for l in range(1, ZoneCount[c]+1):
                                      try:
                                          ignorezones.index(l) # Test Zone in ignore list
                                          debugFunction(1, 'Ignore Zone ' + str(l))
                                      except:
                                          s.send(('WATCH C[' + str(c) + '].Z[' + str(l) + '] ON\r').encode())
                                      
                              # Enable WATCH for all sources
                              for l in range(1, SourceCount+1):
                                  try:
                                      ignoresources.index(l) # Test Source in ignore list
                                      debugFunction(1, 'Ignore Source ' + str(l))
                                  except:
                                      s.send(('WATCH S[' + str(l) + '] ON\r').encode())
                              
                              # Set TCP timeout to reconnect in case of a network outtage
                              set_keepalive(s)
                              return s    
                          
                          
                          # Convert Umlaut, no clue what the Charset is    
                          def checkCharSet(byteStr):
                              i=0
                              while i < len(byteStr):
                                  if byteStr[i] == 0x97:        # ö Ö -> \xd6
                                      byteStr[i] = 0xf6
                                  elif byteStr[i] == 0x91:    # ä Ä -> \xc4
                                      byteStr[i] = 0xe4
                                  elif byteStr[i] == 0x99:    # ü Ü -> \xdc  ß -> \xdf é=\u0083  Ī=\u0084
                                      byteStr[i] = 0xfc
                                  
                                  i+=1
                                  
                              return byteStr
                          
                          def countActiveSources():
                              global ZoneConfig, SourceConfig
                              
                              for s in SourceConfig:
                                  SourceConfig[s]["activeZones"]=0
                          
                              for controller in ZoneConfig:
                                  for zone in ZoneConfig[controller]:
                                      source=ZoneConfig[controller][zone]["currentSource"]
                          
                                      if ZoneConfig[controller][zone]["status"] == "ON":
                                          SourceConfig[source]["activeZones"] +=1
                              
                          def readRussound(host, port, remoteTargets):
                              global DeviceStatus, ZoneConfig, SourceConfig, LastRead, LastReadDateTime, TimebetweenRead,\
                                  MaxTimeReadDiff, MaxTimeReadDiffDate, ConvertErrorStr, ConvertErrorHex, ConvertErrorDateTime, ConnectErrorDate
                          
                              # Initial connect
                              s=connectRussound(host, port);
                          
                              # Main loop to read controller updates
                              while True:
                                  try:
                                      result = s.recv(1024)
                                      debugFunction(1, 'Read: ' + str (result))                                                          
                          
                                      TimebetweenRead=datetime.datetime.now() - LastReadDateTime
                                      
                                      if MaxTimeReadDiff < TimebetweenRead:
                                          MaxTimeReadDiff=TimebetweenRead
                                          MaxTimeReadDiffDate=datetime.datetime.now()
                          
                                      LastReadDateTime=datetime.datetime.now()
                          
                                      for line in result.split(b'\r\n'): #Split results in different lines
                                          if len(line) > 0:
                                              try:
                                                  line=checkCharSet(bytearray(line))
                                                  line = line.decode('iso-8859-1')
                                              except:
                                                  line = line.decode('iso-8859-1', 'ignore')
                                                  ConvertErrorHex=''.join(hex(ord(x))[2:] for x in line)
                                                  debugFunction (0, ConvertErrorHex)
                                                  debugFunction(0, 'Convert Error: ' + line)
                                                  ConvertErrorStr=line
                                                  ConvertErrorDateTime=datetime.datetime.now()
                                      
                                              LastRead=line
                                              if line[0] is 'N':
                                                  if re.search(r'N System.status\="(.*)"$', line):  #N System.status="OFF" | N System.status="ON"
                                                      res=re.split(r'N System.status\="(.*)"$', line, 0);
                                                      DeviceStatus=res[1]
                                                      debugFunction(0, "SYSTEM: " + line)
                                                      
                                                  elif re.search(r'N C\[(\d)\]\.Z\[(\d)\]\.(\w+)\="(.*)"$', line):  #N C[1].Z[5].name="Wohnzimmer"
                                                      res=re.split(r'N C\[(\d)\]\.Z\[(\d)\]\.(\w+)\="(.*)"$', line, 0);
                                                      source=ZoneConfig[res[1]][res[2]]["currentSource"]
                          
                                                      debugFunction(2, "ZONE: %s, Attr: %s, Current:%s" % ( res[2],res[3],json.dumps(source)))
                          
                                                      if res[3] == "status":
                                      
                                                          if ZoneConfig[res[1]][res[2]]["status"] == "ON" and res[4] == "OFF":
                                                              try:
                                                                  SourceConfig[source]["activeZones"] -=1
                                                              except:
                                                                  SourceConfig[source]["activeZones"] =0
                                                                  
                                                          elif ZoneConfig[res[1]][res[2]]["status"] == "OFF" and res[4] == "ON":
                                                              try:
                                                                  SourceConfig[source]["activeZones"] +=1
                                                              except:
                                                                  SourceConfig[source]["activeZones"] = 1
                                                      
                                                      elif res[3] == "currentSource":
                          
                                                          if ZoneConfig[res[1]][res[2]]["status"] == "ON":
                                                              try:
                                                                  SourceConfig[ZoneConfig[res[1]][res[2]]["currentSource"]]["activeZones"] -=1
                                                              except:
                                                                  SourceConfig[ZoneConfig[res[1]][res[2]]["currentSource"]]["activeZones"] = 0
                                                              
                                                              try:
                                                                  SourceConfig[res[4]]["activeZones"] +=1
                                                              except:
                                                                  SourceConfig[res[4]]["activeZones"] = 1
                          
                                                      ZoneConfig[res[1]][res[2]][res[3]]=res[4]
                                                      debugFunction(1, "ZONE: " + line)
                                                      
                                                      if "ZoneConfig" in remoteTargets:
                                                          send2Network(remoteTargets["ZoneConfig"], json.dumps(ZoneConfig))
                          
                                                      if res[3] == "status" and res[4] == "OFF":
                                                          ZoneConfig[res[1]][res[2]]["volume"]=ZoneConfig[res[1]][res[2]]["turnOnVolume"]
                                                          debugFunction(0, "ZONE " + res[2] + ": Set Volume to " + ZoneConfig[res[1]][res[2]]["volume"])
                                                      if res[3] == "status" or res[3] == "currentSource": # Change of Sources
                                                          countActiveSources();
                                                      
                                                  elif re.search(r'N S\[(\d)\]\.(\w+)\="(.*)"$', line): #N S[5].type="DMS-3.1 Media Streamer"
                                                      debugFunction(3, "SOURCECONFIG: " + json.dumps(SourceConfig))
                                                      res=re.split(r'N S\[(\d)\]\.(\w+)\="(.*)"$', line, 0);
                                                      SourceConfig[res[1]][res[2]]=res[3]
                                                      debugFunction(2, "SOURCE: " + line)
                          
                                                      if "SourceConfig" in remoteTargets:
                                                          send2Network(remoteTargets["SourceConfig"], json.dumps(SourceConfig))
                          
                                                      if res[2] in remoteTargets:
                          #                                print (res[2] + " found in " + remoteTargets[res[2]])
                                                          send2Network(remoteTargets[res[2]], res[3])
                                                      
                                                  else:
                                                      debugFunction(0, "ERROR: " + line)
                          
                                  except Exception as err:
                                      ConnectErrorDate=datetime.datetime.now()
                                      debugFunction(0, "EXCEPTION: " + str(err))
                                      s=connectRussound()
                          
                              s.close()
                          
                          def ws():
                              global lastconnect, ZoneConfig, SourceConfig, DeviceVersion, DeviceStatus, SourceCount, LastRead,\
                              LastReadDateTime, MaxTimeReadDiffDate, TimebetweenRead, MaxTimeReadDiff, ConvertErrorStr, ConvertErrorHex,\
                              ConvertErrorDateTime, ConnectErrorDate
                              HOST = ''
                          
                              listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                              listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                              listen_socket.bind((HOST, wport))
                              listen_socket.listen(1)
                              debugFunction (2, 'Serving HTTP on port %s ...' % wport)
                              startdate=datetime.datetime.now()
                              
                              while True:
                                  client_connection, client_address = listen_socket.accept()
                                  request = client_connection.recv(1024).decode('utf-8', 'ignore')
                                  now = datetime.datetime.now()
                                  
                                  result=""
                                  if re.search(r'^GET /(\w*) HTTP', request, 0):  
                                      res=re.split(r'^GET /(\w*) HTTP', request, 0);
                                      result=res[1].lower()
                                  
                                  http_response = "HTTP/1.1 200 OK\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\nContent-Type: application/json\n\n"
                                  if result == 'zoneconfig':
                                      http_response += json.dumps(ZoneConfig)
                          
                                  elif result == 'sourceconfig':
                                      http_response += json.dumps(SourceConfig)
                          
                                  elif result == 'channels':
                                      http_response += json.dumps(Channels)
                          
                                  elif result == 'defaultchannels':
                                      http_response += json.dumps(DefChannel)
                          
                                  elif result == 'status':
                                      http_response += \
                                          '{ "StartDate": ' + json.dumps(startdate.strftime("%d.%m.%Y %H:%M:%S")) + \
                                          ', "LastReconnect": ' + json.dumps(lastconnect.strftime("%d.%m.%Y %H:%M:%S")) + \
                                          ', "ConnectErrorDate": ' + json.dumps(ConnectErrorDate.strftime("%d.%m.%Y %H:%M:%S")) + \
                                          ', "DeviceVersion": ' + json.dumps(DeviceVersion) + \
                                          ', "DeviceStatus": ' + json.dumps(DeviceStatus) + \
                                          ', "ZoneCount": ' + json.dumps(ZoneCount) + \
                                          ', "ControllerType": ' + json.dumps(ControllerType) + \
                                          ', "CountSource": ' + json.dumps(SourceCount) + \
                                          ', "ConvertErrorStr": ' + json.dumps(ConvertErrorStr) + \
                                          ', "ConvertErrorHex": ' + json.dumps(ConvertErrorHex) + \
                                          ', "ConvertErrorDateTime": ' + json.dumps(ConvertErrorDateTime.strftime("%d.%m.%Y %H:%M:%S")) + \
                                          ', "MaxDiffDate": ' + json.dumps(MaxTimeReadDiffDate.strftime("%d.%m.%Y %H:%M:%S")) + \
                                          ', "TimebetweenRead": ' + json.dumps(str(TimebetweenRead)) + \
                                          ', "MaxDiffTimebetweenRead": ' + json.dumps(str(MaxTimeReadDiff)) + \
                                          ', "LastRead": ' + json.dumps(LastRead) + \
                                          ', "LastReadDateTime": ' + json.dumps(LastReadDateTime.strftime("%d.%m.%Y %H:%M:%S")) + \
                                          '}'
                          
                                  else:
                                      http_response += \
                                          '{ "ZoneConfig": ' + json.dumps(ZoneConfig) + \
                                          ', "SourceConfig": ' + json.dumps(SourceConfig) + \
                                          ', "Channels": ' + json.dumps(Channels) + \
                                          ', "DefaultChannel": ' + json.dumps(DefChannel) + \
                                          ', "StartDate": ' + json.dumps(startdate.strftime("%d.%m.%Y %H:%M:%S")) + \
                                          ', "LastReconnect": ' + json.dumps(lastconnect.strftime("%d.%m.%Y %H:%M:%S")) + \
                                          ', "DeviceVersion": ' + json.dumps(DeviceVersion) + \
                                          ', "DeviceStatus": ' + json.dumps(DeviceStatus) + \
                                          ', "CountSource": ' + json.dumps(SourceCount) + \
                                          '}'
                                  client_connection.sendall(http_response.encode())
                                  client_connection.close()
                                  
                          def main(argv):
                              global host, wport, port, debugTarget, debugLevel, macAddr, remoteTargets, controllers, Channels, ignoresources, ignorezones
                              
                              config = configparser.ConfigParser()
                              config.optionxform = str
                              config.read(['riod.ini', '/etc/riod.ini', '/usr/local/etc/riod.ini'])
                              try:
                                  remoteTargets=dict(config.items('RemoteTargets'))
                                  debugFunction(2, "remoteTargets: " + json.dumps(remoteTargets))
                              except:
                                  remoteTargets=[]
                          
                              try:
                                  Channels=dict(config.items('Channels'))
                              except:
                                  Channels=[]
                                      
                              try:
                                  ignoresources=config.get("Common","IgnoreSources").split(',')
                                  ignoresources = list(map(int, ignoresources))
                              except:
                                  ignoresources=[]
                              debugFunction(2, "IgnoreSources: " + json.dumps(ignoresources))
                          
                              
                              try:
                                  ignorezones=config.get("Common","IgnoreZones").split(',')
                                  ignorezones = list(map(int, ignorezones))
                              except:
                                  ignorezones=[]
                              debugFunction(2, "IgnoreZones: " + json.dumps(ignorezones))
                          
                              
                              try:
                                  macAddr=config.get("Common","MAC")
                              except:
                                  maxAddr=None
                              
                          
                              controllers=config.get("Common","Controllers").split(',')    
                              host=config.get("Common","Russound")
                              port=int(config.get("Common","Port"))
                              wport=int(config.get("Webserver","Port"))
                              
                              parser = optparse.OptionParser()
                              parser.add_option('-d', '--debug',
                                  dest="debugLevel",
                                  default=0,
                                  action="store",
                                  type="int",
                              )
                              parser.add_option('-t', '--target',
                                  dest="debugTarget",
                                  default=0,
                                  action="store",
                                  type="int",
                              )
                              parser.add_option('-r', '--russound',
                                  dest="russound",
                                  action="store",
                                  type="string",
                              )
                              parser.add_option('-w', '--wport',
                                  dest="wport",
                                  action="store",
                                  type="int",
                              )
                              parser.add_option('-p', '--port',
                                  dest="port",
                                  default=9621,
                                  action="store",
                                  type="int",
                              )
                              parser.add_option('-m', '--mac',
                                  dest="mac",
                                  action="store",
                                  type="string",
                              )
                          
                              options, remainder = parser.parse_args()
                              
                              if options.russound is not None:
                                  host=options.russound
                              if options.wport is not None:
                                  wport=options.wport
                              if options.mac is not None:
                                  macAddr=options.mac
                              if options.port is not None:
                                  port=options.port
                                  
                              if controllers is None:
                                  controllers=["1"]
                                  
                              if host is None:
                                  print('Russound address not given')
                                  sys.exit(2)
                                  
                              if port is None:
                                  port=9621
                                  
                              if wport is None:
                                  print('Webserver Port not given')
                                  sys.exit(2)
                          
                              debugLevel=options.debugLevel
                              debugTarget=options.debugTarget
                              
                              debugFunction(1, "Russound address: " + host + ", Port: " + str(port))
                              debugFunction(1, "Webserver Port: " + str(wport))
                              debugFunction(1, "Controller : " + json.dumps(controllers))
                          
                              debugFunction(1, "IgnoreZone : " + json.dumps(ignorezones))
                              debugFunction(1, "IgnoreSource : " + json.dumps(ignoresources))
                              debugFunction(1, "remote Target : " + json.dumps(remoteTargets))
                          
                              
                          if __name__ == "__main__":
                              main(sys.argv[1:])
                              
                          t1 = threading.Thread(target=ws)
                          t2 = threading.Thread(target=readRussound, args=(host, port, remoteTargets))
                          t2.daemon = True
                          t1.daemon = True  
                          t1.start()
                          t2.start()
                          
                          t1.join()
                          t2.join()
                          Dann noch die riod.ini
                          Code:
                          # This is the config file for riod.py script
                          # The purpose of riod.py to permanelty run as a daemon on a unix hosts, e.g. raspberry
                          # and provide the status of a Russound device MCA-C3, MCA-C5 or MCA-88
                          # Location of riod.ini could be: current dir, /etc or /usr/local/etc
                          #
                          [Common]
                          # IP or DNS name of Russound MCA-C3, MCA-C5 or MCA-88
                          Russound=mcac5
                          # TCP Port to connect to Russound (supposed to be 9621)
                          Port=9621
                          # Default is 1, if multiple controller are connect, seperated by comma e.g. 1,2,3
                          Controllers=1
                          # Hardware MAC address of Russound to send WOL packet
                          MAC=00:21:c7:00:29:4c
                          # Zones to excluded, seperated by comma, e.g. 7,8 or 8
                          IgnoreZones=8
                          # Sources to excluded, seperated by comma, e.g. 7,8 or 8
                          IgnoreSources=6,7,8
                          
                          [Webserver]
                          #Listen port of the script, to provide status, ZoneConfig, SourceConfig, etc information
                          Port=9621
                          
                          [RemoteTargets]
                          # This section defines, what attributes to be send over the network
                          # it could be any attribute of Zone or Source information.
                          # ZoneConfig means all Zones status as JSON
                          # SourceConfig means all Source status as JSON
                          # Syntax: <Attribute to be send>=<tcp|udp>:<host>:<port>
                          #
                          radioText=udp:127.0.0.1:5003
                          SourceConfig=udp:127.0.0.1:5001
                          ZoneConfig=udp:127.0.0.1:5002
                          #programServiceName=tcp:127.0.0.1:5001
                          # netcat debug UDP: nc -kluv  127.0.0.1 5001
                          # netcat debug TCP: nc -klv  127.0.0.1 5001
                          
                          [Channels]
                          #Unitymedia Hessen
                          Antenne BY=94.00
                          Antenne FFM=106.20
                          AFN=105.15
                          Bayern1=91.20
                          Bayern2=92.15
                          Bayern3=92.45
                          BIG FM=100.95
                          FFH=90.75
                          harmony.fm=107.50
                          hr1=87.60
                          hr2=99.45
                          hr3=88.55
                          hr-info=88.55
                          SWR1=94.65
                          SWR2=94.95
                          SWR3=96.20
                          WDR2=101.25
                          WDR3=101.70
                          YouFM=89.80
                          Zuletzt geändert von maque; 09.03.2019, 18:25.

                          Kommentar


                            #28
                            Hallo, arbeite zur Zeit mit einem ähnlichen php-Script um die Stati des Russound MCA C5 auf den KNX Bus zu bekommen und einfache Parameter zu ändern. Habe jetzt in der RIO Doko gelesen, dass es nicht nur GET sondern auch den WATCH Befehl gibt. Man könnte dann, wenn ich es richtig verstanden habe, im Push Verfahren arbeiten. Hat jemand Erfahrung damit? Ferner habe ich einen gebrauchten Russound DMS 3.1 Media Server erworben. Man kann ebenfalls über Rio Song Metadaten auslesen. Hat jemand Erfahrung damit?
                            Viele Grüße
                            Michael Schlosser

                            Kommentar


                              #29
                              Hallo Michael,

                              das ist genau meine Konfig: MCA-C5 mit DMS3.1

                              schau Dir mal mein Python Daemon auf gitlab an:https://gitlab.com/maque/Russound-RIO-daemon


                              Das läuft auf jedem Raspi und kann eingentlich alles. Da das den Zustand des MCA auch cached, ist es viel schneller als die Original Russound App.

                              Viele Grüße
                              Matthias
                              Zuletzt geändert von maque; 19.11.2020, 11:44.

                              Kommentar


                                #30
                                Hallo!

                                In einen Homeserver-Baustein gepackt hat das noch niemand, oder? Das wäre echt mal schick!
                                Möchte den Komfort meiner Installation nicht mehr missen!

                                Kommentar

                                Lädt...
                                X