├── .gitattributes ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CNAME ├── LICENSE ├── README.md ├── RS485-WiFi-EPEver.ino.d1_mini.bin ├── RS485-WiFi-EPEver.ino.d1_mini.bin.gz ├── RS485-Wifi-Case-V2 ├── Base.stl ├── Lid.stl └── RS485-WiFi v2.step ├── Schematic └── Schematic_RS485 to wifi 1.5.pdf ├── _config.yml ├── epever-upower.yaml ├── epever.yaml ├── images ├── Board-Image.PNG ├── ESPHome flasher download.PNG └── Flashing.PNG ├── platformio.ini └── src ├── RS485-WiFi-EPEver.ino ├── config.h ├── gui.h ├── influxdb.h ├── mqtt.h ├── settings.cpp └── settings.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "functional": "cpp" 4 | } 5 | } -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | rs485towifi.eplop.co.uk -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0) 2 | 3 | Copyright (c) 2020 Colin Hickey 4 | 5 | License 6 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 7 | 8 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 9 | 10 | 1. Definitions 11 | 12 | "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 13 | "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(g) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. 14 | "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. 15 | "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, Noncommercial, ShareAlike. 16 | "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 17 | "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 18 | "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 19 | "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 20 | "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 21 | "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 22 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 23 | 24 | 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 25 | 26 | to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; 27 | to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 28 | to Distribute and Publicly Perform the Work including as incorporated in Collections; and, 29 | to Distribute and Publicly Perform Adaptations. 30 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights described in Section 4(e). 31 | 32 | 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 33 | 34 | You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(d), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(d), as requested. 35 | You may Distribute or Publicly Perform an Adaptation only under: (i) the terms of this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License"). You must include a copy of, or the URI, for Applicable License with every copy of each Adaptation You Distribute or Publicly Perform. You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License. You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. 36 | You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in con-nection with the exchange of copyrighted works. 37 | If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and, (iv) consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(d) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 38 | For the avoidance of doubt: 39 | 40 | Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 41 | Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(c) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and, 42 | Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(c). 43 | Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 44 | 5. Representations, Warranties and Disclaimer 45 | 46 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU. 47 | 48 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 49 | 50 | 7. Termination 51 | 52 | This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 53 | Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 54 | 8. Miscellaneous 55 | 56 | Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 57 | Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 58 | If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 59 | No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 60 | This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 61 | The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. 62 | Creative Commons Notice 63 | Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. 64 | 65 | Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. 66 | 67 | Creative Commons may be contacted at https://creativecommons.org/. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RS485 WiFi EPEver 2 | 3 | This is a DIY RS485 to wifi adaptor which allows you to connect to your EPEver controller over wifi. 4 | 5 | https://github.com/chickey/RS485-WiFi-EPEver/blob/master/images/Board-Image.PNG 6 | 7 | Many EPEver solar charge controllers have an RS485 communication port on them so you can connect a PC or mobile device and see it's live data as well as statistical data. The code also allows this data to be pushed to an MQTT broker or an Influx DB which could then be visualised via a grafana dashboard. 8 | 9 | I am currently tweaking and modifying the code so it shall evolve with more features as time goes on. 10 | 11 | I took great inspiration from the following project:- 12 | 13 | https://github.com/glitterkitty/EpEverSolarMonitor 14 | 15 | I am also using the ESPUI project which without this I wouldn't have had the doody interfaces :-) 16 | 17 | # Tested Hardware 18 | 19 | - LS1024B 20 | 21 | - Tracer 3210A 22 | 23 | - MPPT Tracer 1210 24 | 25 | - Tracer AN Series 26 | 27 | - 2210AN, 3210AN and 4210AN 28 | 29 | - TRITON Series with RS485 module 30 | 31 | 32 | 33 | # Updating 34 | 35 | Ensure when flashing the Wemos board that either it is not plugged into the RS485 adaptor or that that the board is not connected to the charge controller to avoid damage. 36 | 37 | You can now update remotely by browsing to http://IP/ota and browsing to the precompiled binary file and then selecting upload. 38 | 39 | 40 | 41 | # Discussion 42 | 43 | I'm always up for suggestions either via github or if you wish to chat with like minded people and pick people's brains on their setups i have setup a discord server 44 | 45 | https://discord.gg/kBDmrzE -------------------------------------------------------------------------------- /RS485-WiFi-EPEver.ino.d1_mini.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chickey/RS485-WiFi-EPEver/441866bb0d11743ace54229ebb18a21bdd3c3f84/RS485-WiFi-EPEver.ino.d1_mini.bin -------------------------------------------------------------------------------- /RS485-WiFi-EPEver.ino.d1_mini.bin.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chickey/RS485-WiFi-EPEver/441866bb0d11743ace54229ebb18a21bdd3c3f84/RS485-WiFi-EPEver.ino.d1_mini.bin.gz -------------------------------------------------------------------------------- /RS485-Wifi-Case-V2/Base.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chickey/RS485-WiFi-EPEver/441866bb0d11743ace54229ebb18a21bdd3c3f84/RS485-Wifi-Case-V2/Base.stl -------------------------------------------------------------------------------- /RS485-Wifi-Case-V2/Lid.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chickey/RS485-WiFi-EPEver/441866bb0d11743ace54229ebb18a21bdd3c3f84/RS485-Wifi-Case-V2/Lid.stl -------------------------------------------------------------------------------- /Schematic/Schematic_RS485 to wifi 1.5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chickey/RS485-WiFi-EPEver/441866bb0d11743ace54229ebb18a21bdd3c3f84/Schematic/Schematic_RS485 to wifi 1.5.pdf -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /epever-upower.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: epever-upower 3 | friendly_name: epever-upower 4 | 5 | esp8266: 6 | board: d1_mini 7 | 8 | # Enable logging 9 | logger: 10 | 11 | uart: 12 | id: mod_bus 13 | tx_pin: TX 14 | rx_pin: RX 15 | baud_rate: 115200 16 | stop_bits: 1 17 | 18 | modbus: 19 | flow_control_pin: D1 20 | id: modbus1 21 | send_wait_time: 200ms 22 | 23 | modbus_controller: 24 | - id: epever 25 | ## the Modbus device addr 26 | address: 0xA 27 | modbus_id: modbus1 28 | setup_priority: -10 29 | command_throttle: 200ms 30 | update_interval: 30s 31 | 32 | binary_sensor: 33 | - platform: modbus_controller 34 | modbus_controller_id: epever 35 | id: inverter_bypass 36 | name: "Inverter Bypass" 37 | # 0:NoBypass, 1:Bypass 38 | address: 0x2100 39 | register_type: discrete_input 40 | 41 | switch: 42 | #- platform: modbus_controller 43 | # modbus_controller_id: epever 44 | # id: reset_to_fabric_default 45 | # name: "Reset to Factory Default" 46 | # register_type: coil 47 | # address: 0x15 48 | # bitmask: 1 49 | 50 | - platform: modbus_controller 51 | modbus_controller_id: epever 52 | id: manual_control_load 53 | register_type: coil 54 | address: 0x0106 55 | name: "manual control the load" 56 | bitmask: 1 57 | 58 | - platform: modbus_controller 59 | modbus_controller_id: epever 60 | id: charge_from_grid 61 | name: "Charge from grid" 62 | address: 0x9607 63 | register_type: holding 64 | # 1:Solar Priority, 2:Utility & Solar, 3:Solar 65 | write_lambda: |- 66 | ESP_LOGD("main","Modbus Switch incoming state = %f",x); 67 | // return false ; // use this to just change the value 68 | payload.push_back(0x2); // On 69 | payload.push_back(0x3); // Off 70 | return true; 71 | 72 | sensor: 73 | - platform: wifi_signal 74 | name: "WiFi Signal" 75 | update_interval: 60s 76 | 77 | # Utility 78 | - platform: modbus_controller 79 | modbus_controller_id: epever 80 | id: grid_voltage 81 | name: "Grid Voltage" 82 | unit_of_measurement: V 83 | address: 0x3500 84 | register_type: read 85 | accuracy_decimals: 2 86 | filters: 87 | - multiply: 0.01 88 | - platform: modbus_controller 89 | modbus_controller_id: epever 90 | id: grid_current 91 | name: "Grid Current" 92 | unit_of_measurement: A 93 | address: 0x3501 94 | register_type: read 95 | accuracy_decimals: 2 96 | filters: 97 | - multiply: 0.01 98 | - platform: modbus_controller 99 | modbus_controller_id: epever 100 | id: grid_power 101 | name: "Grid Power" 102 | unit_of_measurement: W 103 | address: 0x3502 104 | register_type: read 105 | accuracy_decimals: 0 106 | filters: 107 | - multiply: 0.1 108 | - platform: modbus_controller 109 | modbus_controller_id: epever 110 | id: grid_state 111 | name: "Grid State" 112 | # 0:Normal, 1:Low input; 2:High input, 3: No connected 113 | address: 0x3511 114 | register_type: read 115 | - platform: modbus_controller 116 | modbus_controller_id: epever 117 | id: grid_total 118 | name: "Grid Total" 119 | unit_of_measurement: kWh 120 | address: 0x350F 121 | register_type: read 122 | accuracy_decimals: 2 123 | filters: 124 | - multiply: 0.01 125 | 126 | # PV Array 127 | - platform: modbus_controller 128 | modbus_controller_id: epever 129 | id: pv_voltage 130 | name: "PV Voltage" 131 | unit_of_measurement: V 132 | address: 0x3549 133 | register_type: read 134 | accuracy_decimals: 2 135 | filters: 136 | - multiply: 0.01 137 | - platform: modbus_controller 138 | modbus_controller_id: epever 139 | id: pv_current 140 | name: "PV Current" 141 | unit_of_measurement: A 142 | address: 0x354A 143 | register_type: read 144 | accuracy_decimals: 2 145 | filters: 146 | - multiply: 0.01 147 | - platform: modbus_controller 148 | modbus_controller_id: epever 149 | id: pv_power 150 | name: "PV Power" 151 | unit_of_measurement: W 152 | address: 0x354B 153 | register_type: read 154 | accuracy_decimals: 2 155 | filters: 156 | - multiply: 0.01 157 | - platform: modbus_controller 158 | modbus_controller_id: epever 159 | id: pv_total 160 | name: "PV Total" 161 | unit_of_measurement: kWh 162 | address: 0x3557 163 | register_type: read 164 | accuracy_decimals: 2 165 | filters: 166 | - multiply: 0.01 167 | 168 | # Load 169 | - platform: modbus_controller 170 | modbus_controller_id: epever 171 | id: load_voltage 172 | name: "Load Voltage" 173 | unit_of_measurement: V 174 | address: 0x3521 175 | register_type: read 176 | accuracy_decimals: 2 177 | filters: 178 | - multiply: 0.01 179 | - platform: modbus_controller 180 | modbus_controller_id: epever 181 | id: load_current 182 | name: "Load Current" 183 | unit_of_measurement: A 184 | address: 0x3522 185 | register_type: read 186 | accuracy_decimals: 2 187 | filters: 188 | - multiply: 0.01 189 | - platform: modbus_controller 190 | modbus_controller_id: epever 191 | id: load_total 192 | name: "Load Total" 193 | unit_of_measurement: kWh 194 | address: 0x3530 195 | register_type: read 196 | accuracy_decimals: 2 197 | filters: 198 | - multiply: 0.01 199 | 200 | # Battery 201 | - platform: modbus_controller 202 | modbus_controller_id: epever 203 | id: battery_voltage 204 | name: "Battery Voltage" 205 | unit_of_measurement: V 206 | address: 0x3580 207 | register_type: read 208 | accuracy_decimals: 2 209 | filters: 210 | - multiply: 0.01 211 | - platform: modbus_controller 212 | modbus_controller_id: epever 213 | id: battery_current 214 | name: "Battery Current" 215 | unit_of_measurement: A 216 | address: 0x3581 217 | register_type: read 218 | accuracy_decimals: 2 219 | filters: 220 | - multiply: 0.01 221 | - platform: modbus_controller 222 | modbus_controller_id: epever 223 | id: battery_capacity 224 | name: "Battery Capacity" 225 | unit_of_measurement: "%" 226 | address: 0x3586 227 | register_type: read 228 | accuracy_decimals: 1 229 | - platform: modbus_controller 230 | modbus_controller_id: epever 231 | id: battery_temp 232 | name: "Battery Temp" 233 | unit_of_measurement: "°C" 234 | address: 0x3512 235 | register_type: read 236 | filters: 237 | - multiply: 0.01 238 | - platform: modbus_controller 239 | modbus_controller_id: epever 240 | id: battery_state 241 | name: "Battery State" 242 | # 0:Normal, 1:Overvoltage, 2:Undervoltage, 3:Undervoltage Disconnect, 4:Fault 243 | address: 0x3589 244 | register_type: read 245 | - platform: modbus_controller 246 | modbus_controller_id: epever 247 | id: charging_mode 248 | name: "Charging Mode" 249 | address: 0x9607 250 | register_type: holding 251 | 252 | 253 | # Enable Home Assistant API 254 | api: 255 | encryption: 256 | key: "Qe6Y3FaoxNhQIltffbHTaTd/XgM7y3j9W3tSO46wu4A=" 257 | 258 | ota: 259 | password: "1876349ef83259523a36893c241d1ad2" 260 | 261 | wifi: 262 | ssid: !secret wifi_ssid 263 | password: !secret wifi_password 264 | 265 | # Enable fallback hotspot (captive portal) in case wifi connection fails 266 | ap: 267 | ssid: "Epever Fallback Hotspot" 268 | password: "69fYokgtS49H" 269 | 270 | captive_portal: 271 | 272 | web_server: 273 | port: 80 274 | -------------------------------------------------------------------------------- /epever.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: epever 3 | friendly_name: epever 4 | esp8266: 5 | board: d1_mini 6 | # Enable logging 7 | logger: 8 | baud_rate: 0 9 | 10 | uart: 11 | id: mod_bus 12 | tx_pin: TX 13 | rx_pin: RX 14 | baud_rate: 115200 15 | stop_bits: 1 16 | 17 | modbus: 18 | flow_control_pin: D1 19 | id: modbus1 20 | send_wait_time: 200ms 21 | 22 | modbus_controller: 23 | - id: epever 24 | ## the Modbus device addr 25 | address: 0x1 26 | modbus_id: modbus1 27 | setup_priority: -10 28 | command_throttle: 200ms 29 | update_interval: 30s 30 | 31 | text_sensor: 32 | - platform: modbus_controller 33 | modbus_controller_id: epever 34 | name: "Charging Mode" 35 | id: chg_mode 36 | address: 0x3201 37 | register_type: read 38 | raw_encode: HEXBYTES 39 | 40 | lambda: |- 41 | uint16_t orig_var, local_var = modbus_controller::word_from_hex_str(x, 0); 42 | //ESP_LOGI("mprowe", "The value of local_var is: %d", local_var); 43 | orig_var = local_var; 44 | local_var = local_var >> 2; 45 | //ESP_LOGI("mprowe", "local_var right shift 2 is: %d", local_var); 46 | local_var = local_var & 0x03; 47 | //ESP_LOGI("mprowe", "local_var & 0x03 is: %d", local_var); 48 | switch (local_var) { 49 | case 0: return std::string("Not Charging"); 50 | case 1: return std::string("Float Charging"); 51 | case 2: return std::string("Boost Charging"); 52 | case 3: return std::string("Equalizing"); 53 | default: return std::string("Unknown"); 54 | } 55 | - platform: modbus_controller 56 | modbus_controller_id: epever 57 | name: "Battery Status" 58 | id: battery_status 59 | address: 0x3200 60 | register_type: read 61 | raw_encode: HEXBYTES 62 | 63 | lambda: |- 64 | uint16_t local_var = modbus_controller::word_from_hex_str(x, 0); 65 | //ESP_LOGI("mprowe", "The value of local_var is: %d", local_var); 66 | local_var = local_var >> 2; 67 | //ESP_LOGI("mprowe", "local_var right shift 2 is: %d", local_var); 68 | local_var = local_var & 0x03; 69 | //ESP_LOGI("mprowe", "local_var & 0x03 is: %d", local_var); 70 | switch (local_var) { 71 | case 1: return std::string("Overvolt"); 72 | case 0: return std::string("Normal"); 73 | case 2: return std::string("Under Volt"); 74 | case 3: return std::string("Low Volt Disconnect"); 75 | case 4: return std::string("Fault"); 76 | default: return std::string("Unknown"); 77 | } 78 | 79 | switch: 80 | #- platform: modbus_controller 81 | # modbus_controller_id: epever 82 | # id: reset_to_fabric_default 83 | # name: "Reset to Factory Default" 84 | # register_type: coil 85 | # address: 0x15 86 | # bitmask: 1 87 | 88 | - platform: modbus_controller 89 | modbus_controller_id: epever 90 | id: manual_control_load 91 | register_type: coil 92 | address: 2 93 | name: "manual control the load" 94 | bitmask: 1 95 | 96 | sensor: 97 | - platform: wifi_signal 98 | name: "WiFi Signal" 99 | update_interval: 60s 100 | 101 | - platform: modbus_controller 102 | modbus_controller_id: epever 103 | id: generated_energy_today 104 | name: "Generated energy today" 105 | address: 0x330C 106 | device_class: power 107 | unit_of_measurement: "W" 108 | register_type: read 109 | value_type: U_WORD 110 | accuracy_decimals: 1 111 | #filters: 112 | # - multiply: 0.01 113 | 114 | - platform: modbus_controller 115 | modbus_controller_id: epever 116 | id: pv_input_voltage 117 | name: "PV array input voltage" 118 | address: 0x3100 119 | device_class: voltage 120 | unit_of_measurement: "V" 121 | register_type: read 122 | value_type: U_WORD 123 | accuracy_decimals: 1 124 | filters: 125 | - multiply: 0.01 126 | 127 | - platform: modbus_controller 128 | modbus_controller_id: epever 129 | id: pv_input_current 130 | name: "PV array input current" 131 | address: 0x3101 132 | device_class: current 133 | unit_of_measurement: "A" 134 | register_type: read 135 | value_type: U_WORD 136 | accuracy_decimals: 2 137 | filters: 138 | - multiply: 0.01 139 | 140 | - platform: modbus_controller 141 | modbus_controller_id: epever 142 | id: pv_input_power 143 | name: "PV array input power" 144 | address: 0x3102 145 | device_class: power 146 | unit_of_measurement: "W" 147 | register_type: read 148 | value_type: U_DWORD_R 149 | accuracy_decimals: 1 150 | filters: 151 | - multiply: 0.01 152 | 153 | - platform: modbus_controller 154 | modbus_controller_id: epever 155 | id: battery_voltage 156 | name: "Battery voltage" 157 | address: 0x3104 158 | device_class: voltage 159 | unit_of_measurement: "V" 160 | register_type: read 161 | value_type: U_WORD 162 | accuracy_decimals: 1 163 | filters: 164 | - multiply: 0.01 165 | 166 | - platform: modbus_controller 167 | modbus_controller_id: epever 168 | id: charging_current 169 | name: "Charging current" 170 | address: 0x3105 171 | device_class: current 172 | unit_of_measurement: "A" 173 | register_type: read 174 | value_type: U_WORD 175 | accuracy_decimals: 1 176 | filters: 177 | - multiply: 0.01 178 | 179 | - platform: modbus_controller 180 | modbus_controller_id: epever 181 | id: charging_power 182 | name: "Charging power" 183 | address: 0x3106 184 | device_class: power 185 | unit_of_measurement: "W" 186 | register_type: read 187 | value_type: U_DWORD_R 188 | accuracy_decimals: 1 189 | filters: 190 | - multiply: 0.01 191 | 192 | - platform: modbus_controller 193 | modbus_controller_id: epever 194 | id: load_voltage 195 | name: "Load voltage" 196 | address: 0x310C 197 | device_class: voltage 198 | unit_of_measurement: "V" 199 | register_type: read 200 | value_type: U_WORD 201 | accuracy_decimals: 1 202 | filters: 203 | - multiply: 0.01 204 | 205 | - platform: modbus_controller 206 | modbus_controller_id: epever 207 | id: load_current 208 | name: "Load Current" 209 | address: 0x310D 210 | device_class: current 211 | unit_of_measurement: "A" 212 | register_type: read 213 | value_type: U_WORD 214 | accuracy_decimals: 2 215 | filters: 216 | - multiply: 0.01 217 | 218 | - platform: modbus_controller 219 | modbus_controller_id: epever 220 | id: load_power 221 | name: "Load power" 222 | address: 0x310E 223 | device_class: power 224 | unit_of_measurement: "W" 225 | register_type: read 226 | value_type: U_DWORD_R 227 | accuracy_decimals: 1 228 | filters: 229 | - multiply: 0.01 230 | 231 | - platform: modbus_controller 232 | modbus_controller_id: epever 233 | id: battery_temperature 234 | name: "Battery temperature" 235 | address: 0x3110 236 | device_class: temperature 237 | unit_of_measurement: °C 238 | register_type: read 239 | value_type: S_WORD 240 | accuracy_decimals: 1 241 | filters: 242 | - multiply: 0.01 243 | 244 | - platform: modbus_controller 245 | modbus_controller_id: epever 246 | id: device_temperature 247 | name: "Device temperature" 248 | address: 0x3111 249 | device_class: temperature 250 | unit_of_measurement: °C 251 | register_type: read 252 | value_type: S_WORD 253 | accuracy_decimals: 1 254 | filters: 255 | - multiply: 0.01 256 | 257 | - platform: modbus_controller 258 | modbus_controller_id: epever 259 | id: battery_soc 260 | name: "Battery SOC" 261 | address: 0x311A 262 | device_class: battery 263 | unit_of_measurement: "%" 264 | register_type: read 265 | value_type: U_WORD 266 | accuracy_decimals: 0 267 | 268 | # Enable Home Assistant API 269 | api: 270 | encryption: 271 | key: "" 272 | 273 | ota: 274 | password: "1876349ef83259523a36893c241d1ad2" 275 | 276 | wifi: 277 | ssid: !secret wifi_ssid 278 | password: !secret wifi_password 279 | 280 | # Enable fallback hotspot (captive portal) in case wifi connection fails 281 | ap: 282 | ssid: "Epever Fallback Hotspot" 283 | password: "69fYokgtS49H" 284 | 285 | captive_portal: 286 | 287 | web_server: 288 | port: 80 289 | -------------------------------------------------------------------------------- /images/Board-Image.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chickey/RS485-WiFi-EPEver/441866bb0d11743ace54229ebb18a21bdd3c3f84/images/Board-Image.PNG -------------------------------------------------------------------------------- /images/ESPHome flasher download.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chickey/RS485-WiFi-EPEver/441866bb0d11743ace54229ebb18a21bdd3c3f84/images/ESPHome flasher download.PNG -------------------------------------------------------------------------------- /images/Flashing.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chickey/RS485-WiFi-EPEver/441866bb0d11743ace54229ebb18a21bdd3c3f84/images/Flashing.PNG -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | src_dir = src 13 | include_dir = src 14 | 15 | default_envs = esp8266 16 | 17 | 18 | [libraries] 19 | dnsserver = https://github.com/bbx10/DNSServer_tng.git#911319395a737f58bb9c2ecdd1a256215d4f0815 20 | modbus_master = https://github.com/4-20ma/ModbusMaster.git#3a05ff87677a9bdd8e027d6906dc05ca15ca8ade 21 | ArduinoJson = bblanchon/ArduinoJson @ 6.18.3 22 | pubsubclient = https://github.com/knolleary/pubsubclient.git#2d228f2f862a95846c65a8518c79f48dfc8f188c 23 | ESPAsyncWebServer = https://github.com/me-no-dev/ESPAsyncWebServer.git#1d46269cedf477661ca8a29518414f4b74e957d4 24 | ESPAsyncTCP = https://github.com/me-no-dev/ESPAsyncTCP.git#15476867dcbab906c0f1d47a7f63cdde223abeab 25 | esp_wifi_manager = https://github.com/alanswx/ESPAsyncWiFiManager.git#b19ec4d582f716d656e815049662615554028c2f 26 | ESPUI = https://github.com/s00500/ESPUI.git#05879186211737e7aa63005377b9d7a69865ac6d 27 | 28 | [env:esp8266] 29 | framework = arduino 30 | platform = espressif8266@2.6.3 31 | board = d1_mini 32 | ;board_build.flash_mode = qio 33 | upload_port = /dev/cu.usbserial-1420 34 | monitor_speed = 115200 35 | ;monitor_filters = esp8266_exception_decoder 36 | lib_deps = 37 | ${libraries.pubsubclient} 38 | ${libraries.esp_wifi_manager} 39 | ${libraries.dnsserver} 40 | ${libraries.modbus_master} 41 | ${libraries.ESPUI} 42 | ${libraries.ArduinoJson} 43 | ${libraries.ESPAsyncWebServer} 44 | ${libraries.ESPAsyncTCP} 45 | build_flags = 46 | ; '-DDEFAULT_DEVICE_ID=1' 47 | ; '-DDEFAULT_SERIAL_BAUD=115200' 48 | ; '-DDEFAULT_MQTT_SERVER="192.168.5.2"' 49 | ; '-DDEFAULT_MQTT_USERNAME="YOUR_MQTT_USERNAME"' 50 | ; '-DDEFAULT_MQTT_PASSWORD="YOUR_MQTT_PASSWORD"' 51 | ; '-DDEFAULT_MQTT_TOPIC="epever"' 52 | ; '-DDEFAULT_MQTT_PORT=1883' 53 | ; '-DTRANSMIT_PERIOD=10000' 54 | '-DMQTT_MAX_PACKET_SIZE=512' 55 | '-DLEGACY_MQTT' 56 | 57 | ; '-DDEFAULT_INFLUXDB_HOST="192.168.5.2"' 58 | ; '-DDEFAULT_INFLUXDB_DATABASE="powerwall"' 59 | ; '-DDEFAULT_INFLUXDB_USER="YOUR_INFLUX_USERNAME"' 60 | ; '-DDEFAULT_INFLUXDB_PASSWORD="YOUR_INFLUX_PASSWORD"' 61 | ; '-DDEFAULT_INFLUXDB_PORT=8086' 62 | 63 | ; '-DENABLE_HA_FACTORY_RESET_FUNCTIONS=true' ;uncomment this line to show a Factory Reset button on the About page and a control option for MQTT -------------------------------------------------------------------------------- /src/RS485-WiFi-EPEver.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * RS485 TO WIFI ADAPTOR CODE 3 | * https://github.com/chickey/RS485-WiFi-EPEver 4 | * by Colin Hickey 2021 5 | * 6 | * This code is designed to work with the specific board designed by meself which is on sale at tindie and my own website 7 | * https://store.eplop.co.uk/product/epever-rs485-to-wifi-adaptor-new-revision/ 8 | * https://www.tindie.com/products/plop211/epever-rs485-to-wifi-adaptor-v15/ 9 | * 10 | * 3D printed case is available at https://www.thingiverse.com/thing:4766788/files 11 | * 12 | * If your just using just the code and would like to help out a coffee is always appreciated paypal.me/colinmhickey 13 | * 14 | * A big thankyou to the following project for getting me on the right path https://github.com/glitterkitty/EpEverSolarMonitor 15 | * I also couldn't have made this without the ESPUI project. 16 | * 17 | * Version 0.61 18 | * 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | //#include 26 | #include 27 | #include 28 | #include 29 | 30 | #include //Local WebServer used to serve the configuration portal 31 | #include // switched from tapzu to https://github.com/khoih-prog/ESPAsync_WiFiManager 32 | 33 | #include 34 | 35 | const char* OTA_INDEX PROGMEM 36 | = R"=====(OTA
)====="; 37 | 38 | 39 | //////////////// 40 | //#define DEBUG 41 | //#define GUI_DEBUG 42 | //#define MQTT_DEBUG 43 | //#define INFLUX_DEBUG 44 | //////////////// 45 | 46 | #include "settings.h" 47 | #include "config.h" 48 | #include "mqtt.h" 49 | #include "influxdb.h" 50 | #include "gui.h" 51 | 52 | AsyncWebServer server(80); 53 | DNSServer dns; 54 | ModbusMaster node; // instantiate ModbusMaster object 55 | 56 | 57 | #ifndef TRANSMIT_SLOW_PERIOD 58 | #define TRANSMIT_SLOW_PERIOD 30000 59 | #endif 60 | unsigned long time_slow_now = 0; 61 | #ifndef TRANSMIT_MQTT_PERIOD 62 | #define TRANSMIT_MQTT_PERIOD 5000 63 | #endif 64 | unsigned long time_mqtt_now = 0; 65 | #ifndef TRANSMIT_INFLUX_PERIOD 66 | #define TRANSMIT_INFLUX_PERIOD 30000 67 | #endif 68 | unsigned long time_influx_now = 0; 69 | 70 | void setup(void) { 71 | //Attempt to read settings and if it fails resort to factory defaults 72 | if (!LoadConfigFromEEPROM()) 73 | FactoryResetSettings(); 74 | 75 | // Create ESPUI interface tabs 76 | uint16_t tab1 = ESPUI.addControl( ControlType::Tab, "Settings 1", "Live Data" ); 77 | uint16_t tab2 = ESPUI.addControl( ControlType::Tab, "Settings 2", "Historical Data" ); 78 | uint16_t tab3 = ESPUI.addControl( ControlType::Tab, "Settings 3", "Settings" ); 79 | uint16_t tab4 = ESPUI.addControl( ControlType::Tab, "Settings 4", "About" ); 80 | 81 | // Add Live Data controls 82 | SolarVoltage = ESPUI.addControl( ControlType::Label, "Solar Voltage", "0", ControlColor::Emerald, tab1); 83 | SolarAmps = ESPUI.addControl( ControlType::Label, "Solar Amps", "0", ControlColor::Emerald, tab1); 84 | SolarWattage = ESPUI.addControl( ControlType::Label, "Solar Wattage", "0", ControlColor::Emerald, tab1); 85 | BatteryVoltage = ESPUI.addControl( ControlType::Label, "Battery Voltage", "0", ControlColor::Emerald, tab1); 86 | BatteryAmps = ESPUI.addControl( ControlType::Label, "Battery Amps", "0", ControlColor::Emerald, tab1); 87 | BatteryWattage = ESPUI.addControl( ControlType::Label, "Battery Wattage", "0", ControlColor::Emerald, tab1); 88 | LoadVoltage = ESPUI.addControl( ControlType::Label, "Load Voltage", "0", ControlColor::Emerald, tab1); 89 | LoadAmps = ESPUI.addControl( ControlType::Label, "Load Amps", "0", ControlColor::Emerald, tab1); 90 | LoadWattage = ESPUI.addControl( ControlType::Label, "Load Wattage", "0", ControlColor::Emerald, tab1); 91 | BatteryStateOC = ESPUI.addControl( ControlType::Label, "Battery SOC", "0", ControlColor::Emerald, tab1); 92 | ChargingStatus = ESPUI.addControl( ControlType::Label, "Charging Status", "0", ControlColor::Emerald, tab1); 93 | BatteryStatus = ESPUI.addControl( ControlType::Label, "Battery Status", "4", ControlColor::Emerald, tab1); 94 | BatteryTemp = ESPUI.addControl( ControlType::Label, "Battery temperature", "0", ControlColor::Emerald, tab1); 95 | LoadStatus = ESPUI.addControl( ControlType::Label, "Load Status", "Off", ControlColor::Emerald, tab1); 96 | DeviceTempValue = ESPUI.addControl( ControlType::Label, "Device temperature", "0", ControlColor::Emerald, tab1); 97 | 98 | // Add Historical Data Controls 99 | Maxinputvolttoday = ESPUI.addControl( ControlType::Label, "Max input voltage today", "0", ControlColor::Emerald, tab2); 100 | Mininputvolttoday = ESPUI.addControl( ControlType::Label, "Min input voltage today", "0", ControlColor::Emerald, tab2); 101 | MaxBatteryvolttoday = ESPUI.addControl( ControlType::Label, "Max battery voltage today", "0", ControlColor::Emerald, tab2); 102 | MinBatteryvolttoday = ESPUI.addControl( ControlType::Label, "Min battery voltage today", "0", ControlColor::Emerald, tab2); 103 | ConsumedEnergyToday = ESPUI.addControl( ControlType::Label, "Consumed energy today", "0", ControlColor::Emerald, tab2); 104 | ConsumedEnergyMonth = ESPUI.addControl( ControlType::Label, "Consumed energy this month", "0", ControlColor::Emerald, tab2); 105 | ConsumedEngeryYear = ESPUI.addControl( ControlType::Label, "Consumed energy this year", "0", ControlColor::Emerald, tab2); 106 | TotalConsumedEnergy = ESPUI.addControl( ControlType::Label, "Total consumed energy", "0", ControlColor::Emerald, tab2); 107 | GeneratedEnergyToday = ESPUI.addControl( ControlType::Label, "Generated energy today", "0", ControlColor::Emerald, tab2); 108 | GeneratedEnergyMonth = ESPUI.addControl( ControlType::Label, "Generated energy this month", "0", ControlColor::Emerald, tab2); 109 | GeneratedEnergyYear = ESPUI.addControl( ControlType::Label, "Generated energy this year", "0", ControlColor::Emerald, tab2); 110 | TotalGeneratedEnergy = ESPUI.addControl( ControlType::Label, "Total generated energy", "0", ControlColor::Emerald, tab2); 111 | Co2Reduction = ESPUI.addControl( ControlType::Label, "Carbon dioxide reduction", "0", ControlColor::Emerald, tab2); 112 | 113 | // Add Local Settings controls 114 | INFLUXDBIP = ESPUI.addControl( ControlType::Text, "InfluxDB IP", DEFAULT_INFLUXDB_HOST, ControlColor::Emerald, tab3 ,&InfluxDBIPtxt); 115 | INFLUXDBPORT = ESPUI.addControl( ControlType::Text, "InfluxDB Port", String(DEFAULT_INFLUXDB_PORT), ControlColor::Emerald, tab3 ,&InfluxDBPorttxt); 116 | INFLUXDBDB = ESPUI.addControl( ControlType::Text, "InfluxDB Database", DEFAULT_INFLUXDB_DATABASE, ControlColor::Emerald, tab3 ,&InfluxDBtxt); 117 | INFLUXDBUSER = ESPUI.addControl( ControlType::Text, "InfluxDB Username", DEFAULT_INFLUXDB_USER, ControlColor::Emerald, tab3 ,&InfluxDBUsertxt); 118 | INFLUXDBPASS = ESPUI.addControl( ControlType::Text, "InfluxDB Password", DEFAULT_INFLUXDB_PASSWORD, ControlColor::Emerald, tab3 ,&InfluxDBPasstxt); 119 | INFLUXDBEN = ESPUI.addControl(ControlType::Switcher, "Enable InfluxDB", "", ControlColor::Alizarin,tab3, &InfluxDBEnSwitch); 120 | 121 | MQTTIP = ESPUI.addControl( ControlType::Text, "MQTT IP", DEFAULT_MQTT_SERVER, ControlColor::Emerald, tab3 ,&MQTTIPtxt); 122 | MQTTPORT = ESPUI.addControl( ControlType::Text, "MQTT Port", String(DEFAULT_MQTT_PORT), ControlColor::Emerald, tab3 ,&MQTTPorttxt); 123 | MQTTUSER = ESPUI.addControl( ControlType::Text, "MQTT Username", DEFAULT_MQTT_USERNAME, ControlColor::Emerald, tab3 ,&MQTTUsertxt); 124 | MQTTPASS = ESPUI.addControl( ControlType::Text, "MQTT Password", DEFAULT_MQTT_PASSWORD, ControlColor::Emerald, tab3 ,&MQTTPasstxt); 125 | MQTTTOPIC = ESPUI.addControl( ControlType::Text, "MQTT Topic", DEFAULT_MQTT_TOPIC, ControlColor::Emerald, tab3 ,&MQTTTopictxt); 126 | MQTTEN = ESPUI.addControl(ControlType::Switcher, "Enable MQTT", "", ControlColor::Alizarin,tab3, &MQTTEnSwitch); 127 | MQTT_HA_EN = ESPUI.addControl(ControlType::Switcher, "Enable HA Discovery", "", ControlColor::Alizarin,tab3, &MQTT_HAEnSwitch); 128 | 129 | DEVICEID = ESPUI.addControl( ControlType::Text, "Device ID", String(DEFAULT_DEVICE_ID), ControlColor::Emerald, tab3 ,&DEVICEIDtxt); 130 | DEVICEBAUD = ESPUI.addControl( ControlType::Text, "BAUD Rate", String(DEFAULT_SERIAL_BAUD), ControlColor::Emerald, tab3 ,&DEVICEBAUDtxt); 131 | 132 | LoadSwitchstate = ESPUI.addControl(ControlType::Switcher, "Load", "", ControlColor::Alizarin,tab3, &LoadSwitch); 133 | 134 | RebootButton = ESPUI.addControl( ControlType::Button, "Reboot", "Reboot", ControlColor::Peterriver, tab3, &RebootButtontxt ); 135 | SaveButton = ESPUI.addControl( ControlType::Button, "Save Settings", "Save", ControlColor::Peterriver, tab3, &SaveButtontxt ); 136 | savestatustxt = ESPUI.addControl( ControlType::Label, "Status:", "Changes Saved", ControlColor::Turquoise, tab3 ); 137 | 138 | Abouttxt = ESPUI.addControl( ControlType::Label, "", "RS485 TO WIFI ADAPTOR CODE

