Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Set user defined maximum charging speed limit #33

Open
3 tasks done
ThijsNugteren opened this issue Aug 8, 2023 · 17 comments
Open
3 tasks done

Comments

@ThijsNugteren
Copy link

Checklist

  • I have filled out the template to the best of my ability.
  • This only contains 1 feature request (if you have multiple feature requests, open one feature request for each feature request).
  • This issue is not a duplicate feature request of previous feature requests.

Is your feature request related to a problem? Please describe.

Currently, the load balancer defines the maximum charging speed.
If the load balancer doesn't receive P1 data from the smart meter, a more conservative maximum charging speed is used.

However, it is not alway desired to charge with the maximum allowed charging speed by the load balancer.
For example, one would like to relate the maximum charging speed to the actual domestic PV power.
Or, one would like to be more grid / neighbour friendly by charging with a lower speed.

Describe the solution you'd like

It would be great if a user defined maximum charging speed limit could be set

Describe alternatives you've considered

It is OK if some constraints would be put on such functionality. For example, a limitation in how frequently one can update the user defined maximum charging speed (e.g. once every 15 minutes). Or, to use a lower limit on the user defined maximum charging speed, because of technical chargingpoint / electric vehicle constraints

Additional context

Part of a bigger picture, of dynamic charging based on energy prices and home energy management

@Floris272
Copy link
Contributor

Hi Thijs,
Thanks for your feature request! We have been thinking about something like this internally, But it will probably take some time before this is implemented.

@Github4post
Copy link

Floris thank you very much for making this bluecurrent integration. 👌
I am also very interested in this feature Thijs mentioned. My goal is to use as much solar power as possible to charge my car instead of deliver the Solar energy back to the energy company. During the charging process I want to control the charging current based on the power my home is using and the energy my PV is producing. With Solcast app I can see the expected energy the PV is going to produce in the future. In combination with the expected time needed to fully charge the car and when I need the car I can make a charging plan.
Can you already say when you expect this feature to control the charging current will be integrated?

@msomhorst
Copy link

@Floris272 has there been any progress on this issue in the meanwhile? I'm also interested in this feature: (temporarily) disabling the load balancer and adjust the maximum current based on the available solar energy. Would be great if this will be available on the Websocket API.

@RobertDeLeeuw
Copy link

@msomhorst If this is not added soon, I'm almost thinking of starting to mis-use the P1 port input to the load balancer. Simulate as if the house has a very high load so charging will slow down.

@dmagyar
Copy link

dmagyar commented Jan 10, 2025

@msomhorst If this is not added soon, I'm almost thinking of starting to mis-use the P1 port input to the load balancer. Simulate as if the house has a very high load so charging will slow down.

I second @RobertDeLeeuw on this - when can we expect this feature please? It's easy to hook up an esp32 to drive the p1 port of the charger but why hack it if we can just ask nicely? :)

@ThijsNugteren
Copy link
Author

@msomhorst If this is not added soon, I'm almost thinking of starting to mis-use the P1 port input to the load balancer. Simulate as if the house has a very high load so charging will slow down.

I second @RobertDeLeeuw on this - when can we expect this feature please? It's easy to hook up an esp32 to drive the p1 port of the charger but why hack it if we can just ask nicely? :)

Unfortunately @Floris272 is not working for Blue Current anymore... So it looks like this code is now unsupported. I also mailed and called Blue Current about this, without success.

Last year I wrote some code to misuse P1 data with a 'man in the middle' solution (ESP32 board), to fool the Blue Current load balancer. I succeeded in creating a valid (checksum) manipulated P1 message. Also, the Blue Current load balancer responds to this manipulated data, by adjusting the charging power. Nice! However, the adjusted charging power depends on both the actual charging power and the manipulated data message. Somehow I can't get the load balancer to react exactly the way I want it to. Sometimes it 'kills' the charging session and adjuststhe charging power to 0 kW, when my manipulated power (in the manipulated P1 message) is too high. Anyway, I a happy to share my code with you guys. Maybe you can make something out of it, if Blue Current isn't making a move itself...

@dmagyar
Copy link

dmagyar commented Jan 10, 2025

@msomhorst If this is not added soon, I'm almost thinking of starting to mis-use the P1 port input to the load balancer. Simulate as if the house has a very high load so charging will slow down.

