FHEM Forum

FHEM - Hausautomations-Systeme => Sonstige Systeme => Thema gestartet von: Mitch am 31 August 2023, 20:56:59

Titel: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Mitch am 31 August 2023, 20:56:59
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
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: Mitch am 02 September 2023, 12:20:21
 :'(
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: Mitch am 23 Juni 2024, 19:56:09
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
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: fware8345 am 30 September 2024, 06:31:21
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.
geometry dash (https://geometrydash-online.io)

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.
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: rudolfkoenig am 30 September 2024, 10:01:04
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?
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: Mitch am 01 Oktober 2024, 09:03:25
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
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: Mitch am 31 März 2025, 22:12:23
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.
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: rudolfkoenig am 01 April 2025, 12:27:13
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.
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: vbs am 01 April 2025, 19:15:41
es ist eine verrückte Welt
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: rippi46 am 02 April 2025, 10:03:13
Hallo Mitch,

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

Gruß rippi
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: wolliballa73 am 06 April 2025, 09:44:58
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?
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: Prof. Dr. Peter Henning am 06 April 2025, 11:22:24
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
Titel: Aw: Suche Entwickler für Velux Modul
Beitrag von: Mitch am 30 April 2025, 10:19:58
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.
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Prof. Dr. Peter Henning am 01 Mai 2025, 16:38:20
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
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: JoWiemann am 01 Mai 2025, 19:33:04
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
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Mitch am 02 Mai 2025, 14:21:40
Zitat von: Prof. Dr. Peter Henning am 01 Mai 2025, 16:38:20
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

Deine Arroganz ist mal wieder mit nichts zu überbieten.



Zitat von: JoWiemann am 01 Mai 2025, 19:33:04Hallo,

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

Danke Jörg!
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Prof. Dr. Peter Henning am 02 Mai 2025, 15:46:16
Zitat von: Mitch am 02 Mai 2025, 14:21:40Deine Arroganz ist mal wieder mit nichts zu überbieten.
Aber doch. Durch mein Wissen über LLM.

LG

pah
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Mitch am 03 Mai 2025, 00:28:41
Zitat von: Prof. Dr. Peter Henning am 02 Mai 2025, 15:46:16
Zitat von: Mitch am 02 Mai 2025, 14:21:40Deine Arroganz ist mal wieder mit nichts zu überbieten.
Aber doch. Durch mein Wissen über LLM.

LG

pah


Sicher doch....
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: JoWiemann am 03 Mai 2025, 20:41:43
Hallo,

anbei die beiden Module in einer überarbeiteten Version. Das Gröbste sollte erledigt sein. Ganz zufrieden bin ich aber noch nicht. Am meisten stört mich, dass CLIENT_ID und CLIENT_SECRET aus einem reverse engineering stammen. Sollte hier Velux etwas ändern, wäre doof.

Grüße Jörg

PS: Sofern gewünscht würde ich die Module dann irgendwann ins SVN einchecken.
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Mitch am 04 Mai 2025, 08:50:32
Danke Jörg!
Ich werde heute Abend testen.
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Mitch am 05 Mai 2025, 10:30:12
Aussenrollos funktionieren, Innenrollos und Festermotoren haben gar kein SET

Internals:
   DEF        velux 53141e32171207cd
   DEVICE_ID  53141e32171207cd
   FUUID      67eaf2aa-f33f-5738-5e06-19d43a082891fff9
   GATEWAY    velux
   NAME       velux_blind_1
   NR         722
   STATE      100
   TYPE       VELUXACTIVEDEVICE
   VERSION    0.1 Beta
   eventCount 2
   Helper:
     DBLOG:
       current_position:
         myDbLog:
           TIME       1746433579.77191
           VALUE      100
   READINGS:
     2025-05-05 10:28:47   battery_state   high
     2025-05-05 10:28:47   bridge          70:ee:50:86:28:15
     2025-05-05 10:28:47   current_position 100
     2025-05-05 10:28:47   firmware_revision 8
     2025-05-05 10:28:47   id              53141e32171207cd
     2025-05-05 10:28:47   last_seen       1746433498
     2025-05-05 10:28:47   manufacturer    Netatmo
     2025-05-05 10:28:47   mode            algo_disabled
     2025-03-31 21:53:14   position        100
     2025-05-05 10:28:47   reachable       1
     2025-05-05 10:28:47   silent          0
     2025-05-05 10:28:47   state           100
     2025-05-05 10:28:47   target_position 100
     2025-05-05 10:28:47   type            NXO
     2025-05-05 10:28:47   velux_type      blind
Attributes:
   alias      Kammer Dachfensterrollo
   devStateIcon 0:fts_window_roof_shutter 100:fts_window_roof 9.*:fts_window_roof_shutter_90 8.*:fts_window_roof_shutter_80 7.*:fts_window_roof_shutter_70  6.*:fts_window_roof_shutter_60 5.*:fts_window_roof_shutter_50 4.*:fts_window_roof_shutter_40 3.*:fts_window_roof_shutter_30 2.*:fts_window_roof_shutter_20 1.*:fts_shutter_10
   event-on-change-reading .*
   eventMap   open:Auf close:Zu
   group      Rollos
   icon       fts_window_roof_shutter
   room       Kammer
   stateFormat current_position
   webCmd     Auf:Zu:stop
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: JoWiemann am 05 Mai 2025, 11:33:50
Zitat von: Mitch am 05 Mai 2025, 10:30:12Aussenrollos funktionieren, Innenrollos und Festermotoren haben gar kein SET

Für jeden velux_type möchte ich die richtigen set-Kommandos bereit stellen. Laut Deinem List für ein Rollo haben die dann den velux_type blind. Welchen velux_type haben Fenstermotoren?

Grüße Jörg
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Mitch am 07 Mai 2025, 19:16:54
velux_window

Internals:
   DEF        velux 53252e26171509d2
   DEVICE_ID  53252e26171509d2
   FUUID      67eaf2ab-f33f-5738-1bbb-32cbceedf78177c3
   GATEWAY    velux
   NAME       velux_window_2
   NR         730
   STATE      0
   TYPE       VELUXACTIVEDEVICE
   VERSION    1.0
   eventCount 967
   READINGS:
     2025-05-07 19:14:02   battery_state   high
     2025-05-07 19:14:02   bridge          70:ee:50:86:28:15
     2025-05-07 19:14:02   current_position 0
     2025-05-07 19:14:02   firmware_revision 42
     2025-05-07 19:14:02   id              53252e26171509d2
     2025-05-07 19:14:02   last_seen       1746509678
     2025-05-07 19:14:02   manufacturer    Netatmo
     2025-05-07 19:14:02   mode            algo_disabled
     2025-03-31 21:53:15   position        0
     2025-05-07 19:14:02   rain_position   50
     2025-05-07 19:14:02   reachable       1
     2025-05-07 19:14:02   secure_position 7
     2025-05-07 19:14:02   silent          0
     2025-05-07 19:14:02   target_position 0
     2025-05-07 19:14:02   type            NXO
     2025-05-07 19:14:02   velux_type      window
Attributes:
   alias      Kammer Dachfenster
   devStateIcon 0:fts_window_roof 100:fts_window_roof_open_2 6:fts_window_roof_open_1 7:fts_window_roof_open_1
   event-on-change-reading .*
   eventMap   open:Auf close:Zu
   group      Fenster
   icon       fts_window_roof
   room       Kammer
   stateFormat current_position
   verbose    1
   webCmd     Auf:Zu:stop
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: JoWiemann am 07 Mai 2025, 19:24:22
Hallo Mitch,

anbei die angepassten Module.

Grüße Jörg
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Mitch am 09 Mai 2025, 13:22:50
Fenster Auf geht leider noch nicht, Rest sieht soweit gut aus.
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: JoWiemann am 09 Mai 2025, 18:58:38
Hallo Mitch,

werden bei den Fenstermotoren die set-Befehle angezeigt?

Am Aufruf für die Befehle habe ich nichts geändert.

Grüße Jörg
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Mitch am 12 Mai 2025, 09:32:05
Ja, set wird angezeigt.
Ich muss die Woche nochmal testen, bin nur gerade beruflich sehr eingespannt.
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: JoWiemann am 12 Mai 2025, 09:59:54
Hallo Mitch,

kein Stress. Geht mir genauso.

Grüße Jörg
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: Sailor am 13 Mai 2025, 08:36:56
Hallo Rudi

Zitat von: rudolfkoenig am 30 September 2024, 10:01:04Mit welchem KI wurde es erstellt?
Wer ist der Urheber des Moduls?

Ich glaube hier könnte es langfristig ein rechtliches Problem geben.
Bisher haben wir unseren Code (sofern ins Repository eingecheckt) immer unter GPL veröffentlicht.

Was sagt denn ein Copyright-Anwalt, wenn ganze oder Teile eines Moduls mit einer KI programmiert werden:
a) Müssen wir Programmierer dann dies entsprechend im Kopf des Moduls kennzeichnen?
b) Was sagen denn die Geschäftsbedingungen von ChatGPT4 über die Copyright-Bestimmungen von Code?
c) Kann sich fhem (e.V.) davon freihalten, wenn ein Programmierer fälschlicherweise einen KI generierten Code als seinen eigenen unter GPL veröffentlicht?

Machen wir uns nichts vor - Der Code der Zukunft wird nicht mehr "zu Fuß" von versierten Programmierern geschrieben, sondern von einer KI.
Einfach, weil es geht.
Das mag der Eine oder Andere - berechtigt oder nicht - für befremdlich halten, aber diese Entwicklung dürfte wohl kaum aufzuhalten sein.

Gruß
    Sailor
Titel: Aw: Suche Entwickler für Velux Modul - KIG 300
Beitrag von: JoWiemann am 13 Mai 2025, 08:43:11
Hallo Sailor,

ich möchte mal einen von vielen Artikeln zu Thema KI, OpenSource und Rechte verlinken: https://ki-kanzlei.de/probleme-bei-ki-programmierung

Da wir unter GPL veröffentlichen und man davon ausgehen kann, das die LLM auch durch Fhem Sourcen trainiert worden sind, würde ich sagen, der erzeugte Code ist auch unter die GPL zu stellen und kann nicht mit einer anderen Lizenz versehen werden.

Grüße Jörg