Anfänger Fragen zur Modulentwicklung - Unifi Network Api

Moin zusammen,

ich versuche mich aktuell auch einmal an der Modulentwicklung. Ich habe zwar keine große Erfahrung mit PHP – außer durch Symcon-Skripte – aber ich hoffe, dass ich durch „Abschauen“, Google und die Dokumentation trotzdem auf einem ganz guten Weg bin.

Nun habe ich allerdings ein paar Fragen, bei denen ich mir unsicher bin, wie das richtige Vorgehen ist.

Das Modul ist für die UniFi Network API gedacht:

UniFi Network > Settings > Control Plane > Integrations

Für den Zugriff gibt es die IP-Adresse, den API-Key und die Site. Diese habe ich aktuell in der Geräteinstanz hinterlegt, da sie bei jeder Abfrage benötigt werden und jedes Gerät ohnehin seine eigene Abfrage benötigt. Über den Konfigurator kann ich diese auch direkt vorgeben.
Falls sich hier etwas ändert, müsste ich allerdings bei jeder Instanz den Wert einzeln anpassen.
→ Macht es hier also Sinn, eher eine I/O-Instanz anzulegen und diese als Gateway zu nutzen? Ist es möglich, dann nur diese drei Werte abzurufen oder müssen alle API-Abfragen ebenfalls über diese Instanz laufen? Dann müsste ich das Ganze auf einen Datenfluss mit SendDataToChildren usw. umstellen. Wäre das nicht etwas wie „mit Kanonen auf Spatzen schießen“?

Frage zu Darstellungen/Vorlagen:

Aktuell habe ich alle Optionen beim Anlegen der Variable hinterlegt:

$this->MaintainVariable( 'FirmwareUpdate', $this->Translate( 'FirmwareUpdate' ), 0, [ 'PRESENTATION' => VARIABLE_PRESENTATION_VALUE_PRESENTATION, 'ICON'=> 'circle-info', 'OPTIONS'=>'[{"ColorDisplay":1692672,"Value":false,"Caption":"Aktuell","IconValue":"","IconActive":false,"ColorActive":true,"ColorValue":1692672,"Color":-1},{"ColorDisplay":16711680,"Value":true,"Caption":"Update Verfügbar","IconValue":"","IconActive":false,"ColorActive":true,"ColorValue":16711680,"Color":-1}]' ], $vpos++, 1 );

Ist das der richtige Weg? Oder sollte man besser eine Vorlage anlegen und diese dann zuweisen? Leider habe ich bisher kein Beispiel gefunden, wie man eine Vorlage für die Darstellung anlegt.

Und noch eine dritte Frage:

Ich bekomme beim ReloadModule immer folgende Meldung:

12.07.2025, 13:16:11 | PHPLibrary | Parameter endpoint in der Funktion UNIFINT_getApiData hat keinen Datentyp oder einen nicht unterstützten Datentyp. Definieren Sie entweder 'bool', 'int', 'float' oder 'string'

Ich hatte versucht, das mit

