Hallo zusammen,
als stolzer PS2-Fahrer seit November und stiller Mitleser hier im Forum möchte ich mich an dieser Stelle kurz vorstellen. Mein Name ist Jonas und ich bin mit meinem PS2 SMLR MY24 in Magnesium im Raum Stuttgart unterwegs.
Ich bin großer Fan von @Ixam97’s fantastischer Car Stats Viewer App und finde – neben den ganzen Funktionen und Informationen innerhalb der App – vor allem die eingebaute API-Funktion extrem spannend.
Ich habe mich gefragt, wie ich die Daten, welche über die Webhook API gesendet werden, möglichst einfach und ohne großen Implementierungs- und Infrastruktur-Aufwand entgegen nehmen und speichern könnte.
Die banale Lösung: Google Sheets.
Ist kostenlos, in 5 min eingerichtet und läuft bei mir seit 2 Monaten stabil.
Das Ganze funktioniert mit Google Apps Script. Hier kann man ein kleines Script als öffentlich zugängliche Web App hinterlegen, welches ausgeführt wird, wenn Daten von CSV empfangen werden.
Die empfangenen Daten werden geparst und direkt Zeile für Zeile in das Google Sheet geschrieben.
Es werden automatisch vier Sheets für „Live Data“, „Driving Data“, „Charging Sessions“ und „Charging Points“, und jeweils Spalten für die einzelnen Werte wie „speed“, „stateOfCharge“, etc. angelegt. Zusätzlich werden zum einfacheren Debuggen der Daten zwei weitere Spalten erzeugt („dataPointReceived“ und „humanReadableDate“ bei „Live Data“).
Das Script ist aktuell noch sehr simpel und dient erstmal nur dazu, die Rohdaten von CSV zu speichern – als Datenbasis für spätere Auswertungen, Visualisierungen, etc.
Vielleicht interessant das den ein oder anderen?
So gehts …
-
Mit Google Konto anmelden und neues Google Sheet erstellen.
-
Im Menü:
Extensions
→Apps Script
-
Code einfügen:
function doPost(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var jsonData;
// Parse the JSON content
try {
jsonData = JSON.parse(e.postData.contents);
} catch (error) {
return ContentService.createTextOutput("Error parsing JSON");
}
Logger.log(jsonData);
// JSON keys
var liveDataKeys = [
'alt', 'ambientTemperature', 'apiVersion', 'appVersion', 'batteryLevel',
'chargePortConnected', 'ignitionState', 'lat', 'lon', 'power',
'selectedGear', 'speed', 'stateOfCharge', 'timestamp'
];
var drivingPointKeys = [
'alt', 'distance_delta', 'driving_point_epoch_time', 'energy_delta',
'lat', 'lon', 'point_marker_type', 'state_of_charge'
];
var chargingSessionKeys = [
'chargeTime', 'charged_energy', 'charged_soc', 'charging_session_id',
'end_epoch_time', 'lat', 'lon', 'outside_temp', 'start_epoch_time'
];
var chargingPointKeys = [
'charging_point_epoch_time', 'charging_session_id', 'energy_delta',
'point_marker_type', 'power', 'state_of_charge'
];
// Process and append data for each data type
processData('LiveData', [jsonData], liveDataKeys); // wrapped in array for uniformity
processData('DrivingPoints', jsonData.drivingPoints, drivingPointKeys);
processData('ChargingSessions', jsonData.chargingSessions, chargingSessionKeys);
var allChargingPoints = jsonData.chargingSessions.flatMap(session => session.chargingPoints || []);
processData('ChargingPoints', allChargingPoints, chargingPointKeys);
return ContentService.createTextOutput(JSON.stringify({"result": "success"}))
.setMimeType(ContentService.MimeType.JSON);
}
function processData(sheetName, dataArray, keys) {
if (!dataArray) return; // Skip if data is not present
// get the correct sheet, and create the sheet if not existing
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName) || SpreadsheetApp.getActiveSpreadsheet().insertSheet(sheetName);
// if sheet is empty, add headers
if (sheet.getLastRow() === 0) {
var headerRow = keys.slice();
if (sheetName === 'LiveData') {
headerRow.push("humanReadable");
}
headerRow.push("dataPointReceived");
sheet.appendRow(headerRow);
sheet.setFrozenRows(1);
sheet.getRange(1, 1, 1, keys.length).setFontWeight("bold");
}
// map incoming data to the headers, leave columns empty if data is missing
var rows = dataArray.map(function(item) {
var row = keys.map(function(key) {
return (item[key] !== null && item[key] !== undefined) ? item[key] : ''; // leave empty if data is missing
});
// add human-readable date and time
if (sheetName === 'LiveData' && item['timestamp']) {
row.push(convertTimestampToDate(item['timestamp']));
}
// add timestamp for "dataPointReceived"
row.push(new Date());
return row;
});
// append data to sheet
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows);
}
// Helper function to convert timestamp to human-readable date
function convertTimestampToDate(timestamp) {
if (!timestamp) return '';
var date = new Date(timestamp);
return Utilities.formatDate(date, Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss");
}
////////////////////////////////////////////////
// Testing
function test() {
var testData = {
postData: {
contents: JSON.stringify({
"drivingPoints": [
{
"alt": 2,
"distance_delta": 19.714678,
"driving_point_epoch_time": 1696070487231,
"energy_delta": 45.35667,
"lat": 32.827896,
"lon": 7.4446335,
"point_marker_type": 2,
"state_of_charge": 1.0
}
],
"chargingSessions": [
{
"chargeTime": 51608,
"charged_energy": 932.0753670833333,
"charged_soc": 2,
"chargingPoints": [
{
"charging_point_epoch_time": 1696071422457,
"charging_session_id": 1,
"energy_delta": 1,
"point_marker_type": 1,
"power": 1,
"state_of_charge": 1
},{
"charging_point_epoch_time": 1696071422457,
"charging_session_id": 1,
"energy_delta": 2,
"point_marker_type": 2,
"power": 2,
"state_of_charge": 2
}
],
"charging_session_id": 1,
"end_epoch_time": 1696071474074,
"lat": 32.827896,
"lon": 7.4446335,
"outside_temp": 2,
"start_epoch_time": 1696071422439
}
],
"alt":3,
"ambientTemperature":0,
"apiVersion":"2.1",
"appVersion":"0.25.2.0000",
"batteryLevel":15000,
"chargePortConnected":false,
"ignitionState":"On",
"lat":32.827896,
"lon":7.4446335,
"power":-10,
"selectedGear":"P",
"speed":0,
"stateOfCharge":1.0,
"timestamp":1696069418482
})
}
};
doPost(testData);
}
-
Links bei „Services“ auf das + Icon (
Add a service
) klicken, aus der ListeGoogle Sheets API
auswählen, und aufAdd
. -
Deploy
→ bei „Select type“ auf das Zahnrädchen-Icon klicken →Web App
.
„Execute as“: me
„Who has access“: Anyone
Dann aufDeploy
. -
Authorize access
→ Google Account auswählen → Dann aufAdvanced
und aufGo to project (unsafe)
klicken →Allow
-
Web App URL kopieren und im PS2 in den CSV Settings hinterlegen. Fertig.
Das Script kann bei Bedarf getestet werden, indem man oben neben Run
die Funktion test
auswählt und dann auf Run
klickt. Die deployte URL kann man mit einem beliebigen online tool (z.B. https://reqbin.com) testen.
Ich freue mich über Feedback, Ideen und Verbesserungsvorschläge.