HMCCUCHAN generischer Ansatz

Begonnen von martinp876, 21 Dezember 2019, 16:34:33

Vorheriges Thema - Nächstes Thema

kjmEjfu

Ich habe ehrlich gesagt keine Ahnung, was dieser ganzer Kram mit den Entitäten für einen Vorteil hat.
Aber ich habe - vielleicht auch deshalb - eine Bitte: eventuelle Umbauten am Modul bitte möglichst minimalinvasiv oder rückwärtskompatibel durchführen  :)
Migriere derzeit zu Home Assistant

martinp876

Hi KjmEjfu.
Zap wird darauf achten.
Ich für meinen Teil werde wir gesagt mir der aktuellen Implementierung nicht leben. Aktuell reden wir über interne Strukturen und Methoden , welche erst einmal eben intern sind.
Sollten die Auswirkungen größer sein und nicht als add-on sondern "nur" zusätzliche funktionen bieten ist alles klar. Sollten andere Änderungen notwendig sein (so weit not garnicht ausgemacht!!!) kann man eine neue Version erstellen und diese alternativ einpflegen.

Das Modul ist gut - und mit Sicherheit zukunftsweisend. Allerdings aus meiner Sicht zu kompliziert und fragil. Es lässt sehr viel zu, nutzt zu wenig Info welche von der CCU geliefert werden sollte (muss). Also eine hervorragende Basis, noch kein Endprodukt.
Die Entscheidung muss zap treffen.

martinp876

Hi Zap, ich habe meine Prozedur die Device-Daten zu erfassen einmal ausgemessen. Nach 14s brutto habe ich 80 entities geprüft, 14 komplett eingelesen, also die Description (besser specification? Ich glaube dasnutzt du als Begriff) eingelesen.
Das halte ich erst einmal für ok. Installationen mir deutlich mehr devices nutzen doch immer wieder gleiche Typen. Mehr als 20 Device typen (models) sind unwahrscheinlich. Mehr entites kosten keine Zeit, Tipisch noch nicht einmal eine einzige Anfrage an der ccu (also noch schneller).

Aus meiner Sicht ist eine Funktion wie gepostet also kein Performance Problem. Die Abfrage erfolgt schlieslich nur einmal je FHEM boot je deviceTyp

Ich bin auf deine erste Test - version gespannt. Bitte posten wenn verfügbar.

zap

#18
Hi Martin,

vielen Dank für den Code-Beitrag. Hilft. Ich möchte nicht zu viel auf einmal ändern. Gerade die CCU Zugriffe haben immer wieder für Probleme gesorgt (Timeouts bis hin zum Abschießen des HMIPServer Prozesses auf der CCU). Dennoch wird die 4.4 bereits eine Menge zusätzlicher Infos aus der CCU intern abspeichern. Bin aber immer noch am testen. Leider verhält sich nicht alles so, wie von EQ-3 dokumentiert. Werte, die angeblich immer vorhanden sind, fehlen bei einigen Device Types.

Nur nochmal damit wir vom gleichen reden: Die CCU bietet 2 Zugriffsebenen: 1. ReGa, 2. RPC.

Die ReGa Schnittstelle hat 2 große Vorteile:
1. Mit Homematic Script hat man viele Möglichkeiten, auf die CCU zuzugreifen oder Informationen abzufragen
2. Die Daten kommen (beim Lesen) von der CCU und belasten nicht den Duty Cycle (das ist aber auch ein Nachteil, da die Daten nicht immer top-aktuell sind)

Die RPC Schnittstelle hat vor und Nachteile:
+ RPC Requests sind schneller als Homematic Script Anfragen
+ Mit RPC Requests kann man Dinge tun, die mit HM-Script nicht gehen (Geräte verknüpfen)
- Viele RPC Requests gehen direkt bis zum Gerät und belasten damit den Duty Cycle. Davor warnt sogar Eq-3 in der RPC Doku

Doku für Wired und BidCos-RF: https://www.eq-3.de/Downloads/eq3/download%20bereich/hm_web_ui_doku/HM_XmlRpc_API.pdf

Doku addon für HMIP: https://www.eq-3.de/Downloads/eq3/download%20bereich/hm_web_ui_doku/HMIP_XmlRpc_API_Addendum.pdf

BTW: Mittlerweile habe ich festgestellt, dass die CCU an einen registrierten RPC Server (HMCCURPCPROC) die DeviceDescription frei haus liefert. Die Info muss also nicht explizit abgefragt werden. Die ParamSet Infos aber schon.

Grüße
Dirk


2xCCU3 mit ca. 100 Aktoren, Sensoren
Entwicklung: FHEM auf Proxmox Debian VM
Produktiv inzwischen auf Home Assistant gewechselt.
Maintainer: HMCCU, (Fully, AndroidDB)

martinp876

Hallo Dirk,
Ich nutze aktuell nur das RPC inerface wie von dir implementiert - und tests, was mit die CCU preisgibt. Die Doku kenne  und nutze ich.

- das mit den Duty-Cycle der RPC Schnittstelle verstehe ich nicht. So wie ich es sehe wird nicht direkt "in die Luft gegriffen". Ich erhalten bei den Abfragen nur Daten aus der CCU. Ich kann nicht beobachten, dass etwas am Funk passiert. Warum also Duty-Cycle?
- Das mit den Scripten ist mir noch fremd. Ich muss also scripten in die CCU implementieren um zu kommunizieren? Hier würden mich Details interessieren. Wir kommen die Scripten dahin? Über FHEM?
- Ich frage bislang im Wesentlichen Config-daten ab. Die CCU cached diese - was man unbedingt berücksichtigen muss. Die Daten können uralt sein, da der Cache nie expired. Daraus resultiert: a)Wenig Funklast b) ich kann RPC nutzen ohne zu funken c) Daten sind evtl nicht aktuell d) ein clear cach ist möglich. erzeugt Funklast wenn neu gelesen wird, aber wann ist noch unklar.

Zum "urlesen" nach einem fhem -reboot oder re-connect (beides?) kann man alle Konfigurationen lesen unter nutzung von "listDevices". Ich kann dir die Funktion generieren - das geht schneller und geschmeidiger als die aktuell angebotene Funktion. Allerdings ist die Angebotene notwendig, wenn ein neuer Devicetyp gefunden/gepair/Instanziiert wurde.

ZitatLeider verhält sich nicht alles so, wie von EQ-3 dokumentiert. Werte, die angeblich immer vorhanden sind, fehlen bei einigen Device Types.
Beispiele bitte - aus dem BidCos sektor. Da bin ich besser aufgestellt.

ZitatIch möchte nicht zu viel auf einmal ändern.
Ich würde gerne in die Vollen gehen. Viele kleine Änderungen beinhalten viele Probleme und man hat am Ende viel mehr zu tun.
Allerdings brauche ich erst deine Gedanken zu einer möglichen Architektur.
Zum Einen reden wir von möglichen Änderungen in der Datenstruktur. Diese sind erst einmal für den User transparent, erlauben aber eine geordnete Programmierung. Hast du hier etwa geplant? Wird sich etwas ändern? Wie ist die Struktur der Daten gedacht?

Zum Zweiten können wir das User-Interface, besser Operator-Interface prüfen. Generell habe ich beo hmInfo auch mit "komplexen" Kommandos angefangen. Viele Filter-Möglichkeiten,.... Dann habe ich simplifizierte Kommandos eingeführt welche nur mit "click" auszuführen sind, kein Tippen. Also nur 1 Parameter, mehr gibt das Standard interface in FHEM nicht her.
Ziel sollte immer der Apple-Ansatz sein, dass ein User wenig wissen muss und sich die typischen Sachen automatisch einstellen. Nicht immer nachfragen. Im expert mode kann der geübte Operator dann nachbessern. Der Operator/User sollte mit Adressen wie Serialnumber oder RF Adresse nichts zu tun haben. Kann er sich ansehen, aber nie in ein Kommando eingeben. Das macht FHEM für ihn.

