├── LICENSE ├── README.md ├── common ├── common.yaml └── secrets.yaml ├── modbus-test.yaml └── renogy-modbus.yaml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2022 dgtlcopywrite 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Renogy Modbus on ESPHome 2 | 3 | Here is a very rough draft of my notes for this project. This is an example configuration for ESPHome to communicate with a Renogy Wanderer 10A PWM Charge Controller. Pretty much any Renogy charge controller with an RS-232 port should work with more/less the same config, though some features and fields may not work with all Renogy charge controllers depending on their features. 4 | 5 | Some day I plan on returning to this project and doing a more formal write-up, but hopefully this can help someone before then. 6 | 7 | First, you need a RS-232/TTLconverter. I used this: [Sparkfun BOB-11189 RS-232 Transceiver Breakout](https://www.sparkfun.com/products/11189) which is just a breakout for the MAX 3232. Any equivalent RS232/TTL converter should work. The wiring was a little tricky to get right and I did not take a photo or write down my schematic before tearing it down and throwing it in a box. :( But I used this as my reference for the Renogy pinout: [https://github.com/collinturney/solarshed](https://github.com/collinturney/solarshed) and this for the protocol (look in the reference directory): [https://github.com/KyleJamesWalker/renogy_rover](https://github.com/KyleJamesWalker/renogy_rover). 8 | 9 | Sparkfun also has breakout boards for the 6P6C jack used on the Renogy projects so you can breadboard easily and connect it with a nice clean 6-pin cable. 10 | 11 | Bonus: The renogy jack also supplies 15V out--if you have an appropriate step-down converter you can use this to power the ESP. **DO NOT** connect the V+ lines directly to your ESP--most boards cannot step down from 15V so you will likely need an external converter here. 12 | 13 | ## Connections 14 | 15 | (From memory--may not be accurate) 16 | 17 | | Device Pin | MAX3232 Pin | 18 | | ----------- | ----------- | 19 | | Renogy TX | R1 IN | 20 | | Renogy RX | T1 OUT | 21 | | Renogy GND | GND | 22 | | ESP TX | T1 IN | 23 | | ESP RX | R1 OUT | 24 | | ESP RX | R1 OUT | 25 | | ESP 3V3 | Vcc (3V-5.5V) | 26 | | ESP GND | GND | 27 | 28 | If your wiring doesn't work you can try swapping the RX/TX directions, but make sure you only connect your ESP to the TTL/CMOS side (**DO NOT** connect the ESP to the T OUT or R IN pins--the ESP cannot handle RS-232 voltages). 29 | 30 | ### **WARNING** 31 | 32 | **Please** double-check the schematics, pinouts, and datasheets they provide to avoid any possible damage--I take no responsibility for my memory! When I was playing around with this I did some sanity checking with a multimeter to make sure I didn't fry an ESP with RS-232 level voltages--read the note in the first github link RE: RJ12 pinout. 33 | 34 | ## Configuration 35 | 36 | - common/common.yaml : A port of my common esphome configuration I use for most projects. 37 | - common/secrets.yaml : An example secrets file which gets used by my common configuration. 38 | - modbus-test.yaml : An example configuration file for a TinyPICO ESP32 connected to a Renogy Wanderer Charge Controller with Modbus. 39 | - renogy-modbus.yaml : The Renogy-specific configuration for UART, Modbus, Sensor registers, and switchable load (light, etc). 40 | 41 | ## Nota Bene 42 | 43 | 1. Your ESP's TX and RX pin may be different depending on which controller you're using and how you choose to set it up. 44 | 2. The modbus controller address that worked for me was 0xFF, despite just about everyone else and everything stating it should be 0x00. I never had the time to figure out why, so try the other if one doesn't work. 45 | 3. The log level is set to DEBUG in the test file--helpful when benchtesting, noisy when in use. 46 | 4. I have the update interval set at 3s which may be a bit aggressive for everything. I am using skip_updates on the less relevant fields like daily statistics. You might want to play with these values for your use case. ESPHome will automatically group up simultaneous queries if the memory addresses are adjacent, which reduces the overhead in the payload and speeds up communication. I remember seeing various warnings in the logs about overlapping/repeating addresses and requests being sent before the previous completed, but I do not remember how well-optimized I got this before I shelved the project, so it warrants some playing around. If you're not planning on using a bunch of the extra data fields they can just be omitted, but it's cool how much data this little thing tracks. 47 | 48 | ## Disclaimer 49 | 50 | This is far from well-tested at this point. Use any information here at your own risk. I take no responsibility for errors, accuracy, or anything you choose to do with this information. If you spot something you know to be false, feel free to let me know so I can correct it. 51 | 52 | ## Credits 53 | 54 | Thanks to these two for posting highly useful information that helped me get this far, as well as a handful of others on GitHub who I no doubt forgot since last year: 55 | 56 | - [https://github.com/collinturney/solarshed](https://github.com/collinturney/solarshed) 57 | - [https://github.com/KyleJamesWalker/renogy_rover](https://github.com/KyleJamesWalker/renogy_rover) 58 | 59 | Thanks to [Renogy](https://www.renogy.com/) for releasing a really cool, really reasonably-priced line of Solar Charge Controllers with a standards-based communication protocol **AND** releasing the documentation around their specific registers to the community. This is AWESOME of them. They have a forum where some of this was discussed (I believe the original conversations were on a previous host, but still) [https://pc.renogy-dchome.com/](https://pc.renogy-dchome.com/#/) 60 | 61 | Thanks to [ESPHome](https://www.esphome.io/), its developers both past and present and Nabu Casa for making such an awesome project that makes it so quick and easy to play with ESP32 microcontrollers. -------------------------------------------------------------------------------- /common/common.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | encryption: 3 | key: !secret encryption_key 4 | password: !secret ota_password 5 | 6 | esphome: 7 | name: ${location}-${device} 8 | comment: $deviceDescription 9 | 10 | preferences: 11 | flash_write_interval: 5min 12 | 13 | logger: 14 | level: INFO 15 | 16 | ota: 17 | password: !secret ota_password 18 | 19 | time: 20 | - platform: homeassistant 21 | id: ha_time 22 | 23 | wifi: 24 | ssid: !secret wifi_ssid 25 | password: !secret wifi_psk 26 | power_save_mode: light 27 | ap: 28 | ssid: $locationName $deviceName 29 | password: !secret fallback_psk 30 | 31 | #region Sensors 32 | 33 | binary_sensor: 34 | 35 | - platform: status 36 | name: ${locationName} ${deviceName} Status 37 | id: ${location}_${device}_status 38 | entity_category: diagnostic 39 | 40 | sensor: 41 | 42 | - platform: wifi_signal 43 | name: $locationName $deviceName WiFi Signal 44 | id: ${location}_${device}_wifi_signal 45 | entity_category: diagnostic 46 | update_interval: 60s 47 | 48 | - platform: uptime 49 | name: $locationName $deviceName Uptime 50 | id: ${location}_${device}_uptime 51 | entity_category: diagnostic 52 | accuracy_decimals: 0 53 | unit_of_measurement: m 54 | update_interval: 60s 55 | filters: 56 | - lambda: return x / 60; 57 | 58 | #endregion -------------------------------------------------------------------------------- /common/secrets.yaml: -------------------------------------------------------------------------------- 1 | wifi_ssid : '' 2 | wifi_psk : '' 3 | fallback_psk : '' 4 | ota_password : '' 5 | encryption_key : '' 6 | -------------------------------------------------------------------------------- /modbus-test.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | location: modbus 3 | locationName: Modbus 4 | device: test 5 | deviceName: Test 6 | deviceDescription: Modbus Test 7 | 8 | packages: 9 | common: !include common/common.yaml 10 | renogy-modbus: !include common/renogy-modbus.yaml 11 | 12 | esphome: 13 | name: modbus-test 14 | 15 | esp32: 16 | board: tinypico 17 | 18 | logger: 19 | level: DEBUG 20 | 21 | web_server: 22 | port: 80 23 | 24 | uart: 25 | tx_pin: 23 26 | rx_pin: 19 27 | -------------------------------------------------------------------------------- /renogy-modbus.yaml: -------------------------------------------------------------------------------- 1 | uart: 2 | baud_rate: 9600 3 | data_bits: 8 4 | stop_bits: 1 5 | parity: NONE 6 | 7 | modbus: 8 | send_wait_time: 50ms 9 | 10 | modbus_controller: 11 | 12 | - id: renogy 13 | address: 0xFF 14 | setup_priority: -10 15 | update_interval: 3s 16 | 17 | sensor: 18 | 19 | #region battery 20 | 21 | - id: ${location}_${device}_battery_soc 22 | name: $locationName $deviceName Battery State of Charge 23 | platform: modbus_controller 24 | modbus_controller_id: renogy 25 | address: 0x0100 26 | unit_of_measurement: '%' 27 | register_type: holding 28 | value_type: U_WORD 29 | accuracy_decimals: 0 30 | 31 | - id: ${location}_${device}_battery_voltage 32 | name: $locationName $deviceName Battery Voltage 33 | platform: modbus_controller 34 | modbus_controller_id: renogy 35 | address: 0x0101 36 | unit_of_measurement: V 37 | register_type: holding 38 | value_type: U_WORD 39 | accuracy_decimals: 1 40 | filters: 41 | - multiply: 0.1 42 | 43 | - id: ${location}_${device}_battery_current 44 | name: $locationName $deviceName Battery Current 45 | platform: modbus_controller 46 | modbus_controller_id: renogy 47 | address: 0x0102 48 | unit_of_measurement: A 49 | register_type: holding 50 | value_type: U_WORD 51 | accuracy_decimals: 2 52 | filters: 53 | - multiply: 0.01 54 | 55 | #endregion 56 | 57 | #region temperature 58 | 59 | - id: ${location}_${device}_controller_temperature 60 | name: $locationName $deviceName Controller Temperature 61 | platform: modbus_controller 62 | modbus_controller_id: renogy 63 | address: 0x0103 64 | bitmask: 0xFF00 65 | unit_of_measurement: '°C' 66 | register_type: holding 67 | value_type: S_WORD 68 | accuracy_decimals: 1 69 | 70 | # - id: ${location}_${device}_battery_temperature 71 | # name: $locationName $deviceName Battery Temperature 72 | # platform: modbus_controller 73 | # modbus_controller_id: renogy 74 | # address: 0x0103 75 | # bitmask: 0x00FF 76 | # unit_of_measurement: '°C' 77 | # register_type: holding 78 | # value_type: S_WORD 79 | # accuracy_decimals: 1 80 | 81 | #endregion 82 | 83 | #region load 84 | 85 | - id: ${location}_${device}_load_voltage 86 | name: $locationName $deviceName Load Voltage 87 | platform: modbus_controller 88 | modbus_controller_id: renogy 89 | address: 0x0104 90 | unit_of_measurement: V 91 | register_type: holding 92 | value_type: U_WORD 93 | accuracy_decimals: 1 94 | filters: 95 | - multiply: 0.1 96 | 97 | - id: ${location}_${device}_load_current 98 | name: $locationName $deviceName Load Current 99 | platform: modbus_controller 100 | modbus_controller_id: renogy 101 | address: 0x0105 102 | unit_of_measurement: A 103 | register_type: holding 104 | value_type: U_WORD 105 | accuracy_decimals: 2 106 | filters: 107 | - multiply: 0.01 108 | 109 | - id: ${location}_${device}_load_power 110 | name: $locationName $deviceName Load Power 111 | platform: modbus_controller 112 | modbus_controller_id: renogy 113 | address: 0x0106 114 | unit_of_measurement: W 115 | register_type: holding 116 | value_type: U_WORD 117 | accuracy_decimals: 1 118 | 119 | #endregion 120 | 121 | #region solar panel 122 | 123 | - id: ${location}_${device}_solar_voltage 124 | name: $locationName $deviceName Solar Voltage 125 | platform: modbus_controller 126 | modbus_controller_id: renogy 127 | address: 0x0107 128 | unit_of_measurement: V 129 | register_type: holding 130 | value_type: U_WORD 131 | accuracy_decimals: 1 132 | filters: 133 | - multiply: 0.1 134 | 135 | - id: ${location}_${device}_solar_current 136 | name: $locationName $deviceName Solar Current 137 | platform: modbus_controller 138 | modbus_controller_id: renogy 139 | address: 0x0108 140 | unit_of_measurement: A 141 | register_type: holding 142 | value_type: U_WORD 143 | accuracy_decimals: 2 144 | filters: 145 | - multiply: 0.1 146 | 147 | - id: ${location}_${device}_solar_power 148 | name: $locationName $deviceName Solar Power 149 | platform: modbus_controller 150 | modbus_controller_id: renogy 151 | address: 0x0109 152 | unit_of_measurement: W 153 | register_type: holding 154 | value_type: U_WORD 155 | accuracy_decimals: 1 156 | 157 | #endregion 158 | 159 | #region status 160 | 161 | - id: charging_state 162 | name: charging_state 163 | entity_category: diagnostic 164 | internal: true 165 | platform: modbus_controller 166 | modbus_controller_id: renogy 167 | address: 0x0120 168 | bitmask: 0x00FF 169 | register_type: holding 170 | value_type: U_WORD 171 | accuracy_decimals: 0 172 | on_value: 173 | then: 174 | - component.update: ${location}_${device}_charging_state 175 | 176 | - id: error_state 177 | name: error_state 178 | entity_category: diagnostic 179 | internal: true 180 | platform: modbus_controller 181 | modbus_controller_id: renogy 182 | address: 0x0121 183 | register_type: holding 184 | value_type: U_WORD 185 | accuracy_decimals: 0 186 | on_value: 187 | then: 188 | - component.update: ${location}_${device}_error_state 189 | 190 | #endregion 191 | 192 | #region statistics 193 | 194 | - id: ${location}_${device}_min_battery_voltage_today 195 | name: $locationName $deviceName Min Battery Voltage 196 | platform: modbus_controller 197 | modbus_controller_id: renogy 198 | address: 0x010B 199 | unit_of_measurement: V 200 | register_type: holding 201 | value_type: U_WORD 202 | accuracy_decimals: 1 203 | skip_updates: 1200 204 | filters: 205 | - multiply: 0.1 206 | 207 | - id: ${location}_${device}_max_battery_voltage_today 208 | name: $locationName $deviceName Max Battery Voltage 209 | platform: modbus_controller 210 | modbus_controller_id: renogy 211 | address: 0x010C 212 | unit_of_measurement: V 213 | register_type: holding 214 | value_type: U_WORD 215 | accuracy_decimals: 1 216 | skip_updates: 1200 217 | filters: 218 | - multiply: 0.1 219 | 220 | - id: ${location}_${device}_max_charging_current_today 221 | name: $locationName $deviceName Max Charging Current 222 | platform: modbus_controller 223 | modbus_controller_id: renogy 224 | address: 0x010D 225 | unit_of_measurement: A 226 | register_type: holding 227 | value_type: U_WORD 228 | accuracy_decimals: 2 229 | skip_updates: 1200 230 | filters: 231 | - multiply: 0.01 232 | 233 | - id: ${location}_${device}_max_discharging_current_today 234 | name: $locationName $deviceName Max Discharging Current 235 | platform: modbus_controller 236 | modbus_controller_id: renogy 237 | address: 0x010E 238 | unit_of_measurement: A 239 | register_type: holding 240 | value_type: U_WORD 241 | accuracy_decimals: 2 242 | skip_updates: 1200 243 | filters: 244 | - multiply: 0.01 245 | 246 | - id: ${location}_${device}_max_charging_power_today 247 | name: $locationName $deviceName Max Charging Power 248 | platform: modbus_controller 249 | modbus_controller_id: renogy 250 | address: 0x010F 251 | unit_of_measurement: W 252 | register_type: holding 253 | value_type: U_WORD 254 | accuracy_decimals: 1 255 | skip_updates: 1200 256 | 257 | - id: ${location}_${device}_max_discharging_power_today 258 | name: $locationName $deviceName Max Discharging Power 259 | platform: modbus_controller 260 | modbus_controller_id: renogy 261 | address: 0x0110 262 | unit_of_measurement: W 263 | register_type: holding 264 | value_type: U_WORD 265 | accuracy_decimals: 1 266 | skip_updates: 1200 267 | 268 | - id: ${location}_${device}_charging_amp_hours_today 269 | name: $locationName $deviceName Charged Current 270 | platform: modbus_controller 271 | modbus_controller_id: renogy 272 | address: 0x0111 273 | unit_of_measurement: AH 274 | register_type: holding 275 | value_type: U_WORD 276 | accuracy_decimals: 1 277 | skip_updates: 1200 278 | 279 | - id: ${location}_${device}_discharging_amp_hours_today 280 | name: $locationName $deviceName Discharged Current 281 | platform: modbus_controller 282 | modbus_controller_id: renogy 283 | address: 0x0112 284 | unit_of_measurement: AH 285 | register_type: holding 286 | value_type: U_WORD 287 | accuracy_decimals: 1 288 | skip_updates: 1200 289 | 290 | - id: ${location}_${device}_charging_watt_hours_today 291 | name: $locationName $deviceName Charged Power 292 | platform: modbus_controller 293 | modbus_controller_id: renogy 294 | address: 0x0113 295 | unit_of_measurement: WH 296 | register_type: holding 297 | value_type: U_WORD 298 | accuracy_decimals: 1 299 | skip_updates: 1200 300 | filters: 301 | - multiply: 10 302 | 303 | - id: ${location}_${device}_discharging_watt_hours_today 304 | name: $locationName $deviceName Discharged Power 305 | platform: modbus_controller 306 | modbus_controller_id: renogy 307 | address: 0x0114 308 | unit_of_measurement: WH 309 | register_type: holding 310 | value_type: U_WORD 311 | accuracy_decimals: 1 312 | skip_updates: 1200 313 | filters: 314 | - multiply: 10 315 | 316 | - id: ${location}_${device}_days_operational 317 | name: $locationName $deviceName Days Operational 318 | entity_category: diagnostic 319 | platform: modbus_controller 320 | modbus_controller_id: renogy 321 | address: 0x0115 322 | unit_of_measurement: d 323 | register_type: holding 324 | value_type: U_WORD 325 | accuracy_decimals: 0 326 | skip_updates: 1200 327 | 328 | - id: ${location}_${device}_battery_over_discharges 329 | name: $locationName $deviceName Battery Over-Discharges 330 | platform: modbus_controller 331 | modbus_controller_id: renogy 332 | address: 0x0116 333 | register_type: holding 334 | value_type: U_WORD 335 | accuracy_decimals: 0 336 | skip_updates: 1200 337 | 338 | - id: ${location}_${device}_battery_full_charges 339 | name: $locationName $deviceName Battery Full Charges 340 | platform: modbus_controller 341 | modbus_controller_id: renogy 342 | address: 0x0117 343 | register_type: holding 344 | value_type: U_WORD 345 | accuracy_decimals: 0 346 | skip_updates: 1200 347 | 348 | - id: ${location}_${device}_charging_amp_hours_total 349 | name: $locationName $deviceName Total Charged Current 350 | platform: modbus_controller 351 | modbus_controller_id: renogy 352 | address: 0x0118 353 | unit_of_measurement: AH 354 | register_type: holding 355 | register_count: 2 356 | value_type: U_DWORD 357 | accuracy_decimals: 1 358 | skip_updates: 1200 359 | 360 | - id: ${location}_${device}_discharging_amp_hours_total 361 | name: $locationName $deviceName Total Discharged Current 362 | platform: modbus_controller 363 | modbus_controller_id: renogy 364 | address: 0x011A 365 | unit_of_measurement: AH 366 | register_type: holding 367 | register_count: 2 368 | value_type: U_DWORD 369 | accuracy_decimals: 1 370 | skip_updates: 1200 371 | 372 | - id: ${location}_${device}_charging_watt_hours_total 373 | name: $locationName $deviceName Total Charged Power 374 | platform: modbus_controller 375 | modbus_controller_id: renogy 376 | address: 0x011C 377 | unit_of_measurement: WH 378 | register_type: holding 379 | register_count: 2 380 | value_type: U_DWORD 381 | accuracy_decimals: 1 382 | skip_updates: 1200 383 | filters: 384 | - multiply: 10 385 | 386 | - id: ${location}_${device}_discharging_watt_hours_total 387 | name: $locationName $deviceName Total Discharged Power 388 | platform: modbus_controller 389 | modbus_controller_id: renogy 390 | address: 0x011E 391 | unit_of_measurement: WH 392 | register_type: holding 393 | register_count: 2 394 | value_type: U_DWORD 395 | accuracy_decimals: 1 396 | skip_updates: 1200 397 | filters: 398 | - multiply: 10 399 | #endregion 400 | 401 | switch: 402 | 403 | - id: ${location}_${device}_light 404 | name: $locationName $deviceName Light 405 | platform: modbus_controller 406 | modbus_controller_id: renogy 407 | address: 0x010A 408 | register_type: holding 409 | bitmask: 1 410 | 411 | text_sensor: 412 | 413 | - id: ${location}_${device}_charging_state 414 | name: $locationName $deviceName Charging State 415 | platform: template 416 | icon: mdi:battery-charging 417 | lambda: |- 418 | auto current_state = (int)id(charging_state).state; 419 | if (current_state == 0) return {"Not Charging"}; 420 | if (current_state == 1) return {"Charging"}; 421 | if (current_state == 2) return {"Power Point Tracking"}; 422 | if (current_state == 3) return {"Equalizing"}; 423 | if (current_state == 4) return {"Boosting"}; 424 | if (current_state == 5) return {"Floating"}; 425 | if (current_state == 6) return {"Overpower"}; 426 | return {"Unknown"}; 427 | 428 | - id: ${location}_${device}_error_state 429 | name: $locationName $deviceName Error State 430 | entity_category: diagnostic 431 | platform: template 432 | icon: mdi:alert 433 | lambda: |- 434 | auto current_state = (int)id(error_state).state; 435 | if (current_state == 0) return {"Normal"}; 436 | if (current_state == 1) return {"Battery Over-Discharge"}; 437 | if (current_state == 2) return {"Battery Over-Voltage"}; 438 | if (current_state == 4) return {"Battery Under-Voltage"}; 439 | if (current_state == 8) return {"Load Short Circuit"}; 440 | if (current_state == 16) return {"Load Over-Power/Over-Current"}; 441 | if (current_state == 32) return {"Controller Over-Temperature"}; 442 | if (current_state == 64) return {"Ambient Over-Temperature"}; 443 | if (current_state == 128) return {"Solar Over-Power"}; 444 | if (current_state == 256) return {"Solar Short Circuit"}; 445 | if (current_state == 512) return {"Solar Over-Voltage"}; 446 | if (current_state == 1024) return {"Solar Counter Current"}; 447 | if (current_state == 2048) return {"Solar Working Point Over-Voltage"}; 448 | if (current_state == 4096) return {"Solar Reverse Connection"}; 449 | if (current_state == 8192) return {"Anti-Reverse MOS Short Circuit"}; 450 | if (current_state == 16384) return {"Charge MOS Short Circuit"}; 451 | return {"Unknown"}; --------------------------------------------------------------------------------