I second @RobertDeLeeuw on this - when can we expect this feature please? It's easy to hook up an esp32 to drive the p1 port of the charger but why hack it if we can just ask nicely? :)

Unfortunately @Floris272 is not working for Blue Current anymore... So it looks like this code is now unsupported. I also mailed and called Blue Current about this, without success.

Last year I wrote some code to misuse P1 data with a 'man in the middle' solution (ESP32 board), to fool the Blue Current load balancer. I succeeded in creating a valid (checksum) manipulated P1 message. Also, the Blue Current load balancer responds to this manipulated data, by adjusting the charging power. Nice! However, the adjusted charging power depends on both the actual charging power and the manipulated data message. Somehow I can't get the load balancer to react exactly the way I want it to. Sometimes it 'kills' the charging session and adjuststhe charging power to 0 kW, when my manipulated power (in the manipulated P1 message) is too high. Anyway, I a happy to share my code with you guys. Maybe you can make something out of it, if Blue Current isn't making a move itself...

Please do share, I would be happy to poke around and see how mine reacts. I will also call Blue Current next week to get some emphasis on this. In their integration they have these power values but they are sensors and thus read-only. I'm wondering if it would just work by writing to the API.

@ThijsNugteren
Copy link
Author

Please do share, I would be happy to poke around and see how mine reacts. I will also call Blue Current next week to get some emphasis on this. In their integration they have these power values but they are sensors and thus read-only. I'm wondering if it would just work by writing to the API.

2024-01-10_blue_current_dynamic_charging_speed.zip

See attached. Forgive me my programming skills; code quality isn't on par with pro's. But it works.

Some explanation (see also the architecture picture in the ZIP file):

  • The Dutch Smart Meter uses a CRC checksum for error detection in P1 messages. When manipulating a P1 message, a new valid CRC checksum is calculated
  • P1 messages are being decoded and manipulated on a ESP 32 board. The board is connected to wifi. Manipulation parameters are received via MQTT
  • Instructions for manipulation are sent via MQTT, from python code in Home Assistant
  • If you want to check whether the manipulated P1 data is truly valid, you could hook it up to your ' smart meter data reader' (I am using SlimmeLezer+) and show the results in a HA dashboard. If the manipulated P1 data is not valid, SlimmeLezer+ will not accept and forward it to HA.

I am happy to assist, if you're having trouble getting it to work.

@dmagyar
Copy link

dmagyar commented Jan 10, 2025

Thanks for posting this. I have a HomeWizard P1 lezer too so I'll try to replicate your experiment and probably port the code over to esphome and native HA API. I played around with the websocket API they have and noticed some amp values - probably set by the installer. Like:

{"object":"CH_STATUS","evse_id": "XXX","socket_id": "1","data":{"actual_p1":0,"actual_p2":0,"actual_p3":0,"activity": "unavailable","actual_v1":0,"actual_v2":0,"actual_v3":0,"actual_kwh":       0.00,"max_usage":20,"smartcharging_max_usage":6,"max_offline":12,"offline_since": "","start_datetime": "20250110 15:04:35","stop_datetime": "20250110 15:24:11","total_cost":    0.68,"vehicle_status": "A","evse_id": "XXX"}}

That max_usage: 20 means it can use 20 amperes max (I have a 3x25A grid connection) but interestingly smartcharging_max_usage is set to 6 for whatever reason. It would be nice to get someone from BlueCurrent to help out...

@Github4post
Copy link

I found an interesting solution I want to use EVCC, URL: https://evcc.io/
Also an app for integration in HA is available.
Bluecurrent is not supported in EVCC and I contacted them to ask if they want to support this and they have no intention because they want to develop their own solution. I prefer open solutions and no vendor lock-in like Bluecurrent this is not the way to go anymore.

You triggered me about this P1 manipulation to the charger and I found an interesting P1 simulator URL: https://www.benefactus.nl/projecten-elektronica/project-slimme-meter/slimme-meter-simulator/

If it is possible to use this and implement this is EVCC we have a solution.

@dmagyar
Copy link

dmagyar commented Jan 10, 2025

Very possibly yes. I also digged aroud today and found that you can set the current with the internal webserver - but this is just a display value. curl 'http://ip-of-charger/cgi-bin/save_installation_data?current=16&phase=3&iphase=1' would set this to 3 phases and 16 amps. Unfortunately even when set to 3 amps it was happily charging at 5 and 11 kW as well depending what I set on the car. Let's see what a weekend of tinkering can achieve ;)

