erster push, no usable code
This commit is contained in:
parent
1655c6645a
commit
55ecec4aa4
209
FHEM/73_Bluelink.pm
Normal file
209
FHEM/73_Bluelink.pm
Normal file
@ -0,0 +1,209 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Developed with love
|
||||
#
|
||||
# (c) 2025-2025 Copyright: Sebastian Schwarz
|
||||
#
|
||||
# This script is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# any later version.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
package FHEM::Bluelink;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use GPUtils qw(GP_Import GP_Export);
|
||||
require FHEM::Devices::Bluelink::Bluelink;
|
||||
|
||||
## Import der FHEM Funktionen
|
||||
#-- Run before package compilation
|
||||
BEGIN {
|
||||
|
||||
# Import from main context
|
||||
GP_Import(
|
||||
qw(
|
||||
readingFnAttributes
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#-- Export to main context with different name
|
||||
GP_Export(
|
||||
qw(
|
||||
Initialize
|
||||
)
|
||||
);
|
||||
|
||||
sub Initialize {
|
||||
my $hash = shift;
|
||||
|
||||
# Provider
|
||||
$hash->{WriteFn} = \&Write;
|
||||
|
||||
# Consumer
|
||||
$hash->{DefFn} = 'FHEM::Devices::Bluelink::Bluelink::Define';
|
||||
$hash->{UndefFn} = 'FHEM::Devices::Bluelink::Bluelink::Undef';
|
||||
$hash->{SetFn} = 'FHEM::Devices::Bluelink::Bluelink::Set';
|
||||
$hash->{DeleteFn} = 'FHEM::Devices::Bluelink::Bluelink::Delete';
|
||||
$hash->{RenameFn} = 'FHEM::Devices::Bluelink::Bluelink::Rename';
|
||||
$hash->{NotifyFn} = 'FHEM::Devices::Bluelink::Bluelink::Notify';
|
||||
|
||||
|
||||
$hash->{AttrFn} = 'FHEM::Devices::Bluelink::Bluelink::Attr';
|
||||
$hash->{AttrList} =
|
||||
'disable:1 '
|
||||
. 'interval '
|
||||
. $::readingFnAttributes;
|
||||
$hash->{parseParams} = 1;
|
||||
|
||||
return FHEM::Meta::InitMod( __FILE__, $hash );
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
|
||||
=item device
|
||||
=item summary Modul to control Bluelink Vehicles
|
||||
=item summary_DE Modul zur Steuerung von Bluelink Fahrzeugen
|
||||
=begin html
|
||||
|
||||
<a name="Bluelink"></a>
|
||||
<h3>Bluelink</h3>
|
||||
<a name="Bluelinkdefine"></a>
|
||||
<b>Define</b>
|
||||
<ul><br>
|
||||
<code>define <name> Bluelink </code>
|
||||
<br><br>
|
||||
Example:
|
||||
<ul><br>
|
||||
<code>define bl Bluelink </code><br>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="Bluelinkreadings"></a>
|
||||
<br><br>
|
||||
<b>Readings</b>
|
||||
<ul>
|
||||
<li>1st</li>
|
||||
<li>2nd</li>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="Bluelinkeset"></a>
|
||||
<b>set</b>
|
||||
<ul>
|
||||
<li>first</li>
|
||||
<li>secibd</li>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="Bluelinkeattributes"></a>
|
||||
<b>Attributes</b>
|
||||
<ul>
|
||||
<li>foo</li>
|
||||
<li>bar</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
=begin html_DE
|
||||
|
||||
<a name="Bluelink"></a>
|
||||
<h3>Bluelink</h3>
|
||||
<br>
|
||||
<a name="Bluelinkdefine"></a>
|
||||
<b>Define</b>
|
||||
<ul><br>
|
||||
<code>define <name> Bluelink</code>
|
||||
<br><br>
|
||||
Beispiel:
|
||||
<ul><br>
|
||||
<code>define bl Bluelink</code><br>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="Bluelinkreadings"></a>
|
||||
<br><br>
|
||||
<b>Readings</b>
|
||||
<ul>
|
||||
<li>1st</li>
|
||||
<li>2nd</li>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="Bluelinkset"></a>
|
||||
<b>set</b>
|
||||
<ul>
|
||||
<li>erstens </li>
|
||||
<li>zweitens</li>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="Bluelinkeattributes"></a>
|
||||
<b>Attributes</b>
|
||||
<ul>
|
||||
<li>foo</li>
|
||||
<li>bar</li>
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
|
||||
=end html_DE
|
||||
|
||||
=for :application/json;q=META.json 73_Bluelink.pm
|
||||
{
|
||||
"abstract": "Modul to control Bluelink",
|
||||
"x_lang": {
|
||||
"de": {
|
||||
"abstract": "Modul zum bedienen des Bluelink"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"fhem-mod-device",
|
||||
"fhem-core",
|
||||
"Bluelink",
|
||||
"Smart"
|
||||
],
|
||||
"release_status": "stable",
|
||||
"license": "GPL_2",
|
||||
"version": "v0.0.1",
|
||||
"author": [
|
||||
"Sebastian Schwarz <ema@il.local>"
|
||||
],
|
||||
"x_fhem_maintainer": [
|
||||
"BOFH"
|
||||
],
|
||||
"x_fhem_maintainer_github": [
|
||||
"NO ONE"
|
||||
],
|
||||
"prereqs": {
|
||||
"runtime": {
|
||||
"requires": {
|
||||
"FHEM": 5.00918799,
|
||||
"perl": 5.016,
|
||||
"Meta": 0,
|
||||
"HttpUtils": 0,
|
||||
"Encode": 0
|
||||
},
|
||||
"recommends": {
|
||||
},
|
||||
"suggests": {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
=end :application/json;q=META.json
|
||||
|
||||
=cut
|
2
controls_BlueLink.txt
Normal file
2
controls_BlueLink.txt
Normal file
@ -0,0 +1,2 @@
|
||||
UPD 2025-02-26_15:50:12 4327 FHEM/73_BlueLink.pm
|
||||
UPD 2025-02-26_13:26:10 3120 lib/FHEM/Devices/BlueLink/Hyundai.pm
|
43
hooks/pre-commit
Executable file
43
hooks/pre-commit
Executable file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use File::Basename;
|
||||
use POSIX qw(strftime);
|
||||
use strict;
|
||||
|
||||
my @filenames = (
|
||||
'FHEM/73_GoECharger.pm',
|
||||
'lib/FHEM/Devices/GoE/GoECharger.pm',
|
||||
'www/images/fhemSVG/goecharger.svg'
|
||||
);
|
||||
|
||||
my $controlsfile = 'controls_GoECharger.txt';
|
||||
|
||||
open(FH, ">$controlsfile") || return("Can't open $controlsfile: $!");
|
||||
|
||||
for my $filename (@filenames) {
|
||||
my @statOutput = stat($filename);
|
||||
|
||||
if (scalar @statOutput != 13) {
|
||||
printf 'error: stat has unexpected return value for ' . $filename . "\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my $mtime = $statOutput[9];
|
||||
my $date = POSIX::strftime("%Y-%m-%d", localtime($mtime));
|
||||
my $time = POSIX::strftime("%H:%M:%S", localtime($mtime));
|
||||
my $filetime = $date."_".$time;
|
||||
|
||||
my $filesize = $statOutput[7];
|
||||
|
||||
printf FH 'UPD ' . $filetime . ' ' . $filesize . ' ' .$filename . "\n";
|
||||
}
|
||||
|
||||
close(FH);
|
||||
|
||||
system("git log -1 | tail -n1 > CHANGED");
|
||||
system("git add CHANGED");
|
||||
system("git add $controlsfile");
|
||||
|
||||
print 'Create controls File succesfully' . "\n";
|
||||
|
||||
exit 0;
|
198
lib/FHEM/Devices/Bluelink/Bluelink.pm
Normal file
198
lib/FHEM/Devices/Bluelink/Bluelink.pm
Normal file
@ -0,0 +1,198 @@
|
||||
package FHEM::Bluelink;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use LWP::UserAgent;
|
||||
use HTTP::Request::Common qw(GET POST);
|
||||
use JSON;
|
||||
use MIME::Base64;
|
||||
use Digest::SHA qw(sha256_hex);
|
||||
use Time::HiRes qw(time);
|
||||
|
||||
sub Initialize {
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "Bluelink_Define";
|
||||
$hash->{UndefFn} = "Bluelink_Undef";
|
||||
$hash->{RenameFn} = "Bluelink_Rename";
|
||||
$hash->{DeleteFn} = "Bluelink_Delete";
|
||||
$hash->{NotifyFn} = "Bluelink_Notify";
|
||||
|
||||
$hash->{SetFn} = "Bluelink_Set";
|
||||
$hash->{GetFn} = "Bluelink_Get";
|
||||
$hash->{AttrList} = "username password language pin client_id client_secret access_token refresh_token";
|
||||
}
|
||||
|
||||
sub Bluelink_Define {
|
||||
my ($hash, $def) = @_;
|
||||
my @args = split("[ \t]+", $def);
|
||||
|
||||
return "Usage: define <name> Bluelink <API_URL>" if (@args < 3);
|
||||
|
||||
$hash->{API_URL} = $args[2];
|
||||
return;
|
||||
}
|
||||
|
||||
sub Bluelink_Undef {
|
||||
my ($hash) = @_;
|
||||
return;
|
||||
}
|
||||
|
||||
sub Bluelink_Set {
|
||||
my ($hash, $name, $cmd, @args) = @_;
|
||||
|
||||
return "\"set $name\" needs at least one argument" unless defined $cmd;
|
||||
|
||||
if ($cmd eq "login") {
|
||||
return Bluelink_Login($hash);
|
||||
} elsif ($cmd eq "refreshToken") {
|
||||
return Bluelink_RefreshToken($hash);
|
||||
} elsif ($cmd eq "setLanguage") {
|
||||
return Bluelink_SetLanguage($hash, $args[0]);
|
||||
}
|
||||
|
||||
return "Unknown argument $cmd, choose one of login refreshToken setLanguage";
|
||||
}
|
||||
|
||||
sub Bluelink_Get {
|
||||
my ($hash, $name, $cmd, @args) = @_;
|
||||
|
||||
return "\"get $name\" needs at least one argument" unless defined $cmd;
|
||||
|
||||
if ($cmd eq "deviceid") {
|
||||
return Bluelink_GetDeviceID($hash);
|
||||
} elsif ($cmd eq "token") {
|
||||
return AttrVal($name, "access_token", "No token available");
|
||||
}
|
||||
|
||||
return "Unknown argument $cmd, choose one of deviceid token";
|
||||
}
|
||||
|
||||
sub Bluelink_GetDeviceID {
|
||||
my ($hash) = @_;
|
||||
my $api_url = $hash->{API_URL} . "/api/v1/spa/notifications/register";
|
||||
|
||||
my $uuid = lc(sha256_hex(time() . rand()));
|
||||
my $data = {
|
||||
pushRegId => uc(unpack("H*", pack("C*", map { int(rand(16)) } (1..32)))),
|
||||
pushType => "GCM",
|
||||
uuid => $uuid
|
||||
};
|
||||
|
||||
my $res = Bluelink_HttpPost($api_url, $data);
|
||||
|
||||
return "Device ID: " . ($res->{ResMsg}->{DeviceID} // "Not found");
|
||||
}
|
||||
|
||||
sub Bluelink_Login {
|
||||
my ($hash) = @_;
|
||||
my $username = AttrVal($hash->{NAME}, "username", "");
|
||||
my $password = AttrVal($hash->{NAME}, "password", "");
|
||||
my $api_url = $hash->{API_URL} . "/api/v1/user/signin";
|
||||
|
||||
return "Missing username or password" unless $username && $password;
|
||||
|
||||
my $data = { email => $username, password => $password };
|
||||
my $res = Bluelink_HttpPost($api_url, $data);
|
||||
|
||||
return "Login failed: " . $res->{errMsg} if $res->{errCode};
|
||||
|
||||
my $redirect_url = $res->{redirectUrl};
|
||||
my $code = (split(/\?code=/, $redirect_url))[1];
|
||||
|
||||
return Bluelink_ExchangeCode($hash, $code);
|
||||
}
|
||||
|
||||
sub Bluelink_ExchangeCode {
|
||||
my ($hash, $code) = @_;
|
||||
my $api_url = $hash->{API_URL} . "/api/v1/user/oauth2/token";
|
||||
my $client_id = AttrVal($hash->{NAME}, "client_id", "");
|
||||
my $client_secret = AttrVal($hash->{NAME}, "client_secret", "");
|
||||
|
||||
return "Missing client_id or client_secret" unless $client_id && $client_secret;
|
||||
|
||||
my $auth = encode_base64("$client_id:$client_secret", "");
|
||||
|
||||
my $data = {
|
||||
grant_type => "authorization_code",
|
||||
redirect_uri => $hash->{API_URL} . "/api/v1/user/oauth2/redirect",
|
||||
code => $code
|
||||
};
|
||||
|
||||
my $res = Bluelink_HttpPost($api_url, $data, { Authorization => "Basic $auth" });
|
||||
|
||||
if ($res->{access_token}) {
|
||||
fhem("attr $hash->{NAME} access_token " . $res->{access_token});
|
||||
fhem("attr $hash->{NAME} refresh_token " . $res->{refresh_token});
|
||||
return "Login successful. Token saved.";
|
||||
}
|
||||
|
||||
return "Token exchange failed.";
|
||||
}
|
||||
|
||||
sub Bluelink_RefreshToken {
|
||||
my ($hash) = @_;
|
||||
my $api_url = $hash->{API_URL} . "/api/v1/user/oauth2/token";
|
||||
my $refresh_token = AttrVal($hash->{NAME}, "refresh_token", "");
|
||||
my $client_id = AttrVal($hash->{NAME}, "client_id", "");
|
||||
my $client_secret = AttrVal($hash->{NAME}, "client_secret", "");
|
||||
|
||||
return "Missing refresh_token, client_id or client_secret" unless $refresh_token && $client_id && $client_secret;
|
||||
|
||||
my $auth = encode_base64("$client_id:$client_secret", "");
|
||||
|
||||
my $data = {
|
||||
grant_type => "refresh_token",
|
||||
redirect_uri => "https://www.getpostman.com/oauth2/callback",
|
||||
refresh_token => $refresh_token
|
||||
};
|
||||
|
||||
my $res = Bluelink_HttpPost($api_url, $data, { Authorization => "Basic $auth" });
|
||||
|
||||
if ($res->{access_token}) {
|
||||
fhem("attr $hash->{NAME} access_token " . $res->{access_token});
|
||||
fhem("attr $hash->{NAME} refresh_token " . $res->{refresh_token});
|
||||
return "Token refreshed.";
|
||||
}
|
||||
|
||||
return "Token refresh failed.";
|
||||
}
|
||||
|
||||
sub Bluelink_SetLanguage {
|
||||
my ($hash, $language) = @_;
|
||||
my $api_url = $hash->{API_URL} . "/api/v1/user/language";
|
||||
|
||||
my $data = { lang => $language };
|
||||
|
||||
my $res = Bluelink_HttpPost($api_url, $data);
|
||||
|
||||
return "Language set to $language" if $res;
|
||||
|
||||
return "Failed to set language.";
|
||||
}
|
||||
|
||||
sub Bluelink_HttpPost {
|
||||
my ($url, $data, $headers) = @_;
|
||||
|
||||
my $ua = LWP::UserAgent->new();
|
||||
my $json_data = encode_json($data);
|
||||
|
||||
my $req = HTTP::Request->new(POST => $url);
|
||||
$req->header("Content-Type" => "application/json");
|
||||
$req->content($json_data);
|
||||
|
||||
if ($headers) {
|
||||
foreach my $key (keys %$headers) {
|
||||
$req->header($key => $headers->{$key});
|
||||
}
|
||||
}
|
||||
|
||||
my $res = $ua->request($req);
|
||||
|
||||
return decode_json($res->decoded_content) if $res->is_success;
|
||||
|
||||
return { error => "Request failed", status => $res->code };
|
||||
}
|
||||
|
||||
1;
|
||||
|
113
lib/FHEM/Devices/Bluelink/Hyundai.pm
Normal file
113
lib/FHEM/Devices/Bluelink/Hyundai.pm
Normal file
@ -0,0 +1,113 @@
|
||||
package FHEM::BlueLinkVehicle;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use JSON;
|
||||
use Time::Piece;
|
||||
|
||||
sub Initialize {
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "BlueLinkVehicle_Define";
|
||||
$hash->{SetFn} = "BlueLinkVehicle_Set";
|
||||
$hash->{GetFn} = "BlueLinkVehicle_Get";
|
||||
$hash->{AttrList} = "vin IODev";
|
||||
}
|
||||
|
||||
sub BlueLinkVehicle_Define {
|
||||
my ($hash, $def) = @_;
|
||||
my @args = split("[ \t]+", $def);
|
||||
|
||||
return "Usage: define <name> BlueLinkVehicle <VIN> <IODev>" if (@args < 4);
|
||||
|
||||
$hash->{VIN} = $args[2];
|
||||
my $parent = $args[3];
|
||||
$hash->{IODev} = $defs{$parent};
|
||||
|
||||
return "Parent device $parent not found" unless defined $hash->{IODev};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub BlueLinkVehicle_Set {
|
||||
my ($hash, $name, $cmd, @args) = @_;
|
||||
|
||||
return "\"set $name\" needs at least one argument" unless defined $cmd;
|
||||
|
||||
if ($cmd eq "update") {
|
||||
return BlueLinkVehicle_Update($hash);
|
||||
}
|
||||
|
||||
return "Unknown argument $cmd, choose one of update";
|
||||
}
|
||||
|
||||
sub BlueLinkVehicle_Get {
|
||||
my ($hash, $name, $cmd, @args) = @_;
|
||||
|
||||
return "\"get $name\" needs at least one argument" unless defined $cmd;
|
||||
|
||||
if ($cmd eq "soc") {
|
||||
return ReadingsVal($name, "SoC", "Unknown") . " %";
|
||||
} elsif ($cmd eq "range") {
|
||||
return ReadingsVal($name, "Range", "Unknown") . " km";
|
||||
} elsif ($cmd eq "status") {
|
||||
return ReadingsVal($name, "Status", "Unknown");
|
||||
}
|
||||
|
||||
return "Unknown argument $cmd, choose one of soc range status";
|
||||
}
|
||||
|
||||
sub BlueLinkVehicle_Update {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $parent = $hash->{IODev};
|
||||
my $vin = $hash->{VIN};
|
||||
|
||||
return "Parent module not found" unless $parent;
|
||||
return "VIN not defined" unless $vin;
|
||||
|
||||
# Anfrage an das Hauptmodul senden (IOWrite)
|
||||
IOWrite($parent, "getVehicleStatus", $vin);
|
||||
return "Request sent to parent module.";
|
||||
}
|
||||
|
||||
sub BlueLinkVehicle_ParseTime {
|
||||
my ($time_string) = @_;
|
||||
return unless $time_string;
|
||||
|
||||
my $format = "%Y%m%d%H%M%S %z";
|
||||
my $t = Time::Piece->strptime($time_string . " +0100", $format);
|
||||
|
||||
return $t->strftime("%Y-%m-%d %H:%M:%S");
|
||||
}
|
||||
|
||||
sub BlueLinkVehicle_Receive {
|
||||
my ($hash, $data) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $json = eval { decode_json($data) };
|
||||
return "Invalid JSON data received" if $@;
|
||||
|
||||
if ($json->{vin} && $json->{vin} eq $hash->{VIN}) {
|
||||
my $status = $json->{status};
|
||||
|
||||
my $soc = $status->{EvStatus}->{BatteryStatus} // "Unknown";
|
||||
my $range = $status->{EvStatus}->{DrvDistance}->[0]->{RangeByFuel}->{EvModeRange}->{Value} // "Unknown";
|
||||
my $charging = $status->{EvStatus}->{BatteryCharge} ? "Charging" : "Not Charging";
|
||||
my $updated = BlueLinkVehicle_ParseTime($status->{Time}) // "Unknown";
|
||||
|
||||
readingsBeginUpdate($hash);
|
||||
readingsBulkUpdate($hash, "SoC", $soc);
|
||||
readingsBulkUpdate($hash, "Range", $range);
|
||||
readingsBulkUpdate($hash, "Status", $charging);
|
||||
readingsBulkUpdate($hash, "LastUpdate", $updated);
|
||||
readingsEndUpdate($hash, 1);
|
||||
|
||||
return "Vehicle data updated.";
|
||||
}
|
||||
|
||||
return "VIN mismatch - ignoring data.";
|
||||
}
|
||||
|
||||
1;
|
||||
|
Loading…
x
Reference in New Issue
Block a user