{"id":8259,"date":"2024-11-12T09:35:01","date_gmt":"2024-11-12T08:35:01","guid":{"rendered":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/2024\/11\/12\/pylontech-pv-battery-status-in-the-homeassistant\/"},"modified":"2025-01-05T18:25:28","modified_gmt":"2025-01-05T17:25:28","slug":"pylontech-pv-battery-status-in-the-homeassistant","status":"publish","type":"post","link":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/2024\/11\/12\/pylontech-pv-battery-status-in-the-homeassistant\/?lang=en","title":{"rendered":"Pylontech PV battery status in the HomeAssistant"},"content":{"rendered":"<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_8259\" class=\"pvc_stats all  \" data-element-id=\"8259\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n<p style=\"text-align: justify\">Anyone who has installed a photovoltaic system in their own home may even use an energy storage system. In this example, it is an off-grid system equipped with two modules from the manufacturer Pylontech. The Pylontech US3000C batteries have an output voltage of 48V. The nominal capacity is 3500Wh. The installed cells are LiFePO4 cells and the usable capacity is specified as 3374Wh according to the data sheet. The batteries are designed to be connected in parallel with other Pylontech batteries. The internally installed BMS (battery management system) communicates with the other Pylontech battery modules via a so-called \u201clink\u201d interface. A battery configured as a \u201cmaster\u201d handles the data exchange with the inverter. Here, Pylontech provides the CAN or RS485 bus as an interface. However, if you want information about the individual cells (voltages, currents, charges, temperatures, etc.), there is another interface on each module called \u201cConsole\u201d. This is an RS232 interface via which you can communicate directly with the battery&#8217;s BMS. This port is also used to update the firmware of the BMS. However, I STRONGLY advise against playing around with firmware updates and flash software. This is reserved for the manufacturer or the liable party.<\/p>\n<p style=\"text-align: justify\">However, as this interface also provides a lot of information about the cells installed in the battery, this is an interesting approach. Initially, I had a laptop connected to a terminal and was able to discover and monitor the individual cell voltages and, above all, the possibly different charge status of the modules connected in parallel. So I thought it would be a good idea to have this information available in my home automation system, where it could be visualized and used for control purposes.<\/p>\n<p style=\"text-align: justify\">As we geeks and technology enthusiasts are quite familiar with terms such as Homeassistant, Docker, Proxmox, HomeMatic, NodeRed etc., I thought that this data should also become entities in the Homeassistant. So a small new project was quickly created. My plan was to read the data from the serial interface and send it to the Home Assistant via MQTT.<\/p>\n<p style=\"text-align: justify\">But before I start disassembling the data strings that come out via the serial port, I&#8217;ll have a look at the search engines. Perhaps someone else has already dealt with this topic. And that&#8217;s exactly what happened. I found what I was looking for on GitHub under the term \u201cpylontec2mqtt\u201d. A project is hosted at <a href=\"https:\/\/github.com\/irekzielinski\/Pylontech-Battery-Monitoring\">https:\/\/github.com\/irekzielinski\/Pylontech-Battery-Monitoring<\/a> that uses ESP8266 to collect the serial data from the port and sends it to the Homeassistant server via MQTT and Wifi. A fork with a further development of this project can be found at <a href=\"https:\/\/github.com\/hidaba\/PylontechMonitoring.\">https:\/\/github.com\/hidaba\/PylontechMonitoring.<\/a><\/p>\n<p style=\"text-align: justify\">Why am I publishing the project here on the blog despite the simple replica? I have optimized the circuit a little and packed it into a layout and adapted the code a little. I would like to share the result here. It was important to me to have a sensible structure on a circuit board that is connected with a USB A-B cable for the power supply and a LAN-RJ45 cable for the data connection. I wanted to use a \u201csolid\u201d USB connector (not the fragile mini or micro USB connectors)<\/p>\n<p style=\"text-align: justify\">On a breadboard and with the usual development boards, I quickly \u201cknitted together\u201d a functional model so that I could adapt the software to it.<\/p>\n<div id=\"attachment_8001\" style=\"width: 484px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240102144240-scaled.jpg\"><img fetchpriority=\"high\" decoding=\"async\" aria-describedby=\"caption-attachment-8001\" class=\"wp-image-8000 size-large\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240102144240-1024x770.jpg\" alt=\"\" width=\"474\" height=\"356\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240102144240-1024x770.jpg 1024w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240102144240-300x226.jpg 300w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240102144240-768x578.jpg 768w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240102144240-1536x1155.jpg 1536w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240102144240-2048x1540.jpg 2048w\" sizes=\"(max-width: 474px) 100vw, 474px\" \/><\/a><p id=\"caption-attachment-8001\" class=\"wp-caption-text\">Functional sample on perforated grid<\/p><\/div>\n<p style=\"text-align: justify\">So I first created a circuit diagram from the sketches in the Git project. There is a \u201creal\u201d RS232 level at the \u201cConsole\u201d interface, which is converted to a 5V TTL via the MAX3232 IC. The BSS123 FET is used to realize a level converter to 3.3V for each of the RX and TX signals.<\/p>\n<div id=\"attachment_7993\" style=\"width: 484px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/SchematicPylan2mqtt-1024x706.png\"><img decoding=\"async\" aria-describedby=\"caption-attachment-7993\" class=\"wp-image-7992 size-large\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/SchematicPylan2mqtt-1024x706.png\" alt=\"\" width=\"474\" height=\"327\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/SchematicPylan2mqtt-1024x706.png 1024w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/SchematicPylan2mqtt-300x207.png 300w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/SchematicPylan2mqtt-768x529.png 768w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/SchematicPylan2mqtt-1536x1058.png 1536w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/SchematicPylan2mqtt-2048x1411.png 2048w\" sizes=\"(max-width: 474px) 100vw, 474px\" \/><\/a><p id=\"caption-attachment-7993\" class=\"wp-caption-text\">pylontec2mqtt schematic<\/p><\/div>\n<p style=\"text-align: justify\">The ESP8266 processes this 3.3V TTL level in the form of the Wemos D1Mini or WemosD1Pro development board, which is plugged onto the circuit board. I then packed the entire construction into a small plastic housing, which can be conveniently connected to the Pylontec and a USB power source via the LAN and USB cables.<\/p>\n<div id=\"attachment_8005\" style=\"width: 484px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout_tool-1024x631.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8005\" class=\"wp-image-8004 size-large\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout_tool-1024x631.jpg\" alt=\"\" width=\"474\" height=\"292\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout_tool-1024x631.jpg 1024w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout_tool-300x185.jpg 300w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout_tool-768x473.jpg 768w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout_tool.jpg 1261w\" sizes=\"(max-width: 474px) 100vw, 474px\" \/><\/a><p id=\"caption-attachment-8005\" class=\"wp-caption-text\">Layout preview in designtool<\/p><\/div>\n<p style=\"text-align: justify\">The layout design is shown in the picture above. The circuit board and the position of the components were checked again with the preview before production and then ordered from a trusted manufacturer.<\/p>\n<div id=\"attachment_8007\" style=\"width: 484px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout-1024x632.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8007\" class=\"wp-image-8006 size-large\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout-1024x632.jpg\" alt=\"\" width=\"474\" height=\"293\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout-1024x632.jpg 1024w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout-300x185.jpg 300w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout-768x474.jpg 768w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/layout.jpg 1026w\" sizes=\"(max-width: 474px) 100vw, 474px\" \/><\/a><p id=\"caption-attachment-8007\" class=\"wp-caption-text\">Preview of the circuit board before production<\/p><\/div>\n<p style=\"text-align: justify\">After barely two weeks of waiting, I had the empty circuit boards in my hands and was able to fit them with the components.<\/p>\n<div id=\"attachment_7997\" style=\"width: 484px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/1705007526004-906x1024.png\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-7997\" class=\"wp-image-7996 size-large\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/1705007526004-906x1024.png\" alt=\"\" width=\"474\" height=\"536\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/1705007526004-906x1024.png 906w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/1705007526004-266x300.png 266w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/1705007526004-768x868.png 768w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/1705007526004-1359x1536.png 1359w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/1705007526004.png 1555w\" sizes=\"(max-width: 474px) 100vw, 474px\" \/><\/a><p id=\"caption-attachment-7997\" class=\"wp-caption-text\">fully assembled circuit board<\/p><\/div>\n<p>The picture above shows the fully assembled board. The only thing missing here is the Wemos board with the ESP.<\/p>\n<div id=\"attachment_8003\" style=\"width: 484px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240104102508-scaled.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8003\" class=\"wp-image-8002 size-large\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240104102508-770x1024.jpg\" alt=\"\" width=\"474\" height=\"630\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240104102508-770x1024.jpg 770w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240104102508-226x300.jpg 226w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240104102508-768x1021.jpg 768w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240104102508-1155x1536.jpg 1155w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240104102508-1540x2048.jpg 1540w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/IMG20240104102508-scaled.jpg 1925w\" sizes=\"(max-width: 474px) 100vw, 474px\" \/><\/a><p id=\"caption-attachment-8003\" class=\"wp-caption-text\">Comparison between functional model and first \u201cproduction model\u201d<\/p><\/div>\n<p style=\"text-align: justify\">In the end, I plugged in a WemosD1 Pro, as this offers the option of connecting an external WiFi antenna and thus getting a reasonable wireless range.<\/p>\n<p style=\"text-align: justify\">After flashing the software and commissioning, the Wemos web server can be accessed at the IP address specified in the code. Here you can also check whether the Pylontech battery is communicating with the Wemos. The result then looks like this.<\/p>\n<div id=\"attachment_8014\" style=\"width: 484px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/pylonwebsite-1024x835.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8014\" class=\"wp-image-8013 size-large\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/pylonwebsite-1024x835.jpg\" alt=\"\" width=\"474\" height=\"387\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/pylonwebsite-1024x835.jpg 1024w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/pylonwebsite-300x245.jpg 300w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/pylonwebsite-768x627.jpg 768w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/pylonwebsite.jpg 1091w\" sizes=\"(max-width: 474px) 100vw, 474px\" \/><\/a><p id=\"caption-attachment-8014\" class=\"wp-caption-text\">Webseite of the WEMOS ESP<\/p><\/div>\n<p style=\"text-align: justify\">Here you can see that both battery modules are recognized correctly. The next step is to check whether messages are being sent via the MQTT protocol. The IP address of the MQTT broker must also be specified in the Wemo code. In my setup, I have set up the MQTT Explorer in the Home Assistant to be able to check the MQTT functions quickly and easily.<\/p>\n<div id=\"attachment_8016\" style=\"width: 902px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/mqttexplorer.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8016\" class=\"wp-image-8015 size-full\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/mqttexplorer.jpg\" alt=\"\" width=\"892\" height=\"569\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/mqttexplorer.jpg 892w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/mqttexplorer-300x191.jpg 300w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/mqttexplorer-768x490.jpg 768w\" sizes=\"(max-width: 892px) 100vw, 892px\" \/><\/a><p id=\"caption-attachment-8016\" class=\"wp-caption-text\">MQTT Explorer<\/p><\/div>\n<p style=\"text-align: justify\">The image above shows that the data also arrives correctly via MQTT. Now it is only necessary to create a sensor yaml file in the home assistant to make the topics available as entities. I have added the following code to configuration.yaml for this purpose:<\/p>\n<div class=\"code-wrapper\">\n<pre id=\"htmlText\" style=\"font-family: monospace;color: #000000;background-color: #f8f8ff;font-size: 11pt;border: 1px dashed #999999;padding: 5px;overflow: auto;width: 100%\"><code>mqtt:\r\n  sensor:\r\n#Pylontec Akku Serial Readout (ESP32 192.168.xxx.yyy)\r\n    - state_topic: \"ingmarsretro\/pylontec\/ESP_WiFi_RSSI\"\r\n      name: \"Pylontec_RSSI\"\r\n      unit_of_measurement: dB\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/availability\"\r\n      name: \"Pylontec_Status\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/currentDC\"\r\n      name: \"DC-Strom\"\r\n      unit_of_measurement: \"mA\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/getPowerDC\"  \r\n      name: \"getPower DC\"\r\n      unit_of_measurement: \"W\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/powerIN\"  \r\n      name: \"Power IN\"\r\n      unit_of_measurement: \"W\"  \r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/estPowerAC\"\r\n      name: \"Pylontec_estPowerAC\"\r\n      unit_of_measurement: Watt  \r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/soc\"\r\n      name: \"Pylontec_SOC\"\r\n      unit_of_measurement: \"%\"  \r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/temp\"\r\n      name: \"Pylontec_temperature\"\r\n      unit_of_measurement: \"\u00b0C\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/battery_count\"\r\n      name: \"Pylontec_BatteryCount\"\r\n      unit_of_measurement: \"pcs\"      \r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/base_state\"\r\n      name: \"Pylontec_BaseState\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/is_normal\"  \r\n      name: \"Pylontec_is_normal\"\r\n\r\n    - state_topic: \"ingmarsretro\/pylontec\/powerOUT\"\r\n      name: \"Pylontec_powerOUT\"\r\n      unit_of_measurement: Watt\r\n      \r\n# Pylontech battery module 0      \r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/0\/current\"\r\n      name: \"Pylontec_Battery0_current\"\r\n      unit_of_measurement: \"A\"\r\n\r\n    - state_topic: \"ingmarsretro\/pylontec\/0\/voltage\"\r\n      name: \"Pylontec_Battery0_voltage\"\r\n      unit_of_measurement: \"V\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/0\/soc\"\r\n      name: \"Pylontec_Battery0_soc\"\r\n      unit_of_measurement: \"%\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/0\/charging\"\r\n      name: \"Pylontec_Battery0_charging\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/0\/discharging\"\r\n      name: \"Pylontec_Battery0_discharging\"\r\n     \r\n    - state_topic: \"ingmarsretro\/pylontec\/0\/idle\"\r\n      name: \"Pylontec_Battery0_idle\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/0\/state\"\r\n      name: \"Pylontec_Battery0_state\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/0\/temp\"\r\n      name: \"Pylontec_Battery0_temp\"\r\n      unit_of_measurement: \"\u00b0C\"\r\n      \r\n# Pylontech battery module 1      \r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/1\/current\"\r\n      name: \"Pylontec_Battery1_current\"\r\n      unit_of_measurement: \"A\"\r\n\r\n    - state_topic: \"ingmarsretro\/pylontec\/1\/voltage\"\r\n      name: \"Pylontec_Battery1_voltage\"\r\n      unit_of_measurement: \"V\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/1\/soc\"\r\n      name: \"Pylontec_Battery1_soc\"\r\n      unit_of_measurement: \"%\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/1\/charging\"\r\n      name: \"Pylontec_Battery1_charging\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/1\/discharging\"\r\n      name: \"Pylontec_Battery1_discharging\"\r\n     \r\n    - state_topic: \"ingmarsretro\/pylontec\/1\/idle\"\r\n      name: \"Pylontec_Battery1_idle\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/1\/state\"\r\n      name: \"Pylontec_Battery1_state\"\r\n      \r\n    - state_topic: \"ingmarsretro\/pylontec\/1\/temp\"\r\n      name: \"Pylontec_Battery1_temp\"\r\n      unit_of_measurement: \"\u00b0C\"\r\n      \r\n<\/code><\/pre>\n<p><button id=\"copy-button\">Copy<\/button><\/p>\n<p style=\"text-align: justify\">On the Homeassistant website, the visualization could then look like this, for example:<\/p>\n<p><a href=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/haWAB-1024x547.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8046 size-large\" src=\"http:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/haWAB-1024x547.jpg\" alt=\"\" width=\"474\" height=\"253\" srcset=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/haWAB-1024x547.jpg 1024w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/haWAB-300x160.jpg 300w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/haWAB-768x410.jpg 768w, https:\/\/blog.fh-kaernten.at\/ingmarsretro\/files\/2024\/02\/haWAB.jpg 1341w\" sizes=\"(max-width: 474px) 100vw, 474px\" \/><\/a><\/p>\n<p style=\"text-align: justify\">Last but not least, I am posting the customized code below. The libraries required for compilation and further information can be found in the GitHub links above.<\/p>\n<div class=\"code-wrapper\">\n<pre id=\"htmlText\" style=\"font-family: monospace;color: #000000;background-color: #f8f8ff;font-size: 11pt;border: 1px dashed #999999;padding: 5px;overflow: auto;width: 100%\"><code>#include &lt;ESP8266WiFi.h&gt;\r\n#include &lt;ESP8266mDNS.h&gt;\r\n#include &lt;ArduinoOTA.h&gt;\r\n#include &lt;ESP8266WebServer.h&gt;\r\n#include &lt;circular_log.h&gt;\r\n#include &lt;ArduinoJson.h&gt;\r\n#include &lt;NTPClient.h&gt;\r\n#include &lt;ESP8266TimerInterrupt.h&gt;\r\n\r\n\/\/+++ START CONFIGURATION +++\r\n\r\n\/\/IMPORTANT: Specify your WIFI settings:\r\n#define WIFI_SSID \"wifiname\"\r\n#define WIFI_PASS deinpasswort1234\"\r\n#define WIFI_HOSTNAME \"mppsolar-pylontec\"\r\n\r\n\/\/Uncomment for static ip configuration\r\n#define STATIC_IP\r\n  IPAddress local_IP(192, 168, xxx, yyy);\r\n  IPAddress subnet(255, 255, 255, 0);\r\n  IPAddress gateway(192, 168, xxx, zzz);\r\n  IPAddress primaryDNS(192, 168, xxx, zzz);\r\n\r\n\/\/Uncomment for authentication page\r\n\/\/#define AUTHENTICATION\r\n\/\/set http Authentication\r\nconst char* www_username = \"admin\";\r\nconst char* www_password = \"password\";\r\n\r\n\r\n\/\/IMPORTANT: Uncomment this line if you want to enable MQTT (and fill correct MQTT_ values below):\r\n#define ENABLE_MQTT\r\n\r\n\/\/ Set offset time in seconds to adjust for your timezone, for example:\r\n\/\/ GMT +1 = 3600\r\n\/\/ GMT +1 = 7200\r\n\/\/ GMT +8 = 28800\r\n\/\/ GMT -1 = -3600\r\n\/\/ GMT 0 = 0\r\n#define GMT 3600\r\n\r\n\/\/NOTE 1: if you want to change what is pushed via MQTT - edit function: pushBatteryDataToMqtt.\r\n\/\/NOTE 2: MQTT_TOPIC_ROOT is where battery will push MQTT topics. For example \"soc\" will be pushed to: \"home\/grid_battery\/soc\"\r\n#define MQTT_SERVER        \"192.168.xx.broker\"\r\n#define MQTT_PORT          1883\r\n#define MQTT_USER          \"\"\r\n#define MQTT_PASSWORD      \"\"\r\n#define MQTT_TOPIC_ROOT    \"ingmarsretro\/pylontec\/\"  \/\/this is where mqtt data will be pushed\r\n#define MQTT_PUSH_FREQ_SEC 2  \/\/maximum mqtt update frequency in seconds\r\n\r\n\/\/+++   END CONFIGURATION +++\r\n\r\n#ifdef ENABLE_MQTT\r\n#include &lt;PubSubClient.h&gt;\r\nWiFiClient espClient;\r\nPubSubClient mqttClient(espClient);\r\n#endif \/\/ENABLE_MQTT\r\n\r\n\/\/text response\r\nchar g_szRecvBuff[7000];\r\n\r\nconst long utcOffsetInSeconds = GMT;\r\nchar daysOfTheWeek[7][12] = {\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"};\r\n\/\/ Define NTP Client to get time\r\nWiFiUDP ntpUDP;\r\nNTPClient timeClient(ntpUDP, \"pool.ntp.org\", utcOffsetInSeconds);\r\n\r\n\r\nESP8266WebServer server(80);\r\ncircular_log&lt;7000&gt; g_log;\r\nbool ntpTimeReceived = false;\r\nint g_baudRate = 0;\r\n\r\nvoid Log(const char* msg)\r\n{\r\n  g_log.Log(msg);\r\n}\r\n\r\n\/\/Define Interrupt Timer to Calculate Power meter every second (kWh)\r\n#define USING_TIM_DIV1 true                                             \/\/ for shortest and most accurate timer\r\nESP8266Timer ITimer;\r\nbool setInterval(unsigned long interval, timer_callback callback);      \/\/ interval (in microseconds)\r\n#define TIMER_INTERVAL_MS 1000\r\n\r\n\/\/Global Variables for the Power Meter - accessible from the calculating interrupt und from main\r\nunsigned long powerIN = 0;       \/\/WS gone in to the BAttery\r\nunsigned long powerOUT = 0;      \/\/WS gone out of the Battery\r\n\/\/Global Variables for the Power Meter - \u00dcberlauf\r\nunsigned long powerINWh = 0;       \/\/WS gone in to the BAttery\r\nunsigned long powerOUTWh = 0;      \/\/WS gone out of the Battery\r\n\r\nvoid setup() {\r\n  \r\n  memset(g_szRecvBuff, 0, sizeof(g_szRecvBuff)); \/\/clean variable\r\n  \r\n  pinMode(LED_BUILTIN, OUTPUT); \r\n  digitalWrite(LED_BUILTIN, HIGH);\/\/high is off\r\n  \r\n  \/\/ put your setup code here, to run once:\r\n  WiFi.mode(WIFI_STA);\r\n  WiFi.persistent(false); \/\/our credentialss are hardcoded, so we don't need ESP saving those each boot (will save on flash wear)\r\n  WiFi.hostname(WIFI_HOSTNAME);\r\n  #ifdef STATIC_IP\r\n     WiFi.config(local_IP, gateway, subnet, primaryDNS);\r\n  #endif\r\n  WiFi.begin(WIFI_SSID, WIFI_PASS);\r\n\r\n  for(int ix=0; ix&lt;10; ix++)\r\n  {\r\n    Log(\"Wait for WIFI Connection\");\r\n    if(WiFi.status() == WL_CONNECTED)\r\n    {\r\n      break;\r\n    }\r\n\r\n    delay(1000);\r\n  }\r\n\r\n  ArduinoOTA.setHostname(WIFI_HOSTNAME);\r\n  ArduinoOTA.begin();\r\n  server.on(\"\/\", handleRoot);\r\n  server.on(\"\/log\", handleLog);\r\n  server.on(\"\/req\", handleReq);\r\n  server.on(\"\/jsonOut\", handleJsonOut);\r\n  server.on(\"\/reboot\", [](){\r\n    #ifdef AUTHENTICATION\r\n    if (!server.authenticate(www_username, www_password)) {\r\n      return server.requestAuthentication();\r\n    }\r\n    #endif\r\n    ESP.restart();\r\n  });\r\n  \r\n  server.begin(); \r\n  \r\n  timeClient.begin();\r\n\r\n  \r\n#ifdef ENABLE_MQTT\r\n  mqttClient.setServer(MQTT_SERVER, MQTT_PORT);\r\n#endif\r\n\r\n  Log(\"Boot event\");\r\n  \r\n}\r\n\r\nvoid handleLog()\r\n{\r\n  #ifdef AUTHENTICATION\r\n  if (!server.authenticate(www_username, www_password)) {\r\n    return server.requestAuthentication();\r\n  } \r\n  #endif\r\n  server.send(200, \"text\/html\", g_log.c_str());\r\n}\r\n\r\nvoid switchBaud(int newRate)\r\n{\r\n  if(g_baudRate == newRate)\r\n  {\r\n    return;\r\n  }\r\n  \r\n  if(g_baudRate != 0)\r\n  {\r\n    Serial.flush();\r\n    delay(20);\r\n    Serial.end();\r\n    delay(20);\r\n  }\r\n\r\n  char szMsg[50];\r\n  snprintf(szMsg, sizeof(szMsg)-1, \"New baud: %d\", newRate);\r\n  Log(szMsg);\r\n  \r\n  Serial.begin(newRate);\r\n  g_baudRate = newRate;\r\n\r\n  delay(20);\r\n}\r\n\r\nvoid waitForSerial()\r\n{\r\n  for(int ix=0; ix&lt;150;ix++)\r\n  {\r\n    if(Serial.available()) break;\r\n    delay(10);\r\n  }\r\n}\r\n\r\nint readFromSerial()\r\n{\r\n  memset(g_szRecvBuff, 0, sizeof(g_szRecvBuff));\r\n  int recvBuffLen = 0;\r\n  bool foundTerminator = true;\r\n  \r\n  waitForSerial();\r\n  \r\n  while(Serial.available())\r\n  {\r\n    char szResponse[256] = \"\";\r\n    const int readNow = Serial.readBytesUntil('&gt;', szResponse, sizeof(szResponse)-1); \/\/all commands terminate with \"$$\\r\\n\\rpylon&gt;\" (no new line at the end)\r\n    if(readNow &gt; 0 &amp;&amp; \r\n       szResponse[0] != '\\0')\r\n    {\r\n      if(readNow + recvBuffLen + 1 &gt;= (int)(sizeof(g_szRecvBuff)))\r\n      {\r\n        Log(\"WARNING: Read too much data on the console!\");\r\n        break;\r\n      }\r\n      \r\n      strcat(g_szRecvBuff, szResponse);\r\n      recvBuffLen += readNow;\r\n\r\n      if(strstr(g_szRecvBuff, \"$$\\r\\n\\rpylon\"))\r\n      {\r\n        strcat(g_szRecvBuff, \"&gt;\"); \/\/readBytesUntil will skip this, so re-add\r\n        foundTerminator = true;\r\n        break; \/\/found end of the string\r\n      }\r\n\r\n      if(strstr(g_szRecvBuff, \"Press [Enter] to be continued,other key to exit\"))\r\n      {\r\n        \/\/we need to send new line character so battery continues the output\r\n        Serial.write(\"\\r\");\r\n      }\r\n\r\n      waitForSerial();\r\n    }\r\n  }\r\n\r\n  if(recvBuffLen &gt; 0 )\r\n  {\r\n    if(foundTerminator == false)\r\n    {\r\n      Log(\"Failed to find pylon&gt; terminator\");\r\n    }\r\n  }\r\n\r\n  return recvBuffLen;\r\n}\r\n\r\nbool readFromSerialAndSendResponse()\r\n{\r\n  const int recvBuffLen = readFromSerial();\r\n  if(recvBuffLen &gt; 0)\r\n  {\r\n    server.sendContent(g_szRecvBuff);\r\n    return true;\r\n  }\r\n\r\n  return false;\r\n}\r\n\r\nbool sendCommandAndReadSerialResponse(const char* pszCommand)\r\n{\r\n  switchBaud(115200);\r\n\r\n  if(pszCommand[0] != '\\0')\r\n  {\r\n    Serial.write(pszCommand);\r\n  }\r\n  Serial.write(\"\\n\");\r\n\r\n  const int recvBuffLen = readFromSerial();\r\n  if(recvBuffLen &gt; 0)\r\n  {\r\n    return true;\r\n  }\r\n\r\n  \/\/wake up console and try again:\r\n  wakeUpConsole();\r\n\r\n  if(pszCommand[0] != '\\0')\r\n  {\r\n    Serial.write(pszCommand);\r\n  }\r\n  Serial.write(\"\\n\");\r\n\r\n  return readFromSerial() &gt; 0;\r\n}\r\n\r\nvoid handleReq()\r\n{\r\n  #ifdef AUTHENTICATION\r\n  if (!server.authenticate(www_username, www_password)) {\r\n    return server.requestAuthentication();\r\n  }\r\n  #endif\r\n  bool respOK;\r\n  if(server.hasArg(\"code\") == false)\r\n  {\r\n    respOK = sendCommandAndReadSerialResponse(\"\");\r\n  }\r\n  else\r\n  {\r\n    respOK = sendCommandAndReadSerialResponse(server.arg(\"code\").c_str());\r\n  }\r\n\r\n  handleRoot();\r\n}\r\n\r\n\r\n\r\nvoid handleJsonOut()\r\n{\r\n  #ifdef AUTHENTICATION\r\n  if (!server.authenticate(www_username, www_password)) {\r\n    return server.requestAuthentication();\r\n  }\r\n  #endif\r\n  if(sendCommandAndReadSerialResponse(\"pwr\") == false)\r\n  {\r\n    server.send(500, \"text\/plain\", \"Failed to get response to 'pwr' command\");\r\n    return;\r\n  }\r\n\r\n  parsePwrResponse(g_szRecvBuff);\r\n  prepareJsonOutput(g_szRecvBuff, sizeof(g_szRecvBuff));\r\n  server.send(200, \"application\/json\", g_szRecvBuff);\r\n}\r\n\r\nvoid handleRoot() {\r\n  #ifdef AUTHENTICATION\r\n  if (!server.authenticate(www_username, www_password)) {\r\n    return server.requestAuthentication();\r\n  }\r\n  #endif\r\n  timeClient.update(); \/\/get ntp datetime\r\n  unsigned long days = 0, hours = 0, minutes = 0;\r\n  unsigned long val = os_getCurrentTimeSec();\r\n  days = val \/ (3600*24);\r\n  val -= days * (3600*24);\r\n  hours = val \/ 3600;\r\n  val -= hours * 3600;\r\n  minutes = val \/ 60;\r\n  val -= minutes*60;\r\n\r\n  time_t epochTime = timeClient.getEpochTime();\r\n  String formattedTime = timeClient.getFormattedTime();\r\n  \/\/Get a time structure\r\n  struct tm *ptm = gmtime ((time_t *)&amp;epochTime); \r\n  int currentMonth = ptm-&gt;tm_mon+1;\r\n\r\n  static char szTmp[9500] = \"\";  \r\n  long timezone= GMT \/ 3600;\r\n  snprintf(szTmp, sizeof(szTmp)-1, \"&lt;html&gt;&lt;b&gt;Pylontech Battery&lt;\/b&gt;&lt;br&gt;Time GMT: %s (%s %d)&lt;br&gt;Uptime: %02d:%02d:%02d.%02d&lt;br&gt;&lt;br&gt;free heap: %u&lt;br&gt;Wifi RSSI: %d&lt;BR&gt;Wifi SSID: %s\", \r\n            formattedTime, \"GMT \", timezone,\r\n            (int)days, (int)hours, (int)minutes, (int)val, \r\n            ESP.getFreeHeap(), WiFi.RSSI(), WiFi.SSID().c_str());\r\n\r\n  strncat(szTmp, \"&lt;BR&gt;&lt;a href='\/log'&gt;Runtime log&lt;\/a&gt;&lt;HR&gt;\", sizeof(szTmp)-1);\r\n  strncat(szTmp, \"&lt;form action='\/req' method='get'&gt;Command:&lt;input type='text' name='code'\/&gt;&lt;input type='submit'&gt; &lt;a href='\/req?code=pwr'&gt;PWR&lt;\/a&gt; | &lt;a href='\/req?code=pwr%201'&gt;Power 1&lt;\/a&gt; |  &lt;a href='\/req?code=pwr%202'&gt;Power 2&lt;\/a&gt; | &lt;a href='\/req?code=pwr%203'&gt;Power 3&lt;\/a&gt; | &lt;a href='\/req?code=pwr%204'&gt;Power 4&lt;\/a&gt; | &lt;a href='\/req?code=help'&gt;Help&lt;\/a&gt; | &lt;a href='\/req?code=log'&gt;Event Log&lt;\/a&gt; | &lt;a href='\/req?code=time'&gt;Time&lt;\/a&gt;&lt;br&gt;\", sizeof(szTmp)-1);\r\n  \/\/strncat(szTmp, \"&lt;form action='\/req' method='get'&gt;Command:&lt;input type='text' name='code'\/&gt;&lt;input type='submit'&gt;&lt;a href='\/req?code=pwr'&gt;Power&lt;\/a&gt; | &lt;a href='\/req?code=help'&gt;Help&lt;\/a&gt; | &lt;a href='\/req?code=log'&gt;Event Log&lt;\/a&gt; | &lt;a href='\/req?code=time'&gt;Time&lt;\/a&gt;&lt;br&gt;\", sizeof(szTmp)-1);\r\n  strncat(szTmp, \"&lt;textarea rows='80' cols='180'&gt;\", sizeof(szTmp)-1);\r\n  \/\/strncat(szTmp, \"&lt;textarea rows='45' cols='180'&gt;\", sizeof(szTmp)-1);\r\n  strncat(szTmp, g_szRecvBuff, sizeof(szTmp)-1);\r\n  strncat(szTmp, \"&lt;\/textarea&gt;&lt;\/form&gt;\", sizeof(szTmp)-1);\r\n  strncat(szTmp, \"&lt;\/html&gt;\", sizeof(szTmp)-1);\r\n  \/\/send page\r\n  server.send(200, \"text\/html\", szTmp);\r\n}\r\n\r\nunsigned long os_getCurrentTimeSec()\r\n{\r\n  static unsigned int wrapCnt = 0;\r\n  static unsigned long lastVal = 0;\r\n  unsigned long currentVal = millis();\r\n\r\n  if(currentVal &lt; lastVal)\r\n  {\r\n    wrapCnt++;\r\n  }\r\n\r\n  lastVal = currentVal;\r\n  unsigned long seconds = currentVal\/1000;\r\n  \r\n  \/\/millis will wrap each 50 days, as we are interested only in seconds, let's keep the wrap counter\r\n  return (wrapCnt*4294967) + seconds;\r\n}\r\n\r\nvoid wakeUpConsole()\r\n{\r\n  switchBaud(1200);\r\n\r\n  \/\/byte wakeUpBuff[] = {0x7E, 0x32, 0x30, 0x30, 0x31, 0x34, 0x36, 0x38, 0x32, 0x43, 0x30, 0x30, 0x34, 0x38, 0x35, 0x32, 0x30, 0x46, 0x43, 0x43, 0x33, 0x0D};\r\n  \/\/Serial.write(wakeUpBuff, sizeof(wakeUpBuff));\r\n  Serial.write(\"~20014682C0048520FCC3\\r\");\r\n  delay(1000);\r\n\r\n  byte newLineBuff[] = {0x0E, 0x0A};\r\n  switchBaud(115200);\r\n  \r\n  for(int ix=0; ix&lt;10; ix++)\r\n  {\r\n    Serial.write(newLineBuff, sizeof(newLineBuff));\r\n    delay(1000);\r\n\r\n    if(Serial.available())\r\n    {\r\n      while(Serial.available())\r\n      {\r\n        Serial.read();\r\n      }\r\n      \r\n      break;\r\n    }\r\n  }\r\n}\r\n\r\n#define MAX_PYLON_BATTERIES 8\r\n\r\nstruct pylonBattery\r\n{\r\n  bool isPresent;\r\n  long  soc;     \/\/Coulomb in %\r\n  long  voltage; \/\/in mW\r\n  long  current; \/\/in mA, negative value is discharge\r\n  long  tempr;   \/\/temp of case or BMS?\r\n  long  cellTempLow;\r\n  long  cellTempHigh;\r\n  long  cellVoltLow;\r\n  long  cellVoltHigh;\r\n  char baseState[9];    \/\/Charge | Dischg | Idle\r\n  char voltageState[9]; \/\/Normal\r\n  char currentState[9]; \/\/Normal\r\n  char tempState[9];    \/\/Normal\r\n  char time[20];        \/\/2019-06-08 04:00:29\r\n  char b_v_st[9];       \/\/Normal  (battery voltage?)\r\n  char b_t_st[9];       \/\/Normal  (battery temperature?)\r\n\r\n  bool isCharging()    const { return strcmp(baseState, \"Charge\")   == 0; }\r\n  bool isDischarging() const { return strcmp(baseState, \"Dischg\")   == 0; }\r\n  bool isIdle()        const { return strcmp(baseState, \"Idle\")     == 0; }\r\n  bool isBalancing()   const { return strcmp(baseState, \"Balance\")  == 0; }\r\n  \r\n\r\n  bool isNormal() const\r\n  {\r\n    if(isCharging()    == false &amp;&amp;\r\n       isDischarging() == false &amp;&amp;\r\n       isIdle()        == false &amp;&amp;\r\n       isBalancing()   == false)\r\n    {\r\n      return false; \/\/base state looks wrong!\r\n    }\r\n\r\n    return  strcmp(voltageState, \"Normal\") == 0 &amp;&amp;\r\n            strcmp(currentState, \"Normal\") == 0 &amp;&amp;\r\n            strcmp(tempState,    \"Normal\") == 0 &amp;&amp;\r\n            strcmp(b_v_st,       \"Normal\") == 0 &amp;&amp;\r\n            strcmp(b_t_st,       \"Normal\") == 0 ;\r\n  }\r\n};\r\n\r\nstruct batteryStack\r\n{\r\n  int batteryCount;\r\n  int soc;  \/\/in %, if charging: average SOC, otherwise: lowest SOC\r\n  int temp; \/\/in mC, if highest temp is &gt; 15C, this will show the highest temp, otherwise the lowest\r\n  long currentDC;    \/\/mAh current going in or out of the battery\r\n  long avgVoltage;    \/\/in mV\r\n  char baseState[9];  \/\/Charge | Dischg | Idle | Balance | Alarm!\r\n\r\n  \r\n  pylonBattery batts[MAX_PYLON_BATTERIES];\r\n\r\n  bool isNormal() const\r\n  {\r\n    for(int ix=0; ix&lt;MAX_PYLON_BATTERIES; ix++)\r\n    {\r\n      if(batts[ix].isPresent &amp;&amp; \r\n         batts[ix].isNormal() == false)\r\n      {\r\n        return false;\r\n      }\r\n    }\r\n\r\n    return true;\r\n  }\r\n\r\n  \/\/in Wh\r\n  long getPowerDC() const\r\n  {\r\n    return (long)(((double)currentDC\/1000.0)*((double)avgVoltage\/1000.0));\r\n  }\r\n\r\n  \/\/ power in Wh in charge\r\n  float powerIN() const\r\n  {\r\n    if (currentDC &gt; 0) {\r\n       return (float)(((double)currentDC\/1000.0)*((double)avgVoltage\/1000.0));\r\n    } else {\r\n       return (float)(0);\r\n    }\r\n  }\r\n  \r\n  \/\/ power in Wh in discharge\r\n  float powerOUT() const\r\n  {\r\n    if (currentDC &lt; 0) {\r\n       return (float)(((double)currentDC\/1000.0)*((double)avgVoltage\/1000.0)*-1);\r\n    } else {\r\n       return (float)(0);\r\n    }\r\n  }\r\n\r\n  \/\/Wh estimated current on AC side (taking into account Sofar ME3000SP losses)\r\n  long getEstPowerAc() const\r\n  {\r\n    double powerDC = (double)getPowerDC();\r\n    if(powerDC == 0)\r\n    {\r\n      return 0;\r\n    }\r\n    else if(powerDC &lt; 0)\r\n    {\r\n      \/\/we are discharging, on AC side we will see less power due to losses\r\n      if(powerDC &lt; -1000)\r\n      {\r\n        return (long)(powerDC*0.94);\r\n      }\r\n      else if(powerDC &lt; -600)\r\n      {\r\n        return (long)(powerDC*0.90);\r\n      }\r\n      else\r\n      {\r\n        return (long)(powerDC*0.87);\r\n      }\r\n    }\r\n    else\r\n    {\r\n      \/\/we are charging, on AC side we will have more power due to losses\r\n      if(powerDC &gt; 1000)\r\n      {\r\n        return (long)(powerDC*1.06);\r\n      }\r\n      else if(powerDC &gt; 600)\r\n      {\r\n        return (long)(powerDC*1.1);\r\n      }\r\n      else\r\n      {\r\n        return (long)(powerDC*1.13);\r\n      }\r\n    }\r\n  }\r\n};\r\n\r\nbatteryStack g_stack;\r\n\r\n\r\nlong extractInt(const char* pStr, int pos)\r\n{\r\n  return atol(pStr+pos);\r\n}\r\n\r\nvoid extractStr(const char* pStr, int pos, char* strOut, int strOutSize)\r\n{\r\n  strOut[strOutSize-1] = '\\0';\r\n  strncpy(strOut, pStr+pos, strOutSize-1);\r\n  strOutSize--;\r\n  \r\n  \r\n  \/\/trim right\r\n  while(strOutSize &gt; 0)\r\n  {\r\n    if(isspace(strOut[strOutSize-1]))\r\n    {\r\n      strOut[strOutSize-1] = '\\0';\r\n    }\r\n    else\r\n    {\r\n      break;\r\n    }\r\n\r\n    strOutSize--;\r\n  }\r\n}\r\n\r\n\/* Output has mixed \\r and \\r\\n\r\npwr\r\n\r\n@\r\n\r\nPower Volt   Curr   Tempr  Tlow   Thigh  Vlow   Vhigh  Base.St  Volt.St  Curr.St  Temp.St  Coulomb  Time                 B.V.St   B.T.St  \r\n\r\n1     49735  -1440  22000  19000  19000  3315   3317   Dischg   Normal   Normal   Normal   93%      2019-06-08 04:00:30  Normal   Normal  \r\n\r\n....   \r\n\r\n8     -      -      -      -      -      -      -      Absent   -        -        -        -        -                    -        -       \r\n\r\nCommand completed successfully\r\n\r\n$$\r\n\r\npylon\r\n*\/\r\nbool parsePwrResponse(const char* pStr)\r\n{\r\n  if(strstr(pStr, \"Command completed successfully\") == NULL)\r\n  {\r\n    return false;\r\n  }\r\n  \r\n  int chargeCnt    = 0;\r\n  int dischargeCnt = 0;\r\n  int idleCnt      = 0;\r\n  int alarmCnt     = 0;\r\n  int socAvg       = 0;\r\n  int socLow       = 0;\r\n  int tempHigh     = 0;\r\n  int tempLow      = 0;\r\n\r\n  memset(&amp;g_stack, 0, sizeof(g_stack));\r\n\r\n  for(int ix=0; ix&lt;MAX_PYLON_BATTERIES; ix++)\r\n  {\r\n    char szToFind[32] = \"\";\r\n    snprintf(szToFind, sizeof(szToFind)-1, \"\\r\\r\\n%d     \", ix+1);\r\n\r\n    const char* pLineStart = strstr(pStr, szToFind);\r\n    if(pLineStart == NULL)\r\n    {\r\n      return false;\r\n    }\r\n\r\n    pLineStart += 3; \/\/move past \\r\\r\\n\r\n\r\n    extractStr(pLineStart, 55, g_stack.batts[ix].baseState, sizeof(g_stack.batts[ix].baseState));\r\n    if(strcmp(g_stack.batts[ix].baseState, \"Absent\") == 0)\r\n    {\r\n      g_stack.batts[ix].isPresent = false;\r\n    }\r\n    else\r\n    {\r\n      g_stack.batts[ix].isPresent = true;\r\n      extractStr(pLineStart, 64, g_stack.batts[ix].voltageState, sizeof(g_stack.batts[ix].voltageState));\r\n      extractStr(pLineStart, 73, g_stack.batts[ix].currentState, sizeof(g_stack.batts[ix].currentState));\r\n      extractStr(pLineStart, 82, g_stack.batts[ix].tempState, sizeof(g_stack.batts[ix].tempState));\r\n      extractStr(pLineStart, 100, g_stack.batts[ix].time, sizeof(g_stack.batts[ix].time));\r\n      extractStr(pLineStart, 121, g_stack.batts[ix].b_v_st, sizeof(g_stack.batts[ix].b_v_st));\r\n      extractStr(pLineStart, 130, g_stack.batts[ix].b_t_st, sizeof(g_stack.batts[ix].b_t_st));\r\n      g_stack.batts[ix].voltage = extractInt(pLineStart, 6);\r\n      g_stack.batts[ix].current = extractInt(pLineStart, 13);\r\n      g_stack.batts[ix].tempr   = extractInt(pLineStart, 20);\r\n      g_stack.batts[ix].cellTempLow    = extractInt(pLineStart, 27);\r\n      g_stack.batts[ix].cellTempHigh   = extractInt(pLineStart, 34);\r\n      g_stack.batts[ix].cellVoltLow    = extractInt(pLineStart, 41);\r\n      g_stack.batts[ix].cellVoltHigh   = extractInt(pLineStart, 48);\r\n      g_stack.batts[ix].soc            = extractInt(pLineStart, 91);\r\n\r\n      \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Post-process \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n      g_stack.batteryCount++;\r\n      g_stack.currentDC += g_stack.batts[ix].current;\r\n      g_stack.avgVoltage += g_stack.batts[ix].voltage;\r\n      socAvg += g_stack.batts[ix].soc;\r\n\r\n      if(g_stack.batts[ix].isNormal() == false){ alarmCnt++; }\r\n      else if(g_stack.batts[ix].isCharging()){chargeCnt++;}\r\n      else if(g_stack.batts[ix].isDischarging()){dischargeCnt++;}\r\n      else if(g_stack.batts[ix].isIdle()){idleCnt++;}\r\n      else{ alarmCnt++; } \/\/should not really happen!\r\n\r\n      if(g_stack.batteryCount == 1)\r\n      {\r\n        socLow = g_stack.batts[ix].soc;\r\n        tempLow  = g_stack.batts[ix].cellTempLow;\r\n        tempHigh = g_stack.batts[ix].cellTempHigh;\r\n      }\r\n      else\r\n      {\r\n        if(socLow &gt; g_stack.batts[ix].soc){socLow = g_stack.batts[ix].soc;}\r\n        if(tempHigh &lt; g_stack.batts[ix].cellTempHigh){tempHigh = g_stack.batts[ix].cellTempHigh;}\r\n        if(tempLow &gt; g_stack.batts[ix].cellTempLow){tempLow = g_stack.batts[ix].cellTempLow;}\r\n      }\r\n      \r\n    }\r\n  }\r\n\r\n  \/\/now update stack state:\r\n  g_stack.avgVoltage \/= g_stack.batteryCount;\r\n  g_stack.soc = socLow;\r\n\r\n  if(tempHigh &gt; 15000) \/\/15C\r\n  {\r\n    g_stack.temp = tempHigh; \/\/in the summer we highlight the warmest cell\r\n  }\r\n  else\r\n  {\r\n    g_stack.temp = tempLow; \/\/in the winter we focus on coldest cell\r\n  }\r\n\r\n  if(alarmCnt &gt; 0)\r\n  {\r\n    strcpy(g_stack.baseState, \"Alarm!\");\r\n  }\r\n  else if(chargeCnt == g_stack.batteryCount)\r\n  {\r\n    strcpy(g_stack.baseState, \"Charge\");\r\n    g_stack.soc = (int)(socAvg \/ g_stack.batteryCount);\r\n  }\r\n  else if(dischargeCnt == g_stack.batteryCount)\r\n  {\r\n    strcpy(g_stack.baseState, \"Dischg\");\r\n  }\r\n  else if(idleCnt == g_stack.batteryCount)\r\n  {\r\n    strcpy(g_stack.baseState, \"Idle\");\r\n  }\r\n  else\r\n  {\r\n    strcpy(g_stack.baseState, \"Balance\");\r\n  }\r\n\r\n\r\n  return true;\r\n}\r\n\r\nvoid prepareJsonOutput(char* pBuff, int buffSize)\r\n{\r\n  memset(pBuff, 0, buffSize);\r\n  snprintf(pBuff, buffSize-1, \"{\\\"soc\\\": %d, \\\"temp\\\": %d, \\\"currentDC\\\": %ld, \\\"avgVoltage\\\": %ld, \\\"baseState\\\": \\\"%s\\\", \\\"batteryCount\\\": %d, \\\"powerDC\\\": %ld, \\\"estPowerAC\\\": %ld, \\\"isNormal\\\": %s}\", g_stack.soc, \r\n                                                                                                                                                                                                            g_stack.temp, \r\n                                                                                                                                                                                                            g_stack.currentDC, \r\n                                                                                                                                                                                                            g_stack.avgVoltage, \r\n                                                                                                                                                                                                            g_stack.baseState, \r\n                                                                                                                                                                                                            g_stack.batteryCount, \r\n                                                                                                                                                                                                            g_stack.getPowerDC(), \r\n                                                                                                                                                                                                            g_stack.getEstPowerAc(),\r\n                                                                                                                                                                                                            g_stack.isNormal() ? \"true\" : \"false\");\r\n}\r\n\r\nvoid loop() {\r\n#ifdef ENABLE_MQTT\r\n  mqttLoop();\r\n#endif\r\n  \r\n  ArduinoOTA.handle();\r\n  server.handleClient();\r\n\r\n  \/\/if there are bytes availbe on serial here - it's unexpected\r\n  \/\/when we send a command to battery, we read whole response\r\n  \/\/if we get anything here anyways - we will log it\r\n  int bytesAv = Serial.available();\r\n  if(bytesAv &gt; 0)\r\n  {\r\n    if(bytesAv &gt; 63)\r\n    {\r\n      bytesAv = 63;\r\n    }\r\n    \r\n    char buff[64+4] = \"RCV:\";\r\n    if(Serial.readBytes(buff+4, bytesAv) &gt; 0)\r\n    {\r\n      digitalWrite(LED_BUILTIN, LOW);\r\n      delay(5);\r\n      digitalWrite(LED_BUILTIN, HIGH);\/\/high is off\r\n\r\n      Log(buff);\r\n    }\r\n  }\r\n}\r\n\r\n#ifdef ENABLE_MQTT\r\n#define ABS_DIFF(a, b) (a &gt; b ? a-b : b-a)\r\nvoid mqtt_publish_f(const char* topic, float newValue, float oldValue, float minDiff, bool force)\r\n{\r\n  char szTmp[16] = \"\";\r\n  snprintf(szTmp, 15, \"%.2f\", newValue);\r\n  if(force || ABS_DIFF(newValue, oldValue) &gt; minDiff)\r\n  {\r\n    mqttClient.publish(topic, szTmp, false);\r\n  }\r\n}\r\n\r\nvoid mqtt_publish_i(const char* topic, int newValue, int oldValue, int minDiff, bool force)\r\n{\r\n  char szTmp[16] = \"\";\r\n  snprintf(szTmp, 15, \"%d\", newValue);\r\n  if(force || ABS_DIFF(newValue, oldValue) &gt; minDiff)\r\n  {\r\n    mqttClient.publish(topic, szTmp, false);\r\n  }\r\n}\r\n\r\nvoid mqtt_publish_s(const char* topic, const char* newValue, const char* oldValue, bool force)\r\n{\r\n  if(force || strcmp(newValue, oldValue) != 0)\r\n  {\r\n    mqttClient.publish(topic, newValue, false);\r\n  }\r\n}\r\n\r\nvoid pushBatteryDataToMqtt(const batteryStack&amp; lastSentData, bool forceUpdate \/* if true - we will send all data regardless if it's the same *\/)\r\n{\r\n  mqtt_publish_f(MQTT_TOPIC_ROOT \"soc\",          g_stack.soc,                lastSentData.soc,                0, forceUpdate);\r\n  mqtt_publish_f(MQTT_TOPIC_ROOT \"temp\",         (float)g_stack.temp\/1000.0, (float)lastSentData.temp\/1000.0, 0.1, forceUpdate);\r\n  mqtt_publish_i(MQTT_TOPIC_ROOT \"currentDC\",    g_stack.currentDC,          lastSentData.currentDC,          1, forceUpdate);\r\n  mqtt_publish_i(MQTT_TOPIC_ROOT \"estPowerAC\",   g_stack.getEstPowerAc(),    lastSentData.getEstPowerAc(),   10, forceUpdate);\r\n  mqtt_publish_i(MQTT_TOPIC_ROOT \"battery_count\",g_stack.batteryCount,       lastSentData.batteryCount,       0, forceUpdate);\r\n  mqtt_publish_s(MQTT_TOPIC_ROOT \"base_state\",   g_stack.baseState,          lastSentData.baseState            , forceUpdate);\r\n  mqtt_publish_i(MQTT_TOPIC_ROOT \"is_normal\",    g_stack.isNormal() ? 1:0,   lastSentData.isNormal() ? 1:0,   0, forceUpdate);\r\n  mqtt_publish_i(MQTT_TOPIC_ROOT \"getPowerDC\",   g_stack.getPowerDC(),       lastSentData.getPowerDC(),       1, forceUpdate);\r\n  mqtt_publish_i(MQTT_TOPIC_ROOT \"powerIN\",      g_stack.powerIN(),          lastSentData.powerIN(),          1, forceUpdate);\r\n  mqtt_publish_i(MQTT_TOPIC_ROOT \"powerOUT\",     g_stack.powerOUT(),         lastSentData.powerOUT(),         1, forceUpdate);\r\n\r\n  \/\/ publishing details\r\n  for (int ix = 0; ix &lt; g_stack.batteryCount; ix++) {\r\n    char ixBuff[50];\r\n    String ixBattStr = MQTT_TOPIC_ROOT + String(ix) + \"\/voltage\";\r\n    ixBattStr.toCharArray(ixBuff, 50);\r\n    mqtt_publish_f(ixBuff, g_stack.batts[ix].voltage \/ 1000.0, lastSentData.batts[ix].voltage \/ 1000.0, 0, forceUpdate);\r\n    ixBattStr = MQTT_TOPIC_ROOT + String(ix) + \"\/current\";\r\n    ixBattStr.toCharArray(ixBuff, 50);\r\n    mqtt_publish_f(ixBuff, g_stack.batts[ix].current \/ 1000.0, lastSentData.batts[ix].current \/ 1000.0, 0, forceUpdate);\r\n    ixBattStr = MQTT_TOPIC_ROOT + String(ix) + \"\/soc\";\r\n    ixBattStr.toCharArray(ixBuff, 50);\r\n    mqtt_publish_i(ixBuff, g_stack.batts[ix].soc, lastSentData.batts[ix].soc, 0, forceUpdate);\r\n    ixBattStr = MQTT_TOPIC_ROOT + String(ix) + \"\/charging\";\r\n    ixBattStr.toCharArray(ixBuff, 50);\r\n    mqtt_publish_i(ixBuff, g_stack.batts[ix].isCharging()?1:0, lastSentData.batts[ix].isCharging()?1:0, 0, forceUpdate);\r\n    ixBattStr = MQTT_TOPIC_ROOT + String(ix) + \"\/discharging\";\r\n    ixBattStr.toCharArray(ixBuff, 50);\r\n    mqtt_publish_i(ixBuff, g_stack.batts[ix].isDischarging()?1:0, lastSentData.batts[ix].isDischarging()?1:0, 0, forceUpdate);\r\n    ixBattStr = MQTT_TOPIC_ROOT + String(ix) + \"\/idle\";\r\n    ixBattStr.toCharArray(ixBuff, 50);\r\n    mqtt_publish_i(ixBuff, g_stack.batts[ix].isIdle()?1:0, lastSentData.batts[ix].isIdle()?1:0, 0, forceUpdate);\r\n    ixBattStr = MQTT_TOPIC_ROOT + String(ix) + \"\/state\";\r\n    ixBattStr.toCharArray(ixBuff, 50);\r\n    mqtt_publish_s(ixBuff, g_stack.batts[ix].isIdle()?\"Idle\":g_stack.batts[ix].isCharging()?\"Charging\":g_stack.batts[ix].isDischarging()?\"Discharging\":\"\", lastSentData.batts[ix].isIdle()?\"Idle\":lastSentData.batts[ix].isCharging()?\"Charging\":lastSentData.batts[ix].isDischarging()?\"Discharging\":\"\", forceUpdate);\r\n    ixBattStr = MQTT_TOPIC_ROOT + String(ix) + \"\/temp\";\r\n    ixBattStr.toCharArray(ixBuff, 50);\r\n    mqtt_publish_f(ixBuff, (float)g_stack.batts[ix].tempr\/1000.0, (float)lastSentData.batts[ix].tempr\/1000.0, 0.1, forceUpdate);\r\n  }\r\n} \r\n\r\nvoid mqttLoop()\r\n{\r\n  \/\/if we have problems with connecting to mqtt server, we will attempt to re-estabish connection each 1minute (not more than that)\r\n  static unsigned long g_lastConnectionAttempt = 0;\r\n\r\n  \/\/first: let's make sure we are connected to mqtt\r\n  const char* topicLastWill = MQTT_TOPIC_ROOT \"availability\";\r\n  if (!mqttClient.connected() &amp;&amp; (g_lastConnectionAttempt == 0 || os_getCurrentTimeSec() - g_lastConnectionAttempt &gt; 60)) {\r\n    if(mqttClient.connect(WIFI_HOSTNAME, MQTT_USER, MQTT_PASSWORD, topicLastWill, 1, true, \"offline\"))\r\n    {\r\n      Log(\"Connected to MQTT server: \" MQTT_SERVER);\r\n      mqttClient.publish(topicLastWill, \"online\", true);\r\n    }\r\n    else\r\n    {\r\n      Log(\"Failed to connect to MQTT server.\");\r\n    }\r\n\r\n    g_lastConnectionAttempt = os_getCurrentTimeSec();\r\n  }\r\n\r\n  \/\/next: read data from battery and send via MQTT (but only once per MQTT_PUSH_FREQ_SEC seconds)\r\n  static unsigned long g_lastDataSent = 0;\r\n  if(mqttClient.connected() &amp;&amp; \r\n     os_getCurrentTimeSec() - g_lastDataSent &gt; MQTT_PUSH_FREQ_SEC &amp;&amp;\r\n     sendCommandAndReadSerialResponse(\"pwr\") == true)\r\n  {\r\n    static batteryStack lastSentData; \/\/this is the last state we sent to MQTT, used to prevent sending the same data over and over again\r\n    static unsigned int callCnt = 0;\r\n    \r\n    parsePwrResponse(g_szRecvBuff);\r\n\r\n    bool forceUpdate = (callCnt % 20 == 0); \/\/push all the data every 20th call\r\n    pushBatteryDataToMqtt(lastSentData, forceUpdate);\r\n    \r\n    callCnt++;\r\n    g_lastDataSent = os_getCurrentTimeSec();\r\n    memcpy(&amp;lastSentData, &amp;g_stack, sizeof(batteryStack));\r\n  }\r\n  \r\n  mqttClient.loop();\r\n}\r\n\r\n#endif \/\/ENABLE_MQTT\r\n\r\n<\/code><\/pre>\n<p><button id=\"copy-button\">Copy<\/button><\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_8259\" class=\"pvc_stats all  \" data-element-id=\"8259\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n<p>Anyone who has installed a photovoltaic system in their own home may even use an energy storage system. In this example, it is an off-grid system equipped with two modules from the manufacturer Pylontech. The Pylontech US3000C batteries have an output voltage of 48V. The nominal capacity is 3500Wh. The installed cells are LiFePO4 cells&hellip; <br \/> <a class=\"read-more\" href=\"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/2024\/11\/12\/pylontech-pv-battery-status-in-the-homeassistant\/?lang=en\">Weiterlesen<\/a><\/p>\n","protected":false},"author":86,"featured_media":7999,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"ngg_post_thumbnail":0,"footnotes":""},"categories":[1499],"tags":[2582,2571,2422,2578,2577,2583,2584,2585,2586,2576,2574,2575,2579,2581,2580],"class_list":["post-8259","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-miscellaneous","tag-console-mqtt-en","tag-homeassistant-en","tag-mqtt-en","tag-pv-battery-status-in-the-home-assistant","tag-pylontec2mqtt-en","tag-pylontech-en","tag-pylontech-console-en","tag-pylontech-serial-en","tag-pylontech2mqtt-en","tag-us2000-en","tag-us3000-en","tag-us3000c-en","tag-wemos-en","tag-wemos-d1mini-en","tag-wemosd1-en"],"a3_pvc":{"activated":true,"total_views":959,"today_views":1},"_links":{"self":[{"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/posts\/8259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/users\/86"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/comments?post=8259"}],"version-history":[{"count":0,"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/posts\/8259\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/media\/7999"}],"wp:attachment":[{"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/media?parent=8259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/categories?post=8259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.fh-kaernten.at\/ingmarsretro\/wp-json\/wp\/v2\/tags?post=8259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}