Wie ist deine Meinung?

Ich habe gerade einmal angefangen, Links und Peerings auszulesen. War zäh, jetzt habe ich erste Erfolge.

Gruß Martin



martinp876

Habe mir die struktur der ccu in wiki angesehen. Kurz. Mein Eindruck und meine Präferenz ist rpc. Ich sehe die ccu als inteligentes io. Die Schicht darüber würde gerne auslassen.
In jedem Fall würde ich vermeiden beide Schnittstellen zu nutzen. Ist das möglich?
Ich will mit der Schnittstelle nicht die ccu auf Vordermann bringen sondern die devices steuern.
Die ccu liefert beste arbeit in Sachen Funk Timing, aes, msg sequenzing und caching der dp und config.

Ich habe damit nur das rpc if und fhem zu bedienen. Tcl und rega sind aussen vor.

Was ist deine strategie?

martinp876

So, habe einmal darüber geschlafen.
- RPC ist hinreichend für alles, was man zum Device.steuern benötigt.
- ReGa erlaubt
  - auf die Namen in der CCU zuzugreifen
  - pre-prozessing von Daten.
   ? was kann ReGa noch, ausser die Namen-zuordnung? Ok. die Description fehlt noch - sehe ich nicht in RPC
   ? wir haben Perl von FHEM in Hintergrund. Welche Scripte sind für die ReGa Schnittstelle wirklich notwendig?

Performance-mäßig sehe ich keinen Unterschied zwischen den Schnittellen. Ethernet sollte schnell genug sein. Die ReGa Schnittstelle entlastet nicht die CCU sondern FHEM. Das ist m.E. nicht notwendig. Zustimmung?

Im HMCCUConf sehe ich eine Menge Arbeit für die Device-Definitionen. Willst du das wirklich in dieser Form aufrecht erhalten? Ich würde erwarten, dass die (wichtigen) eventMap intern durch die enmuns abgedeckt werden. Das muss dann "automatisch" aus der ccu geholt werden. Damit sind dann auch neue Devices sofort integriert.

martinp876

Hallo Dirk,

schon wieder ich :)
Ich habe die Abfrage der Description etwas optiniert.
Eingetragen wird:
- alle in der CCU bekannten Entites (device und Kanal)
    - ihre Beschreibung (rxmode,....)
    - ihre Basis-Daten (RF-Adresse, AES-Aktiv, Firmware, Roaming)
    - dekodierte Werte (enums)
    - Parameter  Beschreibung
Das ist ein Aufruf welche ich nach INIT_DONE machen würde, also wenn alle fhem.cfg gelesen sind.
Du müsstest es noch über alle RPC interfaces "loopen".
Findest du ein neues Device kannst du es auch nur für eines aufrufen.

Für mich ist das die Basis, einmal die entites darzustellen.
  - Abgleich der Entites CCU<->fhem
  - Darstellung der Beschreibung

p.s.: Ich warte noch auf deine vorab-Version zum Testen

sub HMCCU_syncDevices ()
{
    my $rpcH = $defs{d_rpc178046BidCos_RF};
    my $r = HMCCURPCPROC_SendRequest ($rpcH,"listDevices", "",""); # get all devices fro BidCos
    foreach my $h (@{$r}){ # split all entity description (devices and channels)
      HMCCU_evalDevDesc ($h,$rpcH);
      }
}

sub HMCCU_evalDevDesc ($$)
{
my ($descH,$rpcH)  = @_;  # hash to device description
    my %FLAGSdev  = (
           0x01 => "Visible"
          ,0x02 => "Internal"
          ,0x08 => "DontDelete"
          );
    my %DIRECTION  = (
           0 => "NONE"
          ,1 => "SENDER"
          ,2 => "RECEIVER"
          );
    my %RX_MODE = (
           0x01 => "ALWAYS"
          ,0x02 => "BURST"
          ,0x04 => "CONFIG"
          ,0x08 => "WAKEUP"
          ,0x10 => "LAZY_CONFIG"
          );
    my %FLAGSparam = (
           0x01 => "Visible"
          ,0x02 => "Internal"
          ,0x04 => "Transform"
          ,0x08 => "Service"
          ,0x10 => "Sticky"
          );
     
     
    my $addr  = defined $descH->{ADDRESS}    ? $descH->{ADDRESS}     : $descH->{PARENT};
    my $idx   = defined $descH->{INDEX}      ? $descH->{INDEX}       : "dev";           # channel number
    my $model = defined $descH->{PARENT_TYPE}? $descH->{PARENT_TYPE} : $descH->{TYPE}  ;# identify model for channel and device
    my $chTyp = defined $descH->{INDEX}      ? $descH->{TYPE}        : "dev";           # if not channel then chTyp = dev
    my $entDataOnly = (defined $HMCCU_DevDef{$model} && defined $HMCCU_DevDef{$model}{$chTyp}) ? 1 : 0; # no need for model description - already available
    foreach my $k (keys %$descH){
      my $devLevel = defined $descH->{ADDRESS} ? "dev" : "chn";
      if ($entDataOnly || $k =~ m/(^ADDRESS|TYPE|VERSION|PARENT|INDEX)/ ){ ## ignore this
        next;
        }
      elsif ($k =~ m/(RF_ADDRESS|INTERFACE|FIRMWARE|AES|ROAMING)/){## todo: add this to dynamic data
        $HMCCU_EntData{$addr}{$idx}{$k} = $descH->{$k};
        }
      elsif($k eq "CHILDREN"){
        $HMCCU_DevDef{$model}{$chTyp}{$k} = scalar @{$descH->{$k}};
        }
      elsif($k eq "PARAMSETS"){
        @{$HMCCU_DevDef{$model}{$chTyp}{$k}} = @{$descH->{$k}};     
        }
      elsif($k eq "FLAGS"){
        foreach (keys %FLAGSdev){
          $HMCCU_DevDef{$model}{$chTyp}{$k}{$FLAGSdev{$_}} = 1 if($_+0 & $descH->{$k}+0);
          }
        }
      elsif($k eq "RX_MODE"){
        foreach (keys %RX_MODE){
          $HMCCU_DevDef{$model}{$chTyp}{$k}{$RX_MODE{$_}} = 1 if($_+0 & $descH->{$k}+0);
          }
        }
      elsif($k eq "DIRECTION"){
        $HMCCU_DevDef{$model}{$chTyp}{$k} = $DIRECTION{$descH->{$k}};
        }
      else{
        $HMCCU_DevDef{$model}{$chTyp}{$k} = $descH->{$k};
        }
      }

    return if ($entDataOnly); # only once required
   
    foreach my $k (@{$HMCCU_DevDef{$model}{$chTyp}{PARAMSETS}}) { # foreach paramset
      my $res = HMCCURPCPROC_SendRequest ($rpcH,"getParamsetDescription", $addr.":".$idx,$k  );
      foreach my $var (keys %$res){
        my $logme= "";
        foreach my $varAttr (keys %{$res->{$var}}){
          if ($varAttr =~ m/(ID)/){
            next;
            }
          elsif($varAttr eq "SPECIAL"){
            foreach my $x (@{$res->{$var}{$varAttr}}){
              foreach my $tp (keys %{$x}){
                $HMCCU_ParamDesc{$var}{$varAttr}{$tp} = $x->{$tp};
                }
              }
            }
          elsif($varAttr eq "VALUE_LIST"){
            @{$HMCCU_ParamDesc{$var}{$varAttr}} = @{$res->{$var}{$varAttr}};           
            }
          elsif($varAttr eq "FLAGS"){
            foreach (keys %FLAGSparam){
              $HMCCU_ParamDesc{$var}{$varAttr}{$FLAGSparam{$_}} = 1 if($_+0 & $res->{$var}{$varAttr}+0);
              }
            }
          else{
            $HMCCU_ParamDesc{$var}{$varAttr} = $res->{$var}{$varAttr};         
            }
          }
        }
      }
}

