Suche Entwickler für Velux Modul

Begonnen von Mitch, 31 August 2023, 20:56:59

Vorheriges Thema - Nächstes Thema

Mitch

Hallo,

ich suchen einen Entwickler, der Lust hätte ein Modul für Velux zu schreiben.
Gerne auch gegen eine Spende!

API vorhanden, basiert auf dei Netatmo API.
https://github.com/nougad/velux-cli
https://github.com/Droccal/homebridge-velux-active/tree/master
FHEM im Proxmox Container

Mitch

FHEM im Proxmox Container

Mitch

Nachdem wohl niemand Lust hatte und ich leider auch immer noch nicht Perl "sprechen" kann, habe ich mir von der KI helfen lassen.
Das Modul verbindet sich, liest alle Geräte ein und ich kann sie auch steuern.
Die Readings sind noch etwas unübersichtlich, aber erstmal ein Anfang.

Evtl. mag ja ein "Profi" mal drüber schauen?

package main;

use strict;
use warnings;
use HttpUtils;
use JSON;

sub VeluxKIX300_Initialize {
    my ($hash) = @_;

    $hash->{DefFn}    = 'VeluxKIX300_Define';
    $hash->{UndefFn}  = 'VeluxKIX300_Undef';
    $hash->{SetFn}    = 'VeluxKIX300_Set';
    $hash->{GetFn}    = 'VeluxKIX300_Get';
    $hash->{AttrList} = "interval " . $readingFnAttributes;
}

sub VeluxKIX300_Define {
    my ($hash, $def) = @_;
    my @a = split("[ \t][ \t]*", $def);

    return "wrong syntax: define <name> VeluxKIX300 <username> <password>" if (int(@a) != 4);

    my ($name, $type, $username, $password) = @a;

    $hash->{NAME}      = $name;
    $hash->{TYPE}      = $type;
    $hash->{USERNAME}  = $username;
    $hash->{PASSWORD}  = $password;
    $hash->{CLIENT_ID} = "5931426da127d981e76bdd3f";
    $hash->{CLIENT_SECRET} = "6ae2d89d15e767ae5c56b456b452d319";
    $hash->{INTERVAL}  = AttrVal($name, 'interval', 300);

    Log3 $name, 3, "VeluxKIX300 ($name) - Module initialized.";

    VeluxKIX300_Authenticate($hash);

    InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VeluxKIX300_GetHomeData", $hash, 0);

    return undef;
}

sub VeluxKIX300_Undef {
    my ($hash, $name) = @_;
    RemoveInternalTimer($hash);
    return undef;
}

sub VeluxKIX300_Set {
    my ($hash, @a) = @_;
    my $name = $hash->{NAME};

    return "\"set $name\" needs at least one argument" if (@a < 2);

    my $cmd = $a[1];
    if ($cmd eq "update") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Set command: update";
        VeluxKIX300_GetHomeData($hash);
        return undef;
    } elsif ($cmd eq "open" || $cmd eq "close" || $cmd eq "set_position" || $cmd eq "retrieve_key" || $cmd eq "stop_movements") {
        my $device = $a[2] // return "Device ID required for $cmd command";
        my $position = $a[3] if ($cmd eq "set_position");
        Log3 $name, 3, "VeluxKIX300 ($name) - Set command: $cmd for device $device";
        VeluxKIX300_ControlDevice($hash, $cmd, $device, $position);
        return undef;
    } else {
        return "Unknown argument $cmd, choose one of update open close set_position retrieve_key stop_movements";
    }
}

sub VeluxKIX300_Get {
    my ($hash, @a) = @_;
    my $name = $hash->{NAME};

    return "\"get $name\" needs at least one argument" if (@a < 2);

    my $cmd = $a[1];
    if ($cmd eq "status") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Get command: status";
        VeluxKIX300_GetHomeData($hash);
        return undef;
    } else {
        return "Unknown argument $cmd, choose one of status";
    }
}

sub VeluxKIX300_Authenticate {
    my ($hash) = @_;
    my $name = $hash->{NAME};
    my $client_id = $hash->{CLIENT_ID};
    my $client_secret = $hash->{CLIENT_SECRET};
    my $username = $hash->{USERNAME};
    my $password = $hash->{PASSWORD};

    my $url = "https://app.velux-active.com/oauth2/token";
    my $data = "grant_type=password&client_id=$client_id&client_secret=$client_secret&username=$username&password=$password&user_prefix=velux";

    Log3 $name, 3, "VeluxKIX300 ($name) - Authenticating...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Content-Type: application/x-www-form-urlencoded",
        callback => \&VeluxKIX300_ParseAuthResponse,
    });
}

