├── .github ├── FUNDING copy.yml ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── config.yml │ ├── feature_request.yaml │ ├── help.yaml │ └── working_device.yaml └── workflows │ ├── ISSUE_WORKFLOW.yaml │ └── update_central_repo.yml ├── .gitignore ├── .vscode └── extensions.json ├── LICENSE ├── Protocol ├── BlueSolar-HEX-protocol.pdf ├── VE.Direct-Protocol-3.33.pdf ├── vicASCII.log ├── vicHEX.log └── viread.log ├── README.md ├── include └── readme.md ├── notes ├── platformio.ini ├── src ├── RTCMemory.h ├── Settings.h ├── VeDirectDataList.h ├── VeDirectDeviceCodes.h ├── VeDirectDeviceList.h ├── VeDirectFrameHandler.cpp ├── VeDirectFrameHandler.h ├── htmlProzessor.h ├── main.cpp ├── main.h ├── status-LED.h └── webpages │ ├── HTML_CONFIRM_RESET.html │ ├── HTML_FOOT.html │ ├── HTML_HEAD.html │ ├── HTML_MAIN.html │ ├── HTML_REBOOT.html │ ├── HTML_SETTINGS.html │ └── HTML_SETTINGS_EDIT.html └── tools ├── mini_html.py ├── post_compile.py └── pre_compile.py /.github/FUNDING copy.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://donate.softwarecrash.de'] 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://donate.softwarecrash.de'] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug Report 2 | description: If something isn't working as expected. 3 | title: "[Bug]: " 4 | labels: ["bug", "triage"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue already exists for the bug you encountered. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: dropdown 14 | attributes: 15 | label: Used Hardware? 16 | description: Please let us know what hardware you are using. 17 | multiple: true 18 | options: 19 | - Wemos D1 Mini 20 | - ESP-01 (512KB Flash) 21 | - ESP-01S (1MB Flash) 22 | - Other (please write your hardware in 'What happened?') 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: what-happened 27 | attributes: 28 | label: What happened? 29 | description: Also tell us, what did you expect to happen? 30 | placeholder: Tell us what you see! 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: screenhots 35 | attributes: 36 | label: Screenshots / Fotos 37 | description: Add screenshots and fotos of your wiring to help explain your problem. 38 | placeholder: Drag&Drop screenshots and Fotos here 39 | validations: 40 | required: true 41 | - type: textarea 42 | attributes: 43 | label: Steps To Reproduce 44 | description: If applicable, steps to reproduce the behavior. 45 | placeholder: | 46 | 1. Go to... 47 | 2. Click on... 48 | 3. See error... 49 | validations: 50 | required: false 51 | - type: dropdown 52 | id: version 53 | attributes: 54 | label: Version 55 | description: What version of our software are you running? 56 | multiple: true 57 | options: 58 | - 1.x.x and above 59 | - 0.2.4 60 | - 0.2.3 61 | - 0.2.2 62 | - 0.2.1 63 | - 0.2.0 64 | - 0.1.x (Depreciated) 65 | validations: 66 | required: true 67 | - type: textarea 68 | id: output 69 | attributes: 70 | label: Relevant livejson output 71 | description: Please copy and paste your livejson-output (http://IP_of_your_ESP/livejson). This will be automatically formatted into code, so no need for backticks. 72 | render: json 73 | validations: 74 | required: true 75 | 76 | - type: dropdown 77 | id: browsers 78 | attributes: 79 | label: What browsers are you seeing the problem on? 80 | multiple: true 81 | options: 82 | - Firefox 83 | - Chrome 84 | - Safari 85 | - Microsoft Edge 86 | - no Issue with the Browser or WebUI 87 | validations: 88 | required: true 89 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature Request 2 | description: I have a suggestion (and may want to implement it 🙂)! 3 | title: "[FEATURE] " 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Is your feature request related to a problem? Please describe 8 | description: A clear and concise description of what the problem is. 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: Describe the solution you'd like 14 | description: A clear and concise description of what you want to happen. 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Describe alternatives you've considered 20 | description: A clear and concise description of any alternative solutions or features you've considered. 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: Additional context 26 | description: Add any other context or screenshots about the feature request here. 27 | validations: 28 | required: false 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help.yaml: -------------------------------------------------------------------------------- 1 | name: 😭 Help! 2 | description: Ask for help 3 | title: "[HELP] <title>" 4 | labels: help wanted 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: A clear and concise description of what the problem is. 9 | description: A clear and concise description of what the problem is. 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: Additional context 15 | description: Add any other context or screenshots about the feature request here. 16 | validations: 17 | required: false 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/working_device.yaml: -------------------------------------------------------------------------------- 1 | name: Working device 2 | description: Report a new working device 3 | title: New working device 4 | labels: ["working device"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this report! 10 | - type: textarea 11 | id: device-type 12 | attributes: 13 | label: What device are you using? 14 | description: Also tell us, wich firmware version you are using? 15 | placeholder: Tell us what you are using! 16 | value: "A new working device!" 17 | validations: 18 | required: true 19 | -------------------------------------------------------------------------------- /.github/workflows/ISSUE_WORKFLOW.yaml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | days-before-issue-stale: 30 16 | days-before-issue-close: 14 17 | stale-issue-label: "stale" 18 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 19 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 20 | days-before-pr-stale: -1 21 | days-before-pr-close: -1 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/update_central_repo.yml: -------------------------------------------------------------------------------- 1 | name: Update Central Firmware Repository 2 | 3 | on: 4 | release: 5 | types: [published, edited] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update-central-repo: 10 | if: ${{ github.event.release.prerelease == false || github.event_name == 'workflow_dispatch' }} 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up Git 17 | run: | 18 | git config --global user.name 'GitHub Actions' 19 | git config --global user.email 'actions@github.com' 20 | 21 | - name: Clone Central Repository 22 | run: | 23 | git clone https://all-solutions:${{ secrets.CENTRAL_REPO_TOKEN }}@github.com/all-solutions/Flash2MQTT.git Flash2MQTT 24 | 25 | - name: Download Firmware Assets 26 | id: fetch-release 27 | uses: actions/github-script@v6 28 | with: 29 | github-token: ${{ secrets.GITHUB_TOKEN }} 30 | script: | 31 | const fs = require('fs'); 32 | const owner = context.repo.owner; 33 | const repo = context.repo.repo; 34 | 35 | let release; 36 | let releaseTag; 37 | 38 | if (context.eventName === 'release') { 39 | releaseTag = context.payload.release.tag_name; 40 | console.log(`Using release tag from event: ${releaseTag}`); 41 | release = await github.rest.repos.getReleaseByTag({ owner, repo, tag: releaseTag }); 42 | } else { 43 | release = await github.rest.repos.getLatestRelease({ owner, repo }); 44 | releaseTag = release.data.tag_name; 45 | console.log(`Using latest release tag: ${releaseTag}`); 46 | } 47 | 48 | const assets = release.data.assets.filter(asset => asset.name.endsWith('.bin')); 49 | 50 | if (assets.length === 0) { 51 | core.setFailed('No .bin assets found in the release.'); 52 | return; 53 | } 54 | 55 | for (const asset of assets) { 56 | const download = await github.rest.repos.getReleaseAsset({ 57 | owner, 58 | repo, 59 | asset_id: asset.id, 60 | headers: { 61 | Accept: 'application/octet-stream', 62 | }, 63 | }); 64 | fs.writeFileSync(asset.name, Buffer.from(download.data)); 65 | console.log(`Downloaded ${asset.name}`); 66 | } 67 | 68 | core.setOutput('tag', releaseTag); 69 | 70 | - name: List Downloaded Files 71 | run: ls -la 72 | 73 | - name: Copy Firmware Files 74 | run: | 75 | mkdir -p Flash2MQTT/firmware/${{ github.event.repository.name }} 76 | rm -f Flash2MQTT/firmware/${{ github.event.repository.name }}/*.bin 77 | cp *.bin Flash2MQTT/firmware/${{ github.event.repository.name }}/ 78 | echo "Updated at $(date -u)" > Flash2MQTT/firmware/${{ github.event.repository.name }}/last_update.txt 79 | 80 | - name: Install jq 81 | run: sudo apt-get update && sudo apt-get install -y jq 82 | 83 | - name: Update variants.json and firmware_list.json 84 | env: 85 | FIRMWARE_NAME: ${{ github.event.repository.name }} 86 | RELEASE_VERSION: ${{ steps.fetch-release.outputs.tag }} 87 | run: | 88 | cd Flash2MQTT/firmware/${FIRMWARE_NAME} 89 | ls *.bin > bin_files.txt 90 | total=0 91 | count=0 92 | version="${RELEASE_VERSION#v}" 93 | version="${version#V}" 94 | echo "Firmware Name: $FIRMWARE_NAME" 95 | echo "Release Version: $version" 96 | 97 | echo "Determining total number of desired variants..." 98 | while read file; do 99 | if [[ "$file" == *"_${version}.bin" ]]; then 100 | variant_part=$(echo "$file" | sed -E 's/^'"$FIRMWARE_NAME"'_//; s/_'"${version}"'\.bin$//') 101 | variant_name="${variant_part}" 102 | if [[ "$variant_name" == "d1_mini" || "$variant_name" == "esp01_1m" ]]; then 103 | total=$((total + 1)) 104 | fi 105 | fi 106 | done < bin_files.txt 107 | 108 | echo '[' > variants.json 109 | while read file; do 110 | if [[ "$file" == *"_${version}.bin" ]]; then 111 | variant_part=$(echo "$file" | sed -E 's/^'"$FIRMWARE_NAME"'_//; s/_'"${version}"'\.bin$//') 112 | variant_name="${variant_part}" 113 | case "$variant_name" in 114 | "d1_mini") display_name="D1 Mini" ;; 115 | "esp01_1m") display_name="ESP-01" ;; 116 | *) continue ;; 117 | esac 118 | count=$((count + 1)) 119 | echo ' {' >> variants.json 120 | echo ' "displayName": "'"$display_name"'",' >> variants.json 121 | echo ' "file": "https://all-solutions.github.io/Flash2MQTT/firmware/'"$FIRMWARE_NAME"'/'"$file"'"' >> variants.json 122 | if [ $count -lt $total ]; then 123 | echo ' },' >> variants.json 124 | else 125 | echo ' }' >> variants.json 126 | fi 127 | fi 128 | done < bin_files.txt 129 | echo ']' >> variants.json 130 | rm bin_files.txt 131 | 132 | cd .. 133 | if [ ! -f firmware_list.json ]; then 134 | echo '[]' > firmware_list.json 135 | fi 136 | tmpfile=$(mktemp) 137 | jq --arg name "$FIRMWARE_NAME" --arg version "$version" \ 138 | 'if any(.[]; .name == $name) then map(if .name == $name then .version = $version else . end) else . + [{"name": $name, "version": $version}] end' \ 139 | firmware_list.json > "$tmpfile" && mv "$tmpfile" firmware_list.json 140 | 141 | - name: Commit and Push Changes 142 | env: 143 | RELEASE_VERSION: ${{ steps.fetch-release.outputs.tag }} 144 | run: | 145 | cd Flash2MQTT 146 | git add firmware/${{ github.event.repository.name }} 147 | git add firmware/firmware_list.json 148 | git commit -m "Update firmware for ${{ github.event.repository.name }} to version $RELEASE_VERSION" || \ 149 | git commit --allow-empty -m "Force update for ${{ github.event.repository.name }} to version $RELEASE_VERSION (no file changes)" 150 | git pull --rebase origin main 151 | git push https://all-solutions:${{ secrets.CENTRAL_REPO_TOKEN }}@github.com/all-solutions/Flash2MQTT.git HEAD:main 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | .vscode 7 | .firmware 8 | src/html.h -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-NonCommercial-NoDerivatives 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 58 | International Public License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-NonCommercial-NoDerivatives 4.0 International Public 63 | License ("Public License"). To the extent this Public License may be 64 | interpreted as a contract, You are granted the Licensed Rights in 65 | consideration of Your acceptance of these terms and conditions, and the 66 | Licensor grants You such rights in consideration of benefits the 67 | Licensor receives from making the Licensed Material available under 68 | these terms and conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Copyright and Similar Rights means copyright and/or similar rights 84 | closely related to copyright including, without limitation, 85 | performance, broadcast, sound recording, and Sui Generis Database 86 | Rights, without regard to how the rights are labeled or 87 | categorized. For purposes of this Public License, the rights 88 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 89 | Rights. 90 | 91 | c. Effective Technological Measures means those measures that, in the 92 | absence of proper authority, may not be circumvented under laws 93 | fulfilling obligations under Article 11 of the WIPO Copyright 94 | Treaty adopted on December 20, 1996, and/or similar international 95 | agreements. 96 | 97 | d. Exceptions and Limitations means fair use, fair dealing, and/or 98 | any other exception or limitation to Copyright and Similar Rights 99 | that applies to Your use of the Licensed Material. 100 | 101 | e. Licensed Material means the artistic or literary work, database, 102 | or other material to which the Licensor applied this Public 103 | License. 104 | 105 | f. Licensed Rights means the rights granted to You subject to the 106 | terms and conditions of this Public License, which are limited to 107 | all Copyright and Similar Rights that apply to Your use of the 108 | Licensed Material and that the Licensor has authority to license. 109 | 110 | g. Licensor means the individual(s) or entity(ies) granting rights 111 | under this Public License. 112 | 113 | h. NonCommercial means not primarily intended for or directed towards 114 | commercial advantage or monetary compensation. For purposes of 115 | this Public License, the exchange of the Licensed Material for 116 | other material subject to Copyright and Similar Rights by digital 117 | file-sharing or similar means is NonCommercial provided there is 118 | no payment of monetary compensation in connection with the 119 | exchange. 120 | 121 | i. Share means to provide material to the public by any means or 122 | process that requires permission under the Licensed Rights, such 123 | as reproduction, public display, public performance, distribution, 124 | dissemination, communication, or importation, and to make material 125 | available to the public including in ways that members of the 126 | public may access the material from a place and at a time 127 | individually chosen by them. 128 | 129 | j. Sui Generis Database Rights means rights other than copyright 130 | resulting from Directive 96/9/EC of the European Parliament and of 131 | the Council of 11 March 1996 on the legal protection of databases, 132 | as amended and/or succeeded, as well as other essentially 133 | equivalent rights anywhere in the world. 134 | 135 | k. You means the individual or entity exercising the Licensed Rights 136 | under this Public License. Your has a corresponding meaning. 137 | 138 | 139 | Section 2 -- Scope. 140 | 141 | a. License grant. 142 | 143 | 1. Subject to the terms and conditions of this Public License, 144 | the Licensor hereby grants You a worldwide, royalty-free, 145 | non-sublicensable, non-exclusive, irrevocable license to 146 | exercise the Licensed Rights in the Licensed Material to: 147 | 148 | a. reproduce and Share the Licensed Material, in whole or 149 | in part, for NonCommercial purposes only; and 150 | 151 | b. produce and reproduce, but not Share, Adapted Material 152 | for NonCommercial purposes only. 153 | 154 | 2. Exceptions and Limitations. For the avoidance of doubt, where 155 | Exceptions and Limitations apply to Your use, this Public 156 | License does not apply, and You do not need to comply with 157 | its terms and conditions. 158 | 159 | 3. Term. The term of this Public License is specified in Section 160 | 6(a). 161 | 162 | 4. Media and formats; technical modifications allowed. The 163 | Licensor authorizes You to exercise the Licensed Rights in 164 | all media and formats whether now known or hereafter created, 165 | and to make technical modifications necessary to do so. The 166 | Licensor waives and/or agrees not to assert any right or 167 | authority to forbid You from making technical modifications 168 | necessary to exercise the Licensed Rights, including 169 | technical modifications necessary to circumvent Effective 170 | Technological Measures. For purposes of this Public License, 171 | simply making modifications authorized by this Section 2(a) 172 | (4) never produces Adapted Material. 173 | 174 | 5. Downstream recipients. 175 | 176 | a. Offer from the Licensor -- Licensed Material. Every 177 | recipient of the Licensed Material automatically 178 | receives an offer from the Licensor to exercise the 179 | Licensed Rights under the terms and conditions of this 180 | Public License. 181 | 182 | b. No downstream restrictions. You may not offer or impose 183 | any additional or different terms or conditions on, or 184 | apply any Effective Technological Measures to, the 185 | Licensed Material if doing so restricts exercise of the 186 | Licensed Rights by any recipient of the Licensed 187 | Material. 188 | 189 | 6. No endorsement. Nothing in this Public License constitutes or 190 | may be construed as permission to assert or imply that You 191 | are, or that Your use of the Licensed Material is, connected 192 | with, or sponsored, endorsed, or granted official status by, 193 | the Licensor or others designated to receive attribution as 194 | provided in Section 3(a)(1)(A)(i). 195 | 196 | b. Other rights. 197 | 198 | 1. Moral rights, such as the right of integrity, are not 199 | licensed under this Public License, nor are publicity, 200 | privacy, and/or other similar personality rights; however, to 201 | the extent possible, the Licensor waives and/or agrees not to 202 | assert any such rights held by the Licensor to the limited 203 | extent necessary to allow You to exercise the Licensed 204 | Rights, but not otherwise. 205 | 206 | 2. Patent and trademark rights are not licensed under this 207 | Public License. 208 | 209 | 3. To the extent possible, the Licensor waives any right to 210 | collect royalties from You for the exercise of the Licensed 211 | Rights, whether directly or through a collecting society 212 | under any voluntary or waivable statutory or compulsory 213 | licensing scheme. In all other cases the Licensor expressly 214 | reserves any right to collect such royalties, including when 215 | the Licensed Material is used other than for NonCommercial 216 | purposes. 217 | 218 | 219 | Section 3 -- License Conditions. 220 | 221 | Your exercise of the Licensed Rights is expressly made subject to the 222 | following conditions. 223 | 224 | a. Attribution. 225 | 226 | 1. If You Share the Licensed Material, You must: 227 | 228 | a. retain the following if it is supplied by the Licensor 229 | with the Licensed Material: 230 | 231 | i. identification of the creator(s) of the Licensed 232 | Material and any others designated to receive 233 | attribution, in any reasonable manner requested by 234 | the Licensor (including by pseudonym if 235 | designated); 236 | 237 | ii. a copyright notice; 238 | 239 | iii. a notice that refers to this Public License; 240 | 241 | iv. a notice that refers to the disclaimer of 242 | warranties; 243 | 244 | v. a URI or hyperlink to the Licensed Material to the 245 | extent reasonably practicable; 246 | 247 | b. indicate if You modified the Licensed Material and 248 | retain an indication of any previous modifications; and 249 | 250 | c. indicate the Licensed Material is licensed under this 251 | Public License, and include the text of, or the URI or 252 | hyperlink to, this Public License. 253 | 254 | For the avoidance of doubt, You do not have permission under 255 | this Public License to Share Adapted Material. 256 | 257 | 2. You may satisfy the conditions in Section 3(a)(1) in any 258 | reasonable manner based on the medium, means, and context in 259 | which You Share the Licensed Material. For example, it may be 260 | reasonable to satisfy the conditions by providing a URI or 261 | hyperlink to a resource that includes the required 262 | information. 263 | 264 | 3. If requested by the Licensor, You must remove any of the 265 | information required by Section 3(a)(1)(A) to the extent 266 | reasonably practicable. 267 | 268 | 269 | Section 4 -- Sui Generis Database Rights. 270 | 271 | Where the Licensed Rights include Sui Generis Database Rights that 272 | apply to Your use of the Licensed Material: 273 | 274 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 275 | to extract, reuse, reproduce, and Share all or a substantial 276 | portion of the contents of the database for NonCommercial purposes 277 | only and provided You do not Share Adapted Material; 278 | 279 | b. if You include all or a substantial portion of the database 280 | contents in a database in which You have Sui Generis Database 281 | Rights, then the database in which You have Sui Generis Database 282 | Rights (but not its individual contents) is Adapted Material; and 283 | 284 | c. You must comply with the conditions in Section 3(a) if You Share 285 | all or a substantial portion of the contents of the database. 286 | 287 | For the avoidance of doubt, this Section 4 supplements and does not 288 | replace Your obligations under this Public License where the Licensed 289 | Rights include other Copyright and Similar Rights. 290 | 291 | 292 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 293 | 294 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 295 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 296 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 297 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 298 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 299 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 300 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 301 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 302 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 303 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 304 | 305 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 306 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 307 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 308 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 309 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 310 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 311 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 312 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 313 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 314 | 315 | c. The disclaimer of warranties and limitation of liability provided 316 | above shall be interpreted in a manner that, to the extent 317 | possible, most closely approximates an absolute disclaimer and 318 | waiver of all liability. 319 | 320 | 321 | Section 6 -- Term and Termination. 322 | 323 | a. This Public License applies for the term of the Copyright and 324 | Similar Rights licensed here. However, if You fail to comply with 325 | this Public License, then Your rights under this Public License 326 | terminate automatically. 327 | 328 | b. Where Your right to use the Licensed Material has terminated under 329 | Section 6(a), it reinstates: 330 | 331 | 1. automatically as of the date the violation is cured, provided 332 | it is cured within 30 days of Your discovery of the 333 | violation; or 334 | 335 | 2. upon express reinstatement by the Licensor. 336 | 337 | For the avoidance of doubt, this Section 6(b) does not affect any 338 | right the Licensor may have to seek remedies for Your violations 339 | of this Public License. 340 | 341 | c. For the avoidance of doubt, the Licensor may also offer the 342 | Licensed Material under separate terms or conditions or stop 343 | distributing the Licensed Material at any time; however, doing so 344 | will not terminate this Public License. 345 | 346 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 347 | License. 348 | 349 | 350 | Section 7 -- Other Terms and Conditions. 351 | 352 | a. The Licensor shall not be bound by any additional or different 353 | terms or conditions communicated by You unless expressly agreed. 354 | 355 | b. Any arrangements, understandings, or agreements regarding the 356 | Licensed Material not stated herein are separate from and 357 | independent of the terms and conditions of this Public License. 358 | 359 | 360 | Section 8 -- Interpretation. 361 | 362 | a. For the avoidance of doubt, this Public License does not, and 363 | shall not be interpreted to, reduce, limit, restrict, or impose 364 | conditions on any use of the Licensed Material that could lawfully 365 | be made without permission under this Public License. 366 | 367 | b. To the extent possible, if any provision of this Public License is 368 | deemed unenforceable, it shall be automatically reformed to the 369 | minimum extent necessary to make it enforceable. If the provision 370 | cannot be reformed, it shall be severed from this Public License 371 | without affecting the enforceability of the remaining terms and 372 | conditions. 373 | 374 | c. No term or condition of this Public License will be waived and no 375 | failure to comply consented to unless expressly agreed to by the 376 | Licensor. 377 | 378 | d. Nothing in this Public License constitutes or may be interpreted 379 | as a limitation upon, or waiver of, any privileges and immunities 380 | that apply to the Licensor or You, including from the legal 381 | processes of any jurisdiction or authority. 382 | 383 | ======================================================================= 384 | 385 | Creative Commons is not a party to its public 386 | licenses. Notwithstanding, Creative Commons may elect to apply one of 387 | its public licenses to material it publishes and in those instances 388 | will be considered the “Licensor.” The text of the Creative Commons 389 | public licenses is dedicated to the public domain under the CC0 Public 390 | Domain Dedication. Except for the limited purpose of indicating that 391 | material is shared under a Creative Commons public license or as 392 | otherwise permitted by the Creative Commons policies published at 393 | creativecommons.org/policies, Creative Commons does not authorize the 394 | use of the trademark "Creative Commons" or any other trademark or logo 395 | of Creative Commons without its prior written consent including, 396 | without limitation, in connection with any unauthorized modifications 397 | to any of its public licenses or any other arrangements, 398 | understandings, or agreements concerning use of licensed material. For 399 | the avoidance of doubt, this paragraph does not form part of the 400 | public licenses. 401 | 402 | Creative Commons may be contacted at creativecommons.org. 403 | 404 | -------------------------------------------------------------------------------- /Protocol/BlueSolar-HEX-protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Victron2MQTT/028466b9445ea66dff76e8afe19bc6bf8815a03c/Protocol/BlueSolar-HEX-protocol.pdf -------------------------------------------------------------------------------- /Protocol/VE.Direct-Protocol-3.33.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Victron2MQTT/028466b9445ea66dff76e8afe19bc6bf8815a03c/Protocol/VE.Direct-Protocol-3.33.pdf -------------------------------------------------------------------------------- /Protocol/vicASCII.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Victron2MQTT/028466b9445ea66dff76e8afe19bc6bf8815a03c/Protocol/vicASCII.log -------------------------------------------------------------------------------- /Protocol/vicHEX.log: -------------------------------------------------------------------------------- 1 | 2 | [2023-08-09 21:47:32.220]# RECV HEX> 3 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 33 30 0D 0A 49 09 2D 39 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 05 4 | 5 | [2023-08-09 21:47:33.201]# RECV HEX> 6 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 33 30 0D 0A 49 09 2D 31 30 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 31 30 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 7C 7 | 8 | [2023-08-09 21:47:34.201]# RECV HEX> 9 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 32 30 0D 0A 49 09 2D 31 30 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 31 30 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 7D 10 | 11 | [2023-08-09 21:47:35.196]# RECV HEX> 12 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 33 30 0D 0A 49 09 2D 39 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 05 13 | 14 | [2023-08-09 21:47:36.197]# RECV HEX> 15 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 32 30 0D 0A 49 09 2D 31 30 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 31 30 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 7D 16 | 17 | [2023-08-09 21:47:37.209]# RECV HEX> 18 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 32 30 0D 0A 49 09 2D 39 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 06 19 | 20 | [2023-08-09 21:47:38.206]# RECV HEX> 21 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 32 30 0D 0A 49 09 2D 39 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 06 22 | 23 | [2023-08-09 21:47:39.204]# RECV HEX> 24 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 32 30 0D 0A 49 09 2D 31 30 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 31 30 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 7D 25 | 26 | [2023-08-09 21:47:40.187]# RECV HEX> 27 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 33 30 0D 0A 49 09 2D 31 30 30 0D 0A 56 50 56 09 31 38 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 31 30 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 7C 28 | 29 | [2023-08-09 21:47:41.186]# RECV HEX> 30 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 32 30 0D 0A 49 09 2D 39 30 0D 0A 56 50 56 09 31 37 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 31 30 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 A6 31 | 32 | [2023-08-09 21:47:42.185]# RECV HEX> 33 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 32 30 0D 0A 49 09 2D 31 30 30 0D 0A 56 50 56 09 31 36 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 E0 34 | 35 | [2023-08-09 21:47:43.183]# RECV HEX> 36 | 0D 0A 50 49 44 09 30 78 41 30 35 33 0D 0A 46 57 09 31 36 31 0D 0A 53 45 52 23 09 48 51 32 31 30 34 56 4C 41 37 41 0D 0A 56 09 31 32 39 33 30 0D 0A 49 09 2D 31 31 30 0D 0A 56 50 56 09 31 36 30 0D 0A 50 50 56 09 30 0D 0A 43 53 09 30 0D 0A 4D 50 50 54 09 30 0D 0A 4F 52 09 30 78 30 30 30 30 30 30 30 31 0D 0A 45 52 52 09 30 0D 0A 4C 4F 41 44 09 4F 4E 0D 0A 49 4C 09 31 30 30 0D 0A 48 31 39 09 34 34 38 33 0D 0A 48 32 30 09 31 33 0D 0A 48 32 31 09 37 30 0D 0A 48 32 32 09 34 0D 0A 48 32 33 09 34 34 0D 0A 48 53 44 53 09 31 31 34 0D 0A 43 68 65 63 6B 73 75 6D 09 7D 37 | -------------------------------------------------------------------------------- /Protocol/viread.log: -------------------------------------------------------------------------------- 1 | =~=~=~=~=~=~=~=~=~=~=~= PuTTY log 2023.08.09 21:50:22 =~=~=~=~=~=~=~=~=~=~=~= 2 | 3 | PID0xA053 4 | FW161 5 | SER#HQ2104VLA7A 6 | V12930 7 | I-90 8 | VPV120 9 | PPV0 10 | CS0 11 | MPPT0 12 | OR0x00000001 13 | ERR0 14 | LOADON 15 | IL100 16 | H194483 17 | H2013 18 | H2170 19 | H224 20 | H2344 21 | HSDS114 22 | Checksum 23 | PID0xA053 24 | FW161 25 | SER#HQ2104VLA7A 26 | V12930 27 | I-90 28 | VPV120 29 | PPV0 30 | CS0 31 | MPPT0 32 | OR0x00000001 33 | ERR0 34 | LOADON 35 | IL100 36 | H194483 37 | H2013 38 | H2170 39 | H224 40 | H2344 41 | HSDS114 42 | Checksum 43 | PID0xA053 44 | FW161 45 | SER#HQ2104VLA7A 46 | V12920 47 | I-110 48 | VPV120 49 | PPV0 50 | CS0 51 | MPPT0 52 | OR0x00000001 53 | ERR0 54 | LOADON 55 | IL100 56 | H194483 57 | H2013 58 | H2170 59 | H224 60 | H2344 61 | HSDS114 62 | Checksum 63 | PID0xA053 64 | FW161 65 | SER#HQ2104VLA7A 66 | V12920 67 | I-100 68 | VPV140 69 | PPV0 70 | CS0 71 | MPPT0 72 | OR0x00000001 73 | ERR0 74 | LOADON 75 | IL100 76 | H194483 77 | H2013 78 | H2170 79 | H224 80 | H2344 81 | HSDS114 82 | Checksum 83 | PID0xA053 84 | FW161 85 | SER#HQ2104VLA7A 86 | V12930 87 | I-100 88 | VPV180 89 | PPV0 90 | CS0 91 | MPPT0 92 | OR0x00000001 93 | ERR0 94 | LOADON 95 | IL100 96 | H194483 97 | H2013 98 | H2170 99 | H224 100 | H2344 101 | HSDS114 102 | Checksum| 103 | PID0xA053 104 | FW161 105 | SER#HQ2104VLA7A 106 | V12930 107 | I-110 108 | VPV290 109 | PPV0 110 | CS0 111 | MPPT0 112 | OR0x00000001 113 | ERR0 114 | LOADON 115 | IL100 116 | H194483 117 | H2013 118 | H2170 119 | H224 120 | H2344 121 | HSDS114 122 | Checksumy 123 | PID0xA053 124 | FW161 125 | SER#HQ2104VLA7A 126 | V12930 127 | I-100 128 | VPV270 129 | PPV0 130 | CS0 131 | MPPT0 132 | OR0x00000001 133 | ERR0 134 | LOADON 135 | IL100 136 | H194483 137 | H2013 138 | H2170 139 | H224 140 | H2344 141 | HSDS114 142 | Checksum| 143 | PID0xA053 144 | FW161 145 | SER#HQ2104VLA7A 146 | V12930 147 | I-100 148 | VPV250 149 | PPV0 150 | CS0 151 | MPPT0 152 | OR0x00000001 153 | ERR0 154 | LOADON 155 | IL100 156 | H194483 157 | H2013 158 | H2170 159 | H224 160 | H2344 161 | HSDS114 162 | Checksum~ 163 | PID0xA053 164 | FW161 165 | SER#HQ2104VLA7A 166 | V12930 167 | I-100 168 | VPV400 169 | PPV0 170 | CS0 171 | MPPT0 172 | OR0x00000001 173 | ERR0 174 | LOADON 175 | IL0 176 | H194483 177 | H2013 178 | H2170 179 | H224 180 | H2344 181 | HSDS114 182 | Checksum 183 | PID0xA053 184 | FW161 185 | SER#HQ2104VLA7A 186 | V12930 187 | I-110 188 | VPV140 189 | PPV0 190 | CS0 191 | MPPT0 192 | OR0x00000001 193 | ERR0 194 | LOADON 195 | IL0 196 | H194483 197 | H2013 198 | H2170 199 | H224 200 | H2344 201 | HSDS114 202 | Checksum 203 | PID0xA053 204 | FW161 205 | SER#HQ2104VLA7A 206 | V12930 207 | I-100 208 | VPV120 209 | PPV0 210 | CS0 211 | MPPT0 212 | OR0x00000001 213 | ERR0 214 | LOADON 215 | IL100 216 | H194483 217 | H2013 218 | H2170 219 | H224 220 | H2344 221 | HSDS114 222 | Checksum 223 | PID0xA053 224 | FW161 225 | SER#HQ2104VLA7A 226 | V12930 227 | I-100 228 | VPV120 229 | PPV0 230 | CS0 231 | MPPT0 232 | OR0x00000001 233 | ERR0 234 | LOADON 235 | IL100 236 | H194483 237 | H2013 238 | H2170 239 | H224 240 | H2344 241 | HSDS114 242 | Checksum 243 | PID0xA053 244 | FW161 245 | SER#HQ2104VLA7A 246 | V12930 247 | I-100 248 | VPV120 249 | PPV0 250 | CS0 251 | MPPT0 252 | OR0x00000001 253 | ERR0 254 | LOADON 255 | IL100 256 | H194483 257 | H2013 258 | H2170 259 | H224 260 | H2344 261 | HSDS114 262 | Checksum 263 | PID0xA053 264 | FW161 265 | SER#HQ2104VLA7A 266 | V12930 267 | I-100 268 | VPV120 269 | PPV0 270 | CS0 271 | MPPT0 272 | OR0x00000001 273 | ERR0 274 | LOADON 275 | IL100 276 | H194483 277 | H2013 278 | H2170 279 | H224 280 | H2344 281 | HSDS114 282 | Checksum 283 | PID0xA053 284 | FW161 285 | SER#HQ2104VLA7A 286 | V12930 287 | I-100 288 | VPV120 289 | PPV0 290 | CS0 291 | MPPT0 292 | OR0x00000001 293 | ERR0 294 | LOADON 295 | IL100 296 | H194483 297 | H2013 298 | H2170 299 | H224 300 | H2344 301 | HSDS114 302 | Checksum 303 | PID0xA053 304 | FW161 305 | SER#HQ2104VLA7A 306 | V12930 307 | I-90 308 | VPV120 309 | PPV0 310 | CS0 311 | MPPT0 312 | OR0x00000001 313 | ERR0 314 | LOADON 315 | IL0 316 | H194483 317 | H2013 318 | H2170 319 | H224 320 | H2344 321 | HSDS114 322 | Checksum -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Victron2MQTT [![GitHub release](https://img.shields.io/github/release/softwarecrash/Victron2MQTT?include_prereleases=&sort=semver&color=blue)](https://github.com/softwarecrash/Victron2MQTT/releases/latest) [![Discord](https://img.shields.io/discord/1007020337482973254?logo=discord&label=Discord)](https://discord.gg/7gTJk22JDE) 2 | 3 | Little Program for ESP82XX to get the Victron data to web and MQTT 4 | 5 | # Features: 6 | - captive portal for wifi and MQTT config 7 | - config in webinterface 8 | - get essential data over webinterface, get all data like cell voltage and more over MQTT 9 | - get Json over web at /livejson? 10 | - firmware update over webinterface 11 | - [Remote Control](https://github.com/softwarecrash/Victron2MQTT/wiki/Remote-Control) 12 | - [Homeassistant Discovery](https://github.com/softwarecrash/Victron2MQTT/wiki/HomeAssistant-integration) 13 | 14 | **Main screen:** 15 | 16 | ![grafik](https://github.com/softwarecrash/Victron2MQTT/assets/44615614/41786ae3-4ed0-44af-bc73-5b994b6cd211) 17 | 18 | 19 | **Settings:** 20 | 21 | ![grafik](https://github.com/softwarecrash/Victron2MQTT/assets/44615614/6943ef5d-8711-4b32-bbd0-80278a6f50fa) 22 | 23 | 24 | **Config:** 25 | 26 | ![grafik](https://github.com/softwarecrash/Victron2MQTT/assets/44615614/64aaa883-aee1-40b9-8e8a-ead788fbf70a) 27 | 28 | **MQTT Data** 29 | 30 | ![grafik](https://github.com/softwarecrash/Victron2MQTT/assets/44615614/73eedc23-fc77-4034-934c-e8c123a9800e) 31 | 32 | 33 | # [Connection to Victron](https://github.com/softwarecrash/Victron2MQTT/wiki/Wiring-Diagram) 34 | 35 | 36 | 37 | # How to use: 38 | - flash your ESP8266 (recommended Wemos D1 Mini) with our [Flash2MQTT-Tool](https://all-solutions.github.io/Flash2MQTT/?get=Victron2MQTT) or with [Tasmotizer](https://github.com/tasmota/tasmotizer/releases) 39 | - connect the ESP like the [wiring diagram](https://github.com/softwarecrash/Victron2MQTT/wiki/Wiring-Diagram) 40 | - search for the wifi ap "Victron2MQTT-AP" and connect to it 41 | - surf to 192.168.4.1 and set up your wifi and optional MQTT 42 | - that's it :) 43 | 44 | ### How-To video by DerKanal 45 | 46 | <a href="http://www.youtube.com/watch?feature=player_embedded&v=8HfPKlgeX0k" target="_blank"> 47 | <img src="http://img.youtube.com/vi/8HfPKlgeX0k/0.jpg" alt="Watch the video" /> 48 | </a> 49 | 50 | ### How-To video by Jarnsen 51 | 52 | <a href="http://www.youtube.com/watch?feature=player_embedded&v=4kO6WVnofig" target="_blank"> 53 | <img src="http://img.youtube.com/vi/4kO6WVnofig/0.jpg" alt="Watch the video" /> 54 | </a> 55 | 56 | # Completely assembled and tested PCB's 57 | 58 | You are welcome to get fully stocked and tested PCB's. These are then already loaded with the lastest firmware. The earnings from the PCBs are used for the further development of existing and new projects. 59 | 60 | [<img src="https://github.com/softwarecrash/Victron2MQTT/assets/17761850/a9e6432a-c4de-4376-8cc6-17c29e26fef6" />](https://all-solutions.store) 61 | 62 | If interested see [here](https://all-solutions.store) 63 | 64 | # Questions? 65 | [Join the Discord Channel (German / English)](https://discord.gg/7gTJk22JDE) 66 | 67 | # 68 | [<img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"/>](https://donate.softwarecrash.de) 69 | 70 | # 71 | [![LICENSE](https://licensebuttons.net/l/by-nc-nd/4.0/88x31.png)](https://creativecommons.org/licenses/by-nc-nd/4.0/) 72 | -------------------------------------------------------------------------------- /include/readme.md: -------------------------------------------------------------------------------- 1 | for pushing tree -------------------------------------------------------------------------------- /notes: -------------------------------------------------------------------------------- 1 | https://github.com/KinDR007/VictronMPPT-ESPHOME 2 | https://www.akkudoktor.net/forum/panels-wechselrichter-laderegler/victron-ve-direct-mqtt-interface-erste-erfolge-und-fragen/ 3 | https://github.com/physee/Victron.Arduino-ESP8266 4 | https://github.com/datjan/esp8266-victron-mppt-solarchargecontroller 5 | 6 | https://www.victronenergy.com/live/vedirect_protocol:faq 7 | https://www.victronenergy.com/upload/documents/Technical-Information-Data-communication-with-Victron-Energy-products_EN.pdf 8 | 9 | 10 | https://www.victronenergy.com/upload/documents/BlueSolar-HEX-protocol.pdf 11 | https://github.com/cterwilliger/VeDirectFrameHandler -------------------------------------------------------------------------------- /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 | [env:d1_mini] 12 | platform = espressif8266@4.2.1 13 | board = d1_mini 14 | framework = arduino 15 | monitor_speed = 115200 16 | build_type = debug 17 | monitor_filters = esp8266_exception_decoder, default, time, printable, colorize 18 | board_build.ldscript = eagle.flash.4m.ld ; 4MB (FS:4MB OTA:~3600KB) 19 | upload_speed = 921600 20 | 21 | custom_prog_version = 1.3.0 22 | 23 | build_flags = 24 | -DVERSION=${this.custom_prog_version} 25 | -DPIO_SRC_NAM="Victron2MQTT" 26 | -DESP8266 -DATOMIC_FS_UPDATE 27 | 28 | extra_scripts = pre:tools/mini_html.py 29 | pre:tools/pre_compile.py 30 | post:tools/post_compile.py 31 | 32 | lib_deps = 33 | knolleary/PubSubClient @ ^2.8 34 | bblanchon/ArduinoJson @ ^6.21.3 35 | esphome/ESPAsyncTCP-esphome @ 2.0.0 36 | ESP32Async/ESPAsyncWebServer @ 3.7.6 37 | mathieucarbou/MycilaWebSerial@^8.0.0 38 | alanswx/ESPAsyncWiFiManager@^0.31 39 | plerup/EspSoftwareSerial @ ^8.2.0 40 | https://github.com/dok-net/ghostl 41 | mathieucarbou/OneWire@^2.3.9 42 | gbertaz/NonBlockingDallas@^1.1.0 -------------------------------------------------------------------------------- /src/RTCMemory.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2020-2022 Fabiano Riccardi * 3 | * * 4 | * This file is part of RTCMemory. * 5 | * * 6 | * RTCMemory is free software; you can redistribute * 7 | * it and/or modify it under the terms of the GNU Lesser General Public * 8 | * License as published by the Free Software Foundation; either * 9 | * version 2.1 of the License, or (at your option) any later version. * 10 | * * 11 | * RTCMemory is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 14 | * Lesser General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with RTCMemory; if not, see <http://www.gnu.org/licenses/> * 18 | ***************************************************************************/ 19 | //10.4.204 modifyd by softwarecrash, removed fs and other things for saving lot of space 20 | #ifndef RTCMEMORY_H 21 | #define RTCMEMORY_H 22 | 23 | //#include <LittleFS.h> 24 | //#include <type_traits> 25 | 26 | /** 27 | * T is the user data structure, N is the max number of bytes used by this library. N acts like a 28 | * "soft" limiter. 29 | */ 30 | template<typename T, int N = 384> class RTCMemory { 31 | public: 32 | /** 33 | * Create RTCMemory. Provide a valid filepath to enable the backup of RTC memory on flash memory. 34 | * By default the library uses LittleFS, but you can select an alternative filesystem such as 35 | * SPIFFS. 36 | */ 37 | RTCMemory(); 38 | 39 | /** 40 | * Initialize the buffer using data in RTC memory. If data are invalid, load data from 41 | * flash. If it fails again, the buffer is cleared. After this call you can safely use this 42 | * instance. 43 | * 44 | * Return true if at least one data source is valid, otherwise false. 45 | */ 46 | bool begin(); 47 | 48 | /** 49 | * Write data to RTC memory. 50 | * 51 | * Return true if the operation is completed successfully, otherwise false. 52 | */ 53 | bool save(); 54 | 55 | /** 56 | * Get a pointer to the user buffer, structured accordingly to the typename T. 57 | * 58 | * Return a valid pointer to the data if begin() was called, otherwise nullptr. 59 | */ 60 | T *getData(); 61 | 62 | private: 63 | const static unsigned int BLOCK_SIZE = 4; 64 | 65 | /** 66 | * Number of bytes to contain the user data structure. This value is ceiled to multiple of 4 to 67 | * respect the memory alignment of RTC memory. 68 | */ 69 | const static unsigned int USER_RTC_MEMORY_SIZE = (sizeof(T) - 1) / BLOCK_SIZE * BLOCK_SIZE + BLOCK_SIZE; 70 | 71 | /** 72 | * Max size of user memory. 73 | */ 74 | const static unsigned int MAX_USER_RTC_MEMORY_SIZE = 508; 75 | 76 | /** 77 | * Max size available in RTC memory. 78 | */ 79 | const static unsigned int MAX_TOTAL_RTC_MEMORY_SIZE = 512; 80 | 81 | struct RTCData { 82 | uint32_t crc32; 83 | // The user buffer 84 | byte data[USER_RTC_MEMORY_SIZE]; 85 | }; 86 | 87 | /** 88 | * Offset from the first block in RTC memory. This offset is used to shift data toward the 89 | * highest RTC memory addresses. 90 | */ 91 | const static unsigned int OFFSET = (MAX_TOTAL_RTC_MEMORY_SIZE - sizeof(RTCData)) / BLOCK_SIZE; 92 | 93 | // The buffer 94 | RTCData rtcData; 95 | 96 | /** 97 | * Tell if this class is ready to be used i.e. if begin() was called. 98 | */ 99 | bool ready; 100 | 101 | /** 102 | * Calculate the CRC (32bit) of the given buffer. 103 | * 104 | * Return the CRC code. 105 | */ 106 | uint32_t calculateCRC32(const uint8_t *data, size_t length) const; 107 | 108 | /** 109 | * Clear the buffer. 110 | */ 111 | void clearBuffer(); 112 | }; 113 | template<typename T, int N> 114 | RTCMemory<T, N>::RTCMemory() : ready(false) { 115 | static_assert(sizeof(T) <= MAX_USER_RTC_MEMORY_SIZE, "The max size of user data is 508 bytes. " 116 | "You must reduce the size of your data " 117 | "structure."); 118 | static_assert(sizeof(RTCData) <= N, "You reach the maximum amount of memory reserved to " 119 | "RTCMemory lib. You may increase this limit beyond 384 bytes " 120 | "and up to 512 bytes, but you will lose RTC data after an " 121 | "OTA update."); 122 | } 123 | 124 | template<typename T, int N> bool RTCMemory<T, N>::begin() { 125 | if (ready) { 126 | return true; 127 | } 128 | 129 | if (!ESP.rtcUserMemoryRead(OFFSET, (uint32_t *)&rtcData, sizeof(RTCData))) { 130 | return false; 131 | } 132 | 133 | uint32_t crcOfData = calculateCRC32((uint8_t *)&rtcData.data, USER_RTC_MEMORY_SIZE); 134 | 135 | ready = true; 136 | 137 | if (crcOfData != rtcData.crc32) { 138 | clearBuffer(); 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | template<typename T, int N> bool RTCMemory<T, N>::save() { 145 | if (ready) { 146 | uint32_t crcOfData = calculateCRC32((uint8_t *)&rtcData.data, USER_RTC_MEMORY_SIZE); 147 | rtcData.crc32 = crcOfData; 148 | 149 | if (ESP.rtcUserMemoryWrite(OFFSET, (uint32_t *)&rtcData, sizeof(RTCData))) { 150 | return true; 151 | } else { 152 | return false; 153 | } 154 | } else { 155 | return false; 156 | } 157 | } 158 | 159 | template<typename T, int N> T *RTCMemory<T, N>::getData() { 160 | if (ready) { return reinterpret_cast<T *>(rtcData.data); } 161 | return nullptr; 162 | }; 163 | 164 | 165 | template<typename T, int N> uint32_t RTCMemory<T, N>::calculateCRC32(const uint8_t *data, size_t length) const { 166 | uint32_t crc = 0xffffffff; 167 | while (length--) { 168 | uint8_t c = *data++; 169 | for (uint32_t i = 0x80; i > 0; i >>= 1) { 170 | bool bit = crc & 0x80000000; 171 | if (c & i) { bit = !bit; } 172 | crc <<= 1; 173 | if (bit) { crc ^= 0x04c11db7; } 174 | } 175 | } 176 | return crc; 177 | } 178 | 179 | template<typename T, int N> void RTCMemory<T, N>::clearBuffer() { 180 | memset((void *)&rtcData.data, 0, USER_RTC_MEMORY_SIZE); 181 | uint32_t result = calculateCRC32((uint8_t *)rtcData.data, USER_RTC_MEMORY_SIZE); 182 | rtcData.crc32 = result; 183 | } 184 | 185 | #endif // END RTCMEMORY_H 186 | -------------------------------------------------------------------------------- /src/Settings.h: -------------------------------------------------------------------------------- 1 | // Settings: Stores persistant settings, loads and saves to EEPROM 2 | 3 | #include <Arduino.h> 4 | #include <EEPROM.h> 5 | 6 | #define EEPROM_SIZE 1024 7 | 8 | class Settings 9 | { 10 | // change eeprom config version ONLY when new parameter is added and need reset the parameter 11 | unsigned int configVersion = 11; 12 | 13 | public: 14 | String deviceNameStr; 15 | struct Data 16 | { // do not re-sort this struct 17 | unsigned int coVers; // config version, if changed, previus config will erased 18 | char deviceName[40]; // device name 19 | char mqttServer[40]; // mqtt Server adress 20 | char mqttUser[40]; // mqtt Username 21 | char mqttPassword[40]; // mqtt Password 22 | char mqttTopic[40]; // mqtt publish topic 23 | char mqttTriggerPath[80]; // MQTT Data Trigger Path 24 | unsigned int mqttPort; // mqtt port 25 | unsigned int mqttRefresh; // mqtt refresh time 26 | bool mqttJson; // switch between classic mqtt and json 27 | bool webUIdarkmode; // flag for dark mode 28 | char httpUser[40]; // http basic auth username 29 | char httpPass[40]; // http basic auth password 30 | bool haDiscovery; // HomeAssistant Discovery switch 31 | // bool debugmode; // Debug-Mode 32 | bool keepRcState; 33 | bool rcState; 34 | byte LEDBrightness; // brigthness of led 35 | } data; 36 | 37 | void load() 38 | { 39 | data = {}; // clear bevor load data 40 | EEPROM.begin(EEPROM_SIZE); 41 | EEPROM.get(0, data); 42 | EEPROM.end(); 43 | coVersCheck(); 44 | sanitycheck(); 45 | deviceNameStr = data.deviceName; 46 | } 47 | 48 | void save() 49 | { 50 | sanitycheck(); 51 | EEPROM.begin(EEPROM_SIZE); 52 | EEPROM.put(0, data); 53 | EEPROM.commit(); 54 | EEPROM.end(); 55 | } 56 | 57 | void reset() 58 | { 59 | data = {}; 60 | save(); 61 | } 62 | 63 | private: 64 | // check the variables from eeprom 65 | 66 | void sanitycheck() 67 | { 68 | if (strlen(data.deviceName) == 0 || strlen(data.deviceName) >= 40) 69 | { 70 | strcpy(data.deviceName, "Victron2MQTT"); 71 | } 72 | if (strlen(data.mqttServer) == 0 || strlen(data.mqttServer) >= 40) 73 | { 74 | strcpy(data.mqttServer, ""); 75 | } 76 | if (strlen(data.mqttUser) == 0 || strlen(data.mqttUser) >= 40) 77 | { 78 | strcpy(data.mqttUser, ""); 79 | } 80 | if (strlen(data.mqttPassword) == 0 || strlen(data.mqttPassword) >= 40) 81 | { 82 | strcpy(data.mqttPassword, ""); 83 | } 84 | if (strlen(data.mqttTopic) == 0 || strlen(data.mqttTopic) >= 40) 85 | { 86 | strcpy(data.mqttTopic, "Victron"); 87 | } 88 | if (data.mqttPort <= 0 || data.mqttPort >= 65530) 89 | { 90 | data.mqttPort = 0; 91 | } 92 | if (data.mqttRefresh <= 1 || data.mqttRefresh >= 65530) 93 | { 94 | data.mqttRefresh = 0; 95 | } 96 | if (data.mqttJson && !data.mqttJson) 97 | { 98 | data.mqttJson = false; 99 | } 100 | if (strlen(data.mqttTriggerPath) == 0 || strlen(data.mqttTriggerPath) >= 80) 101 | { 102 | strcpy(data.mqttTriggerPath, ""); 103 | } 104 | if (data.webUIdarkmode && !data.webUIdarkmode) 105 | { 106 | data.webUIdarkmode = false; 107 | } 108 | if (strlen(data.httpUser) == 0 || strlen(data.httpUser) >= 40) 109 | { 110 | strcpy(data.httpUser, ""); 111 | } 112 | if (strlen(data.httpPass) == 0 || strlen(data.httpPass) >= 40) 113 | { 114 | strcpy(data.httpPass, ""); 115 | } 116 | if (data.haDiscovery && !data.haDiscovery) 117 | { 118 | data.haDiscovery = false; 119 | } 120 | // if (data.debugmode && !data.debugmode) 121 | //{ 122 | // data.debugmode = false; 123 | //} 124 | if (data.keepRcState && !data.keepRcState) 125 | { 126 | data.keepRcState = false; 127 | } 128 | if (data.rcState && !data.rcState) 129 | { 130 | data.rcState = false; 131 | } 132 | if (data.LEDBrightness && !data.LEDBrightness) 133 | { 134 | data.LEDBrightness = 127; 135 | } 136 | } 137 | void coVersCheck() 138 | { 139 | if (data.coVers != configVersion) 140 | { 141 | data.coVers = configVersion; 142 | strcpy(data.deviceName, "Victron2MQTT"); 143 | strcpy(data.mqttServer, ""); 144 | strcpy(data.mqttUser, ""); 145 | strcpy(data.mqttPassword, ""); 146 | strcpy(data.mqttTopic, "Victron"); 147 | strcpy(data.mqttTriggerPath, ""); 148 | data.mqttPort = 0; 149 | data.mqttRefresh = 300; 150 | data.mqttJson = false; 151 | data.webUIdarkmode = false; 152 | strcpy(data.httpUser, ""); 153 | strcpy(data.httpPass, ""); 154 | data.haDiscovery = false; 155 | // data.debugmode = false; 156 | data.keepRcState = false; 157 | data.rcState = false; 158 | data.LEDBrightness = 127; 159 | save(); 160 | load(); 161 | } 162 | } 163 | }; 164 | -------------------------------------------------------------------------------- /src/VeDirectDataList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <Arduino.h> 4 | 5 | struct VePrettyEntry { 6 | const char* key; 7 | const char* name; 8 | const char* op; 9 | }; 10 | 11 | // --- PRETTY-DATENSTRINGS --- 12 | const char key_0[] PROGMEM = "V"; 13 | const char name_0[] PROGMEM = "Voltage"; 14 | const char op_0[] PROGMEM = "1000"; 15 | const char key_1[] PROGMEM = "V2"; 16 | const char name_1[] PROGMEM = "Voltage_2"; 17 | const char op_1[] PROGMEM = "1000"; 18 | const char key_2[] PROGMEM = "V3"; 19 | const char name_2[] PROGMEM = "Voltage_3"; 20 | const char op_2[] PROGMEM = "1000"; 21 | const char key_3[] PROGMEM = "VS"; 22 | const char name_3[] PROGMEM = "Starter_voltage"; 23 | const char op_3[] PROGMEM = "1000"; 24 | const char key_4[] PROGMEM = "VM"; 25 | const char name_4[] PROGMEM = "Mid_voltage"; 26 | const char op_4[] PROGMEM = "1000"; 27 | const char key_5[] PROGMEM = "DM"; 28 | const char name_5[] PROGMEM = "Mid_deviation"; 29 | const char op_5[] PROGMEM = "10"; 30 | const char key_6[] PROGMEM = "VPV"; 31 | const char name_6[] PROGMEM = "Panel_voltage"; 32 | const char op_6[] PROGMEM = "1000"; 33 | const char key_7[] PROGMEM = "PPV"; 34 | const char name_7[] PROGMEM = "Panel_power"; 35 | const char op_7[] PROGMEM = "0"; 36 | const char key_8[] PROGMEM = "I"; 37 | const char name_8[] PROGMEM = "Battery_current"; 38 | const char op_8[] PROGMEM = "1000"; 39 | const char key_9[] PROGMEM = "I2"; 40 | const char name_9[] PROGMEM = "Battery_current_2"; 41 | const char op_9[] PROGMEM = "1000"; 42 | const char key_10[] PROGMEM = "I3"; 43 | const char name_10[] PROGMEM = "Battery_current_3"; 44 | const char op_10[] PROGMEM = "1000"; 45 | const char key_11[] PROGMEM = "IL"; 46 | const char name_11[] PROGMEM = "Load_current"; 47 | const char op_11[] PROGMEM = "1000"; 48 | const char key_12[] PROGMEM = "LOAD"; 49 | const char name_12[] PROGMEM = "Load_output_state"; 50 | const char op_12[] PROGMEM = ""; 51 | const char key_13[] PROGMEM = "T"; 52 | const char name_13[] PROGMEM = "Battery_temperature"; 53 | const char op_13[] PROGMEM = "0"; 54 | const char key_14[] PROGMEM = "P"; 55 | const char name_14[] PROGMEM = "Instantaneous_power"; 56 | const char op_14[] PROGMEM = "0"; 57 | const char key_15[] PROGMEM = "CE"; 58 | const char name_15[] PROGMEM = "Consumed_Amp_Hours"; 59 | const char op_15[] PROGMEM = "1000"; 60 | const char key_16[] PROGMEM = "SOC"; 61 | const char name_16[] PROGMEM = "SOC"; 62 | const char op_16[] PROGMEM = "10"; 63 | const char key_17[] PROGMEM = "TTG"; 64 | const char name_17[] PROGMEM = "Time_to_go"; 65 | const char op_17[] PROGMEM = "0"; 66 | const char key_18[] PROGMEM = "ALARM"; 67 | const char name_18[] PROGMEM = "Alarm"; 68 | const char op_18[] PROGMEM = ""; 69 | const char key_19[] PROGMEM = "RELAY"; 70 | const char name_19[] PROGMEM = "Relay"; 71 | const char op_19[] PROGMEM = ""; 72 | const char key_20[] PROGMEM = "AR"; 73 | const char name_20[] PROGMEM = "Alarm_code"; 74 | const char op_20[] PROGMEM = "0"; 75 | const char key_21[] PROGMEM = "OR"; 76 | const char name_21[] PROGMEM = "Off_reason"; 77 | const char op_21[] PROGMEM = "0"; 78 | const char key_22[] PROGMEM = "H1"; 79 | const char name_22[] PROGMEM = "Deepest_discharge"; 80 | const char op_22[] PROGMEM = "1000"; 81 | const char key_23[] PROGMEM = "H2"; 82 | const char name_23[] PROGMEM = "Last_discharge"; 83 | const char op_23[] PROGMEM = "1000"; 84 | const char key_24[] PROGMEM = "H3"; 85 | const char name_24[] PROGMEM = "Average_discharge"; 86 | const char op_24[] PROGMEM = "1000"; 87 | const char key_25[] PROGMEM = "H4"; 88 | const char name_25[] PROGMEM = "Charge_cycles"; 89 | const char op_25[] PROGMEM = "0"; 90 | const char key_26[] PROGMEM = "H5"; 91 | const char name_26[] PROGMEM = "Full_discharges"; 92 | const char op_26[] PROGMEM = "0"; 93 | const char key_27[] PROGMEM = "H6"; 94 | const char name_27[] PROGMEM = "Cumulative_Ah_drawn"; 95 | const char op_27[] PROGMEM = "1000"; 96 | const char key_28[] PROGMEM = "H7"; 97 | const char name_28[] PROGMEM = "Minimum_voltage"; 98 | const char op_28[] PROGMEM = "1000"; 99 | const char key_29[] PROGMEM = "H8"; 100 | const char name_29[] PROGMEM = "Maximum_voltage"; 101 | const char op_29[] PROGMEM = "1000"; 102 | const char key_30[] PROGMEM = "H9"; 103 | const char name_30[] PROGMEM = "Last_full_charge"; 104 | const char op_30[] PROGMEM = "3600"; 105 | const char key_31[] PROGMEM = "H10"; 106 | const char name_31[] PROGMEM = "Num_automatic_sync"; 107 | const char op_31[] PROGMEM = "0"; 108 | const char key_32[] PROGMEM = "H11"; 109 | const char name_32[] PROGMEM = "Num_low_volt_alarms"; 110 | const char op_32[] PROGMEM = "0"; 111 | const char key_33[] PROGMEM = "H12"; 112 | const char name_33[] PROGMEM = "Num_high_volt_alarms"; 113 | const char op_33[] PROGMEM = "0"; 114 | const char key_34[] PROGMEM = "H13"; 115 | const char name_34[] PROGMEM = "Num_low_aux_vol_alarms"; 116 | const char op_34[] PROGMEM = "0"; 117 | const char key_35[] PROGMEM = "H14"; 118 | const char name_35[] PROGMEM = "Num_high_aux_vol_alarms"; 119 | const char op_35[] PROGMEM = "0"; 120 | const char key_36[] PROGMEM = "H15"; 121 | const char name_36[] PROGMEM = "Min_aux_volt"; 122 | const char op_36[] PROGMEM = "1000"; 123 | const char key_37[] PROGMEM = "H16"; 124 | const char name_37[] PROGMEM = "Max_aux_volt"; 125 | const char op_37[] PROGMEM = "1000"; 126 | const char key_38[] PROGMEM = "H17"; 127 | const char name_38[] PROGMEM = "Amount_discharged_energy"; 128 | const char op_38[] PROGMEM = "100"; 129 | const char key_39[] PROGMEM = "H18"; 130 | const char name_39[] PROGMEM = "Amount_charged_energy"; 131 | const char op_39[] PROGMEM = "100"; 132 | const char key_40[] PROGMEM = "H19"; 133 | const char name_40[] PROGMEM = "total_kWh"; 134 | const char op_40[] PROGMEM = "100"; 135 | const char key_41[] PROGMEM = "H20"; 136 | const char name_41[] PROGMEM = "today_kWh"; 137 | const char op_41[] PROGMEM = "100"; 138 | const char key_42[] PROGMEM = "H21"; 139 | const char name_42[] PROGMEM = "Max_pow_today"; 140 | const char op_42[] PROGMEM = "0"; 141 | const char key_43[] PROGMEM = "H22"; 142 | const char name_43[] PROGMEM = "Yesterday_kWh"; 143 | const char op_43[] PROGMEM = "100"; 144 | const char key_44[] PROGMEM = "H23"; 145 | const char name_44[] PROGMEM = "Max_pow_yesterday"; 146 | const char op_44[] PROGMEM = "0"; 147 | const char key_45[] PROGMEM = "ERR"; 148 | const char name_45[] PROGMEM = "Current_error"; 149 | const char op_45[] PROGMEM = "0"; 150 | const char key_46[] PROGMEM = "CS"; 151 | const char name_46[] PROGMEM = "Operation_state"; 152 | const char op_46[] PROGMEM = "0"; 153 | const char key_47[] PROGMEM = "BMV"; 154 | const char name_47[] PROGMEM = "Model_description"; 155 | const char op_47[] PROGMEM = ""; 156 | const char key_48[] PROGMEM = "FW"; 157 | const char name_48[] PROGMEM = "Firmware_version_16"; 158 | const char op_48[] PROGMEM = ""; 159 | const char key_49[] PROGMEM = "FWE"; 160 | const char name_49[] PROGMEM = "Firmware_version_24"; 161 | const char op_49[] PROGMEM = ""; 162 | const char key_50[] PROGMEM = "PID"; 163 | const char name_50[] PROGMEM = "Device_model"; 164 | const char op_50[] PROGMEM = ""; 165 | const char key_51[] PROGMEM = "SER#"; 166 | const char name_51[] PROGMEM = "Serial_number"; 167 | const char op_51[] PROGMEM = ""; 168 | const char key_52[] PROGMEM = "HSDS"; 169 | const char name_52[] PROGMEM = "Day"; 170 | const char op_52[] PROGMEM = "0"; 171 | const char key_53[] PROGMEM = "MODE"; 172 | const char name_53[] PROGMEM = "Device_mode"; 173 | const char op_53[] PROGMEM = ""; 174 | const char key_54[] PROGMEM = "AC_OUT_V"; 175 | const char name_54[] PROGMEM = "AC_out_volt"; 176 | const char op_54[] PROGMEM = "100"; 177 | const char key_55[] PROGMEM = "AC_OUT_I"; 178 | const char name_55[] PROGMEM = "AC_out_current"; 179 | const char op_55[] PROGMEM = "10"; 180 | const char key_56[] PROGMEM = "AC_OUT_S"; 181 | const char name_56[] PROGMEM = "AC_out_apparent_pow"; 182 | const char op_56[] PROGMEM = ""; 183 | const char key_57[] PROGMEM = "WARN"; 184 | const char name_57[] PROGMEM = "Warning_reason"; 185 | const char op_57[] PROGMEM = ""; 186 | const char key_58[] PROGMEM = "MPPT"; 187 | const char name_58[] PROGMEM = "Tracker_operation_mode"; 188 | const char op_58[] PROGMEM = ""; 189 | const char key_59[] PROGMEM = "MON"; 190 | const char name_59[] PROGMEM = "DC_monitor_mode"; 191 | const char op_59[] PROGMEM = ""; 192 | const char key_60[] PROGMEM = "DC_IN_V"; 193 | const char name_60[] PROGMEM = "DC_input_voltage"; 194 | const char op_60[] PROGMEM = "100"; 195 | const char key_61[] PROGMEM = "DC_IN_I"; 196 | const char name_61[] PROGMEM = "DC_input_current"; 197 | const char op_61[] PROGMEM = "10"; 198 | const char key_62[] PROGMEM = "DC_IN_P"; 199 | const char name_62[] PROGMEM = "DC_input_power"; 200 | const char op_62[] PROGMEM = "0"; 201 | 202 | // --- PRETTY-DATENLISTE --- 203 | const VePrettyEntry VePrettyData[] PROGMEM = { 204 | {key_0, name_0, op_0}, 205 | {key_1, name_1, op_1}, 206 | {key_2, name_2, op_2}, 207 | {key_3, name_3, op_3}, 208 | {key_4, name_4, op_4}, 209 | {key_5, name_5, op_5}, 210 | {key_6, name_6, op_6}, 211 | {key_7, name_7, op_7}, 212 | {key_8, name_8, op_8}, 213 | {key_9, name_9, op_9}, 214 | {key_10, name_10, op_10}, 215 | {key_11, name_11, op_11}, 216 | {key_12, name_12, op_12}, 217 | {key_13, name_13, op_13}, 218 | {key_14, name_14, op_14}, 219 | {key_15, name_15, op_15}, 220 | {key_16, name_16, op_16}, 221 | {key_17, name_17, op_17}, 222 | {key_18, name_18, op_18}, 223 | {key_19, name_19, op_19}, 224 | {key_20, name_20, op_20}, 225 | {key_21, name_21, op_21}, 226 | {key_22, name_22, op_22}, 227 | {key_23, name_23, op_23}, 228 | {key_24, name_24, op_24}, 229 | {key_25, name_25, op_25}, 230 | {key_26, name_26, op_26}, 231 | {key_27, name_27, op_27}, 232 | {key_28, name_28, op_28}, 233 | {key_29, name_29, op_29}, 234 | {key_30, name_30, op_30}, 235 | {key_31, name_31, op_31}, 236 | {key_32, name_32, op_32}, 237 | {key_33, name_33, op_33}, 238 | {key_34, name_34, op_34}, 239 | {key_35, name_35, op_35}, 240 | {key_36, name_36, op_36}, 241 | {key_37, name_37, op_37}, 242 | {key_38, name_38, op_38}, 243 | {key_39, name_39, op_39}, 244 | {key_40, name_40, op_40}, 245 | {key_41, name_41, op_41}, 246 | {key_42, name_42, op_42}, 247 | {key_43, name_43, op_43}, 248 | {key_44, name_44, op_44}, 249 | {key_45, name_45, op_45}, 250 | {key_46, name_46, op_46}, 251 | {key_47, name_47, op_47}, 252 | {key_48, name_48, op_48}, 253 | {key_49, name_49, op_49}, 254 | {key_50, name_50, op_50}, 255 | {key_51, name_51, op_51}, 256 | {key_52, name_52, op_52}, 257 | {key_53, name_53, op_53}, 258 | {key_54, name_54, op_54}, 259 | {key_55, name_55, op_55}, 260 | {key_56, name_56, op_56}, 261 | {key_57, name_57, op_57}, 262 | {key_58, name_58, op_58}, 263 | {key_59, name_59, op_59}, 264 | {key_60, name_60, op_60}, 265 | {key_61, name_61, op_61}, 266 | {key_62, name_62, op_62}, 267 | }; 268 | const size_t VePrettyDataSize = sizeof(VePrettyData) / sizeof(VePrettyData[0]); 269 | 270 | 271 | 272 | /* 273 | 274 | static const char * VePrettyData[][3] PROGMEM { 275 | // get name, pretify name, value operator 276 | {"V","Voltage", "1000",}, // display in webUI | Dont edit 277 | {"V2","Voltage_2", "1000",}, 278 | {"V3","Voltage_3", "1000",}, 279 | {"VS","Starter_voltage", "1000",}, 280 | {"VM","Mid_voltage", "1000",}, 281 | {"DM","Mid_deviation", "10",}, 282 | {"VPV","Panel_voltage", "1000",}, // display in webUI | Dont edit 283 | {"PPV","Panel_power", "0",}, // display in webUI | Dont edit 284 | {"I","Battery_current","1000"}, // display in webUI | Dont edit 285 | {"I2","Battery_current_2","1000"}, 286 | {"I3","Battery_current_3","1000"}, 287 | {"IL","Load_current","1000"}, // display in webUI | Dont edit 288 | {"LOAD","Load_output_state",""}, // display in webUI | Dont edit 289 | {"T","Battery_temperature","0"}, // display in webUI | Dont edit 290 | {"P","Instantaneous_power","0"}, 291 | {"CE","Consumed_Amp_Hours","1000"}, 292 | {"SOC", "SOC", "10"}, // display in webUI | Dont edit 293 | {"TTG","Time_to_go","0"}, 294 | {"ALARM","Alarm",""}, 295 | {"RELAY","Relay",""}, // display in webUI | Dont edit 296 | {"AR", "Alarm_code", "0"}, 297 | {"OR", "Off_reason", "0"}, 298 | {"H1", "Deepest_discharge", "1000"}, 299 | {"H2", "Last_discharge", "1000"}, 300 | {"H3", "Average_discharge", "1000"}, 301 | {"H4", "Charge_cycles", "0"}, 302 | {"H5", "Full_discharges", "0"}, 303 | {"H6", "Cumulative_Ah_drawn", "1000"}, 304 | {"H7", "Minimum_voltage", "1000"}, 305 | {"H8", "Maximum_voltage", "1000"}, 306 | {"H9", "Last_full_charge", "3600"}, 307 | {"H10", "Num_automatic_sync", "0"}, 308 | {"H11", "Num_low_volt_alarms", "0"}, 309 | {"H12", "Num_high_volt_alarms", "0"}, 310 | {"H13", "Num_low_aux_vol_alarms", "0"}, 311 | {"H14", "Num_high_aux_vol_alarms", "0"}, 312 | {"H15", "Min_aux_volt", "1000"}, 313 | {"H16", "Max_aux_volt", "1000"}, 314 | {"H17", "Amount_discharged_energy", "100"}, 315 | {"H18", "Amount_charged_energy", "100"}, 316 | {"H19", "total_kWh", "100"}, 317 | {"H20", "today_kWh", "100"}, 318 | {"H21", "Max_pow_today", "0"}, 319 | {"H22", "Yesterday_kWh", "100"}, 320 | {"H23", "Max_pow_yesterday", "0"}, 321 | {"ERR", "Current_error", "0"}, 322 | {"CS", "Operation_state", "0"}, 323 | {"BMV", "Model_description", ""}, 324 | {"FW", "Firmware_version_16", ""}, 325 | {"FWE", "Firmware_version_24", ""}, 326 | {"PID","Device_model",""}, 327 | {"SER#","Serial_number",""}, 328 | {"HSDS","Day","0"}, 329 | {"MODE","Device_mode",""}, 330 | {"AC_OUT_V","AC_out_volt","100"}, 331 | {"AC_OUT_I","AC_out_current","10"}, 332 | {"AC_OUT_S","AC_out_apparent_pow",""}, 333 | {"WARN","Warning_reason",""}, 334 | {"MPPT","Tracker_operation_mode",""}, 335 | {"MON","DC_monitor_mode",""}, 336 | {"DC_IN_V","DC_input_voltage","100"}, 337 | {"DC_IN_I","DC_input_current","10"}, 338 | {"DC_IN_P","DC_input_power","0"}, 339 | };*/ 340 | static const char * haDescriptor[][4] PROGMEM { 341 | // state_topic, icon, unit_ofmeasurement, class 342 | {"Voltage", "car-battery", "V", "voltage"}, 343 | {"Voltage_2", "car-battery", "V", "voltage"}, 344 | {"Voltage_3", "car-battery", "V", "voltage"}, 345 | {"Starter_voltage", "flash-triangle-outline", "V", "voltage"}, 346 | {"Mid_voltage", "battery-outline", "V", "voltage"}, 347 | {"Mid_deviation", "battery-outline", "%", "battery"}, 348 | {"Panel_voltage", "solar-panel", "V", "voltage"}, 349 | {"Panel_power","solar-power","W","power"}, 350 | {"Battery_current", "current-dc", "A", "current"}, 351 | {"Battery_current_2", "current-dc", "A", "current"}, 352 | {"Battery_current_3", "current-dc", "A", "current"}, 353 | {"Load_current", "battery-charging", "A", "current"}, 354 | {"Load_output_state", "export", "", ""}, 355 | {"Battery_temperature", "thermometer", "°C", "temperature"}, 356 | {"Instantaneous_power", "solar-power", "W", "power"}, 357 | {"Consumed_Amp_Hours", "home-battery-outline", "Ah", ""}, 358 | {"SOC", "battery-charging", "%", "battery"}, 359 | {"Time_to_go", "camera-timer", "min", "duration"}, 360 | {"Alarm", "alarm-light-outline", "", ""}, 361 | {"Relay", "electric-switch", "", ""}, 362 | {"Alarm_code", "alarm-panel-outline", "", ""}, 363 | {"Off_reason", "alarm-panel-outline", "", ""}, 364 | {"Deepest_discharge", "battery-outline", "Ah", "energy"}, 365 | {"Last_discharge", "battery-outline", "Ah", "energy"}, 366 | {"Average_discharge", "battery-outline", "Ah", "energy"}, 367 | {"Charge_cycles", "counter", "", ""}, 368 | {"Full_discharges", "counter", "", ""}, 369 | {"Cumulative_Ah_drawn", "battery-outline", "Ah", "energy"}, 370 | {"Minimum_voltage", "battery-charging-outline", "V", "voltage"}, 371 | {"Maximum_voltage", "battery-charging-high", "V", "voltage"}, 372 | {"Last_full_charge", "clock-time-eight-outline", "h", "duration"}, 373 | {"Num_automatic_sync", "counter", "", ""}, 374 | {"Num_low_volt_alarms", "counter", "", ""}, 375 | {"Num_high_volt_alarms", "counter", "", ""}, 376 | {"Num_low_aux_vol_alarms", "counter", "", ""}, 377 | {"Num_high_aux_vol_alarms", "counter", "", ""}, 378 | {"Min_aux_volt", "battery-low", "V", "voltage"}, 379 | {"Max_aux_volt", "battery-high", "V", "voltage"}, 380 | {"Amount_discharged_energy", "battery-arrow-down-outline", "kWh", "energy"}, 381 | {"Amount_charged_energy", "battery-arrow-up-outline", "kWh", "energy"}, 382 | {"total_kWh", "solar-power", "kWh", "energy"}, 383 | {"today_kWh", "solar-power", "kWh", "energy"}, 384 | {"Max_pow_today", "solar-power-variant-outline", "W", "power"}, 385 | {"Yesterday_kWh", "solar-power", "kWh", "energy"}, 386 | {"Max_pow_yesterday", "solar-power-variant-outline", "W", "power"}, 387 | {"Current_error", "alert-circle-outline", "", ""}, 388 | {"Operation_state", "state-machine", "", ""}, 389 | {"Model_description", "select-inverse", "", ""}, 390 | {"Firmware_version_16", "select-inverse", "", ""}, 391 | {"Firmware_version_24", "select-inverse", "", ""}, 392 | {"Device_model", "select-inverse", "", ""}, 393 | {"Serial_number", "format-list-numbered", "", ""}, 394 | {"Day", "calendar-today", "", ""}, 395 | {"Device_mode", "tablet-dashboard", "", ""}, 396 | {"AC_out_volt", "current-ac", "V", "voltage"}, 397 | {"AC_out_current", "current-ac", "A", "current"}, 398 | {"AC_out_apparent_pow", "current-ac", "VA", "apparent_power"}, 399 | {"Warning_reason", "alert-outline", "", ""}, 400 | {"Tracker_operation_mode", "pencil-outline", "", ""}, 401 | {"DC_monitor_mode", "multicast", "", ""}, 402 | {"DC_input_voltage", "current-dc", "V", "voltage"}, 403 | {"DC_input_current", "current-dc", "A", "current"}, 404 | {"DC_input_power", "current-dc", "W", "power"}, 405 | {"Device_connection", "alert-circle-outline", "", ""} 406 | }; -------------------------------------------------------------------------------- /src/VeDirectDeviceCodes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <Arduino.h> 4 | 5 | struct VeCodeEntry { 6 | const char* code; 7 | const char* text; 8 | }; 9 | 10 | // --- Mappingtabelle: VeDirectDeviceCodeAR --- 11 | static const char VeDirectDeviceCodeAR_code_0[] PROGMEM = "1"; 12 | static const char VeDirectDeviceCodeAR_text_0[] PROGMEM = "Low Voltage"; 13 | static const char VeDirectDeviceCodeAR_code_1[] PROGMEM = "2"; 14 | static const char VeDirectDeviceCodeAR_text_1[] PROGMEM = "High Voltage"; 15 | static const char VeDirectDeviceCodeAR_code_2[] PROGMEM = "4"; 16 | static const char VeDirectDeviceCodeAR_text_2[] PROGMEM = "Low SOC"; 17 | static const char VeDirectDeviceCodeAR_code_3[] PROGMEM = "8"; 18 | static const char VeDirectDeviceCodeAR_text_3[] PROGMEM = "Low Starter Voltage"; 19 | static const char VeDirectDeviceCodeAR_code_4[] PROGMEM = "16"; 20 | static const char VeDirectDeviceCodeAR_text_4[] PROGMEM = "High Starter Voltage"; 21 | static const char VeDirectDeviceCodeAR_code_5[] PROGMEM = "32"; 22 | static const char VeDirectDeviceCodeAR_text_5[] PROGMEM = "Low Temperature"; 23 | static const char VeDirectDeviceCodeAR_code_6[] PROGMEM = "64"; 24 | static const char VeDirectDeviceCodeAR_text_6[] PROGMEM = "High Temperature"; 25 | static const char VeDirectDeviceCodeAR_code_7[] PROGMEM = "128"; 26 | static const char VeDirectDeviceCodeAR_text_7[] PROGMEM = "Mid Voltage"; 27 | static const char VeDirectDeviceCodeAR_code_8[] PROGMEM = "256"; 28 | static const char VeDirectDeviceCodeAR_text_8[] PROGMEM = "Overload"; 29 | static const char VeDirectDeviceCodeAR_code_9[] PROGMEM = "512"; 30 | static const char VeDirectDeviceCodeAR_text_9[] PROGMEM = "DC-ripple"; 31 | static const char VeDirectDeviceCodeAR_code_10[] PROGMEM = "1024"; 32 | static const char VeDirectDeviceCodeAR_text_10[] PROGMEM = "Low V AC out"; 33 | static const char VeDirectDeviceCodeAR_code_11[] PROGMEM = "2048"; 34 | static const char VeDirectDeviceCodeAR_text_11[] PROGMEM = "LHigh V AC out"; 35 | static const char VeDirectDeviceCodeAR_code_12[] PROGMEM = "4096"; 36 | static const char VeDirectDeviceCodeAR_text_12[] PROGMEM = "Short Circuit"; 37 | static const char VeDirectDeviceCodeAR_code_13[] PROGMEM = "8192"; 38 | static const char VeDirectDeviceCodeAR_text_13[] PROGMEM = "BMS Lockout"; 39 | 40 | static const VeCodeEntry VeDirectDeviceCodeAR[] PROGMEM = { 41 | {VeDirectDeviceCodeAR_code_0, VeDirectDeviceCodeAR_text_0}, 42 | {VeDirectDeviceCodeAR_code_1, VeDirectDeviceCodeAR_text_1}, 43 | {VeDirectDeviceCodeAR_code_2, VeDirectDeviceCodeAR_text_2}, 44 | {VeDirectDeviceCodeAR_code_3, VeDirectDeviceCodeAR_text_3}, 45 | {VeDirectDeviceCodeAR_code_4, VeDirectDeviceCodeAR_text_4}, 46 | {VeDirectDeviceCodeAR_code_5, VeDirectDeviceCodeAR_text_5}, 47 | {VeDirectDeviceCodeAR_code_6, VeDirectDeviceCodeAR_text_6}, 48 | {VeDirectDeviceCodeAR_code_7, VeDirectDeviceCodeAR_text_7}, 49 | {VeDirectDeviceCodeAR_code_8, VeDirectDeviceCodeAR_text_8}, 50 | {VeDirectDeviceCodeAR_code_9, VeDirectDeviceCodeAR_text_9}, 51 | {VeDirectDeviceCodeAR_code_10, VeDirectDeviceCodeAR_text_10}, 52 | {VeDirectDeviceCodeAR_code_11, VeDirectDeviceCodeAR_text_11}, 53 | {VeDirectDeviceCodeAR_code_12, VeDirectDeviceCodeAR_text_12}, 54 | {VeDirectDeviceCodeAR_code_13, VeDirectDeviceCodeAR_text_13}, 55 | }; 56 | static const size_t VeDirectDeviceCodeARSize = sizeof(VeDirectDeviceCodeAR) / sizeof(VeDirectDeviceCodeAR[0]); 57 | 58 | // --- Mappingtabelle: VeDirectDeviceCodeOR --- 59 | static const char VeDirectDeviceCodeOR_code_0[] PROGMEM = "0X00000000"; 60 | static const char VeDirectDeviceCodeOR_text_0[] PROGMEM = "None"; 61 | static const char VeDirectDeviceCodeOR_code_1[] PROGMEM = "0X00000001"; 62 | static const char VeDirectDeviceCodeOR_text_1[] PROGMEM = "No input power"; 63 | static const char VeDirectDeviceCodeOR_code_2[] PROGMEM = "0X00000002"; 64 | static const char VeDirectDeviceCodeOR_text_2[] PROGMEM = "Switched off (power switch)"; 65 | static const char VeDirectDeviceCodeOR_code_3[] PROGMEM = "0X00000004"; 66 | static const char VeDirectDeviceCodeOR_text_3[] PROGMEM = "Switched off (device mode register)"; 67 | static const char VeDirectDeviceCodeOR_code_4[] PROGMEM = "0X00000008"; 68 | static const char VeDirectDeviceCodeOR_text_4[] PROGMEM = "Remote input"; 69 | static const char VeDirectDeviceCodeOR_code_5[] PROGMEM = "0X00000010"; 70 | static const char VeDirectDeviceCodeOR_text_5[] PROGMEM = "Protection active"; 71 | static const char VeDirectDeviceCodeOR_code_6[] PROGMEM = "0X00000020"; 72 | static const char VeDirectDeviceCodeOR_text_6[] PROGMEM = "Paygo"; 73 | static const char VeDirectDeviceCodeOR_code_7[] PROGMEM = "0X00000040"; 74 | static const char VeDirectDeviceCodeOR_text_7[] PROGMEM = "BMS"; 75 | static const char VeDirectDeviceCodeOR_code_8[] PROGMEM = "0X00000080"; 76 | static const char VeDirectDeviceCodeOR_text_8[] PROGMEM = "Engine shutdown detection"; 77 | static const char VeDirectDeviceCodeOR_code_9[] PROGMEM = "0X00000100"; 78 | static const char VeDirectDeviceCodeOR_text_9[] PROGMEM = "Analysing input voltage"; 79 | 80 | static const VeCodeEntry VeDirectDeviceCodeOR[] PROGMEM = { 81 | {VeDirectDeviceCodeOR_code_0, VeDirectDeviceCodeOR_text_0}, 82 | {VeDirectDeviceCodeOR_code_1, VeDirectDeviceCodeOR_text_1}, 83 | {VeDirectDeviceCodeOR_code_2, VeDirectDeviceCodeOR_text_2}, 84 | {VeDirectDeviceCodeOR_code_3, VeDirectDeviceCodeOR_text_3}, 85 | {VeDirectDeviceCodeOR_code_4, VeDirectDeviceCodeOR_text_4}, 86 | {VeDirectDeviceCodeOR_code_5, VeDirectDeviceCodeOR_text_5}, 87 | {VeDirectDeviceCodeOR_code_6, VeDirectDeviceCodeOR_text_6}, 88 | {VeDirectDeviceCodeOR_code_7, VeDirectDeviceCodeOR_text_7}, 89 | {VeDirectDeviceCodeOR_code_8, VeDirectDeviceCodeOR_text_8}, 90 | {VeDirectDeviceCodeOR_code_9, VeDirectDeviceCodeOR_text_9}, 91 | }; 92 | static const size_t VeDirectDeviceCodeORSize = sizeof(VeDirectDeviceCodeOR) / sizeof(VeDirectDeviceCodeOR[0]); 93 | 94 | // --- Mappingtabelle: VeDirectDeviceCodeCS --- 95 | static const char VeDirectDeviceCodeCS_code_0[] PROGMEM = "0"; 96 | static const char VeDirectDeviceCodeCS_text_0[] PROGMEM = "Off"; 97 | static const char VeDirectDeviceCodeCS_code_1[] PROGMEM = "1"; 98 | static const char VeDirectDeviceCodeCS_text_1[] PROGMEM = "Low power"; 99 | static const char VeDirectDeviceCodeCS_code_2[] PROGMEM = "2"; 100 | static const char VeDirectDeviceCodeCS_text_2[] PROGMEM = "Fault"; 101 | static const char VeDirectDeviceCodeCS_code_3[] PROGMEM = "3"; 102 | static const char VeDirectDeviceCodeCS_text_3[] PROGMEM = "Bulk"; 103 | static const char VeDirectDeviceCodeCS_code_4[] PROGMEM = "4"; 104 | static const char VeDirectDeviceCodeCS_text_4[] PROGMEM = "Absorption"; 105 | static const char VeDirectDeviceCodeCS_code_5[] PROGMEM = "5"; 106 | static const char VeDirectDeviceCodeCS_text_5[] PROGMEM = "Float"; 107 | static const char VeDirectDeviceCodeCS_code_6[] PROGMEM = "6"; 108 | static const char VeDirectDeviceCodeCS_text_6[] PROGMEM = "Storage"; 109 | static const char VeDirectDeviceCodeCS_code_7[] PROGMEM = "7"; 110 | static const char VeDirectDeviceCodeCS_text_7[] PROGMEM = "Equalize (manual)"; 111 | static const char VeDirectDeviceCodeCS_code_8[] PROGMEM = "9"; 112 | static const char VeDirectDeviceCodeCS_text_8[] PROGMEM = "Inverting"; 113 | static const char VeDirectDeviceCodeCS_code_9[] PROGMEM = "11"; 114 | static const char VeDirectDeviceCodeCS_text_9[] PROGMEM = "Power supply"; 115 | static const char VeDirectDeviceCodeCS_code_10[] PROGMEM = "245"; 116 | static const char VeDirectDeviceCodeCS_text_10[] PROGMEM = "Starting-up"; 117 | static const char VeDirectDeviceCodeCS_code_11[] PROGMEM = "246"; 118 | static const char VeDirectDeviceCodeCS_text_11[] PROGMEM = "Repeated absorption"; 119 | static const char VeDirectDeviceCodeCS_code_12[] PROGMEM = "247"; 120 | static const char VeDirectDeviceCodeCS_text_12[] PROGMEM = "Auto equalize / Recondition"; 121 | static const char VeDirectDeviceCodeCS_code_13[] PROGMEM = "248"; 122 | static const char VeDirectDeviceCodeCS_text_13[] PROGMEM = "BatterySafe"; 123 | static const char VeDirectDeviceCodeCS_code_14[] PROGMEM = "252"; 124 | static const char VeDirectDeviceCodeCS_text_14[] PROGMEM = "External Control"; 125 | 126 | static const VeCodeEntry VeDirectDeviceCodeCS[] PROGMEM = { 127 | {VeDirectDeviceCodeCS_code_0, VeDirectDeviceCodeCS_text_0}, 128 | {VeDirectDeviceCodeCS_code_1, VeDirectDeviceCodeCS_text_1}, 129 | {VeDirectDeviceCodeCS_code_2, VeDirectDeviceCodeCS_text_2}, 130 | {VeDirectDeviceCodeCS_code_3, VeDirectDeviceCodeCS_text_3}, 131 | {VeDirectDeviceCodeCS_code_4, VeDirectDeviceCodeCS_text_4}, 132 | {VeDirectDeviceCodeCS_code_5, VeDirectDeviceCodeCS_text_5}, 133 | {VeDirectDeviceCodeCS_code_6, VeDirectDeviceCodeCS_text_6}, 134 | {VeDirectDeviceCodeCS_code_7, VeDirectDeviceCodeCS_text_7}, 135 | {VeDirectDeviceCodeCS_code_8, VeDirectDeviceCodeCS_text_8}, 136 | {VeDirectDeviceCodeCS_code_9, VeDirectDeviceCodeCS_text_9}, 137 | {VeDirectDeviceCodeCS_code_10, VeDirectDeviceCodeCS_text_10}, 138 | {VeDirectDeviceCodeCS_code_11, VeDirectDeviceCodeCS_text_11}, 139 | {VeDirectDeviceCodeCS_code_12, VeDirectDeviceCodeCS_text_12}, 140 | {VeDirectDeviceCodeCS_code_13, VeDirectDeviceCodeCS_text_13}, 141 | {VeDirectDeviceCodeCS_code_14, VeDirectDeviceCodeCS_text_14}, 142 | }; 143 | static const size_t VeDirectDeviceCodeCSSize = sizeof(VeDirectDeviceCodeCS) / sizeof(VeDirectDeviceCodeCS[0]); 144 | 145 | // --- Mappingtabelle: VeDirectDeviceCodeERR --- 146 | static const char VeDirectDeviceCodeERR_code_0[] PROGMEM = "0"; 147 | static const char VeDirectDeviceCodeERR_text_0[] PROGMEM = "No error"; 148 | static const char VeDirectDeviceCodeERR_code_1[] PROGMEM = "2"; 149 | static const char VeDirectDeviceCodeERR_text_1[] PROGMEM = "Battery voltage too high"; 150 | static const char VeDirectDeviceCodeERR_code_2[] PROGMEM = "17"; 151 | static const char VeDirectDeviceCodeERR_text_2[] PROGMEM = "Charger temperature too high"; 152 | static const char VeDirectDeviceCodeERR_code_3[] PROGMEM = "18"; 153 | static const char VeDirectDeviceCodeERR_text_3[] PROGMEM = "Charger over current"; 154 | static const char VeDirectDeviceCodeERR_code_4[] PROGMEM = "19"; 155 | static const char VeDirectDeviceCodeERR_text_4[] PROGMEM = "Charger current reversed"; 156 | static const char VeDirectDeviceCodeERR_code_5[] PROGMEM = "20"; 157 | static const char VeDirectDeviceCodeERR_text_5[] PROGMEM = "Bulk time limit exceeded"; 158 | static const char VeDirectDeviceCodeERR_code_6[] PROGMEM = "21"; 159 | static const char VeDirectDeviceCodeERR_text_6[] PROGMEM = "Current sensor issue (sensor bias/sensor broken)"; 160 | static const char VeDirectDeviceCodeERR_code_7[] PROGMEM = "26"; 161 | static const char VeDirectDeviceCodeERR_text_7[] PROGMEM = "Terminals overheated"; 162 | static const char VeDirectDeviceCodeERR_code_8[] PROGMEM = "28"; 163 | static const char VeDirectDeviceCodeERR_text_8[] PROGMEM = "Converter issue"; 164 | static const char VeDirectDeviceCodeERR_code_9[] PROGMEM = "33"; 165 | static const char VeDirectDeviceCodeERR_text_9[] PROGMEM = "Input voltage too high (solar panel)"; 166 | static const char VeDirectDeviceCodeERR_code_10[] PROGMEM = "34"; 167 | static const char VeDirectDeviceCodeERR_text_10[] PROGMEM = "Input current too high (solar panel)"; 168 | static const char VeDirectDeviceCodeERR_code_11[] PROGMEM = "38"; 169 | static const char VeDirectDeviceCodeERR_text_11[] PROGMEM = "Input shutdown (excessive battery voltage)"; 170 | static const char VeDirectDeviceCodeERR_code_12[] PROGMEM = "39"; 171 | static const char VeDirectDeviceCodeERR_text_12[] PROGMEM = "Input shutdown (current flow during off mode)"; 172 | static const char VeDirectDeviceCodeERR_code_13[] PROGMEM = "65"; 173 | static const char VeDirectDeviceCodeERR_text_13[] PROGMEM = "Lost communication with one of devices"; 174 | static const char VeDirectDeviceCodeERR_code_14[] PROGMEM = "66"; 175 | static const char VeDirectDeviceCodeERR_text_14[] PROGMEM = "Synchronised charging device configuration issue"; 176 | static const char VeDirectDeviceCodeERR_code_15[] PROGMEM = "67"; 177 | static const char VeDirectDeviceCodeERR_text_15[] PROGMEM = "BMS connection lost"; 178 | static const char VeDirectDeviceCodeERR_code_16[] PROGMEM = "68"; 179 | static const char VeDirectDeviceCodeERR_text_16[] PROGMEM = "Network misconfigured"; 180 | static const char VeDirectDeviceCodeERR_code_17[] PROGMEM = "116"; 181 | static const char VeDirectDeviceCodeERR_text_17[] PROGMEM = "Factory calibration data lost"; 182 | static const char VeDirectDeviceCodeERR_code_18[] PROGMEM = "117"; 183 | static const char VeDirectDeviceCodeERR_text_18[] PROGMEM = "Invalid/incompatible firmware"; 184 | static const char VeDirectDeviceCodeERR_code_19[] PROGMEM = "119"; 185 | static const char VeDirectDeviceCodeERR_text_19[] PROGMEM = "User settings invalid"; 186 | 187 | static const VeCodeEntry VeDirectDeviceCodeERR[] PROGMEM = { 188 | {VeDirectDeviceCodeERR_code_0, VeDirectDeviceCodeERR_text_0}, 189 | {VeDirectDeviceCodeERR_code_1, VeDirectDeviceCodeERR_text_1}, 190 | {VeDirectDeviceCodeERR_code_2, VeDirectDeviceCodeERR_text_2}, 191 | {VeDirectDeviceCodeERR_code_3, VeDirectDeviceCodeERR_text_3}, 192 | {VeDirectDeviceCodeERR_code_4, VeDirectDeviceCodeERR_text_4}, 193 | {VeDirectDeviceCodeERR_code_5, VeDirectDeviceCodeERR_text_5}, 194 | {VeDirectDeviceCodeERR_code_6, VeDirectDeviceCodeERR_text_6}, 195 | {VeDirectDeviceCodeERR_code_7, VeDirectDeviceCodeERR_text_7}, 196 | {VeDirectDeviceCodeERR_code_8, VeDirectDeviceCodeERR_text_8}, 197 | {VeDirectDeviceCodeERR_code_9, VeDirectDeviceCodeERR_text_9}, 198 | {VeDirectDeviceCodeERR_code_10, VeDirectDeviceCodeERR_text_10}, 199 | {VeDirectDeviceCodeERR_code_11, VeDirectDeviceCodeERR_text_11}, 200 | {VeDirectDeviceCodeERR_code_12, VeDirectDeviceCodeERR_text_12}, 201 | {VeDirectDeviceCodeERR_code_13, VeDirectDeviceCodeERR_text_13}, 202 | {VeDirectDeviceCodeERR_code_14, VeDirectDeviceCodeERR_text_14}, 203 | {VeDirectDeviceCodeERR_code_15, VeDirectDeviceCodeERR_text_15}, 204 | {VeDirectDeviceCodeERR_code_16, VeDirectDeviceCodeERR_text_16}, 205 | {VeDirectDeviceCodeERR_code_17, VeDirectDeviceCodeERR_text_17}, 206 | {VeDirectDeviceCodeERR_code_18, VeDirectDeviceCodeERR_text_18}, 207 | {VeDirectDeviceCodeERR_code_19, VeDirectDeviceCodeERR_text_19}, 208 | }; 209 | static const size_t VeDirectDeviceCodeERRSize = sizeof(VeDirectDeviceCodeERR) / sizeof(VeDirectDeviceCodeERR[0]); 210 | 211 | // --- Mappingtabelle: VeDirectDeviceCodeMPPT --- 212 | static const char VeDirectDeviceCodeMPPT_code_0[] PROGMEM = "0"; 213 | static const char VeDirectDeviceCodeMPPT_text_0[] PROGMEM = "Off"; 214 | static const char VeDirectDeviceCodeMPPT_code_1[] PROGMEM = "1"; 215 | static const char VeDirectDeviceCodeMPPT_text_1[] PROGMEM = "Voltage or current limited"; 216 | static const char VeDirectDeviceCodeMPPT_code_2[] PROGMEM = "2"; 217 | static const char VeDirectDeviceCodeMPPT_text_2[] PROGMEM = "MPP Tracker active"; 218 | 219 | static const VeCodeEntry VeDirectDeviceCodeMPPT[] PROGMEM = { 220 | {VeDirectDeviceCodeMPPT_code_0, VeDirectDeviceCodeMPPT_text_0}, 221 | {VeDirectDeviceCodeMPPT_code_1, VeDirectDeviceCodeMPPT_text_1}, 222 | {VeDirectDeviceCodeMPPT_code_2, VeDirectDeviceCodeMPPT_text_2}, 223 | }; 224 | static const size_t VeDirectDeviceCodeMPPTSize = sizeof(VeDirectDeviceCodeMPPT) / sizeof(VeDirectDeviceCodeMPPT[0]); 225 | 226 | 227 | 228 | 229 | 230 | 231 | /* 232 | 233 | 234 | // AR (Alarm_code) 235 | static const char * VeDirectDeviceCodeAR[][2] PROGMEM{ 236 | {"1", "Low Voltage"}, 237 | {"2", "High Voltage"}, 238 | {"4", "Low SOC"}, 239 | {"8", "Low Starter Voltage"}, 240 | {"16", "High Starter Voltage"}, 241 | {"32", "Low Temperature"}, 242 | {"64", "High Temperature"}, 243 | {"128", "Mid Voltage"}, 244 | {"256", "Overload"}, 245 | {"512", "DC-ripple"}, 246 | {"1024", "Low V AC out"}, 247 | {"2048", "LHigh V AC out"}, 248 | {"4096", "Short Circuit"}, 249 | {"8192", "BMS Lockout"}}; 250 | 251 | // OR (off_reason) 252 | static const char * VeDirectDeviceCodeOR[][2] PROGMEM{ 253 | {"0X00000000", "None"}, 254 | {"0X00000001", "No input power"}, 255 | {"0X00000002", "Switched off (power switch)"}, 256 | {"0X00000004", "Switched off (device mode register)"}, 257 | {"0X00000008", "Remote input"}, 258 | {"0X00000010", "Protection active"}, 259 | {"0X00000020", "Paygo"}, 260 | {"0X00000040", "BMS"}, 261 | {"0X00000080", "Engine shutdown detection"}, 262 | {"0X00000100", "Analysing input voltage"}, 263 | }; 264 | // CS (operation_state) 265 | static const char * VeDirectDeviceCodeCS[][2] PROGMEM{ 266 | {"0", "Off"}, 267 | {"1", "Low power"}, 268 | {"2", "Fault"}, 269 | {"3", "Bulk"}, 270 | {"4", "Absorption"}, 271 | {"5", "Float"}, 272 | {"6", "Storage"}, 273 | {"7", "Equalize (manual)"}, 274 | {"9", "Inverting"}, 275 | {"11", "Power supply"}, 276 | {"245", "Starting-up"}, 277 | {"246", "Repeated absorption"}, 278 | {"247", "Auto equalize / Recondition"}, 279 | {"248", "BatterySafe"}, 280 | {"252", "External Control"}}; 281 | 282 | // ERR (Current_error) 283 | static const char * VeDirectDeviceCodeERR[][2] PROGMEM{ 284 | {"0", "No error"}, 285 | {"2", "Battery voltage too high"}, 286 | {"17", "Charger temperature too high"}, 287 | {"18", "Charger over current"}, 288 | {"19", "Charger current reversed"}, 289 | {"20", "Bulk time limit exceeded"}, 290 | {"21", "Current sensor issue (sensor bias/sensor broken)"}, 291 | {"26", "Terminals overheated"}, 292 | {"28", "Converter issue"}, 293 | {"33", "Input voltage too high (solar panel)"}, 294 | {"34", "Input current too high (solar panel)"}, 295 | {"38", "Input shutdown (excessive battery voltage)"}, 296 | {"39", "Input shutdown (current flow during off mode)"}, 297 | {"65", "Lost communication with one of devices"}, 298 | {"66", "Synchronised charging device configuration issue"}, 299 | {"67", "BMS connection lost"}, 300 | {"68", "Network misconfigured"}, 301 | {"116", "Factory calibration data lost"}, 302 | {"117", "Invalid/incompatible firmware"}, 303 | {"119", "User settings invalid"}}; 304 | 305 | // MPPT (Tracker_operation_mode) 306 | static const char * VeDirectDeviceCodeMPPT[][2] PROGMEM{ 307 | {"0", "Off"}, 308 | {"1", "Voltage or current limited"}, 309 | {"2", "MPP Tracker active"}}; */ -------------------------------------------------------------------------------- /src/VeDirectDeviceList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include <Arduino.h> 3 | struct VeDeviceEntry { 4 | uint16_t id; 5 | const char* name; 6 | }; 7 | const char name_203[] PROGMEM = "BMV-700"; 8 | const char name_204[] PROGMEM = "BMV-702"; 9 | const char name_205[] PROGMEM = "BMV-700H"; 10 | const char name_0300[] PROGMEM = "BlueSolar MPPT 70|15"; 11 | const char name_A040[] PROGMEM = "BlueSolar MPPT 75|50"; 12 | const char name_A041[] PROGMEM = "BlueSolar MPPT 150|35"; 13 | const char name_A042[] PROGMEM = "BlueSolar MPPT 75|15"; 14 | const char name_A043[] PROGMEM = "BlueSolar MPPT 100|15"; 15 | const char name_A044[] PROGMEM = "BlueSolar MPPT 100|30"; 16 | const char name_A045[] PROGMEM = "BlueSolar MPPT 100|50"; 17 | const char name_A046[] PROGMEM = "BlueSolar MPPT 150|70"; 18 | const char name_A047[] PROGMEM = "BlueSolar MPPT 150|100 "; 19 | const char name_A049[] PROGMEM = "BlueSolar MPPT 100|50 rev2"; 20 | const char name_A04A[] PROGMEM = "BlueSolar MPPT 100|30 rev2"; 21 | const char name_A04B[] PROGMEM = "BlueSolar MPPT 150|35"; 22 | const char name_A04C[] PROGMEM = "BlueSolar MPPT 75|10"; 23 | const char name_A04D[] PROGMEM = "BlueSolar MPPT 150|45"; 24 | const char name_A04E[] PROGMEM = "BlueSolar MPPT 150|60"; 25 | const char name_A04F[] PROGMEM = "BlueSolar MPPT 150|85"; 26 | const char name_A050[] PROGMEM = "SmartSolar MPPT 250|100"; 27 | const char name_A051[] PROGMEM = "SmartSolar MPPT 150|100"; 28 | const char name_A052[] PROGMEM = "SmartSolar MPPT 150|85"; 29 | const char name_A053[] PROGMEM = "SmartSolar MPPT 75|15"; 30 | const char name_A054[] PROGMEM = "SmartSolar MPPT 75|10"; 31 | const char name_A055[] PROGMEM = "SmartSolar MPPT 100|15"; 32 | const char name_A056[] PROGMEM = "SmartSolar MPPT 100|30"; 33 | const char name_A057[] PROGMEM = "SmartSolar MPPT 100|50"; 34 | const char name_A058[] PROGMEM = "SmartSolar MPPT 150|35"; 35 | const char name_A059[] PROGMEM = "SmartSolar MPPT 150|100 rev2"; 36 | const char name_A05A[] PROGMEM = "SmartSolar MPPT 150|85 rev2"; 37 | const char name_A05B[] PROGMEM = "SmartSolar MPPT 250|70"; 38 | const char name_A05C[] PROGMEM = "SmartSolar MPPT 250|85"; 39 | const char name_A05D[] PROGMEM = "SmartSolar MPPT 250|60"; 40 | const char name_A05E[] PROGMEM = "SmartSolar MPPT 250|45"; 41 | const char name_A05F[] PROGMEM = "SmartSolar MPPT 100|20"; 42 | const char name_A060[] PROGMEM = "SmartSolar MPPT 100|20 48V"; 43 | const char name_A061[] PROGMEM = "SmartSolar MPPT 150|45"; 44 | const char name_A062[] PROGMEM = "SmartSolar MPPT 150|60"; 45 | const char name_A063[] PROGMEM = "SmartSolar MPPT 150|70"; 46 | const char name_A064[] PROGMEM = "SmartSolar MPPT 250|85 rev2"; 47 | const char name_A065[] PROGMEM = "SmartSolar MPPT 250|100 rev2"; 48 | const char name_A066[] PROGMEM = "BlueSolar MPPT 100|20"; 49 | const char name_A067[] PROGMEM = "BlueSolar MPPT 100|20 48V"; 50 | const char name_A068[] PROGMEM = "SmartSolar MPPT 250|60 rev2"; 51 | const char name_A069[] PROGMEM = "SmartSolar MPPT 250|70 rev2"; 52 | const char name_A06A[] PROGMEM = "SmartSolar MPPT 150|45 rev2"; 53 | const char name_A06B[] PROGMEM = "SmartSolar MPPT 150|60 rev2"; 54 | const char name_A06C[] PROGMEM = "SmartSolar MPPT 150|70 rev2"; 55 | const char name_A06D[] PROGMEM = "SmartSolar MPPT 150|85 rev3"; 56 | const char name_A06E[] PROGMEM = "SmartSolar MPPT 150|100 rev3"; 57 | const char name_A06F[] PROGMEM = "BlueSolar MPPT 150|45 rev2"; 58 | const char name_A070[] PROGMEM = "BlueSolar MPPT 150|60 rev2"; 59 | const char name_A071[] PROGMEM = "BlueSolar MPPT 150|70 rev2"; 60 | const char name_A072[] PROGMEM = "BlueSolar MPPT 150/45 rev3"; 61 | const char name_A073[] PROGMEM = "SmartSolar MPPT 150/45 rev3"; 62 | const char name_A074[] PROGMEM = "SmartSolar MPPT 75/10 rev2"; 63 | const char name_A075[] PROGMEM = "SmartSolar MPPT 75/15 rev2"; 64 | const char name_A076[] PROGMEM = "BlueSolar MPPT 100/30 rev3"; 65 | const char name_A077[] PROGMEM = "BlueSolar MPPT 100/50 rev3"; 66 | const char name_A078[] PROGMEM = "BlueSolar MPPT 150/35 rev3"; 67 | const char name_A079[] PROGMEM = "BlueSolar MPPT 75/10 rev2"; 68 | const char name_A07A[] PROGMEM = "BlueSolar MPPT 75/15 rev2"; 69 | const char name_A07B[] PROGMEM = "BlueSolar MPPT 100/15 rev2"; 70 | const char name_A07C[] PROGMEM = "BlueSolar MPPT 75/10 rev3"; 71 | const char name_A07D[] PROGMEM = "BlueSolar MPPT 75/15 rev3"; 72 | const char name_A07E[] PROGMEM = "SmartSolar MPPT 100/30 12V"; 73 | const char name_A07F[] PROGMEM = "All-In-1 SmartSolar MPPT 75/15 12V"; 74 | const char name_A102[] PROGMEM = "SmartSolar MPPT VE.Can 150/70"; 75 | const char name_A103[] PROGMEM = "SmartSolar MPPT VE.Can 150/45"; 76 | const char name_A104[] PROGMEM = "SmartSolar MPPT VE.Can 150/60"; 77 | const char name_A105[] PROGMEM = "SmartSolar MPPT VE.Can 150/85"; 78 | const char name_A106[] PROGMEM = "SmartSolar MPPT VE.Can 150/100"; 79 | const char name_A107[] PROGMEM = "SmartSolar MPPT VE.Can 250/45"; 80 | const char name_A108[] PROGMEM = "SmartSolar MPPT VE.Can 250/60"; 81 | const char name_A109[] PROGMEM = "SmartSolar MPPT VE.Can 250/70"; 82 | const char name_A10A[] PROGMEM = "SmartSolar MPPT VE.Can 250/85"; 83 | const char name_A10B[] PROGMEM = "SmartSolar MPPT VE.Can 250/100"; 84 | const char name_A10C[] PROGMEM = "SmartSolar MPPT VE.Can 150/70 rev2"; 85 | const char name_A10D[] PROGMEM = "SmartSolar MPPT VE.Can 150/85 rev2"; 86 | const char name_A10E[] PROGMEM = "SmartSolar MPPT VE.Can 150/100 rev2"; 87 | const char name_A10F[] PROGMEM = "BlueSolar MPPT VE.Can 150/100"; 88 | const char name_A112[] PROGMEM = "BlueSolar MPPT VE.Can 250/70"; 89 | const char name_A113[] PROGMEM = "BlueSolar MPPT VE.Can 250/100"; 90 | const char name_A114[] PROGMEM = "SmartSolar MPPT VE.Can 250/70 rev2"; 91 | const char name_A115[] PROGMEM = "SmartSolar MPPT VE.Can 250/100 rev2"; 92 | const char name_A116[] PROGMEM = "SmartSolar MPPT VE.Can 250/85 rev2"; 93 | const char name_A117[] PROGMEM = "BlueSolar MPPT VE.Can 150/100 rev2"; 94 | const char name_A201[] PROGMEM = "Phoenix Inverter 12V 250VA 230V"; 95 | const char name_A202[] PROGMEM = "Phoenix Inverter 24V 250VA 230V"; 96 | const char name_A204[] PROGMEM = "Phoenix Inverter 48V 250VA 230V"; 97 | const char name_A211[] PROGMEM = "Phoenix Inverter 12V 375VA 230V"; 98 | const char name_A212[] PROGMEM = "Phoenix Inverter 24V 375VA 230V"; 99 | const char name_A214[] PROGMEM = "Phoenix Inverter 48V 375VA 230V"; 100 | const char name_A221[] PROGMEM = "Phoenix Inverter 12V 500VA 230V"; 101 | const char name_A222[] PROGMEM = "Phoenix Inverter 24V 500VA 230V"; 102 | const char name_A224[] PROGMEM = "Phoenix Inverter 48V 500VA 230V"; 103 | const char name_A231[] PROGMEM = "Phoenix Inverter 12V 250VA 230V"; 104 | const char name_A232[] PROGMEM = "Phoenix Inverter 24V 250VA 230V"; 105 | const char name_A234[] PROGMEM = "Phoenix Inverter 48V 250VA 230V"; 106 | const char name_A239[] PROGMEM = "Phoenix Inverter 12V 250VA 120V"; 107 | const char name_A23A[] PROGMEM = "Phoenix Inverter 24V 250VA 120V"; 108 | const char name_A23C[] PROGMEM = "Phoenix Inverter 48V 250VA 120V"; 109 | const char name_A241[] PROGMEM = "Phoenix Inverter 12V 375VA 230V"; 110 | const char name_A242[] PROGMEM = "Phoenix Inverter 24V 375VA 230V"; 111 | const char name_A244[] PROGMEM = "Phoenix Inverter 48V 375VA 230V"; 112 | const char name_A249[] PROGMEM = "Phoenix Inverter 12V 375VA 120V"; 113 | const char name_A24A[] PROGMEM = "Phoenix Inverter 24V 375VA 120V"; 114 | const char name_A24C[] PROGMEM = "Phoenix Inverter 48V 375VA 120V"; 115 | const char name_A251[] PROGMEM = "Phoenix Inverter 12V 500VA 230V"; 116 | const char name_A252[] PROGMEM = "Phoenix Inverter 24V 500VA 230V"; 117 | const char name_A254[] PROGMEM = "Phoenix Inverter 48V 500VA 230V"; 118 | const char name_A259[] PROGMEM = "Phoenix Inverter 12V 500VA 120V"; 119 | const char name_A25A[] PROGMEM = "Phoenix Inverter 24V 500VA 120V"; 120 | const char name_A25C[] PROGMEM = "Phoenix Inverter 48V 500VA 120V"; 121 | const char name_A261[] PROGMEM = "Phoenix Inverter 12V 800VA 230V"; 122 | const char name_A262[] PROGMEM = "Phoenix Inverter 24V 800VA 230V"; 123 | const char name_A264[] PROGMEM = "Phoenix Inverter 48V 800VA 230V"; 124 | const char name_A269[] PROGMEM = "Phoenix Inverter 12V 800VA 120V"; 125 | const char name_A26A[] PROGMEM = "Phoenix Inverter 24V 800VA 120V"; 126 | const char name_A26C[] PROGMEM = "Phoenix Inverter 48V 800VA 120V"; 127 | const char name_A271[] PROGMEM = "Phoenix Inverter 12V 1200VA 230V"; 128 | const char name_A272[] PROGMEM = "Phoenix Inverter 24V 1200VA 230V"; 129 | const char name_A274[] PROGMEM = "Phoenix Inverter 48V 1200VA 230V"; 130 | const char name_A279[] PROGMEM = "Phoenix Inverter 12V 1200VA 120V"; 131 | const char name_A27A[] PROGMEM = "Phoenix Inverter 24V 1200VA 120V"; 132 | const char name_A27C[] PROGMEM = "Phoenix Inverter 48V 1200VA 120V"; 133 | const char name_A281[] PROGMEM = "Phoenix Inverter 12V 1600VA 230V"; 134 | const char name_A282[] PROGMEM = "Phoenix Inverter 24V 1600VA 230V"; 135 | const char name_A284[] PROGMEM = "Phoenix Inverter 48V 1600VA 230V"; 136 | const char name_A291[] PROGMEM = "Phoenix Inverter 12V 2000VA 230V"; 137 | const char name_A292[] PROGMEM = "Phoenix Inverter 24V 2000VA 230V"; 138 | const char name_A294[] PROGMEM = "Phoenix Inverter 48V 2000VA 230V"; 139 | const char name_A2A1[] PROGMEM = "Phoenix Inverter 12V 3000VA 230V"; 140 | const char name_A2A2[] PROGMEM = "Phoenix Inverter 24V 3000VA 230V"; 141 | const char name_A2A4[] PROGMEM = "Phoenix Inverter 48V 3000VA 230V"; 142 | const char name_A340[] PROGMEM = "Phoenix Smart IP43 Charger 12|50 (1+1)"; 143 | const char name_A341[] PROGMEM = "Phoenix Smart IP43 Charger 12|50 (3)"; 144 | const char name_A342[] PROGMEM = "Phoenix Smart IP43 Charger 24|25 (1+1)"; 145 | const char name_A343[] PROGMEM = "Phoenix Smart IP43 Charger 24|25 (3)"; 146 | const char name_A344[] PROGMEM = "Phoenix Smart IP43 Charger 12|30 (1+1)"; 147 | const char name_A345[] PROGMEM = "Phoenix Smart IP43 Charger 12|30 (3)"; 148 | const char name_A346[] PROGMEM = "Phoenix Smart IP43 Charger 24|16 (1+1)"; 149 | const char name_A347[] PROGMEM = "Phoenix Smart IP43 Charger 24|16 (3)"; 150 | const char name_A381[] PROGMEM = "BMV-712 Smart"; 151 | const char name_A382[] PROGMEM = "BMV-710H Smart"; 152 | const char name_A383[] PROGMEM = "BMV-712 Smart Rev2"; 153 | const char name_C038[] PROGMEM = "SmartShunt 300A/50mV"; 154 | const char name_A389[] PROGMEM = "SmartShunt 500A/50mV"; 155 | const char name_A38A[] PROGMEM = "SmartShunt 1000A/50mV"; 156 | const char name_A38B[] PROGMEM = "SmartShunt 2000A/50mV"; 157 | const char name_A3F0[] PROGMEM = "Smart BuckBoost 12V/12V-50A"; 158 | const char name_C030[] PROGMEM = "SmartShunt 500A/50mV IP65"; 159 | const char name_9999[] PROGMEM = "The migthy simluator"; 160 | 161 | const VeDeviceEntry VeDeviceList[] PROGMEM = { 162 | {0x0203, name_203}, 163 | {0x0204, name_204}, 164 | {0x0205, name_205}, 165 | {0x0300, name_0300}, 166 | {0x9999, name_9999}, 167 | {0xA040, name_A040}, 168 | {0xA041, name_A041}, 169 | {0xA042, name_A042}, 170 | {0xA043, name_A043}, 171 | {0xA044, name_A044}, 172 | {0xA045, name_A045}, 173 | {0xA046, name_A046}, 174 | {0xA047, name_A047}, 175 | {0xA049, name_A049}, 176 | {0xA04A, name_A04A}, 177 | {0xA04B, name_A04B}, 178 | {0xA04C, name_A04C}, 179 | {0xA04D, name_A04D}, 180 | {0xA04E, name_A04E}, 181 | {0xA04F, name_A04F}, 182 | {0xA050, name_A050}, 183 | {0xA051, name_A051}, 184 | {0xA052, name_A052}, 185 | {0xA053, name_A053}, 186 | {0xA054, name_A054}, 187 | {0xA055, name_A055}, 188 | {0xA056, name_A056}, 189 | {0xA057, name_A057}, 190 | {0xA058, name_A058}, 191 | {0xA059, name_A059}, 192 | {0xA05A, name_A05A}, 193 | {0xA05B, name_A05B}, 194 | {0xA05C, name_A05C}, 195 | {0xA05D, name_A05D}, 196 | {0xA05E, name_A05E}, 197 | {0xA05F, name_A05F}, 198 | {0xA060, name_A060}, 199 | {0xA061, name_A061}, 200 | {0xA062, name_A062}, 201 | {0xA063, name_A063}, 202 | {0xA064, name_A064}, 203 | {0xA065, name_A065}, 204 | {0xA066, name_A066}, 205 | {0xA067, name_A067}, 206 | {0xA068, name_A068}, 207 | {0xA069, name_A069}, 208 | {0xA06A, name_A06A}, 209 | {0xA06B, name_A06B}, 210 | {0xA06C, name_A06C}, 211 | {0xA06D, name_A06D}, 212 | {0xA06E, name_A06E}, 213 | {0xA06F, name_A06F}, 214 | {0xA070, name_A070}, 215 | {0xA071, name_A071}, 216 | {0xA072, name_A072}, 217 | {0xA073, name_A073}, 218 | {0xA074, name_A074}, 219 | {0xA075, name_A075}, 220 | {0xA076, name_A076}, 221 | {0xA077, name_A077}, 222 | {0xA078, name_A078}, 223 | {0xA079, name_A079}, 224 | {0xA07A, name_A07A}, 225 | {0xA07B, name_A07B}, 226 | {0xA07C, name_A07C}, 227 | {0xA07D, name_A07D}, 228 | {0xA07E, name_A07E}, 229 | {0xA07F, name_A07F}, 230 | {0xA102, name_A102}, 231 | {0xA103, name_A103}, 232 | {0xA104, name_A104}, 233 | {0xA105, name_A105}, 234 | {0xA106, name_A106}, 235 | {0xA107, name_A107}, 236 | {0xA108, name_A108}, 237 | {0xA109, name_A109}, 238 | {0xA10A, name_A10A}, 239 | {0xA10B, name_A10B}, 240 | {0xA10C, name_A10C}, 241 | {0xA10D, name_A10D}, 242 | {0xA10E, name_A10E}, 243 | {0xA10F, name_A10F}, 244 | {0xA112, name_A112}, 245 | {0xA113, name_A113}, 246 | {0xA114, name_A114}, 247 | {0xA115, name_A115}, 248 | {0xA116, name_A116}, 249 | {0xA117, name_A117}, 250 | {0xA201, name_A201}, 251 | {0xA202, name_A202}, 252 | {0xA204, name_A204}, 253 | {0xA211, name_A211}, 254 | {0xA212, name_A212}, 255 | {0xA214, name_A214}, 256 | {0xA221, name_A221}, 257 | {0xA222, name_A222}, 258 | {0xA224, name_A224}, 259 | {0xA231, name_A231}, 260 | {0xA232, name_A232}, 261 | {0xA234, name_A234}, 262 | {0xA239, name_A239}, 263 | {0xA23A, name_A23A}, 264 | {0xA23C, name_A23C}, 265 | {0xA241, name_A241}, 266 | {0xA242, name_A242}, 267 | {0xA244, name_A244}, 268 | {0xA249, name_A249}, 269 | {0xA24A, name_A24A}, 270 | {0xA24C, name_A24C}, 271 | {0xA251, name_A251}, 272 | {0xA252, name_A252}, 273 | {0xA254, name_A254}, 274 | {0xA259, name_A259}, 275 | {0xA25A, name_A25A}, 276 | {0xA25C, name_A25C}, 277 | {0xA261, name_A261}, 278 | {0xA262, name_A262}, 279 | {0xA264, name_A264}, 280 | {0xA269, name_A269}, 281 | {0xA26A, name_A26A}, 282 | {0xA26C, name_A26C}, 283 | {0xA271, name_A271}, 284 | {0xA272, name_A272}, 285 | {0xA274, name_A274}, 286 | {0xA279, name_A279}, 287 | {0xA27A, name_A27A}, 288 | {0xA27C, name_A27C}, 289 | {0xA281, name_A281}, 290 | {0xA282, name_A282}, 291 | {0xA284, name_A284}, 292 | {0xA291, name_A291}, 293 | {0xA292, name_A292}, 294 | {0xA294, name_A294}, 295 | {0xA2A1, name_A2A1}, 296 | {0xA2A2, name_A2A2}, 297 | {0xA2A4, name_A2A4}, 298 | {0xA340, name_A340}, 299 | {0xA341, name_A341}, 300 | {0xA342, name_A342}, 301 | {0xA343, name_A343}, 302 | {0xA344, name_A344}, 303 | {0xA345, name_A345}, 304 | {0xA346, name_A346}, 305 | {0xA347, name_A347}, 306 | {0xA381, name_A381}, 307 | {0xA382, name_A382}, 308 | {0xA383, name_A383}, 309 | {0xA389, name_A389}, 310 | {0xA38A, name_A38A}, 311 | {0xA38B, name_A38B}, 312 | {0xA3F0, name_A3F0}, 313 | {0xC030, name_C030}, 314 | {0xC038, name_C038}, 315 | }; 316 | 317 | const size_t VeDeviceListSize = sizeof(VeDeviceList) / sizeof(VeDeviceList[0]); 318 | -------------------------------------------------------------------------------- /src/VeDirectFrameHandler.cpp: -------------------------------------------------------------------------------- 1 | #include <Arduino.h> 2 | #include "VeDirectFrameHandler.h" 3 | 4 | extern void writeLog(const char* format, ...); 5 | // The name of the record that contains the checksum. 6 | static constexpr char checksumTagName[] = "CHECKSUM"; 7 | 8 | /* 9 | * rxData 10 | * This function is called by the application which passes a byte of serial data 11 | * It is unchanged from Victron's example code 12 | */ 13 | void VeDirectFrameHandler::rxData(uint8_t inbyte) 14 | { 15 | //Serial.write(inbyte); 16 | if ((inbyte == ':') && (mState != CHECKSUM)) 17 | { 18 | mState = RECORD_HEX; 19 | } 20 | if (mState != RECORD_HEX) 21 | { 22 | mChecksum += inbyte; 23 | } 24 | inbyte = toupper(inbyte); 25 | 26 | switch (mState) 27 | { 28 | case IDLE: 29 | /* wait for \n of the start of an record */ 30 | switch (inbyte) 31 | { 32 | case '\n': 33 | mState = RECORD_BEGIN; 34 | break; 35 | case '\r': /* Skip */ 36 | default: 37 | break; 38 | } 39 | break; 40 | case RECORD_BEGIN: 41 | mTextPointer = mName; 42 | *mTextPointer++ = inbyte; 43 | mState = RECORD_NAME; 44 | break; 45 | case RECORD_NAME: 46 | // The record name is being received, terminated by a \t 47 | switch (inbyte) 48 | { 49 | case '\t': 50 | // the Checksum record indicates a EOR 51 | if (mTextPointer < (mName + sizeof(mName))) 52 | { 53 | *mTextPointer = 0; /* Zero terminate */ 54 | //recordNameCallback(true, mName); 55 | if (strcmp(mName, checksumTagName) == 0) 56 | { 57 | mState = CHECKSUM; 58 | break; 59 | } 60 | } else { 61 | //recordNameCallback(false, nullptr); 62 | } 63 | mTextPointer = mValue; /* Reset value pointer */ 64 | mState = RECORD_VALUE; 65 | break; 66 | 67 | 68 | default: 69 | // add byte to name, but do no overflow 70 | if (mTextPointer < (mName + sizeof(mName))) 71 | *mTextPointer++ = inbyte; 72 | break; 73 | } 74 | break; 75 | case RECORD_VALUE: 76 | // The record value is being received. The \r indicates a new record. 77 | switch (inbyte) 78 | { 79 | case '\n': 80 | // forward record, only if it could be stored completely 81 | if (mTextPointer < (mValue + sizeof(mValue))) 82 | { 83 | *mTextPointer = 0; // make zero ended 84 | //recordValueCallback(true, mValue); 85 | textRxEvent(mName, mValue); 86 | } else { 87 | //recordValueCallback(false, nullptr); 88 | } 89 | mState = RECORD_BEGIN; 90 | break; 91 | case '\r': /* Skip */ 92 | break; 93 | default: 94 | // add byte to value, but do no overflow 95 | if (mTextPointer < (mValue + sizeof(mValue))) 96 | *mTextPointer++ = inbyte; 97 | break; 98 | } 99 | break; 100 | case CHECKSUM: 101 | { 102 | //checksumCallback(mChecksum); 103 | bool valid = mChecksum == 0; 104 | mChecksum = 0; 105 | mState = IDLE; 106 | frameEndEvent(valid); 107 | break; 108 | } 109 | case RECORD_HEX: 110 | if (hexRxEvent(inbyte)) 111 | { 112 | //hexFrameCallback(); 113 | mChecksum = 0; 114 | mState = IDLE; 115 | } 116 | break; 117 | } 118 | } 119 | 120 | /* 121 | * textRxEvent 122 | * This function is called every time a new name/value is successfully parsed. It writes the values to the temporary buffer. 123 | */ 124 | void VeDirectFrameHandler::textRxEvent(char *mName, char *mValue) 125 | { 126 | strcpy(tempName[frameIndex], mName); // copy name to temporary buffer 127 | strcpy(tempValue[frameIndex], mValue); // copy value to temporary buffer 128 | frameIndex++; 129 | } 130 | 131 | /* 132 | * frameEndEvent 133 | * This function is called at the end of the received frame. If the checksum is valid, the temp buffer is read line by line. 134 | * If the name exists in the public buffer, the new value is copied to the public buffer. If not, a new name/value entry 135 | * is created in the public buffer. 136 | */ 137 | void VeDirectFrameHandler::frameEndEvent(bool valid) 138 | { 139 | if (valid) 140 | { 141 | for (int i = 0; i < frameIndex; i++) 142 | { 143 | // read each name already in the temp buffer 144 | bool nameExists = false; 145 | for (size_t j = 0; j < veEnd; j++) 146 | { 147 | // compare to existing names in the public buffer 148 | if (strcmp(tempName[i], veName[j]) == 0) 149 | { 150 | strcpy(veValue[j], tempValue[i]); // overwrite tempValue in the public buffer 151 | nameExists = true; 152 | break; 153 | } 154 | } 155 | if (!nameExists) 156 | { 157 | strcpy(veName[veEnd], tempName[i]); // write new Name to public buffer 158 | strcpy(veValue[veEnd], tempValue[i]); // write new Value to public buffer 159 | veEnd++; // increment end of public buffer 160 | if (veEnd >= buffLen) 161 | { // stop any buffer overrun 162 | veEnd = buffLen - 1; 163 | } 164 | } 165 | } 166 | writeLog("\nCRC OK"); 167 | veErrorCount = 0; 168 | requestCallback(); // call the callback to do other things with the new data 169 | } else { 170 | veErrorCount++; 171 | writeLog("\nCRC wrong"); 172 | } 173 | frameIndex = 0; // reset frame 174 | veError = veErrorCount < veErrorTol ? false : true; //set the crc error flag 175 | } 176 | 177 | bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) 178 | { 179 | return inbyte == '\n'; 180 | } 181 | 182 | void VeDirectFrameHandler::callback(std::function<void()> func) // callback function when finnish request 183 | { 184 | requestCallback = func; 185 | } 186 | -------------------------------------------------------------------------------- /src/VeDirectFrameHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //#include <stdint.h> 4 | //#include <functional> 5 | #ifndef FRAMEHANDLER_H_ 6 | #define FRAMEHANDLER_H_ 7 | 8 | class VeDirectFrameHandler 9 | { 10 | public: 11 | static const size_t frameLen = 22; // VE.Direct Protocol: max frame size is 22 12 | static const size_t nameLen = 9; // VE.Direct Protocol: max name size is 9 including /0 13 | static const size_t valueLen = 33; // VE.Direct Protocol: max value size is 33 including /0 14 | static const size_t buffLen = 40; // Maximum number of lines possible from the device. Current protocol shows this to be the 15 | 16 | 17 | 18 | 19 | //VeDirectFrameHandler(); 20 | //~VeDirectFrameHandler(); 21 | //void setErrorHandler(logFunction f) { logEF = f; } // error handler 22 | void rxData(uint8_t inbyte); // byte of serial data to be passed by the application 23 | //void addHexCallback(hexFrameCallback, void *); // add function called back when hex frame is ready (sync or async) 24 | void callback(std::function<void()> func); // callback function 25 | std::function<void()> requestCallback; 26 | /* 27 | constexpr VeDirectFrameHandler() = default; 28 | void rxData(uint8_t inbyte); // byte of serial data to be passed by the application 29 | std::function<void(bool valid, char const * name)> recordNameCallback = [](bool, char const *){ }; 30 | std::function<void(bool valid, char const * value)> recordValueCallback = [](bool, char const *){ }; 31 | std::function<void(uint8_t checksum)> checksumCallback = [](uint8_t){ }; 32 | std::function<void()> hexFrameCallback = [](){ }; 33 | std::function<void(bool valid)> frameEndEventCallback = [](bool){ }; 34 | 35 | void callback(std::function<void()> func); // callback function 36 | std::function<void()> requestCallback; 37 | */ 38 | 39 | char veName[buffLen][nameLen] = { }; // public buffer for received names 40 | char veValue[buffLen][valueLen] = { }; // public buffer for received values 41 | 42 | size_t veEnd = 0; // current size (end) of the public buffer 43 | int veErrorTol = 10; // error counter for crc, if higher than error tollerance 44 | int veErrorCount = 0; // crc error counter 45 | bool veError = true; // error flag for crc 46 | 47 | private: 48 | enum States { // state machine 49 | IDLE, 50 | RECORD_BEGIN, 51 | RECORD_NAME, 52 | RECORD_VALUE, 53 | CHECKSUM, 54 | RECORD_HEX 55 | }; 56 | 57 | int mState = IDLE; // current state 58 | uint8_t mChecksum = 0; // checksum value 59 | int frameIndex = 0; // which line of the frame are we on 60 | 61 | char * mTextPointer = nullptr; // pointer to the private buffer we're writing to, name or value 62 | 63 | char mName[nameLen] = { }; // buffer for the field name 64 | char mValue[valueLen] = { }; // buffer for the field value 65 | char tempName[frameLen][nameLen] = { }; // private buffer for received names 66 | char tempValue[frameLen][valueLen] = { }; // private buffer for received values 67 | 68 | void textRxEvent(char *, char *); 69 | void frameEndEvent(bool); 70 | bool hexRxEvent(uint8_t); 71 | }; 72 | #endif // FRAMEHANDLER_H_ 73 | -------------------------------------------------------------------------------- /src/htmlProzessor.h: -------------------------------------------------------------------------------- 1 | // #include <Arduino.h> 2 | String htmlProcessor(const String &var) 3 | { 4 | extern Settings _settings; 5 | if (var == F("pre_head_template")) 6 | return (FPSTR(HTML_HEAD)); 7 | if (var == F("pre_foot_template")) 8 | return (FPSTR(HTML_FOOT)); 9 | if (var == F("pre_software_version")) 10 | return (SOFTWARE_VERSION); 11 | if (var == F("pre_swversion")) 12 | return (SWVERSION); 13 | if (var == F("pre_flash_size")) 14 | return (String(FlashSize).c_str()); 15 | if (var == F("pre_esp01")) 16 | return (String(ESP01).c_str()); 17 | if (var == F("pre_device_name")) 18 | return (_settings.data.deviceName); 19 | if (var == F("pre_mqtt_server")) 20 | return (_settings.data.mqttServer); 21 | if (var == F("pre_mqtt_port")) 22 | return (String(_settings.data.mqttPort).c_str()); 23 | if (var == F("pre_mqtt_user")) 24 | return (_settings.data.mqttUser); 25 | if (var == F("pre_mqtt_pass")) 26 | return (_settings.data.mqttPassword); 27 | if (var == F("pre_mqtt_topic")) 28 | return (_settings.data.mqttTopic); 29 | if (var == F("pre_mqtt_refresh")) 30 | return (String(_settings.data.mqttRefresh).c_str()); 31 | if (var == F("pre_mqtt_json")) 32 | return (_settings.data.mqttJson ? "checked" : ""); 33 | if (var == F("pre_mqtt_mqtttrigger")) 34 | return (_settings.data.mqttTriggerPath); 35 | if (var == F("pre_darkmode")) 36 | return (_settings.data.webUIdarkmode ? "dark" : "light"); 37 | if (var == F("pre_webuidarkmode")) 38 | return (_settings.data.webUIdarkmode ? "checked" : ""); 39 | if (var == F("pre_http_user")) 40 | return (_settings.data.httpUser); 41 | if (var == F("pre_http_pass")) 42 | return (_settings.data.httpPass); 43 | if (var == F("pre_hadiscovery")) 44 | return (_settings.data.haDiscovery ? "checked" : ""); 45 | //if (var == F("pre_debugmode")) 46 | // return (_settings.data.debugmode ? "checked" : ""); 47 | if (var == F("pre_keeprcstate")) 48 | return (_settings.data.keepRcState ? "checked" : ""); 49 | if (var == F("pre_led")) 50 | return (String(_settings.data.LEDBrightness)); 51 | return String(); 52 | } 53 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /*Lot of ideas comes from here: 2 | * https://github.com/glitterkitty/EpEverSolarMonitor 3 | * 4 | */ 5 | #include <Arduino.h> 6 | #include "main.h" 7 | 8 | #include <EEPROM.h> 9 | #include <PubSubClient.h> 10 | 11 | #include <ArduinoJson.h> 12 | #include <ESP8266mDNS.h> 13 | #include <ESPAsyncWiFiManager.h> 14 | #include <ESPAsyncTCP.h> 15 | #include <ESPAsyncWebServer.h> 16 | 17 | // #include <WebSerialLite.h> 18 | #include <MycilaWebSerial.h> 19 | #include <SoftwareSerial.h> 20 | 21 | #include <RTCMemory.h> 22 | 23 | #include <OneWire.h> 24 | #include <DallasTemperature.h> 25 | #include <NonBlockingDallas.h> 26 | 27 | #include "VeDirectFrameHandler.h" 28 | #include "VeDirectDataList.h" 29 | #include "VeDirectDeviceList.h" 30 | #include "VeDirectDeviceCodes.h" 31 | #include "Settings.h" //settings functions 32 | #include <Updater.h> //new 33 | 34 | #include "html.h" //the HTML content 35 | #include "htmlProzessor.h" // The html Prozessor 36 | 37 | String topic = ""; // Default first part of topic. We will add device ID in setup 38 | 39 | // flag for saving data and other things 40 | bool shouldSaveConfig = false; 41 | bool restartNow = false; 42 | bool workerCanRun = true; 43 | bool dataProzessing = false; 44 | bool haDiscTrigger = false; 45 | bool haAutoDiscTrigger = false; 46 | bool remoteControlState = false; 47 | unsigned int jsonSize = 0; 48 | unsigned long mqtttimer = 0; 49 | unsigned long RestartTimer = 0; 50 | byte wsReqInvNum = 1; 51 | char mqtt_server[80]; 52 | char mqttClientId[80]; 53 | uint32_t bootcount = 0; 54 | 55 | WiFiClient client; 56 | Settings _settings; 57 | PubSubClient mqttclient(client); 58 | AsyncWebServer server(80); 59 | AsyncWebSocket ws("/ws"); 60 | AsyncWebSocketClient *wsClient; 61 | DNSServer dns; 62 | VeDirectFrameHandler myve; 63 | SoftwareSerial veSerial; 64 | RTCMemory<rtcData> rtcMemory; 65 | WebSerial webSerial; 66 | 67 | OneWire oneWire(TEMPSENS_PIN); 68 | DallasTemperature dallasTemp(&oneWire); 69 | NonBlockingDallas tempSens(&dallasTemp); 70 | 71 | DynamicJsonDocument Json(JSON_BUFFER); 72 | JsonObject jsonESP = Json.createNestedObject("ESP_Data"); 73 | #include "status-LED.h" 74 | ADC_MODE(ADC_VCC); 75 | 76 | //---------------------------------------------------------------------- 77 | void saveConfigCallback() 78 | { 79 | writeLog("Should save config"); 80 | shouldSaveConfig = true; 81 | } 82 | 83 | void notifyClients() 84 | { 85 | if (wsClient != nullptr && wsClient->canSend()) 86 | { 87 | size_t len = measureJson(Json); 88 | AsyncWebSocketMessageBuffer *buffer = ws.makeBuffer(len); 89 | if (buffer) 90 | { 91 | serializeJson(Json, (char *)buffer->get(), len + 1); 92 | wsClient->text(buffer); 93 | } 94 | writeLog("Data sent to WebSocket"); 95 | } 96 | } 97 | 98 | void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) 99 | { 100 | 101 | AwsFrameInfo *info = (AwsFrameInfo *)arg; 102 | if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) 103 | { 104 | data[len] = 0; 105 | if (strcmp((char *)data, "A9") != 0) 106 | { 107 | if (strcmp((char *)data, "remotecontrol_on") == 0) 108 | { 109 | remoteControl(true); 110 | } 111 | if (strcmp((char *)data, "remotecontrol_off") == 0) 112 | { 113 | remoteControl(false); 114 | } 115 | } 116 | } 117 | } 118 | 119 | void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) 120 | { 121 | switch (type) 122 | { 123 | case WS_EVT_CONNECT: 124 | wsClient = client; 125 | writeLog("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); 126 | if (!dataProzessing /*&& wsClient != nullptr && wsClient->canSend()*/) 127 | notifyClients(); 128 | break; 129 | case WS_EVT_PING: 130 | break; 131 | case WS_EVT_DISCONNECT: 132 | writeLog("WebSocket client #%u disconnected\n", client->id()); 133 | wsClient = nullptr; 134 | ws.cleanupClients(); 135 | break; 136 | case WS_EVT_DATA: 137 | handleWebSocketMessage(arg, data, len); 138 | break; 139 | case WS_EVT_PONG: 140 | break; 141 | case WS_EVT_ERROR: 142 | wsClient = nullptr; 143 | ws.cleanupClients(); 144 | break; 145 | } 146 | } 147 | 148 | void ReadVEData() 149 | { 150 | while (veSerial.available()) 151 | { 152 | myve.rxData(veSerial.read()); 153 | esp_yield(); 154 | } 155 | // if (veSerial.available()) 156 | // { 157 | // myve.rxData(veSerial.read()); 158 | // } 159 | } 160 | 161 | bool remoteControl(bool sw) 162 | { 163 | writeLog("set Remote Control to: %d", sw); 164 | digitalWrite(MYPORT_TX, sw); 165 | rtcData *RTCmem = rtcMemory.getData(); 166 | RTCmem->remoteControlState = sw; 167 | rtcMemory.save(); 168 | if (_settings.data.keepRcState) 169 | { 170 | _settings.data.rcState = sw; 171 | _settings.save(); 172 | } 173 | remoteControlState = sw; 174 | mqtttimer = 0; 175 | return remoteControlState; 176 | } 177 | 178 | void setup() 179 | { 180 | _settings.load(); 181 | DBG_BEGIN(DBG_BAUD); 182 | pinMode(LED_PIN, OUTPUT); 183 | analogWrite(LED_PIN, 255 - _settings.data.LEDBrightness); 184 | pinMode(MYPORT_TX, OUTPUT); 185 | 186 | if (!rtcMemory.begin()) 187 | { 188 | remoteControlState = false; 189 | } 190 | rtcData *RTCmem = rtcMemory.getData(); 191 | remoteControlState = RTCmem->remoteControlState; 192 | Serial.printf("bootcount is: %d \n", RTCmem->bootcount); 193 | if (ESP.getResetInfoPtr()->reason == 6) 194 | { 195 | RTCmem->bootcount++; 196 | if (RTCmem->bootcount >= 10) 197 | { 198 | _settings.reset(); 199 | ESP.eraseConfig(); 200 | Serial.println("reset called"); 201 | ESP.restart(); 202 | } 203 | } 204 | rtcMemory.save(); 205 | if (_settings.data.keepRcState) 206 | remoteControlState = _settings.data.rcState; 207 | digitalWrite(MYPORT_TX, remoteControlState); 208 | 209 | haAutoDiscTrigger = _settings.data.haDiscovery; 210 | WiFi.persistent(true); // fix wifi save bug 211 | veSerial.begin(VICTRON_BAUD, SWSERIAL_8N1, MYPORT_RX /*, MYPORT_TX, false*/); 212 | veSerial.flush(); 213 | veSerial.enableRxGPIOPullUp(false); 214 | myve.callback(prozessData); 215 | 216 | sprintf(mqttClientId, "%s-%06X", _settings.data.deviceName, ESP.getChipId()); 217 | 218 | AsyncWiFiManagerParameter custom_mqtt_server("mqtt_server", "MQTT server", NULL, 32); 219 | AsyncWiFiManagerParameter custom_mqtt_user("mqtt_user", "MQTT User", NULL, 32); 220 | AsyncWiFiManagerParameter custom_mqtt_pass("mqtt_pass", "MQTT Password", NULL, 32); 221 | AsyncWiFiManagerParameter custom_mqtt_topic("mqtt_topic", "MQTT Topic", "Victron", 32); 222 | AsyncWiFiManagerParameter custom_mqtt_port("mqtt_port", "MQTT Port", NULL, 6); 223 | AsyncWiFiManagerParameter custom_mqtt_refresh("mqtt_refresh", "MQTT Send Interval", "300", 4); 224 | AsyncWiFiManagerParameter custom_mqtt_triggerpath("mqtt_triggerpath", "MQTT Data Trigger Path", NULL, 80); 225 | AsyncWiFiManagerParameter custom_device_name("device_name", "Device Name", "Victron2MQTT", 32); 226 | 227 | AsyncWiFiManager wm(&server, &dns); // create wifimanager instance 228 | 229 | wm.addParameter(&custom_mqtt_server); 230 | wm.addParameter(&custom_mqtt_user); 231 | wm.addParameter(&custom_mqtt_pass); 232 | wm.addParameter(&custom_mqtt_topic); 233 | wm.addParameter(&custom_mqtt_port); 234 | wm.addParameter(&custom_mqtt_refresh); 235 | wm.addParameter(&custom_mqtt_triggerpath); 236 | wm.addParameter(&custom_device_name); 237 | 238 | wm.setDebugOutput(false); // disable wifimanager debug output 239 | wm.setMinimumSignalQuality(25); // filter weak wifi signals 240 | wm.setConnectTimeout(10); // how long to try to connect for before continuing 241 | wm.setConfigPortalTimeout(300); // auto close configportal after n seconds 242 | wm.setSaveConfigCallback(saveConfigCallback); 243 | bool res = wm.autoConnect("Victron2MQTT-AP"); 244 | 245 | // save settings if wifi setup is fire up 246 | if (shouldSaveConfig) 247 | { 248 | strncpy(_settings.data.mqttServer, custom_mqtt_server.getValue(), 40); 249 | strncpy(_settings.data.mqttUser, custom_mqtt_user.getValue(), 40); 250 | strncpy(_settings.data.mqttPassword, custom_mqtt_pass.getValue(), 40); 251 | _settings.data.mqttPort = atoi(custom_mqtt_port.getValue()); 252 | strncpy(_settings.data.deviceName, custom_device_name.getValue(), 40); 253 | strncpy(_settings.data.mqttTopic, custom_mqtt_topic.getValue(), 40); 254 | _settings.data.mqttRefresh = atoi(custom_mqtt_refresh.getValue()); 255 | strncpy(_settings.data.mqttTriggerPath, custom_mqtt_triggerpath.getValue(), 80); 256 | 257 | _settings.save(); 258 | ESP.restart(); 259 | } 260 | 261 | topic = _settings.data.mqttTopic; 262 | mqttclient.setServer(_settings.data.mqttServer, _settings.data.mqttPort); 263 | mqttclient.setCallback(mqttCallback); 264 | 265 | if (res) 266 | { 267 | // set the device name 268 | MDNS.begin(_settings.data.deviceName); 269 | MDNS.addService("http", "tcp", 80); 270 | WiFi.hostname(_settings.data.deviceName); 271 | 272 | Json["Device_name"] = _settings.data.deviceName; 273 | 274 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) 275 | { 276 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 277 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", HTML_MAIN, htmlProcessor); 278 | request->send(response); }); 279 | 280 | server.on("/livejson", HTTP_GET, [](AsyncWebServerRequest *request) 281 | { 282 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 283 | AsyncResponseStream *response = request->beginResponseStream("application/json"); 284 | serializeJson(Json, *response); 285 | request->send(response); }); 286 | 287 | server.on("/reboot", HTTP_GET, [](AsyncWebServerRequest *request) 288 | { 289 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 290 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", HTML_REBOOT, htmlProcessor); 291 | request->send(response); 292 | restartNow = true; 293 | RestartTimer = millis(); }); 294 | 295 | server.on("/confirmreset", HTTP_GET, [](AsyncWebServerRequest *request) 296 | { 297 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 298 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", HTML_CONFIRM_RESET, htmlProcessor); 299 | request->send(response); }); 300 | 301 | server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request) 302 | { 303 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 304 | AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Device is Erasing..."); 305 | response->addHeader("Refresh", "15; url=/"); 306 | response->addHeader("Connection", "close"); 307 | request->send(response); 308 | delay(500); 309 | _settings.reset(); 310 | ESP.eraseConfig(); 311 | ESP.reset(); }); 312 | 313 | server.on("/set", HTTP_GET, [](AsyncWebServerRequest *request) 314 | { 315 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 316 | String message; 317 | if (request->hasParam("ha")) { 318 | haDiscTrigger = true; 319 | } 320 | if (request->hasParam("remotecontrol")) { 321 | message = request->getParam("remotecontrol")->value(); 322 | remoteControl((message == "1") ? true:false); 323 | } 324 | request->send(200, "text/plain", "message received"); }); 325 | 326 | server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request) 327 | { 328 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 329 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", HTML_SETTINGS, htmlProcessor); 330 | request->send(response); }); 331 | 332 | server.on("/settingsedit", HTTP_GET, [](AsyncWebServerRequest *request) 333 | { 334 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 335 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", HTML_SETTINGS_EDIT, htmlProcessor); 336 | request->send(response); }); 337 | 338 | server.on("/settingssave", HTTP_POST, [](AsyncWebServerRequest *request) 339 | { 340 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 341 | strncpy(_settings.data.mqttServer, request->arg("post_mqttServer").c_str(), 40); 342 | _settings.data.mqttPort = request->arg("post_mqttPort").toInt(); 343 | strncpy(_settings.data.mqttUser, request->arg("post_mqttUser").c_str(), 40); 344 | strncpy(_settings.data.mqttPassword, request->arg("post_mqttPassword").c_str(), 40); 345 | strncpy(_settings.data.mqttTopic, request->arg("post_mqttTopic").c_str(), 40); 346 | _settings.data.mqttRefresh = request->arg("post_mqttRefresh").toInt() < 1 ? 1 : request->arg("post_mqttRefresh").toInt(); // prevent lower numbers 347 | strncpy(_settings.data.deviceName, request->arg("post_deviceName").c_str(), 40); 348 | _settings.data.mqttJson = (request->arg("post_mqttjson") == "true") ? true : false; 349 | strncpy(_settings.data.mqttTriggerPath, request->arg("post_mqtttrigger").c_str(), 80); 350 | _settings.data.webUIdarkmode = (request->arg("post_webuicolormode") == "true") ? true : false; 351 | strncpy(_settings.data.httpUser, request->arg("post_httpUser").c_str(), 40); 352 | strncpy(_settings.data.httpPass, request->arg("post_httpPass").c_str(), 40); 353 | _settings.data.haDiscovery = (request->arg("post_hadiscovery") == "true") ? true : false; 354 | //_settings.data.debugmode = (request->arg("post_debugmode") == "true") ? true : false; 355 | _settings.data.keepRcState = (request->arg("post_keeprcstate") == "true") ? true : false; 356 | _settings.data.LEDBrightness = request->arg("post_led").toInt(); 357 | _settings.save(); 358 | request->redirect("/reboot"); }); 359 | 360 | server.on( 361 | "/update", HTTP_POST, [](AsyncWebServerRequest *request) 362 | { 363 | //https://gist.github.com/JMishou/60cb762047b735685e8a09cd2eb42a60 364 | // the request handler is triggered after the upload has finished... 365 | // create the response, add header, and send response 366 | if(strlen(_settings.data.httpUser) > 0 && !request->authenticate(_settings.data.httpUser, _settings.data.httpPass)) return request->requestAuthentication(); 367 | AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", (Update.hasError())?"FAIL":"OK"); 368 | response->addHeader("Connection", "close"); 369 | response->addHeader("Access-Control-Allow-Origin", "*"); 370 | //restartNow = true; // Tell the main loop to restart the ESP 371 | request->send(response); }, 372 | [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) 373 | { 374 | // Upload handler chunks in data 375 | 376 | if (!index) 377 | { // if index == 0 then this is the first frame of data 378 | Serial.printf("UploadStart: %s\n", filename.c_str()); 379 | Serial.setDebugOutput(true); 380 | 381 | // calculate sketch space required for the update 382 | uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; 383 | if (!Update.begin(maxSketchSpace)) 384 | { // start with max available size 385 | Update.printError(Serial); 386 | } 387 | Update.runAsync(true); // tell the updaterClass to run in async mode 388 | } 389 | 390 | // Write chunked data to the free sketch space 391 | if (Update.write(data, len) != len) 392 | { 393 | Update.printError(Serial); 394 | } 395 | 396 | if (final) 397 | { // if the final flag is set then this is the last frame of data 398 | if (Update.end(true)) 399 | { // true to set the size to the current progress 400 | Serial.printf("Update Success: %u B\nRebooting...\n", index + len); 401 | } 402 | else 403 | { 404 | Update.printError(Serial); 405 | } 406 | Serial.setDebugOutput(false); 407 | } 408 | }); 409 | 410 | server.onNotFound([](AsyncWebServerRequest *request) 411 | { request->send(418, "text/plain", "418 I'm a teapot"); }); 412 | 413 | ws.onEvent(onEvent); 414 | server.addHandler(&ws); 415 | 416 | // WebSerial is accessible at "<IP Address>/webserial" in browser 417 | webSerial.begin(&server); 418 | 419 | server.begin(); 420 | // MDNS.addService("http", "tcp", 80); 421 | // MDNS.update(); 422 | 423 | jsonESP["IP"] = WiFi.localIP(); 424 | jsonESP["sw_version"] = SOFTWARE_VERSION; 425 | tempSens.begin(NonBlockingDallas::resolution_12, TIME_INTERVAL); 426 | tempSens.onTemperatureChange(handleTemperatureChange); 427 | } 428 | analogWrite(LED_PIN, 255); 429 | RTCmem->bootcount = 0; 430 | rtcMemory.save(); 431 | } 432 | 433 | void loop() 434 | { 435 | MDNS.update(); 436 | tempSens.update(); 437 | if (Update.isRunning()) 438 | { 439 | workerCanRun = false; 440 | } 441 | if (workerCanRun) 442 | { 443 | ReadVEData(); 444 | 445 | // Make sure wifi is in the right mode 446 | if (WiFi.status() == WL_CONNECTED) 447 | { // No use going to next step unless WIFI is up and running. 448 | // ws.cleanupClients(); // clean unused client connections 449 | // MDNS.update(); 450 | if (millis() - mqtttimer > (_settings.data.mqttRefresh * 1000) || mqtttimer == 0) 451 | { 452 | writeLog("<MQTT> Data Send..."); 453 | sendtoMQTT(); // Update data to MQTT server if we should 454 | mqtttimer = millis(); 455 | } 456 | 457 | mqttclient.loop(); // Check if we have something to read from MQTT 458 | } 459 | notificationLED(); // notification LED routine 460 | 461 | if ((haDiscTrigger || _settings.data.haDiscovery) && measureJson(Json) > jsonSize) 462 | { 463 | if (sendHaDiscovery()) 464 | { 465 | haDiscTrigger = false; 466 | jsonSize = measureJson(Json); 467 | } 468 | } 469 | jsonESP["Wifi_RSSI"] = WiFi.RSSI(); 470 | } 471 | 472 | if (restartNow && millis() >= (RestartTimer + 500)) 473 | { 474 | writeLog("Restart"); 475 | ESP.reset(); 476 | } 477 | } 478 | 479 | void prozessData() 480 | { 481 | dataProzessing = true; 482 | writeLog("VE callback triggered... prozessing data"); 483 | getJsonData(); 484 | notifyClients(); 485 | dataProzessing = false; 486 | } 487 | 488 | bool getJsonData() 489 | { 490 | jsonESP["ESP_VCC"] = (ESP.getVcc() / 1000.0) + 0.3; 491 | jsonESP["Wifi_RSSI"] = WiFi.RSSI(); 492 | jsonESP["Free_Heap"] = ESP.getFreeHeap(); 493 | jsonESP["json_space"] = Json.capacity() - Json.memoryUsage(); 494 | jsonESP["WS_Clients"] = ws.count(); 495 | jsonESP["Runtime"] = millis() / 1000; 496 | writeLog("VE data: %d:%d:%d", myve.veEnd, myve.veErrorCount, myve.veError); 497 | for (size_t i = 0; i < myve.veEnd; i++) 498 | { 499 | 500 | if (myve.veName[i] == NULL || strlen(myve.veName[i]) == 0 || myve.veValue[i] == NULL || strlen(myve.veValue[i]) == 0) 501 | { 502 | i = myve.veEnd; 503 | break; 504 | } 505 | // writeLog("[%s:%s]",myve.veName[i], myve.veValue[i]); 506 | // search for every Vevalue in the list and replace it with clear name 507 | for (size_t j = 0; j < VePrettyDataSize; j++) 508 | { 509 | VePrettyEntry entry; 510 | memcpy_P(&entry, &VePrettyData[j], sizeof(entry)); 511 | 512 | char key[16]; 513 | strcpy_P(key, entry.key); 514 | 515 | if (strcmp(key, myve.veName[i]) == 0) 516 | { 517 | char name[32]; 518 | char op[8]; 519 | strcpy_P(name, entry.name); 520 | strcpy_P(op, entry.op); 521 | 522 | if (strlen(op) > 0 && strcmp(op, "0") != 0) 523 | { 524 | Json[FPSTR(entry.name)] = (int)((atof(myve.veValue[i]) / atoi(op)) * 100 + 0.5) / 100.0; 525 | } 526 | else if (strcmp(op, "0") == 0) 527 | { 528 | Json[FPSTR(entry.name)] = atoi(myve.veValue[i]); 529 | } 530 | else 531 | { 532 | Json[FPSTR(entry.name)] = myve.veValue[i]; 533 | } 534 | if (strcmp(name, "Device_model") == 0) 535 | { 536 | uint16_t deviceID = strtol(myve.veValue[i], nullptr, 16); 537 | VeDeviceEntry devEntry; 538 | size_t left = 0, right = VeDeviceListSize; 539 | const char *modelName = nullptr; 540 | 541 | while (left < right) 542 | { 543 | size_t mid = (left + right) / 2; 544 | memcpy_P(&devEntry, &VeDeviceList[mid], sizeof(devEntry)); 545 | if (deviceID == devEntry.id) 546 | { 547 | modelName = (const char *)pgm_read_ptr(&devEntry.name); 548 | break; 549 | } 550 | else if (deviceID < devEntry.id) 551 | { 552 | right = mid; 553 | } 554 | else 555 | { 556 | left = mid + 1; 557 | } 558 | } 559 | if (modelName) 560 | { 561 | Json[FPSTR(entry.name)] = FPSTR(modelName); 562 | } 563 | } 564 | struct CodeMap 565 | { 566 | const char *label; 567 | const VeCodeEntry *table; 568 | size_t size; 569 | }; 570 | 571 | const CodeMap maps[] = { 572 | {"Alarm_code", VeDirectDeviceCodeAR, VeDirectDeviceCodeARSize}, 573 | {"Off_reason", VeDirectDeviceCodeOR, VeDirectDeviceCodeORSize}, 574 | {"Operation_state", VeDirectDeviceCodeCS, VeDirectDeviceCodeCSSize}, 575 | {"Current_error", VeDirectDeviceCodeERR, VeDirectDeviceCodeERRSize}, 576 | {"Tracker_operation_mode", VeDirectDeviceCodeMPPT, VeDirectDeviceCodeMPPTSize}, 577 | }; 578 | 579 | for (const auto &map : maps) 580 | { 581 | if (strcmp(name, map.label) == 0) 582 | { 583 | for (size_t k = 0; k < map.size; k++) 584 | { 585 | VeCodeEntry codeEntry; 586 | memcpy_P(&codeEntry, &map.table[k], sizeof(codeEntry)); 587 | 588 | char codeBuf[16]; 589 | strcpy_P(codeBuf, codeEntry.code); 590 | if (strcmp(codeBuf, myve.veValue[i]) == 0) 591 | { 592 | Json[FPSTR(entry.name)] = FPSTR(codeEntry.text); 593 | break; 594 | } 595 | } 596 | } 597 | } 598 | break; 599 | } 600 | } 601 | 602 | Json["Device_connection"] = !myve.veError; 603 | Json["Remote_Control_State"] = remoteControlState; 604 | } 605 | return true; 606 | } 607 | 608 | bool connectMQTT() 609 | { 610 | if (!mqttclient.connected()) 611 | { 612 | if (mqttclient.connect(mqttClientId, _settings.data.mqttUser, _settings.data.mqttPassword, (topic + "/Alive").c_str(), 0, true, "false", true)) 613 | { 614 | mqttclient.publish((topic + String("/IP")).c_str(), String(WiFi.localIP().toString()).c_str()); 615 | mqttclient.publish((topic + String("/Alive")).c_str(), "true", true); // LWT online message must be retained! 616 | mqttclient.subscribe((topic + "/Remote_Control").c_str()); 617 | 618 | if (strlen(_settings.data.mqttTriggerPath) > 0) 619 | { 620 | writeLog("MQTT Data Trigger Subscribed"); 621 | mqttclient.subscribe(_settings.data.mqttTriggerPath); 622 | } 623 | return true; 624 | } 625 | else 626 | { 627 | return false; 628 | } 629 | return false; 630 | } 631 | else 632 | { 633 | return true; 634 | } 635 | } 636 | 637 | bool sendtoMQTT() 638 | { 639 | 640 | if (!connectMQTT()) 641 | { 642 | return false; 643 | } 644 | 645 | String mqttDeviceName = topic; 646 | 647 | //----------------------------------------------------- 648 | mqttclient.publish((mqttDeviceName + String("/Alive")).c_str(), "true", true); // LWT online message must be retained! 649 | mqttclient.publish((mqttDeviceName + String("/Wifi_RSSI")).c_str(), String(WiFi.RSSI()).c_str()); 650 | mqttclient.publish((mqttDeviceName + String("/Remote_Control_State")).c_str(), remoteControlState ? "true" : "false"); 651 | if (!_settings.data.mqttJson) 652 | { 653 | 654 | for (JsonPair i : Json.as<JsonObject>()) 655 | { 656 | mqttclient.publish((mqttDeviceName + "/" + i.key().c_str()).c_str(), i.value().as<String>().c_str()); 657 | } 658 | } 659 | else 660 | { 661 | mqttclient.beginPublish((String(mqttDeviceName + "/DATA")).c_str(), measureJson(Json), false); 662 | serializeJson(Json, mqttclient); 663 | mqttclient.endPublish(); 664 | } 665 | 666 | return true; 667 | } 668 | 669 | void mqttCallback(char *top, byte *payload, unsigned int length) // Need rework 670 | { 671 | String messageTemp; 672 | // updateProgress = true; // stop servicing data 673 | // if (!_settings.data.mqttJson) 674 | // { 675 | 676 | for (unsigned int i = 0; i < length; i++) 677 | { 678 | messageTemp += (char)payload[i]; 679 | } 680 | // } 681 | // else 682 | // { 683 | // StaticJsonDocument<1024> mqttJsonAnswer; 684 | // deserializeJson(mqttJsonAnswer, (const byte *)payload, length); 685 | // } 686 | 687 | if (strlen(_settings.data.mqttTriggerPath) > 0 && strcmp(top, _settings.data.mqttTriggerPath) == 0) 688 | { 689 | writeLog("MQTT Data Trigger Firered Up"); 690 | mqtttimer = 0; 691 | } 692 | if (strcmp(top, (topic + "/Remote_Control").c_str()) == 0) 693 | { 694 | if (messageTemp == "true") 695 | { 696 | mqtttimer = 0; 697 | remoteControl(true); 698 | } 699 | if (messageTemp == "false") 700 | { 701 | mqtttimer = 0; 702 | remoteControl(false); 703 | } 704 | } 705 | } 706 | 707 | bool sendHaDiscovery() 708 | { 709 | if (!connectMQTT()) 710 | { 711 | return false; 712 | } 713 | String haDeviceDescription = String("\"dev\":") + 714 | "{\"ids\":[\"" + mqttClientId + "\"]," + 715 | "\"name\":\"" + _settings.data.deviceName + "\"," + 716 | "\"cu\":\"http://" + WiFi.localIP().toString() + "\"," + 717 | "\"mdl\":\"" + Json["Device_model"].as<String>().c_str() + "\"," + 718 | "\"mf\":\"SoftWareCrash\"," + 719 | "\"sw\":\"" + SOFTWARE_VERSION + "\"" + 720 | "}"; 721 | 722 | char topBuff[128]; 723 | // char configBuff[1024]; 724 | // size_t mqttContentLength; 725 | for (size_t i = 0; i < sizeof haDescriptor / sizeof haDescriptor[0]; i++) 726 | { 727 | if (Json.containsKey(haDescriptor[i][0])) 728 | { 729 | String haPayLoad = String("{") + 730 | "\"name\":\"" + haDescriptor[i][0] + "\"," + 731 | "\"stat_t\":\"" + _settings.data.mqttTopic + "/" + haDescriptor[i][0] + "\"," + 732 | "\"avty_t\":\"" + _settings.data.mqttTopic + "/Alive\"," + 733 | "\"pl_avail\": \"true\"," + 734 | "\"pl_not_avail\": \"false\"," + 735 | "\"uniq_id\":\"" + mqttClientId + "." + haDescriptor[i][0] + "\"," + 736 | "\"ic\":\"mdi:" + haDescriptor[i][1] + "\","; 737 | if (strlen(haDescriptor[i][2]) != 0) 738 | haPayLoad += (String) "\"unit_of_meas\":\"" + haDescriptor[i][2] + "\","; 739 | 740 | if (strcmp(haDescriptor[i][2], "kWh") == 0 || strcmp(haDescriptor[i][2], "Wh") == 0) 741 | haPayLoad += (String) "\"state_class\":\"total\","; 742 | if (strcmp(haDescriptor[i][2], "A") == 0 || strcmp(haDescriptor[i][2], "V") == 0 || strcmp(haDescriptor[i][2], "W") == 0) 743 | haPayLoad += (String) "\"state_class\":\"measurement\","; 744 | 745 | if (strlen(haDescriptor[i][3]) != 0) 746 | haPayLoad += (String) "\"dev_cla\":\"" + haDescriptor[i][3] + "\","; 747 | haPayLoad += haDeviceDescription; 748 | haPayLoad += "}"; 749 | sprintf(topBuff, "homeassistant/sensor/%s/%s/config", _settings.data.mqttTopic, haDescriptor[i][0]); // build the topic 750 | mqttclient.beginPublish(topBuff, haPayLoad.length(), true); 751 | for (size_t i = 0; i < haPayLoad.length(); i++) 752 | { 753 | mqttclient.write(haPayLoad[i]); 754 | } 755 | mqttclient.endPublish(); 756 | } 757 | } 758 | 759 | // switch 760 | String haPayLoad = String("{") + 761 | "\"name\":\"Remote_Control\"," + 762 | "\"command_topic\":\"" + _settings.data.mqttTopic + "/Remote_Control\"," + 763 | "\"stat_t\":\"" + _settings.data.mqttTopic + "/Remote_Control_State\"," + 764 | "\"uniq_id\":\"" + mqttClientId + ".Remote_Control\"," + 765 | "\"avty_t\":\"" + _settings.data.mqttTopic + "/Alive\"," + 766 | "\"pl_avail\": \"true\"," + 767 | "\"pl_not_avail\": \"false\"," + 768 | "\"ic\":\"mdi:toggle-switch-off\"," + 769 | "\"pl_on\":\"true\"," + 770 | "\"pl_off\":\"false\"," + 771 | "\"stat_on\":\"true\"," + 772 | "\"stat_off\":\"false\","; 773 | 774 | haPayLoad += haDeviceDescription; 775 | haPayLoad += "}"; 776 | sprintf(topBuff, "homeassistant/switch/%s/%s/config", _settings.data.mqttTopic, "Remote_Control"); // build the topic 777 | 778 | mqttclient.beginPublish(topBuff, haPayLoad.length(), true); 779 | for (size_t i = 0; i < haPayLoad.length(); i++) 780 | { 781 | mqttclient.write(haPayLoad[i]); 782 | } 783 | mqttclient.endPublish(); 784 | 785 | return true; 786 | } 787 | 788 | void handleTemperatureChange(int deviceIndex, int32_t temperatureRAW) 789 | { 790 | float tempCels = tempSens.rawToCelsius(temperatureRAW); 791 | if (tempCels <= -55 || tempCels >= 125) 792 | return; 793 | writeLog("<DS18x> DS18B20_%d Celsius:%f", deviceIndex + 1, tempCels); 794 | char msgBuffer[8]; 795 | jsonESP["DS18B20_" + String(deviceIndex + 1)] = dtostrf(tempCels, 4, 2, msgBuffer); 796 | } 797 | 798 | void writeLog(const char *format, ...) 799 | { 800 | char msg[256]; 801 | va_list args; 802 | 803 | va_start(args, format); 804 | vsnprintf(msg, sizeof(msg), format, args); // do check return value 805 | va_end(args); 806 | 807 | // write msg to the log 808 | DBG_PRINTLN(msg); 809 | DBG_WEBLN(msg); 810 | } -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #define ARDUINOJSON_USE_DOUBLE 0 2 | #define ARDUINOJSON_USE_LONG_LONG 0 3 | 4 | #define VICTRON_BAUD 19200 // baud rate for modbus 5 | #define MYPORT_TX 12 6 | #define MYPORT_RX 13 7 | #define LED_PIN 02 //D4 with the LED on Wemos D1 Mini 8 | #define TEMPSENS_PIN 04 // DS18B20 Pin 9 | #define TIME_INTERVAL 2500 // Time interval among sensor readings [milliseconds] 10 | #define JSON_BUFFER 1536//2048 11 | 12 | #define FlashSize ESP.getFreeSketchSpace() 13 | #define ESP01 14 | #ifdef ARDUINO_ESP8266_ESP01 15 | #ifdef MYPORT_TX 16 | #undef MYPORT_TX 17 | #define MYPORT_TX 0 18 | #endif 19 | #ifdef MYPORT_RX 20 | #undef MYPORT_RX 21 | #define MYPORT_RX 2 22 | #ifdef ESP01 23 | #undef ESP01 24 | #define ESP01 "display: none;" 25 | #endif 26 | #endif 27 | #endif 28 | 29 | //for wifimanager fork test 30 | #define CORE_DEBUG_LEVEL 0 31 | 32 | #define SOFTWARE_VERSION SWVERSION 33 | #define DBG_BAUD 115200 34 | #define DBG Serial 35 | #define DBG_WEBLN(...) webSerial.println(__VA_ARGS__) 36 | #define DBG_SERIAL(...) DBG(__VA_ARGS__) 37 | #define DBG_BEGIN(...) DBG.begin(__VA_ARGS__) 38 | #define DBG_PRINTLN(...) DBG.println(__VA_ARGS__) 39 | 40 | typedef struct { 41 | byte bootcount; 42 | bool remoteControlState; 43 | } rtcData; 44 | 45 | bool getJsonData(); 46 | 47 | void notifyClients(); 48 | 49 | void mqttCallback(char *top, byte *payload, unsigned int length); 50 | 51 | bool sendtoMQTT(); 52 | 53 | /** 54 | * @brief function for uart callback to prozess avaible data 55 | * 56 | */ 57 | void prozessData(); 58 | 59 | /*** 60 | * 61 | * 62 | */ 63 | bool sendHaDiscovery(); 64 | 65 | /** 66 | * @brief function for ext. TempSensors 67 | */ 68 | void handleTemperatureChange(int deviceIndex, int32_t temperatureRAW); 69 | 70 | bool remoteControl(bool sw); 71 | 72 | /** 73 | * @brief this function act like s/n/printf() and give the output to the configured serial and webserial 74 | * 75 | */ 76 | void writeLog(const char* format, ...); -------------------------------------------------------------------------------- /src/status-LED.h: -------------------------------------------------------------------------------- 1 | #include <Arduino.h> 2 | /* 3 | Blinking LED = Relais Off 4 | Waveing LED = Relais On 5 | every 5 seconds: 6 | 1x all ok - Working 7 | 2x no Device Connection 8 | 3x no MQTT Connection 9 | 4x no WiFi Connection 10 | 11 | */ 12 | extern Settings _settings; 13 | bool ledPin = 0; 14 | unsigned int ledTimer = 0; 15 | unsigned int repeatTime = 5000; 16 | unsigned int cycleTime = 250; 17 | unsigned int cycleMillis = 0; 18 | byte ledState = 0; 19 | 20 | //bool waveHelper = false; 21 | void notificationLED() 22 | { 23 | if (millis() > (ledTimer + repeatTime) && ledState == 0) 24 | { 25 | if (WiFi.status() != WL_CONNECTED) 26 | ledState = 4; 27 | else if (!mqttclient.connected() && strlen(_settings.data.mqttServer) > 0) 28 | ledState = 3; 29 | else if (strcmp(myve.veValue[0], "") == 0) 30 | ledState = 2; 31 | else if (WiFi.status() == WL_CONNECTED && (mqttclient.connected() || strlen(_settings.data.mqttServer) <= 0) && !myve.veError) 32 | ledState = 1; 33 | } 34 | 35 | if (ledState > 0) 36 | { 37 | if (millis() > (cycleMillis + cycleTime)) 38 | { 39 | if (!ledPin) 40 | { 41 | ledPin = true; 42 | } 43 | else 44 | { 45 | ledPin = false; 46 | ledState--; 47 | } 48 | cycleMillis = millis(); 49 | if (ledState == 0) 50 | { 51 | ledTimer = millis(); 52 | } 53 | } 54 | 55 | } 56 | if (!ledPin) 57 | { 58 | analogWrite(LED_PIN, 255); 59 | } else 60 | { 61 | analogWrite(LED_PIN, 255-_settings.data.LEDBrightness); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/webpages/HTML_CONFIRM_RESET.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 | <figure class="text-center"> 3 | <h1>Erase all Data?</h1> 4 | </figure> 5 | <div class="d-grid gap-2"> 6 | <a class="btn btn-danger" href="/reset" role="button">Yes</a> 7 | <a class="btn btn-primary" href="/settings" role="button">No</a> 8 | </div> 9 | %pre_foot_template% 10 | <p hidden>Hidden Helper</p> -------------------------------------------------------------------------------- /src/webpages/HTML_FOOT.html: -------------------------------------------------------------------------------- 1 | <figure class="text-center"> 2 | Victron2MQTT <a id="software_version">%pre_software_version%</a> By <a 3 | href="https://github.com/softwarecrash/Victron2MQTT/" target="_blank">Softwarecrash</a> 4 | <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/" target="_blank"><img 5 | alt="Creative Commons License" style="border-width:0" 6 | src="https://licensebuttons.net/l/by-nc-nd/4.0/80x15.png" /></a> 7 | </figure> 8 | </div> 9 | <div id="update_alert" style="display: none;"> 10 | <figure class="text-center"><a id="fwdownload" target="_blank">Download the latest version <b 11 | id="gitversion"></b></a></figure> 12 | </div> 13 | <script> 14 | $(document).ready(function () { 15 | $.getJSON("https://api.github.com/repos/softwarecrash/Victron2MQTT/releases/latest", function () { 16 | }) 17 | .done(function (data) { 18 | console.log("get data from github done success"); 19 | $('#fwdownload').attr('href', data.html_url); 20 | $('#gitversion').text(data.tag_name.substring(1)); 21 | let x = data.tag_name.substring(1).split('.').map(e => parseInt(e)); 22 | let y = "%pre_swversion%".split('.').map(e => parseInt(e)); 23 | let z = ""; 24 | for (i = 0; i < x.length; i++) { if (x[i] === y[i]) { z += "e"; } else if (x[i] > y[i]) { z += "m"; } else { z += "l"; } } 25 | if (!z.match(/[l|m]/g)) { 26 | console.log("Git-Version equal, nothing to do."); 27 | } else if (z.split('e').join('')[0] == "m") { 28 | console.log("Git-Version higher, activate notification."); 29 | document.getElementById("update_alert").style.display = ''; 30 | } else { console.log("Git-Version lower, nothing to do."); } 31 | }) 32 | .fail(function () { 33 | console.log("error can not get version"); 34 | }); 35 | }); 36 | </script> 37 | </body> 38 | 39 | </html> 40 | -------------------------------------------------------------------------------- /src/webpages/HTML_HEAD.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en" xml:lang="en" data-bs-theme="%pre_darkmode%"> 3 | 4 | <head> 5 | <meta http-equiv="content-type" content="text/html;charset=UTF-8"> 6 | <meta name="viewport" content="width=device-width, initial-scale=1"> 7 | <link rel="shortcut icon" 8 | href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAHsSURBVDhPrZLLSxtRFMZHY9A2Wou0FB+oiZUWTaPEVo15qInBjamSqLFVk0rV0ke0saAmgg90o5v42NQHCmYkXehC/wbFuK//gG6yyiIki0Dg69zDEEgp46L+4HKHc8/3zTnnXg7/yf0b7O7tIZVK0XcikaBdCjKIx+MI8jzGxj+hWadHu9mC94NDlHAXZBAIrENTVw+O49LrlaYOfv8cJUnBnV9cYNjlJlGOPBfZMnnaZGrqBw6DQTH131AFLtcHEjDxQ0UBunvsMBhMWFhcoiQpyODj6Fj6rz6fH03NOmpL2/BGmEkLnZdXVGJ+YRFPnj5DKPSLxAzu5uYWkUgEXba3KHxchP39AyhVVVheXoFarYHZ0oHPX76iplYNj2cCptY29DsHRLlYQTKZxOnpGaLRKFRV1dAbjLAJhsUlZdBqX8PpfIeS0jKhCiW+e73gsmQkZpABIxaL4ff1NbXxzeNBVnYOFPmPUP3iJazWTuQ9UNCQJyYnKef4+IR0aQMGzx/Bbu/Fz+0dDA4Nw+0ewfTMLFZX1+imWIw9NEdvPzY2t0iTYeAT7r1Fb4TBaBIeUwfNRal6TiU7HH0UY2dt7WY0NulIk2HwN2y4FosV8tw8XIbDYjQTSYPw1RUC6xvU0mGQF6OZSBrcDfAHIwsaPAvZdQgAAAAASUVORK5CYII=" /> 9 | <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" 10 | integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous"> 11 | <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> 12 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" 13 | integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" 14 | crossorigin="anonymous"></script> 15 | <title>%pre_device_name% 16 | 17 | 18 | 22 |
23 | -------------------------------------------------------------------------------- /src/webpages/HTML_MAIN.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 | 7 | 8 |
9 |

10 |
11 | 12 |
13 |
14 |
15 |
17 |
18 |
19 |
20 | 21 |
22 |
Battery:
23 |
24 | 25 | 26 |
27 |
28 |
29 |
Panel:
30 |
31 | 32 | 33 |
34 |
35 |
36 |
Load:
37 |
38 | 39 |
40 |
41 | 42 |
43 |
Load out:
44 |
45 |
46 | 47 |
48 |
Relay:
49 |
50 |
51 | 52 |
53 |
Remote Control:
54 |
55 |
57 |
58 |
59 | 60 |
61 | Settings 62 |
63 | 64 | 172 | %pre_foot_template% 173 | -------------------------------------------------------------------------------- /src/webpages/HTML_REBOOT.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 |
3 |

Rebooting

4 |

.

5 |
6 |
7 | Main 8 |
9 | 10 | 44 | 45 | %pre_foot_template% 46 | -------------------------------------------------------------------------------- /src/webpages/HTML_SETTINGS.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 | 3 |
4 |

Settings

5 |
6 |
7 | 8 |
9 |
10 | 12 | 13 |
14 |
15 | 16 | 24 | Configure 25 | Reboot 26 | Reset ESP 27 | WebSerial 28 | Back 29 |
30 | 31 | 81 | 82 | %pre_foot_template% 83 | -------------------------------------------------------------------------------- /src/webpages/HTML_SETTINGS_EDIT.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 |
3 |