zap

#23
Hi Martin,

zur Device Description:

Der "listDevices" Befehl muss nicht explizit aufgerufen werden. Das macht HMCCURPCPROC beim Starten des RPC Servers. Die CCU reagiert darauf mit "NewDevices" Events, die ein Array mit den Device Descriptions enthalten. Dann kommt HMCCU_UpdateDeviceTable (oder so ähnlich) zum Zug, das die Informationen im Hash vom IO Device ablegt (derzeit noch nicht alle). An der Stelle hänge ich im Moment, da die RPC Server als separate Prozesse laufen und etwas im Speichermanagement schief geht. Witzigerweise scheint das noch nie funktioniert zu haben ;)

UPDATE: Wieder mal auf die Doku verlassen und verloren. Man muss doch die Geräte explizit mit listDevices abfragen. Theorie: Die CCU ruft listDevices in HMCCURPCPROC auf, das antwortet mit einem leeren Array woraufhin die CCU für jedes Gerät einen newDevice Event schickt. Macht sie aber nicht :(

HMCCUConfig

Möchte ich mittelfristig loswerden. Habe bereits damit begonnen, auf FHEM Config Templates umzustellen, die auch von anderen Modulen bereits verwendet werden. Da kann man mit einer Definition gleich mehrere Device Types erschlagen.

ReGa vs. RPC

Über die Schnittstelle kommt man an die ganze restliche Logik dran, z.B. Räume, Kategorien (Licht, Heizung, usw). Man kann Programme ausführen, Systemvariablen lesen / schreiben usw. Für HMCCU essentiell sind die Gerätenamen. Sonst müsste man bei jeder Kanal- und Device-Definition in FHEM immer die Adresse kennen und angeben. Finde ich für den Nutzer etwas unhandlich.
RPC taugt hingegen zum Lesen / Schreiben von Datenpunkten und eben dem Auslesen der Device- und Paramset-Beschreibung => Für diese Zwecke brauchst Du also keine ReGa.
Ein großer Vorteil der ReGa ist, dass Zugriffe nicht zwingend zu einer Kommunikation zwischen CCU und dem Gerät führen (Duty Cycle). Bei RPC kommt das deutlich häufiger vor, siehe auch die entsprechenden Hinweise bei einzelnen RPC Methoden in der EQ-3 Doku. Mir ist nicht klar, ob die CCU auch bei anderen Requests von Zeit zu Zeit aktiv die Geräte fragt und wenn ja, nach welchen Kriterien (Alter der Informationen?) das passiert.

Homematic Script

Es gibt zwei Möglichkeiten, Homematic Script in der CCU auszuführen:
1. Man erstellt ein Programm in der CCU, das wiederum HM-Script Code enthält
2. Man schickt den Script Code per HTTP POST an die ReGa, die den Code ausführt
HMCCU unterstützt zwar beide Varianten, nutzt für die Kommunikation jedoch 2. Die Scripte findest Du in HMCCUConf.pm.

Weitere Planung meinerseits (erste Gedanken)

1. Ich werde versuchen, soviele Infos zu den Device Types wie möglich bereits in der anstehenden Version abzufragen und im internen Hash vom IO Device zu speichern. Dazu werde ich die schon vorhandenen Strukturen nutzen und erweitern:
$hash->{hmccu}{dev} und/oder $hash->{hmccu}{dp}
An der Stelle habe ich noch keine Idee, wie ich damit umgehe, dass mit einem Firmware Update neue Datenpunkte hinzukommen oder sich (eher selten) Wertemengen ändern. Gerade bei neuen HmIP Geräten kommt das öfters vor. Muss HMCCU dann auch noch die ältere Firmware Version unterstützen?

2. Nutzung von Config Templates anstelle der Defaults aus HMCCUConf.pm

3. Automatisches Setzen einiger Attribute bei der Definition von Devices, sofern bestimmte Funktionalitäten aus der Device Description ableitbar sind (z.B. LEVEL, STATE, SET_TEMPERATURE vorhanden => automatisch als state/control Datapoint setzen). Das erspart dem User zumindest bei den gebräuchlichsten Geräten die Attribut-Hölle.

Ich denke / hoffe, 1. hilft Dir erst mal am meisten.

Etwas Geduld bitte noch. Ich muss noch den listDevice/newDevices Bug beseitigen.

2xCCU3 mit ca. 100 Aktoren, Sensoren
Entwicklung: FHEM auf Proxmox Debian VM
Produktiv inzwischen auf Home Assistant gewechselt.
Maintainer: HMCCU, (Fully, AndroidDB)

martinp876

Hi Dirk,
Ungeduldig bin ich immer. Aber das ist kein primäres Problem.
Mir ist technisch unklar, was rpc mit dutycycle zu tun hat. Ach hier kommen die daten aus dem cache. Man kann diesen löschen,....  Aber warum rega besser sein soll kann ich nicht erkennen. Ich sehe auch keine Aktivitäten am Funk. Und nur hier gibt es dutycycle.
Rega hat zugriff auf die höheren level. Und wieder kenne ich deine Philosophie und Zielen nicht. In der ccu  kann man Räume usw definieren. Das interessiert mich nicht. Ich habe Räume in fhem. Ich wil nicht die gesamte funktion der ccu abbilden. Auch will ich kein gemischtes system. So wenig ccu als möglich.
Die namen sind notweñdig, also brauche ich rega. Sicher kommt noch etwas.

Ich habe nun fast eine version zum lesen aller device config und register. Die enums sind implementiert. Heute bekomme ich voraussichtlich keine zeit :( .wenn es fertig ist schicke ich es dir.
Neue devices müssen m.e. nicht automatisch erkannt werden. Schön wenn es geht, aber ein sync kommando sollte auch reichen. So oft kommt es nicht vor. Schon gar nicht plötzlich. Ist also prio 3.

Gruss Martin

zap

Hallo Martin,

noch ein Tipp: Wenn Du bei HMCCURPCPROC Devices im Attribut ccuflags das Flag expert setzt, hast Du den zusätzlichen set Befehl rpcrequest.

Damit kannst Du jeden RPC Request ausführen. Das Ergebnis wird im FHEM Fenster gedumpt.

Also z.B.

set d_rpcBidCos rpcrequest getDeviceDescription LEQ0919881
set d_rpcBidCos rpcrequest listDevices
set d_rpcBidCos rpcrequest getParamsetDescription LEQ0919881:1 VALUES

Mittlerweile denke ich an eine Speicherung der Art:

$hash->{hmccu}{dev}: Hier kommen die ganzen Device spezifischen Sachen rein, also z.B. Version und Firmware
$hash->{hmccu}{mod}{version/firmware}: Für die Devicemodel spezifischen Dinge (z.B. die ParameterSet Beschreibungen).

$hash->{hmccu}{dp} behalte ich aus Kompatibilitätsgründen erst mal bei, bis ich alle internen Funktionen umgestellt habe. Das kostet sonst zu viel Aufwand.

Um die Unterscheidung nach Firmware/Version wird man nicht herumkommen. Wenn ein Nutzer unterschiedliche Firmwarestände bei gleichen Geräten hat, gibt's sonst Probleme.

Grüße
Dirk

2xCCU3 mit ca. 100 Aktoren, Sensoren
Entwicklung: FHEM auf Proxmox Debian VM
Produktiv inzwischen auf Home Assistant gewechselt.
Maintainer: HMCCU, (Fully, AndroidDB)

martinp876

Hallo Dirk,

wo fange ich an.
Nachstehender Code liest alle Device configs aus. Du musst die globalenVariable eintragen. Die Subroutines einfach am Ende hineinkopieren.
Sicher erzähle ich die schon bekanntes :)

Die Hashes der RCP Schnittstelle hole ich noch nicht automatisch. Du musst an 2 Stellen
"$defs{d_rpc178046...}"
ersetzen. Muss natürlich noch geändert werden.
Ich mache dann ein
reload reload 88_HMCCU.pm

Ich arbeite in einem Terminal-Fenster. Angenehmer als die Web-Commandline, aber funktional identisch.
Dann, um die Definitionen der (aller) CCU entites zu lesen mache ein
{HMCCU_syncDevices ()}
Jetzt kannst du es testen.
{HMCCU_params(<entityName>)}
<entityName> ist der Name eines Kanals, also HMCCUCHN. DEV interessiert mich immer noch nicht.

Du erhältst alle Peerings und Register - in einer noch schlechten formatierung. Alle enums sind eingetragen. "Specials" noch nicht.

HMCCU_ParamDesc: Definition eines Parameters, min, max,... Das ist nicht model-spezifisch
HMCCU_DevDef: Definition eines Devices
    HMCCU_DevDef{<model>}{<channelType>}{<variable>} = value
    <channelType>: für das Device selbst, welchen keinen Typ hat setze ich "dev"
    Wenige Variable sind keine Werte sondern bspw ein Array
HMCCU_EntData: hier habe ich die Entity data
    HMCCU_EntData{<serial>}{<chn>}{<var>} = <value>
   <serial>: Seriennummer oder auch Addresse - verwende ich synonym
   <chn>: Nummer des Kanals. Es gibt immer 0 und 1. Dann bis zu 50. "dev" setze ich für das Device, welches kein Kanal ist.
   <var>: variable mit dem <value>

Ich würde dich bitten, die Struktur beizubehalten. Das kann man dann automatisch "befüllen"

Wenn du alles im HMCCU hash halten willst ist das ok. Sehe ich dann so:
$hash->{hmccu}{dev}: HMCCU_EntData
$hash->{hmccu}{mod}: HMCCU_DevDef
$hash->{hmccu}{par}: HMCCU_ParamDesc



#---Insert MPI---
use vars qw(%HMCCU_DevDef);
use vars qw(%HMCCU_ParamDesc);
use vars qw(%HMCCU_EntData);

my %HMCCU_DevDef;
my %HMCCU_ParamDesc;
my %HMCCU_EntData;

sub HMCCU_syncDevices ()
{
    my $rpcH = $defs{d_rpc178046HmIP_RF};
    foreach my $rpcH ($defs{d_rpc178046HmIP_RF},$defs{d_rpc178046BidCos_RF}){
      my $r = HMCCURPCPROC_SendRequest ($rpcH,"listDevices", "",""); # get all devices fro BidCos
      foreach my $h (@{$r}){ # split all entity description (devices and channels)
        HMCCU_evalDevDesc ($h,$rpcH);
        }
      }
    HMCCU_ccuNames();
}

sub HMCCU_evalDevDesc ($$)
{
my ($descH,$rpcH)  = @_;  # hash to device description
    my %FLAGSdev  = (
           0x01 => "Visible"
          ,0x02 => "Internal"
          ,0x08 => "DontDelete"
          );
    my %DIRECTION  = (
           0 => "NONE"
          ,1 => "SENDER"
          ,2 => "RECEIVER"
          );
    my %RX_MODE = (
           0x01 => "ALWAYS"
          ,0x02 => "BURST"
          ,0x04 => "CONFIG"
          ,0x08 => "WAKEUP"
          ,0x10 => "LAZY_CONFIG"
          );
    my %FLAGSparam = (
           0x01 => "Visible"
          ,0x02 => "Internal"
          ,0x04 => "Transform"
          ,0x08 => "Service"
          ,0x10 => "Sticky"
          );
     
     
    my $addr  = defined $descH->{ADDRESS}    ? $descH->{ADDRESS}     : $descH->{PARENT};
    my $idx   = defined $descH->{INDEX}      ? $descH->{INDEX}       : "dev";           # channel number
    my $model = defined $descH->{PARENT_TYPE}? $descH->{PARENT_TYPE} : $descH->{TYPE}  ;# identify model for channel and device
    my $chTyp = defined $descH->{INDEX}      ? $descH->{TYPE}        : "dev";           # if not channel then chTyp = dev
    my $entDataOnly = (defined $HMCCU_DevDef{$model} && defined $HMCCU_DevDef{$model}{$chTyp}) ? 1 : 0; # no need for model description - already available
    $HMCCU_EntData{$addr}{dev}{MODEL} = $model;
    $HMCCU_EntData{$addr}{$idx}{CHTyp} = $chTyp;
    foreach my $k (keys %$descH){
      my $devLevel = defined $descH->{ADDRESS} ? "dev" : "chn";
      if ($entDataOnly || $k =~ m/(^ADDRESS|TYPE|VERSION|PARENT|INDEX)/ ){ ## ignore this
        next;
        }
      elsif ($k =~ m/(RF_ADDRESS|INTERFACE|FIRMWARE|AES|ROAMING)/){## todo: add this to dynamic data
        $HMCCU_EntData{$addr}{$idx}{$k} = $descH->{$k};
        }
      elsif($k eq "CHILDREN"){
        $HMCCU_DevDef{$model}{$chTyp}{$k} = scalar @{$descH->{$k}};
        }
      elsif($k eq "PARAMSETS"){
        @{$HMCCU_DevDef{$model}{$chTyp}{$k}} = @{$descH->{$k}};     
        }
      elsif($k eq "FLAGS"){
        foreach (keys %FLAGSdev){
          $HMCCU_DevDef{$model}{$chTyp}{$k}{$FLAGSdev{$_}} = 1 if($_+0 & $descH->{$k}+0);
          }
        }
      elsif($k eq "RX_MODE"){
        foreach (keys %RX_MODE){
          $HMCCU_DevDef{$model}{$chTyp}{$k}{$RX_MODE{$_}} = 1 if($_+0 & $descH->{$k}+0);
          }
        }
      elsif($k eq "DIRECTION"){
        $HMCCU_DevDef{$model}{$chTyp}{$k} = $DIRECTION{$descH->{$k}};
        }
      else{
        $HMCCU_DevDef{$model}{$chTyp}{$k} = $descH->{$k};
      }
    }
    $HMCCU_DevDef{$model}{$chTyp}{LinkRole} = $HMCCU_DevDef{$model}{$chTyp}{LINK_SOURCE_ROLES} ne "" ? "source"
                                             :$HMCCU_DevDef{$model}{$chTyp}{LINK_TARGET_ROLES} ne "" ? "target"
                                             :"none";
   
    return if ($entDataOnly); # only once required
   
    foreach my $k (@{$HMCCU_DevDef{$model}{$chTyp}{PARAMSETS}}) { # foreach paramset
      my $res = HMCCURPCPROC_SendRequest ($rpcH,"getParamsetDescription", $addr.":".$idx,$k  );
      foreach my $var (keys %$res){
        if ($var =~ m/fault/){
          Log 1,"General go a fault $addr : $idx : $model";
          next;
        }
        foreach my $varAttr (keys %{$res->{$var}}){
          if ($varAttr =~ m/(ID)/){
            next;
            }
          elsif($varAttr eq "SPECIAL"){
            foreach my $x (@{$res->{$var}{$varAttr}}){
              foreach my $tp (keys %{$x}){
                $HMCCU_ParamDesc{$var}{$varAttr}{$tp} = $x->{$tp};
                }
              }
            }
          elsif($varAttr eq "VALUE_LIST"){
            @{$HMCCU_ParamDesc{$var}{$varAttr}} = @{$res->{$var}{$varAttr}};           
            }
          elsif($varAttr eq "FLAGS"){
            foreach (keys %FLAGSparam){
              $HMCCU_ParamDesc{$var}{$varAttr}{$FLAGSparam{$_}} = 1 if($_+0 & $res->{$var}{$varAttr}+0);
              }
            }
          else{
            $HMCCU_ParamDesc{$var}{$varAttr} = $res->{$var}{$varAttr};         
            }
          }
        }
      }
}

sub HMCCU_ccuNames (){
    my $hash = $defs{HMccu};
my $response = HMCCU_HMScriptExt ($hash, "!GetDeviceList", undef, undef, undef);

return if ($response eq '' || $response =~ /^ERROR:.*/);

my @scrlines = split /[\n\r]+/,$response;
foreach my $hmdef (@scrlines) {
      my @hmdata = split /;/,$hmdef;
      next if (scalar ((@hmdata) == 0) || ($hmdata[0] != m/^[CD]/));
      my $addr = $hmdata[0] eq "C" ? $hmdata[1] : $hmdata[2];
      my $name = $hmdata[0] eq "C" ? $hmdata[2] : $hmdata[3];
      $HMCCU_EntData{$addr}{ccuName} = $name;
    }
}

sub HMCCU_params($)
{
my ($name)  = @_;  # address of the entity - i.e. the serial number
    my $rpcIf = $defs{d_rpc178046BidCos_RF};
    my $addr = $defs{$name}{ccuaddr};
    return "$name is undefined" if(!defined $defs{$name}
                                 ||!defined $defs{$name}{ccuaddr});
    return "$name not found in CCU" if(!defined $HMCCU_EntData{$addr});
    my ($devAddr,$idx) = split(":",$addr);
    $idx = "dev" if (!defined $idx);

    my $res="    addr:$addr";
    $res .= "\ndev:$_  : ".$HMCCU_EntData{$devAddr}{dev}{$_} foreach (sort keys %{$HMCCU_EntData{$devAddr}{dev}});
    $res .= "\n    $_  : ".$HMCCU_EntData{$addr}{$idx}{$_}   foreach (sort keys %{$HMCCU_EntData{$addr}{$idx}});
    $res .= "\n";

    my @addrList = ($addr);
    push @addrList,$devAddr if ($HMCCU_EntData{$addr}{$idx}{CHTyp} eq "MAINTENANCE");
    foreach my $dispAddr (@addrList){
      my ($dispSer,$dispIdx) = split(":",$dispAddr);
      $dispIdx = "dev" if (!defined $dispIdx);
      my $model = $HMCCU_EntData{$dispAddr}{dev}{MODEL};
      my $CHTyp = $HMCCU_EntData{$dispAddr}{$dispIdx}{CHTyp};
      my $role  = defined $HMCCU_DevDef{$model}{$CHTyp}{LinkRole} ? $HMCCU_DevDef{$model}{$CHTyp}{LinkRole} : "none";

      foreach my $ps (@{$HMCCU_DevDef{$model}{$CHTyp}{PARAMSETS}}){
        Log 1,"General now type $dispSer/$dispAddr : $ps : role:$role - $model: $CHTyp";
        my @peers;
        my @psList;
        if($ps eq "LINK"){ # with link we have to search each peer
          my $rl = HMCCURPCPROC_SendRequest ($rpcIf,"getLinks", $dispAddr, "");
          my $sStr = $role eq "source" ? "RECEIVER" : "SENDER";
          foreach my $hl (@{$rl}){
            push @peers, map{$hl->{$_}} grep /$sStr/,keys %$hl;
          }
          next if(scalar (@peers) == 0 || $role eq "none");
          Log 1,"General my peers :".join(",",@peers);
          @psList = @peers;
        }
        else{
          push @psList,$ps;
        }
        foreach my $psL (@psList){
          my $r = HMCCURPCPROC_SendRequest($rpcIf,"getParamset", $dispAddr, $psL);
          foreach my $k (sort keys %$r) {
            next if($k =~ m/(faultCode|faultString)/);
            my $val = $r->{$k};
            $val = $HMCCU_ParamDesc{$k}{VALUE_LIST}[$val] if (defined $HMCCU_ParamDesc{$k}{VALUE_LIST}
                                                          && scalar @{$HMCCU_ParamDesc{$k}{VALUE_LIST}} >= $val);
            $res .= " $dispIdx   $psL: $k = $val\n";
          }
        }
      }
    }
    return $res;
}

martinp876

So hört sich das sehr gut an.
Allerdings bin ich mir bei ein paar deiner Bemerkungen über deine Ziele immer noch unsicher.
Hier ein paar meiner ziele und Definitionen:
1) ich sehe die ccu als intelligentes io. In keinem Fall als Controller. Fhem ist der Controller!
=》 mich interessieren keine Räume der ccu.
=》 ich brauche die ccunames um debuggen zu können
=》ich werde Informationen über ccu alarme und queue  Status benötigen um alarme zu generieren. Fhem muss anzeigen, wenn Handlungsbedarf  besteht.

