FHEM > Codeschnipsel

BMW i3-Daten vom Server per MQTT weiterleiten

(1/2) > >>

hasenhirn:
Moin,

ich habe mal ein kleines Programm gebastelt um Daten vom I3s meiner Frau per MQTT in FHEM weiterzuleiten.
Die Idee und Programmteile habe ich von Rumbel aus dem Active-Tourer-Forum.de geklaut  - danke dafür :-)
Als erste info, ich habe keine Ahnung vom Programmieren, aber scheint trotzdem zu funktionieren  ;D ;D ;D
Wenn jemand daran Interesse hat kann er es gerne verwende oder abändern.
Ich übernehme aber keine Haftung für das Programm - Nutzung ist auf eigene Gefahr!!!

Wer es mal ausprobieren möchte, einfach den Code in eine Datei oder den Anhang nehmen und z.B. auf einem RasPi mit "sudo ./I3_Info.pl &" im Verzeichniss in der die Datei liegt starten. Natürlich müssen vorher die Zugangsdaten und die Daten für den MQTT-Server angepasst werden.
Viel Spaß damit!!!

Gruß
Thomas


--- Code: ---#!/usr/bin/perl

##########################################################
#
# Author : Thomas O.
#
# Version : 0.1
#
# Zweck : Daten für den I3 vom BMW-Server laden und an einen MQTT-Server weiterleiten
#
# Idee und Progammteile geklaut von Rumbel aus dem Active-Tourer-Forum.de  - danke dafür :-)
#
# Das Programm kann gerne von jedem verwendet, verändert oder sonst etwas damit gemacht werden.
# Ich übernehme KEINE Haftung für irgendwelche Schäden oder Fehlfunktionen!!!

##########################################################

use strict;
use warnings;
use POSIX;
use LWP::UserAgent ();
use Data::Dumper;
use JSON::Parse ':all';
use POSIX qw(strftime);
use XML::Simple qw(:strict);
use Net::MQTT::Simple;

########### Diese Daten müssen angepasst werden #######################################################

my $MQTT_IP = "192.168.1.38";
my $MQTT_Port = "1883";
my $mqtt_username = "user";
my $mqtt_password = "passwort";
my $User = 'BMW_Username@t-Offline.de';
my $Pass = "BMW_Passwort";
my $VIN = "WBY12345678901234";# Bei der VIN müssen alle 17 Stellen eingegeben werden
 
########### Diese Daten müssen angepasst werden #######################################################

my $abrufdauer = "300";#Pause zwische den Abrufen
my $dataurl = "https://www.bmw-connecteddrive.com/api/vehicle";
my $authurl = "https://customer.bmwgroup.com/gcdm/oauth/authenticate";
my $modul = "";
my $modul_1 = "dynamic";
my $modul_2 = "service";
my $modul_3 = "navigation";
my $rep = "";
my $DEBUG = 0;
my $Bearer = "Pv8MTRaNjsaXL5rIY5mq2i1DONjRuuiP";
my $BearerValidUntil = "1623578914";
my $AnzahlBearerAbrufe = 0;
my $mqtt = "";
my @Werte = ('remaining_range', 'updateTime_converted_timestamp', 'chargingHVStatus', 'vehicle_tracking', 'beRemainingRangeElectricKm', 'updateTime_converted_time', 'trunk_state', 'overall_energy_consumption', 'mileage', 'condition_based_services', 'Segment_LastTrip_time_segment_end_formatted_date', 'heading', 'door_passenger_rear', 'chargingLevelHv', 'updateTime', 'beEnergyLevelHv', 'battery_size_max', 'remaining_charging_time_minutes', 'connectorStatus', 'Segment_LastTrip_ratio_electric_driven_distance', 'lights_parking', 'door_passenger_front', 'updateTime_converted_date', 'unitOfEnergy', 'soc_hv_percent', 'window_driver_front', 'beChargingLevelHv', 'hood_state', 'unitOfCombustionConsumption', 'lsc_trigger', 'Segment_LastTrip_time_segment_end_formatted_time', 'gps_lng', 'beRemainingRangeElectric', 'gps_lat', 'remaining_fuel', 'lastUpdateReason', 'charging_status', 'beRemainingRangeFuelMile', 'charging_connection_type', 'beMaxRangeElectricKm', 'lastChargingEndReason', 'Segment_LastTrip_time_segment_end_formatted', 'door_lock_state', 'single_immediate_charging', 'beMaxRangeElectric', 'beRemainingRangeFuel', 'unitOfLength', 'door_driver_front', 'updateTime_converted', 'beRemainingRangeElectricMile', 'window_passenger_front', 'door_driver_rear', 'unitOfElectricConsumption', 'kombi_current_remaining_range_fuel', 'beRemainingRangeFuelKm', 'Segment_LastTrip_time_segment_end');