public function getApiData( $endpoint = '' ):string {

zu beheben, aber das hat keinen Unterschied gemacht.
Hat vielleicht jemand einen Tipp oder einen Link, wie ich das korrekt umsetze?

Und zu guter Letzt natürlich noch der Link zum Modul:
bgersmann/UnifiNet

Freue mich natürlich auch über generelle Anregungen, wo ich im Code noch etwas verbessern/ändern sollte.

Vielen Dank!

Gerade wenn man noch nicht so fit ist helfen die KIs echt gut weiter. Auch zum planen der Vorgehensweise. Code würde ich immer mit gewissem Misstrauen betrachten. Zeigt aber oft einen guten Weg.

Hier mal die Antwort von ChatGPT o3 auf deine Fragen. Mal sehen wie weit die ki von den Profiantworten weg liegt :wink:

Hallo,

super, dass du dich an ein eigenes UniFi-Modul wagst! Hier ein kompakter Leitfaden zu deinen drei Punkten – plus ein paar bewährte „Best-Practices“ aus der Symcon-Welt.

1 · Gateway-Instanz (I/O) vs. Credentials in jedem Gerät

Empfehlung: Lege eine eigene I/O-Instanz an (z. B. „UniFi Gateway“), die nur einmal die drei Zugangsdaten (IP, API-Key, Site) enthält und den reinen HTTP-Verkehr abwickelt.

Warum?
• Zentrale Pflege – änderst du den Key oder die Site, brauchst du nur eine Instanz anzufassen.
• Session-Handling & Rate-Limit – das Gateway kann sich um Tokens, Fehler-Retries usw. kümmern.
• Klare Datenflüsse – Devices schicken über SendDataToParent() nur noch ein kleines Paket { endpoint: „/rest/device/XYZ“ }, das Gateway baut die komplette URL, signiert sie und antwortet über SendDataToChildren() mit dem JSON.
• Typisches Muster – so arbeiten fast alle Store-Module (Shelly, Homematic IP, …). Die Symcon-Doku zeigt genau dieses I/O ↔ Device-Prinzip 

„Kanonen auf Spatzen?“
Für sehr wenige Devices könnte auch ein Direktaufruf funktionieren, aber erfahrungsgemäß wächst ein UniFi-Setup schnell. Die einmalige Mehrarbeit spart später viel Pflegeaufwand.

2 · Darstellung direkt in MaintainVariable() oder eigene Vorlage?

Deine Inline-Definition ist technisch korrekt, aber wenn du dieselbe Farblogik an mehreren Stellen brauchst, lohnt sich eine Template-Variable:

// Einmalig im Create() oder bei Bedarf anlegen

$templateID = IPS_CreateTemplate(VARIABLE_PRESENTATION_SWITCH);
IPS_SetTemplateName($templateID, 'UniFi Firmware');
IPS_SetTemplateValues($templateID, [
    'ICON'      => 'circle-info',
    'LAYOUT'    => 1,
    'OPTIONS'   => json_encode([
        ['Value'=>false,'Caption'=>'Aktuell',          'Color'=>0x19, 'ColorActive'=>true],
        ['Value'=>true, 'Caption'=>'Update verfügbar','Color'=>0xFF0000,'ColorActive'=>true]
    ])
]);

// Später bei jeder Variable nur noch
$this->MaintainVariable(
    'FirmwareUpdate',
    $this->Translate('FirmwareUpdate'),
    0,
    [
        'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH,
        'TEMPLATE'     => $templateID   // Referenz auf die Vorlage
    ],
    $vpos++, 1
);
•	Templates werden einmal erstellt, bleiben systemweit erhalten und können in jeder Instanz wiederverwendet werden    
•	Inline-Arrays sind weiterhin ok, wenn es wirklich nur diese eine Variable gibt.

3 · PHPLibrary-Warnung „Parameter … hat keinen Datentyp“

Das kommt von Symcon ≥ 7.0: Die Library-Scanner erwarten für jede öffentlich registrierte Funktion strikt einen der vier skalaren Typen (bool|int|float|string). Arrays oder gemischte Typen sind nicht zulässig.

Lösung:

function UNIFINT_getApiData(string $endpoint): string
{
// …
}

•	Achte darauf, dass alle Parameter (auch optionale) einen gültigen Typ haben.
•	Falls dein Funktionsprototyp zusätzliche Arrays erwartet, entweder auf String-JSON wechseln oder die Funktion als privat markieren, damit sie gar nicht erst in die Library-API exportiert wird.
•	Nach dem Anpassen ReloadModule reicht – die Warnung verschwindet. Eine identische Diskussion findest du im Community-Thread  

Weitere kleine Tipps
• Konfigurator: Lass beim Anlegen eines Geräts automatisch prüfen, ob bereits ein passendes UniFi-Gateway existiert; wenn nicht, erzeuge eines mit denselben Credentials.
• Debug: Gib im Gateway die Roh-Requests/-Responses aus – gerade beim Basteln an Endpunkten ein Lebensretter.
• Error-Handling: Antworte dem Device mit strukturierten Fehlermeldungen (HTTP-Code, Text), damit du sie in ReceiveData() elegant behandeln kannst.

Viel Erfolg beim Weiterbauen – und melde dich gern, wenn noch Fragen offen sind!

1 „Gefällt mir“

Ja, die lieben KIs.
Die Antwort kommt als Rückgabewert aus dem ForwardData im Parent und nicht per SendDataToChildren().

SendDataToChildren nutzt ein Parent um aktiv Daten an Childs zu versenden, wenn also er selber z.b. zyklisch Daten abruft und verteilen muss.

Das sehe ich anders, weil dann Module wieder den Vorlagenmanager zumüllen, wie zuvor den Profilmanager.
Nutzt einfache keine Vorlagen in Modulen.
Der User kann ja noch immer die Darstellung überschreiben und z.b. seine Vorlage auswählen.
Michael

3 „Gefällt mir“

Danke für die Tipps :slight_smile:
Habe es nun mal umgebaut auf ein Gateway, klappt auch soweit.

Werde es mal ein paar Tage laufen lassen und testen.

Die PHP Warnungen bin ich auch los geworden, da lag es am fehlenden Typ des Parameters.

Die Vorlagen habe ich wie von Michael vorgeschlagen so gelassen. Gerade bei den einzelnen ports gibt es da ja sonst recht viele Kombinationen für das Template.
Dazu noch eine Frage: Wenn im code MaintainVariable mit neuer Darstellung aufgerufen wird, wird dann die Änderung vom User wieder überschrieben?

Ansonsten hab ich es mal im Store als „UnifiNet“ als Beta eingereicht, wenn es ein paar Tage läuft schreib ich noch etwas doku und reiche es mal als Stable ein, dann müssen sich die Symcon Jungs ja damit beschäftigen :smiley:

Nein.
Es gibt immer die Darstellung/Profile vom Modul und dann zusätzlich die Einstellungen vom User.
Letztere haben eine höhere Prio und werden bei MaintanceVariable/RegisterVariable nicht verändert.
Darum darf man auch kein IPS_SetVariableCustomPresentation oder IPS_SetVariableCustomProfile in einem Modul nutzen. Das überschreibt die User Einstellungen.

Michael

2 „Gefällt mir“

Noch mal eine Frage zum Modul-Generator:

Wie lege ich denn dort einen Websocket-Client an?

Moin,

noch mal eine Frage:

Ich hole mir im Gateway (I/O) einen Screenshot als base64, wenn ich den per SendDataToChildren an die Geräte Instanz weitergeben will bekomm ich einen Fehler:

26.07.2025, 11:53:50 | FlowHandler          | Kann Daten nicht zur Instanz #29817 weiterleiten: Output-Buffer exceeds Limit (1048576 bytes). Operation halted.

Als workaround lege ich nun im Gateway das Medien Objekt an und fülle es. Wie wäre denn hier das richtige vorgehen?

Ist meistens wenn dein Code eine Ausgabe erzeugt.
Zum Beispiel bei einem Fehler.
Michael

Danke, ich war einfach nur etwas Blind, hatte es noch mit „IPS_SetMediaFile“ statt „IPS_SetMediaContent“ versucht…

Noch mal ne kleine Frage, ich setzte bei MaintainVariable die Darstellung mit Icon usw. Nun habe ich einen Fall, da wird das Icon nicht angezeigt.
Dabei geht es um die Darstellung „Dauer“, da kann ich in der Variablendarstellung in der Konsole allerdings auch kein Icon auswählen.

$this->MaintainVariable( 'UptimeSec', $this->Translate( 'UptimeSec' ), 1, [ 'PRESENTATION' => VARIABLE_PRESENTATION_DURATION, 'FORMAT'=> 2,'ICON'=> 'circle-info'], $vpos++, 1 );

Muss ich dann den Umweg über Set_icon gehen?
Oder ist das eher ein Fehler in Symcon, da man hier kein Standardicon wählen kann?

Wenn ich das richtig verstehe, hat DAUER gar keine Einstellung für ICON!

(
[PRESENTATION] => {08A6AF76-394E-D354-48D5-BFC690488E4E}
[MILLISECONDS] =>
[FORMAT] => 1
[COUNTDOWN_TYPE] => 0
)

Icon muss man ja über Weitere visuelle Einstellungen vornehemen, also ja über SetIcon.

Gruß Heiko

Ja, gesehen hab ich das ja auch. Fände es aber eher sinnvoll die Dauer um ein Icon zu erweitern?
Bei den Profilen konnte ich ja auch immer ein Icon mitgeben, sollte also bei den Darstellungen auch so sein? Fände ich zumindest sonst inkonsistent…

Und IPS_SetIcon würde auch eine Änderung vom User überschreiben?

1 „Gefällt mir“

Moin @Dr.Niels ,

darf ich dich mal anstupsen, ob du da etwas sagen kannst, wie ich es richtig mache?

Icons kommen bei Darstellungen nur, wenn sich diese auch darauf basierend ändern. Das ist hier nicht der Fall, daher sehe ich nicht, dass wir ein Icon bei der Dauer hinzufügen.

Du kannst aber gerne deinen erstellten Variablen ein Icon zum Start mitgeben. Prinzipiell also prüfen, ob du per RegisterVariable eine neue Variable erstellst. Beim ModuleStrict kannst du das direkt über den Rückgabewert von RegisterVariable machen. Beim klassischen Module müsstest du einmal vor dem Aufruf von Register prüfen, ob die Variable schon da ist. Falls die Variable frisch erstellt wurde, hat der Benutzer logischerweise noch nichts gemacht und du kannst per IPS_SetIcon ein Icon vorgeben. Wenn der Benutzer möchte, kann er es ja später noch ändern.

1 „Gefällt mir“