2) ich mache mir Gedanken über Duty cycle. Aber das hat nichts mit rpc zu tun. Ich habe recht viel getestet in culhm. Macht mir keine Angst.
3) im Gegensatz zu  culhm muss ich bei der ccu noch ergründen, wie der Cache der device Register funktioniert. Bei culhm konnte ich nach Belieben und Bedarf den Cache in fhem Update. Ich habe aber schon einen Ansatz gesehen, es hier gleich zu tun

4) ob ich die ccu zur aggregierung weiterer Information einsetzen muss oder sollte wird sich ergeben. Bspw  sd Team.

martinp876

Hi Dirk, ich schon wieder :)
Folgendes funktioniert bei mir schon wie ich es mir etwa vorstelle.
Zitata) Definitionen lesen
  - HMCCU_syncDevices synchronisiert CCU und FHEM. Mir gefällt der Begriff "cache" an dieser Stellt.  Wie im Kommentar beschrieben ist die Prozedur nach Init aufzurufen (man kann sich an den Event init-done hängen) Damit sind dann auch die RPC schnittstellen schon vorhanden.
  - Als Kommando kann man den re-sync der Definitionen sowie der Device-liste auch aufrufen mit entsprechender Gewichtung: anlegen fehlender Entites, Bereinigen(anlegen und löschen), verify und force (update aller devices,auch der schon angelegten)
  => Die Optionen sollten in einem Kommando der HMCCU zu Verfügung stehen
  => Ich würde explizit keine (KEINE) weiteren optionen zulassen. Das ist verwirrend. Der Anwender kann das Rename nachträglich durchführen. Ich benenne es systematisch... aber nicht unbedingt aus der CCU ableitbar. Auch Filter würde ich nicht vorsehen. Will ein Anwender einen Kanal nicht in FHEM "betreiben" kann er diesen in unused verschieben o.ä.
  ??> offen ist die Ausnahme: Die 50 Kanäle der RPC instanz würde ich nicht anlegen... oder doch?
  ??> die Kommandos habe ich xxxChn genannt damit noch platz ist für Device kommandos. Aber nicht von mir :)
  ??> eine LookupTable für <addr> => <FHEM-name> ist notwendig und muss verankert werden. die andere Richtung habe ich im Device
  => Die Geräte haben "Funktionskanäle" und einen Maintenance kanal (kanal 0). Weiter gibt es noch den "Device-kanal". Bei Channels verwalte ich kanal 0 und Device in einer Entity. Das passt zusammen und macht sinn.
  ??> Kanal 0 entspricht dann dem "Device" aus CUL_HM (fast). Hier bekommt man das Interface (RPC) her sowie die CCU (man könnte mehrere haben) . Auch zu verwalten, noch offen ist die CCU-RPC entity, welcher das Device zugeordnet ist. Diese wird für peerings notwendig sein und hat Auswirungen auf Kommunikationseigenschafften und Abläufe.
  ??> die Abfrage dauert lange. Einen Prozess zu spawnen wäre sinnvoll. Ist aber nicht besonders kritisch, da es nur bei Reboot und on-demand stattfindet. Lässliche Sünde.
  ??> external RPC ist nicht berücksichtigt.