sub VeluxKIX300_ParseAuthResponse {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error authenticating: $err";
        return;
    }

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
        return;
    }

    $hash->{ACCESS_TOKEN} = $json->{access_token};
    $hash->{REFRESH_TOKEN} = $json->{refresh_token};
    $hash->{EXPIRES_IN} = $json->{expires_in};
    $hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
    Log3 $name, 3, "VeluxKIX300 ($name) - Access token: $hash->{ACCESS_TOKEN}";

    VeluxKIX300_GetHomeID($hash);
}

sub VeluxKIX300_RefreshToken {
    my ($hash) = @_;
    my $name = $hash->{NAME};
    my $client_id = $hash->{CLIENT_ID};
    my $client_secret = $hash->{CLIENT_SECRET};
    my $refresh_token = $hash->{REFRESH_TOKEN};

    my $url = "https://app.velux-active.com/oauth2/token";
    my $data = "grant_type=refresh_token&client_id=$client_id&client_secret=$client_secret&refresh_token=$refresh_token";

    Log3 $name, 3, "VeluxKIX300 ($name) - Refreshing token...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Content-Type: application/x-www-form-urlencoded",
        callback => \&VeluxKIX300_ParseRefreshTokenResponse,
    });
}

sub VeluxKIX300_ParseRefreshTokenResponse {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error refreshing token: $err";
        VeluxKIX300_Authenticate($hash);  # Fallback to re-authentication
        return;
    }

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
        VeluxKIX300_Authenticate($hash);  # Fallback to re-authentication
        return;
    }

    $hash->{ACCESS_TOKEN} = $json->{access_token};
    $hash->{REFRESH_TOKEN} = $json->{refresh_token} if $json->{refresh_token};
    $hash->{EXPIRES_IN} = $json->{expires_in};
    $hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
    Log3 $name, 3, "VeluxKIX300 ($name) - Access token refreshed: $hash->{ACCESS_TOKEN}";
}

sub VeluxKIX300_GetHomeID {
    my ($hash) = @_;
    my $name = $hash->{NAME};
    my $access_token = $hash->{ACCESS_TOKEN};

    return if (!$access_token);

    my $url = "https://app.velux-active.com/api/gethomedata";
    my $data = "access_token=$access_token&gateway_types[]=NXG";

    Log3 $name, 3, "VeluxKIX300 ($name) - Getting Home ID...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Content-Type: application/x-www-form-urlencoded",
        callback => \&VeluxKIX300_ParseHomeID,
    });
}

sub VeluxKIX300_ParseHomeID {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error fetching Home ID: $err";
        return;
    }

    Log3 $name, 5, "VeluxKIX300 ($name) - Home ID response: $data";  # Log the full response for debugging

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
        return;
    }

    Log3 $name, 5, "VeluxKIX300 ($name) - Parsed JSON: " . encode_json($json);  # Log the parsed JSON for debugging

    if (defined($json->{body}->{homes}) && @{$json->{body}->{homes}} > 0) {
        $hash->{HOME_ID} = $json->{body}->{homes}[0]->{id};
        Log3 $name, 3, "VeluxKIX300 ($name) - Home ID: $hash->{HOME_ID}";
        VeluxKIX300_GetHomeData($hash);
    } else {
        Log3 $name, 3, "VeluxKIX300 ($name) - No homes found.";
    }
}

sub VeluxKIX300_GetHomeData {
    my ($hash) = @_;
    my $name = $hash->{NAME};

    if (time() >= $hash->{TOKEN_EXPIRES}) {
        VeluxKIX300_RefreshToken($hash);
        return;
    }

    my $access_token = $hash->{ACCESS_TOKEN};
    my $home_id = $hash->{HOME_ID};

    return if (!$access_token || !$home_id);

    my $url = "https://app.velux-active.com/api/homestatus";
    my $data = "access_token=$access_token&home_id=$home_id";

    Log3 $name, 4, "VeluxKIX300 ($name) - Getting Home Data...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Content-Type: application/x-www-form-urlencoded",
        callback => \&VeluxKIX300_ParseHomeData,
    });

    InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VeluxKIX300_GetHomeData", $hash, 0);
}

