diff --git a/Plugins.Modbus/Plugins.Modbus.csproj b/Plugins.Modbus/Plugins.Modbus.csproj
index 77ca746aa..beb2c49a5 100644
--- a/Plugins.Modbus/Plugins.Modbus.csproj
+++ b/Plugins.Modbus/Plugins.Modbus.csproj
@@ -9,13 +9,15 @@
-
-
-
-
+
+
+
+
+
-
-
+
+
+
diff --git a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj
index 11c8d1b6b..22aa752a3 100644
--- a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj
+++ b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj
@@ -9,11 +9,12 @@
-
-
-
-
-
+
+
+
+
+
+
diff --git a/Plugins.SolarEdge/Plugins.SolarEdge.csproj b/Plugins.SolarEdge/Plugins.SolarEdge.csproj
index f22cae188..4038113a4 100644
--- a/Plugins.SolarEdge/Plugins.SolarEdge.csproj
+++ b/Plugins.SolarEdge/Plugins.SolarEdge.csproj
@@ -9,12 +9,13 @@
-
+
-
-
+
+
-
+
+
diff --git a/Plugins.Solax/Plugins.Solax.csproj b/Plugins.Solax/Plugins.Solax.csproj
index 87f606ff9..c7200d080 100644
--- a/Plugins.Solax/Plugins.Solax.csproj
+++ b/Plugins.Solax/Plugins.Solax.csproj
@@ -8,12 +8,13 @@
-
-
-
-
+
+
+
+
-
+
+
diff --git a/README.md b/README.md
index b98cde8d8..69192cb45 100644
--- a/README.md
+++ b/README.md
@@ -7,13 +7,13 @@
[![edgeRelease](https://github.com/pkuehnel/TeslaSolarCharger/actions/workflows/edgeRelease.yml/badge.svg)](https://github.com/pkuehnel/TeslaSolarCharger/actions/workflows/edgeRelease.yml)
-TeslaSolarCharger is a service to set one or multiple Teslas' charging current using the datalogger **[TeslaMate](https://github.com/adriankumpf/teslamate)**.
+TeslaSolarCharger is a service to set one or multiple Teslas' charging current.
## Table of Contents
- [How to install](#how-to-install)
- [Docker compose](#docker-compose)
- - [Setting up TeslaMate including TeslaSolarCharger](#Setting-up-TeslaMate-including-TeslaSolarCharger)
+ - [Setting up TeslaSolarCharger](#Setting-up-TeslaSolarCharger)
- [docker-compose.yml content](#docker-composeyml-content)
- [First startup of the application](#first-startup-of-the-application)
- [Install and setup BLE API](#install-and-setup-ble-api)
@@ -34,7 +34,7 @@ You can either install the software in a Docker container or download the binari
The easiest way to use TeslaSolarCharger is with Docker.
-Depending on your system, you have to install Docker first. To do this on a RaspberryPi (should be the same on standard Linux systems), you need to execute the following commands in your Terminal window:
+Depending on your system, you have to install Docker first. To do this on a Raspberry Pi (should be the same on standard Linux systems), you need to execute the following commands in your Terminal window:
1. Install Docker
```
curl -sSL https://get.docker.com | sh
@@ -50,99 +50,24 @@ Depending on your system, you have to install Docker first. To do this on a Rasp
```
If any issues occur, try to identify them using [this more detailed instruction](https://www.simplilearn.com/tutorials/docker-tutorial/raspberry-pi-docker).
-If you are using a Windows host, install the Software from [here](https://docs.docker.com/desktop/install/windows-install/). Windows 11 is highly recommended. Select Linux Containers in the installation process.
+If you are using a Windows host, install the Software from [here](https://docs.docker.com/desktop/install/windows-install/). Windows 11 is highly recommended. Select Linux Containers in the installation process. Note: The SMA plugin is not supported on Docker on Windows.
-### Setting up TeslaMate including TeslaSolarCharger
+### Setting up TeslaSolarCharger
-To set up TeslaSolarCharger, you must create a `docker-compose.yml` (name is important!) file in a new directory. Note: During the setup, some additional data folders to persist data will be created in that folder, so it is recommended to use a new directory for your `docker-compose.yml`.
+To set up TeslaSolarCharger, you must create a `docker-compose.yml` (name is important!) file in a new directory.
#### docker-compose.yml content
-The needed content of your `docker-compose.yml` depends on your inverter. By default, TeslaSolarCharger can consume JSON/XML REST APIs. To get the software running on [SMA](https://www.sma.de/) or [SolarEdge](https://www.solaredge.com/), you can use specific plugins which create the needed JSON API. You can use the software with any ModbusTCP-capable inverter also.
+The required content of your `docker-compose.yml` depends on your inverter. By default, TeslaSolarCharger can consume JSON/XML REST APIs, MQTT messages or Modbus TCP. To get the software running on [SMA](https://www.sma.de/), [SolarEdge](https://www.solaredge.com/) or Solax based inverters, you can use specific plugins which create the required JSON API.
##### Content without using a plugin
-Below you can see the content for your `docker-compose.yml` if you are not using any plugin. Note: I recommend changing as few things as possible on this file as this will increase the effort to set everything up but feel free to change the database password, encryption key, and Timezone. Important: If you change the password or the encryption key, you need to use the same password and encryption key at all points in your `docker-compose.yml`
+Below you can see the content for your `docker-compose.yml` if you are not using any plugin. Note: I recommend changing as few things as possible on this file as this will increase the effort to set everything up but feel free to change the Timezone.
```yaml
version: '3.3'
services:
- teslamate:
- image: teslamate/teslamate:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- - TZ=Europe/Berlin ##You can change your Timezone here
- ports:
- - 4000:4000
- volumes:
- - ./import:/opt/app/import
- cap_drop:
- - all
-
- database:
- image: postgres:15
- restart: always
- environment:
- - POSTGRES_USER=teslamate
- - POSTGRES_PASSWORD=secret ##You can change your password here
- - POSTGRES_DB=teslamate
- volumes:
- - teslamate-db:/var/lib/postgresql/data
-
- grafana:
- image: teslamate/grafana:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- ports:
- - 3100:3000
- volumes:
- - teslamate-grafana-data:/var/lib/grafana
-
- mosquitto:
- image: eclipse-mosquitto:2
- restart: always
- command: mosquitto -c /mosquitto-no-auth.conf
- #ports:
- # - 1883:1883
- volumes:
- - mosquitto-conf:/mosquitto/config
- - mosquitto-data:/mosquitto/data
-
- teslamateapi:
- image: tobiasehlert/teslamateapi:latest
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
- restart: always
- depends_on:
- - database
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - TZ=Europe/Berlin ##You can change your Timezone here
- - ENABLE_COMMANDS=true
- - COMMANDS_ALL=true
- - API_TOKEN_DISABLE=true
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- #ports:
- # - 8080:8080
-
teslasolarcharger:
image: pkuehnel/teslasolarcharger:latest
container_name: teslasolarcharger
@@ -152,8 +77,6 @@ services:
max-file: "10"
max-size: "100m"
restart: always
- depends_on:
- - teslamateapi
environment:
# - Serilog__MinimumLevel__Default=Verbose #uncomment this line and recreate container with docker compose up -d for more detailed logs
- TZ=Europe/Berlin ##You can change your Timezone here
@@ -163,10 +86,6 @@ services:
- teslasolarcharger-configs:/app/configs
volumes:
- teslamate-db:
- teslamate-grafana-data:
- mosquitto-conf:
- mosquitto-data:
teslasolarcharger-configs:
```
@@ -178,7 +97,7 @@ volumes:
[![Docker pulls](https://img.shields.io/docker/pulls/pkuehnel/teslasolarchargersmaplugin)](https://hub.docker.com/r/pkuehnel/teslasolarchargersmaplugin)
The SMA plugin is used to access your EnergyMeter (or Sunny Home Manager 2.0) values.
-To use the plugin, add these lines to the bottom of your `docker-compose.yml`.
+To use the plugin, add these lines before the volumes section of your `docker-compose.yml`. Note: The SMA plugin is not supported on Docker on Windows.
```yaml
smaplugin:
@@ -202,83 +121,7 @@ You can also copy the complete content from here:
```yaml
version: '3.3'
-
-services:
- teslamate:
- image: teslamate/teslamate:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- - TZ=Europe/Berlin ##You can change your Timezone here
- ports:
- - 4000:4000
- volumes:
- - ./import:/opt/app/import
- cap_drop:
- - all
-
- database:
- image: postgres:15
- restart: always
- environment:
- - POSTGRES_USER=teslamate
- - POSTGRES_PASSWORD=secret ##You can change your password here
- - POSTGRES_DB=teslamate
- volumes:
- - teslamate-db:/var/lib/postgresql/data
-
- grafana:
- image: teslamate/grafana:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- ports:
- - 3100:3000
- volumes:
- - teslamate-grafana-data:/var/lib/grafana
-
- mosquitto:
- image: eclipse-mosquitto:2
- restart: always
- command: mosquitto -c /mosquitto-no-auth.conf
- #ports:
- # - 1883:1883
- volumes:
- - mosquitto-conf:/mosquitto/config
- - mosquitto-data:/mosquitto/data
-
- teslamateapi:
- image: tobiasehlert/teslamateapi:latest
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
- restart: always
- depends_on:
- - database
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - TZ=Europe/Berlin ##You can change your Timezone here
- - ENABLE_COMMANDS=true
- - COMMANDS_ALL=true
- - API_TOKEN_DISABLE=true
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- #ports:
- # - 8080:8080
-
+services:
teslasolarcharger:
image: pkuehnel/teslasolarcharger:latest
container_name: teslasolarcharger
@@ -288,8 +131,6 @@ services:
max-file: "10"
max-size: "100m"
restart: always
- depends_on:
- - teslamateapi
environment:
# - Serilog__MinimumLevel__Default=Verbose #uncomment this line and recreate container with docker compose up -d for more detailed logs
- TZ=Europe/Berlin ##You can change your Timezone here
@@ -312,10 +153,6 @@ services:
- ASPNETCORE_URLS=http://+:7192
volumes:
- teslamate-db:
- teslamate-grafana-data:
- mosquitto-conf:
- mosquitto-data:
teslasolarcharger-configs:
```
@@ -328,9 +165,9 @@ volumes:
[![Docker size](https://img.shields.io/docker/image-size/pkuehnel/teslasolarchargersolaredgeplugin/latest)](https://hub.docker.com/r/pkuehnel/teslasolarchargersolaredgeplugin)
[![Docker pulls](https://img.shields.io/docker/pulls/pkuehnel/teslasolarchargersolaredgeplugin)](https://hub.docker.com/r/pkuehnel/teslasolarchargersolaredgeplugin)
-The SolarEdge Plugin uses the cloud API, which is limited to 300 which is reset after 15 minutes. When the limit is reached the solaredge API does not gather any new values. This results in TSC displaying 0 grid and home battery power until 15 minutes are over.
+The SolarEdge Plugin uses the cloud API, which is limited to 300 which is reset after 15 minutes. When the limit is reached the SolarEdge API does not gather any new values. This results in TSC displaying 0 grid and home battery power until 15 minutes are over.
-To use the plugin, just add these lines to the bottom of your `docker-compose.yml`. Note: You have to change your site ID and your API key in the `CloudUrl` environment variable
+To use the plugin, just add these lines before the volumes section of your `docker-compose.yml`. Note: You have to change your site ID and your API key in the `CloudUrl` environment variable
```yaml
solaredgeplugin:
@@ -355,84 +192,7 @@ You can also copy the complete content from here:
```yaml
version: '3.3'
-
-services:
- teslamate:
- image: teslamate/teslamate:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- - TZ=Europe/Berlin ##You can change your Timezone here
- ports:
- - 4000:4000
- volumes:
- - ./import:/opt/app/import
- cap_drop:
- - all
-
- database:
- image: postgres:15
- restart: always
- environment:
- - POSTGRES_USER=teslamate
- - POSTGRES_PASSWORD=secret ##You can change your password here
- - POSTGRES_DB=teslamate
- volumes:
- - teslamate-db:/var/lib/postgresql/data
-
- grafana:
- image: teslamate/grafana:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- ports:
- - 3100:3000
- volumes:
- - teslamate-grafana-data:/var/lib/grafana
-
- mosquitto:
- image: eclipse-mosquitto:2
- restart: always
- command: mosquitto -c /mosquitto-no-auth.conf
- #ports:
- # - 1883:1883
- volumes:
- - mosquitto-conf:/mosquitto/config
- - mosquitto-data:/mosquitto/data
-
-
- teslamateapi:
- image: tobiasehlert/teslamateapi:latest
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
- restart: always
- depends_on:
- - database
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - TZ=Europe/Berlin ##You can change your Timezone here
- - ENABLE_COMMANDS=true
- - COMMANDS_ALL=true
- - API_TOKEN_DISABLE=true
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- #ports:
- # - 8080:8080
-
+services:
teslasolarcharger:
image: pkuehnel/teslasolarcharger:latest
container_name: teslasolarcharger
@@ -442,8 +202,6 @@ services:
max-file: "10"
max-size: "100m"
restart: always
- depends_on:
- - teslamateapi
environment:
# - Serilog__MinimumLevel__Default=Verbose #uncomment this line and recreate container with docker compose up -d for more detailed logs
- TZ=Europe/Berlin ##You can change your Timezone here
@@ -467,177 +225,19 @@ services:
- 7193:80
volumes:
- teslamate-db:
- teslamate-grafana-data:
- mosquitto-conf:
- mosquitto-data:
teslasolarcharger-configs:
```
-##### Content using Modbus plugin
-
-[![Docker version](https://img.shields.io/docker/v/pkuehnel/teslasolarchargermodbusplugin/latest)](https://hub.docker.com/r/pkuehnel/teslasolarchargermodbusplugin)
-[![Docker size](https://img.shields.io/docker/image-size/pkuehnel/teslasolarchargermodbusplugin/latest)](https://hub.docker.com/r/pkuehnel/teslasolarchargermodbusplugin)
-[![Docker pulls](https://img.shields.io/docker/pulls/pkuehnel/teslasolarchargermodbusplugin)](https://hub.docker.com/r/pkuehnel/teslasolarchargermodbusplugin)
-
-You can also use the Modbus plugin. This is a general plugin, so don't be surprised if it does not work as expected right after starting up. Feel free to share your configurations [here](https://github.com/pkuehnel/TeslaSolarCharger/discussions/174) so I can add templates for future users.
-
-To use the plugin, just add these lines to the bottom of your `docker-compose.yml`. Note: As some inverters struggle with too many requests within a specific time, you can change the `RequestBlockMilliseconds` environment variable.
-
-```yaml
- modbusplugin:
- image: pkuehnel/teslasolarchargermodbusplugin:latest
- container_name: teslasolarcharger_modbusplugin
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
- restart: always
- environment:
- - RequestBlockMilliseconds=0
- ports:
- - 7191:80
-
-```
-
-You can also copy the complete content from here:
-
- Complete file using Modbus plugin
-
-```yaml
-version: '3.3'
-
-services:
- teslamate:
- image: teslamate/teslamate:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- - TZ=Europe/Berlin ##You can change your Timezone here
- ports:
- - 4000:4000
- volumes:
- - ./import:/opt/app/import
- cap_drop:
- - all
-
- database:
- image: postgres:15
- restart: always
- environment:
- - POSTGRES_USER=teslamate
- - POSTGRES_PASSWORD=secret ##You can change your password here
- - POSTGRES_DB=teslamate
- volumes:
- - teslamate-db:/var/lib/postgresql/data
-
- grafana:
- image: teslamate/grafana:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- ports:
- - 3100:3000
- volumes:
- - teslamate-grafana-data:/var/lib/grafana
-
- mosquitto:
- image: eclipse-mosquitto:2
- restart: always
- command: mosquitto -c /mosquitto-no-auth.conf
- #ports:
- # - 1883:1883
- volumes:
- - mosquitto-conf:/mosquitto/config
- - mosquitto-data:/mosquitto/data
-
- teslamateapi:
- image: tobiasehlert/teslamateapi:latest
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
- restart: always
- depends_on:
- - database
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - TZ=Europe/Berlin ##You can change your Timezone here
- - ENABLE_COMMANDS=true
- - COMMANDS_ALL=true
- - API_TOKEN_DISABLE=true
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- #ports:
- # - 8080:8080
-
- teslasolarcharger:
- image: pkuehnel/teslasolarcharger:latest
- container_name: teslasolarcharger
- logging:
- driver: "json-file"
- options:
- max-file: "10"
- max-size: "100m"
- restart: always
- depends_on:
- - teslamateapi
- environment:
-# - Serilog__MinimumLevel__Default=Verbose #uncomment this line and recreate container with docker compose up -d for more detailed logs
- - TZ=Europe/Berlin ##You can change your Timezone here
- ports:
- - 7190:80
- volumes:
- - teslasolarcharger-configs:/app/configs
-
- modbusplugin:
- image: pkuehnel/teslasolarchargermodbusplugin:latest
- container_name: teslasolarcharger_modbusplugin
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
- restart: always
- environment:
- - RequestBlockMilliseconds=0
- ports:
- - 7191:80
-
-volumes:
- teslamate-db:
- teslamate-grafana-data:
- mosquitto-conf:
- mosquitto-data:
- teslasolarcharger-configs:
-```
-
-
-
-
##### Content using Solax plugin
[![Docker version](https://img.shields.io/docker/v/pkuehnel/teslasolarchargersolaxplugin/latest)](https://hub.docker.com/r/pkuehnel/teslasolarchargersolaxplugin)
[![Docker size](https://img.shields.io/docker/image-size/pkuehnel/teslasolarchargersolaxplugin/latest)](https://hub.docker.com/r/pkuehnel/teslasolarchargersolaxplugin)
[![Docker pulls](https://img.shields.io/docker/pulls/pkuehnel/teslasolarchargersolaxplugin)](https://hub.docker.com/r/pkuehnel/teslasolarchargersolaxplugin)
-To use the Solax plugin, just add these lines to the bottom of your `docker-compose.yml`. Note: You have to specify your solar system's IP address and password.
+To use the Solax plugin, just add these lines before the volumes section of your `docker-compose.yml`. Note: You have to specify your solar system's IP address and password.
```yaml
solaxplugin:
@@ -663,83 +263,7 @@ You can also copy the complete content from here:
```yaml
version: '3.3'
-
services:
- teslamate:
- image: teslamate/teslamate:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- - TZ=Europe/Berlin ##You can change your Timezone here
- ports:
- - 4000:4000
- volumes:
- - ./import:/opt/app/import
- cap_drop:
- - all
-
- database:
- image: postgres:15
- restart: always
- environment:
- - POSTGRES_USER=teslamate
- - POSTGRES_PASSWORD=secret ##You can change your password here
- - POSTGRES_DB=teslamate
- volumes:
- - teslamate-db:/var/lib/postgresql/data
-
- grafana:
- image: teslamate/grafana:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- ports:
- - 3100:3000
- volumes:
- - teslamate-grafana-data:/var/lib/grafana
-
- mosquitto:
- image: eclipse-mosquitto:2
- restart: always
- command: mosquitto -c /mosquitto-no-auth.conf
- #ports:
- # - 1883:1883
- volumes:
- - mosquitto-conf:/mosquitto/config
- - mosquitto-data:/mosquitto/data
-
- teslamateapi:
- image: tobiasehlert/teslamateapi:latest
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
- restart: always
- depends_on:
- - database
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - TZ=Europe/Berlin ##You can change your Timezone here
- - ENABLE_COMMANDS=true
- - COMMANDS_ALL=true
- - API_TOKEN_DISABLE=true
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- #ports:
- # - 8080:8080
-
teslasolarcharger:
image: pkuehnel/teslasolarcharger:latest
container_name: teslasolarcharger
@@ -749,8 +273,6 @@ services:
max-file: "10"
max-size: "100m"
restart: always
- depends_on:
- - teslamateapi
environment:
# - Serilog__MinimumLevel__Default=Verbose #uncomment this line and recreate container with docker compose up -d for more detailed logs
- TZ=Europe/Berlin ##You can change your Timezone here
@@ -775,10 +297,6 @@ services:
- 7194:80
volumes:
- teslamate-db:
- teslamate-grafana-data:
- mosquitto-conf:
- mosquitto-data:
teslasolarcharger-configs:
```
@@ -788,78 +306,66 @@ volumes:
1. Move to your above created directory with your `docker-compose.yml`.
1. Start all containers using the command `docker compose up -d`.
-1. Use a third-party app to create a new Tesla Token [[Android](https://play.google.com/store/apps/details?id=net.leveugle.teslatokens&hl=en_US&gl=US)] [[iOS](https://apps.apple.com/us/app/tesla-token/id1411393432)]
-1. Open your browser, go to `http://your-ip-address:4000` and paste your token and refresh token into the form.
-1. Go to `Geo-Fences` and add a Geo-Fence called `Home` at the location you want TeslaSolarCharger to be active.
1. Open `http://your-ip-address:7190`
1. Go to `Base Configuration` (if you are on a mobile device, it is behind the menu button).
+1. Generate a Fleet API token
+1. Again go to `Base Configuration`
+1. Use the map to select your home area. This is the area where TSC will start charging your car based on solar power.
+1. Click on `Save` at the bottom of the page.
+1. Go to `Car Settings` and reload the page until the message `Restart TSC to add new cars` is displayed.
+1. Restart the container with `docker compose restart teslasolarcharger` (you need to be in the directory of your `docker-compose.yml`).
+1. Wake up the car by opening a car door. Now the SoC values, car name,... should be displayed on the `Overview` page:
+![image](https://github.com/user-attachments/assets/8ba58c08-f66f-4b4a-897b-439b83a8b04a)
+1. If there are any messages displayed below your car name, just follow the instructions.
+
+If you only want to charge based on Spot Price, you are done now.
-##### Setting Up Urls to get grid power
+##### Setting up solar power values
-To let the TeslaSolarCharger know how much power there is to charge the car, you need to add a value in `Grid Power Url`.
+To let the TeslaSolarCharger know how much power there is to charge the car, you need to set TSC up to gather the solar values
-###### Using vendor specific plugins
+###### REST values including vendor specific plugins
+To set up a REST API click on `Add new REST source` and fill out the fields.
-**Note:** These values will be filled in automatically in a future release. Maybe it is already working, and I just forgot to remove this section ;-)
-Depending on your used plugins, you have to paste one of the following URLs to the `Grid Power Url` field:
+If you are using a plugin, you need to use the following values:
+- SMA Plugin:
+ - Url: `http://:7192/api/CurrentPower/GetAllValues` (Note: the serial number in the screenshot is optional in case you have multiple SMA EnergyMeter/Homa Managers)
+ - Node Pattern Type: JSON
+ - Add a result with the configuration seen in the screenshot
+![SMA plugin](https://github.com/user-attachments/assets/2785c050-e51e-404c-8c61-898f72177374)
-- SMA Plugin: `http://:7192/api/CurrentPower/GetPower`
-**Note:** If you have more than one EnergyMeter/Home Manager 2.0 in your network, you need to add the serial number of the correct device. A grid URL would look like this then: `http://:7192/api/CurrentPower/GetPower?serialNumber=3001231234`
- SolarEdge Plugin:
- - Grid Power, InverterPower, HomeBatterySoc, Home Battery Power Url: `http://solaredgeplugin/api/CurrentValues/GetCurrentPvValues`
+ - Url: `http://solaredgeplugin/api/CurrentValues/GetCurrentPvValues`
- Set Result types to json and use the following json patterns:
- Grid Power: `$.gridPower`
- Inverter Power: `$.inverterPower`
- Home Battery SoC: `$.homeBatterySoc`
- Home Battery Power: `$.homeBatteryPower`
- Solax Plugin:
- - Grid Power, InverterPower, HomeBatterySoc, Home Battery Power Url: `http://solaxplugin/api/CurrentValues/GetCurrentPvValues`
+ - Url: `http://solaxplugin/api/CurrentValues/GetCurrentPvValues`
- Set Result types to json and use the following json patterns:
- Grid Power: `$.gridPower`
- Inverter Power: `$.inverterPower`
- Home Battery SoC: `$.homeBatterySoc`
- Home Battery Power: `$.homeBatteryPower`
- The result should look like this:
- ![image](https://user-images.githubusercontent.com/35361981/226210694-18e1af38-25e8-43d8-a13d-6671f0d65fbc.png)
-
-
-###### Using the Modbus plugin
-
-**Warning:** As this plugin keeps an open connection to your inverter, it is highly recommended not to kill this container but always shut it down gracefully.
-To use the Modbus plugin, you must create the URL string yourself. The URL looks like this:
+![Solax Plugin](https://github.com/user-attachments/assets/d8d3324b-2988-4532-bb56-2ef4a8b4f52e)
-```text
-http://modbusplugin/api/Modbus/GetInt32Value?unitIdentifier=3&startingAddress=&quantity=&ipAddress=&port=502&factor=&connectDelaySeconds=1&timeoutSeconds=10
-```
-
-An example URL with all values filled could look like this:
-```text
-http://modbusplugin/api/Modbus/GetInt32Value?unitIdentifier=3&startingAddress=30775&quantity=2&ipAddress=192.168.1.28&port=502&factor=1&connectDelaySeconds=1&timeoutSeconds=10
-```
-You can test the result of the URL by pasting it into your browser and replacing `modbusplugin` with `ipOfYourDockerHost:7191`, e.g.:
+###### Modbus values
-```text
-http://192.168.1.50:7191/api/Modbus/GetInt32Value?unitIdentifier=3&startingAddress=30775&quantity=2&ipAddress=192.168.1.28&port=502&factor=1&connectDelaySeconds=1&timeoutSeconds=10
-```
+Fill out the values according to the documentation of your inverter:
-What the values mean:
-
-- `unitIdentifier`: Internal ID of your inverter (in most cases, 3)
-- `startingAddress`: Register address of the value you want to extract. You will find this value in the documentation of your inverter.
-- `quantity`: Number of registers to read from (for integer values should be 2)
-- `ipAddress`: IP Address of your inverter
+- `unitIdentifier`: Internal ID of your inverter (in most cases, 3 or 1)
+- `Host`: IP Address or hostname of your inverter
- `port`: Modbus TCP Port of your inverter (default: 502)
-- `factor`: Factor to multiply the resulting value with. The result should be Watt, so if your inverter returns Watt, you can leave 1. If your inverter returns 0.1W, you have to use 10.
-- `connectDelaySeconds`: Delay before communication the first time (you should use 1)
-- `timeoutSeconds`: Timeout until returning an error if the inverter is not responding (you should use 10)
-
-For more convenience, you can go to `http://your-ip-address:7191/swagger`. There you can try your values with a user interface.
-
-###### Using no plugin
+- `Connect Delay Milliseconds`: Delay before communicating the first time (you should use 1000)
+- `Read Timeout Milliseconds`: Timeout until returning an error if the inverter is not responding (you should use 1000)
+- `Address`: Register address of the value you want to extract.
+- `Length`: Number of registers to read from (for integer values should be 2)
+- `Correction Factor`: Factor to multiply the resulting value with. The result should be Watt, so if your inverter returns Watt, you can leave 1. If your inverter returns 0.1W, you have to use 10.
-If you have your own API or your energymeter directly has a REST API, you can also use these to get the grid power. Just insert the `Grid Power Url` Url; if there is a plain integer value, it should work. If your API returns JSON or XML results, you must add the exact path to that specific value.
###### JSON Path
@@ -879,7 +385,7 @@ If you have the following JSON result:
}
```
-You can use `$.data.value` as `Grid Power Json Pattern`.
+You can use `$.data.value` as `Json Pattern`.
###### XML Path
@@ -910,7 +416,7 @@ Grid Power:
Assuming the `Measurement` node with `Type` `GridPower` is the power your house feeds to the grid, you need the following values in your Base configuration:
```yaml
-- CurrentPowerToGridUrl=http://192.168.xxx.xxx/measurements.xml
+- Url=http://192.168.xxx.xxx/measurements.xml
- CurrentPowerToGridXmlPattern=Device/Measurements/Measurement
- CurrentPowerToGridXmlAttributeHeaderName=Type
- CurrentPowerToGridXmlAttributeHeaderValue=GridPower
@@ -921,15 +427,13 @@ Inverter Power:
Assuming the `Measurement` node with `Type` `AC_Power` is the power your inverter is currently feeding, you can use the following values in your Base configuration:
```yaml
-- CurrentInverterPowerUrl=http://192.168.xxx.xxx/measurements.xml
+- Url=http://192.168.xxx.xxx/measurements.xml
- CurrentInverterPowerXmlPattern=Device/Measurements/Measurement
- CurrentInverterPowerAttributeHeaderName=Type
- CurrentInverterPowerAttributeHeaderValue=AC_Power
- CurrentInverterPowerAttributeValueName=Value
```
-**Note:** These values are not needed. They are just used to show additional information.
-
#### Correction Factors
The correction factor is used to *multiply* the input value so that the results correspond with what TeslaSolarCharger expects.
@@ -942,12 +446,12 @@ The correction factor is used to *multiply* the input value so that the results
You can use the correction factors to scale/ correct these values as appropriate. For example:
-- Grid Power input expresses a positive integer as an import and a negative as an export: Select negative for the operator and 1 for the correction factor (this multiplies by -1).
-- Inverter Power is expressed as kW instead of W: Select positive for the operator and 1000 for the correction factor (this multiplies by 1000).
-- Home Battery expresses its state of charge as an absolute value in kWh: Select positive for the operator and a correction factor of 1/(Full Charge Capacity) e.g. if the battery has a full charge capacity of 100kWh the correction factor is 1/100 or 0.01
+- Grid Power input expresses a positive integer as an import and a negative as an export: Select `Minus` as operator and `1` for the correction factor (this multiplies by -1).
+- Inverter Power is expressed as kW instead of W: Select `Plus` for the operator and 1000 for the correction factor (this multiplies by 1000).
+- Home Battery expresses its state of charge as an absolute value in kWh: Select `Plus` for the operator and a correction factor of 1/(Full Charge Capacity) e.g. if the battery has a full charge capacity of 100kWh the correction factor is 1/100 or 0.01
#### Install and setup BLE API
-To go around Teslas API limitations, you can use Bluetooth (BLE) to control your car. You can do this either by using the same device as your TSC is running on, or by using a separate device. Note: The device needs to be placed near the car.
+To go around Teslas API limitations, you can use Bluetooth (BLE) to control your car. You can do this either by using the same device as your TSC is running on, or by using a separate device. Note: The device needs to be placed near the car. Even if it is working when being a few meters away or in different rooms, I can guarantee you, that you will have issues sooner or later. The device needs to be in one room with the car without any walls between them.
Confirmed working hardware:
* Raspberry Pi Zero 2W (only capable when used as separate device)
@@ -986,81 +490,6 @@ You can also copy the complete content from here:
version: '3.3'
services:
- teslamate:
- image: teslamate/teslamate:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- - TZ=Europe/Berlin ##You can change your Timezone here
- ports:
- - 4000:4000
- volumes:
- - ./import:/opt/app/import
- cap_drop:
- - all
-
- database:
- image: postgres:15
- restart: always
- environment:
- - POSTGRES_USER=teslamate
- - POSTGRES_PASSWORD=secret ##You can change your password here
- - POSTGRES_DB=teslamate
- volumes:
- - teslamate-db:/var/lib/postgresql/data
-
- grafana:
- image: teslamate/grafana:latest
- restart: always
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- ports:
- - 3100:3000
- volumes:
- - teslamate-grafana-data:/var/lib/grafana
-
- mosquitto:
- image: eclipse-mosquitto:2
- restart: always
- command: mosquitto -c /mosquitto-no-auth.conf
- #ports:
- # - 1883:1883
- volumes:
- - mosquitto-conf:/mosquitto/config
- - mosquitto-data:/mosquitto/data
-
- teslamateapi:
- image: tobiasehlert/teslamateapi:latest
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
- restart: always
- depends_on:
- - database
- environment:
- - DATABASE_USER=teslamate
- - DATABASE_PASS=secret ##You can change your password here
- - DATABASE_NAME=teslamate
- - DATABASE_HOST=database
- - MQTT_HOST=mosquitto
- - TZ=Europe/Berlin ##You can change your Timezone here
- - ENABLE_COMMANDS=true
- - COMMANDS_ALL=true
- - API_TOKEN_DISABLE=true
- - ENCRYPTION_KEY=supersecret ##You can change your encryption key here
- #ports:
- # - 8080:8080
-
teslasolarcharger:
image: pkuehnel/teslasolarcharger:latest
container_name: teslasolarcharger
@@ -1070,8 +499,6 @@ services:
max-file: "10"
max-size: "100m"
restart: always
- depends_on:
- - teslamateapi
environment:
# - Serilog__MinimumLevel__Default=Verbose #uncomment this line and recreate container with docker compose up -d for more detailed logs
- TZ=Europe/Berlin ##You can change your Timezone here
@@ -1093,10 +520,6 @@ services:
- /var/run/dbus:/var/run/dbus
volumes:
- teslamate-db:
- teslamate-grafana-data:
- mosquitto-conf:
- mosquitto-data:
teslasolarcharger-configs:
tscbleapi:
```
@@ -1143,19 +566,15 @@ If you set `PowerBuffer` to a value different from `0`, the system uses the valu
To configure your home battery, you need to add the following settings:
-- URL for getting the state of charge
-- URL for getting current charging/discharging power
- Home Battery Minimum SoC
- Home Battery Charging Power
-After setting everything up, your overview page should look like this:
-
-![image](https://user-images.githubusercontent.com/35361981/183434947-16d13372-09ff-45a7-94a2-8d4043f39f18.png)
+As long as your home battery's SoC is below the set value, the configured charging power is reserved for the home battery. E.g. if you set Home Battery Minimum SoC to 80% and Home Battery Charging Power to 5000W TSC lets the home battery charge with 5000W as long as its SoC is below 80%.
-**Note:** If your battery is discharging, the power should be displayed in red. If the battery is charging, the power should be displayed in green. If this is the other way around, you must update the `Correction Factor` below your `HomeBatteryPower Url` setting and invert it to a negative number, e.g. `-1.0`.
+**Note:** If your battery is discharging, the power should be displayed in red. If the battery is charging, the power should be displayed in green. If this is the other way around, you must update the `Operator` to be `Minus`.
### Telegram integration
-In this section you learn how to create the Telegram Bot Key and where you get the Telegram ChannelID from:
+In this section, you learn how to create the Telegram Bot Key and where you get the Telegram ChannelID from:
- Create a bot by chatting with `BotFather`
@@ -1165,6 +584,7 @@ In this section you learn how to create the Telegram Bot Key and where you get t
![newbot](https://user-images.githubusercontent.com/35361981/233468050-b996475a-fe3a-4131-805e-0fe4c60ce603.jpg)
+- Click on the link starting with `t.me/` (second line of `BotFarther`'s answer in the chat) and send any message to your newly created bot. The reason for that is, that a chat exists, where TSC can send messages to.
- Copy the Bot token as Telegram Bot Key to your TSC
![BotToken](https://user-images.githubusercontent.com/35361981/233468177-620b0c2f-d9fa-46de-9f87-2eb7b6562553.jpg)
@@ -1190,8 +610,8 @@ Currently, there are four different charge modes available:
1. **PV only**: Only solar energy is used to charge. You can set a SOC level that should be reached at the specified date and time (if charge every day is enabled, the car charges to that SoC every day, not only once). If solar power is insufficient to reach the set soc level in time, the car starts charging at full speed. Note: To let this work, specify `usable kWh` in the car settings section.
1. **Maximum Power**: The car charges with the maximum available power
-1. **Min SoC + PV**: If plugged in, the car starts charging with maximum power until the set Min SoC is reached. After that, only PV Power is used to charge the car.
-1. **Spot Price + PV**: You can set a Min Soc, which should be reached at a specific date and time (if charge every day is enabled, the car charges to that SoC every day, not only once). The charge times are then planned to charge at the cheapest possible time. This is especially useful if you have hourly electricity prices like with [Tibber](https://tibber.com/) or [aWATTar](https://www.awattar.de/). Note: The car will still charge based on Solar energy if available, and you need to enable `Use Spot Price` in the Charge Prices settings for correct charge price calculation.
+1. **Min SoC + PV**: If plugged in, the car starts charging with maximum power until the set Min SoC is reached. Thereafter, only PV Power is used to charge the car.
+1. **Spot Price + PV**: You can set a Min Soc, which should be reached at a specific date and time (if charge every day is enabled, the car charges to that SoC every day, not only once). The charge times are then planned to charge at the cheapest possible time. This is especially useful if you have hourly electricity prices, like with [Tibber](https://tibber.com/) or [aWATTar](https://www.awattar.de/). Note: The car will still charge based on Solar energy if available, and you need to enable `Use Spot Price` in the Charge Prices settings for correct charge price calculation.
1. **TSC Disabled**: TSC leaves this car as is and does not update the charging speed etc.
## Generate logfiles
diff --git a/TeslaSolarCharger.Model/Contracts/IDbConnectionStringHelper.cs b/TeslaSolarCharger.Model/Contracts/IDbConnectionStringHelper.cs
index 2ce1907b8..6677e9c30 100644
--- a/TeslaSolarCharger.Model/Contracts/IDbConnectionStringHelper.cs
+++ b/TeslaSolarCharger.Model/Contracts/IDbConnectionStringHelper.cs
@@ -2,6 +2,6 @@
public interface IDbConnectionStringHelper
{
- string GetTeslaMateConnectionString();
+ string? GetTeslaMateConnectionString();
string GetTeslaSolarChargerDbPath();
}
\ No newline at end of file
diff --git a/TeslaSolarCharger.Model/Contracts/ITeslaSolarChargerContext.cs b/TeslaSolarCharger.Model/Contracts/ITeslaSolarChargerContext.cs
index d55c3d00a..d0f868a7a 100644
--- a/TeslaSolarCharger.Model/Contracts/ITeslaSolarChargerContext.cs
+++ b/TeslaSolarCharger.Model/Contracts/ITeslaSolarChargerContext.cs
@@ -28,5 +28,6 @@ public interface ITeslaSolarChargerContext
DbSet MqttConfigurations { get; set; }
DbSet MqttResultConfigurations { get; set; }
DbSet BackendNotifications { get; set; }
+ DbSet LoggedErrors { get; set; }
void RejectChanges();
}
diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs
index 9555ce231..5ebdff73a 100644
--- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs
+++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs
@@ -1,4 +1,3 @@
-using TeslaSolarCharger.Model.Enums;
using TeslaSolarCharger.Shared.Enums;
namespace TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
@@ -9,12 +8,13 @@ public class Car
public int? TeslaMateCarId { get; set; }
public string? Name { get; set; }
public string? Vin { get; set; }
- public TeslaCarFleetApiState TeslaFleetApiState { get; set; } = TeslaCarFleetApiState.NotConfigured;
+ public TeslaCarFleetApiState? TeslaFleetApiState { get; set; }
public ChargeMode ChargeMode { get; set; }
public int MinimumSoc { get; set; }
public DateTime LatestTimeToReachSoC { get; set; }
public bool IgnoreLatestTimeToReachSocDate { get; set; }
+ public bool IgnoreLatestTimeToReachSocDateOnWeekend { get; set; }
public int MaximumAmpere { get; set; }
@@ -23,7 +23,6 @@ public class Car
public int UsableEnergy { get; set; }
public bool? ShouldBeManaged { get; set; }
- public bool? ShouldSetChargeStartTimes { get; set; }
public int ChargingPriority { get; set; }
@@ -41,10 +40,23 @@ public class Car
public double? Longitude { get; set; }
public CarStateEnum? State { get; set; }
public bool VehicleCommandProtocolRequired { get; set; }
- public DateTime? RateLimitedUntil { get; set; }
+ public DateTime? VehicleRateLimitedUntil { get; set; }
+ public DateTime? VehicleDataRateLimitedUntil { get; set; }
+ public DateTime? CommandsRateLimitedUntil { get; set; }
+ public DateTime? WakeUpRateLimitedUntil { get; set; }
+ public DateTime? ChargingCommandsRateLimitedUntil { get; set; }
public bool UseBle { get; set; }
+ public bool UseBleForWakeUp { get; set; }
public int ApiRefreshIntervalSeconds { get; set; }
public string? BleApiBaseUrl { get; set; }
+ public string? WakeUpCalls { get; set; }
+ public string? VehicleDataCalls { get; set; }
+ public string? VehicleCalls { get; set; }
+ public string? ChargeStartCalls { get; set; }
+ public string? ChargeStopCalls { get; set; }
+ public string? SetChargingAmpsCall { get; set; }
+ public string? OtherCommandCalls { get; set; }
+
public List ChargingProcesses { get; set; } = new List();
}
diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/ChargingDetail.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/ChargingDetail.cs
index 48a172e34..2bae317b0 100644
--- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/ChargingDetail.cs
+++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/ChargingDetail.cs
@@ -7,6 +7,7 @@ public class ChargingDetail
public int SolarPower { get; set; }
public int HomeBatteryPower { get; set; }
public int GridPower { get; set; }
+ public int? ChargerVoltage { get; set; }
public int ChargingProcessId { get; set; }
diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Error.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Error.cs
new file mode 100644
index 000000000..1bfadc992
--- /dev/null
+++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Error.cs
@@ -0,0 +1,19 @@
+namespace TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
+
+public class LoggedError
+{
+ public int Id { get; set; }
+ public DateTime StartTimeStamp { get; set; }
+ public DateTime? EndTimeStamp { get; set; }
+ public List FurtherOccurrences { get; set; } = new();
+ public string IssueKey { get; set; }
+ public string Headline {get; set; }
+ public string? Vin { get; set; }
+ public string Source { get; set; }
+ public string MethodName { get; set; }
+ public string Message { get; set; }
+ public string? StackTrace { get; set; }
+ public DateTime? DismissedAt { get; set; }
+ public bool TelegramNotificationSent { get; set; }
+ public bool TelegramResolvedMessageSent { get; set; }
+}
diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/ModbusConfiguration.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/ModbusConfiguration.cs
index f2d629f36..b499ec677 100644
--- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/ModbusConfiguration.cs
+++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/ModbusConfiguration.cs
@@ -1,5 +1,4 @@
-using TeslaSolarCharger.Model.BaseClasses;
-using TeslaSolarCharger.Shared.Enums;
+using TeslaSolarCharger.Shared.Enums;
namespace TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
diff --git a/TeslaSolarCharger.Model/EntityFramework/DbConnectionStringHelper.cs b/TeslaSolarCharger.Model/EntityFramework/DbConnectionStringHelper.cs
index 26278b9f3..ff99869fe 100644
--- a/TeslaSolarCharger.Model/EntityFramework/DbConnectionStringHelper.cs
+++ b/TeslaSolarCharger.Model/EntityFramework/DbConnectionStringHelper.cs
@@ -15,7 +15,7 @@ public DbConnectionStringHelper(ILogger logger, IConfi
_configurationWrapper = configurationWrapper;
}
- public string GetTeslaMateConnectionString()
+ public string? GetTeslaMateConnectionString()
{
_logger.LogTrace("{method}()", nameof(GetTeslaMateConnectionString));
var server = _configurationWrapper.TeslaMateDbServer();
@@ -23,6 +23,14 @@ public string GetTeslaMateConnectionString()
var databaseName = _configurationWrapper.TeslaMateDbDatabaseName();
var username = _configurationWrapper.TeslaMateDbUser();
var password = _configurationWrapper.TeslaMateDbPassword();
+ if (string.IsNullOrEmpty(server)
+ || port == default
+ || string.IsNullOrEmpty(databaseName)
+ || string.IsNullOrEmpty(username)
+ || string.IsNullOrEmpty(password))
+ {
+ return null;
+ }
var connectionString = $"Host={server};Port={port};Database={databaseName};Username={username};Password={password}";
_logger.LogTrace("ConnectionString: {connectionString}", connectionString);
return connectionString;
diff --git a/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs b/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs
index 4149fe90b..5f3d1c253 100644
--- a/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs
+++ b/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs
@@ -1,5 +1,7 @@
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Newtonsoft.Json;
using TeslaSolarCharger.Model.Contracts;
using TeslaSolarCharger.Model.Converters;
using TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
@@ -27,6 +29,7 @@ public class TeslaSolarChargerContext : DbContext, ITeslaSolarChargerContext
public DbSet MqttConfigurations { get; set; } = null!;
public DbSet MqttResultConfigurations { get; set; } = null!;
public DbSet BackendNotifications { get; set; } = null!;
+ public DbSet LoggedErrors { get; set; } = null!;
// ReSharper disable once UnassignedGetOnlyAutoProperty
public string DbPath { get; }
@@ -107,6 +110,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity()
.Property(c => c.ApiRefreshIntervalSeconds)
.HasDefaultValue(500);
+
+ var timeListToString = new ValueConverter, string?>(
+ v => JsonConvert.SerializeObject(v),
+ v => v == null ? new() : JsonConvert.DeserializeObject>(v) ?? new List()
+ );
+
+ var valueComparer = new ValueComparer>(
+ (c1, c2) => c2 != null && c1 != null && c1.SequenceEqual(c2), // Determines equality
+ c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), // Calculates hash code
+ c => c.ToList() // Makes a snapshot copy
+ );
+
+ modelBuilder.Entity()
+ .Property(e => e.FurtherOccurrences)
+ .HasConversion(timeListToString)
+ .Metadata.SetValueComparer(valueComparer);
+
}
#pragma warning disable CS8618
diff --git a/TeslaSolarCharger.Model/Migrations/20240606191531_AddIgnoreLatestTimeToReachSocDateOnWeekend.Designer.cs b/TeslaSolarCharger.Model/Migrations/20240606191531_AddIgnoreLatestTimeToReachSocDateOnWeekend.Designer.cs
new file mode 100644
index 000000000..c15d2274e
--- /dev/null
+++ b/TeslaSolarCharger.Model/Migrations/20240606191531_AddIgnoreLatestTimeToReachSocDateOnWeekend.Designer.cs
@@ -0,0 +1,717 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using TeslaSolarCharger.Model.EntityFramework;
+
+#nullable disable
+
+namespace TeslaSolarCharger.Model.Migrations
+{
+ [DbContext(typeof(TeslaSolarChargerContext))]
+ [Migration("20240606191531_AddIgnoreLatestTimeToReachSocDateOnWeekend")]
+ partial class AddIgnoreLatestTimeToReachSocDateOnWeekend
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CachedCarState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CarStateJson")
+ .HasColumnType("TEXT");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("LastUpdated")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("CachedCarStates");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargeMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerActualCurrent")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerPhases")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerPilotCurrent")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerRequestedCurrent")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerVoltage")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargingPriority")
+ .HasColumnType("INTEGER");
+
+ b.Property("ClimateOn")
+ .HasColumnType("INTEGER");
+
+ b.Property("IgnoreLatestTimeToReachSocDate")
+ .HasColumnType("INTEGER");
+
+ b.Property("IgnoreLatestTimeToReachSocDateOnWeekend")
+ .HasColumnType("INTEGER");
+
+ b.Property("LatestTimeToReachSoC")
+ .HasColumnType("TEXT");
+
+ b.Property("Latitude")
+ .HasColumnType("REAL");
+
+ b.Property("Longitude")
+ .HasColumnType("REAL");
+
+ b.Property("MaximumAmpere")
+ .HasColumnType("INTEGER");
+
+ b.Property("MinimumAmpere")
+ .HasColumnType("INTEGER");
+
+ b.Property("MinimumSoc")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("PluggedIn")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShouldBeManaged")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShouldSetChargeStartTimes")
+ .HasColumnType("INTEGER");
+
+ b.Property("SoC")
+ .HasColumnType("INTEGER");
+
+ b.Property("SocLimit")
+ .HasColumnType("INTEGER");
+
+ b.Property("State")
+ .HasColumnType("INTEGER");
+
+ b.Property("TeslaFleetApiState")
+ .HasColumnType("INTEGER");
+
+ b.Property("TeslaMateCarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("UsableEnergy")
+ .HasColumnType("INTEGER");
+
+ b.Property("VehicleCommandProtocolRequired")
+ .HasColumnType("INTEGER");
+
+ b.Property("Vin")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TeslaMateCarId")
+ .IsUnique();
+
+ b.HasIndex("Vin")
+ .IsUnique();
+
+ b.ToTable("Cars");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargePrice", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AddSpotPriceToGridPrice")
+ .HasColumnType("INTEGER");
+
+ b.Property("EnergyProvider")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(6);
+
+ b.Property("EnergyProviderConfiguration")
+ .HasColumnType("TEXT");
+
+ b.Property("GridPrice")
+ .HasColumnType("TEXT");
+
+ b.Property("SolarPrice")
+ .HasColumnType("TEXT");
+
+ b.Property("SpotPriceCorrectionFactor")
+ .HasColumnType("TEXT");
+
+ b.Property("ValidSince")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("ChargePrices");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargingProcessId")
+ .HasColumnType("INTEGER");
+
+ b.Property("GridPower")
+ .HasColumnType("INTEGER");
+
+ b.Property("SolarPower")
+ .HasColumnType("INTEGER");
+
+ b.Property("TimeStamp")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChargingProcessId");
+
+ b.ToTable("ChargingDetails");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Cost")
+ .HasColumnType("TEXT");
+
+ b.Property("EndDate")
+ .HasColumnType("TEXT");
+
+ b.Property("OldHandledChargeId")
+ .HasColumnType("INTEGER");
+
+ b.Property("StartDate")
+ .HasColumnType("TEXT");
+
+ b.Property("UsedGridEnergyKwh")
+ .HasColumnType("TEXT");
+
+ b.Property("UsedSolarEnergyKwh")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CarId");
+
+ b.ToTable("ChargingProcesses");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AverageSpotPrice")
+ .HasColumnType("TEXT");
+
+ b.Property("CalculatedPrice")
+ .HasColumnType("TEXT");
+
+ b.Property("CarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargingProcessId")
+ .HasColumnType("INTEGER");
+
+ b.Property("UsedGridEnergy")
+ .HasColumnType("TEXT");
+
+ b.Property("UsedSolarEnergy")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("HandledCharges");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConnectDelayMilliseconds")
+ .HasColumnType("INTEGER");
+
+ b.Property("Endianess")
+ .HasColumnType("INTEGER");
+
+ b.Property("Host")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Port")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadTimeoutMilliseconds")
+ .HasColumnType("INTEGER");
+
+ b.Property("UnitIdentifier")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("ModbusConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusResultConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Address")
+ .HasColumnType("INTEGER");
+
+ b.Property("BitStartIndex")
+ .HasColumnType("INTEGER");
+
+ b.Property("CorrectionFactor")
+ .HasColumnType("TEXT");
+
+ b.Property("InvertedByModbusResultConfigurationId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Length")
+ .HasColumnType("INTEGER");
+
+ b.Property("ModbusConfigurationId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Operator")
+ .HasColumnType("INTEGER");
+
+ b.Property("RegisterType")
+ .HasColumnType("INTEGER");
+
+ b.Property("UsedFor")
+ .HasColumnType("INTEGER");
+
+ b.Property("ValueType")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InvertedByModbusResultConfigurationId");
+
+ b.HasIndex("ModbusConfigurationId");
+
+ b.ToTable("ModbusResultConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Host")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Password")
+ .HasColumnType("TEXT");
+
+ b.Property("Port")
+ .HasColumnType("INTEGER");
+
+ b.Property("Username")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("MqttConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttResultConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CorrectionFactor")
+ .HasColumnType("TEXT");
+
+ b.Property("MqttConfigurationId")
+ .HasColumnType("INTEGER");
+
+ b.Property("NodePattern")
+ .HasColumnType("TEXT");
+
+ b.Property("NodePatternType")
+ .HasColumnType("INTEGER");
+
+ b.Property("Operator")
+ .HasColumnType("INTEGER");
+
+ b.Property("Topic")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("UsedFor")
+ .HasColumnType("INTEGER");
+
+ b.Property("XmlAttributeHeaderName")
+ .HasColumnType("TEXT");
+
+ b.Property("XmlAttributeHeaderValue")
+ .HasColumnType("TEXT");
+
+ b.Property("XmlAttributeValueName")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MqttConfigurationId");
+
+ b.ToTable("MqttResultConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.PowerDistribution", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargingPower")
+ .HasColumnType("INTEGER");
+
+ b.Property("GridProportion")
+ .HasColumnType("REAL");
+
+ b.Property("HandledChargeId")
+ .HasColumnType("INTEGER");
+
+ b.Property("PowerFromGrid")
+ .HasColumnType("INTEGER");
+
+ b.Property("TimeStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("UsedWattHours")
+ .HasColumnType("REAL");
+
+ b.HasKey("Id");
+
+ b.HasIndex("HandledChargeId");
+
+ b.ToTable("PowerDistributions");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("HttpMethod")
+ .HasColumnType("INTEGER");
+
+ b.Property("NodePatternType")
+ .HasColumnType("INTEGER");
+
+ b.Property("Url")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("RestValueConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfigurationHeader", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("RestValueConfigurationId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RestValueConfigurationId", "Key")
+ .IsUnique();
+
+ b.ToTable("RestValueConfigurationHeaders");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueResultConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CorrectionFactor")
+ .HasColumnType("TEXT");
+
+ b.Property("NodePattern")
+ .HasColumnType("TEXT");
+
+ b.Property("Operator")
+ .HasColumnType("INTEGER");
+
+ b.Property("RestValueConfigurationId")
+ .HasColumnType("INTEGER");
+
+ b.Property("UsedFor")
+ .HasColumnType("INTEGER");
+
+ b.Property("XmlAttributeHeaderName")
+ .HasColumnType("TEXT");
+
+ b.Property("XmlAttributeHeaderValue")
+ .HasColumnType("TEXT");
+
+ b.Property("XmlAttributeValueName")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RestValueConfigurationId");
+
+ b.ToTable("RestValueResultConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.SpotPrice", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("EndDate")
+ .HasColumnType("TEXT");
+
+ b.Property("Price")
+ .HasColumnType("TEXT");
+
+ b.Property("StartDate")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("SpotPrices");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.TeslaToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AccessToken")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAtUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("IdToken")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("RefreshToken")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Region")
+ .HasColumnType("INTEGER");
+
+ b.Property("UnauthorizedCounter")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("TeslaTokens");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.TscConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Key")
+ .IsUnique();
+
+ b.ToTable("TscConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingDetail", b =>
+ {
+ b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", "ChargingProcess")
+ .WithMany("ChargingDetails")
+ .HasForeignKey("ChargingProcessId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("ChargingProcess");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", b =>
+ {
+ b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", "Car")
+ .WithMany("ChargingProcesses")
+ .HasForeignKey("CarId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Car");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusResultConfiguration", b =>
+ {
+ b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusResultConfiguration", "InvertedByModbusResultConfiguration")
+ .WithMany()
+ .HasForeignKey("InvertedByModbusResultConfigurationId");
+
+ b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusConfiguration", "ModbusConfiguration")
+ .WithMany("ModbusResultConfigurations")
+ .HasForeignKey("ModbusConfigurationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("InvertedByModbusResultConfiguration");
+
+ b.Navigation("ModbusConfiguration");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttResultConfiguration", b =>
+ {
+ b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttConfiguration", "MqttConfiguration")
+ .WithMany("MqttResultConfigurations")
+ .HasForeignKey("MqttConfigurationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("MqttConfiguration");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.PowerDistribution", b =>
+ {
+ b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", "HandledCharge")
+ .WithMany("PowerDistributions")
+ .HasForeignKey("HandledChargeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("HandledCharge");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfigurationHeader", b =>
+ {
+ b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfiguration", "RestValueConfiguration")
+ .WithMany("Headers")
+ .HasForeignKey("RestValueConfigurationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("RestValueConfiguration");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueResultConfiguration", b =>
+ {
+ b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfiguration", "RestValueConfiguration")
+ .WithMany("RestValueResultConfigurations")
+ .HasForeignKey("RestValueConfigurationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("RestValueConfiguration");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", b =>
+ {
+ b.Navigation("ChargingProcesses");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", b =>
+ {
+ b.Navigation("ChargingDetails");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", b =>
+ {
+ b.Navigation("PowerDistributions");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusConfiguration", b =>
+ {
+ b.Navigation("ModbusResultConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttConfiguration", b =>
+ {
+ b.Navigation("MqttResultConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfiguration", b =>
+ {
+ b.Navigation("Headers");
+
+ b.Navigation("RestValueResultConfigurations");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/TeslaSolarCharger.Model/Migrations/20240606191531_AddIgnoreLatestTimeToReachSocDateOnWeekend.cs b/TeslaSolarCharger.Model/Migrations/20240606191531_AddIgnoreLatestTimeToReachSocDateOnWeekend.cs
new file mode 100644
index 000000000..911b2acf9
--- /dev/null
+++ b/TeslaSolarCharger.Model/Migrations/20240606191531_AddIgnoreLatestTimeToReachSocDateOnWeekend.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace TeslaSolarCharger.Model.Migrations
+{
+ ///
+ public partial class AddIgnoreLatestTimeToReachSocDateOnWeekend : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "IgnoreLatestTimeToReachSocDateOnWeekend",
+ table: "Cars",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: false);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "IgnoreLatestTimeToReachSocDateOnWeekend",
+ table: "Cars");
+ }
+ }
+}
diff --git a/TeslaSolarCharger.Model/Migrations/20240709105336_SplitRateLimitedInfoIntoMultipleFields.Designer.cs b/TeslaSolarCharger.Model/Migrations/20240709105336_SplitRateLimitedInfoIntoMultipleFields.Designer.cs
new file mode 100644
index 000000000..f92a4600d
--- /dev/null
+++ b/TeslaSolarCharger.Model/Migrations/20240709105336_SplitRateLimitedInfoIntoMultipleFields.Designer.cs
@@ -0,0 +1,786 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using TeslaSolarCharger.Model.EntityFramework;
+
+#nullable disable
+
+namespace TeslaSolarCharger.Model.Migrations
+{
+ [DbContext(typeof(TeslaSolarChargerContext))]
+ [Migration("20240709105336_SplitRateLimitedInfoIntoMultipleFields")]
+ partial class SplitRateLimitedInfoIntoMultipleFields
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.BackendNotification", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("BackendIssueId")
+ .HasColumnType("INTEGER");
+
+ b.Property("DetailText")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Headline")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("IsConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.Property("ValidFromDate")
+ .HasColumnType("TEXT");
+
+ b.Property("ValidFromVersion")
+ .HasColumnType("TEXT");
+
+ b.Property("ValidToDate")
+ .HasColumnType("TEXT");
+
+ b.Property("ValidToVersion")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("BackendNotifications");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CachedCarState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CarStateJson")
+ .HasColumnType("TEXT");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("LastUpdated")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("CachedCarStates");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ApiRefreshIntervalSeconds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(500);
+
+ b.Property("BleApiBaseUrl")
+ .HasColumnType("TEXT");
+
+ b.Property("ChargeMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerActualCurrent")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerPhases")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerPilotCurrent")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerRequestedCurrent")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargerVoltage")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargingCommandsRateLimitedUntil")
+ .HasColumnType("TEXT");
+
+ b.Property("ChargingPriority")
+ .HasColumnType("INTEGER");
+
+ b.Property("ClimateOn")
+ .HasColumnType("INTEGER");
+
+ b.Property("CommandsRateLimitedUntil")
+ .HasColumnType("TEXT");
+
+ b.Property("IgnoreLatestTimeToReachSocDate")
+ .HasColumnType("INTEGER");
+
+ b.Property("LatestTimeToReachSoC")
+ .HasColumnType("TEXT");
+
+ b.Property("Latitude")
+ .HasColumnType("REAL");
+
+ b.Property("Longitude")
+ .HasColumnType("REAL");
+
+ b.Property("MaximumAmpere")
+ .HasColumnType("INTEGER");
+
+ b.Property("MinimumAmpere")
+ .HasColumnType("INTEGER");
+
+ b.Property("MinimumSoc")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("PluggedIn")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShouldBeManaged")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShouldSetChargeStartTimes")
+ .HasColumnType("INTEGER");
+
+ b.Property("SoC")
+ .HasColumnType("INTEGER");
+
+ b.Property("SocLimit")
+ .HasColumnType("INTEGER");
+
+ b.Property("State")
+ .HasColumnType("INTEGER");
+
+ b.Property("TeslaFleetApiState")
+ .HasColumnType("INTEGER");
+
+ b.Property("TeslaMateCarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("UsableEnergy")
+ .HasColumnType("INTEGER");
+
+ b.Property("UseBle")
+ .HasColumnType("INTEGER");
+
+ b.Property("VehicleCommandProtocolRequired")
+ .HasColumnType("INTEGER");
+
+ b.Property("VehicleDataRateLimitedUntil")
+ .HasColumnType("TEXT");
+
+ b.Property("VehicleRateLimitedUntil")
+ .HasColumnType("TEXT");
+
+ b.Property("Vin")
+ .HasColumnType("TEXT");
+
+ b.Property("WakeUpRateLimitedUntil")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TeslaMateCarId")
+ .IsUnique();
+
+ b.HasIndex("Vin")
+ .IsUnique();
+
+ b.ToTable("Cars");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargePrice", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AddSpotPriceToGridPrice")
+ .HasColumnType("INTEGER");
+
+ b.Property("EnergyProvider")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(6);
+
+ b.Property("EnergyProviderConfiguration")
+ .HasColumnType("TEXT");
+
+ b.Property("GridPrice")
+ .HasColumnType("TEXT");
+
+ b.Property("SolarPrice")
+ .HasColumnType("TEXT");
+
+ b.Property("SpotPriceCorrectionFactor")
+ .HasColumnType("TEXT");
+
+ b.Property("ValidSince")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("ChargePrices");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargingProcessId")
+ .HasColumnType("INTEGER");
+
+ b.Property("GridPower")
+ .HasColumnType("INTEGER");
+
+ b.Property("HomeBatteryPower")
+ .HasColumnType("INTEGER");
+
+ b.Property("SolarPower")
+ .HasColumnType("INTEGER");
+
+ b.Property("TimeStamp")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChargingProcessId");
+
+ b.ToTable("ChargingDetails");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Cost")
+ .HasColumnType("TEXT");
+
+ b.Property("EndDate")
+ .HasColumnType("TEXT");
+
+ b.Property("OldHandledChargeId")
+ .HasColumnType("INTEGER");
+
+ b.Property("StartDate")
+ .HasColumnType("TEXT");
+
+ b.Property("UsedGridEnergyKwh")
+ .HasColumnType("TEXT");
+
+ b.Property("UsedHomeBatteryEnergyKwh")
+ .HasColumnType("TEXT");
+
+ b.Property("UsedSolarEnergyKwh")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CarId");
+
+ b.ToTable("ChargingProcesses");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AverageSpotPrice")
+ .HasColumnType("TEXT");
+
+ b.Property("CalculatedPrice")
+ .HasColumnType("TEXT");
+
+ b.Property("CarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChargingProcessId")
+ .HasColumnType("INTEGER");
+
+ b.Property("UsedGridEnergy")
+ .HasColumnType("TEXT");
+
+ b.Property("UsedSolarEnergy")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("HandledCharges");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConnectDelayMilliseconds")
+ .HasColumnType("INTEGER");
+
+ b.Property("Endianess")
+ .HasColumnType("INTEGER");
+
+ b.Property("Host")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Port")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadTimeoutMilliseconds")
+ .HasColumnType("INTEGER");
+
+ b.Property("UnitIdentifier")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("ModbusConfigurations");
+ });
+
+ modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusResultConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Address")
+ .HasColumnType("INTEGER");
+
+ b.Property("BitStartIndex")
+ .HasColumnType("INTEGER");
+
+ b.Property