Suche Entwickler für Velux Modul - KIG 300

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 KIG 300 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

Mitch

Habe jetzt seit langem und mit einer anderen KI nochmal daran gebastelt und zwei neue Module erstellt:

##############################################
# $Id: 73_VELUXACTIVE.pm 0001 2025-03-31 20:44:19Z $
package main;

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

my $VERSION = "1.0";

# API endpoints
my $VELUX_AUTH_URL = "https://app.velux-active.com/oauth2/token";
my $VELUX_API_URL = "https://app.velux-active.com/api/v1";
my $VELUX_SYNC_API = "https://app.velux-active.com/syncapi/v1";
my $VELUX_HOME_API = "https://app.velux-active.com/api";

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

    $hash->{DefFn}    = "VELUXACTIVE_Define";
    $hash->{UndefFn}  = "VELUXACTIVE_Undef";
    $hash->{SetFn}    = "VELUXACTIVE_Set";
    $hash->{GetFn}    = "VELUXACTIVE_Get";
    $hash->{AttrFn}   = "VELUXACTIVE_Attr";
    $hash->{AttrList} = "interval:300,600,900,1800,3600 ".
                        "disable:0,1 ".
                        $readingFnAttributes;
}

sub VELUXACTIVE_Define($$) {
    my ($hash, $def) = @_;
    my @args = split("[ \t]+", $def);

    return "Usage: define <name> VELUXACTIVE <username> <password>" if(@args != 4);
   
    my $name = $args[0];
    my $username = $args[2];
    my $password = $args[3];
   
    $hash->{VERSION} = $VERSION;
    $hash->{STATE} = "Initialized";
    $hash->{USERNAME} = $username;
    $hash->{PASSWORD} = $password;
    $hash->{CLIENT_ID} = "5931426da127d981e76bdd3f";
    $hash->{CLIENT_SECRET} = "6ae2d89d15e767ae5c56b456b452d319";
    $hash->{INTERVAL} = AttrVal($name, "interval", 300);
   
    # Initialize timer for status updates
    RemoveInternalTimer($hash);
   
    # Start authentication process
    VELUXACTIVE_Authenticate($hash);
   
    InternalTimer(gettimeofday() + 5, "VELUXACTIVE_GetHomeData", $hash);
    InternalTimer(gettimeofday() + 5, "VELUXACTIVE_UpdateStatus", $hash);

    return undef;
}

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

sub VELUXACTIVE_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, "VELUXACTIVE ($name) - Set command: update";
        VELUXACTIVE_GetHomeData($hash);
        return undef;
    } elsif ($cmd eq "open" || $cmd eq "close" || $cmd eq "set_position" || $cmd eq "stop") {
        my $device = $a[2] // return "Device ID required for $cmd command";
        my $position = $a[3] if ($cmd eq "set_position");
        Log3 $name, 3, "VELUXACTIVE ($name) - Set command: $cmd for device $device";
        VELUXACTIVE_ControlDevice($hash, $cmd, $device, $position);
        return undef;
    } else {
        return "Unknown argument $cmd, choose one of update open close set_position stop";
    }
}

sub VELUXACTIVE_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, "VELUXACTIVE ($name) - Get command: status";
        VELUXACTIVE_GetHomeData($hash);
        return undef;
    } else {
        return "Unknown argument $cmd, choose one of status";
    }
}

sub VELUXACTIVE_UpdateStatus($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
   
    eval {
        if (!VELUXACTIVE_RefreshToken($hash)) {
            Log3 $name, 2, "VELUXACTIVE ($name) - Failed to refresh token";
            my $interval = AttrVal($name, "interval", 900);
            InternalTimer(gettimeofday() + $interval, "VELUXACTIVE_UpdateStatus", $hash);
            return;
        }
       
        my $token = $hash->{".TOKEN"};
        my $param = {
            url => "$VELUX_API_URL/home/setup",
            method => "GET",
            timeout => 30,
            header => {
                "Authorization" => "Bearer $token",
                "Accept" => "application/json"
            }
        };
       
        my ($err, $data) = HttpUtils_BlockingGet($param);
       
        if ($err) {
            Log3 $name, 2, "VELUXACTIVE ($name) - Error getting status: $err";
            $hash->{STATE} = "Error";
            return;
        }
       
        my $json = eval { decode_json($data) };
        if ($@) {
            Log3 $name, 2, "VELUXACTIVE ($name) - Error decoding status response: $@";
            $hash->{STATE} = "Invalid response";
            return;
        }
       
        # Store device information
        readingsBeginUpdate($hash);
       
        if (defined($json->{homes})) {
            foreach my $home (@{$json->{homes}}) {
                if (defined($home->{devices})) {
                    foreach my $device (@{$home->{devices}}) {
                        readingsBulkUpdate($hash, "device_".$device->{id}."_name", $device->{name});
                        readingsBulkUpdate($hash, "device_".$device->{id}."_type", $device->{type});
                        readingsBulkUpdate($hash, "device_".$device->{id}."_state", $device->{state} || "unknown");
                    }
                }
            }
        }
       
        readingsEndUpdate($hash, 1);
        $hash->{STATE} = "Updated";
    };
    if ($@) {
        Log3 $name, 1, "VELUXACTIVE ($name) - Error in UpdateStatus: $@";
        $hash->{STATE} = "Internal Error";
    }
   
    my $interval = AttrVal($name, "interval", 900);
    InternalTimer(gettimeofday() + $interval, "VELUXACTIVE_UpdateStatus", $hash);
}