sub VeluxKIX300_ParseHomeData {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error fetching Home Data: $err";
        return;
    }

    Log3 $name, 5, "VeluxKIX300 ($name) - Home Data response: $data";  # Log the full response for debugging

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
        return;
    }

    Log3 $name, 5, "VeluxKIX300 ($name) - Parsed JSON: " . encode_json($json);  # Log the parsed JSON for debugging

    if (defined($json->{body}->{home}) && defined($json->{body}->{home}->{modules})) {
        my @modules = @{$json->{body}->{home}->{modules}};
        readingsBeginUpdate($hash);
        foreach my $module (@modules) {
            my $id = $module->{id};
            my $bridge = $module->{bridge};  # Extract bridge ID for control commands
            $hash->{MODULES}->{$id} = $bridge;  # Store bridge ID in the hash for later use
            Log3 $name, 3, "VeluxKIX300 ($name) - Found module ID: $id with bridge ID: $bridge";  # Log module and bridge IDs for debugging
            while (my ($key, $value) = each %$module) {
                readingsBulkUpdate($hash, "module_${id}_$key", $value);
            }
        }
        readingsEndUpdate($hash, 1);
        Log3 $name, 3, "VeluxKIX300 ($name) - Modules updated.";
    } else {
        Log3 $name, 3, "VeluxKIX300 ($name) - No modules found.";
    }
}

sub VeluxKIX300_ControlDevice {
    my ($hash, $cmd, $device, $position) = @_;
    my $name = $hash->{NAME};

    if (time() >= $hash->{TOKEN_EXPIRES}) {
        VeluxKIX300_RefreshToken($hash);
        return;
    }

    my $access_token = $hash->{ACCESS_TOKEN};
    my $home_id = $hash->{HOME_ID};
    my $bridge = $hash->{MODULES}->{$device};  # Retrieve the bridge ID for the device

    return if (!$access_token || !$home_id || !$bridge);

    my $url = "https://app.velux-active.com/syncapi/v1/setstate";
    my $data;

    if ($cmd eq "set_position") {
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $device,
                        bridge => $bridge,
                        target_position => int($position),  # Ensure target_position is a number
                    }
                ]
            }
        });
    } elsif ($cmd eq "retrieve_key") {
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $device,
                        bridge => $bridge,
                        retrieve_key => \1,
                    }
                ]
            }
        });
    } elsif ($cmd eq "stop_movements") {
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $bridge,  # Use the bridge ID for stopping movements
                        stop_movements => "all",
                    }
                ]
            }
        });
    } else {
        my $action = ($cmd eq "open") ? 100 : 0;
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $device,
                        bridge => $bridge,
                        target_position => $action,
                    }
                ]
            }
        });
    }

    Log3 $name, 3, "VeluxKIX300 ($name) - Sending command $cmd to module $device with home_id $home_id and bridge_id $bridge...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Authorization: Bearer $access_token\r\nContent-Type: application/json",
        callback => \&VeluxKIX300_ParseControlResponse,
    });
}

sub VeluxKIX300_ParseControlResponse {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error controlling module: $err";
        return;
    }

    Log3 $name, 3, "VeluxKIX300 ($name) - Control response: $data";
}

1;

=pod
=item summary    This module integrates Velux KIX300 gateway into FHEM
=item summary_DE Dieses Modul integriert das Velux KIX300 Gateway in FHEM
=begin html

=end html
=cut
FHEM im Proxmox Container

fware8345

#3
Zitat von: Mitch am 23 Juni 2024, 19:56:09Nachdem wohl niemand Lust hatte und ich leider auch immer noch nicht Perl "sprechen" kann, habe ich mir von der KI helfen lassen.
Das Modul verbindet sich, liest alle Geräte ein und ich kann sie auch steuern.
Die Readings sind noch etwas unübersichtlich, aber erstmal ein Anfang.

Evtl. mag ja ein "Profi" mal drüber schauen?

package main;

use strict;
use warnings;
use HttpUtils;
use JSON;