################################################################################################################################

# i3Info
{
print "i3Info Start \n" if $DEBUG  > 0 ;
i3Info_MQTT_Login();
while (1) {
my $BearerAbruf = 0;
print "Bearer : $Bearer \n"  if $DEBUG  > 1;
print "BearerValidUntil : " if $DEBUG  > 1;
print strftime(" %d.%m.%Y - %H:%M:%S", localtime($BearerValidUntil))  if $DEBUG  > 1;
print "\n"  if $DEBUG  > 1;
if ( $Bearer eq "" ) {
    print "Bearer leer \n"  if $DEBUG  > 1;
    $BearerAbruf = i3Info_BearerAbruf();
}
if ( $BearerValidUntil <= time()) {
    print "Bearer abgelaufen \n"  if $DEBUG  > 1;
    $BearerAbruf = i3Info_BearerAbruf();
}
if ( $BearerAbruf eq 0 ) {
     $modul = $modul_1;
     i3Info_Datenabruf();
     i3Info_Datenauswertung();
#     $modul = $modul_2;
#     i3Info_Datenabruf();
#     i3Info_Datenauswertung();
#     $modul = $modul_3;
#     i3Info_Datenabruf();
#     i3Info_Datenauswertung();
    }
    sleep $abrufdauer;
    }
print "i3Info Ende \n" if $DEBUG > 0 ;
}

################################################################################################################################

sub i3Info_Datenauswertung()
{
print "i3Info_Datenauswertung Start \n" if $DEBUG  > 0 ;
my $content=parse_json($rep->decoded_content());
#print Dumper($content);
my $i = 0;
while ($i <= $#Werte) {
$mqtt->publish("I3/$Werte[$i]", "$content->{attributesMap}->{$Werte[$i]}");
#    print $content->{attributesMap}->{$Werte[$i]}. " $Werte[$i] \n";
$i++;
}
print "i3Info_Datenauswertung Ende \n" if $DEBUG  > 0 ;
}

################################################################################################################################

sub i3Info_MQTT_Login()
{
$ENV{MQTT_SIMPLE_ALLOW_INSECURE_LOGIN} = 1; #Unverschlüsselte Verbindung mit Login erlaubt
$mqtt = Net::MQTT::Simple->new( "$MQTT_IP:$MQTT_Port");
if($mqtt_username and $mqtt_password) {
    $mqtt->login($mqtt_username, $mqtt_password);
}
#$mqtt->disconnect();
}

################################################################################################################################