sub VELUXACTIVE_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 = $VELUX_AUTH_URL;
    my $data = "grant_type=password&client_id=$client_id&client_secret=$client_secret&username=$username&password=$password&user_prefix=velux";

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

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

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

    if ($err ne "") {
        Log3 $name, 3, "VELUXACTIVE ($name) - Error authenticating: $err";
        $hash->{STATE} = "Auth Error";
        return;
    }

    my $json;
    eval {
        $json = decode_json($data);
    };
    if ($@) {
        Log3 $name, 3, "VELUXACTIVE ($name) - Error parsing JSON: $@";
        $hash->{STATE} = "JSON Error";
        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};
    $hash->{STATE} = "Authenticated";
    Log3 $name, 3, "VELUXACTIVE ($name) - Successfully authenticated";

    VELUXACTIVE_GetHomeID($hash);
}

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

    return if (!$access_token);

    my $url = "$VELUX_HOME_API/gethomedata";
    my $data = "access_token=$access_token&gateway_types[]=NXG";

    Log3 $name, 3, "VELUXACTIVE ($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 => \&VELUXACTIVE_ParseHomeID,
    });
}

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

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

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

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

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

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

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

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

    my $url = "$VELUX_HOME_API/homestatus";
    my $data = "access_token=$access_token&home_id=$home_id";

    Log3 $name, 4, "VELUXACTIVE ($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 => \&VELUXACTIVE_ParseHomeData,
    });

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

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

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

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

    if (defined($json->{body}->{home}) && defined($json->{body}->{home}->{modules})) {
        readingsBeginUpdate($hash);
        foreach my $module (@{$json->{body}->{home}->{modules}}) {
            my $id = $module->{id};
            my $bridge = $module->{bridge};
           
            $hash->{MODULES}->{$id} = $bridge;
           
            # Create or update device readings
            while (my ($key, $value) = each %$module) {
                readingsBulkUpdate($hash, "module_${id}_$key", $value);
            }
           
            # Get module type and location for name
            my $module_type = $module->{velux_type} || $module->{type} || "device";
            my $number = 1;
           
            # Create safe device name
            my $base_name = "velux_${module_type}_${number}";
            my $device_name = $base_name;
            while (defined($defs{$device_name})) {
                $number++;
                $device_name = "velux_${module_type}_${number}";
            }
           
            # Check if we already have a device for this module ID
            my $existing_device = undef;
            foreach my $d (keys %defs) {
                if ($defs{$d}{TYPE} eq "VELUXACTIVEDEVICE" &&
                    $defs{$d}{DEVICE_ID} &&
                    $defs{$d}{DEVICE_ID} eq $id) {
                    $existing_device = $d;
                    last;
                }
            }
           
            # If no existing device found, create a new one
            if (!$existing_device) {
                Log3 $name, 3, "VELUXACTIVE ($name) - Creating device $device_name for module $id";
                my $ret = fhem("define $device_name VELUXACTIVEDEVICE $name $id", 1);
                if ($ret) {
                    Log3 $name, 2, "VELUXACTIVE ($name) - Error creating device $device_name: $ret";
                } else {
                    Log3 $name, 3, "VELUXACTIVE ($name) - Created device $device_name";
                    fhem("attr $device_name room Velux", 1);
                    $existing_device = $device_name;
                }
            }
           
            # Update the device if we found or created one
            if ($existing_device) {
                my $device = $defs{$existing_device};
                if ($device) {
                    readingsBeginUpdate($device);
                    while (my ($key, $value) = each %$module) {
                        readingsBulkUpdate($device, $key, $value);
                    }
                    readingsEndUpdate($device, 1);
                }
            }
           
            # Update device state
            my $device = $defs{$device_name};
            if ($device) {
                readingsBeginUpdate($device);
                while (my ($key, $value) = each %$module) {
                    readingsBulkUpdate($device, $key, $value);
                }
                readingsEndUpdate($device, 1);
            }
        }
        readingsEndUpdate($hash, 1);
        Log3 $name, 3, "VELUXACTIVE ($name) - Modules updated.";
    } else {
        Log3 $name, 3, "VELUXACTIVE ($name) - No modules found.";
    }
}

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

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

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

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

    my $url = "$VELUX_SYNC_API/setstate";
    my $data;

    if ($cmd eq "set_position") {
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $device,
                        bridge => $bridge,
                        target_position => int($position),
                    }
                ]
            }
        });
    } elsif ($cmd eq "stop") {
        $data = encode_json({
            home => {
                id => $home_id,
                modules => [
                    {
                        id => $bridge,
                        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, "VELUXACTIVE ($name) - Sending command $cmd to module $device";

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

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

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

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

sub VELUXACTIVE_Attr(@) {
    my ($cmd, $name, $attrName, $attrVal) = @_;
    my $hash = $defs{$name};

    if ($attrName eq "interval") {
        if ($cmd eq "set") {
            if ($attrVal !~ /^\d+$/ || $attrVal < 300) {
                return "Invalid interval value. Must be >= 300 seconds";
            }
            $hash->{INTERVAL} = $attrVal;
            RemoveInternalTimer($hash);
            InternalTimer(gettimeofday() + $attrVal, "VELUXACTIVE_GetHomeData", $hash);
        }
        elsif ($cmd eq "del") {
            $hash->{INTERVAL} = 900;
            RemoveInternalTimer($hash);
            InternalTimer(gettimeofday() + 900, "VELUXACTIVE_GetHomeData", $hash);
        }
    }
    return undef;
}

1;

##############################################
# $Id: 74_VELUXACTIVEDEVICE.pm 0001 2025-03-31 21:06:05Z $
package main;

use strict;
use warnings;
use JSON;

my $VERSION = "1.0";

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

    $hash->{DefFn}    = "VELUXACTIVEDEVICE_Define";
    $hash->{UndefFn}  = "VELUXACTIVEDEVICE_Undef";
    $hash->{SetFn}    = "VELUXACTIVEDEVICE_Set";
    $hash->{GetFn}    = "VELUXACTIVEDEVICE_Get";
    $hash->{AttrList} = "disable:0,1 alias room ".$readingFnAttributes;
    $hash->{Match}    = "^VELUXACTIVEDEVICE.*";

    Log3 undef, 3, "VELUXACTIVEDEVICE - Initialize done";
    return undef;
}

sub VELUXACTIVEDEVICE_Define($$) {
    my ($hash, $def) = @_;
    my @args = split("[ \t]+", $def);

    return "Usage: define <name> VELUXACTIVEDEVICE <gateway_name> <device_id>" if(@args != 4);
   
    my $name = $args[0];
    my $gateway = $args[2];
    my $device_id = $args[3];
   
    $hash->{VERSION} = $VERSION;
    $hash->{GATEWAY} = $gateway;
    $hash->{DEVICE_ID} = $device_id;
    $hash->{TYPE} = "VELUXACTIVEDEVICE";
   
    # Initialize state based on parent device readings
    my $parent = $defs{$gateway};
    if ($parent) {
        my $position = ReadingsVal($gateway, "module_${device_id}_current_position", undef);
        if (defined $position) {
            readingsSingleUpdate($hash, "position", $position, 1);
            $hash->{STATE} = $position == 0 ? "closed" :
                           $position == 100 ? "open" :
                           "position $position";
        } else {
            $hash->{STATE} = "unknown";
        }
    } else {
        $hash->{STATE} = "no gateway";
    }
   
    return undef;
}

sub VELUXACTIVEDEVICE_Undef($$) {
    my ($hash, $arg) = @_;
    return undef;
}

sub VELUXACTIVEDEVICE_Set($@) {
    my ($hash, @a) = @_;
    return "No arguments specified" if(@a < 2);
   
    my $name = shift @a;
    my $cmd = shift @a;
    my $gateway = $hash->{GATEWAY};
    my $device_id = $hash->{DEVICE_ID};
   
    # Check if gateway exists
    return "Gateway $gateway not found" if(!defined($defs{$gateway}));
   
    if ($cmd eq "open") {
        fhem("set $gateway open $device_id", 1);
        readingsSingleUpdate($hash, "target_position", 100, 1);
        $hash->{STATE} = "opening";
        return undef;
    }
    elsif ($cmd eq "close") {
        fhem("set $gateway close $device_id", 1);
        readingsSingleUpdate($hash, "target_position", 0, 1);
        $hash->{STATE} = "closing";
        return undef;
    }
    elsif ($cmd eq "stop") {
        fhem("set $gateway stop $device_id", 1);
        my $current_pos = ReadingsVal($name, "position", 50);
        readingsSingleUpdate($hash, "target_position", $current_pos, 1);
        $hash->{STATE} = "stopped";
        return undef;
    }
    elsif ($cmd eq "position") {
        my $position = shift @a;
        return "Usage: set $name position <0-100>" if(!defined($position));
        return "Position must be between 0 and 100" if($position < 0 || $position > 100);
        fhem("set $gateway set_position $device_id $position", 1);
        readingsSingleUpdate($hash, "target_position", $position, 1);
        $hash->{STATE} = $position == 0 ? "closing" :
                        $position == 100 ? "opening" :
                        "moving to $position";
        return undef;
    }
   
    return "Unknown argument $cmd, choose one of open close stop position";
}

sub VELUXACTIVEDEVICE_Get($@) {
    my ($hash, @a) = @_;
    return "No arguments specified" if(@a < 2);
   
    my $name = shift @a;
    my $cmd = shift @a;
   
    if ($cmd eq "status") {
        my $gateway = $hash->{GATEWAY};
        fhem("get $gateway status");
        return undef;
    }
   
    return "Unknown argument $cmd, choose one of status";
}

1;

Es werden jetzt die gefundenen Devices automatisch erzeugt und können dann gesteuert werden.
FHEM im Proxmox Container

rudolfkoenig

Vielen Dank fuer das Teilen des Module!

Als Vorlage fuer andere Module empfehle ich sie nicht, dafuer haben sie zu viele Problemchen, aber wenn es den Zweck erfuellt, finde ich es sehr schoen.

vbs


rippi46

Hallo Mitch,

funktioniert das auch mit der Connexoon-Box(thoma/somfy) oder funktioniert das nur mit dem Velux-Gateway?

Gruß rippi
FHEM, LMS, VDR ,Dell 9010 Ubuntu 20.04,Raspimatic, HM/HMIP, Max, Elro, Brennenstuhl u. Intertechno mit Connair.
Picoreplayer, Raspi IR-Lanadapter, Firmata(wifi), LaCrosse,
nanocul433, nanocul868, Signalduino, Connexoon,
MySensor-GW+Sensoren, RGBWW, Zigbee2mqtt,Xiaomi,Nextion,LEDMatrix,Alexa

wolliballa73

nur mal aus Interesse: ist nicht das KLF200 von Velux? Ich nutze das Modul KLF200Node in FHEM für meine Somfy-Motoren, aber das sollte das andere Zeugs von Velux ja auch steuern können, oder?
CU,
Matze

Prof. Dr. Peter Henning

JA, kann es. Und ich rate doch dringend davon ab, sich von einem Sprachmodell Programme schreiben zu lassen, wenn man selbst nicht programmieren kann.

LG

pah

Mitch

Zitat von: wolliballa73 am 06 April 2025, 09:44:58nur mal aus Interesse: ist nicht das KLF200 von Velux? Ich nutze das Modul KLF200Node in FHEM für meine Somfy-Motoren, aber das sollte das andere Zeugs von Velux ja auch steuern können, oder?

Nein, geht nicht, es geht um das KIG 300 Gateway

Zitat von: Prof. Dr. Peter Henning am 06 April 2025, 11:22:24JA, kann es. Und ich rate doch dringend davon ab, sich von einem Sprachmodell Programme schreiben zu lassen, wenn man selbst nicht programmieren kann.

LG

pah

Warum nicht, das funktioniert sehr gut.
FHEM im Proxmox Container

Prof. Dr. Peter Henning

Zitat von: Mitch am 30 April 2025, 10:19:58Warum nicht, das funktioniert sehr gut.

Das denkt nur der Unwissende. Aber weiterhin viel Spaß damit, sich von einem Sprachmodell zum Idioten machen zu lassen.

pah

JoWiemann

Hallo,

grundsätzlich funktioniert das Modul. Ich sehe es allerdings eher als assisted Programming, den als fertige Lösung. In den beiden Modulen gibt es noch einige Ungereimtheiten, die aber auszubügeln sind. Da ich selber jetzt Velux Fenster habe, werde ich mir das mal die nächste Zeit genauer ansehen.

Anbei mal ein Log, dass jeden irritieren würde.
2025.05.01 19:19:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:19:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:20:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:20:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:30 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:22:59 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:24:26 1: VELUXACTIVE (Velux_EW) - Error in UpdateStatus: Undefined subroutine &main::VELUXACTIVE_RefreshToken called at /opt/fhem/FHEM/73_VELUXACTIVE.pm line 110.

2025.05.01 19:24:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:24:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:25:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:25:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:30 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:55 3: VELUXACTIVE (Velux_EW) - Modules updated.

Grüße Jörg
Jörg Wiemann

Slave: RPi B+ mit 512 MB, COC (868 MHz), CUL V3 (433.92MHz SlowRF); FHEMduino, Aktuelles FHEM

Master: CubieTruck; Debian; Aktuelles FHEM