sub VeluxKIX300_Initialize {
    my ($hash) = @_;

    $hash->{DefFn}    = 'VeluxKIX300_Define';
    $hash->{UndefFn}  = 'VeluxKIX300_Undef';
    $hash->{SetFn}    = 'VeluxKIX300_Set';
    $hash->{GetFn}    = 'VeluxKIX300_Get';
    $hash->{AttrList} = "interval " . $readingFnAttributes;
}

sub VeluxKIX300_Define {
    my ($hash, $def) = @_;
    my @a = split("[ \t][ \t]*", $def);

    return "wrong syntax: define <name> VeluxKIX300 <username> <password>" if (int(@a) != 4);

    my ($name, $type, $username, $password) = @a;

    $hash->{NAME}      = $name;
    $hash->{TYPE}      = $type;
    $hash->{USERNAME}  = $username;
    $hash->{PASSWORD}  = $password;
    $hash->{CLIENT_ID} = "5931426da127d981e76bdd3f";
    $hash->{CLIENT_SECRET} = "6ae2d89d15e767ae5c56b456b452d319";
    $hash->{INTERVAL}  = AttrVal($name, 'interval', 300);

    Log3 $name, 3, "VeluxKIX300 ($name) - Module initialized.";

    VeluxKIX300_Authenticate($hash);

    InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VeluxKIX300_GetHomeData", $hash, 0);

    return undef;
}

sub VeluxKIX300_Undef {
    my ($hash, $name) = @_;
    RemoveInternalTimer($hash);
    return undef;
}

sub VeluxKIX300_Set {
    my ($hash, @a) = @_;
    my $name = $hash->{NAME};

    return "\"set $name\" needs at least one argument" if (@a < 2);

    my $cmd = $a[1];
    if ($cmd eq "update") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Set command: update";
        VeluxKIX300_GetHomeData($hash);
        return undef;
    } elsif ($cmd eq "open" || $cmd eq "close" || $cmd eq "set_position" || $cmd eq "retrieve_key" || $cmd eq "stop_movements") {
        my $device = $a[2] // return "Device ID required for $cmd command";
        my $position = $a[3] if ($cmd eq "set_position");
        Log3 $name, 3, "VeluxKIX300 ($name) - Set command: $cmd for device $device";
        VeluxKIX300_ControlDevice($hash, $cmd, $device, $position);
        return undef;
    } else {
        return "Unknown argument $cmd, choose one of update open close set_position retrieve_key stop_movements";
    }
}

sub VeluxKIX300_Get {
    my ($hash, @a) = @_;
    my $name = $hash->{NAME};

    return "\"get $name\" needs at least one argument" if (@a < 2);

    my $cmd = $a[1];
    if ($cmd eq "status") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Get command: status";
        VeluxKIX300_GetHomeData($hash);
        return undef;
    } else {
        return "Unknown argument $cmd, choose one of status";
    }
}

sub VeluxKIX300_Authenticate {
    my ($hash) = @_;
    my $name = $hash->{NAME};
    my $client_id = $hash->{CLIENT_ID};
    my $client_secret = $hash->{CLIENT_SECRET};
    my $username = $hash->{USERNAME};
    my $password = $hash->{PASSWORD};

    my $url = "https://app.velux-active.com/oauth2/token";
    my $data = "grant_type=password&client_id=$client_id&client_secret=$client_secret&username=$username&password=$password&user_prefix=velux";

    Log3 $name, 3, "VeluxKIX300 ($name) - Authenticating...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Content-Type: application/x-www-form-urlencoded",
        callback => \&VeluxKIX300_ParseAuthResponse,
    });
}

sub VeluxKIX300_ParseAuthResponse {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error authenticating: $err";
        return;
    }

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
        return;
    }

    $hash->{ACCESS_TOKEN} = $json->{access_token};
    $hash->{REFRESH_TOKEN} = $json->{refresh_token};
    $hash->{EXPIRES_IN} = $json->{expires_in};
    $hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
    Log3 $name, 3, "VeluxKIX300 ($name) - Access token: $hash->{ACCESS_TOKEN}";

    VeluxKIX300_GetHomeID($hash);
}