sub i3Info_BearerAbruf()
{
print "i3Info_BearerAbruf Start \n" if $DEBUG  > 0;
my $Bearerexpires = 0;
my $Zugangsdatentest = i3Info_Zugangsdatentest();
print "Zugangsdatentest : $Zugangsdatentest \n" if $DEBUG  > 0;
if ($Zugangsdatentest eq "OK")
    {
    print "AnzahlBearerAbrufe : $AnzahlBearerAbrufe \n"  if $DEBUG  > 1;   
     if ( $AnzahlBearerAbrufe < 3 )
        {
        my $Browser = LWP::UserAgent->new;
        $Browser->timeout(10);
        $Browser->agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); 
        $Browser->default_header('Content-Type' => "application/x-www-form-urlencode");
        $Browser->default_header("Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
        $Browser->max_redirect(1);
        $Browser->add_handler( response_header =>  sub { my($response, $ua, $h,$data) = @_; $rep = $response; }  );
        $Browser->post("$authurl?username=$User&password=$Pass&client_id=dbf0a542-ebd1-4ff0-a9a7-55172fbfce35&redirect_uri=https%3A%2F%2Fwww.bmw-connecteddrive.com%2Fapp%2Fdefault%2Fstatic%2Fexternal-dispatch.html&response_type=token&scope=authenticate_user%20fupo&state=eyJtYXJrZXQiOiJkZSIsImxhbmd1YWdlIjoiZGUiLCJkZXN0aW5hdGlvbiI6InVzZXJEYXNoYm9hcmQiLCJwYXJhbWV0ZXJzIjoie30ifQ&locale=DE-de");
        print $rep->code if $DEBUG  > 1;
        print " \n" if $DEBUG  > 1;
        my $Bearerdata = $rep->header("Location");
        print " Bearerdata : $Bearerdata \n"if $DEBUG  > 0;
        if ( $Bearerdata eq "https://www.bmw-connecteddrive.com/app/default/static/external-dispatch.html?error=access_denied" )
        {
        $AnzahlBearerAbrufe++ ;
        print " Fehler beim Abruf - neuer Versuch \n"if $DEBUG  > 0;
        i3Info_BearerAbruf();
        }
        $Browser->remove_handler( undef );
        $Bearerdata =~ s/.*access_token=([^&]*).*expires_in=([^&]*).*/$1,$2/;
        ($Bearer, $Bearerexpires) = split(/,/,$Bearerdata);
        $BearerValidUntil = time() + $Bearerexpires - 10;
        print "Bearer : $Bearer \n"  if $DEBUG  > 1;
        my $date = strftime(" %d.%m.%Y - %H:%M:%S", localtime($BearerValidUntil));
        print "BearerValidUntil : $BearerValidUntil \n"  if $DEBUG  > 1;
        print "BearerValidUntil : $date \n"  if $DEBUG  > 1;
        print "AnzahlBearerAbrufe : $AnzahlBearerAbrufe \n"  if $DEBUG  > 1;
        print "i3Info_BearerAbruf Ende \n" if $DEBUG  > 0;
        return 0;
        }
    }
    print "Zugangsdatenfehler \n" if $DEBUG  > 1;
    print "i3Info_BearerAbruf Ende mit Fehler \n" if $DEBUG  > 0;
    exit;
}

################################################################################################################################

sub i3Info_Zugangsdatentest()
{
    print "i3Info_Zugangsdatentest Start \n" if $DEBUG  > 0;
    if ($User eq "" or $Pass eq "" or $VIN eq "")
    {       
        #fhem("set BMW_CurrentState Error: Username,Passwort oder VIN leer") if $DEBUG > 0;
        return "Error: Username,Passwort oder VIN leer";
    }
   
    # Test Mailadresse
    if ($User !~ m/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/)
    {
        #fhem("set BMW_CurrentState Error: Mailadresse schein falsch zu sein: <$User>") if $DEBUG > 0;
        return "Error: Mailadresse schein falsch zu sein: <$User>";
    }
   
    # Passwort auf unzulässige Zeichen testen
    if ($Pass =~ m/.*[&#+].*/) # auf & prüfen
    {
        #fhem("set BMW_CurrentState Error: Password enthält unzulässige Zeichen ( &#+ )") if $DEBUG > 0;
        return "Error: Password enthält unzulässige Zeichen ( &#+ )";   
    }
     
    # VIN auf Richtigkeit und länge testen
    if ($VIN !~ m/WB[ASY].{14}/)
    {
        #fhem("set BMW_CurrentState Error: VIN überprüfen. Sind 17 Zeichen eingegeben und fängt an mit WB...? ") if $DEBUG > 0;
        return "Error: VIN überprüfen. Sind 17 Zeichen eingegeben?)";   
    }
    print "i3Info_Zugangsdatentest Ende \n" if $DEBUG  > 0;   
    return "OK";
}

################################################################################################################################

sub i3Info_Datenabruf
{
    print "i3Info_Datenabruf Start \n" if $DEBUG  > 0;
    my $Browser = LWP::UserAgent->new;
    $Browser->timeout(10);
    $Browser->agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); 
    $Browser->default_header('Content-Type' => "application/json, text/plain, */*");
    $Browser->default_header("Authorization" => "Bearer ". $Bearer);
    $Browser->default_header("Connection" => "keep-alive");
    $Browser->default_header("Accept-Encoding" => "gzip, deflate, br");
    $Browser->default_header("Accept-Language" => "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7");
    $Browser->default_header("Host" => "www.bmw-connecteddrive.com");
    $Browser->default_header("Referer" => "https://www.bmw-connecteddrive.com/app/de/index.html");
    $Browser->agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); 
    $rep = $Browser->get("$dataurl/$modul/v1/$VIN?offset=-60");
    if ( $DEBUG > 1 )
    {
    print "Abruf-URL : $dataurl/$modul/v1/$VIN?offset=-60 \n" ;
    print "\n Code : ";
    print Dumper($rep->code);
    print "\n Message : ";
    print Dumper($rep->message);
    print "\n Content : ";
    print Dumper($rep->decoded_content());   
    print "\n";
    }
    if ( $rep->code != 200 )
    {
    i3Info_BearerAbruf();
    }   
$AnzahlBearerAbrufe = 0 ;
print "i3Info_Datenabruf Ende \n" if $DEBUG  > 0;
}