https://github.com/chickey/RS485-WiFi-EPEver

by Colin Hickey 2021

I'm always up for suggestions either via github or if you wish to chat with like minded people and pick people's brains on their setups i have setup a discord server

https://discord.gg/kBDmrzE", ControlColor::Turquoise,tab4 ); 139 | #ifdef ENABLE_HA_FACTORY_RESET_FUNCTIONS 140 | FactoryResetButton = ESPUI.addControl( ControlType::Button, "Reset to Factory Settings", "Reset", ControlColor::Alizarin, tab4, &FactoryResetButtontxt ); 141 | #endif 142 | 143 | Serial.begin(myConfig.Device_BAUD); 144 | 145 | // Connect D0 to RST to wake up 146 | pinMode(D0, WAKEUP_PULLUP); 147 | 148 | // init modbus in receive mode 149 | pinMode(MAX485_RE, OUTPUT); 150 | pinMode(MAX485_DE, OUTPUT); 151 | postTransmission(); 152 | 153 | // EPEver Device ID and Baud Rate 154 | node.begin(myConfig.Device_ID, Serial); 155 | 156 | // modbus callbacks 157 | node.preTransmission(preTransmission); 158 | node.postTransmission(postTransmission); 159 | 160 | AsyncWiFiManager wifiManager(&server,&dns); 161 | wifiManager.autoConnect("RS485-WiFi"); 162 | wifiManager.setConfigPortalTimeout(180); 163 | ESPUI.jsonInitialDocumentSize = 16000; // This is the default, adjust when you have too many widgets or options 164 | setupGUI(); //Start Web Interface with OTA enabled 165 | 166 | uint8_t baseMac[6]; 167 | WiFi.macAddress(baseMac); 168 | sprintf(baseMacChr, "%02X%02X%02X%02X%02X%02X", baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]); 169 | 170 | setPublishHADiscovery = myConfig.HADiscovery_Enable; 171 | 172 | } 173 | 174 | void niceDelay(unsigned long delayTime) 175 | { 176 | if (myConfig.MQTT_Enable) { 177 | // establish/keep mqtt connection 178 | mqtt_reconnect(); 179 | mqtt_client.loop(); 180 | } 181 | 182 | unsigned long startTime = millis(); 183 | while (millis() - startTime < delayTime) 184 | { 185 | yield(); 186 | } 187 | } 188 | 189 | uint16_t ReadTegister(uint16_t Register) { 190 | // Read register at the address passed in 191 | 192 | niceDelay(50); 193 | node.clearResponseBuffer(); 194 | uint8_t result = node.readInputRegisters(Register, 1); 195 | if (result == node.ku8MBSuccess) { 196 | 197 | EQChargeVoltValue = node.getResponseBuffer(0); 198 | 199 | #ifdef DEBUG 200 | Serial.println(String(node.getResponseBuffer(0))); 201 | #endif 202 | } else { 203 | #ifdef DEBUG 204 | Serial.print(F("Miss read - ")); 205 | Serial.print(Register); 206 | Serial.print(F(", ret val:")); 207 | Serial.println(result, HEX); 208 | #endif 209 | } 210 | return result; 211 | } 212 | 213 | void ReadValuesSlow() { 214 | // clear old data 215 | // 216 | memset(stats.buf,0,sizeof(stats.buf)); 217 | // Statistical Data 218 | niceDelay(50); 219 | node.clearResponseBuffer(); 220 | uint8_t result = node.readInputRegisters(STATISTICS, STATISTICS_CNT); 221 | 222 | if (result == node.ku8MBSuccess) { 223 | 224 | for(uint8_t i=0; i< STATISTICS_CNT ;i++) stats.buf[i] = node.getResponseBuffer(i); 225 | 226 | } else { 227 | #ifdef DEBUG 228 | Serial.print(F("Miss read statistics, ret val:")); 229 | Serial.println(result, HEX); 230 | #endif 231 | } 232 | 233 | // BATTERY_TYPE 234 | niceDelay(50); 235 | node.clearResponseBuffer(); 236 | result = node.readInputRegisters(BATTERY_TYPE, 1); 237 | if (result == node.ku8MBSuccess) { 238 | 239 | BatteryType = node.getResponseBuffer(0); 240 | #ifdef DEBUG 241 | Serial.println(String(node.getResponseBuffer(0))); 242 | #endif 243 | } else { 244 | #ifdef DEBUG 245 | Serial.print(F("Miss read BATTERY_TYPE, ret val:")); 246 | Serial.println(result, HEX); 247 | #endif 248 | } 249 | 250 | // EQ_CHARGE_VOLT 251 | niceDelay(50); 252 | node.clearResponseBuffer(); 253 | result = node.readInputRegisters(EQ_CHARGE_VOLT, 1); 254 | if (result == node.ku8MBSuccess) { 255 | 256 | EQChargeVoltValue = node.getResponseBuffer(0); 257 | #ifdef DEBUG 258 | Serial.println(String(node.getResponseBuffer(0))); 259 | #endif 260 | } else { 261 | #ifdef DEBUG 262 | Serial.print(F("Miss read EQ_CHARGE_VOLT, ret val:")); 263 | Serial.println(result, HEX); 264 | #endif 265 | } 266 | 267 | // CHARGING_LIMIT_VOLT 268 | niceDelay(50); 269 | node.clearResponseBuffer(); 270 | result = node.readInputRegisters(CHARGING_LIMIT_VOLT, 1); 271 | if (result == node.ku8MBSuccess) { 272 | 273 | ChargeLimitVolt = node.getResponseBuffer(0); 274 | #ifdef DEBUG 275 | Serial.println(String(node.getResponseBuffer(0))); 276 | #endif 277 | } else { 278 | #ifdef DEBUG 279 | Serial.print(F("Miss read CHARGING_LIMIT_VOLT, ret val:")); 280 | Serial.println(result, HEX); 281 | #endif 282 | } 283 | 284 | // Capacity 285 | niceDelay(50); 286 | node.clearResponseBuffer(); 287 | result = node.readInputRegisters(BATTERY_CAPACITY, 1); 288 | if (result == node.ku8MBSuccess) { 289 | 290 | BatteryCapactity = node.getResponseBuffer(0); 291 | #ifdef DEBUG 292 | Serial.println(String(node.getResponseBuffer(0))); 293 | #endif 294 | } else { 295 | #ifdef DEBUG 296 | Serial.print(F("Miss read BATTERY_CAPACITY, ret val:")); 297 | Serial.println(result, HEX); 298 | #endif 299 | } 300 | 301 | } 302 | 303 | void ReadValues() { 304 | // clear old data 305 | // 306 | memset(rtc.buf,0,sizeof(rtc.buf)); 307 | memset(live.buf,0,sizeof(live.buf)); 308 | 309 | // Read registers for clock 310 | // 311 | niceDelay(50); 312 | node.clearResponseBuffer(); 313 | uint8_t result = node.readHoldingRegisters(RTC_CLOCK, RTC_CLOCK_CNT); 314 | if (result == node.ku8MBSuccess) { 315 | 316 | rtc.buf[0] = node.getResponseBuffer(0); 317 | rtc.buf[1] = node.getResponseBuffer(1); 318 | rtc.buf[2] = node.getResponseBuffer(2); 319 | 320 | } else { 321 | #ifdef DEBUG 322 | Serial.print(F("Miss read rtc-data, ret val:")); 323 | Serial.println(result, HEX); 324 | #endif 325 | } 326 | if (result==226) ErrorCounter++; 327 | 328 | // read LIVE-Data 329 | // 330 | niceDelay(50); 331 | node.clearResponseBuffer(); 332 | result = node.readInputRegisters(LIVE_DATA, LIVE_DATA_CNT); 333 | 334 | if (result == node.ku8MBSuccess) { 335 | 336 | for(uint8_t i=0; i< LIVE_DATA_CNT ;i++) live.buf[i] = node.getResponseBuffer(i); 337 | 338 | } else { 339 | #ifdef DEBUG 340 | Serial.print(F("Miss read liva-data, ret val:")); 341 | Serial.println(result, HEX); 342 | #endif 343 | } 344 | 345 | // device temperature 346 | niceDelay(50); 347 | node.clearResponseBuffer(); 348 | result = node.readInputRegisters(DEVICE_TEMP, 1); 349 | if (result == node.ku8MBSuccess) { 350 | 351 | deviceTemp = node.getResponseBuffer(0); 352 | 353 | } else { 354 | #ifdef DEBUG 355 | Serial.print(F("Miss read device temperature, ret val:")); 356 | Serial.println(result, HEX); 357 | #endif 358 | } 359 | 360 | // battery SOC 361 | niceDelay(50); 362 | node.clearResponseBuffer(); 363 | result = node.readInputRegisters(BATTERY_SOC, 1); 364 | if (result == node.ku8MBSuccess) { 365 | 366 | batterySOC = node.getResponseBuffer(0); 367 | 368 | } else { 369 | #ifdef DEBUG 370 | Serial.print(F("Miss read batterySOC, ret val:")); 371 | Serial.println(result, HEX); 372 | #endif 373 | } 374 | 375 | // Battery Net Current = Icharge - Iload 376 | niceDelay(50); 377 | node.clearResponseBuffer(); 378 | result = node.readInputRegisters( BATTERY_CURRENT_L, 2); 379 | if (result == node.ku8MBSuccess) { 380 | 381 | batteryCurrent = node.getResponseBuffer(0); 382 | batteryCurrent |= node.getResponseBuffer(1) << 16; 383 | 384 | } else { 385 | #ifdef DEBUG 386 | Serial.print(F("Miss read batteryCurrent, ret val:")); 387 | Serial.println(result, HEX); 388 | #endif 389 | } 390 | 391 | if (!switch_load) { 392 | // State of the Load Switch 393 | niceDelay(50); 394 | node.clearResponseBuffer(); 395 | result = node.readCoils( LOAD_STATE, 1 ); 396 | if (result == node.ku8MBSuccess) { 397 | 398 | loadState = node.getResponseBuffer(0); 399 | 400 | } else { 401 | #ifdef DEBUG 402 | Serial.print(F("Miss read loadState, ret val:")); 403 | Serial.println(result, HEX); 404 | #endif 405 | } 406 | } 407 | 408 | // Read Model 409 | niceDelay(50); 410 | node.clearResponseBuffer(); 411 | result = node.readInputRegisters(CCMODEL, 1); 412 | if (result == node.ku8MBSuccess) { 413 | 414 | CCModel = node.getResponseBuffer(0); 415 | 416 | } else { 417 | #ifdef DEBUG 418 | Serial.print(F("Miss read Model, ret val:")); 419 | Serial.println(result, HEX); 420 | #endif 421 | } 422 | 423 | // Read Status Flags 424 | niceDelay(50); 425 | node.clearResponseBuffer(); 426 | result = node.readInputRegisters( 0x3200, 2 ); 427 | if (result == node.ku8MBSuccess) { 428 | 429 | uint16_t temp = node.getResponseBuffer(0); 430 | #ifdef DEBUG 431 | Serial.print(F("Batt Flags : ")); 432 | Serial.println(temp); 433 | #endif 434 | 435 | status_batt.volt = temp & 0b1111; 436 | status_batt.temp = (temp >> 4 ) & 0b1111; 437 | status_batt.resistance = (temp >> 8 ) & 0b1; 438 | status_batt.rated_volt = (temp >> 15 ) & 0b1; 439 | 440 | temp = node.getResponseBuffer(1); 441 | #ifdef DEBUG 442 | Serial.print(F("Chrg Flags : ")); 443 | Serial.println(temp, HEX); 444 | #endif 445 | 446 | charger_mode = ( temp & 0b0000000000001100 ) >> 2 ; 447 | #ifdef DEBUG 448 | Serial.print(F("charger_mode : ")); 449 | Serial.println( charger_mode ); 450 | #endif 451 | } else { 452 | #ifdef DEBUG 453 | Serial.print(F("Miss read ChargeState, ret val:")); 454 | Serial.println(result, HEX); 455 | #endif 456 | } 457 | } 458 | 459 | void preTransmission() 460 | { 461 | digitalWrite(MAX485_RE, 1); 462 | digitalWrite(MAX485_DE, 1); 463 | } 464 | 465 | void postTransmission() 466 | { 467 | digitalWrite(MAX485_RE, 0); 468 | digitalWrite(MAX485_DE, 0); 469 | } 470 | 471 | void debug_output(){ 472 | #ifdef DEBUG 473 | //Output values to serial 474 | Serial.printf("\n\nTime: 20%02d-%02d-%02d %02d:%02d:%02d \n", rtc.r.y , rtc.r.M , rtc.r.d , rtc.r.h , rtc.r.m , rtc.r.s ); 475 | 476 | Serial.print( F("\nLive-Data: Volt Amp Watt ")); 477 | Serial.printf( "\n Panel: %7.3f %7.3f %7.3f ", live.l.pV/100.f , live.l.pI/100.f , live.l.pP/100.0f ); 478 | Serial.printf( "\n Batt: %7.3f %7.3f %7.3f ", live.l.bV/100.f , live.l.bI/100.f , live.l.bP/100.0f ); 479 | Serial.printf( "\n Load: %7.3f %7.3f %7.3f \n", live.l.lV/100.f , live.l.lI/100.f , live.l.lP/100.0f ); 480 | Serial.printf( "\n Battery Current: %7.3f A ", batteryCurrent/100.f ); 481 | Serial.printf( "\n Battery SOC: %7.0f %% ", batterySOC/1.0f ); 482 | Serial.printf( "\n Load Switch: %s ", (loadState==1?" On":"Off") ); 483 | 484 | Serial.print( F("\n\nStatistics: ")); 485 | Serial.printf( "\n Panel: min: %7.3f max: %7.3f V", stats.s.pVmin/100.f , stats.s.pVmax/100.f ); 486 | Serial.printf( "\n Battery: min: %7.3f max: %7.3f V\n", stats.s.bVmin /100.f , stats.s.bVmax/100.f); 487 | 488 | Serial.printf( "\n Consumed: day: %7.3f mon: %7.3f year: %7.3f total: %7.3f kWh", 489 | stats.s.consEnerDay/100.f ,stats.s.consEnerMon/100.f ,stats.s.consEnerYear/100.f ,stats.s.consEnerTotal/100.f ); 490 | Serial.printf( "\n Generated: day: %7.3f mon: %7.3f year: %7.3f total: %7.3f kWh", 491 | stats.s.genEnerDay/100.f ,stats.s.genEnerMon/100.f ,stats.s.genEnerYear/100.f ,stats.s.genEnerTotal/100.f ); 492 | Serial.printf( "\n CO2-Reduction: %7.3f t\n", stats.s.c02Reduction/100.f ); 493 | 494 | Serial.print( F("\nStatus:")); 495 | Serial.printf( "\n batt.volt: %s ", batt_volt_status[status_batt.volt] ); 496 | Serial.printf( "\n batt.temp: %s ", batt_temp_status[status_batt.temp] ); 497 | Serial.printf( "\n charger.charging: %s \n\n", charger_charging_status[ charger_mode] ); 498 | #endif 499 | } 500 | 501 | void loop(void) { 502 | 503 | String buffer; 504 | buffer.reserve(64); 505 | // Print out to serial if debug is enabled. 506 | // 507 | #ifdef DEBUG 508 | debug_output(); 509 | #endif 510 | 511 | // Do these need to be continuously updated as they only change when the 512 | // user changes them via webpage 513 | // Disabling them should reduce the JSON packet size from ESPUI 514 | ESPUI.updateControlValue(MQTTIP , String(myConfig.mqtt_server)); 515 | ESPUI.updateControlValue(MQTTPORT , String(myConfig.mqtt_port)); 516 | ESPUI.updateControlValue(MQTTUSER , String(myConfig.mqtt_username)); 517 | ESPUI.updateControlValue(MQTTPASS , String(myConfig.mqtt_password)); 518 | ESPUI.updateControlValue(MQTTTOPIC , String(myConfig.mqtt_topic)); 519 | ESPUI.updateControlValue(MQTTEN , String(myConfig.MQTT_Enable)); 520 | ESPUI.updateControlValue(MQTT_HA_EN , String(myConfig.HADiscovery_Enable)); 521 | 522 | 523 | ESPUI.updateControlValue(INFLUXDBIP , String(myConfig.influxdb_host)); 524 | ESPUI.updateControlValue(INFLUXDBPORT , String(myConfig.influxdb_httpPort)); 525 | ESPUI.updateControlValue(INFLUXDBUSER , String(myConfig.influxdb_user)); 526 | ESPUI.updateControlValue(INFLUXDBPASS , String(myConfig.influxdb_password)); 527 | ESPUI.updateControlValue(INFLUXDBDB , String(myConfig.influxdb_database)); 528 | ESPUI.updateControlValue(INFLUXDBEN , String(myConfig.influxdb_enabled)); 529 | 530 | ESPUI.updateControlValue(DEVICEID , String(myConfig.Device_ID)); 531 | ESPUI.updateControlValue(DEVICEBAUD , String(myConfig.Device_BAUD)); 532 | 533 | // Read Values from Charge Controller 534 | ReadValues(); 535 | 536 | //Update ESPUI Live Data components 537 | ESPUI.updateControlValue(LoadStatus , String(loadState==1?F(" On"):F("Off"))); 538 | buffer.clear(); buffer.concat(deviceTemp/100.0f); buffer.concat(F("°C")); 539 | ESPUI.updateControlValue(DeviceTempValue , buffer); 540 | 541 | buffer.clear(); buffer.concat(live.l.pV/100.f); buffer.concat(F("V")); 542 | ESPUI.updateControlValue(SolarVoltage , buffer); 543 | 544 | buffer.clear(); buffer.concat(live.l.pI/100.f); buffer.concat(F("A")); 545 | ESPUI.updateControlValue(SolarAmps , buffer); 546 | 547 | buffer.clear(); buffer.concat(live.l.pP/100.0f); buffer.concat(F("w")); 548 | ESPUI.updateControlValue(SolarWattage , buffer); 549 | 550 | buffer.clear(); buffer.concat(live.l.bV/100.f); buffer.concat(F("V")); 551 | ESPUI.updateControlValue(BatteryVoltage , buffer); 552 | 553 | buffer.clear(); buffer.concat(live.l.bI/100.f); buffer.concat(F("A")); 554 | ESPUI.updateControlValue(BatteryAmps , buffer); 555 | 556 | buffer.clear(); buffer.concat(live.l.bP/100.0f); buffer.concat(F("w")); 557 | ESPUI.updateControlValue(BatteryWattage , buffer); 558 | 559 | buffer.clear(); buffer.concat(live.l.lV/100.f); buffer.concat(F("V")); 560 | ESPUI.updateControlValue(LoadVoltage , buffer); 561 | 562 | buffer.clear(); buffer.concat(live.l.lI/100.f); buffer.concat(F("A")); 563 | ESPUI.updateControlValue(LoadAmps , buffer); 564 | 565 | buffer.clear(); buffer.concat(live.l.lP/100.0f); buffer.concat(F("w")); 566 | ESPUI.updateControlValue(LoadWattage , buffer); 567 | 568 | buffer.clear(); buffer.concat(batterySOC/1.0f); buffer.concat(F("%")); 569 | ESPUI.updateControlValue(BatteryStateOC , buffer); 570 | ESPUI.updateControlValue(ChargingStatus , String(charger_charging_status[ charger_mode])); 571 | ESPUI.updateControlValue(BatteryStatus , String(batt_volt_status[status_batt.volt])); 572 | ESPUI.updateControlValue(BatteryTemp , String(batt_temp_status[status_batt.temp])); 573 | 574 | //Update historical values 575 | buffer.clear(); buffer.concat(stats.s.pVmax/100.f); buffer.concat(F("V")); 576 | ESPUI.updateControlValue(Maxinputvolttoday, buffer); 577 | 578 | buffer.clear(); buffer.concat(stats.s.pVmin/100.f); buffer.concat(F("V")); 579 | ESPUI.updateControlValue(Mininputvolttoday , buffer); 580 | 581 | buffer.clear(); buffer.concat(stats.s.bVmax/100.f); buffer.concat(F("V")); 582 | ESPUI.updateControlValue(MaxBatteryvolttoday , buffer); 583 | 584 | buffer.clear(); buffer.concat(stats.s.bVmin/100.f); buffer.concat(F("V")); 585 | ESPUI.updateControlValue(MinBatteryvolttoday, buffer); 586 | 587 | buffer.clear(); buffer.concat(stats.s.consEnerDay/100.f); buffer.concat(F("kWh")); 588 | ESPUI.updateControlValue(ConsumedEnergyToday , buffer); 589 | 590 | buffer.clear(); buffer.concat(stats.s.consEnerMon/100.f); buffer.concat(F("kWh")); 591 | ESPUI.updateControlValue(ConsumedEnergyMonth , buffer); 592 | 593 | buffer.clear(); buffer.concat(stats.s.consEnerYear/100.f); buffer.concat(F("kWh")); 594 | ESPUI.updateControlValue(ConsumedEngeryYear , buffer); 595 | 596 | buffer.clear(); buffer.concat(stats.s.consEnerTotal/100.f); buffer.concat(F("kWh")); 597 | ESPUI.updateControlValue(TotalConsumedEnergy , buffer); 598 | 599 | buffer.clear(); buffer.concat(stats.s.genEnerDay/100.f); buffer.concat(F("kWh")); 600 | ESPUI.updateControlValue(GeneratedEnergyToday , buffer); 601 | 602 | buffer.clear(); buffer.concat(stats.s.genEnerMon/100.f); buffer.concat(F("kWh")); 603 | ESPUI.updateControlValue(GeneratedEnergyMonth , buffer); 604 | 605 | buffer.clear(); buffer.concat(stats.s.genEnerYear/100.f); buffer.concat(F("kWh")); 606 | ESPUI.updateControlValue(GeneratedEnergyYear , buffer); 607 | 608 | buffer.clear(); buffer.concat(stats.s.genEnerTotal/100.f); buffer.concat(F("kWh")); 609 | ESPUI.updateControlValue(TotalGeneratedEnergy , buffer); 610 | 611 | buffer.clear(); buffer.concat(stats.s.c02Reduction/100.f); buffer.concat(F("t")); 612 | ESPUI.updateControlValue(Co2Reduction , buffer); 613 | 614 | // Do the Switching of the Load here and post new state to MQTT if enabled 615 | if( switch_load == 1 ){ 616 | switch_load = 0; 617 | 618 | #ifdef DEBUG 619 | Serial.print(F("Switching Load ")); 620 | Serial.println( (loadState?F("On"):F("Off")) ); 621 | niceDelay(200); 622 | #endif 623 | 624 | uint8_t result = node.writeSingleCoil(0x0002, loadState); 625 | 626 | #ifdef DEBUG 627 | if (result != node.ku8MBSuccess) { 628 | Serial.print(F("Miss write loadState, ret val:")); 629 | Serial.println(result, HEX); 630 | } 631 | #endif 632 | 633 | //reset the transmission timers to avoid publishing twice 634 | time_slow_now += TRANSMIT_SLOW_PERIOD; 635 | time_mqtt_now += TRANSMIT_MQTT_PERIOD; 636 | time_influx_now += TRANSMIT_INFLUX_PERIOD; 637 | if (myConfig.MQTT_Enable) { 638 | // establish/keep mqtt connection 639 | mqtt_reconnect(); 640 | mqtt_publish(SUBSET); 641 | mqtt_client.loop(); 642 | } 643 | 644 | // establish/keep influxdb connection 645 | if(myConfig.influxdb_enabled == 1) { 646 | Influxdb_postData(); 647 | } 648 | } 649 | 650 | // read less frequently needed values if timer has elapsed. 651 | if(millis() >= time_slow_now + TRANSMIT_SLOW_PERIOD){ 652 | time_slow_now += TRANSMIT_SLOW_PERIOD; 653 | time_mqtt_now += TRANSMIT_MQTT_PERIOD; 654 | ReadValuesSlow(); 655 | // also transmit slow read values 656 | if (myConfig.MQTT_Enable) { 657 | // establish/keep mqtt connection 658 | mqtt_reconnect(); 659 | mqtt_publish(COMPLETE); 660 | mqtt_client.loop(); 661 | } 662 | //check if we should publish the Home Assistant Discovery MQTT packets 663 | if (setPublishHADiscovery) 664 | { 665 | publishHADiscovery(); 666 | } 667 | } 668 | // Transmit to MQTT if timer has elapsed. 669 | if(millis() >= time_mqtt_now + TRANSMIT_MQTT_PERIOD){ 670 | time_mqtt_now += TRANSMIT_MQTT_PERIOD; 671 | 672 | if (myConfig.MQTT_Enable) { 673 | // establish/keep mqtt connection 674 | mqtt_reconnect(); 675 | mqtt_publish(SUBSET); 676 | mqtt_client.loop(); 677 | } 678 | } 679 | // Transmit to Influx if timer has elapsed. 680 | if(millis() >= time_influx_now + TRANSMIT_INFLUX_PERIOD){ 681 | time_influx_now += TRANSMIT_INFLUX_PERIOD; 682 | 683 | // establish/keep influxdb connection 684 | if(myConfig.influxdb_enabled == 1) { 685 | Influxdb_postData(); 686 | } 687 | } 688 | 689 | //Check error count and if it exceeds 5 reset modbus 690 | #ifdef DEBUG 691 | Serial.print(F("Error count = ")); 692 | Serial.println(ErrorCounter); 693 | #endif 694 | if (ErrorCounter>5) { 695 | // init modbus in receive mode 696 | pinMode(MAX485_RE, OUTPUT); 697 | pinMode(MAX485_DE, OUTPUT); 698 | postTransmission(); 699 | 700 | // EPEver Device ID and Baud Rate 701 | node.begin(myConfig.Device_ID, Serial); 702 | 703 | // modbus callbacks 704 | node.preTransmission(preTransmission); 705 | node.postTransmission(postTransmission); 706 | ErrorCounter = 0; 707 | } 708 | 709 | // power down MAX485_DE 710 | postTransmission(); 711 | } 712 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | 2 | // Pins 3 | #define MAX485_DE D2 // data or 4 | #define MAX485_RE D1 // recv enable 5 | 6 | // ModBus Register Locations 7 | #define LIVE_DATA 0x3100 // start of live-data 8 | #define LIVE_DATA_CNT 17 // 17 regs 9 | 10 | // just for reference, not used in code 11 | #define PANEL_VOLTS 0x00 12 | #define PANEL_AMPS 0x01 13 | #define PANEL_POWER_L 0x02 14 | #define PANEL_POWER_H 0x03 15 | 16 | #define BATT_VOLTS 0x04 17 | #define BATT_AMPS 0x05 18 | #define BATT_POWER_L 0x06 19 | #define BATT_POWER_H 0x07 20 | 21 | #define LOAD_VOLTS 0x0C 22 | #define LOAD_AMPS 0x0D 23 | #define LOAD_POWER_L 0x0E 24 | #define LOAD_POWER_H 0x0F 25 | 26 | #define CCMODEL 0x2b 27 | 28 | #define RTC_CLOCK 0x9013 // D7-0 Sec, D15-8 Min : D7-0 Hour, D15-8 Day : D7-0 Month, D15-8 Year 29 | #define RTC_CLOCK_CNT 3 // 3 regs 30 | 31 | #define DEVICE_TEMP 0x3111 // Device temperature 32 | #define BATTERY_SOC 0x311A // State of Charge in percent, 1 reg 33 | 34 | #define BATTERY_CURRENT_L 0x331B // Battery current L 35 | #define BATTERY_CURRENT_H 0x331C // Battery current H 36 | 37 | 38 | #define STATISTICS 0x3300 // start of statistical data 39 | #define STATISTICS_CNT 22 // 22 regs 40 | 41 | // just for reference, not used in code 42 | #define PV_MAX 0x00 // Maximum input volt (PV) today 43 | #define PV_MIN 0x01 // Minimum input volt (PV) today 44 | #define BATT_MAX 0x02 // Maximum battery volt today 45 | #define BATT_MIN 0x03 // Minimum battery volt today 46 | 47 | #define CONS_ENERGY_DAY_L 0x04 // Consumed energy today L 48 | #define CONS_ENGERY_DAY_H 0x05 // Consumed energy today H 49 | #define CONS_ENGERY_MON_L 0x06 // Consumed energy this month L 50 | #define CONS_ENGERY_MON_H 0x07 // Consumed energy this month H 51 | #define CONS_ENGERY_YEAR_L 0x08 // Consumed energy this year L 52 | #define CONS_ENGERY_YEAR_H 0x09 // Consumed energy this year H 53 | #define CONS_ENGERY_TOT_L 0x0A // Total consumed energy L 54 | #define CONS_ENGERY_TOT_H 0x0B // Total consumed energy H 55 | 56 | #define GEN_ENERGY_DAY_L 0x0C // Generated energy today L 57 | #define GEN_ENERGY_DAY_H 0x0D // Generated energy today H 58 | #define GEN_ENERGY_MON_L 0x0E // Generated energy this month L 59 | #define GEN_ENERGY_MON_H 0x0F // Generated energy this month H 60 | #define GEN_ENERGY_YEAR_L 0x10 // Generated energy this year L 61 | #define GEN_ENERGY_YEAR_H 0x11 // Generated energy this year H 62 | #define GEN_ENERGY_TOT_L 0x12 // Total generated energy L 63 | #define GEN_ENERGY_TOT_H 0x13 // Total Generated energy H 64 | 65 | #define CO2_REDUCTION_L 0x14 // Carbon dioxide reduction L 66 | #define CO2_REDUCTION_H 0x15 // Carbon dioxide reduction H 67 | 68 | #define LOAD_STATE 0x02 // r/w load switch state 69 | 70 | #define STATUS_FLAGS 0x3200 71 | #define STATUS_BATTERY 0x00 // Battery status register 72 | #define STATUS_CHARGER 0x01 // Charging equipment status register 73 | #define BATTERY_TYPE 0x9000 // Battery Type 0001H- Sealed , 0002H- GEL, 0003H- Flooded, 0000H- User defined 74 | #define BATTERY_CAPACITY 0x9001 // Rated capacity of the battery in Ah 75 | #define HIGH_VOLTAGE_DISCONNECT 0x9003 // 76 | #define CHARGING_LIMIT_VOLT 0x9004 // 77 | #define EQ_CHARGE_VOLT 0x9006 78 | 79 | uint16_t ErrorCounter=0; 80 | uint16_t savestatustxt; 81 | uint16_t Abouttxt; 82 | uint16_t SaveButton; 83 | uint16_t RebootButton; 84 | uint16_t FactoryResetButton; 85 | uint16_t Model; 86 | String CCModel; 87 | uint16_t StatusLabel; 88 | uint16_t BatteryStateOC; 89 | uint16_t ChargingStatus; 90 | uint16_t SolarVoltage; 91 | uint16_t SolarAmps; 92 | uint16_t SolarWattage; 93 | uint16_t BatteryVoltage; 94 | uint16_t BatteryAmps; 95 | uint16_t BatteryWattage; 96 | uint16_t BatteryStatus; 97 | uint16_t LoadVoltage; 98 | uint16_t LoadAmps; 99 | uint16_t LoadWattage; 100 | uint16_t LoadStatus; 101 | uint16_t LoadSwitchstate; 102 | uint16_t Status; 103 | uint16_t TPPassthrough; 104 | uint16_t DeviceTempValue; 105 | uint16_t MQTTEN; 106 | uint16_t MQTTIP; 107 | uint16_t MQTTPORT; 108 | uint16_t MQTTUSER; 109 | uint16_t MQTTPASS; 110 | uint16_t MQTTTOPIC; 111 | uint16_t MQTT_HA_EN; 112 | 113 | uint16_t DEVICEID; 114 | uint16_t DEVICEBAUD; 115 | 116 | uint16_t INFLUXDBIP; 117 | uint16_t INFLUXDBPORT; 118 | uint16_t INFLUXDBDB; 119 | uint16_t INFLUXDBUSER; 120 | uint16_t INFLUXDBPASS; 121 | uint16_t INFLUXDBEN; 122 | 123 | uint16_t OverVoltDist; 124 | uint16_t OverVoltRecon; 125 | uint16_t EQChargeVolt; 126 | uint16_t BoostChargeVolt; 127 | uint16_t FloatChargeVolt; 128 | uint16_t BoostReconChargeVolt; 129 | uint16_t BatteryChargePercent; 130 | uint16_t ChargeLimitVolt; 131 | uint16_t DischargeLimitVolt; 132 | uint16_t LowVoltDisconnect; 133 | uint16_t LowVoltReconnect; 134 | uint16_t UnderVoltWarningVolt; 135 | uint16_t UnderVoltReconnectVolt; 136 | uint16_t BatteryDischargePercent; 137 | uint16_t BoostDuration; 138 | uint16_t EQDuration; 139 | uint16_t BatteryCapactity; 140 | uint16_t BatteryType; 141 | 142 | uint16_t Maxinputvolttoday; 143 | uint16_t Mininputvolttoday; 144 | uint16_t MaxBatteryvolttoday; 145 | uint16_t MinBatteryvolttoday; 146 | uint16_t ConsumedEnergyToday; 147 | uint16_t ConsumedEnergyMonth; 148 | uint16_t ConsumedEngeryYear; 149 | uint16_t TotalConsumedEnergy; 150 | uint16_t GeneratedEnergyToday; 151 | uint16_t GeneratedEnergyMonth; 152 | uint16_t GeneratedEnergyYear; 153 | uint16_t TotalGeneratedEnergy; 154 | uint16_t Co2Reduction; 155 | uint16_t NetBatteryCurrent; 156 | uint16_t BatteryTemp; 157 | uint16_t AmbientTemp; 158 | 159 | uint16_t EQChargeVoltValue; 160 | 161 | // datastructures, also for buffer to values conversion 162 | // 163 | // clock 164 | union { 165 | struct { 166 | uint8_t s; 167 | uint8_t m; 168 | uint8_t h; 169 | uint8_t d; 170 | uint8_t M; 171 | uint8_t y; 172 | } r; 173 | uint16_t buf[3]; 174 | } rtc ; 175 | 176 | // live data 177 | union { 178 | struct { 179 | 180 | int16_t pV; 181 | int16_t pI; 182 | int32_t pP; 183 | 184 | int16_t bV; 185 | int16_t bI; 186 | int32_t bP; 187 | 188 | 189 | uint16_t dummy[4]; 190 | 191 | int16_t lV; 192 | int16_t lI; 193 | int32_t lP; 194 | int16_t bT; 195 | 196 | } l; 197 | uint16_t buf[17]; 198 | } live; 199 | 200 | 201 | // statistics 202 | union { 203 | struct { 204 | 205 | // 4*1 = 4 206 | uint16_t pVmax; 207 | uint16_t pVmin; 208 | uint16_t bVmax; 209 | uint16_t bVmin; 210 | 211 | // 4*2 = 8 212 | uint32_t consEnerDay; 213 | uint32_t consEnerMon; 214 | uint32_t consEnerYear; 215 | uint32_t consEnerTotal; 216 | 217 | // 4*2 = 8 218 | uint32_t genEnerDay; 219 | uint32_t genEnerMon; 220 | uint32_t genEnerYear; 221 | uint32_t genEnerTotal; 222 | 223 | // 1*2 = 2 224 | uint32_t c02Reduction; 225 | 226 | } s; 227 | uint16_t buf[22]; 228 | } stats; 229 | 230 | // these are too far away for the union conversion trick 231 | uint16_t deviceTemp = 0; 232 | uint16_t batterySOC = 0; 233 | int32_t batteryCurrent = 0; 234 | 235 | // battery status 236 | struct { 237 | uint8_t volt; // d3-d0 Voltage: 00H Normal, 01H Overvolt, 02H UnderVolt, 03H Low Volt Disconnect, 04H Fault 238 | uint8_t temp; // d7-d4 Temperature: 00H Normal, 01H Over warning settings, 02H Lower than the warning settings 239 | uint8_t resistance; // d8 abnormal 1, normal 0 240 | uint8_t rated_volt; // d15 1-Wrong identification for rated voltage 241 | } status_batt; 242 | 243 | const char* batt_volt_status[] = { 244 | "Normal", 245 | "Overvolt", 246 | "Low Volt Disconnect", 247 | "Fault" 248 | }; 249 | 250 | const char* batt_temp_status[] = { 251 | "Normal", 252 | "Over WarnTemp", 253 | "Below WarnTemp" 254 | }; 255 | 256 | uint8_t charger_mode = 0; 257 | 258 | const char* charger_charging_status[] = { 259 | "Off", 260 | "Float", 261 | "Boost", 262 | "Equlization" 263 | }; -------------------------------------------------------------------------------- /src/gui.h: -------------------------------------------------------------------------------- 1 | void handleOTAUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) 2 | { 3 | if (!index) 4 | { 5 | #if defined(DEBUG) || defined(GUI_DEBUG) 6 | Serial.print(F("UploadStart: ")); 7 | Serial.println(filename.c_str()); 8 | #endif 9 | // calculate sketch space required for the update, for ESP32 use the max constant 10 | #if defined(ESP32) 11 | if (!Update.begin(UPDATE_SIZE_UNKNOWN)) 12 | #else 13 | const uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; 14 | if (!Update.begin(maxSketchSpace)) 15 | #endif 16 | { 17 | // start with max available size 18 | Update.printError(Serial); 19 | } 20 | #if defined(ESP8266) 21 | Update.runAsync(true); 22 | #endif 23 | } 24 | 25 | if (len) 26 | { 27 | Update.write(data, len); 28 | } 29 | 30 | // if the final flag is set then this is the last frame of data 31 | if (final) 32 | { 33 | if (Update.end(true)) 34 | { 35 | 36 | #if defined(DEBUG) || defined(GUI_DEBUG) 37 | // true to set the size to the current progress 38 | Serial.print(F("Update Success: ")); 39 | Serial.print(index + len); 40 | Serial.println(F("b written\nRebooting...")); 41 | #endif 42 | ESP.restart(); 43 | } 44 | else 45 | { 46 | Update.printError(Serial); 47 | } 48 | } 49 | } 50 | 51 | void setupGUI() 52 | { 53 | ESPUI.jsonUpdateDocumentSize = 2000; // This is the default, and this value is not affected by the amount of widgets 54 | ESPUI.jsonInitialDocumentSize = 12000; // Default is 8000. Increased as there are a lot of widgets causing display to not work on newer versions of ESPUI library 55 | 56 | ESPUI.begin(DEVICE_FULL_NAME); // It is important that ESPUI.begin(...) is called first so that ESPUI.server is initalized 57 | 58 | ESPUI.server->on("/ota", 59 | HTTP_POST, 60 | [](AsyncWebServerRequest* request) { request->send(200); }, 61 | handleOTAUpload); 62 | 63 | ESPUI.server->on("/ota", 64 | HTTP_GET, 65 | [](AsyncWebServerRequest* request) { 66 | AsyncWebServerResponse* response = request->beginResponse_P(200, F("text/html"), OTA_INDEX); 67 | request->send(response); 68 | } 69 | ); 70 | } 71 | 72 | void SaveButtontxt(Control *sender, int type) { 73 | //check if already writing EEPRON (double button click) 74 | if (isWrittingEEPROM) return; 75 | 76 | switch (type) { 77 | case B_DOWN: 78 | #if defined(DEBUG) || defined(GUI_DEBUG) 79 | Serial.println(F("\nSave Button Pressed")); 80 | #endif 81 | 82 | WriteConfigToEEPROM(); 83 | ESPUI.updateControlValue(savestatustxt , "Changes Saved"); 84 | break; 85 | } 86 | } 87 | 88 | void RebootButtontxt(Control *sender, int type) { 89 | switch (type) { 90 | case B_DOWN: 91 | #if defined(DEBUG) || defined(GUI_DEBUG) 92 | Serial.println(F("Rebooting")); 93 | #endif 94 | ESP.restart(); 95 | break; 96 | } 97 | } 98 | 99 | #ifdef ENABLE_HA_FACTORY_RESET_FUNCTIONS 100 | void FactoryResetButtontxt(Control *sender, int type) { 101 | switch (type) { 102 | case B_DOWN: 103 | #if defined(DEBUG) || defined(GUI_DEBUG) 104 | Serial.println(F("Resetging Flash and Rebooting")); 105 | #endif 106 | FactoryResetSettings(); 107 | ESP.restart(); 108 | break; 109 | } 110 | } 111 | #endif 112 | 113 | void OverVoltDisttxt(Control *sender, int type) { 114 | #if defined(DEBUG) || defined(GUI_DEBUG) 115 | Serial.print(F("Text: ID: ")); 116 | Serial.print(sender->id); 117 | Serial.print(F(", Value: ")); 118 | Serial.println(sender->value); 119 | #endif 120 | } 121 | 122 | void OverVoltRecontxt(Control *sender, int type) { 123 | #if defined(DEBUG) || defined(GUI_DEBUG) 124 | Serial.print(F("Text: ID: ")); 125 | Serial.print(sender->id); 126 | Serial.print(F(", Value: ")); 127 | Serial.println(sender->value); 128 | #endif 129 | } 130 | 131 | void EQChargeVolttxt(Control *sender, int type) { 132 | #if defined(DEBUG) || defined(GUI_DEBUG) 133 | Serial.print(F("Text: ID: ")); 134 | Serial.print(sender->id); 135 | Serial.print(F(", Value: ")); 136 | Serial.println(sender->value); 137 | #endif 138 | } 139 | 140 | void BoostChargeVolttxt(Control *sender, int type) { 141 | #if defined(DEBUG) || defined(GUI_DEBUG) 142 | Serial.print(F("Text: ID: ")); 143 | Serial.print(sender->id); 144 | Serial.print(F(", Value: ")); 145 | Serial.println(sender->value); 146 | #endif 147 | } 148 | 149 | void FloatChargeVolttxt(Control *sender, int type) { 150 | #if defined(DEBUG) || defined(GUI_DEBUG) 151 | Serial.print(F("Text: ID: ")); 152 | Serial.print(sender->id); 153 | Serial.print(F(", Value: ")); 154 | Serial.println(sender->value); 155 | #endif 156 | } 157 | 158 | void BoostReconChargeVolttxt(Control *sender, int type) { 159 | #if defined(DEBUG) || defined(GUI_DEBUG) 160 | Serial.print(F("Text: ID: ")); 161 | Serial.print(sender->id); 162 | Serial.print(F(", Value: ")); 163 | Serial.println(sender->value); 164 | #endif 165 | } 166 | 167 | void BatteryChargePercenttxt(Control *sender, int type) { 168 | #if defined(DEBUG) || defined(GUI_DEBUG) 169 | Serial.print(F("Text: ID: ")); 170 | Serial.print(sender->id); 171 | Serial.print(F(", Value: ")); 172 | Serial.println(sender->value); 173 | #endif 174 | } 175 | 176 | void ChargeLimitVolttxt(Control *sender, int type) { 177 | #if defined(DEBUG) || defined(GUI_DEBUG) 178 | Serial.print(F("Text: ID: ")); 179 | Serial.print(sender->id); 180 | Serial.print(F(", Value: ")); 181 | Serial.println(sender->value); 182 | #endif 183 | } 184 | 185 | void DischargeLimitVolttxt(Control *sender, int type) { 186 | #if defined(DEBUG) || defined(GUI_DEBUG) 187 | Serial.print(F("Text: ID: ")); 188 | Serial.print(sender->id); 189 | Serial.print(F(", Value: ")); 190 | Serial.println(sender->value); 191 | #endif 192 | } 193 | 194 | void LowVoltDisconnecttxt(Control *sender, int type) { 195 | #if defined(DEBUG) || defined(GUI_DEBUG) 196 | Serial.print(F("Text: ID: ")); 197 | Serial.print(sender->id); 198 | Serial.print(F(", Value: ")); 199 | Serial.println(sender->value); 200 | #endif 201 | } 202 | 203 | void LowVoltReconnecttxt(Control *sender, int type) { 204 | #if defined(DEBUG) || defined(GUI_DEBUG) 205 | Serial.print(F("Text: ID: ")); 206 | Serial.print(sender->id); 207 | Serial.print(F(", Value: ")); 208 | Serial.println(sender->value); 209 | #endif 210 | } 211 | 212 | void UnderVoltWarningVolttxt(Control *sender, int type) { 213 | #if defined(DEBUG) || defined(GUI_DEBUG) 214 | Serial.print(F("Text: ID: ")); 215 | Serial.print(sender->id); 216 | Serial.print(F(", Value: ")); 217 | Serial.println(sender->value); 218 | #endif 219 | } 220 | 221 | void UnderVoltReconnectVolttxt(Control *sender, int type) { 222 | #if defined(DEBUG) || defined(GUI_DEBUG) 223 | Serial.print(F("Text: ID: ")); 224 | Serial.print(sender->id); 225 | Serial.print(F(", Value: ")); 226 | Serial.println(sender->value); 227 | #endif 228 | } 229 | 230 | void BatteryDischargePercenttxt(Control *sender, int type) { 231 | #if defined(DEBUG) || defined(GUI_DEBUG) 232 | Serial.print(F("Text: ID: ")); 233 | Serial.print(sender->id); 234 | Serial.print(F(", Value: ")); 235 | Serial.println(sender->value); 236 | #endif 237 | } 238 | 239 | void BoostDurationtxt(Control *sender, int type) { 240 | #if defined(DEBUG) || defined(GUI_DEBUG) 241 | Serial.print(F("Text: ID: ")); 242 | Serial.print(sender->id); 243 | Serial.print(F(", Value: ")); 244 | Serial.println(sender->value); 245 | #endif 246 | } 247 | 248 | void EQDurationtxt(Control *sender, int type) { 249 | #if defined(DEBUG) || defined(GUI_DEBUG) 250 | Serial.print(F("Text: ID: ")); 251 | Serial.print(sender->id); 252 | Serial.print(F(", Value: ")); 253 | Serial.println(sender->value); 254 | #endif 255 | } 256 | 257 | void BatteryCapactitytxt(Control *sender, int type) { 258 | #if defined(DEBUG) || defined(GUI_DEBUG) 259 | Serial.print(F("Text: ID: ")); 260 | Serial.print(sender->id); 261 | Serial.print(F(", Value: ")); 262 | Serial.println(sender->value); 263 | #endif 264 | } 265 | 266 | void DEVICEIDtxt(Control *sender, int type) { 267 | #if defined(DEBUG) || defined(GUI_DEBUG) 268 | Serial.print(F("Text: ID: ")); 269 | Serial.print(sender->id); 270 | Serial.print(F(", Value: ")); 271 | Serial.println(sender->value); 272 | #endif 273 | myConfig.Device_ID = atoi ( (sender->value).c_str() ); 274 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 275 | } 276 | 277 | void DEVICEBAUDtxt(Control *sender, int type) { 278 | #if defined(DEBUG) || defined(GUI_DEBUG) 279 | Serial.print(F("Text: ID: ")); 280 | Serial.print(sender->id); 281 | Serial.print(F(", Value: ")); 282 | Serial.println(sender->value); 283 | Serial.begin(myConfig.Device_BAUD); 284 | #endif 285 | myConfig.Device_BAUD = atoi ( (sender->value).c_str() ); 286 | Serial.begin(myConfig.Device_BAUD); 287 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 288 | } 289 | 290 | void MQTTIPtxt(Control *sender, int type) { 291 | #if defined(DEBUG) || defined(GUI_DEBUG) 292 | Serial.print(F("Text: ID: ")); 293 | Serial.print(sender->id); 294 | Serial.print(F(", Value: ")); 295 | Serial.println(sender->value); 296 | #endif 297 | strcpy(myConfig.mqtt_server,(sender->value).c_str()); 298 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 299 | } 300 | 301 | void MQTTPorttxt(Control *sender, int type) { 302 | #if defined(DEBUG) || defined(GUI_DEBUG) 303 | Serial.print(F("Text: ID: ")); 304 | Serial.print(sender->id); 305 | Serial.print(F(", Value: ")); 306 | Serial.println(sender->value); 307 | #endif 308 | myConfig.mqtt_port = atoi ( (sender->value).c_str() ); 309 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 310 | } 311 | 312 | void MQTTUsertxt(Control *sender, int type) { 313 | #if defined(DEBUG) || defined(GUI_DEBUG) 314 | Serial.print(F("Text: ID: ")); 315 | Serial.print(sender->id); 316 | Serial.print(F(", Value: ")); 317 | Serial.println(sender->value); 318 | #endif 319 | strcpy(myConfig.mqtt_username,(sender->value).c_str()); 320 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 321 | } 322 | 323 | void MQTTPasstxt(Control *sender, int type) { 324 | #if defined(DEBUG) || defined(GUI_DEBUG) 325 | Serial.print(F("Text: ID: ")); 326 | Serial.print(sender->id); 327 | Serial.print(F(", Value: ")); 328 | Serial.println(sender->value); 329 | #endif 330 | strcpy(myConfig.mqtt_password,(sender->value).c_str()); 331 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 332 | } 333 | 334 | void MQTTTopictxt(Control *sender, int type) { 335 | #if defined(DEBUG) || defined(GUI_DEBUG) 336 | Serial.print(F("Text: ID: ")); 337 | Serial.print(sender->id); 338 | Serial.print(F(", Value: ")); 339 | Serial.println(sender->value); 340 | #endif 341 | strcpy(myConfig.mqtt_topic,(sender->value).c_str()); 342 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 343 | } 344 | 345 | void InfluxDBIPtxt(Control *sender, int type) { 346 | #if defined(DEBUG) || defined(GUI_DEBUG) 347 | Serial.print(F("Text: ID: ")); 348 | Serial.print(sender->id); 349 | Serial.print(F(", Value: ")); 350 | Serial.println(sender->value); 351 | #endif 352 | strcpy(myConfig.influxdb_host,(sender->value).c_str()); 353 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 354 | } 355 | 356 | void InfluxDBPorttxt(Control *sender, int type) { 357 | #if defined(DEBUG) || defined(GUI_DEBUG) 358 | Serial.print(F("Text: ID: ")); 359 | Serial.print(sender->id); 360 | Serial.print(F(", Value: ")); 361 | Serial.println(sender->value); 362 | #endif 363 | myConfig.influxdb_httpPort = atoi ( (sender->value).c_str() ); 364 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 365 | } 366 | 367 | void InfluxDBtxt(Control *sender, int type) { 368 | #if defined(DEBUG) || defined(GUI_DEBUG) 369 | Serial.print(F("Text: ID: ")); 370 | Serial.print(sender->id); 371 | Serial.print(F(", Value: ")); 372 | Serial.println(sender->value); 373 | #endif 374 | strcpy(myConfig.influxdb_database,(sender->value).c_str()); 375 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 376 | } 377 | 378 | void InfluxDBUsertxt(Control *sender, int type) { 379 | #if defined(DEBUG) || defined(GUI_DEBUG) 380 | Serial.print(F("Text: ID: ")); 381 | Serial.print(sender->id); 382 | Serial.print(F(", Value: ")); 383 | Serial.println(sender->value); 384 | #endif 385 | strcpy(myConfig.influxdb_user,(sender->value).c_str()); 386 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 387 | } 388 | 389 | void InfluxDBPasstxt(Control *sender, int type) { 390 | #if defined(DEBUG) || defined(GUI_DEBUG) 391 | Serial.print(F("Text: ID: ")); 392 | Serial.print(sender->id); 393 | Serial.print(F(", Value: ")); 394 | Serial.println(sender->value); 395 | #endif 396 | strcpy(myConfig.influxdb_password,(sender->value).c_str()); 397 | ESPUI.updateControlValue(savestatustxt , "Changes Unsaved"); 398 | } 399 | 400 | void BatteryTypeList(Control *sender, int type) { 401 | #if defined(DEBUG) || defined(GUI_DEBUG) 402 | Serial.print(F("Text: ID: ")); 403 | Serial.print(sender->id); 404 | Serial.print(F(", Value: ")); 405 | Serial.println(sender->value); 406 | #endif 407 | } 408 | 409 | void ChargingModeList(Control *sender, int type) { 410 | #if defined(DEBUG) || defined(GUI_DEBUG) 411 | Serial.print(F("Text: ID: ")); 412 | Serial.print(sender->id); 413 | Serial.print(F(", Value: ")); 414 | Serial.println(sender->value); 415 | #endif 416 | } 417 | 418 | void RatedVoltagelvlList(Control *sender, int type) { 419 | #if defined(DEBUG) || defined(GUI_DEBUG) 420 | Serial.print(F("Text: ID: ")); 421 | Serial.print(sender->id); 422 | Serial.print(F(", Value: ")); 423 | Serial.println(sender->value); 424 | #endif 425 | } 426 | 427 | void LoadSwitch(Control *sender, int value) { 428 | switch (value) { 429 | case S_ACTIVE: 430 | loadState = true; 431 | switch_load = 1; 432 | break; 433 | 434 | case S_INACTIVE: 435 | loadState = false; 436 | switch_load = 1; 437 | break; 438 | } 439 | 440 | #if defined(DEBUG) || defined(GUI_DEBUG) 441 | Serial.print(value == S_ACTIVE ? F("Active: ") : F("Inactive ")); 442 | Serial.println(sender->id); 443 | #endif 444 | } 445 | 446 | void InfluxDBEnSwitch(Control *sender, int value) { 447 | switch (value) { 448 | case S_ACTIVE: 449 | myConfig.influxdb_enabled = 1; 450 | break; 451 | 452 | case S_INACTIVE: 453 | myConfig.influxdb_enabled = 0; 454 | break; 455 | } 456 | 457 | #if defined(DEBUG) || defined(GUI_DEBUG) 458 | Serial.print(value == S_ACTIVE ? F("Active: ") : F("Inactive ")); 459 | Serial.println(sender->id); 460 | #endif 461 | } 462 | 463 | void MQTTEnSwitch(Control *sender, int value) { 464 | switch (value) { 465 | case S_ACTIVE: 466 | myConfig.MQTT_Enable = true; 467 | setPublishHADiscovery = myConfig.HADiscovery_Enable; 468 | break; 469 | 470 | case S_INACTIVE: 471 | myConfig.MQTT_Enable = false; 472 | setPublishHADiscovery = false; 473 | break; 474 | } 475 | 476 | #if defined(DEBUG) || defined(GUI_DEBUG) 477 | Serial.print(value == S_ACTIVE ? F("Active: ") : F("Inactive ")); 478 | Serial.println(sender->id); 479 | #endif 480 | } 481 | 482 | void MQTT_HAEnSwitch(Control *sender, int value) { 483 | switch (value) { 484 | case S_ACTIVE: 485 | myConfig.HADiscovery_Enable = true; 486 | setPublishHADiscovery = myConfig.MQTT_Enable; 487 | break; 488 | 489 | case S_INACTIVE: 490 | myConfig.HADiscovery_Enable = false; 491 | setPublishHADiscovery = false; 492 | break; 493 | } 494 | } -------------------------------------------------------------------------------- /src/influxdb.h: -------------------------------------------------------------------------------- 1 | void Influxdb_postData() { 2 | String poststring, url; 3 | poststring.reserve(768); //reserve String in memory to prevent fragmentation 4 | url.reserve(256); 5 | HTTPClient http; 6 | 7 | //Construct URL for the influxdb 8 | url.concat(F("http://")); 9 | url.concat(myConfig.influxdb_host); 10 | url.concat(F(":")); 11 | url.concat(myConfig.influxdb_httpPort); 12 | url.concat(F("/write?db=")); 13 | url.concat(myConfig.influxdb_database); 14 | url.concat(F("&u=")); 15 | url.concat(myConfig.influxdb_user); 16 | url.concat(F("&p=")); 17 | url.concat(myConfig.influxdb_password); 18 | 19 | #if defined(DEBUG) || defined(INFLUX_DEBUG) 20 | Serial.println(url); 21 | #endif 22 | 23 | //Output to grafana using a http post 24 | poststring.concat(F("Panel-Voltage value=")); 25 | poststring.concat(live.l.pV /100.f); 26 | poststring.concat(F("\nPanel-Amp value=")); 27 | poststring.concat(live.l.pI /100.f); 28 | poststring.concat(F("\nPanel-Watt value=")); 29 | poststring.concat(live.l.pP /100.f); 30 | poststring.concat(F("\nBattery-Voltage value=")); 31 | poststring.concat(live.l.bV /100.f); 32 | poststring.concat(F("\nBattery-Amp value=")); 33 | poststring.concat(live.l.bI /100.f); 34 | poststring.concat(F("\nBatter-Watt value=")); 35 | poststring.concat(live.l.bP /100.f); 36 | poststring.concat(F("\nLoad-Voltage value=")); 37 | poststring.concat(live.l.lV /100.f); 38 | poststring.concat(F("\nLoad-Amp value=")); 39 | poststring.concat(live.l.lI /100.f); 40 | poststring.concat(F("\nLoad-Watt value=")); 41 | poststring.concat(live.l.lP /100.f); 42 | poststring.concat(F("\nDevice temp value=")); 43 | poststring.concat(deviceTemp/100.0f); 44 | poststring.concat(F("\nBattery-Current value=")); 45 | poststring.concat(batteryCurrent/100.f); 46 | poststring.concat(F("\nBattery-SOC value=")); 47 | poststring.concat(batterySOC/1.0f); 48 | poststring.concat(F("\nLoad-State value=")); 49 | poststring.concat(loadState==1?F("1\n"):F("0\n")); 50 | poststring.concat(F("Battery-MinVolte value=")); 51 | poststring.concat(stats.s.bVmin /100.f); 52 | poststring.concat(F("\nBattery-MaxVolt value=")); 53 | poststring.concat(stats.s.bVmax/100.f); 54 | poststring.concat(F("\nPanel-MinVolt value=")); 55 | poststring.concat(stats.s.pVmin/100.f); 56 | poststring.concat(F("\nPanel-MaxVolt value=")); 57 | poststring.concat(stats.s.pVmax/100.f); 58 | poststring.concat(F("\nConsumed-Day value=")); 59 | poststring.concat(stats.s.consEnerDay/100.f); 60 | poststring.concat(F("\nConsumed-All value=")); 61 | poststring.concat(stats.s.consEnerTotal/100.f); 62 | poststring.concat(F("\nGen-Day value=")); 63 | poststring.concat(stats.s.genEnerDay/100.f); 64 | poststring.concat(F("\nGen-All value=")); 65 | poststring.concat(stats.s.genEnerTotal/100.f); 66 | poststring.concat(F("\nBattery-Voltage-Status value=\"")); 67 | poststring.concat(batt_volt_status[status_batt.volt]); 68 | poststring.concat(F("\"\nBattery-Temp value=\"")); 69 | poststring.concat(batt_temp_status[status_batt.temp]); 70 | poststring.concat(F("\"\nCharger-Mode value=\"")); 71 | poststring.concat(charger_charging_status[charger_mode]); 72 | poststring.concat(F("\"\n")); 73 | 74 | #if defined(DEBUG) || defined(INFLUX_DEBUG) 75 | Serial.println(poststring); 76 | #endif 77 | 78 | http.begin(url); 79 | http.addHeader("Content-Type", "data-binary"); 80 | http.POST(poststring); 81 | String payload = http.getString(); 82 | 83 | #if defined(DEBUG) || defined(INFLUX_DEBUG) 84 | Serial.println(payload); 85 | #endif 86 | 87 | WiFiClient client; 88 | 89 | if (!client.connect(myConfig.influxdb_host, myConfig.influxdb_httpPort)) { 90 | Serial.println("connection failed"); 91 | 92 | } else { 93 | // This will send the request to the server 94 | client.print(String("GET ") + url + " HTTP/1.1\r\nHost: " + myConfig.influxdb_host + "\r\nConnection: close\r\n\r\n"); 95 | 96 | unsigned long timeout = millis() + 2500; 97 | // Read all the lines of the reply from server and print them to Serial 98 | while (client.connected()) 99 | { 100 | yield(); 101 | 102 | if (millis() > timeout) { 103 | Serial.println(">>> Client Timeout !"); 104 | client.stop(); 105 | return; 106 | } 107 | 108 | if (client.available()) 109 | { 110 | String line = client.readStringUntil('\n'); 111 | //Serial.println(line); 112 | } 113 | } 114 | client.stop(); 115 | } 116 | } -------------------------------------------------------------------------------- /src/mqtt.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | WiFiClient wifi_client; 4 | PubSubClient mqtt_client(wifi_client); 5 | 6 | long oldTime = 0; 7 | int switch_load = 0; 8 | bool loadState = false, setPublishHADiscovery = false; 9 | 10 | const char* will_Topic = "/LWT"; 11 | const char* will_Message = "offline"; 12 | int will_QoS = 0; 13 | bool will_Retain = true; 14 | 15 | const char* HADISCOVERY_BASE_TOPIC = "homeassistant/"; 16 | 17 | void mqtt_publish_s( const char* topic , const char* msg ){ 18 | #if defined(DEBUG) || defined(MQTT_DEBUG) 19 | Serial.print(topic); 20 | Serial.print(": "); 21 | Serial.println(msg); 22 | #endif 23 | mqtt_client.publish(topic, msg); 24 | } 25 | 26 | void mqtt_publish_f( char* topic , float value ){ 27 | char mqtt_msg[64]; 28 | snprintf (mqtt_msg, 64, "%7.3f", value); 29 | mqtt_client.publish(topic, mqtt_msg); 30 | #if defined(DEBUG) || defined(MQTT_DEBUG) 31 | Serial.print(topic); 32 | Serial.print(": "); 33 | Serial.println(mqtt_msg); 34 | #endif 35 | } 36 | 37 | void mqtt_publish_i( char* topic , int value ){ 38 | char mqtt_msg[64]; 39 | snprintf (mqtt_msg, 64, " %d", value); 40 | mqtt_client.publish(topic, mqtt_msg); 41 | #if defined(DEBUG) || defined(MQTT_DEBUG) 42 | Serial.print(topic); 43 | Serial.print(": "); 44 | Serial.println(mqtt_msg); 45 | #endif 46 | } 47 | 48 | 49 | // control load on / off here, setting sleep duration 50 | void mqtt_callback(char* topic, byte* payload, unsigned int length) { 51 | 52 | #if defined(DEBUG) || defined(MQTT_DEBUG) 53 | Serial.print(F("Message arrived [")); 54 | Serial.print(topic); 55 | Serial.print(F("] ")); 56 | for (unsigned int i = 0; i < length; i++) { 57 | Serial.print((char)payload[i]); 58 | } 59 | Serial.println(); 60 | #endif 61 | payload[length] = '\0'; 62 | char buf[MQTT_MAX_PACKET_SIZE]; 63 | 64 | // solar/load/control 65 | sprintf_P(buf, PSTR("%s/load/control"),myConfig.mqtt_topic); 66 | if ( strncmp( topic, buf, length ) == 0 ){ 67 | 68 | // Switch - but i can't seem to switch a coil directly here ?!? 69 | if ( strncmp_P( (char *) payload , PSTR("1"),1) == 0 || strcmp_P( (char *) payload , PSTR("on")) == 0 ) { 70 | loadState = true; 71 | switch_load = 1; 72 | } 73 | if ( strncmp_P( (char *) payload , PSTR("0"),1) == 0 || strcmp_P( (char *) payload , PSTR("off")) == 0 ) { 74 | loadState = false; 75 | switch_load = 1; 76 | } 77 | if ( strncmp_P( (char *) payload , PSTR("reboot"),1) == 0 ) { 78 | ESP.restart(); 79 | } 80 | #ifdef ENABLE_HA_FACTORY_RESET_FUNCTIONS 81 | if ( strncmp_P( (char *) payload , PSTR("factoryreset"),1) == 0 ) { 82 | ESP.restart(); 83 | } 84 | #endif 85 | } 86 | } 87 | 88 | void mqtt_loadpublish() { 89 | 90 | #if defined(DEBUG) || defined(MQTT_DEBUG) 91 | Serial.println(F("Publishing load: ")); 92 | #endif 93 | 94 | char buf[MQTT_MAX_PACKET_SIZE]; 95 | sprintf_P(buf, PSTR("%s/load/state"), myConfig.mqtt_topic); 96 | mqtt_publish_s( buf, (char*) (loadState == 1? "on": "off") ); // pimatic state topic does not work with integers or floats ?!? 97 | } 98 | 99 | #define SUBSET 0 100 | #define COMPLETE 1 101 | 102 | void mqtt_publish_old(uint16_t publishAll = COMPLETE) { 103 | // time 104 | // 105 | //sprintf(buf, "20%02d-%02d-%02d %02d:%02d:%02d" , rtc.r.y , rtc.r.M , rtc.r.d , rtc.r.h , rtc.r.m , rtc.r.s); 106 | //mqtt_publish_s( "solar/status/time", buf ); 107 | 108 | char buf[MQTT_MAX_PACKET_SIZE]; 109 | // panel 110 | sprintf(buf,"%s/panel/V",myConfig.mqtt_topic); 111 | mqtt_publish_f( buf, live.l.pV /100.f); 112 | sprintf(buf,"%s/panel/I",myConfig.mqtt_topic); 113 | mqtt_publish_f( buf, live.l.pI /100.f); 114 | sprintf(buf,"%s/panel/P",myConfig.mqtt_topic); 115 | mqtt_publish_f( buf, live.l.pP /100.f); 116 | 117 | sprintf(buf,"%s/battery/V",myConfig.mqtt_topic); 118 | mqtt_publish_f( buf, live.l.bV /100.f); 119 | sprintf(buf,"%s/battery/I",myConfig.mqtt_topic); 120 | mqtt_publish_f( buf, live.l.bI /100.f); 121 | sprintf(buf,"%s/battery/P",myConfig.mqtt_topic); 122 | mqtt_publish_f( buf, live.l.bP /100.f); 123 | sprintf(buf,"%s/battery/T",myConfig.mqtt_topic); 124 | mqtt_publish_f( buf, live.l.bT /100.f); 125 | 126 | sprintf(buf,"%s/load/V",myConfig.mqtt_topic); 127 | mqtt_publish_f( buf, live.l.lV /100.f); 128 | sprintf(buf,"%s/load/I",myConfig.mqtt_topic); 129 | mqtt_publish_f( buf, live.l.lI /100.f); 130 | sprintf(buf,"%s/load/P",myConfig.mqtt_topic); 131 | mqtt_publish_f( buf, live.l.lP /100.f); 132 | 133 | sprintf(buf,"%s/battery/SOC",myConfig.mqtt_topic); 134 | mqtt_publish_f( buf, batterySOC/1.0f); 135 | sprintf(buf,"%s/battery/netI",myConfig.mqtt_topic); 136 | mqtt_publish_f( buf, batteryCurrent/100.0f); 137 | sprintf(buf,"%s/load/state",myConfig.mqtt_topic); 138 | mqtt_publish_s( buf, (char*) (loadState == 1? "on": "off") ); // pimatic state topic does not work with integers or floats ?!? 139 | 140 | sprintf(buf,"%s/battery/minV",myConfig.mqtt_topic); 141 | mqtt_publish_f( buf, stats.s.bVmin /100.f); 142 | sprintf(buf,"%s/battery/maxV",myConfig.mqtt_topic); 143 | mqtt_publish_f( buf, stats.s.bVmax /100.f); 144 | 145 | sprintf(buf,"%s/panel/minV",myConfig.mqtt_topic); 146 | mqtt_publish_f( buf, stats.s.pVmin /100.f); 147 | sprintf(buf,"%s/panel/maxV",myConfig.mqtt_topic); 148 | mqtt_publish_f( buf, stats.s.pVmax /100.f); 149 | 150 | if (publishAll) { 151 | sprintf(buf,"%s/co2reduction/t",myConfig.mqtt_topic); 152 | mqtt_publish_f( buf, stats.s.c02Reduction/100.f); 153 | 154 | sprintf(buf,"%s/energy/consumed_day",myConfig.mqtt_topic); 155 | mqtt_publish_f( buf, stats.s.consEnerDay/100.f ); 156 | sprintf(buf,"%s/energy/consumed_day",myConfig.mqtt_topic); 157 | mqtt_publish_f( buf, stats.s.consEnerMon/100.f ); 158 | sprintf(buf,"%s/energy/consumed_year",myConfig.mqtt_topic); 159 | mqtt_publish_f( buf, stats.s.consEnerYear/100.f ); 160 | sprintf(buf,"%s/energy/consumed_all",myConfig.mqtt_topic); 161 | mqtt_publish_f( buf, stats.s.consEnerTotal/100.f ); 162 | 163 | sprintf(buf,"%s/energy/generated_day",myConfig.mqtt_topic); 164 | mqtt_publish_f( buf, stats.s.genEnerDay/100.f ); 165 | sprintf(buf,"%s/energy/generated_month",myConfig.mqtt_topic); 166 | mqtt_publish_f( buf, stats.s.genEnerMon/100.f ); 167 | sprintf(buf,"%s/energy/generated_year",myConfig.mqtt_topic); 168 | mqtt_publish_f( buf, stats.s.genEnerYear/100.f ); 169 | sprintf(buf,"%s/energy/generated_all",myConfig.mqtt_topic); 170 | mqtt_publish_f( buf, stats.s.genEnerTotal/100.f ); 171 | } 172 | 173 | sprintf(buf,"%s/status/batt_volt",myConfig.mqtt_topic); 174 | mqtt_publish_s( buf, batt_volt_status[status_batt.volt] ); 175 | sprintf(buf,"%s/status/batt_temp",myConfig.mqtt_topic); 176 | mqtt_publish_s( buf, batt_temp_status[status_batt.temp] ); 177 | 178 | //sprintf(buf,"%s/status/charger_input",mqtt_topic); 179 | //mqtt_publish_s( buf, charger_input_status[ charger_input ] ); 180 | sprintf(buf,"%s/status/charger_mode",myConfig.mqtt_topic); 181 | mqtt_publish_s(buf, charger_charging_status[charger_mode ] ); 182 | sprintf(buf,"%s/status/device_temp",myConfig.mqtt_topic); 183 | mqtt_publish_f(buf, deviceTemp/100.0f); 184 | } 185 | 186 | void mqtt_publish(uint16_t publishAll = COMPLETE) { 187 | // publish via mqtt using simple format 188 | #ifdef LEGACY_MQTT 189 | mqtt_publish_old(publishAll); 190 | #endif 191 | 192 | // time 193 | // 194 | //sprintf(buf, "20%02d-%02d-%02d %02d:%02d:%02d" , rtc.r.y , rtc.r.M , rtc.r.d , rtc.r.h , rtc.r.m , rtc.r.s); 195 | //mqtt_publish_s( "solar/status/time", buf ); 196 | 197 | StaticJsonDocument panelDoc; 198 | char buf[MQTT_MAX_PACKET_SIZE]; 199 | char mqtt_topic[64]; 200 | size_t jsonLength; 201 | 202 | // panel 203 | panelDoc[F("V")] = live.l.pV /100.f; 204 | panelDoc[F("I")] = live.l.pI /100.f; 205 | panelDoc[F("P")] = live.l.pP /100.f; 206 | panelDoc[F("minV")] = stats.s.pVmin /100.f; 207 | panelDoc[F("maxV")] = stats.s.pVmax /100.f; 208 | 209 | sprintf(mqtt_topic,"%s/panel",myConfig.mqtt_topic); 210 | jsonLength = serializeJson(panelDoc, buf); 211 | mqtt_client.publish(mqtt_topic, buf, jsonLength); 212 | panelDoc.clear(); 213 | 214 | 215 | //battery 216 | panelDoc[F("V")] = live.l.bV /100.f; 217 | panelDoc[F("I")] = live.l.bI /100.f; 218 | panelDoc[F("P")] = live.l.bP /100.f; 219 | panelDoc[F("minV")] = stats.s.bVmin /100.f; 220 | panelDoc[F("maxV")] = stats.s.bVmax /100.f; 221 | panelDoc[F("SOC")] = batterySOC/1.0f; 222 | panelDoc[F("netI")] = batteryCurrent/100.0f; 223 | panelDoc[F("T")] = live.l.bT /100.f; 224 | 225 | 226 | sprintf_P(mqtt_topic, PSTR("%s/battery"), myConfig.mqtt_topic); 227 | jsonLength = serializeJson(panelDoc, buf); 228 | mqtt_client.publish(mqtt_topic, buf, jsonLength); 229 | panelDoc.clear(); 230 | 231 | //load 232 | panelDoc[F("V")] = live.l.lV /100.f; 233 | panelDoc[F("I")] = live.l.lI /100.f; 234 | panelDoc[F("P")] = live.l.lP /100.f; 235 | panelDoc[F("state")] = (loadState == 1? F("on"): F("off")); 236 | 237 | sprintf_P(mqtt_topic, PSTR("%s/load"), myConfig.mqtt_topic); 238 | jsonLength = serializeJson(panelDoc, buf); 239 | mqtt_client.publish(mqtt_topic, buf, jsonLength); 240 | panelDoc.clear(); 241 | 242 | if (publishAll){ 243 | 244 | //co2reduction 245 | panelDoc[F("t")] = stats.s.c02Reduction/100.f; 246 | 247 | sprintf_P(mqtt_topic, PSTR("%s/co2reduction"), myConfig.mqtt_topic); 248 | jsonLength = serializeJson(panelDoc, buf); 249 | mqtt_client.publish(mqtt_topic, buf, jsonLength); 250 | panelDoc.clear(); 251 | 252 | //energy 253 | panelDoc[F("consumed_day")] = stats.s.consEnerDay/100.f; 254 | panelDoc[F("consumed_month")] = stats.s.consEnerMon/100.f; 255 | panelDoc[F("consumed_year")] = stats.s.consEnerYear/100.f; 256 | panelDoc[F("consumed_all")] = stats.s.consEnerTotal/100.f; 257 | 258 | panelDoc[F("generated_day")] = stats.s.genEnerDay/100.f; 259 | panelDoc[F("generated_month")] = stats.s.genEnerMon/100.f; 260 | panelDoc[F("generated_year")] = stats.s.genEnerYear/100.f; 261 | panelDoc[F("generated_all")] = stats.s.genEnerTotal/100.f; 262 | 263 | sprintf_P(mqtt_topic, PSTR("%s/energy"), myConfig.mqtt_topic); 264 | jsonLength = serializeJson(panelDoc, buf); 265 | mqtt_client.publish(mqtt_topic, buf, jsonLength); 266 | panelDoc.clear(); 267 | } 268 | 269 | // device temperature 270 | panelDoc[F("device_temp")] = deviceTemp/100.0f; 271 | sprintf_P(mqtt_topic, PSTR("%s/status"), myConfig.mqtt_topic); 272 | jsonLength = serializeJson(panelDoc, buf); 273 | mqtt_client.publish(mqtt_topic, buf, jsonLength); 274 | 275 | //status 276 | panelDoc[F("batt_volt")] = batt_volt_status[status_batt.volt]; 277 | panelDoc[F("batt_temp")] = batt_temp_status[status_batt.temp]; 278 | panelDoc[F("charger_mode")] = charger_charging_status[ charger_mode ]; 279 | // panelDoc["charger_input"] = charger_input_status[ charger_input ]; 280 | 281 | sprintf_P(mqtt_topic, PSTR("%s/status"), myConfig.mqtt_topic); 282 | jsonLength = serializeJson(panelDoc, buf); 283 | mqtt_client.publish(mqtt_topic, buf, jsonLength); 284 | } 285 | 286 | void sendHA_Discovery_Packet( const __FlashStringHelper* type, const __FlashStringHelper* name, const __FlashStringHelper* stat_r_leaf, const __FlashStringHelper* val_tpl, const __FlashStringHelper* uniq_id, const __FlashStringHelper* unit_of_meas = 0, const __FlashStringHelper* dev_cla = 0, const __FlashStringHelper* cmd_t = 0, const __FlashStringHelper* pl_on = 0, const __FlashStringHelper* pl_off = 0) 287 | { 288 | StaticJsonDocument<768> panelDoc; 289 | char buf[MQTT_MAX_PACKET_SIZE]; 290 | char mqtt_topic[128]; 291 | 292 | sprintf_P(mqtt_topic, PSTR("%s%S%s/%s%S/config"), HADISCOVERY_BASE_TOPIC, type, myConfig.mqtt_topic, baseMacChr, uniq_id); 293 | 294 | sprintf_P(buf, PSTR("%s%S"), myConfig.mqtt_topic, stat_r_leaf); 295 | panelDoc[F("stat_t")] = buf; 296 | panelDoc[F("name")] = name; 297 | 298 | sprintf_P(buf, PSTR("%s%S"), baseMacChr, uniq_id); 299 | panelDoc[F("uniq_id")] = buf; 300 | 301 | sprintf_P(buf, PSTR("{{value_json.%S}}"), val_tpl); 302 | panelDoc[F("val_tpl")] = buf; 303 | 304 | if (unit_of_meas != 0) 305 | panelDoc[F("unit_of_meas")] = unit_of_meas; 306 | 307 | if (dev_cla != 0) 308 | { 309 | panelDoc[F("dev_cla")] = dev_cla; 310 | #ifdef ENERGY_PANEL 311 | if (strcmp_P("energy", (PGM_P)dev_cla) == 0) 312 | { 313 | panelDoc[F("state_class")] = F("measurement"); 314 | panelDoc[F("last_reset_topic")] = F("epever/status/last_reset"); 315 | } 316 | #endif 317 | } 318 | 319 | sprintf_P(buf, PSTR("%s%S"), myConfig.mqtt_topic, will_Topic); 320 | panelDoc[F("avty_t")] = buf; 321 | panelDoc[F("pl_avail")] = F("online"); 322 | panelDoc[F("pl_not_avail")] = F("offline"); 323 | 324 | // there is a command topic, so add control attributes 325 | if (cmd_t != 0) 326 | { 327 | if (pl_on != 0 && pl_off != 0) 328 | { 329 | panelDoc[F("stat_on")] = pl_on; 330 | panelDoc[F("stat_off")] = pl_off; 331 | } 332 | 333 | sprintf_P(buf, PSTR("%s%S%S"), myConfig.mqtt_topic, stat_r_leaf, cmd_t); 334 | panelDoc[F("cmd_t")] = buf; 335 | } 336 | 337 | if (pl_on != 0) 338 | panelDoc[F("pl_on")] = pl_on; 339 | 340 | if (pl_off != 0) 341 | panelDoc[F("pl_off")] = pl_off; 342 | 343 | 344 | JsonObject device = panelDoc.createNestedObject(F("device")); 345 | device[F("name")] = DEVICE_DESCRIPTION; 346 | device[F("mdl")] = DEVICE_NAME; 347 | device[F("mf")] = DEVICE_MANUFACTURER; 348 | device[F("sw")] = SW_VERSION; 349 | JsonArray identifiers = device.createNestedArray(F("identifiers")); 350 | identifiers.add(baseMacChr); 351 | 352 | size_t jsonLength = serializeJson(panelDoc, buf, MQTT_MAX_PACKET_SIZE); 353 | mqtt_client.publish(mqtt_topic, (const uint8_t*)buf, jsonLength, true); 354 | } 355 | 356 | void publishHADiscovery() 357 | { 358 | if (myConfig.MQTT_Enable && myConfig.HADiscovery_Enable && mqtt_client.connected()) 359 | { 360 | //Publish Panel Sensors 361 | sendHA_Discovery_Packet(F("sensor/"), F("Panel V"), F("/panel"), F("V"), F("panelV"), F("V"), F("voltage")); 362 | sendHA_Discovery_Packet(F("sensor/"), F("Panel I"), F("/panel"), F("I"), F("panelI"), F("A"), F("current")); 363 | sendHA_Discovery_Packet(F("sensor/"), F("Panel P"), F("/panel"), F("P"), F("panelP"), F("W"), F("energy")); 364 | sendHA_Discovery_Packet(F("sensor/"), F("Panel minV"), F("/panel"), F("minV"), F("panelminV"), F("V"), F("voltage")); 365 | sendHA_Discovery_Packet(F("sensor/"), F("Panel maxV"), F("/panel"), F("maxV"), F("panelmaxV"), F("V"), F("voltage")); 366 | 367 | //Publish Battery Sensors 368 | sendHA_Discovery_Packet(F("sensor/"), F("Battery V"), F("/battery"), F("V"), F("batteryV"), F("V"), F("voltage")); 369 | sendHA_Discovery_Packet(F("sensor/"), F("Battery I"), F("/battery"), F("I"), F("batteryI"), F("A"), F("current")); 370 | sendHA_Discovery_Packet(F("sensor/"), F("Battery P"), F("/battery"), F("P"), F("batteryP"), F("W"), F("energy")); 371 | sendHA_Discovery_Packet(F("sensor/"), F("Battery minV"), F("/battery"), F("V"), F("batteryminV"), F("V"), F("voltage")); 372 | sendHA_Discovery_Packet(F("sensor/"), F("Battery maxV"), F("/battery"), F("V"), F("batterymaxV"), F("V"), F("voltage")); 373 | sendHA_Discovery_Packet(F("sensor/"), F("Battery SOC"), F("/battery"), F("SOC"), F("batterySOC"), F("%"), F("battery")); 374 | sendHA_Discovery_Packet(F("sensor/"), F("Battery netI"), F("/battery"), F("netI"), F("batterynetI"), F("A"), F("current")); 375 | 376 | //Publish Load Sensors 377 | sendHA_Discovery_Packet(F("sensor/"), F("Load V"), F("/load"), F("V"), F("loadV"), F("V"), F("voltage")); 378 | sendHA_Discovery_Packet(F("sensor/"), F("Load I"), F("/load"), F("I"), F("loadI"), F("A"), F("current")); 379 | sendHA_Discovery_Packet(F("sensor/"), F("Load P"), F("/load"), F("P"), F("loadP"), F("W"), F("energy")); 380 | sendHA_Discovery_Packet(F("binary_sensor/"), F("Load State"), F("/load"), F("state"), F("loadState"), 0, 0, 0, F("on"), F("off")); 381 | 382 | //Publish Load Switch 383 | sendHA_Discovery_Packet(F("switch/"), F("Load Switch"), F("/load"), F("state"), F("loadSwitch"), 0 ,0 ,F("/control"), F("on"), F("off")); 384 | 385 | //Publish Energy Sensors 386 | sendHA_Discovery_Packet(F("sensor/"), F("Consumed Today"), F("/energy"), F("consumed_day"), F("consDay"), F("kWh"), F("energy")); 387 | sendHA_Discovery_Packet(F("sensor/"), F("Consumed Month"), F("/energy"), F("consumed_month"), F("consMth"), F("kWh"), F("energy")); 388 | sendHA_Discovery_Packet(F("sensor/"), F("Consumed Year"), F("/energy"), F("consumed_year"), F("consYr"), F("kWh"), F("energy")); 389 | sendHA_Discovery_Packet(F("sensor/"), F("Consumed All"), F("/energy"), F("consumed_all"), F("consAll"), F("kWh"), F("energy")); 390 | sendHA_Discovery_Packet(F("sensor/"), F("Generated Today"), F("/energy"), F("generated_day"), F("genDay"), F("kWh"), F("energy")); 391 | sendHA_Discovery_Packet(F("sensor/"), F("Generated Month"), F("/energy"), F("generated_month"), F("genMth"), F("kWh"), F("energy")); 392 | sendHA_Discovery_Packet(F("sensor/"), F("Generated Year"), F("/energy"), F("generated_year"), F("genYr"), F("kWh"), F("energy")); 393 | sendHA_Discovery_Packet(F("sensor/"), F("Generated All"), F("/energy"), F("generated_all"), F("genAll"), F("kWh"), F("energy")); 394 | 395 | //Publish CO2 Sensors 396 | sendHA_Discovery_Packet(F("sensor/"), F("CO2 Reduction"), F("/co2reduction"), F("t"), F("co2reduction"), F("tons"), F("power")); 397 | 398 | //Publish Status Sensors 399 | sendHA_Discovery_Packet(F("sensor/"), F("Battery Temperature"), F("/status"), F("batt_temp"), F("battTemp"), F("C"), F("temperature")); 400 | sendHA_Discovery_Packet(F("sensor/"), F("Battery Voltage"), F("/status"), F("batt_volt"), F("battVolt")); 401 | sendHA_Discovery_Packet(F("sensor/"), F("Charger Mode"), F("/status"), F("charger_mode"), F("chargeMode")); 402 | //Publish Device temperature Sensors 403 | sendHA_Discovery_Packet(F("sensor/"), F("Device Temperature"), F("/status"), F("device_temp"), F("deviceTemp"), F("C"), F("temperature")); 404 | 405 | //Publish Restart Switch 406 | sendHA_Discovery_Packet(F("switch/"), F("Restart"), F("/load"), F("state"), F("restart"), 0 ,0 ,F("/control"), F("restart"), F("")); 407 | 408 | #ifdef ENABLE_HA_FACTORY_RESET_FUNCTIONS 409 | //Publish Factory Reset Switch 410 | sendHA_Discovery_Packet(F("switch/"), F("Factory Reset"), F("/load"), F("state"), F("factoryreset"), 0 ,0 ,F("/control"), F("FactoryReset"), F("")); 411 | #endif 412 | 413 | //set to false as they don't need to be republished 414 | setPublishHADiscovery = false; 415 | } 416 | } 417 | 418 | void mqtt_reconnect() { 419 | 420 | // Loop until we're reconnected 421 | while (!mqtt_client.connected()) { 422 | 423 | #if defined(DEBUG) || defined(MQTT_DEBUG) 424 | Serial.print(F("Attempting MQTT connection...")); 425 | #endif 426 | 427 | char buf[MQTT_MAX_PACKET_SIZE]; 428 | strcpy(buf, myConfig.mqtt_topic); 429 | strcat(buf, will_Topic); 430 | 431 | // Attempt to connect 432 | mqtt_client.setServer(myConfig.mqtt_server, myConfig.mqtt_port); 433 | if (mqtt_client.connect( baseMacChr, 434 | myConfig.mqtt_username, 435 | myConfig.mqtt_password, 436 | buf, 437 | will_QoS, 438 | will_Retain, 439 | will_Message)) { 440 | 441 | // Once connected, publish an announcement... 442 | mqtt_client.publish(buf, "online", true); 443 | 444 | #if defined(DEBUG) || defined(MQTT_DEBUG) 445 | Serial.println(F("connected")); 446 | #endif 447 | mqtt_client.setCallback(mqtt_callback); 448 | 449 | char buf[MQTT_MAX_PACKET_SIZE]; 450 | // ... and resubscribe 451 | sprintf(buf,"%s/load/control",myConfig.mqtt_topic); 452 | mqtt_client.subscribe(buf); 453 | sprintf(buf,"%s/setting/sleep",myConfig.mqtt_topic); 454 | mqtt_client.subscribe(buf); 455 | 456 | } else { 457 | #if defined(DEBUG) || defined(MQTT_DEBUG) 458 | Serial.print(F("failed, rc=")); 459 | Serial.print(mqtt_client.state()); 460 | Serial.println(F(" try again in 5 seconds")); 461 | #endif 462 | // Wait 5 seconds before retrying 463 | delay(5000); 464 | } 465 | } 466 | } -------------------------------------------------------------------------------- /src/settings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "settings.h" 4 | eeprom_settings myConfig; 5 | char baseMacChr[13] = {0}; 6 | bool isWrittingEEPROM = false; 7 | 8 | uint32_t calculateCRC32(const uint8_t *data, size_t length) 9 | { 10 | //This calculates a CRC32 the same as used in MPEG2 streams 11 | uint32_t crc = 0xffffffff; 12 | while (length--) { 13 | uint8_t c = *data++; 14 | for (uint32_t i = 0x80; i > 0; i >>= 1) { 15 | bool bit = crc & 0x80000000; 16 | if (c & i) { 17 | bit = !bit; 18 | } 19 | crc <<= 1; 20 | if (bit) { 21 | crc ^= 0x04c11db7; 22 | } 23 | } 24 | } 25 | return crc; 26 | } 27 | 28 | void WriteConfigToEEPROM() { 29 | isWrittingEEPROM = true; 30 | 31 | uint32_t checksum = calculateCRC32((uint8_t*)&myConfig, sizeof(eeprom_settings)); 32 | uint32_t existingChecksum; 33 | 34 | EEPROM.begin(EEPROM_storageSize); 35 | EEPROM.get(EEPROM_CHECKSUM_ADDRESS, existingChecksum); 36 | 37 | if (checksum == existingChecksum) 38 | { 39 | //No changes have been made - return 40 | EEPROM.end(); 41 | #ifdef DEBUG 42 | Serial.println(F("No Changes Made - Not Saving EEPROM")); 43 | #endif 44 | 45 | isWrittingEEPROM = false; 46 | return; 47 | } 48 | 49 | EEPROM.put(EEPROM_CONFIG_ADDRESS, myConfig); 50 | EEPROM.put(EEPROM_CHECKSUM_ADDRESS, checksum); 51 | EEPROM.end(); 52 | 53 | #ifdef DEBUG 54 | Serial.println(F("Saving EEPROM")); 55 | #endif 56 | 57 | isWrittingEEPROM = false; 58 | } 59 | 60 | bool LoadConfigFromEEPROM() { 61 | eeprom_settings restoredConfig; 62 | uint32_t existingChecksum; 63 | 64 | EEPROM.begin(EEPROM_storageSize); 65 | EEPROM.get(EEPROM_CONFIG_ADDRESS, restoredConfig); 66 | EEPROM.get(EEPROM_CHECKSUM_ADDRESS, existingChecksum); 67 | EEPROM.end(); 68 | 69 | // Calculate the checksum of an entire buffer at once. 70 | uint32_t checksum = calculateCRC32((uint8_t*)&restoredConfig, sizeof(eeprom_settings)); 71 | 72 | #ifdef DEBUG 73 | Serial.println(checksum, HEX); 74 | Serial.println(existingChecksum, HEX); 75 | #endif 76 | 77 | if (checksum == existingChecksum) { 78 | //Clone the config into our global variable and return all OK 79 | memcpy(&myConfig, &restoredConfig, sizeof(eeprom_settings)); 80 | return true; 81 | } 82 | 83 | #ifdef DEBUG 84 | //Config is not configured or gone bad, return FALSE 85 | Serial.println(F("Config is not configured or gone bad")); 86 | #endif 87 | 88 | return false; 89 | } 90 | 91 | void FactoryResetSettings() { 92 | strcpy(myConfig.influxdb_host , DEFAULT_INFLUXDB_HOST ); 93 | strcpy(myConfig.influxdb_database, DEFAULT_INFLUXDB_DATABASE ); 94 | strcpy(myConfig.influxdb_user , DEFAULT_INFLUXDB_USER ); 95 | strcpy(myConfig.influxdb_password, DEFAULT_INFLUXDB_PASSWORD ); 96 | 97 | myConfig.influxdb_enabled = false; 98 | myConfig.influxdb_httpPort = DEFAULT_INFLUXDB_PORT; 99 | 100 | strcpy(myConfig.mqtt_server , DEFAULT_MQTT_SERVER ); 101 | strcpy(myConfig.mqtt_username, DEFAULT_MQTT_USERNAME); 102 | strcpy(myConfig.mqtt_password, DEFAULT_MQTT_PASSWORD); 103 | strcpy(myConfig.mqtt_topic , DEFAULT_MQTT_TOPIC); 104 | 105 | myConfig.MQTT_Enable =false; 106 | myConfig.mqtt_port = DEFAULT_MQTT_PORT; 107 | 108 | myConfig.HADiscovery_Enable = false; 109 | 110 | myConfig.Device_ID = DEFAULT_DEVICE_ID; 111 | myConfig.Device_BAUD = DEFAULT_SERIAL_BAUD; 112 | 113 | WriteConfigToEEPROM(); 114 | } 115 | -------------------------------------------------------------------------------- /src/settings.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | #ifndef config_settings_H_ 4 | #define config_settings_H_ 5 | 6 | #define STR_HELPER(x) #x 7 | #define STR(x) STR_HELPER(x) 8 | 9 | #ifndef DEVICE_NAME 10 | #define DEVICE_NAME "RS485-WiFi" 11 | #endif 12 | #ifndef SW_VERSION 13 | #define SW_VERSION 0.61 14 | #endif 15 | #ifndef DEVICE_DESCRIPTION 16 | #define DEVICE_DESCRIPTION "EpEver Solar Monitor" 17 | #endif 18 | 19 | #ifndef DEVICE_MANUFACTURER 20 | #define DEVICE_MANUFACTURER "EpEver" 21 | #endif 22 | 23 | 24 | #define DEVICE_FULL_NAME DEVICE_NAME " v" STR(SW_VERSION) 25 | 26 | //Where in EEPROM do we store the configuration 27 | #define EEPROM_storageSize 2048 28 | #define EEPROM_WIFI_CHECKSUM_ADDRESS 0 29 | #define EEPROM_WIFI_CONFIG_ADDRESS EEPROM_WIFI_CHECKSUM_ADDRESS+sizeof(uint32_t) 30 | 31 | #define EEPROM_CHECKSUM_ADDRESS 512 32 | #define EEPROM_CONFIG_ADDRESS EEPROM_CHECKSUM_ADDRESS+sizeof(uint32_t) 33 | 34 | #ifndef DEFAULT_MQTT_SERVER 35 | #define DEFAULT_MQTT_SERVER "192.168.0.254" 36 | #endif 37 | #ifndef DEFAULT_MQTT_USERNAME 38 | #define DEFAULT_MQTT_USERNAME "username" 39 | #endif 40 | #ifndef DEFAULT_MQTT_PASSWORD 41 | #define DEFAULT_MQTT_PASSWORD "password" 42 | #endif 43 | #ifndef DEFAULT_MQTT_TOPIC 44 | #define DEFAULT_MQTT_TOPIC "solar" 45 | #endif 46 | #ifndef DEFAULT_MQTT_PORT 47 | #define DEFAULT_MQTT_PORT 1883 48 | #endif 49 | 50 | #ifndef DEFAULT_INFLUXDB_HOST 51 | #define DEFAULT_INFLUXDB_HOST "192.168.0.254" 52 | #endif 53 | #ifndef DEFAULT_INFLUXDB_DATABASE 54 | #define DEFAULT_INFLUXDB_DATABASE "powerwall" 55 | #endif 56 | #ifndef DEFAULT_INFLUXDB_USER 57 | #define DEFAULT_INFLUXDB_USER "username" 58 | #endif 59 | #ifndef DEFAULT_INFLUXDB_PASSWORD 60 | #define DEFAULT_INFLUXDB_PASSWORD "PASSWORD" 61 | #endif 62 | #ifndef DEFAULT_INFLUXDB_PORT 63 | #define DEFAULT_INFLUXDB_PORT 8086 64 | #endif 65 | 66 | #ifndef DEFAULT_DEVICE_ID 67 | #define DEFAULT_DEVICE_ID 1 68 | #endif 69 | #ifndef DEFAULT_SERIAL_BAUD 70 | #define DEFAULT_SERIAL_BAUD 115200 71 | #endif 72 | 73 | //We have allowed space for 2048-512 bytes of EEPROM for settings (1536 bytes) 74 | struct eeprom_settings { 75 | bool MQTT_Enable; 76 | int mqtt_port; 77 | char mqtt_server[64 + 1]; 78 | char mqtt_username[64 + 1]; 79 | char mqtt_password[64 + 1]; 80 | char mqtt_topic[64 + 1]; 81 | 82 | bool influxdb_enabled; 83 | char influxdb_host[64 +1 ]; 84 | int influxdb_httpPort; 85 | char influxdb_database[32 + 1]; 86 | char influxdb_user[32 + 1]; 87 | char influxdb_password[32 + 1]; 88 | 89 | bool HADiscovery_Enable; 90 | 91 | int Device_ID; 92 | int Device_BAUD; 93 | }; 94 | 95 | extern char baseMacChr[13]; 96 | extern eeprom_settings myConfig; 97 | extern bool isWrittingEEPROM; 98 | 99 | void WriteConfigToEEPROM(); 100 | bool LoadConfigFromEEPROM(); 101 | void WriteWIFIConfigToEEPROM(); 102 | bool LoadWIFIConfigFromEEPROM(); 103 | void FactoryResetSettings(); 104 | 105 | #endif 106 | --------------------------------------------------------------------------------