@dmagyar
Copy link

dmagyar commented Jan 14, 2025

A bit of an update; I had no RJ12 stuff in my workshop and I was not able to get it work with a stripped RJ11 cable - but now they have arrived and I have a working DSMR lezer using esphome and the level-shifter circuit. I've also noticed that if you disconnect the charger from the P1 port it throttles the charge back to 4kW. Next step is to create the emulator and be "man-in-the-middle" between my meter and the charger, essentially rewriting the power values while proxying everything else.

@ThijsNugteren
Copy link
Author

@dmagyar Once you get it to work, you will notice that the load balancer increases/decreases charging speed per 1A (= 3 phases x 230V = steps of 690W). You will also notice that it take around 20 seconds for the load balancer to respond to a new (manipulated) P1 message.

I am not entirely sure about the schematics for connecting the P1 input wires to the ESP32 board, and the ESP32 board with the load balancer input. I soldered it on the back side of the ESP 32 board and then glued it on a mount, without writing down the schematics... amature... I can peel the glue off and have a look, if you don't get it to work.

Good luck!

@RobertDeLeeuw
Copy link

I went a different route. I have taken a Raspberry PI, installed DHCP and DNS servers on it.
Add a second Ethernet port with a USB to Ethernet adapter. Configure it to fix IP Address 8.8.8.8 (Hardcoded DNS server in the BlueCurrent)
Let the DNS server point all the *.bluecurrent.nl sub-domains to 8.8.8.8.
I installed Mobility House Python OCPP library and created a simple OCPP 1.5 CPMS.
Reading the P1 port with the Raspberry PI and send OCPP DataTransfers, same as the BlueCurrent back-office does for the P1 port smart charging/load balancing.
I will try to write a better blog post soon.

@dmagyar
Copy link

dmagyar commented Jan 16, 2025

I went a different route. I have taken a Raspberry PI, installed DHCP and DNS servers on it. Add a second Ethernet port with a USB to Ethernet adapter. Configure it to fix IP Address 8.8.8.8 (Hardcoded DNS server in the BlueCurrent) Let the DNS server point all the *.bluecurrent.nl sub-domains to 8.8.8.8. I installed Mobility House Python OCPP library and created a simple OCPP 1.5 CPMS. Reading the P1 port with the Raspberry PI and send OCPP DataTransfers, same as the BlueCurrent back-office does for the P1 port smart charging/load balancing. I will try to write a better blog post soon.

This is very clever indeed but my company reimburses me with the charging costs (company car) so I need the back-office work normally. How did you trick the charging point to connect to your fake service instead of their own websocket API? Does it not validate TLS certificate chains?

@dmagyar
Copy link

dmagyar commented Jan 22, 2025

It took me some time but it's finally ready. I can arbitrarily change consumed amps/power and indeed control the charger. The code below needs a lot of improvements, I thought I share in case someone would like to repeat this after me. Also realised that the easiest/cheapest way to interface with the P1 port is to use a MAX3232 TTL to RS232 converter module (25 cents form Aliexpress lol). One module can handle both directions which is very convenient. Please let me know what you think about this and how should I improve it further.

# P1 SPOOFER - ESPHOME
#
# You will need an esp8266 boaard (d1_mini) a max3232 TTL<->RS232 converter board, a 15ohm resistor and 2x RJ12 wires/sockets. 
# Connect the TTL RX/TX pins to the max3232 module along with GND and +3.3v from the ESP. 
# Some cheap modules tend to overheat, use a 15 Ohm resistor in series with the 3.3v to limit current (and thus heat)
# Connect R1+5VPWR pins together on the RJ12 side (first and second pins) and connect the two GNDs as well. Do this for both sockets/plugs.
# Connect the receiving RJ12s DATA pin to the RX of the max3232 while the transmitting RJ12s DATA pin to the TX of the max3232
# Conect the joined GND pins together and also to the RS232 side of the max3232 module An additional capacitor between GND and 3.3v (470uF+) will greatly increase stability
# Make changes to the below code (passwords, wifi etc.) and install it to the ESP.
# This will dynamically proxy telegrams from the receiver side to the transmitter side while re-writing ampere and power values based on your input