################################################################################################################################
--- Ende Code ---

hasenhirn:
Moin,

ich habe im Programm ein paar Klammerzeichen hinter der Definition der sub's entfernt welche beim Aufruf Fehlermeldungen produziert haben.
Die neu Version habe ich angehängt.
Des weiteren war der Start über die Kommandozeile auch nur eine Notlösung auf die Schnelle.
Im Anhang habe ich noch einen Service-Datei um das Programm über systemd starten zu lassen. So gefällt mir das schon besser  ;) ;D

Gruß
Tom


--- Code: ---[Unit]
Description=I3_Info, the daemon for communication with the BMW I3 system.
After=network-online.target
ConditionPathExists=/var/log

[Service]
Type=simple
Restart=always
RestartSec=30
User=root
Group=dialout
WorkingDirectory=/usr/bin
PIDFile=/var/run/I3_Info.pid
ExecStart=/usr/bin/perl I3_Info.pl

[Install]
WantedBy=multi-user.target
--- Ende Code ---

Bjoernar:
Hallo,

Ich habe dein Modul verwendet und so geändert das die Daten direkt und nicht über mqtt in fhem landen.
Ich habe nun aber immer wieder das Problem das die Daten nicht aktualisiert werden.

UpdateTime ist schon ein paar Stunden alt.
Die Daten werden aber korrekt von BMW geholt und an fhem übergeben.

Wenn ich das Auto öffne und wieder schliße oder über die my BMW App die Daten Abfrage sind danach auch die Daten über die API aktuelle.

Hast du solche Probleme auch?

Gruß
Björnar

hasenhirn:
Moin Bjoernar,

das ist halt ein BMW  ;)
Bei meinem Tesla z.B. werden die Daten im Prinzip direkt vom Auto abgerufen und das Auto dafür aufgeweckt.
Beim BMW werden die Daten auf einen Server übertragen und du bekommst sie von dort.
Wenn das Auto jetzt "einschläft" werden immer die Daten vom Server abgefragt und das Auto "schläft" weiter. So spart das Auto Strom.
In den Daten die abgerufen werden steht irgendwo auch das Datum und die Uhrzeit der letzten Aktualisierung.
In der Variablen "Segment_LastTrip_time_segment_end" steht glaube ich die Zeit der letzten Datenübertragung vom Auto zu Server.
Ich hoffe das hat dir weiter geholfen.

LG
Thomas

s00rb:
Moin hasenhirn,

Erstmal vielen Dank für deine Arbeit und die Bereitstellung der beiden Script.
Die laufen auch problemlos bei mir.

Eine Sache bzw. Frage habe ich allerdings.

Bei mir ist es so, dass alle 2-3 Tage ein neues MQTT device angelegt wird in dem die Daten vom i3 gelistet werden. Im „alten“ Device werden die readings nicht mehr aktualisiert.
Somit ist es sinnlos irgendwelche notifys zu erstellen, da sich das Ursprungs-Device andauernd ändert.

Verhält sich das bei dir (bzw anderen Nutzern) genau so? Oder wie hast Du (ihr) gelöst ?
Ist es möglich über den Script eine Client ID zu vergeben, sodass nicht immer ein neues MQTT Device erstellt wird?

Lieben Gruß Mario

Navigation

[0] Themen-Index

[#] Nächste Seite

Zur normalen Ansicht wechseln