├── secrets.yaml ├── images ├── Atom_CAN_Kit.png ├── Atom_S3_Lite.png ├── HA_Dashboard.png ├── CAN_Protocol_Table.png ├── ESPHome_Web_Server.png ├── JK-BMS_24S_GPS_port.png ├── Atom_Lite_ESP32-PICO_pin.png ├── lovelace-cards-contribution.png ├── Atomic_CANBus_Base_CA-IS3050G.png ├── JK-BMS-CAN_LFP_Cut-Off_Values.png ├── PCB_ESP32_JK-BMS-CAN_Prototype.jpg ├── JK-BMS-CAN_Charging_Logic_Diagram.png ├── JK-BMS-CAN_Li-ion_Cut-Off_Values.png ├── Auto_Charge_Voltage_Logic_Flowchart.png ├── PCB_ESP32_JK-BMS-CAN_powered_by_JK-BMS.png ├── JK-BMS-CAN_Cut-Off_Charging_Logic_for_LFP_Diagram.png └── JK-BMS-CAN_Cut-Off_Charging_Logic_for_LFP_Equation.png ├── documents ├── BMS LV CAN Protocol │ ├── SMA CAN protocol.pdf │ ├── SMA CAN Protocol Mapping.pdf │ ├── SEPLOS BMS CAN Protocoll V1.0.pdf │ ├── Sol-Ark CAN Bus Protocol V1.3.pdf │ ├── BMS_Protocol _CAN_Hybrid_EN_V1.0.pdf │ ├── Master-LV-communication-guide-EN-8000.pdf │ ├── CAN-Bus-protocol-DEYE-low-voltage-V1.2-20180408.pdf │ ├── CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf │ ├── Growatt-BMS-CAN-Bus-protocol-low-voltage-V1.04-1.pdf │ └── Growatt-BMS-CAN-Bus-protocol-low-voltage-V1.04-1-translate.pdf └── README │ ├── Cut-Off_Charging_Logic.md │ └── Auto_Charge_Voltage_Logic.md ├── README.md ├── config ├── config_atom-lite-esp32-pico.yaml ├── config_generic-esp32-devkit-v1.yaml └── config_atom-s3-lite-esp32-s3.yaml ├── LICENSE.txt └── old_version ├── esp32_wire_jk-bms-can.yaml └── esp32_ble_jk-bms-can.yaml /secrets.yaml: -------------------------------------------------------------------------------- 1 | wifi_ssid: YourSSID 2 | wifi_password: YourPassword 3 | domain : .local 4 | -------------------------------------------------------------------------------- /images/Atom_CAN_Kit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/Atom_CAN_Kit.png -------------------------------------------------------------------------------- /images/Atom_S3_Lite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/Atom_S3_Lite.png -------------------------------------------------------------------------------- /images/HA_Dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/HA_Dashboard.png -------------------------------------------------------------------------------- /images/CAN_Protocol_Table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/CAN_Protocol_Table.png -------------------------------------------------------------------------------- /images/ESPHome_Web_Server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/ESPHome_Web_Server.png -------------------------------------------------------------------------------- /images/JK-BMS_24S_GPS_port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/JK-BMS_24S_GPS_port.png -------------------------------------------------------------------------------- /images/Atom_Lite_ESP32-PICO_pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/Atom_Lite_ESP32-PICO_pin.png -------------------------------------------------------------------------------- /images/lovelace-cards-contribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/lovelace-cards-contribution.png -------------------------------------------------------------------------------- /images/Atomic_CANBus_Base_CA-IS3050G.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/Atomic_CANBus_Base_CA-IS3050G.png -------------------------------------------------------------------------------- /images/JK-BMS-CAN_LFP_Cut-Off_Values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/JK-BMS-CAN_LFP_Cut-Off_Values.png -------------------------------------------------------------------------------- /images/PCB_ESP32_JK-BMS-CAN_Prototype.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/PCB_ESP32_JK-BMS-CAN_Prototype.jpg -------------------------------------------------------------------------------- /images/JK-BMS-CAN_Charging_Logic_Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/JK-BMS-CAN_Charging_Logic_Diagram.png -------------------------------------------------------------------------------- /images/JK-BMS-CAN_Li-ion_Cut-Off_Values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/JK-BMS-CAN_Li-ion_Cut-Off_Values.png -------------------------------------------------------------------------------- /images/Auto_Charge_Voltage_Logic_Flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/Auto_Charge_Voltage_Logic_Flowchart.png -------------------------------------------------------------------------------- /images/PCB_ESP32_JK-BMS-CAN_powered_by_JK-BMS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/PCB_ESP32_JK-BMS-CAN_powered_by_JK-BMS.png -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/SMA CAN protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/SMA CAN protocol.pdf -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/SMA CAN Protocol Mapping.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/SMA CAN Protocol Mapping.pdf -------------------------------------------------------------------------------- /images/JK-BMS-CAN_Cut-Off_Charging_Logic_for_LFP_Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/JK-BMS-CAN_Cut-Off_Charging_Logic_for_LFP_Diagram.png -------------------------------------------------------------------------------- /images/JK-BMS-CAN_Cut-Off_Charging_Logic_for_LFP_Equation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/images/JK-BMS-CAN_Cut-Off_Charging_Logic_for_LFP_Equation.png -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/SEPLOS BMS CAN Protocoll V1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/SEPLOS BMS CAN Protocoll V1.0.pdf -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/Sol-Ark CAN Bus Protocol V1.3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/Sol-Ark CAN Bus Protocol V1.3.pdf -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/BMS_Protocol _CAN_Hybrid_EN_V1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/BMS_Protocol _CAN_Hybrid_EN_V1.0.pdf -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/Master-LV-communication-guide-EN-8000.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/Master-LV-communication-guide-EN-8000.pdf -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/CAN-Bus-protocol-DEYE-low-voltage-V1.2-20180408.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/CAN-Bus-protocol-DEYE-low-voltage-V1.2-20180408.pdf -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/Growatt-BMS-CAN-Bus-protocol-low-voltage-V1.04-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/Growatt-BMS-CAN-Bus-protocol-low-voltage-V1.04-1.pdf -------------------------------------------------------------------------------- /documents/BMS LV CAN Protocol/Growatt-BMS-CAN-Bus-protocol-low-voltage-V1.04-1-translate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sleeper85/esphome-jk-bms-can/HEAD/documents/BMS LV CAN Protocol/Growatt-BMS-CAN-Bus-protocol-low-voltage-V1.04-1-translate.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JK-BMS-CAN 2 | 3 | [![Badge License: GPLv3](https://img.shields.io/badge/License-GPLv3-brightgreen.svg)](https://www.gnu.org/licenses/gpl-3.0) 4 | [![Badge Version](https://img.shields.io/github/v/release/Sleeper85/esphome-jk-bms-can?include_prereleases&color=yellow&logo=DocuSign&logoColor=white)](https://github.com/Sleeper85/esphome-jk-bms-can/releases/latest) 5 | ![GitHub stars](https://img.shields.io/github/stars/Sleeper85/esphome-jk-bms-can) 6 | ![GitHub forks](https://img.shields.io/github/forks/Sleeper85/esphome-jk-bms-can) 7 | ![GitHub watchers](https://img.shields.io/github/watchers/Sleeper85/esphome-jk-bms-can) 8 | [!["Buy Me A Coffee"](https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg)](https://www.buymeacoffee.com/sleeper85) 9 | 10 | > [!IMPORTANT] 11 | > This version is no longer supported and is replaced by the `multi-BMS` version `YamBMS`.
`YamBMS` works with one or multiple BMS. 12 | 13 | ### >>> [YamBMS ( Yet another multi-BMS Merging Solution )](https://github.com/Sleeper85/esphome-yambms) <<< 14 | -------------------------------------------------------------------------------- /config/config_atom-lite-esp32-pico.yaml: -------------------------------------------------------------------------------- 1 | 2 | # *** These are the specific settings you need to change in the YAML if you use the Atom Lite ESP32-PICO board *** 3 | 4 | # 1) Choose the configuration file you want to use: 5 | # - esp32_ble_jk-bms-can.yaml : If you don't want to connect a wire between the BMS and the ESP32 choose the Bluetooth version. 6 | # - esp32_wire_jk-bms-can.yaml : If you prefer to use a wired connection between the BMS and the ESP32 choose the Wire version. 7 | 8 | # 2) Edit the YAML file you have chosen with the information below : 9 | 10 | # +--------------------------------------+ 11 | # | ESP32 CAN/serial port pins | 12 | # +--------------------------------------+ 13 | # GPIO pins your CAN bus transceiver ATOMIC CANBus Base (CA-IS3050G) 14 | can_tx_pin: GPIO22 15 | can_rx_pin: GPIO19 16 | # GPIO pins your JK-BMS UART-TTL is connected to the grove port of Atom Lite 17 | tx_pin: GPIO32 18 | rx_pin: GPIO26 19 | 20 | # +--------------------------------------+ 21 | # | ESP32 settings | 22 | # +--------------------------------------+ 23 | # Atom Lite is not stable with the Bluetooth version 24 | esp32: 25 | board: m5stack-atom 26 | framework: 27 | type: arduino 28 | -------------------------------------------------------------------------------- /config/config_generic-esp32-devkit-v1.yaml: -------------------------------------------------------------------------------- 1 | 2 | # *** These are the specific settings you need to change in the YAML if you use the ESP32 DevKit v1 30 pin board *** 3 | 4 | # 1) Choose the configuration file you want to use: 5 | # - esp32_ble_jk-bms-can.yaml : If you don't want to connect a wire between the BMS and the ESP32 choose the Bluetooth version. 6 | # - esp32_wire_jk-bms-can.yaml : If you prefer to use a wired connection between the BMS and the ESP32 choose the Wire version. 7 | 8 | # 2) Edit the YAML file you have chosen with the information below : 9 | 10 | # +--------------------------------------+ 11 | # | ESP32 CAN/serial port pins | 12 | # +--------------------------------------+ 13 | # GPIO pins your CAN bus transceiver (TJA1050, TJA1051T or SN65HVD230) is connected to the ESP32 TX->TX and RX->RX ! 14 | can_tx_pin: GPIO23 15 | can_rx_pin: GPIO22 16 | # GPIO pins your JK-BMS UART-TTL is connected to the ESP32 TX->RX and RX->TX ! 17 | tx_pin: GPIO17 18 | rx_pin: GPIO16 19 | 20 | # +--------------------------------------+ 21 | # | ESP32 settings | 22 | # +--------------------------------------+ 23 | # For a stable Bluetooth connection keep the "esp-idf" framework 24 | esp32: 25 | board: esp32doit-devkit-v1 26 | framework: 27 | type: esp-idf 28 | -------------------------------------------------------------------------------- /config/config_atom-s3-lite-esp32-s3.yaml: -------------------------------------------------------------------------------- 1 | 2 | # *** These are the specific settings you need to change in the YAML if you use the Atom S3 Lite ESP32-S3 board *** 3 | 4 | # 1) Choose the configuration file you want to use: 5 | # - esp32_ble_jk-bms-can.yaml : If you don't want to connect a wire between the BMS and the ESP32 choose the Bluetooth version. 6 | # - esp32_wire_jk-bms-can.yaml : If you prefer to use a wired connection between the BMS and the ESP32 choose the Wire version. 7 | 8 | # 2) Edit the YAML file you have chosen with the information below : 9 | 10 | # +--------------------------------------+ 11 | # | ESP32 CAN/serial port pins | 12 | # +--------------------------------------+ 13 | # GPIO pins your CAN bus transceiver ATOMIC CANBus Base (CA-IS3050G) 14 | can_tx_pin: GPIO5 15 | can_rx_pin: GPIO6 16 | # GPIO pins your JK-BMS UART-TTL is connected to the grove port of Atom Lite 17 | tx_pin: GPIO1 18 | rx_pin: GPIO2 19 | 20 | # +--------------------------------------+ 21 | # | ESP32 settings | 22 | # +--------------------------------------+ 23 | esp32: 24 | board: esp32-s3-devkitc-1 25 | variant: esp32s3 26 | framework: 27 | type: esp-idf 28 | 29 | logger: 30 | hardware_uart: USB_SERIAL_JTAG 31 | 32 | # You must also change the GPIO of the LED, replace 2 with 35 33 | output: 34 | - platform: gpio 35 | pin: 35 36 | id: led 37 | inverted: true 38 | -------------------------------------------------------------------------------- /documents/README/Cut-Off_Charging_Logic.md: -------------------------------------------------------------------------------- 1 | # JK-BMS-CAN 2 | 3 | [![Badge License: GPLv3](https://img.shields.io/badge/License-GPLv3-brightgreen.svg)](https://www.gnu.org/licenses/gpl-3.0) 4 | [![Badge Version](https://img.shields.io/github/v/release/Sleeper85/esphome-jk-bms-can?include_prereleases&color=yellow&logo=DocuSign&logoColor=white)](https://github.com/Sleeper85/esphome-jk-bms-can/releases/latest) 5 | ![GitHub stars](https://img.shields.io/github/stars/Sleeper85/esphome-jk-bms-can) 6 | ![GitHub forks](https://img.shields.io/github/forks/Sleeper85/esphome-jk-bms-can) 7 | ![GitHub watchers](https://img.shields.io/github/watchers/Sleeper85/esphome-jk-bms-can) 8 | 9 | ## Cut-Off Charging Logic Equation 10 | 11 | Source: [Charging Marine Lithium Battery Banks](https://nordkyndesign.com/charging-marine-lithium-battery-banks) 12 | 13 | Special thanks to [@shvmm](https://github.com/shvmm) for creating the equations. 14 | 15 | Note: The equations below are valid for other chemistries like Li-ion and LTO but with other CVmin and CVmax values. 16 | 17 | ![Image](../../images/JK-BMS-CAN_Cut-Off_Charging_Logic_for_LFP_Equation.png "JK-BMS-CAN Cut-Off Charging Logic_for LFP Equation") 18 | 19 | ## Cut-Off Charging Logic Diagram 20 | 21 | Note: The diagram below is valid for other chemistries like Li-ion and LTO but with other CVmin and CVmax values. 22 | 23 | ![Image](../../images/JK-BMS-CAN_Cut-Off_Charging_Logic_for_LFP_Diagram.png "JK-BMS-CAN Cut-Off Charging Logic_for LFP Diagram") 24 | 25 | ## LFP Cut-Off Values 26 | 27 | ![Image](../../images/JK-BMS-CAN_LFP_Cut-Off_Values.png "JK-BMS-CAN LFP Cut-Off Values") 28 | 29 | ## Li-ion Cut-Off Values 30 | 31 | ![Image](../../images/JK-BMS-CAN_Li-ion_Cut-Off_Values.png "JK-BMS-CAN Li-ion Cut-Off Values") 32 | 33 | ## LTO Cut-Off Values 34 | 35 | Will be added in the future. 36 | -------------------------------------------------------------------------------- /documents/README/Auto_Charge_Voltage_Logic.md: -------------------------------------------------------------------------------- 1 | # JK-BMS-CAN 2 | 3 | [![Badge License: GPLv3](https://img.shields.io/badge/License-GPLv3-brightgreen.svg)](https://www.gnu.org/licenses/gpl-3.0) 4 | [![Badge Version](https://img.shields.io/github/v/release/Sleeper85/esphome-jk-bms-can?include_prereleases&color=yellow&logo=DocuSign&logoColor=white)](https://github.com/Sleeper85/esphome-jk-bms-can/releases/latest) 5 | ![GitHub stars](https://img.shields.io/github/stars/Sleeper85/esphome-jk-bms-can) 6 | ![GitHub forks](https://img.shields.io/github/forks/Sleeper85/esphome-jk-bms-can) 7 | ![GitHub watchers](https://img.shields.io/github/watchers/Sleeper85/esphome-jk-bms-can) 8 | 9 | ## Automatic Charge Voltage 10 | 11 | Special thanks to [@MrPabloUK](https://github.com/MrPabloUK) for creating the logic. 12 | 13 | When enabled, the Automatic Charge Voltage feature will dynamically adjust the request CVL (Charge Voltage Limit) as sent by the ESP32 device. 14 | This feature is designed to automatically reduce the liklihood of a cell reaching overvoltage thresholds, as well as reducing charge current enough for balancing to be more effective. 15 | 16 | There are several inputs used in this logic: 17 | - Cell bulk voltage - automatically calculated as bulk voltage / cell count. 18 | - Charge V factor - multiplier that alters the aggression of the logic. The higher the value, the more severe the cell penalty. 19 | - Cell voltage - as reported by the BMS 20 | 21 | This logic will first check if enabled, then if any cells are at or over cell bulk voltage. 22 | If either of these are not true, then standard full bulk voltage will be sent to the inverter as the requested CVL. 23 | 24 | If not, then each cell that is at or over cell bulk voltage will have an automatically calculated penalty voltage applied. 25 | This penalty will increase in severity as the cell voltage increases above bulk voltage. 26 | Penalties are then applied against the current battery voltage, resulting in a target voltage equal or lower than the current battery voltage. 27 | This target voltage is then sent to the inverter as the requested CVL. 28 | 29 | A reference spreadsheet has been created that shows how the CVL changes based on cell voltages: 30 | https://docs.google.com/spreadsheets/d/1UwZ94Qca-DBP5gppzKmAjbMJYZGjR4lMwZtwwQR9wWY/edit?usp=sharing 31 | 32 | ![Image](../../images/Auto_Charge_Voltage_Logic_Flowchart.png "Auto Charge Voltage Logic") 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /old_version/esp32_wire_jk-bms-can.yaml: -------------------------------------------------------------------------------- 1 | # JK-BMS-CAN ( PYLON, Seplos, GoodWe, SMA and Victron CAN bus protocol ) 2 | 3 | # esp32_wire_jk-bms-can.yaml is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation, either version 3 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | # See the GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # V1.16.4 Sleeper85 : Improved Charging Logic for ESP32 startup/reboot and Float charge, Add CAN ID 0x356 bytes [06:07] cycles for Sofar, Change switch name 17 | # V1.16.3 Sleeper85 : ID 0x379 will be sent when choosing protocol 2 or 4 (Battery Capacity for Victron, Sol-Ark and Luxpower) 18 | # V1.16.2 Sleeper85 : Split the "Charge/Discharge values" section and added instructions for "Stop Discharging" + Set "esp-idf" framework by default 19 | # V1.16.1 Sleeper85 : Slider charging_current max value = ${charge_a}, Improved Alarm/Charging/Discharging Logic, Improved CAN protocol and Victron support 20 | # V1.15.5 Sleeper85 : Improved code and set api "reboot_timout" to "0s" by default (no reboot without HA) 21 | # V1.15.4 Sleeper85 : Improved documentation for API, Web Server and WiFi settings 22 | # V1.15.3 Sleeper85 : Add 'CAN Protocol Settings' and new CAN ID based on the SMA and Victron protocol (alpha) 23 | # V1.15.2 Sleeper85 : Improved Alarm handling, all alarms will set charge/discharge current to 0A and set 'Charging Status' to Alarm 24 | # V1.15.1 Sleeper85 : New CANBUS script with CANBUS Status in HA, stop sending CAN messages if the inverter is not responding (fix WDT reboot issues) 25 | # V1.14.3 Sleeper85 : Improved documentation + Charging Voltage tips for Deye 26 | # V1.14.2 Sleeper85 : Improve 'Charging Voltage' behavior 27 | # V1.14.1 Sleeper85 : Add 'Float charge function' 28 | # V1.13.6 Sleeper85 : Add 'Absorption time' and 'Absorption Offset V.' slider 29 | # V1.13.5 Sleeper85 : Set CAN manufacter to "PYLON" for improve compatibility with Deye and other inverters 30 | # V1.13.4 Sleeper85 : Improve 'Charge Status' behavior + add 'Rebulk Offset V.' slider 31 | # V1.13.3 uksa007 : Improve compatibility with Deye and other inverters 32 | # V1.13.2 uksa007 : Send Max Temperature of T1, T2 to inverter 33 | # V1.13.1 uksa007 : Fix compile issues with new version of ESPhome 2023.4.0, set rebulk offset to 2.5 34 | 35 | substitutions: 36 | # +--------------------------------------+ 37 | # name that will appear in esphome and homeassistant. 38 | name: jk-bms-can 39 | # +--------------------------------------+ 40 | # Number of Battery modules max 8. Each LX U5.4-L battery is 5.4kWh, select the number closest to your capactiy eg 3.2V * 280Ah * 16 = 14.3kWh 41 | batt_modules: "3" 42 | # +--------------------------------------+ 43 | # | Battery Charge Settings | 44 | # +--------------------------------------+ 45 | # Tips for Deye inverter : Add 0.1v to the settings below because the Deye charging voltage is always 0.1v lower than requested. 46 | # Float V. : 53.7v (3.35v/cell - Natural voltage of a fully charged cell at rest, I advise you not to go higher.) 47 | # Absorption V : 55.3v (3.45v/cell - It's not necessary to use a charging voltage higher than 55.2V for a full charge.) 48 | # Absorption Offset V. : 0.15v (The absorption phase will start at 55.15v (BMS voltage). Warning: the BMS voltage must be correctly calibrated.) 49 | # +--------------------------------------+ 50 | # This is max charging amps eg 100A, for Bulk - Constant Current charging(CC), should be at least 10A less than BMS change current protection, 0.5C max 51 | # 100A * 50V = 5000W 52 | charge_a: "100" 53 | # Float Voltage : corresponds to the voltage at which the battery would be maintained at the end of the absorption phase. (53.6v eg 3.35v/cell for 16 cells 48V battery) 54 | float_v: "53.6" 55 | # Absorption Voltage : corresponds to the Bulk voltage that will be used to charge the battery. (55.2v eg 3.45v/cell for 16 cells 48V battery) 56 | absorption_v: "55.2" 57 | # Absorption time in minutes to hold charge voltage after charge voltage is reached eg 30 58 | absorption_time: "30" 59 | # Absorption offset, x Volts below absorption voltage battery will start the absorption timer, eg 55.2-0.05 = 52.15v 60 | absorption_offset_v: "0.05" 61 | # Rebulk offset, x Volts below absorption voltage battery will request rebulk, eg 55.2-2.5 = 52.7v 62 | rebulk_offset_v: "2.5" 63 | # +--------------------------------------+ 64 | # | Battery Discharge Settings | 65 | # +--------------------------------------+ 66 | # Max discharge amps eg 120, should be at least 10A less than BMS over discharge current protection, 0.5C max 67 | # 120A * 50V = 6000W 68 | discharge_a: "120" 69 | # Minimum discharge voltage eg 48v/16 = 3V per cell 70 | min_discharge_v: "48" 71 | # +--------------------------------------+ 72 | # | Battery State of Health (SOH) | 73 | # +--------------------------------------+ 74 | # Maximum charging cycles is used to calculate the battey SOH, LF280K v3 =8000.0, LF280K v2 =6000.0, LF280=3000.0 (decimal is required) 75 | max_cycles: "6000.0" 76 | # +--------------------------------------+ 77 | # | CAN Protocol Settings | 78 | # +--------------------------------------+ 79 | # CAN BMS Name (0x35E) : 0 NoSent / 1 PYLON / 2 GOODWE / 3 SEPLOS 80 | can_bms_name: "1" 81 | # CAN Protocol 82 | # 1 : PYLON 1.2 (Deye) 83 | # 2 : SEPLOS 1.0, PYLON 1.3, GOODWE 1.5 (GoodWe, Sol-Ark, Luxpower) 84 | # 3 : SMA (Sunny Island, Sofar) 85 | # 4 : VICTRON 86 | can_protocol: "1" 87 | # +--------------------------------------+ 88 | # | ESP32 CAN/serial port pins | 89 | # +--------------------------------------+ 90 | # GPIO pins your CAN bus transceiver (TJA1050, TJA1051T or SN65HVD230) is connected to the ESP32 TX->TX and RX->RX ! 91 | can_tx_pin: GPIO23 92 | can_rx_pin: GPIO22 93 | # GPIO pins your JK-BMS UART-TTL is connected to the ESP32 TX->RX and RX->TX ! 94 | tx_pin: GPIO17 95 | rx_pin: GPIO16 96 | 97 | # +------------------------------------------------------------------+ 98 | # | ** The settings below can be modified according to your needs ** | 99 | # +------------------------------------------------------------------+ 100 | external_components_source: github://syssi/esphome-jk-bms@main 101 | # components 102 | # github://syssi/esphome-jk-bms@main 103 | 104 | esphome: 105 | name: ${name} 106 | on_boot: 107 | then: 108 | - switch.turn_on: switch_charging 109 | - switch.turn_on: switch_discharging 110 | - switch.turn_on: switch_chg_float 111 | 112 | # +--------------------------------------+ 113 | # | ESP32 settings | 114 | # +--------------------------------------+ 115 | # For a stable Bluetooth connection keep the "esp-idf" framework 116 | esp32: 117 | board: esp32doit-devkit-v1 118 | framework: 119 | type: esp-idf 120 | 121 | external_components: 122 | - source: ${external_components_source} 123 | refresh: 0s 124 | 125 | logger: 126 | # level: DEBUG 127 | 128 | ota: 129 | 130 | # Please use the native `api` component instead of the `mqtt` section. 131 | # If you use Home Assistant, the native API is more lightweight. 132 | # If there is no HA server connected to this API, the ESP32 reboots every 15 minutes to try to resolve the problem. 133 | # If you don't use Home Assistant please uncomment the "reboot_timeout: 0s" option. 134 | api: 135 | reboot_timeout: 0s 136 | 137 | # If you don't want to use ESPHome's native API you can use MQQT instead. 138 | # In this case don't forget to remove the 'api:' section. 139 | # mqtt: 140 | # broker: !secret mqtt_host 141 | # username: !secret mqtt_username 142 | # password: !secret mqtt_password 143 | # id: mqtt_client 144 | 145 | # In the event of problems with the WiFi network, the ESP32 will reboot every 15 minutes to try to resolve the problem. 146 | # If we don't want to connect the ESP32 to the WiFi network please remove the 4 lines below. 147 | wifi: 148 | ssid: !secret wifi_ssid 149 | password: !secret wifi_password 150 | domain: !secret domain 151 | 152 | #web_server: 153 | # port: 80 154 | # log: false 155 | # ota: false 156 | 157 | # +--------------------------------------+ 158 | # | ** Don't make changes below this ** | 159 | # +--------------------------------------+ 160 | 161 | globals: 162 | - id: can_ack_counter 163 | type: int 164 | restore_value: no 165 | initial_value: '0' 166 | - id: charge_status 167 | type: std::string 168 | restore_value: no 169 | initial_value: '"Wait"' 170 | - id: can_status 171 | type: std::string 172 | restore_value: no 173 | initial_value: '"OFF"' 174 | - id: alarm_status 175 | type: std::string 176 | restore_value: no 177 | initial_value: '"NoAlarm"' 178 | - id: charging_v 179 | type: float 180 | restore_value: no 181 | initial_value: '0.0' 182 | - id: charging_a 183 | type: int 184 | restore_value: no 185 | initial_value: '0' 186 | - id: discharging_a 187 | type: int 188 | restore_value: no 189 | initial_value: '0' 190 | - id: can_msg_counter 191 | type: int 192 | restore_value: no 193 | initial_value: '0' 194 | 195 | output: 196 | - platform: gpio 197 | pin: 2 198 | id: led 199 | inverted: true 200 | 201 | light: 202 | - platform: binary 203 | output: led 204 | id: blue_led 205 | name: "Blue LED" 206 | internal: true 207 | 208 | # +--------------------------------------+ 209 | # | JK-BMS UART connection | 210 | # +--------------------------------------+ 211 | 212 | uart: 213 | id: uart_0 214 | baud_rate: 115200 215 | rx_buffer_size: 384 216 | tx_pin: ${tx_pin} 217 | rx_pin: ${rx_pin} 218 | # debug: 219 | # direction: BOTH 220 | 221 | jk_modbus: 222 | id: modbus0 223 | uart_id: uart_0 224 | 225 | jk_bms: 226 | id: bms0 227 | jk_modbus_id: modbus0 228 | # enable_fake_traffic: true 229 | 230 | # +--------------------------------------+ 231 | 232 | binary_sensor: 233 | - platform: jk_bms 234 | balancing: 235 | name: "${name} balancing" 236 | balancing_switch: 237 | name: "${name} balancing switch" 238 | charging: 239 | name: "${name} charging" 240 | charging_switch: 241 | id: charging_switch 242 | name: "${name} charging switch" 243 | discharging: 244 | name: "${name} discharging" 245 | discharging_switch: 246 | id: discharging_switch 247 | name: "${name} discharging switch" 248 | dedicated_charger_switch: 249 | name: "${name} dedicated charger switch" 250 | 251 | sensor: 252 | - platform: jk_bms 253 | min_cell_voltage: 254 | id: min_cell_voltage 255 | name: "${name} min cell voltage" 256 | max_cell_voltage: 257 | id: max_cell_voltage 258 | name: "${name} max cell voltage" 259 | min_voltage_cell: 260 | id: min_voltage_cell 261 | name: "${name} min voltage cell" 262 | max_voltage_cell: 263 | id: max_voltage_cell 264 | name: "${name} max voltage cell" 265 | delta_cell_voltage: 266 | name: "${name} delta cell voltage" 267 | average_cell_voltage: 268 | name: "${name} average cell voltage" 269 | cell_voltage_1: 270 | name: "${name} cell voltage 1" 271 | cell_voltage_2: 272 | name: "${name} cell voltage 2" 273 | cell_voltage_3: 274 | name: "${name} cell voltage 3" 275 | cell_voltage_4: 276 | name: "${name} cell voltage 4" 277 | cell_voltage_5: 278 | name: "${name} cell voltage 5" 279 | cell_voltage_6: 280 | name: "${name} cell voltage 6" 281 | cell_voltage_7: 282 | name: "${name} cell voltage 7" 283 | cell_voltage_8: 284 | name: "${name} cell voltage 8" 285 | cell_voltage_9: 286 | name: "${name} cell voltage 9" 287 | cell_voltage_10: 288 | name: "${name} cell voltage 10" 289 | cell_voltage_11: 290 | name: "${name} cell voltage 11" 291 | cell_voltage_12: 292 | name: "${name} cell voltage 12" 293 | cell_voltage_13: 294 | name: "${name} cell voltage 13" 295 | cell_voltage_14: 296 | name: "${name} cell voltage 14" 297 | cell_voltage_15: 298 | name: "${name} cell voltage 15" 299 | cell_voltage_16: 300 | name: "${name} cell voltage 16" 301 | # cell_voltage_17: 302 | # name: "${name} cell voltage 17" 303 | # cell_voltage_18: 304 | # name: "${name} cell voltage 18" 305 | # cell_voltage_19: 306 | # name: "${name} cell voltage 19" 307 | # cell_voltage_20: 308 | # name: "${name} cell voltage 20" 309 | # cell_voltage_21: 310 | # name: "${name} cell voltage 21" 311 | # cell_voltage_22: 312 | # name: "${name} cell voltage 22" 313 | # cell_voltage_23: 314 | # name: "${name} cell voltage 23" 315 | # cell_voltage_24: 316 | # name: "${name} cell voltage 24" 317 | power_tube_temperature: 318 | id: power_tube_temperature 319 | name: "${name} power tube temperature" 320 | temperature_sensor_1: 321 | id: temperature_sensor_1 322 | name: "${name} temperature sensor 1" 323 | temperature_sensor_2: 324 | id: temperature_sensor_2 325 | name: "${name} temperature sensor 2" 326 | total_voltage: 327 | id: total_voltage 328 | name: "${name} total voltage" 329 | current: 330 | id: current 331 | name: "${name} current" 332 | power: 333 | name: "${name} power" 334 | charging_power: 335 | name: "${name} charging power" 336 | discharging_power: 337 | name: "${name} discharging power" 338 | capacity_remaining: 339 | id: capacity_remaining 340 | name: "${name} capacity remaining" 341 | capacity_remaining_derived: 342 | name: "${name} capacity remaining derived" 343 | temperature_sensors: 344 | name: "${name} temperature sensors" 345 | charging_cycles: 346 | id: charging_cycles 347 | name: "${name} charging cycles" 348 | total_charging_cycle_capacity: 349 | name: "${name} total charging cycle capacity" 350 | battery_strings: 351 | name: "${name} battery strings" 352 | errors_bitmask: 353 | id: errors_bitmask 354 | name: "${name} errors bitmask" 355 | operation_mode_bitmask: 356 | name: "${name} operation mode bitmask" 357 | total_voltage_overvoltage_protection: 358 | name: "${name} total voltage overvoltage protection" 359 | total_voltage_undervoltage_protection: 360 | name: "${name} total voltage undervoltage protection" 361 | cell_voltage_overvoltage_protection: 362 | name: "${name} cell voltage overvoltage protection" 363 | cell_voltage_overvoltage_recovery: 364 | name: "${name} cell voltage overvoltage recovery" 365 | cell_voltage_overvoltage_delay: 366 | name: "${name} cell voltage overvoltage delay" 367 | cell_voltage_undervoltage_protection: 368 | name: "${name} cell voltage undervoltage protection" 369 | cell_voltage_undervoltage_recovery: 370 | name: "${name} cell voltage undervoltage recovery" 371 | cell_voltage_undervoltage_delay: 372 | name: "${name} cell voltage undervoltage delay" 373 | cell_pressure_difference_protection: 374 | name: "${name} cell pressure difference protection" 375 | discharging_overcurrent_protection: 376 | name: "${name} discharging overcurrent protection" 377 | discharging_overcurrent_delay: 378 | name: "${name} discharging overcurrent delay" 379 | charging_overcurrent_protection: 380 | name: "${name} charging overcurrent protection" 381 | charging_overcurrent_delay: 382 | name: "${name} charging overcurrent delay" 383 | balance_starting_voltage: 384 | name: "${name} balance starting voltage" 385 | balance_opening_pressure_difference: 386 | name: "${name} balance opening pressure difference" 387 | power_tube_temperature_protection: 388 | name: "${name} power tube temperature protection" 389 | power_tube_temperature_recovery: 390 | name: "${name} power tube temperature recovery" 391 | temperature_sensor_temperature_protection: 392 | name: "${name} temperature sensor temperature protection" 393 | temperature_sensor_temperature_recovery: 394 | name: "${name} temperature sensor temperature recovery" 395 | temperature_sensor_temperature_difference_protection: 396 | name: "${name} temperature sensor temperature difference protection" 397 | charging_high_temperature_protection: 398 | name: "${name} charging high temperature protection" 399 | discharging_high_temperature_protection: 400 | name: "${name} discharging high temperature protection" 401 | charging_low_temperature_protection: 402 | name: "${name} charging low temperature protection" 403 | charging_low_temperature_recovery: 404 | name: "${name} charging low temperature recovery" 405 | discharging_low_temperature_protection: 406 | name: "${name} discharging low temperature protection" 407 | discharging_low_temperature_recovery: 408 | name: "${name} discharging low temperature recovery" 409 | total_battery_capacity_setting: 410 | id: total_battery_capacity_setting 411 | name: "${name} total battery capacity setting" 412 | current_calibration: 413 | name: "${name} current calibration" 414 | device_address: 415 | name: "${name} device address" 416 | sleep_wait_time: 417 | name: "${name} sleep wait time" 418 | alarm_low_volume: 419 | name: "${name} alarm low volume" 420 | manufacturing_date: 421 | name: "${name} manufacturing date" 422 | total_runtime: 423 | name: "${name} total runtime" 424 | # start_current_calibration: 425 | # name: "${name} start current calibration" 426 | actual_battery_capacity: 427 | name: "${name} actual battery capacity" 428 | # protocol_version: 429 | # name: "${name} protocol version" 430 | # +--------------------------------------+ 431 | # | Uptime sensor | 432 | # +--------------------------------------+ 433 | - platform: uptime 434 | name: ${name} Uptime Sensor 435 | id: uptime_sensor 436 | update_interval: 60s 437 | on_raw_value: 438 | then: 439 | - text_sensor.template.publish: 440 | id: uptime_human 441 | state: !lambda |- 442 | int seconds = round(id(uptime_sensor).raw_state); 443 | int days = seconds / (24 * 3600); 444 | seconds = seconds % (24 * 3600); 445 | int hours = seconds / 3600; 446 | seconds = seconds % 3600; 447 | int minutes = seconds / 60; 448 | seconds = seconds % 60; 449 | return ( 450 | (days ? to_string(days) + "d " : "") + 451 | (hours ? to_string(hours) + "h " : "") + 452 | (minutes ? to_string(minutes) + "m " : "") + 453 | (to_string(seconds) + "s") 454 | ).c_str(); 455 | 456 | text_sensor: 457 | - platform: jk_bms 458 | errors: 459 | name: "${name} errors" 460 | operation_mode: 461 | name: "${name} operation mode" 462 | battery_type: 463 | name: "${name} battery type" 464 | # password: 465 | # name: "${name} password" 466 | device_type: 467 | name: "${name} device type" 468 | software_version: 469 | name: "${name} software version" 470 | manufacturer: 471 | name: "${name} manufacturer" 472 | total_runtime_formatted: 473 | name: "${name} total runtime formatted" 474 | # +--------------------------------------+ 475 | # | Template text sensors | 476 | # +--------------------------------------+ 477 | - platform: template 478 | name: ${name} Uptime Human Readable 479 | id: uptime_human 480 | icon: mdi:clock-start 481 | - platform: template 482 | name: "${name} Charging Status" 483 | id: charging_status 484 | - platform: template 485 | name: "${name} CANBUS Status" 486 | id: canbus_status 487 | 488 | # +--------------------------------------+ 489 | # | Slider | 490 | # +--------------------------------------+ 491 | number: 492 | - platform: template 493 | name: "${name} Bulk voltage" 494 | id: "bulk_voltage" 495 | step: 0.1 496 | min_value: 52.8 497 | max_value: 57.6 498 | mode: slider 499 | initial_value: "${absorption_v}" 500 | unit_of_measurement: V 501 | icon: mdi:battery-charging 502 | optimistic: true 503 | - platform: template 504 | name: "${name} Float voltage" 505 | id: "float_voltage" 506 | step: 0.1 507 | min_value: 52.8 508 | max_value: 57.6 509 | mode: slider 510 | initial_value: "${float_v}" 511 | unit_of_measurement: V 512 | icon: mdi:battery-charging 513 | optimistic: true 514 | - platform: template 515 | name: "${name} Charging current max" 516 | id: "charging_current" 517 | step: 1 518 | min_value: 0 519 | max_value: "${charge_a}" 520 | mode: slider 521 | initial_value: "${charge_a}" 522 | unit_of_measurement: A 523 | icon: mdi:current-dc 524 | optimistic: true 525 | - platform: template 526 | name: "${name} Rebulk Offset V." 527 | id: "rebulk_offset" 528 | step: 0.1 529 | min_value: 0 530 | max_value: 5 531 | mode: slider 532 | initial_value: "${rebulk_offset_v}" 533 | unit_of_measurement: V 534 | icon: mdi:sine-wave 535 | optimistic: true 536 | - platform: template 537 | name: "${name} Absorption time" 538 | id: "absorption_time" 539 | step: 1 540 | min_value: 0 541 | max_value: 180 542 | mode: slider 543 | initial_value: "${absorption_time}" 544 | unit_of_measurement: min 545 | icon: mdi:clock-start 546 | optimistic: true 547 | - platform: template 548 | name: "${name} Absorption Offset V." 549 | id: "absorption_offset" 550 | step: 0.05 551 | min_value: 0 552 | max_value: 1 553 | mode: slider 554 | initial_value: "${absorption_offset_v}" 555 | unit_of_measurement: V 556 | icon: mdi:sine-wave 557 | optimistic: true 558 | 559 | script: 560 | - id: absorption_script 561 | then: 562 | - lambda: id(charge_status) = "Absorption"; 563 | # delay value in ms 564 | - delay: !lambda "return id(absorption_time).state * 60 * 1000;" 565 | - lambda: id(charge_status) = "EOC"; 566 | 567 | switch: 568 | - platform: template 569 | name: "${name} CAN Charge enabled" 570 | id: switch_charging 571 | optimistic: true 572 | - platform: template 573 | name: "${name} CAN Discharge enabled" 574 | id: switch_discharging 575 | optimistic: true 576 | - platform: template 577 | name: "${name} CAN Force bulk (top bal)" 578 | id: switch_chg_bulk 579 | optimistic: true 580 | - platform: template 581 | name: "${name} CAN Float charge enabled" 582 | id: switch_chg_float 583 | optimistic: true 584 | 585 | 586 | # +--------------------------------------+ 587 | # | CAN bus script | 588 | # +--------------------------------------+ 589 | canbus: 590 | - platform: esp32_can 591 | tx_pin: ${can_tx_pin} 592 | rx_pin: ${can_rx_pin} 593 | can_id: 4 594 | bit_rate: 500kbps 595 | on_frame: 596 | - can_id: 0x305 # Inverter ACK - SMA/LG/Pylon/Goodwe reply 597 | then: 598 | - light.toggle: 599 | id: blue_led 600 | - lambda: |- 601 | id(can_ack_counter) = 0; // Reset ACK counter 602 | id(can_status) = "ON"; // Set CANBUS Status to ON 603 | id(canbus_status).publish_state(id(can_status)); // Publish text sensor 604 | ESP_LOGI("main", "received can id: 0x305 ACK"); 605 | 606 | interval: 607 | - interval: 120s 608 | then: 609 | - lambda: id(can_ack_counter) = 0; // Reset ACK counter for test inverter ACK 610 | 611 | - interval: 100ms 612 | then: 613 | # Start CAN Handling 614 | - if: 615 | condition: 616 | lambda: |- 617 | 618 | if (id(can_ack_counter) < 20) { // Inverter ACK ? => CANBUS ON 619 | 620 | id(can_ack_counter)++; // CANBUS ACK counter ++ 621 | id(can_msg_counter)++; // CANBUS MSG counter ++ 622 | return true; // Condition OK 623 | 624 | } 625 | else if (id(can_status) == "OFF") { // CANBUS already OFF ? 626 | 627 | return false; // Nothing to do 628 | 629 | } 630 | else { 631 | 632 | id(can_status) = "OFF"; // Set CANBUS Status to OFF 633 | id(canbus_status).publish_state(id(can_status)); // Publish text sensor 634 | ESP_LOGI("main", "No rx can 0x305 reply, Inverter not connected/responding..."); 635 | return false; // Condition NOK 636 | 637 | } 638 | 639 | then: 640 | - if: 641 | condition: 642 | lambda: return ((id(can_msg_counter) == 1) & ((${can_protocol} == 1) | (${can_protocol} == 2))); 643 | then: 644 | canbus.send: # Protection Alarms, Warning and Flags ( Pylontech / Goodwe / Seplos ) 645 | can_id: 0x359 646 | data: !lambda |- 647 | 648 | // +---------------------------+ 649 | // | JK-BMS errors bitmask | 650 | // +---------------------------+ 651 | 652 | // 0x8B 0x00 0x00: Battery warning message 0000 0000 0000 0000 653 | // 654 | // Bit 0 Low capacity 1 (alarm), 0 (normal) warning 655 | // Bit 1 Power tube overtemperature 1 (alarm), 0 (normal) alarm 656 | // Bit 2 Charging overvoltage 1 (alarm), 0 (normal) alarm 657 | // Bit 3 Discharging undervoltage 1 (alarm), 0 (normal) alarm 658 | // Bit 4 Battery over temperature 1 (alarm), 0 (normal) alarm 659 | // Bit 5 Charging overcurrent 1 (alarm), 0 (normal) alarm 660 | // Bit 6 Discharging overcurrent 1 (alarm), 0 (normal) alarm 661 | // Bit 7 Cell pressure difference 1 (alarm), 0 (normal) alarm 662 | // Bit 8 Overtemperature alarm in the battery box 1 (alarm), 0 (normal) alarm 663 | // Bit 9 Battery low temperature 1 (alarm), 0 (normal) alarm 664 | // Bit 10 Cell overvoltage 1 (alarm), 0 (normal) alarm 665 | // Bit 11 Cell undervoltage 1 (alarm), 0 (normal) alarm 666 | // Bit 12 309_A protection 1 (alarm), 0 (normal) alarm 667 | // Bit 13 309_A protection 1 (alarm), 0 (normal) alarm 668 | // Bit 14 Reserved 669 | // Bit 15 Reserved 670 | // 671 | // Examples: 672 | // 0x0001 = 00000000 00000001: Low capacity alarm 673 | // 0x0002 = 00000000 00000010: MOS tube over-temperature alarm 674 | // 0x0003 = 00000000 00000011: Low capacity alarm AND power tube over-temperature alarm 675 | 676 | // +---------------------------+ 677 | // | Protection : byte 0 and 1 | 678 | // +---------------------------+ 679 | 680 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 681 | 682 | // JK-BMS alarm ? 683 | if (id(errors_bitmask).state > 1) { 684 | uint16_t jk_errormask = id(errors_bitmask).state; 685 | 686 | if ((jk_errormask & 0x04) | (jk_errormask & 0x80) | (jk_errormask & 0x400)) { // Hight.Voltage.Alarm JK bit 2,7,10 687 | can_mesg[0] = 0x02; // byte0_bit1 (0x02 = bin 10) 688 | id(alarm_status) = "OVP"; 689 | ESP_LOGI("main", "Hight.Voltage.Alarm JK bit 2,7,10 - can_msg[0] : %x", can_mesg[0]); 690 | } 691 | if ((jk_errormask & 0x08) | (jk_errormask & 0x800)) { // Low.Voltage.Alarm JK bit 3,11 692 | can_mesg[0] = can_mesg[0] | 0x04; // byte0_bit2 (0x04 = bin 100) 693 | id(alarm_status) = "UVP"; 694 | ESP_LOGI("main", "Low.Voltage.Alarm JK bit 3,11 - can_msg[0] : %x", can_mesg[0]); 695 | } 696 | if ((jk_errormask & 0x02) | (jk_errormask & 0x10) | (jk_errormask & 0x100)) { // Hight.Temp.Alarm JK bit 1,4,8 697 | can_mesg[0] = can_mesg[0] | 0x08; // byte0_bit3 (0x08 = bin 1000) 698 | id(alarm_status) = "OTP"; 699 | ESP_LOGI("main", "Hight.Temp.Alarm JK bit 1,4,8 - can_msg[0] : %x", can_mesg[0]); 700 | } 701 | if ((jk_errormask & 0x200)) { // Low.Temp.Alarm JK bit 9 702 | can_mesg[0] = can_mesg[0] | 0x10; // byte0_bit4 (0x10 = bin 10000) 703 | id(alarm_status) = "UTP"; 704 | ESP_LOGI("main", "Low.Temp.Alarm JK bit 9 - can_msg[0] : %x", can_mesg[0]); 705 | } 706 | if ((jk_errormask & 0x40)) { // Discharge.Over.Current JK bit 6 707 | can_mesg[0] = can_mesg[0] | 0x80; // byte0_bit7 (0x80 = bin 10000000) 708 | id(alarm_status) = "DOCP"; 709 | ESP_LOGI("main", "Discharge.Over.Current JK bit 6 - can_msg[0] : %x", can_mesg[0]); 710 | } 711 | if ((jk_errormask & 0x20)) { // Charge.Over.Current JK bit 5 712 | can_mesg[1] = 0x01; // byte1_bit0 (0x01 = bin 1) 713 | id(alarm_status) = "COCP"; 714 | ESP_LOGI("main", "Charge.Over.Current JK bit 5 - can_msg[1] : %x", can_mesg[1]); 715 | } 716 | if ((jk_errormask & 0x1000) | (jk_errormask & 0x2000)) { // BMS internal error JK bit 12,13 717 | can_mesg[1] = can_mesg[1] | 0x08; // byte1_bit3 (0x08 = bin 1000) 718 | id(alarm_status) = "BMS"; 719 | ESP_LOGI("main", "BMS internal error JK bit 12,13 - can_msg[1] : %x", can_mesg[1]); 720 | } 721 | if ((jk_errormask & 0x80)) { // Cell Imbalance JK bit 7 722 | can_mesg[1] = can_mesg[1] | 0x10; // byte1_bit4 (0x10 = bin 10000) 723 | ESP_LOGI("main", "Cell Imbalance JK bit 7 - can_msg[1] : %x", can_mesg[1]); 724 | } 725 | } 726 | // No Alarm 727 | else id(alarm_status) = "NoAlarm"; 728 | 729 | // +---------------------------+ 730 | // | Warning : byte 2 and 3 | 731 | // +---------------------------+ 732 | 733 | can_mesg[2] = 0x00; // byte2 (JK-BMS infos not available) 734 | can_mesg[3] = 0x00; // byte3 (JK-BMS infos not available) 735 | 736 | // +---------------------------+ 737 | // | Flags : byte 4 to 7 | 738 | // +---------------------------+ 739 | 740 | int batt_mods = ${batt_modules}; 741 | 742 | can_mesg[4] = batt_mods; // byte4 - Module in parallel 743 | can_mesg[5] = 0x00; // byte5 744 | can_mesg[6] = 0x00; // byte6 745 | can_mesg[7] = 0x00; // byte7 - DIP switches 1,3 10000100 0x84 746 | 747 | ESP_LOGI("main", "send can id: 0x359 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 748 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 749 | 750 | - if: 751 | condition: 752 | lambda: return ((id(can_msg_counter) == 1) & ((${can_protocol} == 3) | (${can_protocol} == 4))); 753 | then: 754 | canbus.send: # Protection Alarms and Warning ( SMA / Victron ) 755 | can_id: 0x35A 756 | data: !lambda |- 757 | 758 | // +---------------------------+ 759 | // | Protection : byte 0,1,2,3 | 760 | // +---------------------------+ 761 | 762 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 763 | 764 | // JK-BMS alarm ? 765 | if (id(errors_bitmask).state > 1) { 766 | uint16_t jk_errormask = id(errors_bitmask).state; 767 | 768 | if ((jk_errormask & 0x04) | (jk_errormask & 0x80) | (jk_errormask & 0x400)) { // Hight.Voltage.Alarm JK bit 2,7,10 769 | can_mesg[0] = 0x04; // byte0_bit2 (0x04 = bin 100) 770 | id(alarm_status) = "OVP"; 771 | ESP_LOGI("main", "Hight.Voltage.Alarm JK bit 2,7,10 - can_msg[0] : %x", can_mesg[0]); 772 | } 773 | if ((jk_errormask & 0x08) | (jk_errormask & 0x800)) { // Low.Voltage.Alarm JK bit 3,11 774 | can_mesg[0] = can_mesg[0] | 0x10; // byte0_bit4 (0x10 = bin 10000) 775 | id(alarm_status) = "UVP"; 776 | ESP_LOGI("main", "Low.Voltage.Alarm JK bit 3,11 - can_msg[0] : %x", can_mesg[0]); 777 | } 778 | if ((jk_errormask & 0x02) | (jk_errormask & 0x10) | (jk_errormask & 0x100)) { // Hight.Temp.Alarm JK bit 1,4,8 779 | can_mesg[0] = can_mesg[0] | 0x40; // byte0_bit6 (0x40 = bin 1000000) 780 | id(alarm_status) = "OTP"; 781 | ESP_LOGI("main", "Hight.Temp.Alarm JK bit 1,4,8 - can_msg[0] : %x", can_mesg[0]); 782 | } 783 | if ((jk_errormask & 0x200)) { // Low.Temp.Alarm JK bit 9 784 | can_mesg[1] = 0x01; // byte1_bit0 (0x01 = bin 1) 785 | id(alarm_status) = "UTP"; 786 | ESP_LOGI("main", "Low.Temp.Alarm JK bit 9 - can_msg[1] : %x", can_mesg[1]); 787 | } 788 | if ((jk_errormask & 0x40)) { // Discharge.Over.Current JK bit 6 789 | can_mesg[1] = can_mesg[1] | 0x40; // byte1_bit6 (0x40 = bin 1000000) 790 | id(alarm_status) = "DOCP"; 791 | ESP_LOGI("main", "Discharge.Over.Current JK bit 6 - can_msg[1] : %x", can_mesg[1]); 792 | } 793 | if ((jk_errormask & 0x20)) { // Charge.Over.Current JK bit 5 794 | can_mesg[2] = 0x01; // byte2_bit0 (0x01 = bin 1) 795 | id(alarm_status) = "COCP"; 796 | ESP_LOGI("main", "Charge.Over.Current JK bit 5 - can_msg[2] : %x", can_mesg[2]); 797 | } 798 | if ((jk_errormask & 0x1000) | (jk_errormask & 0x2000)) { // BMS.Internal.Error JK bit 12,13 799 | can_mesg[2] = can_mesg[2] | 0x40; // byte2_bit6 (0x40 = bin 1000000) 800 | id(alarm_status) = "BMS"; 801 | ESP_LOGI("main", "BMS internal error JK bit 12,13 - can_msg[2] : %x", can_mesg[2]); 802 | } 803 | if ((jk_errormask & 0x80)) { // Cell.Imbalance JK bit 7 804 | can_mesg[3] = 0x01; // byte3_bit0 (0x01 = bin 1) 805 | ESP_LOGI("main", "Cell Imbalance JK bit 7 - can_msg[3] : %x", can_mesg[3]); 806 | } 807 | } 808 | // No Alarm 809 | else id(alarm_status) = "NoAlarm"; 810 | 811 | ESP_LOGI("main", "send can id: 0x35A hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 812 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 813 | 814 | - if: 815 | condition: 816 | lambda: return id(can_msg_counter) == 2; 817 | then: 818 | canbus.send: # BMS instruction : Charge Volts, Charge Amps, Discharge Amps, Min voltage 819 | can_id: 0x351 820 | data: !lambda |- 821 | 822 | // +--------------------------------------+ 823 | // | Charging Logic | 824 | // +--------------------------------------+ 825 | 826 | // Warning : information from JK BMS is not available immediately after boot 827 | 828 | // Alarm : if JK-BMS alarm ! 829 | if (id(errors_bitmask).state > 1) { 830 | id(charge_status) = "Alarm"; 831 | } 832 | // No Alarm => Wait 833 | else if ((id(errors_bitmask).state < 2) & (id(charge_status) == "Alarm")) { 834 | id(charge_status) = "Wait"; 835 | } 836 | // Charge ON : BMS and ESP32 charging switch is ON 837 | else if ((id(charging_switch).state) & (id(switch_charging).state)) { 838 | 839 | // Force Bulk : 'Charging manually (top bal)' switch is ON 840 | if (id(switch_chg_bulk).state) { 841 | id(charge_status) = "Force Bulk"; 842 | } 843 | // No Force Bulk => Wait 844 | else if ((!id(switch_chg_bulk).state) & (id(charge_status) == "Force Bulk")) { 845 | id(charge_status) = "Wait"; 846 | } 847 | // Bulk : Bat. V. <= Rebulk V. ( Absorption V. - Rebulk Offset V. ) ( Bulk : Rebulk V. = 55.2-2.5 = 52.7V by default ) 848 | else if (id(total_voltage).state <= (id(bulk_voltage).state - id(rebulk_offset).state)) { 849 | id(charge_status) = "Bulk"; 850 | if (id(absorption_script).is_running()) id(absorption_script).stop(); 851 | } 852 | // Absorption : Bat. V >= ( Absorption V. - Absorption Offset V. ) ( Absorption V. = 55.2-0.05 = 55.15V by default ) 853 | else if ((id(charge_status) == "Bulk") & (id(total_voltage).state >= (id(bulk_voltage).state - id(absorption_offset).state))) { 854 | id(charge_status) = "Absorption"; 855 | if (!id(absorption_script).is_running()) id(absorption_script).execute(); // 10 % from top start absorption timer 856 | } 857 | // Bulk : (Bat. V. > Rebulk V.) + Wait / ESP32 startup ( Bulk : when starting the ESP32 ) 858 | else if ((id(charge_status) == "Wait")) { 859 | id(charge_status) = "Bulk"; 860 | if (id(absorption_script).is_running()) id(absorption_script).stop(); 861 | } 862 | // Float : (Bat. V. > Rebulk V.) + Float switch ON and End Of Charge ( Float : after Absorption ) 863 | else if ((id(switch_chg_float).state) & (id(charge_status) == "EOC")) { 864 | id(charge_status) = "Float"; 865 | } 866 | // No Float => Wait 867 | else if ((!id(switch_chg_float).state) & (id(charge_status) == "Float")) { 868 | id(charge_status) = "Wait"; 869 | } 870 | } 871 | // Charge OFF 872 | else id(charge_status) = "Wait"; 873 | 874 | // +--------------------------------------+ 875 | // | Charge values | 876 | // +--------------------------------------+ 877 | 878 | // Bulk Charge 879 | if ((id(charge_status) == "Bulk") | (id(charge_status) == "Force Bulk") | (id(charge_status) == "Absorption")) { 880 | id(charging_v) = id(bulk_voltage).state; 881 | id(charging_a) = id(charging_current).state; 882 | } 883 | // Float Charge 884 | else if (id(charge_status) == "Float") { 885 | if (id(total_voltage).state > id(float_voltage).state){ 886 | id(charging_v) = round(id(total_voltage).state * 10)/10; // Actual battery voltage 887 | id(charging_a) = 0; 888 | } else { 889 | id(charging_v) = id(float_voltage).state; 890 | id(charging_a) = id(charging_current).state; 891 | } 892 | } 893 | // End Of Charge (EOC) or Wait : Stop Charging 894 | else if ((id(charge_status) == "EOC") | (id(charge_status) == "Wait")) { 895 | id(charging_v) = round(id(total_voltage).state * 10)/10; // Actual battery voltage 896 | id(charging_a) = 0; 897 | } 898 | 899 | // +--------------------------------------+ 900 | // | Discharge values | 901 | // +--------------------------------------+ 902 | 903 | // Stop Discharging if BMS or ESP32 switch is OFF 904 | if ((!id(discharging_switch).state) | (!id(switch_discharging).state)) id(discharging_a) = 0; 905 | // Stop Discharging if battery voltage is low 906 | else if (id(total_voltage).state <= ${min_discharge_v}) id(discharging_a) = 0; 907 | // Discharging is OK 908 | else id(discharging_a) = ${discharge_a}; 909 | 910 | // +--------------------------------------+ 911 | // | Alarm overwrite values | 912 | // +--------------------------------------+ 913 | 914 | ESP_LOGI("main", "Alarm Status : %s", id(alarm_status).c_str()); 915 | 916 | // Alarm : Stop Charging and Discharging 917 | if ((id(alarm_status) == "OTP") | (id(alarm_status) == "BMS")){ 918 | id(charging_v) = 51.2; 919 | id(charging_a) = 0; 920 | id(discharging_a) = 0; 921 | } 922 | // Alarm : Stop Charging 923 | else if ((id(alarm_status) == "OVP") | (id(alarm_status) == "UTP") | (id(alarm_status) == "COCP")){ 924 | id(charging_v) = 51.2; 925 | id(charging_a) = 0; 926 | } 927 | // Alarm : Stop Discharging 928 | else if ((id(alarm_status) == "UVP") | (id(alarm_status) == "DOCP")){ 929 | id(discharging_a) = 0; 930 | } 931 | 932 | // +--------------------------------------+ 933 | // | CAN messages | 934 | // +--------------------------------------+ 935 | 936 | // Byte [00:01] = CVL : Charge Limit Voltage 937 | // Byte [02:03] = CCL : Charge Limit Current 938 | // Byte [04:05] = DCL : Discharge Limit Current 939 | // Byte [06:07] = DVL : Discharge Limit Voltage 940 | 941 | uint8_t can_mesg[8]; 942 | 943 | can_mesg[0] = uint16_t(id(charging_v) * 10) & 0xff; 944 | can_mesg[1] = uint16_t(id(charging_v) * 10) >> 8 & 0xff; 945 | can_mesg[2] = uint16_t(id(charging_a) * 10) & 0xff; 946 | can_mesg[3] = uint16_t(id(charging_a) * 10) >> 8 & 0xff; 947 | can_mesg[4] = uint16_t(id(discharging_a) * 10) & 0xff; 948 | can_mesg[5] = uint16_t(id(discharging_a) * 10) >> 8 & 0xff; 949 | can_mesg[6] = uint16_t(${min_discharge_v} * 10) & 0xff; 950 | can_mesg[7] = uint16_t(${min_discharge_v} * 10) >> 8 & 0xff; 951 | 952 | // Publish text sensor 953 | id(charging_status).publish_state(id(charge_status)); 954 | 955 | // Logs 956 | ESP_LOGI("main", "send can id: 0x351 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 957 | ESP_LOGI("main", "Charge Status : %s", id(charge_status).c_str()); 958 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 959 | 960 | - if: 961 | condition: 962 | lambda: return id(can_msg_counter) == 3; 963 | then: 964 | canbus.send: # Actual State of Charge (SOC) / State of Health (SOH) 965 | can_id: 0x355 966 | data: !lambda |- 967 | int soh = round(((id(charging_cycles).state/${max_cycles})-1)*-100); 968 | uint8_t can_mesg[4]; 969 | can_mesg[0] = uint16_t(id(capacity_remaining).state) & 0xff; 970 | can_mesg[1] = uint16_t(id(capacity_remaining).state) >> 8 & 0xff; 971 | can_mesg[2] = soh & 0xff; 972 | can_mesg[3] = soh >> 8 & 0xff; 973 | ESP_LOGI("main", "send can id: 0x355 hex: %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3]); 974 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3]}; 975 | 976 | - if: 977 | condition: 978 | lambda: return id(can_msg_counter) == 4; 979 | then: 980 | canbus.send: # Actual Voltage / Current / Temperature / Cycles (Deye 0x305 ACK) 981 | can_id: 0x356 982 | data: !lambda |- 983 | 984 | // Byte [00:01] : Actual Voltage 985 | // Byte [02:03] : Actual Current 986 | // Byte [04:05] : Actual Temperature 987 | // Byte [06:07] : Actual Cycles number (Sofar) 988 | 989 | uint8_t can_mesg[8]; 990 | can_mesg[0] = uint16_t(id(total_voltage).state * 100) & 0xff; 991 | can_mesg[1] = uint16_t(id(total_voltage).state * 100) >> 8 & 0xff; 992 | can_mesg[2] = int16_t(id(current).state * 10) & 0xff; 993 | can_mesg[3] = int16_t(id(current).state * 10) >> 8 & 0xff; 994 | can_mesg[4] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) & 0xff; 995 | can_mesg[5] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; 996 | can_mesg[6] = uint16_t(id(charging_cycles).state) & 0xff; 997 | can_mesg[7] = uint16_t(id(charging_cycles).state) >> 8 & 0xff; 998 | ESP_LOGI("main", "send can id: 0x356 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 999 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1000 | 1001 | - if: 1002 | condition: 1003 | lambda: return ((id(can_msg_counter) == 5) & ((${can_protocol} == 1) | (${can_protocol} == 2))); 1004 | then: 1005 | canbus.send: # Request flag to Enable/Disable: Charge, Discharge ( Pylontech / Goodwe / Seplos ) 1006 | can_id: 0x35C 1007 | data: !lambda |- 1008 | uint8_t can_mesg[2]; 1009 | can_mesg[0] = 0x00; 1010 | can_mesg[1] = 0x00; 1011 | 1012 | // Bit 7 : Charge enable 1013 | if ((id(charging_switch).state) & (id(switch_charging).state)) 1014 | can_mesg[0] = 0x80; 1015 | 1016 | // Bit 6 : Discharge enable 1017 | if ((id(discharging_switch).state) & (id(switch_discharging).state)) 1018 | can_mesg[0] = can_mesg[0] | 0x40; 1019 | 1020 | ESP_LOGI("main", "send can id: 0x35C hex: %x %x", can_mesg[0], can_mesg[1]); 1021 | return {can_mesg[0], can_mesg[1]}; 1022 | 1023 | - if: 1024 | condition: 1025 | lambda: return ((id(can_msg_counter) == 6) & (${can_protocol} == 2)); 1026 | then: 1027 | canbus.send: # Actual Max Cell Temp, Min Cell Temp, Max Cell V, Min Cell V ( Pylontech / Goodwe / Seplos ) 1028 | can_id: 0x70 1029 | data: !lambda |- 1030 | 1031 | // Byte [00:01] : Max cell temperature 1032 | // Byte [02:03] : Min cell temperature 1033 | // Byte [04:05] : Max cell voltage 1034 | // Byte [06:07] : Min cell voltage 1035 | 1036 | int max_cell_voltage_i = id(max_cell_voltage).state * 100.0; 1037 | int min_cell_voltage_i = id(min_cell_voltage).state * 100.0; 1038 | uint8_t can_mesg[8]; 1039 | can_mesg[0] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) & 0xff; 1040 | can_mesg[1] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; 1041 | can_mesg[2] = int16_t(min(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) & 0xff; 1042 | can_mesg[3] = int16_t(min(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; 1043 | can_mesg[4] = max_cell_voltage_i & 0xff; 1044 | can_mesg[5] = max_cell_voltage_i >> 8 & 0xff; 1045 | can_mesg[6] = min_cell_voltage_i & 0xff; 1046 | can_mesg[7] = min_cell_voltage_i >> 8 & 0xff; 1047 | ESP_LOGI("main", "send can id: 0x70 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1048 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1049 | 1050 | - if: 1051 | condition: 1052 | lambda: return ((id(can_msg_counter) == 6) & (${can_protocol} == 2)); 1053 | then: 1054 | - canbus.send: # Actual Max Cell Temp ID, Min Cell Temp ID, Max Cell V ID, Min Cell ID ( Pylontech / Goodwe / Seplos ) 1055 | can_id: 0x371 1056 | data: !lambda |- 1057 | 1058 | // Byte [00:01] : Max cell temperature ID 1059 | // Byte [02:03] : Min cell temperature ID 1060 | // Byte [04:05] : Max cell voltage ID 1061 | // Byte [06:07] : Min cell voltage ID 1062 | 1063 | uint8_t can_mesg[8]; 1064 | 1065 | // Min-Max Temp. Sensor ID ? 1066 | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ 1067 | can_mesg[0] = 0x01; 1068 | can_mesg[2] = 0x02; 1069 | } 1070 | else { 1071 | can_mesg[0] = 0x02; 1072 | can_mesg[2] = 0x01; 1073 | } 1074 | 1075 | can_mesg[1] = 0x00; 1076 | can_mesg[3] = 0x00; 1077 | can_mesg[4] = uint16_t(id(max_voltage_cell).state) & 0xff; 1078 | can_mesg[5] = uint16_t(id(max_voltage_cell).state) >> 8 & 0xff; 1079 | can_mesg[6] = uint16_t(id(min_voltage_cell).state) & 0xff; 1080 | can_mesg[7] = uint16_t(id(min_voltage_cell).state) >> 8 & 0xff; 1081 | ESP_LOGI("main", "send can id: 0x371 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1082 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1083 | 1084 | - if: 1085 | condition: 1086 | lambda: return ((id(can_msg_counter) == 7) & (${can_protocol} == 4)); 1087 | then: 1088 | - canbus.send: # Battery modules information ( Victron ) 1089 | can_id: 0x372 1090 | data: !lambda |- 1091 | 1092 | // Byte [00:01] : Nbr. of battery modules online 1093 | // Byte [02:03] : Nbr. of modules blocking charge 1094 | // Byte [04:05] : Nbr. of modules blocking discharge 1095 | // Byte [06:07] : Nbr. of battery modules offline 1096 | 1097 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 1098 | can_mesg[0] = 0x01; 1099 | 1100 | ESP_LOGI("main", "send can id: 0x372 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1101 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1102 | 1103 | - if: 1104 | condition: 1105 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1106 | then: 1107 | canbus.send: # Actual Min Cell V, Max Cell V, Min Cell Temp (Kelvin), Max Cell Temp (Kelvin) ( Victron ) 1108 | can_id: 0x373 1109 | data: !lambda |- 1110 | 1111 | // Byte [00:01] : Min cell voltage 1112 | // Byte [02:03] : Max cell voltage 1113 | // Byte [04:05] : Min cell temperature 1114 | // Byte [06:07] : Max cell temperature 1115 | 1116 | int min_cell_voltage_i = id(min_cell_voltage).state * 1000.0; 1117 | int max_cell_voltage_i = id(max_cell_voltage).state * 1000.0; 1118 | int min_temp_kelvin = min(id(temperature_sensor_1).state, id(temperature_sensor_2).state) + 273.15; 1119 | int max_temp_kelvin = max(id(temperature_sensor_1).state, id(temperature_sensor_2).state) + 273.15; 1120 | 1121 | uint8_t can_mesg[8]; 1122 | can_mesg[0] = min_cell_voltage_i & 0xff; 1123 | can_mesg[1] = min_cell_voltage_i >> 8 & 0xff; 1124 | can_mesg[2] = max_cell_voltage_i & 0xff; 1125 | can_mesg[3] = max_cell_voltage_i >> 8 & 0xff; 1126 | can_mesg[4] = min_temp_kelvin & 0xff; 1127 | can_mesg[5] = min_temp_kelvin >> 8 & 0xff; 1128 | can_mesg[6] = max_temp_kelvin & 0xff; 1129 | can_mesg[7] = max_temp_kelvin >> 8 & 0xff; 1130 | 1131 | ESP_LOGI("main", "send can id: 0x373 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1132 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1133 | 1134 | - if: 1135 | condition: 1136 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1137 | then: 1138 | - canbus.send: # Min cell voltage ID [ASCII] ( Victron ) 1139 | can_id: 0x374 1140 | data: !lambda |- 1141 | 1142 | int cell_id = id(min_voltage_cell).state; 1143 | 1144 | ESP_LOGI("main", "send can id: 0x374 [ASCII] Min cell voltage ID : %i", cell_id); 1145 | 1146 | if (cell_id == 1) return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1147 | else if (cell_id == 2) return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1148 | else if (cell_id == 3) return {0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1149 | else if (cell_id == 4) return {0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1150 | else if (cell_id == 5) return {0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1151 | else if (cell_id == 6) return {0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1152 | else if (cell_id == 7) return {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1153 | else if (cell_id == 8) return {0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1154 | else if (cell_id == 9) return {0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1155 | else if (cell_id == 10) return {0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1156 | else if (cell_id == 11) return {0x31, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1157 | else if (cell_id == 12) return {0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1158 | else if (cell_id == 13) return {0x31, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1159 | else if (cell_id == 14) return {0x31, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1160 | else if (cell_id == 15) return {0x31, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1161 | else if (cell_id == 16) return {0x31, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1162 | else return {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1163 | 1164 | - if: 1165 | condition: 1166 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1167 | then: 1168 | - canbus.send: # Max cell voltage ID [ASCII] ( Victron ) 1169 | can_id: 0x375 1170 | data: !lambda |- 1171 | 1172 | int cell_id = id(max_voltage_cell).state; 1173 | 1174 | ESP_LOGI("main", "send can id: 0x375 [ASCII] Max cell voltage ID : %i", cell_id); 1175 | 1176 | if (cell_id == 1) return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1177 | else if (cell_id == 2) return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1178 | else if (cell_id == 3) return {0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1179 | else if (cell_id == 4) return {0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1180 | else if (cell_id == 5) return {0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1181 | else if (cell_id == 6) return {0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1182 | else if (cell_id == 7) return {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1183 | else if (cell_id == 8) return {0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1184 | else if (cell_id == 9) return {0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1185 | else if (cell_id == 10) return {0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1186 | else if (cell_id == 11) return {0x31, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1187 | else if (cell_id == 12) return {0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1188 | else if (cell_id == 13) return {0x31, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1189 | else if (cell_id == 14) return {0x31, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1190 | else if (cell_id == 15) return {0x31, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1191 | else if (cell_id == 16) return {0x31, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1192 | else return {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1193 | 1194 | - if: 1195 | condition: 1196 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1197 | then: 1198 | - canbus.send: # Min cell temperature ID [ASCII] ( Victron ) 1199 | can_id: 0x376 1200 | data: !lambda |- 1201 | 1202 | // Min Temp. Sensor ID ? 1203 | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ 1204 | ESP_LOGI("main", "send can id: 0x376 [ASCII] Min Temp. Sensor ID : 2"); 1205 | return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1206 | } 1207 | else { 1208 | ESP_LOGI("main", "send can id: 0x376 [ASCII] Min Temp. Sensor ID : 1"); 1209 | return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1210 | } 1211 | 1212 | - if: 1213 | condition: 1214 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1215 | then: 1216 | - canbus.send: # Max cell temperature ID [ASCII] ( Victron ) 1217 | can_id: 0x377 1218 | data: !lambda |- 1219 | 1220 | // Max Temp. Sensor ID ? 1221 | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ 1222 | ESP_LOGI("main", "send can id: 0x377 [ASCII] Max Temp. Sensor ID : 1"); 1223 | return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1224 | } 1225 | else { 1226 | ESP_LOGI("main", "send can id: 0x377 [ASCII] Max Temp. Sensor ID : 2"); 1227 | return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1228 | } 1229 | 1230 | - if: 1231 | condition: 1232 | lambda: return ((id(can_msg_counter) == 9) & ((${can_protocol} == 2) | (${can_protocol} == 4))); 1233 | then: 1234 | - canbus.send: # Battery Installed Capacity Ah ( Victron, Sol-Ark, Luxpower ) 1235 | can_id: 0x379 1236 | data: !lambda |- 1237 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 1238 | can_mesg[0] = uint16_t(id(total_battery_capacity_setting).state) & 0xff; 1239 | can_mesg[1] = uint16_t(id(total_battery_capacity_setting).state) >> 8 & 0xff; 1240 | 1241 | ESP_LOGI("main", "send can id: 0x379 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1242 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1243 | 1244 | - if: 1245 | condition: 1246 | lambda: return ((id(can_msg_counter) == 10) & (${can_protocol} == 4)); 1247 | then: 1248 | - canbus.send: # Product identification [ASCII] ( Victron ) 1249 | can_id: 0x382 1250 | data: !lambda |- 1251 | ESP_LOGI("main", "send can id: 0x382 [ASCII] Product : JK-BMS"); 1252 | return {0x4A, 0x4B, 0x2D, 0x42, 0x4D, 0x53, 0x00, 0x00}; // JK-BMS 1253 | 1254 | - if: 1255 | condition: 1256 | lambda: return ((id(can_msg_counter) == 11) & ((${can_protocol} == 3) | (${can_protocol} == 4))); 1257 | then: 1258 | - canbus.send: # Battery information ( SMA, Victron ) 1259 | can_id: 0x35F 1260 | data: !lambda |- 1261 | 1262 | // SMA Victron 1263 | // Byte [00:01] : Bat-Type Product ID 1264 | // Byte [02:03] : BMS Version Firmware version (1.16 => HEX [01:10]) 1265 | // Byte [04:05] : Bat-Capacity Available Capacity Ah 1266 | // Byte [06:07] : Manufacturer ID Hardware version 1267 | 1268 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 1269 | can_mesg[2] = 0x01; 1270 | can_mesg[3] = 0x10; 1271 | can_mesg[4] = uint16_t(id(total_battery_capacity_setting).state) & 0xff; 1272 | can_mesg[5] = uint16_t(id(total_battery_capacity_setting).state) >> 8 & 0xff; 1273 | 1274 | ESP_LOGI("main", "send can id: 0x35F hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1275 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1276 | 1277 | - if: 1278 | condition: 1279 | lambda: return id(can_msg_counter) == 12; 1280 | then: 1281 | - canbus.send: 1282 | can_id: 0x35E # Manufacturer name 1283 | data: !lambda |- 1284 | if (${can_bms_name} == 1){ 1285 | ESP_LOGI("main", "send can id: 0x35E ASCII : PYLON"); 1286 | return {0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20}; // PYLON ( recognized by Deye, display PYLON name and SOH ) 1287 | } 1288 | else if (${can_bms_name} == 2){ 1289 | ESP_LOGI("main", "send can id: 0x35E ASCII : GOODWE"); 1290 | return {0x47, 0x4F, 0x4F, 0x44, 0x57, 0x45, 0x20, 0x20}; // GOODWE 1291 | } 1292 | else if (${can_bms_name} == 3){ 1293 | ESP_LOGI("main", "send can id: 0x35E ASCII : SHEnergy"); 1294 | return {0x53, 0x48, 0x45, 0x6E, 0x65, 0x72, 0x67, 0x79}; // SHEnergy (SEPLOS) 1295 | } 1296 | # Reset counter 1297 | - lambda: id(can_msg_counter) = 0; 1298 | -------------------------------------------------------------------------------- /old_version/esp32_ble_jk-bms-can.yaml: -------------------------------------------------------------------------------- 1 | # JK-BMS-CAN ( PYLON, Seplos, GoodWe, SMA and Victron CAN bus protocol ) 2 | 3 | # esp32_ble_jk-bms-can.yaml is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation, either version 3 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | # See the GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # V1.16.4 Sleeper85 : Improved Charging Logic for ESP32 startup/reboot and Float charge, Add CAN ID 0x356 bytes [06:07] cycles for Sofar, Change switch name 17 | # V1.16.3 Sleeper85 : ID 0x379 will be sent when choosing protocol 2 or 4 (Battery Capacity for Victron, Sol-Ark and Luxpower) 18 | # V1.16.2 Sleeper85 : Split the "Charge/Discharge values" section and added instructions for "Stop Discharging" + Set "esp-idf" framework by default 19 | # V1.16.1 Sleeper85 : Slider charging_current max value = ${charge_a}, Improved Alarm/Charging/Discharging Logic, Improved CAN protocol and Victron support 20 | # V1.15.5 Sleeper85 : Improved code and set api "reboot_timout" to "0s" by default (no reboot without HA) 21 | # V1.15.4 Sleeper85 : Improved documentation for API, Web Server and WiFi settings 22 | # V1.15.3 Sleeper85 : Add 'CAN Protocol Settings' and new CAN ID based on the SMA and Victron protocol (alpha) 23 | # V1.15.2 Sleeper85 : Improved Alarm handling, all alarms will set charge/discharge current to 0A and set 'Charging Status' to Alarm 24 | # V1.15.1 Sleeper85 : New CANBUS script with CANBUS Status in HA, stop sending CAN messages if the inverter is not responding (fix WDT reboot issues) 25 | # V1.14.3 Sleeper85 : Improved documentation + Charging Voltage tips for Deye 26 | # V1.14.2 Sleeper85 : Improve 'Charging Voltage' behavior 27 | # V1.14.1 Sleeper85 : Add 'Float charge function' 28 | # V1.13.6 Sleeper85 : Add 'Absorption time' and 'Absorption Offset V.' slider 29 | # V1.13.5 Sleeper85 : Set CAN manufacter to "PYLON" for improve compatibility with Deye and other inverters 30 | # V1.13.4 Sleeper85 : Improve 'Charge Status' behavior + add 'Rebulk Offset V.' slider 31 | # V1.13.3 uksa007 : Improve compatibility with Deye and other inverters 32 | # V1.13.2 uksa007 : Send Max Temperature of T1, T2 to inverter 33 | # V1.13.1 uksa007 : Fix compile issues with new version of ESPhome 2023.4.0, set rebulk offset to 2.5 34 | 35 | substitutions: 36 | # +--------------------------------------+ 37 | # name that will appear in esphome and homeassistant. 38 | name: jk-bms-ble-can 39 | # +--------------------------------------+ 40 | # | Bluetooth Settings | 41 | # +--------------------------------------+ 42 | # Please use "JK02_24S" if you own a old JK-BMS < hardware version 11.0 (hardware version >= 6.0 and < 11.0) 43 | # Please use "JK02_32S" if you own a new JK-BMS >= hardware version 11.0 (f.e. JK-B2A8S20P hw 11.XW, sw 11.26) 44 | # Please use "JK04" if you have some old JK-BMS <= hardware version 3.0 (f.e. JK-B2A16S hw 3.0, sw. 3.3.0) 45 | protocol_version: JK02_32S 46 | mac_address: C8:47:8C:10:7E:AB 47 | # +--------------------------------------+ 48 | # Number of Battery modules max 8. Each LX U5.4-L battery is 5.4kWh, select the number closest to your capactiy eg 3.2V * 280Ah * 16 = 14.3kWh 49 | batt_modules: "3" 50 | # +--------------------------------------+ 51 | # | Battery Charge Settings | 52 | # +--------------------------------------+ 53 | # Tips for Deye inverter : Add 0.1v to the settings below because the Deye charging voltage is always 0.1v lower than requested. 54 | # Float V. : 53.7v (3.35v/cell - Natural voltage of a fully charged cell at rest, I advise you not to go higher.) 55 | # Absorption V : 55.3v (3.45v/cell - It's not necessary to use a charging voltage higher than 55.2V for a full charge.) 56 | # Absorption Offset V. : 0.15v (The absorption phase will start at 55.15v (BMS voltage). Warning: the BMS voltage must be correctly calibrated.) 57 | # +--------------------------------------+ 58 | # This is max charging amps eg 100A, for Bulk - Constant Current charging(CC), should be at least 10A less than BMS change current protection, 0.5C max 59 | # 100A * 50V = 5000W 60 | charge_a: "100" 61 | # Float Voltage : corresponds to the voltage at which the battery would be maintained at the end of the absorption phase. (53.6v eg 3.35v/cell for 16 cells 48V battery) 62 | float_v: "53.6" 63 | # Absorption Voltage : corresponds to the Bulk voltage that will be used to charge the battery. (55.2v eg 3.45v/cell for 16 cells 48V battery) 64 | absorption_v: "55.2" 65 | # Absorption time in minutes to hold charge voltage after charge voltage is reached eg 30 66 | absorption_time: "30" 67 | # Absorption offset, x Volts below absorption voltage battery will start the absorption timer, eg 55.2-0.05 = 52.15v 68 | absorption_offset_v: "0.05" 69 | # Rebulk offset, x Volts below absorption voltage battery will request rebulk, eg 55.2-2.5 = 52.7v 70 | rebulk_offset_v: "2.5" 71 | # +--------------------------------------+ 72 | # | Battery Discharge Settings | 73 | # +--------------------------------------+ 74 | # Max discharge amps eg 120, should be at least 10A less than BMS over discharge current protection, 0.5C max 75 | # 120A * 50V = 6000W 76 | discharge_a: "120" 77 | # Minimum discharge voltage eg 48v/16 = 3V per cell 78 | min_discharge_v: "48" 79 | # +--------------------------------------+ 80 | # | Battery State of Health (SOH) | 81 | # +--------------------------------------+ 82 | # Maximum charging cycles is used to calculate the battey SOH, LF280K v3 =8000.0, LF280K v2 =6000.0, LF280=3000.0 (decimal is required) 83 | max_cycles: "6000.0" 84 | # +--------------------------------------+ 85 | # | CAN Protocol Settings | 86 | # +--------------------------------------+ 87 | # CAN BMS Name (0x35E) : 0 NoSent / 1 PYLON / 2 GOODWE / 3 SEPLOS 88 | can_bms_name: "1" 89 | # CAN Protocol 90 | # 1 : PYLON 1.2 (Deye) 91 | # 2 : SEPLOS 1.0, PYLON 1.3, GOODWE 1.5 (GoodWe, Sol-Ark, Luxpower) 92 | # 3 : SMA (Sunny Island) 93 | # 4 : VICTRON 94 | can_protocol: "1" 95 | # +--------------------------------------+ 96 | # | ESP32 CAN port pins | 97 | # +--------------------------------------+ 98 | # GPIO pins your CAN bus transceiver (TJA1050, TJA1051T or SN65HVD230) is connected to the ESP, note! TX->TX and RX->RX. 99 | can_tx_pin: GPIO23 100 | can_rx_pin: GPIO22 101 | 102 | # +------------------------------------------------------------------+ 103 | # | ** The settings below can be modified according to your needs ** | 104 | # +------------------------------------------------------------------+ 105 | external_components_source: github://syssi/esphome-jk-bms@main 106 | # components 107 | # github://syssi/esphome-jk-bms@main 108 | 109 | esphome: 110 | name: ${name} 111 | on_boot: 112 | then: 113 | - switch.turn_on: switch_charging 114 | - switch.turn_on: switch_discharging 115 | - switch.turn_on: switch_chg_float 116 | 117 | # +--------------------------------------+ 118 | # | ESP32 settings | 119 | # +--------------------------------------+ 120 | # For a stable Bluetooth connection keep the "esp-idf" framework 121 | esp32: 122 | board: esp32doit-devkit-v1 123 | framework: 124 | type: esp-idf 125 | 126 | external_components: 127 | - source: ${external_components_source} 128 | refresh: 0s 129 | 130 | logger: 131 | # level: DEBUG 132 | 133 | ota: 134 | on_begin: 135 | then: 136 | - lambda: id(enable_bluetooth_connection).turn_off(); 137 | - logger.log: "BLE shutdown for flashing" 138 | 139 | # Please use the native `api` component instead of the `mqtt` section. 140 | # If you use Home Assistant, the native API is more lightweight. 141 | # If there is no HA server connected to this API, the ESP32 reboots every 15 minutes to try to resolve the problem. 142 | # If you don't use Home Assistant please uncomment the "reboot_timeout: 0s" option. 143 | api: 144 | reboot_timeout: 0s 145 | 146 | # If you don't want to use ESPHome's native API you can use MQQT instead. 147 | # In this case don't forget to remove the 'api:' section. 148 | # mqtt: 149 | # broker: !secret mqtt_host 150 | # username: !secret mqtt_username 151 | # password: !secret mqtt_password 152 | # id: mqtt_client 153 | 154 | # In the event of problems with the WiFi network, the ESP32 will reboot every 15 minutes to try to resolve the problem. 155 | # If we don't want to connect the ESP32 to the WiFi network please remove the 4 lines below. 156 | wifi: 157 | ssid: !secret wifi_ssid 158 | password: !secret wifi_password 159 | domain: !secret domain 160 | 161 | #web_server: 162 | # port: 80 163 | # log: false 164 | # ota: false 165 | 166 | # +--------------------------------------+ 167 | # | ** Don't make changes below this ** | 168 | # +--------------------------------------+ 169 | 170 | globals: 171 | - id: can_ack_counter 172 | type: int 173 | restore_value: no 174 | initial_value: '0' 175 | - id: charge_status 176 | type: std::string 177 | restore_value: no 178 | initial_value: '"Wait"' 179 | - id: can_status 180 | type: std::string 181 | restore_value: no 182 | initial_value: '"OFF"' 183 | - id: alarm_status 184 | type: std::string 185 | restore_value: no 186 | initial_value: '"NoAlarm"' 187 | - id: charging_v 188 | type: float 189 | restore_value: no 190 | initial_value: '0.0' 191 | - id: charging_a 192 | type: int 193 | restore_value: no 194 | initial_value: '0' 195 | - id: discharging_a 196 | type: int 197 | restore_value: no 198 | initial_value: '0' 199 | - id: can_msg_counter 200 | type: int 201 | restore_value: no 202 | initial_value: '0' 203 | 204 | output: 205 | - platform: gpio 206 | pin: 2 207 | id: led 208 | inverted: true 209 | 210 | light: 211 | - platform: binary 212 | output: led 213 | id: blue_led 214 | name: "Blue LED" 215 | internal: true 216 | 217 | # +--------------------------------------+ 218 | # | JK-BMS BLE connection | 219 | # +--------------------------------------+ 220 | 221 | esp32_ble_tracker: 222 | on_ble_advertise: 223 | then: 224 | - lambda: |- 225 | if (x.get_name().rfind("JK-", 0) == 0) { 226 | ESP_LOGI("ble_adv", "New JK-BMS found"); 227 | ESP_LOGI("ble_adv", " Name: %s", x.get_name().c_str()); 228 | ESP_LOGI("ble_adv", " MAC address: %s", x.address_str().c_str()); 229 | ESP_LOGD("ble_adv", " Advertised service UUIDs:"); 230 | for (auto uuid : x.get_service_uuids()) { 231 | ESP_LOGD("ble_adv", " - %s", uuid.to_string().c_str()); 232 | } 233 | } 234 | 235 | ble_client: 236 | - mac_address: ${mac_address} 237 | id: client0 238 | 239 | jk_bms_ble: 240 | - ble_client_id: client0 241 | protocol_version: ${protocol_version} 242 | throttle: 5s 243 | id: bms0 244 | # enable_fake_traffic: true 245 | 246 | # +--------------------------------------+ 247 | 248 | binary_sensor: 249 | - platform: jk_bms_ble 250 | balancing: 251 | name: "${name} BMS Balancing" 252 | charging: 253 | name: "${name} BMS Charging" 254 | discharging: 255 | name: "${name} BMS Discharging" 256 | online_status: 257 | name: "${name} Online Status" 258 | 259 | button: 260 | - platform: jk_bms_ble 261 | retrieve_settings: 262 | name: "${name} retrieve settings" 263 | retrieve_device_info: 264 | name: "${name} retrieve device info" 265 | 266 | number: 267 | - platform: jk_bms_ble 268 | jk_bms_ble_id: bms0 269 | balance_trigger_voltage: 270 | name: "${name} balance trigger voltage" 271 | cell_count: 272 | name: "${name} cell count" 273 | total_battery_capacity: 274 | name: "${name} total battery capacity" 275 | cell_voltage_overvoltage_protection: 276 | name: "${name} cell voltage overvoltage protection" 277 | cell_voltage_overvoltage_recovery: 278 | name: "${name} cell voltage overvoltage recovery" 279 | cell_voltage_undervoltage_protection: 280 | name: "${name} cell voltage undervoltage protection" 281 | cell_voltage_undervoltage_recovery: 282 | name: "${name} cell voltage undervoltage recovery" 283 | balance_starting_voltage: 284 | name: "${name} balance starting voltage" 285 | voltage_calibration: 286 | name: "${name} voltage calibration" 287 | current_calibration: 288 | name: "${name} current calibration" 289 | power_off_voltage: 290 | name: "${name} power off voltage" 291 | max_balance_current: 292 | name: "${name} max balance current" 293 | max_charge_current: 294 | name: "${name} max charge current" 295 | max_discharge_current: 296 | name: "${name} max discharge current" 297 | # +--------------------------------------+ 298 | # | Slider | 299 | # +--------------------------------------+ 300 | - platform: template 301 | name: "${name} Bulk voltage" 302 | id: "bulk_voltage" 303 | step: 0.1 304 | min_value: 52.8 305 | max_value: 57.6 306 | mode: slider 307 | initial_value: "${absorption_v}" 308 | unit_of_measurement: V 309 | icon: mdi:battery-charging 310 | optimistic: true 311 | - platform: template 312 | name: "${name} Float voltage" 313 | id: "float_voltage" 314 | step: 0.1 315 | min_value: 52.8 316 | max_value: 57.6 317 | mode: slider 318 | initial_value: "${float_v}" 319 | unit_of_measurement: V 320 | icon: mdi:battery-charging 321 | optimistic: true 322 | - platform: template 323 | name: "${name} Charging current max" 324 | id: "charging_current" 325 | step: 1 326 | min_value: 0 327 | max_value: "${charge_a}" 328 | mode: slider 329 | initial_value: "${charge_a}" 330 | unit_of_measurement: A 331 | icon: mdi:current-dc 332 | optimistic: true 333 | - platform: template 334 | name: "${name} Rebulk Offset V." 335 | id: "rebulk_offset" 336 | step: 0.1 337 | min_value: 0 338 | max_value: 5 339 | mode: slider 340 | initial_value: "${rebulk_offset_v}" 341 | unit_of_measurement: V 342 | icon: mdi:sine-wave 343 | optimistic: true 344 | - platform: template 345 | name: "${name} Absorption time" 346 | id: "absorption_time" 347 | step: 1 348 | min_value: 0 349 | max_value: 180 350 | mode: slider 351 | initial_value: "${absorption_time}" 352 | unit_of_measurement: min 353 | icon: mdi:clock-start 354 | optimistic: true 355 | - platform: template 356 | name: "${name} Absorption Offset V." 357 | id: "absorption_offset" 358 | step: 0.05 359 | min_value: 0 360 | max_value: 1 361 | mode: slider 362 | initial_value: "${absorption_offset_v}" 363 | unit_of_measurement: V 364 | icon: mdi:sine-wave 365 | optimistic: true 366 | 367 | sensor: 368 | - platform: jk_bms_ble 369 | jk_bms_ble_id: bms0 370 | min_cell_voltage: 371 | id: min_cell_voltage 372 | name: "${name} min cell voltage" 373 | max_cell_voltage: 374 | id: max_cell_voltage 375 | name: "${name} max cell voltage" 376 | min_voltage_cell: 377 | id: min_voltage_cell 378 | name: "${name} min voltage cell" 379 | max_voltage_cell: 380 | id: max_voltage_cell 381 | name: "${name} max voltage cell" 382 | delta_cell_voltage: 383 | name: "${name} delta cell voltage" 384 | average_cell_voltage: 385 | name: "${name} average cell voltage" 386 | cell_voltage_1: 387 | name: "${name} cell voltage 1" 388 | cell_voltage_2: 389 | name: "${name} cell voltage 2" 390 | cell_voltage_3: 391 | name: "${name} cell voltage 3" 392 | cell_voltage_4: 393 | name: "${name} cell voltage 4" 394 | cell_voltage_5: 395 | name: "${name} cell voltage 5" 396 | cell_voltage_6: 397 | name: "${name} cell voltage 6" 398 | cell_voltage_7: 399 | name: "${name} cell voltage 7" 400 | cell_voltage_8: 401 | name: "${name} cell voltage 8" 402 | cell_voltage_9: 403 | name: "${name} cell voltage 9" 404 | cell_voltage_10: 405 | name: "${name} cell voltage 10" 406 | cell_voltage_11: 407 | name: "${name} cell voltage 11" 408 | cell_voltage_12: 409 | name: "${name} cell voltage 12" 410 | cell_voltage_13: 411 | name: "${name} cell voltage 13" 412 | cell_voltage_14: 413 | name: "${name} cell voltage 14" 414 | cell_voltage_15: 415 | name: "${name} cell voltage 15" 416 | cell_voltage_16: 417 | name: "${name} cell voltage 16" 418 | # cell_voltage_17: 419 | # name: "${name} cell voltage 17" 420 | # cell_voltage_18: 421 | # name: "${name} cell voltage 18" 422 | # cell_voltage_19: 423 | # name: "${name} cell voltage 19" 424 | # cell_voltage_20: 425 | # name: "${name} cell voltage 20" 426 | # cell_voltage_21: 427 | # name: "${name} cell voltage 21" 428 | # cell_voltage_22: 429 | # name: "${name} cell voltage 22" 430 | # cell_voltage_23: 431 | # name: "${name} cell voltage 23" 432 | # cell_voltage_24: 433 | # name: "${name} cell voltage 24" 434 | cell_resistance_1: 435 | name: "${name} cell resistance 1" 436 | cell_resistance_2: 437 | name: "${name} cell resistance 2" 438 | cell_resistance_3: 439 | name: "${name} cell resistance 3" 440 | cell_resistance_4: 441 | name: "${name} cell resistance 4" 442 | cell_resistance_5: 443 | name: "${name} cell resistance 5" 444 | cell_resistance_6: 445 | name: "${name} cell resistance 6" 446 | cell_resistance_7: 447 | name: "${name} cell resistance 7" 448 | cell_resistance_8: 449 | name: "${name} cell resistance 8" 450 | cell_resistance_9: 451 | name: "${name} cell resistance 9" 452 | cell_resistance_10: 453 | name: "${name} cell resistance 10" 454 | cell_resistance_11: 455 | name: "${name} cell resistance 11" 456 | cell_resistance_12: 457 | name: "${name} cell resistance 12" 458 | cell_resistance_13: 459 | name: "${name} cell resistance 13" 460 | cell_resistance_14: 461 | name: "${name} cell resistance 14" 462 | cell_resistance_15: 463 | name: "${name} cell resistance 15" 464 | cell_resistance_16: 465 | name: "${name} cell resistance 16" 466 | # cell_resistance_17: 467 | # name: "${name} cell resistance 17" 468 | # cell_resistance_18: 469 | # name: "${name} cell resistance 18" 470 | # cell_resistance_19: 471 | # name: "${name} cell resistance 19" 472 | # cell_resistance_20: 473 | # name: "${name} cell resistance 20" 474 | # cell_resistance_21: 475 | # name: "${name} cell resistance 21" 476 | # cell_resistance_22: 477 | # name: "${name} cell resistance 22" 478 | # cell_resistance_23: 479 | # name: "${name} cell resistance 23" 480 | # cell_resistance_24: 481 | # name: "${name} cell resistance 24" 482 | total_voltage: 483 | id: total_voltage 484 | name: "${name} total voltage" 485 | current: 486 | id: current 487 | name: "${name} current" 488 | power: 489 | name: "${name} power" 490 | charging_power: 491 | name: "${name} charging power" 492 | discharging_power: 493 | name: "${name} discharging power" 494 | temperature_sensor_1: 495 | id: temperature_sensor_1 496 | name: "${name} temperature sensor 1" 497 | temperature_sensor_2: 498 | id: temperature_sensor_2 499 | name: "${name} temperature sensor 2" 500 | power_tube_temperature: 501 | id: power_tube_temperature 502 | name: "${name} power tube temperature" 503 | state_of_charge: 504 | id: state_of_charge 505 | name: "${name} state of charge" 506 | capacity_remaining: 507 | name: "${name} capacity remaining" 508 | total_battery_capacity_setting: 509 | id: total_battery_capacity_setting 510 | name: "${name} total battery capacity setting" 511 | charging_cycles: 512 | id: charging_cycles 513 | name: "${name} charging cycles" 514 | total_charging_cycle_capacity: 515 | name: "${name} total charging cycle capacity" 516 | total_runtime: 517 | name: "${name} total runtime" 518 | balancing_current: 519 | name: "${name} balancing current" 520 | errors_bitmask: 521 | id: errors_bitmask 522 | name: "${name} errors bitmask" 523 | # +--------------------------------------+ 524 | # | Uptime sensor | 525 | # +--------------------------------------+ 526 | - platform: uptime 527 | name: ${name} Uptime Sensor 528 | id: uptime_sensor 529 | update_interval: 60s 530 | on_raw_value: 531 | then: 532 | - text_sensor.template.publish: 533 | id: uptime_human 534 | state: !lambda |- 535 | int seconds = round(id(uptime_sensor).raw_state); 536 | int days = seconds / (24 * 3600); 537 | seconds = seconds % (24 * 3600); 538 | int hours = seconds / 3600; 539 | seconds = seconds % 3600; 540 | int minutes = seconds / 60; 541 | seconds = seconds % 60; 542 | return ( 543 | (days ? to_string(days) + "d " : "") + 544 | (hours ? to_string(hours) + "h " : "") + 545 | (minutes ? to_string(minutes) + "m " : "") + 546 | (to_string(seconds) + "s") 547 | ).c_str(); 548 | 549 | switch: 550 | - platform: jk_bms_ble 551 | charging: 552 | id: charging_switch 553 | name: "${name} BMS Charge switch" 554 | discharging: 555 | id: discharging_switch 556 | name: "${name} BMS Discharge switch" 557 | balancer: 558 | name: "${name} BMS Balance switch" 559 | # +--------------------------------------+ 560 | - platform: ble_client 561 | ble_client_id: client0 562 | id: enable_bluetooth_connection 563 | name: "${name} Enable Bluetooth connection" 564 | # +--------------------------------------+ 565 | - platform: template 566 | name: "${name} CAN Charge enabled" 567 | id: switch_charging 568 | optimistic: true 569 | - platform: template 570 | name: "${name} CAN Discharge enabled" 571 | id: switch_discharging 572 | optimistic: true 573 | - platform: template 574 | name: "${name} CAN Force bulk (top bal)" 575 | id: switch_chg_bulk 576 | optimistic: true 577 | - platform: template 578 | name: "${name} CAN Float charge enabled" 579 | id: switch_chg_float 580 | optimistic: true 581 | 582 | text_sensor: 583 | - platform: jk_bms_ble 584 | errors: 585 | name: "${name} errors" 586 | total_runtime_formatted: 587 | name: "${name} total runtime formatted" 588 | # +--------------------------------------+ 589 | # | Template text sensors | 590 | # +--------------------------------------+ 591 | - platform: template 592 | name: ${name} Uptime Human Readable 593 | id: uptime_human 594 | icon: mdi:clock-start 595 | - platform: template 596 | name: "${name} Charging Status" 597 | id: charging_status 598 | - platform: template 599 | name: "${name} CANBUS Status" 600 | id: canbus_status 601 | 602 | script: 603 | - id: absorption_script 604 | then: 605 | - lambda: id(charge_status) = "Absorption"; 606 | # delay value in ms 607 | - delay: !lambda "return id(absorption_time).state * 60 * 1000;" 608 | - lambda: id(charge_status) = "EOC"; 609 | 610 | # +--------------------------------------+ 611 | # | CAN bus script | 612 | # +--------------------------------------+ 613 | canbus: 614 | - platform: esp32_can 615 | tx_pin: ${can_tx_pin} 616 | rx_pin: ${can_rx_pin} 617 | can_id: 4 618 | bit_rate: 500kbps 619 | on_frame: 620 | - can_id: 0x305 # Inverter ACK - SMA/LG/Pylon/Goodwe reply 621 | then: 622 | - light.toggle: 623 | id: blue_led 624 | - lambda: |- 625 | id(can_ack_counter) = 0; // Reset ACK counter 626 | id(can_status) = "ON"; // Set CANBUS Status to ON 627 | id(canbus_status).publish_state(id(can_status)); // Publish text sensor 628 | ESP_LOGI("main", "received can id: 0x305 ACK"); 629 | 630 | interval: 631 | - interval: 120s 632 | then: 633 | - lambda: id(can_ack_counter) = 0; // Reset ACK counter for test inverter ACK 634 | 635 | - interval: 100ms 636 | then: 637 | # Start CAN Handling 638 | - if: 639 | condition: 640 | lambda: |- 641 | 642 | if (id(can_ack_counter) < 20) { // Inverter ACK ? => CANBUS ON 643 | 644 | id(can_ack_counter)++; // CANBUS ACK counter ++ 645 | id(can_msg_counter)++; // CANBUS MSG counter ++ 646 | return true; // Condition OK 647 | 648 | } 649 | else if (id(can_status) == "OFF") { // CANBUS already OFF ? 650 | 651 | return false; // Nothing to do 652 | 653 | } 654 | else { 655 | 656 | id(can_status) = "OFF"; // Set CANBUS Status to OFF 657 | id(canbus_status).publish_state(id(can_status)); // Publish text sensor 658 | ESP_LOGI("main", "No rx can 0x305 reply, Inverter not connected/responding..."); 659 | return false; // Condition NOK 660 | 661 | } 662 | 663 | then: 664 | - if: 665 | condition: 666 | lambda: return ((id(can_msg_counter) == 1) & ((${can_protocol} == 1) | (${can_protocol} == 2))); 667 | then: 668 | canbus.send: # Protection Alarms, Warning and Flags ( Pylontech / Goodwe / Seplos ) 669 | can_id: 0x359 670 | data: !lambda |- 671 | 672 | // +---------------------------+ 673 | // | JK-BMS errors bitmask | 674 | // +---------------------------+ 675 | 676 | // 0x8B 0x00 0x00: Battery warning message 0000 0000 0000 0000 677 | // 678 | // Bit 0 Low capacity 1 (alarm), 0 (normal) warning 679 | // Bit 1 Power tube overtemperature 1 (alarm), 0 (normal) alarm 680 | // Bit 2 Charging overvoltage 1 (alarm), 0 (normal) alarm 681 | // Bit 3 Discharging undervoltage 1 (alarm), 0 (normal) alarm 682 | // Bit 4 Battery over temperature 1 (alarm), 0 (normal) alarm 683 | // Bit 5 Charging overcurrent 1 (alarm), 0 (normal) alarm 684 | // Bit 6 Discharging overcurrent 1 (alarm), 0 (normal) alarm 685 | // Bit 7 Cell pressure difference 1 (alarm), 0 (normal) alarm 686 | // Bit 8 Overtemperature alarm in the battery box 1 (alarm), 0 (normal) alarm 687 | // Bit 9 Battery low temperature 1 (alarm), 0 (normal) alarm 688 | // Bit 10 Cell overvoltage 1 (alarm), 0 (normal) alarm 689 | // Bit 11 Cell undervoltage 1 (alarm), 0 (normal) alarm 690 | // Bit 12 309_A protection 1 (alarm), 0 (normal) alarm 691 | // Bit 13 309_A protection 1 (alarm), 0 (normal) alarm 692 | // Bit 14 Reserved 693 | // Bit 15 Reserved 694 | // 695 | // Examples: 696 | // 0x0001 = 00000000 00000001: Low capacity alarm 697 | // 0x0002 = 00000000 00000010: MOS tube over-temperature alarm 698 | // 0x0003 = 00000000 00000011: Low capacity alarm AND power tube over-temperature alarm 699 | 700 | // +---------------------------+ 701 | // | Protection : byte 0 and 1 | 702 | // +---------------------------+ 703 | 704 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 705 | 706 | // JK-BMS alarm ? 707 | if (id(errors_bitmask).state > 1) { 708 | uint16_t jk_errormask = id(errors_bitmask).state; 709 | 710 | if ((jk_errormask & 0x04) | (jk_errormask & 0x80) | (jk_errormask & 0x400)) { // Hight.Voltage.Alarm JK bit 2,7,10 711 | can_mesg[0] = 0x02; // byte0_bit1 (0x02 = bin 10) 712 | id(alarm_status) = "OVP"; 713 | ESP_LOGI("main", "Hight.Voltage.Alarm JK bit 2,7,10 - can_msg[0] : %x", can_mesg[0]); 714 | } 715 | if ((jk_errormask & 0x08) | (jk_errormask & 0x800)) { // Low.Voltage.Alarm JK bit 3,11 716 | can_mesg[0] = can_mesg[0] | 0x04; // byte0_bit2 (0x04 = bin 100) 717 | id(alarm_status) = "UVP"; 718 | ESP_LOGI("main", "Low.Voltage.Alarm JK bit 3,11 - can_msg[0] : %x", can_mesg[0]); 719 | } 720 | if ((jk_errormask & 0x02) | (jk_errormask & 0x10) | (jk_errormask & 0x100)) { // Hight.Temp.Alarm JK bit 1,4,8 721 | can_mesg[0] = can_mesg[0] | 0x08; // byte0_bit3 (0x08 = bin 1000) 722 | id(alarm_status) = "OTP"; 723 | ESP_LOGI("main", "Hight.Temp.Alarm JK bit 1,4,8 - can_msg[0] : %x", can_mesg[0]); 724 | } 725 | if ((jk_errormask & 0x200)) { // Low.Temp.Alarm JK bit 9 726 | can_mesg[0] = can_mesg[0] | 0x10; // byte0_bit4 (0x10 = bin 10000) 727 | id(alarm_status) = "UTP"; 728 | ESP_LOGI("main", "Low.Temp.Alarm JK bit 9 - can_msg[0] : %x", can_mesg[0]); 729 | } 730 | if ((jk_errormask & 0x40)) { // Discharge.Over.Current JK bit 6 731 | can_mesg[0] = can_mesg[0] | 0x80; // byte0_bit7 (0x80 = bin 10000000) 732 | id(alarm_status) = "DOCP"; 733 | ESP_LOGI("main", "Discharge.Over.Current JK bit 6 - can_msg[0] : %x", can_mesg[0]); 734 | } 735 | if ((jk_errormask & 0x20)) { // Charge.Over.Current JK bit 5 736 | can_mesg[1] = 0x01; // byte1_bit0 (0x01 = bin 1) 737 | id(alarm_status) = "COCP"; 738 | ESP_LOGI("main", "Charge.Over.Current JK bit 5 - can_msg[1] : %x", can_mesg[1]); 739 | } 740 | if ((jk_errormask & 0x1000) | (jk_errormask & 0x2000)) { // BMS internal error JK bit 12,13 741 | can_mesg[1] = can_mesg[1] | 0x08; // byte1_bit3 (0x08 = bin 1000) 742 | id(alarm_status) = "BMS"; 743 | ESP_LOGI("main", "BMS internal error JK bit 12,13 - can_msg[1] : %x", can_mesg[1]); 744 | } 745 | if ((jk_errormask & 0x80)) { // Cell Imbalance JK bit 7 746 | can_mesg[1] = can_mesg[1] | 0x10; // byte1_bit4 (0x10 = bin 10000) 747 | ESP_LOGI("main", "Cell Imbalance JK bit 7 - can_msg[1] : %x", can_mesg[1]); 748 | } 749 | } 750 | // No Alarm 751 | else id(alarm_status) = "NoAlarm"; 752 | 753 | // +---------------------------+ 754 | // | Warning : byte 2 and 3 | 755 | // +---------------------------+ 756 | 757 | can_mesg[2] = 0x00; // byte2 (JK-BMS infos not available) 758 | can_mesg[3] = 0x00; // byte3 (JK-BMS infos not available) 759 | 760 | // +---------------------------+ 761 | // | Flags : byte 4 to 7 | 762 | // +---------------------------+ 763 | 764 | int batt_mods = ${batt_modules}; 765 | 766 | can_mesg[4] = batt_mods; // byte4 - Module in parallel 767 | can_mesg[5] = 0x00; // byte5 768 | can_mesg[6] = 0x00; // byte6 769 | can_mesg[7] = 0x00; // byte7 - DIP switches 1,3 10000100 0x84 770 | 771 | ESP_LOGI("main", "send can id: 0x359 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 772 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 773 | 774 | - if: 775 | condition: 776 | lambda: return ((id(can_msg_counter) == 1) & ((${can_protocol} == 3) | (${can_protocol} == 4))); 777 | then: 778 | canbus.send: # Protection Alarms and Warning ( SMA / Victron ) 779 | can_id: 0x35A 780 | data: !lambda |- 781 | 782 | // +---------------------------+ 783 | // | Protection : byte 0,1,2,3 | 784 | // +---------------------------+ 785 | 786 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 787 | 788 | // JK-BMS alarm ? 789 | if (id(errors_bitmask).state > 1) { 790 | uint16_t jk_errormask = id(errors_bitmask).state; 791 | 792 | if ((jk_errormask & 0x04) | (jk_errormask & 0x80) | (jk_errormask & 0x400)) { // Hight.Voltage.Alarm JK bit 2,7,10 793 | can_mesg[0] = 0x04; // byte0_bit2 (0x04 = bin 100) 794 | id(alarm_status) = "OVP"; 795 | ESP_LOGI("main", "Hight.Voltage.Alarm JK bit 2,7,10 - can_msg[0] : %x", can_mesg[0]); 796 | } 797 | if ((jk_errormask & 0x08) | (jk_errormask & 0x800)) { // Low.Voltage.Alarm JK bit 3,11 798 | can_mesg[0] = can_mesg[0] | 0x10; // byte0_bit4 (0x10 = bin 10000) 799 | id(alarm_status) = "UVP"; 800 | ESP_LOGI("main", "Low.Voltage.Alarm JK bit 3,11 - can_msg[0] : %x", can_mesg[0]); 801 | } 802 | if ((jk_errormask & 0x02) | (jk_errormask & 0x10) | (jk_errormask & 0x100)) { // Hight.Temp.Alarm JK bit 1,4,8 803 | can_mesg[0] = can_mesg[0] | 0x40; // byte0_bit6 (0x40 = bin 1000000) 804 | id(alarm_status) = "OTP"; 805 | ESP_LOGI("main", "Hight.Temp.Alarm JK bit 1,4,8 - can_msg[0] : %x", can_mesg[0]); 806 | } 807 | if ((jk_errormask & 0x200)) { // Low.Temp.Alarm JK bit 9 808 | can_mesg[1] = 0x01; // byte1_bit0 (0x01 = bin 1) 809 | id(alarm_status) = "UTP"; 810 | ESP_LOGI("main", "Low.Temp.Alarm JK bit 9 - can_msg[1] : %x", can_mesg[1]); 811 | } 812 | if ((jk_errormask & 0x40)) { // Discharge.Over.Current JK bit 6 813 | can_mesg[1] = can_mesg[1] | 0x40; // byte1_bit6 (0x40 = bin 1000000) 814 | id(alarm_status) = "DOCP"; 815 | ESP_LOGI("main", "Discharge.Over.Current JK bit 6 - can_msg[1] : %x", can_mesg[1]); 816 | } 817 | if ((jk_errormask & 0x20)) { // Charge.Over.Current JK bit 5 818 | can_mesg[2] = 0x01; // byte2_bit0 (0x01 = bin 1) 819 | id(alarm_status) = "COCP"; 820 | ESP_LOGI("main", "Charge.Over.Current JK bit 5 - can_msg[2] : %x", can_mesg[2]); 821 | } 822 | if ((jk_errormask & 0x1000) | (jk_errormask & 0x2000)) { // BMS.Internal.Error JK bit 12,13 823 | can_mesg[2] = can_mesg[2] | 0x40; // byte2_bit6 (0x40 = bin 1000000) 824 | id(alarm_status) = "BMS"; 825 | ESP_LOGI("main", "BMS internal error JK bit 12,13 - can_msg[2] : %x", can_mesg[2]); 826 | } 827 | if ((jk_errormask & 0x80)) { // Cell.Imbalance JK bit 7 828 | can_mesg[3] = 0x01; // byte3_bit0 (0x01 = bin 1) 829 | ESP_LOGI("main", "Cell Imbalance JK bit 7 - can_msg[3] : %x", can_mesg[3]); 830 | } 831 | } 832 | // No Alarm 833 | else id(alarm_status) = "NoAlarm"; 834 | 835 | ESP_LOGI("main", "send can id: 0x35A hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 836 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 837 | 838 | - if: 839 | condition: 840 | lambda: return id(can_msg_counter) == 2; 841 | then: 842 | canbus.send: # BMS instruction : Charge Volts, Charge Amps, Discharge Amps, Min voltage 843 | can_id: 0x351 844 | data: !lambda |- 845 | 846 | // +----------------+ 847 | // | Charging Logic | 848 | // +----------------+ 849 | 850 | // Warning : information from JK BMS is not available immediately after boot 851 | 852 | // Alarm : if JK-BMS alarm ! 853 | if (id(errors_bitmask).state > 1) { 854 | id(charge_status) = "Alarm"; 855 | } 856 | // No Alarm => Wait 857 | else if ((id(errors_bitmask).state < 2) & (id(charge_status) == "Alarm")) { 858 | id(charge_status) = "Wait"; 859 | } 860 | // Charge ON : BMS and ESP32 charging switch is ON 861 | else if ((id(charging_switch).state) & (id(switch_charging).state)) { 862 | 863 | // Force Bulk : 'Charging manually (top bal)' switch is ON 864 | if (id(switch_chg_bulk).state) { 865 | id(charge_status) = "Force Bulk"; 866 | } 867 | // No Force Bulk => Wait 868 | else if ((!id(switch_chg_bulk).state) & (id(charge_status) == "Force Bulk")) { 869 | id(charge_status) = "Wait"; 870 | } 871 | // Bulk : Bat. V. <= Rebulk V. ( Absorption V. - Rebulk Offset V. ) ( Bulk : Rebulk V. = 55.2-2.5 = 52.7V by default ) 872 | else if (id(total_voltage).state <= (id(bulk_voltage).state - id(rebulk_offset).state)) { 873 | id(charge_status) = "Bulk"; 874 | if (id(absorption_script).is_running()) id(absorption_script).stop(); 875 | } 876 | // Absorption : Bat. V >= ( Absorption V. - Absorption Offset V. ) ( Absorption V. = 55.2-0.05 = 55.15V by default ) 877 | else if ((id(charge_status) == "Bulk") & (id(total_voltage).state >= (id(bulk_voltage).state - id(absorption_offset).state))) { 878 | id(charge_status) = "Absorption"; 879 | if (!id(absorption_script).is_running()) id(absorption_script).execute(); // 10 % from top start absorption timer 880 | } 881 | // Bulk : (Bat. V. > Rebulk V.) + Wait / ESP32 startup ( Bulk : when starting the ESP32 ) 882 | else if ((id(charge_status) == "Wait")) { 883 | id(charge_status) = "Bulk"; 884 | if (id(absorption_script).is_running()) id(absorption_script).stop(); 885 | } 886 | // Float : (Bat. V. > Rebulk V.) + Float switch ON and End Of Charge ( Float : after Absorption ) 887 | else if ((id(switch_chg_float).state) & (id(charge_status) == "EOC")) { 888 | id(charge_status) = "Float"; 889 | } 890 | // No Float => Wait 891 | else if ((!id(switch_chg_float).state) & (id(charge_status) == "Float")) { 892 | id(charge_status) = "Wait"; 893 | } 894 | } 895 | // Charge OFF 896 | else id(charge_status) = "Wait"; 897 | 898 | // +--------------------------------------+ 899 | // | Charge values | 900 | // +--------------------------------------+ 901 | 902 | // Bulk Charge 903 | if ((id(charge_status) == "Bulk") | (id(charge_status) == "Force Bulk") | (id(charge_status) == "Absorption")) { 904 | id(charging_v) = id(bulk_voltage).state; 905 | id(charging_a) = id(charging_current).state; 906 | } 907 | // Float Charge 908 | else if (id(charge_status) == "Float") { 909 | if (id(total_voltage).state > id(float_voltage).state){ 910 | id(charging_v) = round(id(total_voltage).state * 10)/10; // Actual battery voltage 911 | id(charging_a) = 0; 912 | } else { 913 | id(charging_v) = id(float_voltage).state; 914 | id(charging_a) = id(charging_current).state; 915 | } 916 | } 917 | // End Of Charge (EOC) or Wait : Stop Charging 918 | else if ((id(charge_status) == "EOC") | (id(charge_status) == "Wait")) { 919 | id(charging_v) = round(id(total_voltage).state * 10)/10; // Actual battery voltage 920 | id(charging_a) = 0; 921 | } 922 | 923 | // +--------------------------------------+ 924 | // | Discharge values | 925 | // +--------------------------------------+ 926 | 927 | // Stop Discharging if BMS or ESP32 switch is OFF 928 | if ((!id(discharging_switch).state) | (!id(switch_discharging).state)) id(discharging_a) = 0; 929 | // Stop Discharging if battery voltage is low 930 | else if (id(total_voltage).state <= ${min_discharge_v}) id(discharging_a) = 0; 931 | // Discharging is OK 932 | else id(discharging_a) = ${discharge_a}; 933 | 934 | // +--------------------------------------+ 935 | // | Alarm overwrite values | 936 | // +--------------------------------------+ 937 | 938 | ESP_LOGI("main", "Alarm Status : %s", id(alarm_status).c_str()); 939 | 940 | // Alarm : Stop Charging and Discharging 941 | if ((id(alarm_status) == "OTP") | (id(alarm_status) == "BMS")){ 942 | id(charging_v) = 51.2; 943 | id(charging_a) = 0; 944 | id(discharging_a) = 0; 945 | } 946 | // Alarm : Stop Charging 947 | else if ((id(alarm_status) == "OVP") | (id(alarm_status) == "UTP") | (id(alarm_status) == "COCP")){ 948 | id(charging_v) = 51.2; 949 | id(charging_a) = 0; 950 | } 951 | // Alarm : Stop Discharging 952 | else if ((id(alarm_status) == "UVP") | (id(alarm_status) == "DOCP")){ 953 | id(discharging_a) = 0; 954 | } 955 | 956 | // +--------------------------------------+ 957 | // | CAN messages | 958 | // +--------------------------------------+ 959 | 960 | // Byte [00:01] = CVL : Charge Limit Voltage 961 | // Byte [02:03] = CCL : Charge Limit Current 962 | // Byte [04:05] = DCL : Discharge Limit Current 963 | // Byte [06:07] = DVL : Discharge Limit Voltage 964 | 965 | uint8_t can_mesg[8]; 966 | 967 | can_mesg[0] = uint16_t(id(charging_v) * 10) & 0xff; 968 | can_mesg[1] = uint16_t(id(charging_v) * 10) >> 8 & 0xff; 969 | can_mesg[2] = uint16_t(id(charging_a) * 10) & 0xff; 970 | can_mesg[3] = uint16_t(id(charging_a) * 10) >> 8 & 0xff; 971 | can_mesg[4] = uint16_t(id(discharging_a) * 10) & 0xff; 972 | can_mesg[5] = uint16_t(id(discharging_a) * 10) >> 8 & 0xff; 973 | can_mesg[6] = uint16_t(${min_discharge_v} * 10) & 0xff; 974 | can_mesg[7] = uint16_t(${min_discharge_v} * 10) >> 8 & 0xff; 975 | 976 | // Publish text sensor 977 | id(charging_status).publish_state(id(charge_status)); 978 | 979 | // Logs 980 | ESP_LOGI("main", "send can id: 0x351 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 981 | ESP_LOGI("main", "Charge Status : %s", id(charge_status).c_str()); 982 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 983 | 984 | - if: 985 | condition: 986 | lambda: return id(can_msg_counter) == 3; 987 | then: 988 | canbus.send: # Actual State of Charge (SOC) / State of Health (SOH) 989 | can_id: 0x355 990 | data: !lambda |- 991 | int soh = round(((id(charging_cycles).state/${max_cycles})-1)*-100); 992 | uint8_t can_mesg[4]; 993 | can_mesg[0] = uint16_t(id(state_of_charge).state) & 0xff; 994 | can_mesg[1] = uint16_t(id(state_of_charge).state) >> 8 & 0xff; 995 | can_mesg[2] = soh & 0xff; 996 | can_mesg[3] = soh >> 8 & 0xff; 997 | ESP_LOGI("main", "send can id: 0x355 hex: %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3]); 998 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3]}; 999 | 1000 | - if: 1001 | condition: 1002 | lambda: return id(can_msg_counter) == 4; 1003 | then: 1004 | canbus.send: # Actual Voltage / Current / Temperature / Cycles (Deye 0x305 ACK) 1005 | can_id: 0x356 1006 | data: !lambda |- 1007 | 1008 | // Byte [00:01] : Actual Voltage 1009 | // Byte [02:03] : Actual Current 1010 | // Byte [04:05] : Actual Temperature 1011 | // Byte [06:07] : Actual Cycles number (Sofar) 1012 | 1013 | uint8_t can_mesg[8]; 1014 | can_mesg[0] = uint16_t(id(total_voltage).state * 100) & 0xff; 1015 | can_mesg[1] = uint16_t(id(total_voltage).state * 100) >> 8 & 0xff; 1016 | can_mesg[2] = int16_t(id(current).state * 10) & 0xff; 1017 | can_mesg[3] = int16_t(id(current).state * 10) >> 8 & 0xff; 1018 | can_mesg[4] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) & 0xff; 1019 | can_mesg[5] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; 1020 | can_mesg[6] = uint16_t(id(charging_cycles).state) & 0xff; 1021 | can_mesg[7] = uint16_t(id(charging_cycles).state) >> 8 & 0xff; 1022 | ESP_LOGI("main", "send can id: 0x356 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1023 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1024 | 1025 | - if: 1026 | condition: 1027 | lambda: return ((id(can_msg_counter) == 5) & ((${can_protocol} == 1) | (${can_protocol} == 2))); 1028 | then: 1029 | canbus.send: # Request flag to Enable/Disable: Charge, Discharge ( Pylontech / Goodwe / Seplos ) 1030 | can_id: 0x35C 1031 | data: !lambda |- 1032 | uint8_t can_mesg[2]; 1033 | can_mesg[0] = 0x00; 1034 | can_mesg[1] = 0x00; 1035 | 1036 | // Bit 7 : Charge enable 1037 | if ((id(charging_switch).state) & (id(switch_charging).state)) 1038 | can_mesg[0] = 0x80; 1039 | 1040 | // Bit 6 : Discharge enable 1041 | if ((id(discharging_switch).state) & (id(switch_discharging).state)) 1042 | can_mesg[0] = can_mesg[0] | 0x40; 1043 | 1044 | ESP_LOGI("main", "send can id: 0x35C hex: %x %x", can_mesg[0], can_mesg[1]); 1045 | return {can_mesg[0], can_mesg[1]}; 1046 | 1047 | - if: 1048 | condition: 1049 | lambda: return ((id(can_msg_counter) == 6) & (${can_protocol} == 2)); 1050 | then: 1051 | canbus.send: # Actual Max Cell Temp, Min Cell Temp, Max Cell V, Min Cell V ( Pylontech / Goodwe / Seplos ) 1052 | can_id: 0x70 1053 | data: !lambda |- 1054 | 1055 | // Byte [00:01] : Max cell temperature 1056 | // Byte [02:03] : Min cell temperature 1057 | // Byte [04:05] : Max cell voltage 1058 | // Byte [06:07] : Min cell voltage 1059 | 1060 | int max_cell_voltage_i = id(max_cell_voltage).state * 100.0; 1061 | int min_cell_voltage_i = id(min_cell_voltage).state * 100.0; 1062 | uint8_t can_mesg[8]; 1063 | can_mesg[0] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) & 0xff; 1064 | can_mesg[1] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; 1065 | can_mesg[2] = int16_t(min(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) & 0xff; 1066 | can_mesg[3] = int16_t(min(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; 1067 | can_mesg[4] = max_cell_voltage_i & 0xff; 1068 | can_mesg[5] = max_cell_voltage_i >> 8 & 0xff; 1069 | can_mesg[6] = min_cell_voltage_i & 0xff; 1070 | can_mesg[7] = min_cell_voltage_i >> 8 & 0xff; 1071 | ESP_LOGI("main", "send can id: 0x70 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1072 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1073 | 1074 | - if: 1075 | condition: 1076 | lambda: return ((id(can_msg_counter) == 6) & (${can_protocol} == 2)); 1077 | then: 1078 | - canbus.send: # Actual Max Cell Temp ID, Min Cell Temp ID, Max Cell V ID, Min Cell ID ( Pylontech / Goodwe / Seplos ) 1079 | can_id: 0x371 1080 | data: !lambda |- 1081 | 1082 | // Byte [00:01] : Max cell temperature ID 1083 | // Byte [02:03] : Min cell temperature ID 1084 | // Byte [04:05] : Max cell voltage ID 1085 | // Byte [06:07] : Min cell voltage ID 1086 | 1087 | uint8_t can_mesg[8]; 1088 | 1089 | // Min-Max Temp. Sensor ID ? 1090 | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ 1091 | can_mesg[0] = 0x01; 1092 | can_mesg[2] = 0x02; 1093 | } 1094 | else { 1095 | can_mesg[0] = 0x02; 1096 | can_mesg[2] = 0x01; 1097 | } 1098 | 1099 | can_mesg[1] = 0x00; 1100 | can_mesg[3] = 0x00; 1101 | can_mesg[4] = uint16_t(id(max_voltage_cell).state) & 0xff; 1102 | can_mesg[5] = uint16_t(id(max_voltage_cell).state) >> 8 & 0xff; 1103 | can_mesg[6] = uint16_t(id(min_voltage_cell).state) & 0xff; 1104 | can_mesg[7] = uint16_t(id(min_voltage_cell).state) >> 8 & 0xff; 1105 | ESP_LOGI("main", "send can id: 0x371 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1106 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1107 | 1108 | - if: 1109 | condition: 1110 | lambda: return ((id(can_msg_counter) == 7) & (${can_protocol} == 4)); 1111 | then: 1112 | - canbus.send: # Battery modules information ( Victron ) 1113 | can_id: 0x372 1114 | data: !lambda |- 1115 | 1116 | // Byte [00:01] : Nbr. of battery modules online 1117 | // Byte [02:03] : Nbr. of modules blocking charge 1118 | // Byte [04:05] : Nbr. of modules blocking discharge 1119 | // Byte [06:07] : Nbr. of battery modules offline 1120 | 1121 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 1122 | can_mesg[0] = 0x01; 1123 | 1124 | ESP_LOGI("main", "send can id: 0x372 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1125 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1126 | 1127 | - if: 1128 | condition: 1129 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1130 | then: 1131 | canbus.send: # Actual Min Cell V, Max Cell V, Min Cell Temp (Kelvin), Max Cell Temp (Kelvin) ( Victron ) 1132 | can_id: 0x373 1133 | data: !lambda |- 1134 | 1135 | // Byte [00:01] : Min cell voltage 1136 | // Byte [02:03] : Max cell voltage 1137 | // Byte [04:05] : Min cell temperature 1138 | // Byte [06:07] : Max cell temperature 1139 | 1140 | int min_cell_voltage_i = id(min_cell_voltage).state * 1000.0; 1141 | int max_cell_voltage_i = id(max_cell_voltage).state * 1000.0; 1142 | int min_temp_kelvin = min(id(temperature_sensor_1).state, id(temperature_sensor_2).state) + 273.15; 1143 | int max_temp_kelvin = max(id(temperature_sensor_1).state, id(temperature_sensor_2).state) + 273.15; 1144 | 1145 | uint8_t can_mesg[8]; 1146 | can_mesg[0] = min_cell_voltage_i & 0xff; 1147 | can_mesg[1] = min_cell_voltage_i >> 8 & 0xff; 1148 | can_mesg[2] = max_cell_voltage_i & 0xff; 1149 | can_mesg[3] = max_cell_voltage_i >> 8 & 0xff; 1150 | can_mesg[4] = min_temp_kelvin & 0xff; 1151 | can_mesg[5] = min_temp_kelvin >> 8 & 0xff; 1152 | can_mesg[6] = max_temp_kelvin & 0xff; 1153 | can_mesg[7] = max_temp_kelvin >> 8 & 0xff; 1154 | 1155 | ESP_LOGI("main", "send can id: 0x373 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1156 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1157 | 1158 | - if: 1159 | condition: 1160 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1161 | then: 1162 | - canbus.send: # Min cell voltage ID [ASCII] ( Victron ) 1163 | can_id: 0x374 1164 | data: !lambda |- 1165 | 1166 | int cell_id = id(min_voltage_cell).state; 1167 | 1168 | ESP_LOGI("main", "send can id: 0x374 [ASCII] Min cell voltage ID : %i", cell_id); 1169 | 1170 | if (cell_id == 1) return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1171 | else if (cell_id == 2) return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1172 | else if (cell_id == 3) return {0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1173 | else if (cell_id == 4) return {0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1174 | else if (cell_id == 5) return {0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1175 | else if (cell_id == 6) return {0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1176 | else if (cell_id == 7) return {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1177 | else if (cell_id == 8) return {0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1178 | else if (cell_id == 9) return {0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1179 | else if (cell_id == 10) return {0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1180 | else if (cell_id == 11) return {0x31, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1181 | else if (cell_id == 12) return {0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1182 | else if (cell_id == 13) return {0x31, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1183 | else if (cell_id == 14) return {0x31, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1184 | else if (cell_id == 15) return {0x31, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1185 | else if (cell_id == 16) return {0x31, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1186 | else return {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1187 | 1188 | - if: 1189 | condition: 1190 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1191 | then: 1192 | - canbus.send: # Max cell voltage ID [ASCII] ( Victron ) 1193 | can_id: 0x375 1194 | data: !lambda |- 1195 | 1196 | int cell_id = id(max_voltage_cell).state; 1197 | 1198 | ESP_LOGI("main", "send can id: 0x375 [ASCII] Max cell voltage ID : %i", cell_id); 1199 | 1200 | if (cell_id == 1) return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1201 | else if (cell_id == 2) return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1202 | else if (cell_id == 3) return {0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1203 | else if (cell_id == 4) return {0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1204 | else if (cell_id == 5) return {0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1205 | else if (cell_id == 6) return {0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1206 | else if (cell_id == 7) return {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1207 | else if (cell_id == 8) return {0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1208 | else if (cell_id == 9) return {0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1209 | else if (cell_id == 10) return {0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1210 | else if (cell_id == 11) return {0x31, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1211 | else if (cell_id == 12) return {0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1212 | else if (cell_id == 13) return {0x31, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1213 | else if (cell_id == 14) return {0x31, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1214 | else if (cell_id == 15) return {0x31, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1215 | else if (cell_id == 16) return {0x31, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1216 | else return {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1217 | 1218 | - if: 1219 | condition: 1220 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1221 | then: 1222 | - canbus.send: # Min cell temperature ID [ASCII] ( Victron ) 1223 | can_id: 0x376 1224 | data: !lambda |- 1225 | 1226 | // Min Temp. Sensor ID ? 1227 | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ 1228 | ESP_LOGI("main", "send can id: 0x376 [ASCII] Min Temp. Sensor ID : 2"); 1229 | return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1230 | } 1231 | else { 1232 | ESP_LOGI("main", "send can id: 0x376 [ASCII] Min Temp. Sensor ID : 1"); 1233 | return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1234 | } 1235 | 1236 | - if: 1237 | condition: 1238 | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); 1239 | then: 1240 | - canbus.send: # Max cell temperature ID [ASCII] ( Victron ) 1241 | can_id: 0x377 1242 | data: !lambda |- 1243 | 1244 | // Max Temp. Sensor ID ? 1245 | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ 1246 | ESP_LOGI("main", "send can id: 0x377 [ASCII] Max Temp. Sensor ID : 1"); 1247 | return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1248 | } 1249 | else { 1250 | ESP_LOGI("main", "send can id: 0x377 [ASCII] Max Temp. Sensor ID : 2"); 1251 | return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 1252 | } 1253 | 1254 | - if: 1255 | condition: 1256 | lambda: return ((id(can_msg_counter) == 9) & ((${can_protocol} == 2) | (${can_protocol} == 4))); 1257 | then: 1258 | - canbus.send: # Battery Installed Capacity Ah ( Victron, Sol-Ark, Luxpower ) 1259 | can_id: 0x379 1260 | data: !lambda |- 1261 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 1262 | can_mesg[0] = uint16_t(id(total_battery_capacity_setting).state) & 0xff; 1263 | can_mesg[1] = uint16_t(id(total_battery_capacity_setting).state) >> 8 & 0xff; 1264 | 1265 | ESP_LOGI("main", "send can id: 0x379 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1266 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1267 | 1268 | - if: 1269 | condition: 1270 | lambda: return ((id(can_msg_counter) == 10) & (${can_protocol} == 4)); 1271 | then: 1272 | - canbus.send: # Product identification [ASCII] ( Victron ) 1273 | can_id: 0x382 1274 | data: !lambda |- 1275 | ESP_LOGI("main", "send can id: 0x382 [ASCII] Product : JK-BMS"); 1276 | return {0x4A, 0x4B, 0x2D, 0x42, 0x4D, 0x53, 0x00, 0x00}; // JK-BMS 1277 | 1278 | - if: 1279 | condition: 1280 | lambda: return ((id(can_msg_counter) == 11) & ((${can_protocol} == 3) | (${can_protocol} == 4))); 1281 | then: 1282 | - canbus.send: # Battery information ( SMA, Victron ) 1283 | can_id: 0x35F 1284 | data: !lambda |- 1285 | 1286 | // SMA Victron 1287 | // Byte [00:01] : Bat-Type Product ID 1288 | // Byte [02:03] : BMS Version Firmware version (1.16 => HEX [01:10]) 1289 | // Byte [04:05] : Bat-Capacity Available Capacity Ah 1290 | // Byte [06:07] : Manufacturer ID Hardware version 1291 | 1292 | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; 1293 | can_mesg[2] = 0x01; 1294 | can_mesg[3] = 0x10; 1295 | can_mesg[4] = uint16_t(id(total_battery_capacity_setting).state) & 0xff; 1296 | can_mesg[5] = uint16_t(id(total_battery_capacity_setting).state) >> 8 & 0xff; 1297 | 1298 | ESP_LOGI("main", "send can id: 0x35F hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); 1299 | return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; 1300 | 1301 | - if: 1302 | condition: 1303 | lambda: return id(can_msg_counter) == 12; 1304 | then: 1305 | - canbus.send: 1306 | can_id: 0x35E # Manufacturer name 1307 | data: !lambda |- 1308 | if (${can_bms_name} == 1){ 1309 | ESP_LOGI("main", "send can id: 0x35E ASCII : PYLON"); 1310 | return {0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20}; // PYLON ( recognized by Deye, display PYLON name and SOH ) 1311 | } 1312 | else if (${can_bms_name} == 2){ 1313 | ESP_LOGI("main", "send can id: 0x35E ASCII : GOODWE"); 1314 | return {0x47, 0x4F, 0x4F, 0x44, 0x57, 0x45, 0x20, 0x20}; // GOODWE 1315 | } 1316 | else if (${can_bms_name} == 3){ 1317 | ESP_LOGI("main", "send can id: 0x35E ASCII : SHEnergy"); 1318 | return {0x53, 0x48, 0x45, 0x6E, 0x65, 0x72, 0x67, 0x79}; // SHEnergy (SEPLOS) 1319 | } 1320 | # Reset counter 1321 | - lambda: id(can_msg_counter) = 0; 1322 | --------------------------------------------------------------------------------