Zitatb) Beschreibung abfragen HMCCU_paramDef
Hier bekommt man die Beschreibung der Register/Parameter einer entity. Dieses Kommando sollte in HMCCUCHN verankert werden und Return in einem separaten Fenster

Zitatb) Beschreibung abfragen HMCCU_param
Hier bekommt man die Inhalteder Register/Parameter einer entity. Dieses Kommando sollte in HMCCUCHN verankert werden.
  => Die gelesenen Werte sollte man speichern. Es ist nun so, dass diese Werte im Device vorliegen. Die CCU cached diese Daten. FHEM könnte also einen 2nd level cache anlegen. Halte ich für Sinnvoll. Diese Werte sollten aber in der Entity abgelegt werden. Wenn sie nicht existiert werden sie nicht abgelegt
  ??> die Ablage habe ich noch nicht vorgesehen
  ??> in HMCCUCHN sehe ich 2 Aufruf-schärfen: Ausgabe der Daten im FHEM-cache, wenn vorhanden und Updage-cache. Ich will immer ein kommando, FHEM und CCU zu synchronisieren.
  => Werte aus "values" müssen in Readings abgebildet werden
  => Werte aus "Links" kommen nach "helper". Eine "get <channel> regVal" gibt den Wert bei Bedarf aus. Ein "get <channel> regTable" gibt alles auf einmal aus.
  => Werte aus "Master" kommen nach "helper"
  ??> Die Peerliste muss noch besser eingebunden werden. Kommt nach readings und internals (damit man clicken kann)