substitutions:
  device_name: fake-p1
  device_ip: <your-desired-ip>
  friendly_name: fake-p1

esphome:
  name: ${device_name}
  friendly_name: ${device_name}

esp8266:
  board: d1_mini

mdns:
  
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  manual_ip:
    static_ip: ${device_ip}
    gateway: <gateway>
    subnet: <mask>
    dns1: <dns>

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "ESPHOME"
    password: "12345678"
      
api:
  reboot_timeout: 0s
  encryption:
    # regenerate api key
    key: "replace me"
    
ota:
  platform: esphome
  # also change this password
  password: "replace me" 

# you can remove this to conserve space
web_server:
  port: 80

logger:
  # Serial logging is disabled by setting the logger baud rate to 0.
  # Otherwise the logger will occupy the hardware UART, making it unavailable
  # for receiving smart meter data on pin D7 (GPIO13).
  baud_rate: 0
  level: INFO
  tx_buffer_size: 1024

uart:
  - id: p1_in
    rx_pin:
      number: D7
      inverted: false
    baud_rate: 115200
    rx_buffer_size: 1700
#    debug:
#      direction: RX
#      dummy_receiver: false
#      after:
#        delimiter: "\n"
#      sequence:
#        - lambda: UARTDebug::log_string(direction, bytes);    
  - id: p1_out
    tx_pin:
      number: D8
      inverted: false
    baud_rate: 115200  

dsmr:
  uart_id: p1_in
  max_telegram_length: 1700

number:
  - platform: template
    name: "L1 Fake Amps"
    optimistic: true
    id: l1_fake_amps
    min_value: 0
    max_value: 100
    step: 1
    initial_value: 0
    mode: box

  - platform: template
    name: "L2 Fake Amps"
    optimistic: true
    id: l2_fake_amps
    min_value: 0
    max_value: 100
    step: 1
    initial_value: 0
    mode: box

  - platform: template
    name: "L3 Fake Amps"
    optimistic: true
    id: l3_fake_amps
    min_value: 0
    max_value: 100
    step: 1
    initial_value: 0
    mode: box

text_sensor:

  - platform: dsmr
    telegram:
      name: "telegram"
      on_value:
        then:
              lambda: |-
                unsigned int crc = 0;
                char *buf = (char *)x.c_str();
                char *buf2 = strdup(buf);
                char *token;
                const char delim[] = "\n";

                int rbuflen = strlen(buf)+10;
                char *rbuf = (char *)malloc(rbuflen);
                memset(rbuf, 0, rbuflen);

                // recalculate fake power values
                int l1pwr = (int)(id(l1_voltage).state)*(int)(id(l1_fake_amps).state);
                int l2pwr = (int)(id(l2_voltage).state)*(int)(id(l2_fake_amps).state);
                int l3pwr = (int)(id(l3_voltage).state)*(int)(id(l3_fake_amps).state);
                int totalpwr = l1pwr+l2pwr+l3pwr;

                token = strtok(buf2, delim);
                int i = 0;
                int bpos = 0;
                while (token != NULL) {
                  bpos = strlen(rbuf);
                  if (!strncmp(token, "1-0:1.7.0", 9)) {
                    snprintf(rbuf+bpos, rbuflen, "1-0:1.7.0(%02d.%03d*kW)\r\n", (int)(totalpwr/1000), (int)(totalpwr-(int)(totalpwr/1000)*1000));
                  } else if (!strncmp(token, "1-0:21.7.0", 10)) {
                    snprintf(rbuf+bpos, rbuflen, "1-0:21.7.0(%02d.%03d*kW)\r\n", (int)(l1pwr/1000), (int)(l1pwr-(int)(l1pwr/1000)*1000));
                  } else if (!strncmp(token, "1-0:41.7.0", 10)) {
                    snprintf(rbuf+bpos, rbuflen, "1-0:41.7.0(%02d.%03d*kW)\r\n", (int)(l2pwr/1000), (int)(l2pwr-(int)(l2pwr/1000)*1000));
                  } else if (!strncmp(token, "1-0:61.7.0", 10)) {
                    snprintf(rbuf+bpos, rbuflen, "1-0:61.7.0(%02d.%03d*kW)\r\n", (int)(l3pwr/1000), (int)(l3pwr-(int)(l3pwr/1000)*1000));
                  } else if (!strncmp(token, "1-0:31.7.0", 10)) {
                    snprintf(rbuf+bpos, rbuflen, "1-0:31.7.0(%03d*A)\r\n", (int)id(l1_fake_amps).state);
                  } else if (!strncmp(token, "1-0:51.7.0", 10)) {
                    snprintf(rbuf+bpos, rbuflen, "1-0:51.7.0(%03d*A)\r\n", (int)id(l2_fake_amps).state);
                  } else if (!strncmp(token, "1-0:71.7.0", 10)) {
                    snprintf(rbuf+bpos, rbuflen, "1-0:71.7.0(%03d*A)\r\n", (int)id(l3_fake_amps).state);
                  } else {
                    strlcat(rbuf, token, rbuflen);
                    strlcat(rbuf, "\n", rbuflen);
                  }
                  token = strtok(NULL, delim);
                  i++;
                }
                free(buf2);

                for (int pos = 0; pos < strlen((const char *)rbuf)-6; pos++){
                  crc ^= (unsigned int)rbuf[pos];    // XOR byte into least sig. byte of crc

                  for (int i = 8; i != 0; i--) {    // Loop over each bit
                    if ((crc & 0x0001) != 0) {      // If the LSB is set
                      crc >>= 1;                    // Shift right and XOR 0xA001
                      crc ^= 0xA001;
                    }
                    else                            // Else LSB is not set
                      crc >>= 1;                    // Just shift right
                  }
                }
                //ESP_LOGI("dm", "original: \n###%s###", buf);
                //ESP_LOGI("dm", "RBUF CRC: %04X %c%c%c%c", crc, rbuf[strlen(rbuf)-6],rbuf[strlen(rbuf)-5],rbuf[strlen(rbuf)-4],rbuf[strlen(rbuf)-3]);
                sprintf((char *)(rbuf+strlen(rbuf)-6),"%04X\r\n", crc);
                ESP_LOGI("dm", "spoofed: \n###%s###", rbuf);

                p1_out->write_str(rbuf);
                free(rbuf);

