diff --git a/README.md b/README.md index b76bc94950..e082142a96 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ The documentation is available at [docs.teslamate.org](https://docs.teslamate.or - [See when your car was online or asleep](https://docs.teslamate.org/docs/screenshots#states) - [Lifetime driving map](https://docs.teslamate.org/docs/screenshots/#lifetime-driving-map) - [Visited addresses](https://docs.teslamate.org/docs/screenshots/#visited-addresses) +- [Battery Health](https://docs.teslamate.org/docs/screenshots/#battery-health) **General** diff --git a/grafana/dashboards/battery-health.json b/grafana/dashboards/battery-health.json index 44b8fa14da..17eedf0107 100644 --- a/grafana/dashboards/battery-health.json +++ b/grafana/dashboards/battery-health.json @@ -1,5 +1,5 @@ { - "__elements": [], + "__elements": {}, "__requires": [ { "type": "panel", @@ -17,7 +17,7 @@ "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "8.5.26" + "version": "10.1.2" }, { "type": "panel", @@ -36,6 +36,12 @@ "id": "stat", "name": "Stat", "version": "" + }, + { + "type": "panel", + "id": "xychart", + "name": "XY Chart", + "version": "" } ], "annotations": { @@ -141,7 +147,7 @@ }, "textMode": "value_and_name" }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -286,7 +292,7 @@ }, "textMode": "value_and_name" }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -369,7 +375,7 @@ }, "textMode": "value_and_name" }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -503,7 +509,7 @@ }, "textMode": "value_and_name" }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -631,7 +637,7 @@ "showThresholdLabels": false, "showThresholdMarkers": true }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -724,9 +730,10 @@ "fields": "", "values": false }, - "showUnfilled": true + "showUnfilled": true, + "valueMode": "color" }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -809,7 +816,7 @@ }, "textMode": "value_and_name" }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -917,8 +924,9 @@ "value" ], "legend": { - "displayMode": "hidden", - "placement": "bottom" + "displayMode": "list", + "placement": "bottom", + "showLegend": false }, "pieType": "pie", "reduceOptions": { @@ -1030,9 +1038,10 @@ "values": false }, "showUnfilled": true, - "text": {} + "text": {}, + "valueMode": "color" }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -1147,9 +1156,10 @@ "values": false }, "showUnfilled": true, - "text": {} + "text": {}, + "valueMode": "color" }, - "pluginVersion": "8.5.26", + "pluginVersion": "10.1.2", "targets": [ { "datasource": { @@ -1206,14 +1216,16 @@ "type": "bargauge" }, { - "datasource": "TeslaMate", + "datasource": { + "type": "postgres", + "uid": "TeslaMate" + }, "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" + "mode": "continuous-RdYlGr" }, "custom": { - "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1260,12 +1272,6 @@ { "id": "custom.show", "value": "lines" - }, - { - "id": "custom.lineStyle", - "value": { - "fill": "solid" - } } ] } @@ -1288,13 +1294,20 @@ }, "series": [ { + "name": "Odometer", "pointColor": { "field": "kWh" }, + "pointSize": { + "fixed": 5, + "max": 100, + "min": 1 + }, "x": "odometer", "y": "kWh" }, { + "name": "Median Odometer", "pointColor": { "fixed": "dark-red" }, @@ -1304,7 +1317,7 @@ ], "seriesMapping": "manual", "tooltip": { - "mode": "multi", + "mode": "single", "sort": "none" } }, @@ -1312,13 +1325,17 @@ "targets": [ { "alias": "", - "datasource": "TeslaMate", + "datasource": { + "type": "postgres", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", "group": [], "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT convert_km(AVG(p.odometer)::numeric,'$length_unit') AS odometer, \r\n\tAVG(c.rated_battery_range_km) * ('$aux'::json -> 'RatedEfficiency')::text::float / AVG(c.usable_battery_level) AS \"kWh\",\r\n\tMAX(cp.id) AS id,\r\n\tto_char(cp.end_date, 'YYYY-MM-dd') AS Title\r\n\tFROM charging_processes cp\r\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\tFROM charges GROUP BY charging_process_id) AS last_charges\tON cp.id = last_charges.charging_process_id\r\n\t\tINNER JOIN charges c\r\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\r\n\t\tINNER JOIN positions p ON p.id = cp.position_id\r\n\tWHERE cp.car_id = $car_id\r\n\t\tAND cp.end_date IS NOT NULL\r\n\t\tAND cp.end_rated_range_km > cp.start_rated_range_km\r\n\tGROUP BY 4", + "rawSql": "SELECT convert_km(AVG(p.odometer)::numeric,'$length_unit') AS odometer, \r\n\tAVG(c.rated_battery_range_km * ('$aux'::json -> 'RatedEfficiency')::text::float / c.usable_battery_level) AS \"kWh\",\r\n\tMAX(cp.id) AS id,\r\n\tto_char(cp.end_date, 'YYYY-MM-dd') AS Title\r\n\tFROM charging_processes cp\r\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\tFROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS last_charges\tON cp.id = last_charges.charging_process_id\r\n\t\tINNER JOIN charges c\r\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\r\n\t\tINNER JOIN positions p ON p.id = cp.position_id\r\n\tWHERE cp.car_id = $car_id\r\n\t\tAND cp.end_date IS NOT NULL\r\n\t\tAND cp.charge_energy_added >= ('$aux'::json -> 'RatedEfficiency')::text::float\r\n\tGROUP BY 4", "refId": "Projected Range", "select": [ [ @@ -1330,6 +1347,23 @@ } ] ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, "timeColumn": "time", "where": [ { @@ -1341,13 +1375,17 @@ }, { "alias": "", - "datasource": "TeslaMate", + "datasource": { + "type": "postgres", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", "group": [], "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT \n ROUND(MIN(convert_km(p.odometer::numeric,'$length_unit')),0) AS \"M-Odometer\",\n\tROUND(PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY c.rated_battery_range_km * ('$aux'::json -> 'RatedEfficiency')::text::float / c.usable_battery_level)::numeric,1) AS \"M-kWh\",\n\tto_char(cp.end_date, 'YYYYMM') || CASE WHEN to_char(cp.end_date, 'DD')::int <= 15 THEN '1' ELSE '2' END AS Title\n\tFROM charging_processes cp\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\tFROM charges GROUP BY charging_process_id) AS last_charges\tON cp.id = last_charges.charging_process_id\n\t\tINNER JOIN charges c\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\n\t\tINNER JOIN positions p ON p.id = cp.position_id\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.end_rated_range_km > cp.start_rated_range_km\n\tGROUP BY 3", + "rawSql": "SELECT \n ROUND(MIN(convert_km(p.odometer::numeric,'$length_unit')),0) AS \"M-Odometer\",\n\tROUND(PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY c.rated_battery_range_km * ('$aux'::json -> 'RatedEfficiency')::text::float / c.usable_battery_level)::numeric,1) AS \"M-kWh\",\n\tto_char(cp.end_date, 'YYYYMM') || CASE WHEN to_char(cp.end_date, 'DD')::int <= 15 THEN '1' ELSE '2' END AS Title\n\tFROM charging_processes cp\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\tFROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS last_charges\tON cp.id = last_charges.charging_process_id\n\t\tINNER JOIN charges c\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\n\t\tINNER JOIN positions p ON p.id = cp.position_id\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.charge_energy_added >= ('$aux'::json -> 'RatedEfficiency')::text::float\n\tGROUP BY 3", "refId": "Median", "select": [ [ @@ -1359,6 +1397,23 @@ } ] ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, "table": "drives", "timeColumn": "start_date", "timeColumnType": "timestamp", @@ -1376,7 +1431,7 @@ } ], "refresh": "", - "schemaVersion": 36, + "schemaVersion": 38, "style": "dark", "tags": [ "tesla" @@ -1458,13 +1513,13 @@ "type": "postgres", "uid": "TeslaMate" }, - "definition": "-- CONCATENATED JOIN QUERIES TO IMPROVE PERFORMANCE\n-- The following query is the result of many tests and hours of work. This panel is for your own personal use. \n-- If you think you can improve it and contribute, please create a pull request and do not take it to your repository, \n-- much less upload it to another repository as if the original idea were yours, nor do you share it on social media\n-- without mentioning the author. Respect the ingenuity and work of others. Cheers!\n-- 2023-07-21\n-- By @jheredianet - Twitter: @juanheredia", + "definition": "-- CONCATENATED JOIN QUERIES TO IMPROVE PERFORMANCE", "hide": 2, "includeAll": false, "multi": false, "name": "aux", "options": [], - "query": "WITH\naux\tAS\n(\n\tSELECT cp.charge_energy_added,\n\t\tcp.car_id, (SELECT efficiency FROM cars WHERE id = $car_id) * 100.0 AS rated_efficiency,\n\t\t(cp.end_rated_range_km - cp.start_rated_range_km) AS added_range_km\n\tFROM charging_processes cp\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\n\t\tFROM charges\n\t\tGROUP BY charging_process_id) AS last_charges\n\t\tON cp.id = last_charges.charging_process_id\n\t\tINNER JOIN charges c\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.end_rated_range_km > cp.start_rated_range_km\n\tORDER BY cp.end_date DESC \n\tLIMIT 1\n), \nCurrentCapacity\t AS\n(\n\tSELECT AVG(Capacity) AS CurrentCapacity FROM\n (SELECT (100.0 * cp.charge_energy_added) / (GREATEST(1,MAX(usable_battery_level) - MIN(usable_battery_level))) AS Capacity\t\n FROM charging_processes cp\n\t INNER JOIN charges c\tON cp.id = c.charging_process_id\n INNER JOIN aux ON cp.car_id = aux.car_id\n\t WHERE cp.car_id = $car_id AND cp.charge_energy_added >= aux.rated_efficiency \n GROUP BY cp.charge_energy_added, cp.end_date\n ORDER BY cp.end_date DESC \n LIMIT 5\n ) AS lastEstimatedCapacity\n), \nMaxCapacity AS\n(\n SELECT AVG(c.rated_battery_range_km * aux.rated_efficiency / c.usable_battery_level) AS MaxCapacity, cp.id\n FROM charging_processes cp\n\tJOIN (SELECT charging_process_id, MAX(date) as date FROM charges \n WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS last_charges\t\n ON cp.id = last_charges.charging_process_id\n\tINNER JOIN charges c ON c.charging_process_id = cp.id AND c.date = last_charges.date\n\tINNER JOIN positions p ON p.id = cp.position_id\n INNER JOIN aux ON cp.car_id = aux.car_id\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.charge_energy_added >= aux.rated_efficiency \n\tGROUP BY cp.id \n ORDER BY 1 DESC \n LIMIT 1\n), \nCurrentRange AS\n(\n SELECT\n\t\tfloor(extract(epoch from date)/86400)*86400 AS timecurrent,\n\t\tsum(rated_battery_range_km) / sum(usable_battery_level) * 100 AS rated_range_km\n\tFROM (\n\t\tSELECT battery_level, usable_battery_level, date, rated_battery_range_km from charges c \n\t\tJOIN charging_processes p ON p.id = c.charging_process_id \n\t\tWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL) AS data\n\tGROUP BY 1\n\tORDER BY 1 DESC\n\tLIMIT 1\n), \nMaxRange AS\n(\n SELECT\n\t\tfloor(extract(epoch from date)/86400)*86400 AS time,\n\t\tsum(rated_battery_range_km) / sum(usable_battery_level) * 100 AS max_rated_range_km\n\tFROM (\n\t\tSELECT battery_level, usable_battery_level, date, rated_battery_range_km from charges c \n\t\tJOIN charging_processes p ON p.id = c.charging_process_id \n\t\tWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL) AS data\n\tGROUP BY 1\n\tORDER BY 2 DESC\n\tLIMIT 1\n) \nSELECT CONCAT('{\"LastChargekWhAdded\": ', aux.charge_energy_added, \n ', \"LastMileageAdded\" : ', convert_km(aux.added_range_km,'$length_unit'),\n ', \"MaxRange\": ', convert_km(MaxRange.max_rated_range_km,'$length_unit'),\n ', \"CurrentRange\": ',convert_km(CurrentRange.rated_range_km,'$length_unit'),\n ', \"MaxCapacity\": ', MaxCapacity.MaxCapacity,\n ', \"CurrentCapacity\": ', CASE WHEN CurrentCapacity.CurrentCapacity IS NULL THEN 1 ELSE CurrentCapacity.CurrentCapacity END,\n ', \"RatedEfficiency\": ',aux.rated_efficiency, '}') \nFROM MaxRange, CurrentRange, Aux, MaxCapacity, CurrentCapacity;", + "query": "WITH DerivatedEfficiency AS (\n SELECT (charge_energy_added / NULLIF(end_rated_range_km - start_rated_range_km, 0))::numeric * 100.0 as \"efficiency\",\n count(*) as count FROM charging_processes \n WHERE car_id = $car_id \n AND duration_min > 10 \n AND end_battery_level <= 95 \n AND start_rated_range_km IS NOT NULL \n AND end_rated_range_km IS NOT NULL \n AND charge_energy_added > 0\n GROUP BY 1\n ORDER BY 2 DESC\n LIMIT 1\n),\naux\tAS\n(\n\tSELECT cp.charge_energy_added,\n\t\tcp.car_id, (SELECT efficiency FROM DerivatedEfficiency) AS rated_efficiency,\n\t\t(cp.end_rated_range_km - cp.start_rated_range_km) AS added_range_km\n\tFROM charging_processes cp\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\n\t\tFROM charges\n\t\tGROUP BY charging_process_id) AS last_charges\n\t\tON cp.id = last_charges.charging_process_id\n\t\tINNER JOIN charges c\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.end_rated_range_km > cp.start_rated_range_km\n\tORDER BY cp.end_date DESC \n\tLIMIT 1\n), \nCurrentCapacity\t AS\n(\n\tSELECT AVG(Capacity) AS CurrentCapacity FROM\n (SELECT (100.0 * cp.charge_energy_added) / (GREATEST(1,MAX(usable_battery_level) - MIN(usable_battery_level))) AS Capacity\t\n FROM charging_processes cp\n\t INNER JOIN charges c\tON cp.id = c.charging_process_id\n INNER JOIN aux ON cp.car_id = aux.car_id\n\t WHERE cp.car_id = $car_id AND cp.charge_energy_added >= aux.rated_efficiency \n GROUP BY cp.charge_energy_added, cp.end_date\n ORDER BY cp.end_date DESC \n LIMIT 5\n ) AS lastEstimatedCapacity\n), \nMaxCapacity AS\n(\n SELECT AVG(c.rated_battery_range_km * aux.rated_efficiency / c.usable_battery_level) AS MaxCapacity, cp.id\n FROM charging_processes cp\n\tJOIN (SELECT charging_process_id, MAX(date) as date FROM charges \n WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS last_charges\t\n ON cp.id = last_charges.charging_process_id\n\tINNER JOIN charges c ON c.charging_process_id = cp.id AND c.date = last_charges.date\n\tINNER JOIN positions p ON p.id = cp.position_id\n INNER JOIN aux ON cp.car_id = aux.car_id\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.charge_energy_added >= aux.rated_efficiency \n\tGROUP BY cp.id \n ORDER BY 1 DESC \n LIMIT 1\n), \nCurrentRange AS\n(\n SELECT\n\t\tfloor(extract(epoch from date)/86400)*86400 AS timecurrent,\n\t\tCASE WHEN sum(usable_battery_level) = 0 THEN sum(rated_battery_range_km) * 100\n\t\t ELSE sum(rated_battery_range_km) / sum(usable_battery_level) * 100\n END AS rated_range_km\n\tFROM (\n\t\tSELECT battery_level, usable_battery_level, date, rated_battery_range_km from charges c \n\t\tJOIN charging_processes p ON p.id = c.charging_process_id \n\t\tWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL) AS data\n\tGROUP BY 1\n\tORDER BY 1 DESC\n\tLIMIT 1\n), \nMaxRange AS\n(\n SELECT\n\t\tfloor(extract(epoch from date)/86400)*86400 AS time,\n\t CASE WHEN sum(usable_battery_level) = 0 THEN sum(rated_battery_range_km) * 100\n\t\t ELSE sum(rated_battery_range_km) / sum(usable_battery_level) * 100\n\t END AS max_rated_range_km\n FROM (\n\t\tSELECT battery_level, usable_battery_level, date, rated_battery_range_km from charges c \n\t\tJOIN charging_processes p ON p.id = c.charging_process_id \n\t\tWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL) AS data\n\tGROUP BY 1\n\tORDER BY 2 DESC\n\tLIMIT 1\n) \nSELECT CONCAT('{\"LastChargekWhAdded\": ', aux.charge_energy_added, \n ', \"LastMileageAdded\" : ', convert_km(aux.added_range_km,'$length_unit'),\n ', \"MaxRange\": ', convert_km(MaxRange.max_rated_range_km,'$length_unit'),\n ', \"CurrentRange\": ',convert_km(CurrentRange.rated_range_km,'$length_unit'),\n ', \"MaxCapacity\": ', MaxCapacity.MaxCapacity,\n ', \"CurrentCapacity\": ', CASE WHEN CurrentCapacity.CurrentCapacity IS NULL THEN 1 ELSE CurrentCapacity.CurrentCapacity END,\n ', \"RatedEfficiency\": ',aux.rated_efficiency, '}') \nFROM MaxRange, CurrentRange, Aux, MaxCapacity, CurrentCapacity;", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1523,6 +1578,6 @@ "timezone": "browser", "title": "Battery Health", "uid": "jchmRiqUfXgTM", - "version": 10, + "version": 12, "weekStart": "" -} +} \ No newline at end of file diff --git a/website/docs/screenshots.mdx b/website/docs/screenshots.mdx index 577e1c5025..f164c035fb 100644 --- a/website/docs/screenshots.mdx +++ b/website/docs/screenshots.mdx @@ -64,4 +64,8 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; ## Visited addresses -Visited addresses \ No newline at end of file +Visited addresses + +## Battery Health + +Visited addresses \ No newline at end of file diff --git a/website/static/screenshots/battery-health.png b/website/static/screenshots/battery-health.png new file mode 100644 index 0000000000..bae4aafd31 Binary files /dev/null and b/website/static/screenshots/battery-health.png differ