sub VeluxKIX300_RefreshToken {
    my ($hash) = @_;
    my $name = $hash->{NAME};
    my $client_id = $hash->{CLIENT_ID};
    my $client_secret = $hash->{CLIENT_SECRET};
    my $refresh_token = $hash->{REFRESH_TOKEN};

    my $url = "https://app.velux-active.com/oauth2/token";
    my $data = "grant_type=refresh_token&client_id=$client_id&client_secret=$client_secret&refresh_token=$refresh_token";

    Log3 $name, 3, "VeluxKIX300 ($name) - Refreshing token...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Content-Type: application/x-www-form-urlencoded",
        callback => \&VeluxKIX300_ParseRefreshTokenResponse,
    });
}

sub VeluxKIX300_ParseRefreshTokenResponse {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error refreshing token: $err";
        VeluxKIX300_Authenticate($hash);  # Fallback to re-authentication
        return;
    }

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
        VeluxKIX300_Authenticate($hash);  # Fallback to re-authentication
        return;
    }

    $hash->{ACCESS_TOKEN} = $json->{access_token};
    $hash->{REFRESH_TOKEN} = $json->{refresh_token} if $json->{refresh_token};
    $hash->{EXPIRES_IN} = $json->{expires_in};
    $hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
    Log3 $name, 3, "VeluxKIX300 ($name) - Access token refreshed: $hash->{ACCESS_TOKEN}";
}

sub VeluxKIX300_GetHomeID {
    my ($hash) = @_;
    my $name = $hash->{NAME};
    my $access_token = $hash->{ACCESS_TOKEN};

    return if (!$access_token);

    my $url = "https://app.velux-active.com/api/gethomedata";
    my $data = "access_token=$access_token&gateway_types[]=NXG";

    Log3 $name, 3, "VeluxKIX300 ($name) - Getting Home ID...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Content-Type: application/x-www-form-urlencoded",
        callback => \&VeluxKIX300_ParseHomeID,
    });
}

sub VeluxKIX300_ParseHomeID {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error fetching Home ID: $err";
        return;
    }

    Log3 $name, 5, "VeluxKIX300 ($name) - Home ID response: $data";  # Log the full response for debugging

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
        return;
    }

    Log3 $name, 5, "VeluxKIX300 ($name) - Parsed JSON: " . encode_json($json);  # Log the parsed JSON for debugging

    if (defined($json->{body}->{homes}) && @{$json->{body}->{homes}} > 0) {
        $hash->{HOME_ID} = $json->{body}->{homes}[0]->{id};
        Log3 $name, 3, "VeluxKIX300 ($name) - Home ID: $hash->{HOME_ID}";
        VeluxKIX300_GetHomeData($hash);
    } else {
        Log3 $name, 3, "VeluxKIX300 ($name) - No homes found.";
    }
}

sub VeluxKIX300_GetHomeData {
    my ($hash) = @_;
    my $name = $hash->{NAME};

    if (time() >= $hash->{TOKEN_EXPIRES}) {
        VeluxKIX300_RefreshToken($hash);
        return;
    }

    my $access_token = $hash->{ACCESS_TOKEN};
    my $home_id = $hash->{HOME_ID};

    return if (!$access_token || !$home_id);

    my $url = "https://app.velux-active.com/api/homestatus";
    my $data = "access_token=$access_token&home_id=$home_id";

    Log3 $name, 4, "VeluxKIX300 ($name) - Getting Home Data...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Content-Type: application/x-www-form-urlencoded",
        callback => \&VeluxKIX300_ParseHomeData,
    });

    InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VeluxKIX300_GetHomeData", $hash, 0);
}

sub VeluxKIX300_ParseHomeData {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error fetching Home Data: $err";
        return;
    }

    Log3 $name, 5, "VeluxKIX300 ($name) - Home Data response: $data";  # Log the full response for debugging

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
        return;
    }

    Log3 $name, 5, "VeluxKIX300 ($name) - Parsed JSON: " . encode_json($json);  # Log the parsed JSON for debugging

    if (defined($json->{body}->{home}) && defined($json->{body}->{home}->{modules})) {
        my @modules = @{$json->{body}->{home}->{modules}};
        readingsBeginUpdate($hash);
        foreach my $module (@modules) {
            my $id = $module->{id};
            my $bridge = $module->{bridge};  # Extract bridge ID for control commands
            $hash->{MODULES}->{$id} = $bridge;  # Store bridge ID in the hash for later use
            Log3 $name, 3, "VeluxKIX300 ($name) - Found module ID: $id with bridge ID: $bridge";  # Log module and bridge IDs for debugging
            while (my ($key, $value) = each %$module) {
                readingsBulkUpdate($hash, "module_${id}_$key", $value);
            }
        }
        readingsEndUpdate($hash, 1);
        Log3 $name, 3, "VeluxKIX300 ($name) - Modules updated.";
    } else {
        Log3 $name, 3, "VeluxKIX300 ($name) - No modules found.";
    }
}

sub VeluxKIX300_ControlDevice {
    my ($hash, $cmd, $device, $position) = @_;
    my $name = $hash->{NAME};

    if (time() >= $hash->{TOKEN_EXPIRES}) {
        VeluxKIX300_RefreshToken($hash);
        return;
    }

    my $access_token = $hash->{ACCESS_TOKEN};
    my $home_id = $hash->{HOME_ID};
    my $bridge = $hash->{MODULES}->{$device};  # Retrieve the bridge ID for the device

    return if (!$access_token || !$home_id || !$bridge);

    my $url = "https://app.velux-active.com/syncapi/v1/setstate";
    my $data;

    if ($cmd eq "set_position") {
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $device,
                        bridge => $bridge,
                        target_position => int($position),  # Ensure target_position is a number
                    }
                ]
            }
        });
    } elsif ($cmd eq "retrieve_key") {
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $device,
                        bridge => $bridge,
                        retrieve_key => \1,
                    }
                ]
            }
        });
    } elsif ($cmd eq "stop_movements") {
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $bridge,  # Use the bridge ID for stopping movements
                        stop_movements => "all",
                    }
                ]
            }
        });
    } else {
        my $action = ($cmd eq "open") ? 100 : 0;
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $device,
                        bridge => $bridge,
                        target_position => $action,
                    }
                ]
            }
        });
    }

    Log3 $name, 3, "VeluxKIX300 ($name) - Sending command $cmd to module $device with home_id $home_id and bridge_id $bridge...";

    HttpUtils_NonblockingGet({
        url      => $url,
        timeout  => 10,
        hash     => $hash,
        data     => $data,
        method   => "POST",
        header   => "Authorization: Bearer $access_token\r\nContent-Type: application/json",
        callback => \&VeluxKIX300_ParseControlResponse,
    });
}

sub VeluxKIX300_ParseControlResponse {
    my ($param, $err, $data) = @_;
    my $hash = $param->{hash};
    my $name = $hash->{NAME};

    if ($err ne "") {
        Log3 $name, 3, "VeluxKIX300 ($name) - Error controlling module: $err";
        return;
    }

    Log3 $name, 3, "VeluxKIX300 ($name) - Control response: $data";
}

1;

=pod
=item summary    This module integrates Velux KIX300 gateway into FHEM
=item summary_DE Dieses Modul integriert das Velux KIX300 Gateway in FHEM
=begin html

=end html
=cut
KI hat die richtige Lösung gefunden. Es ist Arbeit.

rudolfkoenig

ZitatEvtl. mag ja ein "Profi" mal drüber schauen?
Ich habe das Modul durchgeschaut.
Da ich das Protokoll nicht kenne, und das Modul auch nicht getestet habe, sind meine Kommentare mit Vorsicht zu geniessen.

- get sollte was zurueckliefern. Wenn das nicht der Fall ist, sollte der Befehl lieber ein set sein. "get status" gibt es  schon als "set update".
- GetHomeData traegt sich beim Aufruf in die Timerliste ein, ohne den vorherigen Eintrag zu entfernen. Da GetHomeData von mehreren Stellen aufgerufen wird (im define und set direkt, ueber Authenticate indirekt), wird sie (GetHomeData) zunehmend haeufig "parallel" aufgerufen.
- falls der Token abgelaufen ist, wir der Token zwar aktualisiert, aber der auszufuehrende Befehl vergessen.
- Fehler sollten mit "Log 1" protokolliert werden (nicht Log 3)

Mit welchem KI wurde es erstellt?
Wie lange hat es gedauert, im Sinne von Anzahl der Iterationen?
Wer ist der Urheber des Moduls?

Mitch

Danke für das feedback, werde ich mir anschauen.

Als KI hatte ich ChatGPT4 benutzt. Ich hatte das Netatmo Modul und Commandref zur Verfügung gestellt.
Es hat 5 Iterationen gedauert, wobei schon nach der erst fast alle funktioniert hatte.
Urheber ist somit die KI
FHEM im Proxmox Container