sensor:
  - platform: dsmr
    energy_delivered_tariff1:
      name: ${friendly_name} Energy Delivered Tariff 1
      state_class: total_increasing
    energy_delivered_tariff2:
      name: ${friendly_name} Energy Delivered Tariff 2
      state_class: total_increasing
    energy_returned_tariff1:
      name: ${friendly_name} Energy Returned Tariff 1
    energy_returned_tariff2:
      name: ${friendly_name} Energy Returned Tariff 2
    power_delivered:
      name: ${friendly_name} Power Consumed
    power_returned:
      name: ${friendly_name} Power Returned
    electricity_failures:
      name: ${friendly_name} Electricity Failures
    electricity_long_failures:
      name: ${friendly_name} Electricity Long Failures
    voltage_l1:
      id: l1_voltage
      name: ${friendly_name} Voltage L1
    voltage_l2:
      id: l2_voltage
      name: ${friendly_name} Voltage L2
    voltage_l3:
      id: l3_voltage
      name: ${friendly_name} Voltage L3
    current_l1:
      name: ${friendly_name} Current L1
    current_l2:
      name: ${friendly_name} Current L2
    current_l3:
      name: ${friendly_name} Current L3
    power_delivered_l1:
      name: ${friendly_name} Power Delivered L1
    power_delivered_l2:
      name: ${friendly_name} Power Delivered L2
    power_delivered_l3:
      name: ${friendly_name} Power Delivered L3
    power_returned_l1:
      name: ${friendly_name} Power Returned L1
    power_returned_l2:
      name: ${friendly_name} Power Returned L2
    power_returned_l3:
      name: ${friendly_name} Power Returned L3
    gas_delivered:
      name: ${friendly_name} Gas Delivered

@dmagyar
Copy link

dmagyar commented Jan 22, 2025

Okay, it's time to give up. It does not work. I can manipulate the telegrams and all my other lezers detect them just fine (zonneplan, homewizard, esphome). But does not matter how I adjust the current (I've implemented a constant addition to the current value as well) it's not limiting charge. It stays at the rate I set on the car. I think an alternative approach would be to mess with the PWM signal between the car and the charger, telling a lower value to the car which controls the charge process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants