├── .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 |
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 |
--------------------------------------------------------------------------------