ZitatNoch ein Kommentar zum Attribut "nochn0"
in meiner Welt kommt das nicht vor. Aber: Wenn du dies anbietest musst (MUSST) du aufräumen. Es gibt nichts schlechteres als falsche Werte (Readings). Mit nochn0 werden einige Readings nicht mehr upgedatet. An diesem Zeitpunkt sind diese Werte also per definition falsch. Es gilt das Verursacher-prinzip im Bezug auf Aufräumen und raus-wischen. Da du dieses attribut anbietest ist es deine Aufgabe, bereits bei der Implementiertung auch das Aufräumen zu implementieren.

Ich bastle noch weiter - allmählich habe ich eigentlich alle primären Funktionen zusammen - dank deiner guten Interfaces.
P.S.: Mir ist klar, dass du einige Details schon implementiert hast. das habe ich nicht komplett ergründet. Aber da du alles kennst kannst du die Teile welche dich interessieren sicher einfach übernehmen oder integrieren.

Wenn du die Funktionen einmal testest bekommest du einen Eindruck, wie es meiner Ansicht nach aussehen sollte.

Nun werde ich eine Pause machen und dann einmal das Setzen von Werten ansehen.


######################################################################
# HMCCU_syncDevices
# Subprocess: update device definitions
# called by - system start/restart
#     option: - Init       # read all devices and params. No device verification
#             - createChn  # create all missing channels. Dont touch existing
#             - updateChn  # create all missing channels. delete non-existing
#             - verifyChn  # report the difference between CCU and FHEM
#             - forceChn   # force reread update all parameter and create/delete entities

######################################################################

sub HMCCU_syncDevices ($)
{
my ($option)  = @_;  #
    if($option eq "forceChn"){# clear definitions first
       %HMCCU_DevDef    = ();
       %HMCCU_ParamDesc = ();
       %HMCCU_EntData   = ();
    }
    foreach my $rpcH (map{$defs{$_}}devspec2array ("TYPE=HMCCURPCPROC")){#todo: check other options
      my $r = HMCCURPCPROC_SendRequest ($rpcH,"listDevices", "",""); # get all devices
      foreach my $h (@{$r}){                                         # split all entity description (devices and channels)
        HMCCU_evalDevDesc ($h,$rpcH);
      }
    }
    HMCCU_ccuNames();
   
    if($option =~ m/(.*)Chn$/){
      my $task = $1;
      my @missingChn;
      my @obsoletChn;
      my @existChn;
      my %fhemChnH;
      $fhemChnH{$defs{$_}{DEF}} = $_ foreach(devspec2array ("TYPE=HMCCUCHN"));
      foreach(keys %HMCCU_EntData){
        if (defined $fhemChnH{$_}){
          push @existChn,$_ ;
        }
        else{
          push @missingChn,$_;
        }
      }
      foreach(keys %fhemChnH){
        push @obsoletChn,$_ if (!defined $HMCCU_EntData{$_}) ;
      };
      if($task eq "verify"){
         return "\nCCU channels not yet defined:\n   "     .join("\n   ",sort map{"$_: $HMCCU_EntData{$_}{ccuName}"} @missingChn)
               ."\nFHEM channels not esixting in CCU:\n   ".join("\n   ",sort map{"$_: $fhemChnH{$_}"} @obsoletChn)
               ."\nInSync for :\n   "                      .join("\n   ",sort map{"$_: $fhemChnH{$_} \tccuName:$HMCCU_EntData{$_}{ccuName}"} @existChn)
                ;
      }
      elsif($task =~ m/(create|update|force)/){
        foreach my $addr (@missingChn){
          # Define new client device
          my $ret = CommandDefine (undef, "$fhemChnH{$defs{$addr}{DEF}} HMCCUCHN $addr");
          if ($ret) {
          next;
          }
        }
      }
      elsif($task =~ m/(update|force)/){
        foreach my $addr (@obsoletChn){
          CommandDefine (undef, "$fhemChnH{$defs{$addr}{DEF}}");
        }
      }
    }
}

