Re: Habe mir eine WebApp für den Batterie-Status gebaut
Verfasst: So 29. Jan 2017, 13:38
Schade verstehe kein Wort, hätte aber auch gern so ne App. Gibts da irgendwie ne Anleitung für Doofe?
Elektroauto Forum auf GoingElectric.de
https://www.goingelectric.de/forum/
https://www.goingelectric.de/forum/viewtopic.php?f=72&t=21224
Das wäre ja zwecklos.Knobi hat geschrieben:Gibts da irgendwie ne Anleitung für Doofe?
Sehr gerne.endurance hat geschrieben:DANKE - vor allem dafür den Stein hier ins Rollen gebracht zu haben
Man könnte auch den Batterie-Prozentwert unten bei den Metriken abbilden, dann hätte man noch die Hälfte der Höhe für weitere Werte.endurance hat geschrieben: Wird so langsam eng (Einheiten habe ich schon kleiner dargestellt).
Code: Alles auswählen
main {
top: 40%;
left: 50%;
width: 200px;
height: 200px;
position: absolute;
border: 12px solid var(--color-main-border);
border-radius: 100%;
overflow: hidden;
transform: translate(-50%, -50%);
box-shadow: 0 0 0 transparent;
transition: all 1.6s cubic-bezier(0.165, 0.84, 0.44, 1);
}...
Code: Alles auswählen
29.01.2017 14:04; 1485698662000; 103; 97; 16,69; 17,52; --:--; 42622; -.-; 31.3
29.01.2017 14:04; 1485698662000; 103; 97; 17,70; 18,46; --:--; 42622; --.-; 31.3
29.01.2017 19:01; 1485716519000; 109; 96; 17,71; 18,46; 01:10; 42622; --.-; --.-
29.01.2017 19:04; 1485716687000; 109; 96; 17,73; 18,46; 01:07; 42622; 0.4; --.-
29.01.2017 19:05; 1485716713000; 109; 96; 17,74; 18,46; 01:06; 42622; 1.4; --.-
29.01.2017 19:06; 1485716791000; 109; 96; 17,75; 18,46; 01:05; 42622; 0.5; --.-
coole Sache gibt es das auch zum reuse? Was sind die Requirements - spezielles Aufnahmedevice, echo dot...? Scheint mir auch für Smarthome ganz interessant... gerade selber was gefundensystematic hat geschrieben: Ich hab mal ein Anfängervideo gedreht (miese Quali, ich weiß), damit ihr ungefähr wisst, wie sich das anhört.
https://www.dropbox.com/s/ewe73qfsjl1t7 ... 5.m4v?dl=0
Code: Alles auswählen
<?php
class Battery_API {
private $token_file = './access/token.json';
private $auth_file = './access/auth.json';
private $stats_file = './appstats.txt';
private $auth_api = 'https://customer.bmwgroup.com/gcdm/oauth/authenticate';
private $vehicle_api = 'https://www.bmw-connecteddrive.de/api/vehicle';
private $auth;
private $token;
private $json;
function __construct () {
$this->check_security();
$this->auth = $this->get_auth_data();
$this->token = $this->get_token();
$this->json = $this->get_vehicle_data();
$this->send_response_json();
}
function check_security() {
if ( empty( $_SERVER['HTTP_REFERER'] ) OR strcmp( parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_HOST ), $_SERVER['SERVER_NAME'] ) !== 0 ) {
http_response_code( 403 ) && exit;
}
}
function get_auth_data() {
return json_decode(
@file_get_contents(
$this->auth_file
)
);
}
function cache_remote_token( $token_data ) {
file_put_contents(
$this->token_file,
json_encode( $token_data )
);
}
function get_cached_token() {
return json_decode(
@file_get_contents(
$this->token_file
)
);
}
function get_token() {
// Get cached token
if ( $cached_token_data = $this->get_cached_token() ) {
if ( $cached_token_data->expires > time() ) {
$token = $cached_token_data->token;
}
}
// Get remote token
if ( empty( $token ) ) {
$token_data = $this->get_remote_token();
$token = $token_data->token;
$this->cache_remote_token( $token_data );
}
return $token;
}
function get_remote_token() {
// Init cURL
$ch = curl_init();
// Set cURL options
curl_setopt( $ch, CURLOPT_URL, $this->auth_api );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, false );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_HEADER, true );
curl_setopt( $ch, CURLOPT_NOBODY, true );
curl_setopt( $ch, CURLOPT_COOKIESESSION, true );
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/x-www-form-urlencoded' ) );
curl_setopt( $ch, CURLOPT_POSTFIELDS, 'username=' . urlencode( $this->auth->username) . '&password=' . urlencode( $this->auth->password) . '&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=eyJtYXJrZXQiOiJkZSIsImxhbmd1YWdlIjoiZGUiLCJkZXN0aW5hdGlvbiI6ImxhbmRpbmdQYWdlIn0&locale=DE-de' );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false);
// Exec curl request
$response = curl_exec( $ch );
// Close connection
curl_close( $ch );
// Extract token
preg_match( '/access_token=([\w\d]+).*token_type=(\w+).*expires_in=(\d+)/', $response, $matches );
// Check token type
if ( empty( $matches[2] ) OR $matches[2] !== 'Bearer' ) {
http_response_code( 424 ) && exit;
}
return (object) array(
'token' => $matches[1],
'expires' => time() + $matches[3]
);
}
function get_vehicle_data() {
// Init cURL
$ch_1 = curl_init();
$ch_2 = curl_init();
// Set cURL options
curl_setopt( $ch_1, CURLOPT_URL, $this->vehicle_api . '/dynamic/v1/' . $this->auth->vehicle . '?offset=-60' );
curl_setopt( $ch_1, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json' , 'Authorization: Bearer ' . $this->token ) );
curl_setopt( $ch_1, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch_1, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $ch_1, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt( $ch_2, CURLOPT_URL, $this->vehicle_api . '/navigation/v1/' . $this->auth->vehicle );
curl_setopt( $ch_2, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json' , 'Authorization: Bearer ' . $this->token ) );
curl_setopt( $ch_2, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch_2, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $ch_2, CURLOPT_SSL_VERIFYPEER, false);
// Build the multi-curl handle
$mh = curl_multi_init();
curl_multi_add_handle( $mh, $ch_1 );
curl_multi_add_handle( $mh, $ch_2 );
// Execute all queries simultaneously
$running = null;
do {
curl_multi_exec( $mh, $running );
} while ( $running );
// Close the handles
curl_multi_remove_handle( $mh, $ch_1 );
curl_multi_remove_handle( $mh, $ch_2 );
curl_multi_close( $mh );
// all of our requests are done, we can now access the results
$response_1 = curl_multi_getcontent( $ch_1 );
$response_2 = curl_multi_getcontent( $ch_2 );
// Decode response
$json = (object)array_merge(
json_decode( $response_1, true )['attributesMap'],
json_decode( $response_2, true )
);
// Exit if error
if ( json_last_error() ) {
http_response_code( 500 ) && exit;
}
return $json;
}
function read_last_line() {
// check for empty file
if(filesize($this->stats_file)===0)
return " ";
$fp = fopen($this->stats_file, "r");
$pos = -2;
$t = " ";
if($fp)
{
while ($t != "\n")
{
if(fseek($fp, $pos, SEEK_END)!=0)
{
break;
}
$t = fgetc($fp);
$pos = $pos - 1;
}
$t = fgets($fp);
fclose($fp);
}
return $t;
}
function send_response_json() {
// Set JSON vars
$attributes = $this->json;
$chargingPower='-.-';
$consumption='-.-';
$consumptionPower=0.0;
$act_stateOfCharge=0.0;
$last_stateOfCharge=0.0;
$elapsedtime=0;
$socchange=0.0;
$mileagechange=0.0;
$updateTime = $attributes->updateTime_converted;
$updateTime_timestamp = $attributes->updateTime_converted_timestamp;
$electricRange = intval( $attributes->beRemainingRangeElectricKm );
$chargingLevel = intval( $attributes->chargingLevelHv );
$chargingActive = intval( $attributes->chargingSystemStatus === 'CHARGINGACTIVE' );
// remaining charging time is only included while charging so check first to avoid PHP notice
if(isset($attributes->chargingTimeRemaining))
$chargingTimeRemaining = intval( $attributes->chargingTimeRemaining );
else
$chargingTimeRemaining=0;
//$chargingClock = strftime("%a %H:%M",time()+mktime(0,$chargingTimeRemaining,0,1,1,1970));
$chargingClock = ($chargingTimeRemaining ? strftime("%a %H:%M",time()+mktime(1,$chargingTimeRemaining,0,1,1,1970)):'--:--' );
$chargingTimeRemaining = ( $chargingTimeRemaining ? ( date( 'H:i', mktime( 0, $chargingTimeRemaining ) )) : '--:--' );
$stateOfCharge = number_format( round( $attributes->soc, 2 ), 2, ',', '.');
$act_stateOfCharge=$stateOfCharge;
$stateOfChargeMax = number_format( round( $attributes->socMax, 2 ), 2, ',', '.');
$doorLockState = intval( $attributes->door_lock_state === 'SECURED' );
if($doorLockState == '1')
$doorLockState='CLOSED';
else
$doorLockState='OPEN';
$mileage = intval( $attributes->mileage );
$actline = $updateTime.";\t".$updateTime_timestamp.";\t".$electricRange.";\t\t".$chargingLevel.";\t\t\t\t".$stateOfCharge.";\t".$stateOfChargeMax.";\t".$chargingTimeRemaining.";\t".$mileage;
$lastline = $this->read_last_line();
if($lastline!=" " && $lastline!='')
{
$lastpower=0.0;
// extract the last and actual values
list($last_updatetime,$last_updatetime_timestamp,$last_electricRange,$last_chargingLevel,$last_stateOfCharge,$last_stateOfChargeMax,$last_chargingTimeRemaining,$last_mileage,$last_chargingpower,$last_consumption) = str_getcsv($lastline, ';');
// only to be symmetric to last line
list($act_updatetime, $act_updatetime_timestamp, $act_electricRange, $act_chargingLevel, $act_stateOfCharge, $act_stateOfChargeMax, $act_chargingTimeRemaining,$act_mileage) = str_getcsv($actline, ';');
// from german to english number format
$last_stateOfCharge=str_replace(',','.',$last_stateOfCharge);
$act_stateOfCharge=str_replace(',','.',$act_stateOfCharge);
// calc soc change
$socchange=$act_stateOfCharge-$last_stateOfCharge;
$elapsedtime=$act_updatetime_timestamp-$last_updatetime_timestamp;
$mileagechange=$act_mileage-$last_mileage;
if($elapsedtime!=0)
{
// convert to hours
$elapsedtime=($elapsedtime/1000)/3600;
// caculate charging
$chargingPower=$socchange/$elapsedtime;
// cut off after x digits
if($chargingPower>10)
$chargingPower=round($chargingPower,1);
else
$chargingPower=round($chargingPower,2);
if($chargingPower<=0)
if($last_chargingpower>0)
$chargingPower=$last_chargingpower;
else
$chargingPower='-.--';
// calculate consumption
if($mileagechange>0)
{
$consumption=(($socchange*(-1))/$mileagechange)*100;
if($consumption>10)
$consumption=round($consumption,1);
else
$consumption=round($consumption,2);
if($consumption<=0)
{
if($last_consumption>0)
$consumption=$last_consumption;
else
$consumption='-.--';
}
}
else
{
if($socchange<0)
{
// eg. while heating without driving
$consumption=99.9;
}
}
$actline = $actline.";\t".$chargingPower.";\t".$consumption."\r\n";
}
else
{
if($last_consumption>0)
$consumption=$last_consumption;
if($last_chargingpower>0)
$chargingPower=$last_chargingpower;
// should not be needed as if elapsedtime ==0 SOC should also be unchanged and not new line will be written to stats file
$actline = $actline.";\t".$chargingPower.";\t".$consumption."\r\n";
}
}
else
$actline = $actline.";\t--.--;\t--.--"."\r\n";
//simple debug output
//$doorLockState='>'.$lastline.'<';
// log changes (only if SOC has really be changed)
if($lastline!=$actline && $act_stateOfCharge!=$last_stateOfCharge && $mileage!=0)
{
$filehandle = fopen($this->stats_file,'a');
if($filehandle)
{
fputs($filehandle,$actline);
fclose($filehandle);
}
}
// Send Header
header('Access-Control-Allow-Origin: https://' . $_SERVER['SERVER_NAME'] );
header('Content-Type: application/json; charset=utf-8');
// Send JSON
die(
json_encode(
array(
'updateTime' => $updateTime,
'electricRange' => $electricRange,
'chargingLevel' => $chargingLevel,
'chargingActive' => $chargingActive,
'chargingTimeRemaining' => $chargingTimeRemaining,
'stateOfCharge' => $stateOfCharge,
'stateOfChargeMax' => $stateOfChargeMax,
'doorLockState' => $doorLockState,
'mileage' => $mileage,
'chargingClock' => $chargingClock,
'chargingPower' => $chargingPower,
'consumption' => $consumption
)
)
);
}
}
new Battery_API();
Code: Alles auswählen
<!DOCTYPE HTML>
<html manifest="battery.appcache">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Battery Status">
<link rel="preload" href="./fonts/advent-pro-100-reduced.woff2?2017-01-21-v3" as="font" type="font/woff2" crossorigin>
<link rel="apple-touch-icon" href="./img/apple-touch-icon.png?2017-01-21-v3" type="image/png">
<link rel="icon" href="img/favicon.ico?2017-01-21-v3" type="image/x-icon">
<title>i3 Status</title>
<style>
* {
border: 0;
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--color-white: rgb(254, 254, 254);
--color-white-light: rgba(254, 254, 254, .6);
--color-green: #509000;
--color-blue: #1c69d4;
--color-blue-light: #C7EEFF;
--color-red: #F90000;
--color-orange: #DC9012;
--color-purple: #f0f;
--color-cyan: #0ff;
--color-main-border: #222a32;
--color-page-gradient-from: #485563;
--color-page-gradient-to: #29323c;
}
@font-face {
font-family: 'Advent Pro';
font-style: normal;
font-weight: 100;
src: local('Advent Pro Thin'), local('AdventPro-Thin'), url('./fonts/advent-pro-100-reduced.woff2?2017-01-21-v3') format('woff2');
}
html {
height: 100%;
background: linear-gradient( to bottom right, var(--color-page-gradient-from), var(--color-page-gradient-to) );
color: var(--color-white);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
text-transform: uppercase;
text-align: center;
-webkit-font-smoothing: antialiased;
}
h2 {
font-size: 22px;
font-weight: 200;
line-height: 24px;
letter-spacing: 1px;
}
h4 {
color: var(--color-white-light);
font-size: 14px;
font-weight: 200;
line-height: 16px;
letter-spacing: 1px;
}
units {
font-size: 14px;
font-weight: 200;
line-height: 16px;
letter-spacing: 1px;
}
section {
margin-bottom: 20px;
flex-basis: 50%;
max-width: 50%;
}
header {
top: 0;
width: 100%;
padding: 45px 0 4px;
position: absolute;
}
footer {
width: 100%;
height: 30%;
bottom: 0;
position: absolute;
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: center;
}
header,
footer {
opacity: 1;
transition: opacity .25s;
}
header:empty,
footer:empty {
opacity: 0;
}
main {
top: 40%;
left: 50%;
width: 200px;
height: 200px;
position: absolute;
border: 12px solid var(--color-main-border);
border-radius: 100%;
overflow: hidden;
transform: translate(-50%, -50%);
box-shadow: 0 0 0 transparent;
transition: all 1.6s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.loading main {
display: flex;
align-items: center;
justify-content: center;
animation: pulse 1.7s infinite;
}
.loading main::after {
display: block;
content: 'Updating';
color: var(--color-white);
font-size: 22px;
font-weight: 200;
line-height: 24px;
letter-spacing: 1px;
}
.percent {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: absolute;
z-index: 3;
display: flex;
align-items: center;
justify-content: center;
}
.percent::before {
content: attr(data-percent);
display: block;
font-family: 'Advent Pro';
font-size: 80px;
line-height: 80px;
font-weight: 100;
text-rendering: optimizeLegibility;
}
.percent::after {
top: 58px;
right: 20px;
position: absolute;
content: '%';
display: block;
font-size: 26px;
line-height: 26px;
font-weight: 100;
opacity: .6;
}
.water {
bottom: 0;
left: 0;
width: 100%;
position: absolute;
z-index: 2;
background: var(--color-green);
}
.wave {
width: 200%;
bottom: 100%;
display: none;
position: absolute;
}
.wave-back {
right: 0;
fill: var(--color-blue-light);
animation: wave-back 1.4s infinite linear;
}
.wave-front {
left: 0;
margin-bottom: -1px;
fill: var(--color-blue);
animation: wave-front .7s infinite linear;
}
@keyframes wave-front {
100% {
transform: translate(-50%, 0);
}
}
@keyframes wave-back {
100% {
transform: translate(50%, 0);
}
}
@keyframes pulse {
50% {
box-shadow: -5px 0 20px var(--color-purple), 5px 0 20px var(--color-cyan);
}
}
[data-percent^="0"] + .water,
[data-percent^="1"] + .water,
[data-percent^="2"] + .water {
background: var(--color-red);
}
[data-percent^="3"] + .water,
[data-percent^="4"] + .water {
background: var(--color-orange);
}
[data-percent="100"] + .water {
background: var(--color-green);
}
[data-charging="1"].water {
background: var(--color-blue);
}
[data-charging="1"] .wave {
display: block;
}
</style>
<script id="header-tmpl" type="text/x-dot-template">
<h2>
Mein i3 Status
</h2>
<h4>
{{=it.updateTime}}
</h4>
</script>
<script id="main-tmpl" type="text/x-dot-template">
<span class="percent" data-percent="{{=it.chargingLevel}}"></span>
<div class="water" data-charging="{{=it.chargingActive}}" style="height:{{=it.chargingLevel}}%">
<svg viewBox="0 0 560 20" class="wave wave-back">
<use xlink:href="#wave"></use>
</svg>
<svg viewBox="0 0 560 20" class="wave wave-front">
<use xlink:href="#wave"></use>
</svg>
</div>
</script>
<script id="footer-tmpl" type="text/x-dot-template">
<section>
<h4>
RANGE/AV CONS.
</h4>
<h2>
{{=it.electricRange}} <units>KM</units>/{{=it.consumption}} <units>KWH/...</units>
</h2>
</section>
<section>
<h4>
SOC ACT/SOC MAX
</h4>
<h2>
{{=it.stateOfCharge}}/{{=it.stateOfChargeMax}} <units>KWH</units>
</h2>
</section>
<section>
<h4>
CHARGED IN/AV POWER
</h4>
<h2>
{{=it.chargingTimeRemaining}} <units>H</units>/{{=it.chargingPower}} <units>KW</units>
</h2>
</section>
<section>
<h4>
FULLY CHARGED AT
</h4>
<h2>
{{=it.chargingClock}} <units>H</units>
</h2>
</section>
<section>
<h4>
CAR DOOR STATUS
</h4>
<h2>
{{=it.doorLockState}}
</h2>
</section>
<section>
<h4>
MILEAGE
</h4>
<h2>
{{=it.mileage}} <units>KM</units>
</h2>
</section>
</script>
</head>
<body class="loading">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" hidden>
<symbol id="wave">
<path d="M420,20c21.5-0.4,38.8-2.5,51.1-4.5c13.4-2.2,26.5-5.2,27.3-5.4C514,6.5,518,4.7,528.5,2.7c7.1-1.3,17.9-2.8,31.5-2.7c0,0,0,0,0,0v20H420z"></path>
<path d="M420,20c-21.5-0.4-38.8-2.5-51.1-4.5c-13.4-2.2-26.5-5.2-27.3-5.4C326,6.5,322,4.7,311.5,2.7C304.3,1.4,293.6-0.1,280,0c0,0,0,0,0,0v20H420z"></path>
<path d="M140,20c21.5-0.4,38.8-2.5,51.1-4.5c13.4-2.2,26.5-5.2,27.3-5.4C234,6.5,238,4.7,248.5,2.7c7.1-1.3,17.9-2.8,31.5-2.7c0,0,0,0,0,0v20H140z"></path>
<path d="M140,20c-21.5-0.4-38.8-2.5-51.1-4.5c-13.4-2.2-26.5-5.2-27.3-5.4C46,6.5,42,4.7,31.5,2.7C24.3,1.4,13.6-0.1,0,0c0,0,0,0,0,0l0,20H140z"></path>
</symbol>
</svg>
<header id="header"></header>
<main id="main"></main>
<footer id="footer"></footer>
<script src="./scripts/doT.min.js"></script>
<script>
const req = new XMLHttpRequest();
req.addEventListener( 'load', function() {
if ( this.status !== 200 ) {
return alert( 'Request failed: ' + this.status );
}
const json = JSON.parse( this.responseText );
[ 'header', 'main', 'footer' ].forEach( id => {
document.getElementById( id ).innerHTML = doT.template( document.getElementById( `${id}-tmpl` ).text )( json );
} );
document.getElementsByTagName( 'body' )[0].classList.remove( 'loading' );
} );
req.open( 'GET', './api/' );
req.send();
</script>
<script>
window.addEventListener( 'load', function( e ) {
window.applicationCache.addEventListener( 'updateready', function( e ) {
if ( window.applicationCache.status == window.applicationCache.UPDATEREADY ) {
window.applicationCache.swapCache();
window.location.reload();
}
}, false );
}, false );
</script>
</body>
</html>
Am webspace solls nicht liegen, hab da was bei hosteurope.endurance hat geschrieben: Das wäre ja zwecklos.
Als erstes braucht Du einen webspace mit PHP Umgebung. Gibt es für <10€/monat. Wenn Du daheim einen Rechner immer laufen hast kannst Du auch dort "hosten" - musst Dich dann aber mit nginx, Apache oder ISS .... beschäftigen. Wenn der Serverteil bei Dir läuft helfe ich auch gerne weiter.