Skip to content

Commit

Permalink
Merge pull request #1310 from pkuehnel/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
pkuehnel authored Jun 9, 2024
2 parents e584cd4 + bb4ddee commit 16c7fee
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 20 deletions.
182 changes: 181 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ TeslaSolarCharger is a service to set one or multiple Teslas' charging current u
- [Setting up TeslaMate including TeslaSolarCharger](#Setting-up-TeslaMate-including-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)
- [Often used optional settings](#often-used-optional-settings)
- [Power Buffer](#power-buffer)
- [Home Battery](#home-battery)
Expand All @@ -27,7 +28,7 @@ TeslaSolarCharger is a service to set one or multiple Teslas' charging current u

## How to install

You can either install the software in a Docker container or download the binaries and deploy it on any server.
You can either install the software in a Docker container or download the binaries and deploy it on any server. In June 2024, Tesla implemented rate limits to their API, so there is a BLE (Bluetooth Low Energy, implemented since Bluetooth Version 4.0) capable device needed near the car. You can find details on how to set up BLE [here](#setup-ble-api).

### Docker compose

Expand Down Expand Up @@ -929,6 +930,185 @@ Assuming the `Measurement` node with `Type` `AC_Power` is the power your inverte

**Note:** These values are not needed. They are just used to show additional information.

#### 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.

##### Install BLE API on the same device as TSC
To set up the BLE API on the same device as your TSC is running on, you need to add the following lines to your docker-compose.yml:

```yaml
services:
#here are all the other services like TeslaMate, TSC, etc.
bleapi:
image: ghcr.io/pkuehnel/teslasolarchargerbleapi:latest
container_name: TeslaSolarChargerBleApi
privileged: true
restart: unless-stopped
network_mode: host
environment:
- ASPNETCORE_URLS=http://+:7210
volumes:
- tscbleapi:/externalFiles
- /var/run/dbus:/var/run/dbus
volumes:
#here are all the other volumes like teslamate-db, teslamate-grafana-data, etc.
tscbleapi:
```

You can also copy the complete content from here:
<details>
<summary>Complete file including BLE API</summary>

```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
bleapi:
image: ghcr.io/pkuehnel/teslasolarchargerbleapi:latest
container_name: TeslaSolarChargerBleApi
privileged: true
restart: unless-stopped
network_mode: host
environment:
- ASPNETCORE_URLS=http://+:7210
volumes:
- tscbleapi:/externalFiles
- /var/run/dbus:/var/run/dbus
volumes:
teslamate-db:
teslamate-grafana-data:
mosquitto-conf:
mosquitto-data:
teslasolarcharger-configs:
tscbleapi:
```

</details>

##### Install BLE API on a separate device
To set up a separate device for the BLE API, you need to install Docker on the device, like described [here](#docker-compose). Thereafter, you can use the following docker-compose.yml and start the container with `docker compose up -d`:

```yaml
services:
bleapi:
image: ghcr.io/pkuehnel/teslasolarchargerbleapi:latest
container_name: TeslaSolarChargerBleApi
privileged: true
restart: unless-stopped
network_mode: host
environment:
- ASPNETCORE_URLS=http://+:7210
volumes:
- tscbleapi:/externalFiles
- /var/run/dbus:/var/run/dbus
volumes:
tscbleapi:
```

##### Setup BLE (same device and separate device)
After starting the BLE API, you need to add the BLE API Base URL to your TeslaSolarCharger configuration. The URL is `http://<IP of device with BLE API running>:7210/`

Now you can pair each car by going to the `Car Settings` enable "Use BLE", click Save and then click on Pair Car. Note: It could take up to three tries to pair the car. After you get a message that pairing succeeded, you can test the API by clicking on the `Set to 7A`. Note: The car needs to be awake during the pairing and test process.



## Often used optional settings

When you are at this point, your car connected to any charging cable in your set home area should start charging based on solar power. But there are a few additional settings that are maybe helpful for your environment:
Expand Down
9 changes: 9 additions & 0 deletions TeslaSolarCharger/Client/Pages/BaseConfiguration.razor
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ else
<button class="btn btn-primary" disabled="@_tokenGenerationButtonDisabled" type="button" @onclick="GenerateFleetApiToken">Generate Token</button>
</div>
<hr />
<InputComponent ValueId="bleApiBaseUrl"
LabelText="BLE API Base URL"
UnitText=""
HelpText="Needed to send commands via BLE to the car. An example value would be `http://raspible:7210/`">
<InputFragment>
<InputText id="bleApiBaseUrl" @bind-Value="_dtoBaseConfiguration.BleApiBaseUrl" class="form-control" placeholder=" " />
</InputFragment>
</InputComponent>
<hr />
}
<h3>TeslaMate:</h3>
<InputComponent ValueId="teslaMateDbUser"
Expand Down
11 changes: 6 additions & 5 deletions TeslaSolarCharger/Client/Pages/CarSettings.razor
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ else
<hr />
<h3>BLE Pairing and test</h3>
<div>
TSC can use BLE instead of the Fleet API. This is useful to come around rate limits.
To come around rate limits TSC can use BLE instead of the Fleet API. This requires <a href="https://github.com/pkuehnel/TeslaSolarCharger?tab=readme-ov-file#install-and-setup-ble-api" target="_blank">setting up a BLE API</a>.
</div>
@if (_pairingResults.TryGetValue(carBasicConfiguration.Vin, out var result))
{
Expand All @@ -54,7 +54,7 @@ else
Before you can test BLE access you must pair the car with TSC. This includes placing the card on your center console and confirming the new "phone key" on the car's screen.
</div>
<div>
After clicking the test button the front lights should flash. Note: The car needs to be awake for this test.
After clicking the test button the car 's current should be set to 7A. Note: The car needs to be awake for this test.
</div>
@if (_bleTestResults.TryGetValue(carBasicConfiguration.Vin, out var bleResult))
{
Expand All @@ -64,7 +64,7 @@ else
</p>
</div>
}
<RightAlignedButtonComponent IsLoading="_loadingVins.Contains(carBasicConfiguration.Vin)" OnButtonClicked="_ => TestBle(carBasicConfiguration.Vin)" ButtonText="Flash lights"></RightAlignedButtonComponent>
<RightAlignedButtonComponent IsLoading="_loadingVins.Contains(carBasicConfiguration.Vin)" OnButtonClicked="_ => TestBle(carBasicConfiguration.Vin)" ButtonText="Set to 7A"></RightAlignedButtonComponent>

</div>
}
Expand Down Expand Up @@ -114,10 +114,11 @@ else
{
_bleTestResults.Remove(vin);
_loadingVins.Add(vin);
var result = await HttpClient.GetFromJsonAsync<DtoBleResult>($"/api/Ble/FlashLights?vin={vin}").ConfigureAwait(false) ?? new DtoBleResult { Success = false, Message = "Could not deserialize message from TSC." };
var resultString = await HttpClient.GetStringAsync($"/api/Ble/SetAmp?vin={vin}&amps=7").ConfigureAwait(false);
var result = JsonConvert.DeserializeObject<DtoBleResult>(resultString) ?? new DtoBleResult { Success = false, Message = "Could not deserialize message from TSC." };
if(result.Success && string.IsNullOrWhiteSpace(result.Message))
{
result.Message = "Ble success seems to work. Please double check if lights were flashing.";
result.Message = "Ble access seems to work. Please double check if the charge current was set to 7A. Note: As TSC starts using BLE as soon as it is working you might see the 7A only for a short time as TSC changes it every 30 seconds by default.";
}
_bleTestResults[vin] = result;
_loadingVins.Remove(vin);
Expand Down
2 changes: 1 addition & 1 deletion TeslaSolarCharger/Client/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ else
<div>Car currently rate limited.</div>
<hr/>
<p>
Your car is rate limited by Tesla and TSC won't be able to set the charging power until @(car.RateLimitedUntil.Value.ToLocalTime().ToString("g")). For now there is no solution to this as Tesla implemented the rate limits without an option to pay for more. So even after the rate limit is over, TSC only has 50 requests. After the 50 requests within 24 hours, the rate limit is present again for the next 24 hours. As a workaround you can go to basic configuration, scroll down to advanced settings and set the Car power adjustment intervall to 1800 seconds. This leads to 50 calls per day max.<br/>
Your car is rate limited by Tesla and TSC won't be able to set the charging power until @(car.RateLimitedUntil.Value.ToLocalTime().ToString("g")). For now, the only solution for this is to set up a BLE device (see <a href="https://github.com/pkuehnel/TeslaSolarCharger?tab=readme-ov-file#install-and-setup-ble-api" target="_blank">here</a>). Currently there is no option from Tesla's side to pay for more requests per day. So even after the rate limit is over, TSC only has 50 requests for the next day. After the 50 requests within 24 hours, the rate limit is present again for the next 24 hours. Despite setting up the BLE API as a workaround, you can go to basic configuration, scroll down to advanced settings and set the Car power adjustment interval to 1800 seconds. This leads to 50 calls per day max but also means TSC can only adjust the charging power every 30 minutes.<br />
</p>

</div>
Expand Down
6 changes: 3 additions & 3 deletions TeslaSolarCharger/Server/Controllers/BleController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ public class BleController (IBleService bleService) : ApiBaseController
public Task<DtoBleResult> PairKey(string vin) => bleService.PairKey(vin);

[HttpGet]
public Task StartCharging(string vin) => bleService.StartCharging(vin);
public Task<DtoBleResult> StartCharging(string vin) => bleService.StartCharging(vin);

[HttpGet]
public Task StopCharging(string vin) => bleService.StopCharging(vin);
public Task<DtoBleResult> StopCharging(string vin) => bleService.StopCharging(vin);

[HttpGet]
public Task SetAmp(string vin, int amps) => bleService.SetAmp(vin, amps);
public Task<DtoBleResult> SetAmp(string vin, int amps) => bleService.SetAmp(vin, amps);

[HttpGet]
public Task<DtoBleResult> FlashLights(string vin) => bleService.FlashLights(vin);
Expand Down
6 changes: 3 additions & 3 deletions TeslaSolarCharger/Server/Services/Contracts/IBleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace TeslaSolarCharger.Server.Services.Contracts;

public interface IBleService
{
Task StartCharging(string vin);
Task StopCharging(string vin);
Task SetAmp(string vin, int amps);
Task<DtoBleResult> StartCharging(string vin);
Task<DtoBleResult> StopCharging(string vin);
Task<DtoBleResult> SetAmp(string vin, int amps);
Task<DtoBleResult> FlashLights(string vin);
Task<DtoBleResult> PairKey(string vin);
}
13 changes: 8 additions & 5 deletions TeslaSolarCharger/Server/Services/TeslaBleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class TeslaBleService(ILogger<TeslaBleService> logger,
ITeslamateApiService teslamateApiService,
ISettings settings) : IBleService
{
public async Task StartCharging(string vin)
public async Task<DtoBleResult> StartCharging(string vin)
{
logger.LogTrace("{method}({vin})", nameof(StartCharging), vin);
var request = new DtoBleRequest
Expand All @@ -25,24 +25,26 @@ public async Task StartCharging(string vin)
CommandName = "charging-start",
};
var result = await SendCommandToBle(request).ConfigureAwait(false);
return result;
}

public Task WakeUpCar(int carId)
{
throw new NotImplementedException();
}

public async Task StopCharging(string vin)
public async Task<DtoBleResult> StopCharging(string vin)
{
var request = new DtoBleRequest
{
Vin = vin,
CommandName = "charging-stop",
};
var result = await SendCommandToBle(request).ConfigureAwait(false);
return result;
}

public async Task SetAmp(string vin, int amps)
public async Task<DtoBleResult> SetAmp(string vin, int amps)
{
logger.LogTrace("{method}({vin}, {amps})", nameof(SetAmp), vin, amps);
var request = new DtoBleRequest
Expand All @@ -52,6 +54,7 @@ public async Task SetAmp(string vin, int amps)
Parameters = new List<string> { amps.ToString() },
};
var result = await SendCommandToBle(request).ConfigureAwait(false);
return result;
}

public async Task<DtoBleResult> FlashLights(string vin)
Expand All @@ -71,7 +74,7 @@ public async Task<DtoBleResult> PairKey(string vin)
var bleBaseUrl = configurationWrapper.BleBaseUrl();
if (string.IsNullOrWhiteSpace(bleBaseUrl))
{
return new DtoBleResult() { Message = "BLE Base Url is not set.", StatusCode = HttpStatusCode.BadRequest, Success = false, };
return new DtoBleResult() { Message = "BLE Base URL is not set. Set a BLE URL in your base configuration.", StatusCode = HttpStatusCode.BadRequest, Success = false, };
}

bleBaseUrl += "Pairing/PairCar";
Expand Down Expand Up @@ -119,7 +122,7 @@ private async Task<DtoBleResult> SendCommandToBle(DtoBleRequest request)
return new DtoBleResult()
{
Success = false,
Message = "BLE Base Url is not set.",
Message = "BLE Base URL is not set. Set a BLE URL in your base configuration.",
StatusCode = HttpStatusCode.BadRequest,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public class BaseConfigurationBase
public int? HomeBatteryChargingPower { get; set; }
public int? MaxCombinedCurrent { get; set; }
public int? MaxInverterAcPower { get; set; }
public string? BleApiBaseUrl { get; set; }

public FrontendConfiguration? FrontendConfiguration { get; set; }

Expand Down
8 changes: 6 additions & 2 deletions TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,12 @@ public string FleetApiClientId()

public string? BleBaseUrl()
{
var environmentVariableName = "BleBaseUrl";
var value = configuration.GetValue<string?>(environmentVariableName);
var value = GetBaseConfiguration().BleApiBaseUrl;
if (string.IsNullOrWhiteSpace(value))
{
var environmentVariableName = "BleBaseUrl";
value = configuration.GetValue<string?>(environmentVariableName);
}
if (!string.IsNullOrWhiteSpace(value))
{
if (!value.EndsWith("/"))
Expand Down

0 comments on commit 16c7fee

Please sign in to comment.