sub HMCCU_evalDevDesc ($$)
{
my ($descH,$rpcH)  = @_;  # hash to device description
    my %FLAGSdev  = (
           0x01 => "Visible"
          ,0x02 => "Internal"
          ,0x08 => "DontDelete"
          );
    my %DIRECTION  = (
           0 => "NONE"
          ,1 => "SENDER"
          ,2 => "RECEIVER"
          );
    my %RX_MODE = (
           0x01 => "ALWAYS"
          ,0x02 => "BURST"
          ,0x04 => "CONFIG"
          ,0x08 => "WAKEUP"
          ,0x10 => "LAZY_CONFIG"
          );
    my %FLAGSparam = (
           0x01 => "Visible"
          ,0x02 => "Internal"
          ,0x04 => "Transform"
          ,0x08 => "Service"
          ,0x10 => "Sticky"
          );
    my %OPERATIONS = (
           0x01 => "R"
          ,0x02 => "W"
          ,0x03 => "RW"
          ,0x04 => "E"
          ,0x05 => "RE"
          ,0x06 => "WE"
          ,0x07 => "RWE"
          );
     
     
    my $addr  = defined $descH->{ADDRESS}    ? $descH->{ADDRESS}     : $descH->{PARENT};
    return if (defined $HMCCU_EntData{$addr});
    my $idx   = defined $descH->{INDEX}      ? $descH->{INDEX}       : "dev";           # channel number
    my $model = defined $descH->{PARENT_TYPE}? $descH->{PARENT_TYPE} : $descH->{TYPE}  ;# identify model for channel and device
    my $chTyp = defined $descH->{INDEX}      ? $descH->{TYPE}        : "dev";           # if not channel then chTyp = dev
    my $entDataOnly = (defined $HMCCU_DevDef{$model} && defined $HMCCU_DevDef{$model}{$chTyp}) ? 1 : 0; # no need for model description - already available


    $HMCCU_EntData{$addr}{dev}{interface} = $rpcH->{NAME};# store RPC interface
    $HMCCU_EntData{$addr}{dev}{MODEL}     = $model;
    $HMCCU_EntData{$addr}{$idx}{CHTyp}    = $chTyp;
   
    $HMCCU_DevDef{$model}{$chTyp}{done} = 1;     # create hash entry
    my $DevDefH = $HMCCU_DevDef{$model}{$chTyp}; # create hash entry
   
    foreach my $k (keys %$descH){
      my $devLevel = defined $descH->{ADDRESS} ? "dev" : "chn";
      if ($entDataOnly || $k =~ m/(^ADDRESS|TYPE|VERSION|PARENT|INDEX)/ ){ ## ignore this
        next;
        }
      elsif ($k =~ m/(RF_ADDRESS|INTERFACE|FIRMWARE|AES|ROAMING)/){## todo: add this to dynamic data
        $HMCCU_EntData{$addr}{$idx}{$k} = $descH->{$k};
        }
      elsif($k eq "CHILDREN"){
        $DevDefH->{$k} = scalar @{$descH->{$k}};
        }
      elsif($k eq "PARAMSETS"){
        @{$DevDefH->{$k}} = @{$descH->{$k}};     
        }
      elsif($k eq "FLAGS"){
        foreach (keys %FLAGSdev){
          $DevDefH->{$k}{$FLAGSdev{$_}} = 1 if($_+0 & $descH->{$k}+0);
          }
        }
      elsif($k eq "RX_MODE"){
        foreach (keys %RX_MODE){
          $DevDefH->{$k}{$RX_MODE{$_}} = 1 if($_+0 & $descH->{$k}+0);
          }
        }
      elsif($k eq "DIRECTION"){
        $DevDefH->{$k} = $DIRECTION{$descH->{$k}};
        }
      else{
        $DevDefH->{$k} = $descH->{$k};
      }
    }
    $DevDefH->{LinkRole} = (!defined $DevDefH->{LINK_SOURCE_ROLES} || $DevDefH->{LINK_SOURCE_ROLES} ne "") ? "source"
                          :(!defined $DevDefH->{LINK_TARGET_ROLES} || $DevDefH->{LINK_TARGET_ROLES} ne "") ? "target"
                          :"none";
   
    return if ($entDataOnly); # only once required
    foreach my $k (@{$DevDefH->{PARAMSETS}}) { # foreach paramset
      my $res = HMCCURPCPROC_SendRequest ($rpcH,"getParamsetDescription", $addr.":".($idx eq "dev"?"":$idx),$k  );

      @{$DevDefH->{DPs}{$k}} = keys %$res; # store the list if datapoints

      foreach my $var (keys %$res){
        next if ($var =~ m/fault/);
        foreach my $varAttr (keys %{$res->{$var}}){
          if   ($varAttr =~ m/(ID)/){
            next;
            }
          elsif($varAttr eq "SPECIAL"){
            foreach my $x (@{$res->{$var}{$varAttr}}){
              foreach my $tp (keys %{$x}){
                $HMCCU_ParamDesc{$var}{$varAttr}{$tp} = $x->{$tp};
                }
              }
            }
          elsif($varAttr eq "VALUE_LIST"){
            @{$HMCCU_ParamDesc{$var}{$varAttr}} = @{$res->{$var}{$varAttr}};           
            }
          elsif($varAttr =~ m/LINK_.*ROLES/){
            @{$HMCCU_ParamDesc{$var}{$varAttr}} = split(" ",$res->{$var}{$varAttr});
            }
          elsif($varAttr eq "FLAGS"){
            foreach (keys %FLAGSparam){
              $HMCCU_ParamDesc{$var}{$varAttr}{$FLAGSparam{$_}} = 1 if($_+0 & $res->{$var}{$varAttr}+0);
              }
            }
          elsif($varAttr eq "OPERATIONS"){
            $HMCCU_ParamDesc{$var}{$varAttr} = $OPERATIONS{$res->{$var}{$varAttr}};
          }
          elsif($varAttr =~ m /(MIN|MAX|DEFAULT)/){
            if ($res->{$var}{TYPE} eq "ENUM"){
              $HMCCU_ParamDesc{$var}{MIN} = "0";
              $HMCCU_ParamDesc{$var}{MAX} = scalar(@{$res->{$var}{VALUE_LIST}});
              $HMCCU_ParamDesc{$var}{DEFAULT} = "-";
            }
            else{
              $HMCCU_ParamDesc{$var}{$varAttr} = $res->{$var}{$varAttr};
            }
          }
          else{
            $HMCCU_ParamDesc{$var}{$varAttr} = $res->{$var}{$varAttr};         
            }
          }
        }
      }
}

sub HMCCU_ccuNames ()
{
    my $hash = $defs{HMccu};
    my $update = 0;
    foreach(keys %HMCCU_EntData){
      if (!defined $HMCCU_EntData{$_}{ccuName}){
        $update = 1;
        last;
      }
    }
    return if(!$update); # do not update if the namelist is complete
my $response = HMCCU_HMScriptExt ($hash, "!GetDeviceList", undef, undef, undef);

return if ($response eq '' || $response =~ /^ERROR:.*/);

my @scrlines = split /[\n\r]+/,$response;
foreach my $hmdef (@scrlines) {
      my @hmdata = split /;/,$hmdef;
      next if (scalar ((@hmdata) == 0) || ($hmdata[0] !~ m/^[CD]/));
      my $addr = $hmdata[0] eq "C" ? $hmdata[1] : $hmdata[2];
      my $name = $hmdata[0] eq "C" ? $hmdata[2] : $hmdata[3];
      $HMCCU_EntData{$addr}{ccuName} = $name;
    }
}