Edit Configuration

4 |
5 |
6 |
7 | Device Name 8 | 10 |
11 |
12 | MQTT Server 13 | 15 |
16 |
17 | MQTT Port 18 | 20 |
21 |
22 | MQTT User 23 | 25 |
26 |
27 | MQTT Password 28 | 30 |
31 |
32 | MQTT Topic 33 | 35 |
36 |
37 | MQTT Refresh (sec) 38 | 40 |
41 |
42 | MQTT Data Trigger Path 43 | 45 |
46 |
47 | MQTT Json Style 48 |
49 | 51 |
52 |
53 |
54 | HA Discovery 55 |
56 | 58 |
59 |
60 |
61 | WebUI Dark Mode 62 |
63 | 65 |
66 |
67 |
68 | keep RC State 69 |
70 | 72 |
73 |
74 | 75 |
76 | LED brightness 77 |
78 | 80 |
81 |
82 | 91 |
92 | HTTP Username 93 | 95 |
96 |
97 | HTTP Password 98 | 100 |
101 | 102 |
103 | 104 | 105 | Back 106 |
107 | 108 | 120 | 121 | %pre_foot_template% 122 | -------------------------------------------------------------------------------- /tools/mini_html.py: -------------------------------------------------------------------------------- 1 | Import("env") 2 | import os 3 | import glob 4 | from pathlib import Path 5 | import sys 6 | import pip 7 | import subprocess 8 | import pkg_resources 9 | 10 | def ensure_module_version(package_name, required_version): 11 | try: 12 | installed_version = pkg_resources.get_distribution(package_name).version 13 | if installed_version == required_version: 14 | print(f"{package_name} {required_version} is already installed.") 15 | return 16 | else: 17 | print(f"{package_name} version {installed_version} found – replacing with {required_version}.") 18 | except pkg_resources.DistributionNotFound: 19 | print(f"{package_name} is not installed – installing version {required_version}.") 20 | 21 | # Install the required version (will upgrade or downgrade as needed) 22 | subprocess.check_call(['pip', 'install', '--upgrade', f'{package_name}=={required_version}']) 23 | 24 | 25 | ensure_module_version("minify_html", "0.15.0") 26 | import minify_html 27 | 28 | filePath = 'src/webpages/' 29 | 30 | try: 31 | print("==========================") 32 | print("Generating webpage") 33 | print("==========================") 34 | print("Preparing html.h file from source") 35 | print(" -insert header") 36 | cpp_output = "#pragma once\n\n#include // PROGMEM\n\n" 37 | print(" -insert html") 38 | 39 | for x in glob.glob(filePath+"*.html"): 40 | print("prozessing file:" + Path(x).stem) 41 | print(Path(x).stem) 42 | cpp_output += "static const char "+Path(x).stem+"[] PROGMEM = R\"rawliteral(" 43 | f = open(x, "r") 44 | #if env.GetProjectOption("build_type") == "debug": 45 | # content = f.read() 46 | #else: 47 | # content = minify_html.minify(f.read(), minify_js=True) 48 | content = minify_html.minify(f.read(), minify_js=True) 49 | #content = f.read() 50 | cpp_output += content 51 | f.close() 52 | cpp_output += ")rawliteral\";\n" 53 | #cpp_output += "#define " +Path(x).stem+ "_LEN " + str(len(content)) +"\n" 54 | 55 | 56 | f = open ("./src/html.h", "w") 57 | f.write(cpp_output) 58 | f.close() 59 | print("==========================\n") 60 | 61 | except SyntaxError as e: 62 | print(e) -------------------------------------------------------------------------------- /tools/post_compile.py: -------------------------------------------------------------------------------- 1 | Import("env") 2 | import os 3 | import shutil 4 | import gzip 5 | 6 | def post_program_action(source, target, env): 7 | 8 | targetfile = os.path.abspath(target[0].get_abspath()) 9 | filename = os.path.basename(targetfile) 10 | startpath = os.path.dirname(targetfile) 11 | destpath = os.path.normpath(os.path.join(startpath, '../../../.firmware')) 12 | 13 | # if it should be placed in a subfolder of the environment (e.g. 'd1_mini'), comment out the line above and uncomment the two below 14 | #basedir = os.path.basename(startpath) 15 | #destpath = os.path.normpath(os.path.join(startpath, '../../../.firmware', basedir)) 16 | 17 | print("\nCopying " + filename + " file to the build directory...\n") 18 | print("Target file: " + targetfile) 19 | print("Destination directory: " + destpath) 20 | 21 | # create directories if they don't exist 22 | if not os.path.exists(destpath): 23 | os.makedirs(destpath) 24 | 25 | # copy the target file to the destination, if it exist 26 | if os.path.exists(targetfile): 27 | shutil.copy(targetfile, destpath) 28 | with open(destpath+'/'+filename, 'rb') as src, gzip.open(destpath+'/'+os.path.splitext(filename)[0]+'_OTA.bin.gz', 'wb') as dst: dst.writelines(src) 29 | 30 | env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", post_program_action) 31 | -------------------------------------------------------------------------------- /tools/pre_compile.py: -------------------------------------------------------------------------------- 1 | Import("env") 2 | 3 | env.Append(CPPDEFINES=[ 4 | ("SWVERSION", env.StringifyMacro(env.GetProjectOption("custom_prog_version"))), 5 | ("HWBOARD", env.StringifyMacro(env["PIOENV"])), 6 | ]) 7 | 8 | env.Replace(PROGNAME="Victron2MQTT_%s_%s" % (str(env["PIOENV"]), env.GetProjectOption("custom_prog_version"))) 9 | --------------------------------------------------------------------------------