sub HMCCU_params($)
{
my ($name)  = @_;  # address of the entity - i.e. the serial number
    my $addr = $defs{$name}{ccuaddr};
    return "$name is undefined" if(!defined $defs{$name}
                                 ||!defined $defs{$name}{ccuaddr});
    return "$name not found in CCU" if(!defined $HMCCU_EntData{$addr});
    my ($devAddr,$idx) = split(":",$addr);
    $idx = "dev" if (!defined $idx);
    my $rpcIf = $defs{$HMCCU_EntData{$devAddr}{dev}{interface}};
    return "RPC interface undefined for $addr" if (!defined $rpcIf || $rpcIf eq "");

    my $res="***  $name addr:$addr";
    $res .= "\ndev:$_  : ".$HMCCU_EntData{$devAddr}{dev}{$_} foreach (sort keys %{$HMCCU_EntData{$devAddr}{dev}});
    $res .= "\n    $_  : ".$HMCCU_EntData{$addr}{$idx}{$_}   foreach (sort keys %{$HMCCU_EntData{$addr}{$idx}});
    $res .= "\n----\n";

    my @addrList = ($addr);
    unshift @addrList,$devAddr if ($HMCCU_EntData{$addr}{$idx}{CHTyp} eq "MAINTENANCE");
    foreach my $dispAddr (@addrList){
      my ($dispSer,$dispIdx) = split(":",$dispAddr);
      $dispIdx = "dev" if (!defined $dispIdx);
      my $model = $HMCCU_EntData{$dispAddr}{dev}{MODEL};
      my $CHTyp = $HMCCU_EntData{$dispAddr}{$dispIdx}{CHTyp};
      my $role  = defined $HMCCU_DevDef{$model}{$CHTyp}{LinkRole} ? $HMCCU_DevDef{$model}{$CHTyp}{LinkRole} : "none";

      my %regH;
      foreach my $ps (@{$HMCCU_DevDef{$model}{$CHTyp}{PARAMSETS}}){
        my @peers;
        my @psList;
        if($ps eq "LINK"){ # with link we have to search each peer
          my $rl = HMCCURPCPROC_SendRequest ($rpcIf,"getLinks", $dispAddr, "");
          my $sStr = $role eq "source" ? "RECEIVER" : "SENDER";
          foreach my $hl (@{$rl}){
            push @peers, map{$hl->{$_}} grep /$sStr/,keys %$hl;
          }
          next if(scalar (@peers) == 0 || $role eq "none");
          @psList = @peers;
          $res .= " peerList:".join(",",@peers);
        }
        else{
          push @psList,$ps;
        }
        foreach my $psL (@psList){
          my $r = HMCCURPCPROC_SendRequest($rpcIf,"getParamset", $dispAddr, $psL);
          foreach my $k (sort keys %$r) {
            next if($k =~ m/(faultCode|faultString)/);
            my $val = $r->{$k};
            if (   defined $HMCCU_ParamDesc{$k}{VALUE_LIST}
               && scalar @{$HMCCU_ParamDesc{$k}{VALUE_LIST}} >= $val){
              $val = $HMCCU_ParamDesc{$k}{VALUE_LIST}[$val] ;
            }
            elsif($HMCCU_ParamDesc{$k}{TYPE} eq "BOOL"){
              $val = $val==0 ? "false" : "true";
            }
            elsif($HMCCU_ParamDesc{$k}{TYPE} eq "FLOAT"){
              $val =~ s/0*$//;
              $val =~ s/\.$//;
            }
            if($k =~m /^(SHORT|LONG)_(.*)/){
              $regH{$2}{$psL}{$1} = $val;
            }
            else{
              $res .= " $dispIdx   $psL: $k = $val\n";
            }
          }
        }
      }

      foreach my $reg (keys %regH){
        $res .= "\n ls: $reg:";
        foreach my $peer(sort keys %{$regH{$reg}}){
          foreach my $ls(sort keys %{$regH{$reg}{$peer}}){
            $res .= "\t$regH{$reg}{$peer}{$ls}";
          }
        }
      }
    }
    return $res;
}
sub HMCCU_paramDef($)
{
my ($name)  = @_;  # address of the entity - i.e. the serial number
    my $addr = $defs{$name}{ccuaddr};

    my $rpcIf = $defs{$HMCCU_EntData{$addr}{dev}{interface}};
    return "$name is undefined" if(!defined $defs{$name}
                                 ||!defined $defs{$name}{ccuaddr});
    return "$name not found in CCU" if(!defined $HMCCU_EntData{$addr});
    my ($devAddr,$idx) = split(":",$addr);
    $idx = "dev" if (!defined $idx);

    my $model = $HMCCU_EntData{$addr}{dev}{MODEL};
    my $chType = $HMCCU_EntData{$addr}{$idx}{CHTyp};
    my @chTypList = ($chType);
    unshift (@chTypList,"dev") if($idx == 0);
    Log 1,"General list:".join(",",@chTypList);
    my $res;
    foreach my $chTyp(@chTypList){
      $res .="***  $name addr:$devAddr No:$idx typ:$chTyp";
      foreach my $par (sort grep/DPs/,keys %{$HMCCU_DevDef{$model}{$chTyp}}){
        if (ref($HMCCU_DevDef{$model}{$chTyp}{$par}) eq 'ARRAY'){
          $res .= "\n   :$par  : ".join(",",@{$HMCCU_DevDef{$model}{$chTyp}{$par}});       
        }
        elsif(ref($HMCCU_DevDef{$model}{$chTyp}{$par}) eq 'HASH'){
          $res .= "\n   :$par  : ".join(",",keys %{$HMCCU_DevDef{$model}{$chTyp}{$par}});       
        }
        else{
          $res .= "\n   :$par  : ".$HMCCU_DevDef{$model}{$chTyp}{$par};
        }
      }
      foreach my $ifType (sort keys %{$HMCCU_DevDef{$model}{$chTyp}{DPs}}){
        $res .= "\nIF type: $ifType";
        my $resDp;
        foreach my $DP (sort @{$HMCCU_DevDef{$model}{$chTyp}{DPs}{$ifType}}){
          $resDp .= sprintf("\n %-23s :",$DP);
          my $parH = $HMCCU_ParamDesc{$DP};
          foreach my $DPattr("MIN","MAX","TYPE","UNIT","DEFAULT","OPERATIONS","FLAGS","VALUE_LIST"){#,"TAB_ORDER"
            if(!defined $parH->{$DPattr} || $parH->{$DPattr} eq ""){
              $resDp .= "-\t|";
            }
            elsif(ref($parH->{$DPattr}) eq 'ARRAY'){
              $resDp .= join(",",@{$parH->{$DPattr}})."\t|";
            }
            elsif(ref($parH->{$DPattr}) eq 'HASH'){
              $resDp .= join(",",keys%{$parH->{$DPattr}})."\t|";
            }         
            else{
              my $val = $parH->{$DPattr};
              $val =~ s/0*$//;
              $val =~ s/\.$//;
              $resDp .= "$val\t|";
            }         
          }
        }
        $res .= "\n                           ".join("\t|",("MIN","MAX","TYPE","UNIT","DEF","OPER","FLAGS","VALUE_LIST"))
               .$resDp
               ."\n                           ".join("\t|",("MIN","MAX","TYPE","UNIT","DEF","OPER","FLAGS","VALUE_LIST"));
      }
      $res .="\n";
    }
   
   
    $res .= "\n----\n";
    return $res;
}


zap

#29
Ich hab ein richtig schlechtes Gewissen, dass ich dieses Jahr noch nichts habe hören lassen. Ich habe auch etwas gebastelt. Allerdings mit etwas generischeren Ansätzen. Ich habe Deinen Code zwar nicht einfach übernommen, das Ergebnis ist für Dich hoffentlich auch brauchbar.

Ich befürchte, Du muss einiges an Deinem Code nochmal ändern. Sorry dafür vorab.

Ich checke spätestens Sonntag die neue Version ein. Diese enthält auf jeden Fall eine Abfrage aller Device und Paramset Descriptions inklusive Speicherung im Hash vom IO Device. Außerdem Funktionen, um auf diese Infos zuzugreifen. Ich poste dann hier eine Beschreibung der Funktionen.

Ich sehe es allerdings nicht so, dass alle Funktionen der CCU in FHEM abgebildet werden sollen. Es gibt Dinge, die sind in der CCU einfach und komfortabel. Warum sollte ich die in FHEM nachbauen (z.B. Verknüpfungen, die absolut genialen Heizungsgruppen, ...).

Ich halte es auch nicht für sinnvoll, für jeden Kanal eines Gerätes ein eigenes Device in FHEM zu haben. Das ist der totale Overkill.

So als Richtschnur: Dinge, die man nur einmal macht (Geräte anlernen, verknüpfen, ...) sollte man in der CCU machen. Alles andere, also Nutzung der Geräte, in FHEM. So ist es auch in anderen Smarthome Plattformen gelöst (ioBroker, OpenHab).
2xCCU3 mit ca. 100 Aktoren, Sensoren
Entwicklung: FHEM auf Proxmox Debian VM
Produktiv inzwischen auf Home Assistant gewechselt.
Maintainer: HMCCU, (Fully, AndroidDB)