├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report2.yaml │ ├── config.yml │ ├── feature_request2.yaml │ ├── help.yaml │ └── working_device.yaml └── workflows │ ├── ISSUE_WORKFLOW.yaml │ └── update_central_repo.yml ├── .gitignore ├── LICENSE ├── Protocol ├── Deye Modbus储能-组串-微逆宁波德业V118.pdf ├── HS_MS_MSX RS232 Protocol 20140603B (1).pdf ├── PH1800 PV1800 EP1800 PV3500 EP3500 RS485 Modbud RTU communication Protocol 1.4.15 (1).xlsx ├── PI16_VoltronicPowerSUNNYProtocol.md ├── PI18_InfiniSolar-V-protocol-20170926.pdf ├── PI30MAX.Communication.Protocol20210217.pdf ├── PI30REVO.20Protocol.20V03--20201112.pdf ├── PI30_Communication-Protocol-20150924-Customer.pdf ├── PI30_HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf ├── PI30_PIP-GK_MK-Protocol.pdf ├── PI41_LV5048.5KW.protocol-20190222.for.customer.pdf └── readme.MD ├── README.md ├── images └── ToDo.md ├── include └── readme.md ├── lib └── pubsubclient-2.8.13 │ ├── .gitignore │ ├── .travis.yml │ ├── CHANGES.txt │ ├── LICENSE.txt │ ├── README.md │ ├── examples │ ├── mqtt_auth │ │ └── mqtt_auth.ino │ ├── mqtt_basic │ │ └── mqtt_basic.ino │ ├── mqtt_esp8266 │ │ └── mqtt_esp8266.ino │ ├── mqtt_large_message │ │ └── mqtt_large_message.ino │ ├── mqtt_publish_in_callback │ │ └── mqtt_publish_in_callback.ino │ ├── mqtt_reconnect_nonblocking │ │ └── mqtt_reconnect_nonblocking.ino │ └── mqtt_stream │ │ └── mqtt_stream.ino │ ├── keywords.txt │ ├── library.json │ ├── library.properties │ ├── src │ ├── PubSubClient.cpp │ └── PubSubClient.h │ └── tests │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── src │ ├── connect_spec.cpp │ ├── keepalive_spec.cpp │ ├── lib │ │ ├── Arduino.h │ │ ├── BDDTest.cpp │ │ ├── BDDTest.h │ │ ├── Buffer.cpp │ │ ├── Buffer.h │ │ ├── Client.h │ │ ├── IPAddress.cpp │ │ ├── IPAddress.h │ │ ├── Print.h │ │ ├── ShimClient.cpp │ │ ├── ShimClient.h │ │ ├── Stream.cpp │ │ ├── Stream.h │ │ └── trace.h │ ├── publish_spec.cpp │ ├── receive_spec.cpp │ └── subscribe_spec.cpp │ ├── testcases │ ├── __init__.py │ ├── mqtt_basic.py │ ├── mqtt_publish_in_callback.py │ └── settings.py │ └── testsuite.py ├── platformio.ini ├── src ├── PI_Serial │ ├── PI_Serial.cpp │ ├── PI_Serial.h │ ├── Q1.h │ ├── Q1_old.h │ ├── QEX.h │ ├── QFLAG.h │ ├── QMN.h │ ├── QMOD.h │ ├── QPI.h │ ├── QPIGS.h │ ├── QPIGS2.h │ ├── QPIRI.h │ └── QPIWS.h ├── Settings.h ├── htmlProzessor.h ├── main.cpp ├── main.h ├── modbus │ ├── modbus.cpp │ ├── modbus.h │ └── modbus_registers.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.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://donate.softwarecrash.de'] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report2.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.5.1 60 | - 0.3.3 61 | - 0.3.1 62 | - 0.3.0 63 | - 0.2.x (Depreciated) 64 | validations: 65 | required: true 66 | - type: textarea 67 | id: output 68 | attributes: 69 | label: Relevant livejson output 70 | 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. 71 | render: json 72 | validations: 73 | required: true 74 | 75 | - type: dropdown 76 | id: browsers 77 | attributes: 78 | label: What browsers are you seeing the problem on? 79 | multiple: true 80 | options: 81 | - Firefox 82 | - Chrome 83 | - Safari 84 | - Microsoft Edge 85 | - no Issue with the Browser or WebUI 86 | validations: 87 | required: true 88 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request2.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: input 11 | id: device-manufacturer 12 | attributes: 13 | label: Please enter the name of the device manufacturer 14 | description: Tell us the device manufacturer 15 | placeholder: enter device manufacturer! 16 | validations: 17 | required: true 18 | - type: input 19 | id: device-type 20 | attributes: 21 | label: Please enter the device type 22 | description: Also tell us the device type 23 | placeholder: enter device type! 24 | validations: 25 | required: true 26 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /Protocol/Deye Modbus储能-组串-微逆宁波德业V118.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/Deye Modbus储能-组串-微逆宁波德业V118.pdf -------------------------------------------------------------------------------- /Protocol/HS_MS_MSX RS232 Protocol 20140603B (1).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/HS_MS_MSX RS232 Protocol 20140603B (1).pdf -------------------------------------------------------------------------------- /Protocol/PH1800 PV1800 EP1800 PV3500 EP3500 RS485 Modbud RTU communication Protocol 1.4.15 (1).xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/PH1800 PV1800 EP1800 PV3500 EP3500 RS485 Modbud RTU communication Protocol 1.4.15 (1).xlsx -------------------------------------------------------------------------------- /Protocol/PI18_InfiniSolar-V-protocol-20170926.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/PI18_InfiniSolar-V-protocol-20170926.pdf -------------------------------------------------------------------------------- /Protocol/PI30MAX.Communication.Protocol20210217.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/PI30MAX.Communication.Protocol20210217.pdf -------------------------------------------------------------------------------- /Protocol/PI30REVO.20Protocol.20V03--20201112.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/PI30REVO.20Protocol.20V03--20201112.pdf -------------------------------------------------------------------------------- /Protocol/PI30_Communication-Protocol-20150924-Customer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/PI30_Communication-Protocol-20150924-Customer.pdf -------------------------------------------------------------------------------- /Protocol/PI30_HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/PI30_HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf -------------------------------------------------------------------------------- /Protocol/PI30_PIP-GK_MK-Protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/PI30_PIP-GK_MK-Protocol.pdf -------------------------------------------------------------------------------- /Protocol/PI41_LV5048.5KW.protocol-20190222.for.customer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/Protocol/PI41_LV5048.5KW.protocol-20190222.for.customer.pdf -------------------------------------------------------------------------------- /Protocol/readme.MD: -------------------------------------------------------------------------------- 1 | some protocol source 2 | https://github.com/jblance/mpp-solar/tree/master/docs/protocols 3 | https://github.com/syssi/esphome-pipsolar 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solar2MQTT [![GitHub release](https://img.shields.io/github/release/softwarecrash/Solar2MQTT?include_prereleases=&sort=semver&color=blue)](https://github.com/softwarecrash/Solar2MQTT/releases/latest) [![Discord](https://img.shields.io/discord/1007020337482973254?logo=discord&label=Discord)](https://discord.gg/fb2nZWDExz) 2 | 3 | # Features: 4 | - captive portal for wifi and MQTT config 5 | - config in webinterface 6 | - Full Controll with [Custom commands](https://github.com/softwarecrash/Solar2MQTT/wiki/Set-parameters) 7 | - get essential data over webinterface, get [all data](https://github.com/softwarecrash/Solar2MQTT/wiki/Datapoints-and-units) over MQTT 8 | - classic MQTT datapoints or Json string over MQTT 9 | - get Json over web at /livejson? 10 | - firmware update over webinterface 11 | - debug log on Wemos USB or Webserial 12 | - [blink codes](https://github.com/softwarecrash/Solar2MQTT/wiki/Blink-Codes) for the current state of the ESP 13 | - [Reset functions](https://github.com/softwarecrash/Solar2MQTT/wiki/Reset) 14 | - [Support Home Assistant](https://github.com/softwarecrash/Solar2MQTT/wiki/HomeAssistant-integration) 15 | - with Teapod 16 | 17 | 18 | 19 | 20 | **works with** 21 | - Most devices that use the watchpower PC Software 22 | - Most devices that use the Solarpower PC Software 23 | - PIP devices 24 | - i solar 25 | - IGrid 26 | - Many devices from EASUN 27 | - and many many others based on the chinese solar inverter with a rj45 jack and usb port, primary identified by the display 28 | - Take a look at the [device list in the wiki](https://github.com/softwarecrash/Solar2MQTT/wiki/Confirmed-Working-Device-List) 29 | 30 | 31 | **Main screen:** 32 | 33 | ![image](https://github.com/softwarecrash/Solar2MQTT/assets/17761850/de945ad5-29ad-476e-9562-a0eba1b4f2ce) 34 | 35 | **Settings:** 36 | 37 | ![image](https://github.com/softwarecrash/Solar2MQTT/assets/17761850/075d1e66-3912-4a33-b7d3-a52da99c8553) 38 | 39 | **Config:** 40 | 41 | ![image](https://github.com/softwarecrash/Solar2MQTT/assets/17761850/823093bf-8abe-4b7e-913f-7bac9420d108) 42 | 43 | 44 | 45 | 46 | 47 | # How to use: 48 | - flash your ESP8266 (recommended Wemos D1 Mini) with our [Flash2MQTT-Tool](https://all-solutions.github.io/Flash2MQTT/?get=Solar2MQTT) or with [Tasmotizer](https://github.com/tasmota/tasmotizer/releases) 49 | - connect the ESP like the [wiring diagram](https://github.com/softwarecrash/Solar2MQTT/wiki/Wiring-Diagram) 50 | - search for the wifi ap "Solar2MQTT-AP" and connect to it 51 | - surf to 192.168.4.1 and set up your wifi and optional MQTT 52 | - that's it :) 53 | 54 | ### How-To video by Jarnsen 55 | 56 | <a href="http://www.youtube.com/watch?feature=player_embedded&v=7u8hPLdXeso" target="_blank"> 57 | <img src="http://img.youtube.com/vi/7u8hPLdXeso/0.jpg" alt="Watch the video" /> 58 | </a> 59 | 60 | 61 | 62 | **POWER:** Using a 3.3V DC Buck Converter that can handle up to 20V or a DC/DC or USB power currently. 63 | 64 | # Parts required to build 65 | 66 | Most of the parts can be bought as modules, it's usually cheaper that way. 67 | 68 | - ESP8266 - Wemos D1 Mini or ESP8266-01 69 | - MAX3232 module Like this https://amzn.eu/d/8t3gk5t or https://bit.ly/3BFPqrw or with orginal cable https://www.amazon.de/dp/B09XWPTDYP 70 | - DC-DC buck module - 12-80v down to 5v 71 | 72 | # Completely assembled and tested PCB's 73 | 74 | 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. 75 | 76 | ![Solar-MQTT-PCB](https://user-images.githubusercontent.com/17761850/233859179-cc9c9075-b88a-4f38-b804-bc0f409cf8ce.png) 77 | 78 | If interested see [here](https://all-solutions.store) 79 | 80 | # 81 | Questions? 82 | [Join the Discord Channel (German / English)](https://discord.gg/pAArqVsVS4) 83 | 84 | # 85 | [<img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"/>](https://donate.softwarecrash.de) 86 | 87 | [![LICENSE](https://licensebuttons.net/l/by-nc-nd/4.0/88x31.png)](https://creativecommons.org/licenses/by-nc-nd/4.0/) 88 | 89 | -------------------------------------------------------------------------------- /images/ToDo.md: -------------------------------------------------------------------------------- 1 | wiki: 2 | - ~~verbundungsdiagramme einbauen~~ 3 | - anleitung um herauszufinden ob der inverter mit der soft funktioniert 4 | - fehlende infos ergänzen 5 | 6 | readme.md 7 | - ~~neue bilder~~ 8 | - ~~allgemein mal drüber schauen und neu formatieren~~ 9 | 10 | diesen ordner ~~mit bildern löschen~~ wenn fertig 11 | -------------------------------------------------------------------------------- /include/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/include/readme.md -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/.gitignore: -------------------------------------------------------------------------------- 1 | tests/bin 2 | .pioenvs 3 | .piolibdeps 4 | .clang_complete 5 | .gcc-flags.json 6 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: cpp 3 | compiler: 4 | - g++ 5 | script: cd tests && make && make test 6 | os: 7 | - linux 8 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/CHANGES.txt: -------------------------------------------------------------------------------- 1 | 2.8 2 | * Add setBufferSize() to override MQTT_MAX_PACKET_SIZE 3 | * Add setKeepAlive() to override MQTT_KEEPALIVE 4 | * Add setSocketTimeout() to overide MQTT_SOCKET_TIMEOUT 5 | * Added check to prevent subscribe/unsubscribe to empty topics 6 | * Declare wifi mode prior to connect in ESP example 7 | * Use `strnlen` to avoid overruns 8 | * Support pre-connected Client objects 9 | 10 | 2.7 11 | * Fix remaining-length handling to prevent buffer overrun 12 | * Add large-payload API - beginPublish/write/publish/endPublish 13 | * Add yield call to improve reliability on ESP 14 | * Add Clean Session flag to connect options 15 | * Add ESP32 support for functional callback signature 16 | * Various other fixes 17 | 18 | 2.4 19 | * Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely 20 | whilst waiting for inbound data 21 | * Fixed return code when publishing >256 bytes 22 | 23 | 2.3 24 | * Add publish(topic,payload,retained) function 25 | 26 | 2.2 27 | * Change code layout to match Arduino Library reqs 28 | 29 | 2.1 30 | * Add MAX_TRANSFER_SIZE def to chunk messages if needed 31 | * Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE 32 | 33 | 2.0 34 | * Add (and default to) MQTT 3.1.1 support 35 | * Fix PROGMEM handling for Intel Galileo/ESP8266 36 | * Add overloaded constructors for convenience 37 | * Add chainable setters for server/callback/client/stream 38 | * Add state function to return connack return code 39 | 40 | 1.9 41 | * Do not split MQTT packets over multiple calls to _client->write() 42 | * API change: All constructors now require an instance of Client 43 | to be passed in. 44 | * Fixed example to match 1.8 api changes - dpslwk 45 | * Added username/password support - WilHall 46 | * Added publish_P - publishes messages from PROGMEM - jobytaffey 47 | 48 | 1.8 49 | * KeepAlive interval is configurable in PubSubClient.h 50 | * Maximum packet size is configurable in PubSubClient.h 51 | * API change: Return boolean rather than int from various functions 52 | * API change: Length parameter in message callback changed 53 | from int to unsigned int 54 | * Various internal tidy-ups around types 55 | 1.7 56 | * Improved keepalive handling 57 | * Updated to the Arduino-1.0 API 58 | 1.6 59 | * Added the ability to publish a retained message 60 | 61 | 1.5 62 | * Added default constructor 63 | * Fixed compile error when used with arduino-0021 or later 64 | 65 | 1.4 66 | * Fixed connection lost handling 67 | 68 | 1.3 69 | * Fixed packet reading bug in PubSubClient.readPacket 70 | 71 | 1.2 72 | * Fixed compile error when used with arduino-0016 or later 73 | 74 | 75 | 1.1 76 | * Reduced size of library 77 | * Added support for Will messages 78 | * Clarified licensing - see LICENSE.txt 79 | 80 | 81 | 1.0 82 | * Only Quality of Service (QOS) 0 messaging is supported 83 | * The maximum message size, including header, is 128 bytes 84 | * The keepalive interval is set to 30 seconds 85 | * No support for Will messages 86 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2020 Nicholas O'Leary 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/README.md: -------------------------------------------------------------------------------- 1 | # Arduino Client for MQTT 2 | 3 | This library provides a client for doing simple publish/subscribe messaging with 4 | a server that supports MQTT. 5 | 6 | ## Examples 7 | 8 | The library comes with a number of example sketches. See File > Examples > PubSubClient 9 | within the Arduino application. 10 | 11 | Full API documentation is available here: https://pubsubclient.knolleary.net 12 | 13 | ## Limitations 14 | 15 | - It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1. 16 | - The maximum message size, including header, is **256 bytes** by default. This 17 | is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h` or can be changed 18 | by calling `PubSubClient::setBufferSize(size)`. 19 | - The keepalive interval is set to 15 seconds by default. This is configurable 20 | via `MQTT_KEEPALIVE` in `PubSubClient.h` or can be changed by calling 21 | `PubSubClient::setKeepAlive(keepAlive)`. 22 | - The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by 23 | changing value of `MQTT_VERSION` in `PubSubClient.h`. 24 | 25 | 26 | ## Compatible Hardware 27 | 28 | The library uses the Arduino Ethernet Client api for interacting with the 29 | underlying network hardware. This means it Just Works with a growing number of 30 | boards and shields, including: 31 | 32 | - Arduino Ethernet 33 | - Arduino Ethernet Shield 34 | - Arduino YUN – use the included `YunClient` in place of `EthernetClient`, and 35 | be sure to do a `Bridge.begin()` first 36 | - Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield, 37 | enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`. 38 | - Sparkfun WiFly Shield – [library](https://github.com/dpslwk/WiFly) 39 | - TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library) 40 | - Intel Galileo/Edison 41 | - ESP8266 42 | - ESP32 43 | 44 | The library cannot currently be used with hardware based on the ENC28J60 chip – 45 | such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an 46 | [alternative library](https://github.com/njh/NanodeMQTT) available. 47 | 48 | ## License 49 | 50 | This code is released under the MIT License. 51 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/examples/mqtt_auth/mqtt_auth.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Basic MQTT example with Authentication 3 | 4 | - connects to an MQTT server, providing username 5 | and password 6 | - publishes "hello world" to the topic "outTopic" 7 | - subscribes to the topic "inTopic" 8 | */ 9 | 10 | #include <SPI.h> 11 | #include <Ethernet.h> 12 | #include <PubSubClient.h> 13 | 14 | // Update these with values suitable for your network. 15 | byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; 16 | IPAddress ip(172, 16, 0, 100); 17 | IPAddress server(172, 16, 0, 2); 18 | 19 | void callback(char* topic, byte* payload, unsigned int length) { 20 | // handle message arrived 21 | } 22 | 23 | EthernetClient ethClient; 24 | PubSubClient client(server, 1883, callback, ethClient); 25 | 26 | void setup() 27 | { 28 | Ethernet.begin(mac, ip); 29 | // Note - the default maximum packet size is 128 bytes. If the 30 | // combined length of clientId, username and password exceed this use the 31 | // following to increase the buffer size: 32 | // client.setBufferSize(255); 33 | 34 | if (client.connect("arduinoClient", "testuser", "testpass")) { 35 | client.publish("outTopic","hello world"); 36 | client.subscribe("inTopic"); 37 | } 38 | } 39 | 40 | void loop() 41 | { 42 | client.loop(); 43 | } 44 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/examples/mqtt_basic/mqtt_basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Basic MQTT example 3 | 4 | This sketch demonstrates the basic capabilities of the library. 5 | It connects to an MQTT server then: 6 | - publishes "hello world" to the topic "outTopic" 7 | - subscribes to the topic "inTopic", printing out any messages 8 | it receives. NB - it assumes the received payloads are strings not binary 9 | 10 | It will reconnect to the server if the connection is lost using a blocking 11 | reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to 12 | achieve the same result without blocking the main loop. 13 | 14 | */ 15 | 16 | #include <SPI.h> 17 | #include <Ethernet.h> 18 | #include <PubSubClient.h> 19 | 20 | // Update these with values suitable for your network. 21 | byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; 22 | IPAddress ip(172, 16, 0, 100); 23 | IPAddress server(172, 16, 0, 2); 24 | 25 | void callback(char* topic, byte* payload, unsigned int length) { 26 | Serial.print("Message arrived ["); 27 | Serial.print(topic); 28 | Serial.print("] "); 29 | for (int i=0;i<length;i++) { 30 | Serial.print((char)payload[i]); 31 | } 32 | Serial.println(); 33 | } 34 | 35 | EthernetClient ethClient; 36 | PubSubClient client(ethClient); 37 | 38 | void reconnect() { 39 | // Loop until we're reconnected 40 | while (!client.connected()) { 41 | Serial.print("Attempting MQTT connection..."); 42 | // Attempt to connect 43 | if (client.connect("arduinoClient")) { 44 | Serial.println("connected"); 45 | // Once connected, publish an announcement... 46 | client.publish("outTopic","hello world"); 47 | // ... and resubscribe 48 | client.subscribe("inTopic"); 49 | } else { 50 | Serial.print("failed, rc="); 51 | Serial.print(client.state()); 52 | Serial.println(" try again in 5 seconds"); 53 | // Wait 5 seconds before retrying 54 | delay(5000); 55 | } 56 | } 57 | } 58 | 59 | void setup() 60 | { 61 | Serial.begin(57600); 62 | 63 | client.setServer(server, 1883); 64 | client.setCallback(callback); 65 | 66 | Ethernet.begin(mac, ip); 67 | // Allow the hardware to sort itself out 68 | delay(1500); 69 | } 70 | 71 | void loop() 72 | { 73 | if (!client.connected()) { 74 | reconnect(); 75 | } 76 | client.loop(); 77 | } 78 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/examples/mqtt_esp8266/mqtt_esp8266.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Basic ESP8266 MQTT example 3 | This sketch demonstrates the capabilities of the pubsub library in combination 4 | with the ESP8266 board/library. 5 | It connects to an MQTT server then: 6 | - publishes "hello world" to the topic "outTopic" every two seconds 7 | - subscribes to the topic "inTopic", printing out any messages 8 | it receives. NB - it assumes the received payloads are strings not binary 9 | - If the first character of the topic "inTopic" is an 1, switch ON the ESP Led, 10 | else switch it off 11 | It will reconnect to the server if the connection is lost using a blocking 12 | reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to 13 | achieve the same result without blocking the main loop. 14 | To install the ESP8266 board, (using Arduino 1.6.4+): 15 | - Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs": 16 | http://arduino.esp8266.com/stable/package_esp8266com_index.json 17 | - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266" 18 | - Select your ESP8266 in "Tools -> Board" 19 | */ 20 | 21 | #include <ESP8266WiFi.h> 22 | #include <PubSubClient.h> 23 | 24 | // Update these with values suitable for your network. 25 | 26 | const char* ssid = "........"; 27 | const char* password = "........"; 28 | const char* mqtt_server = "broker.mqtt-dashboard.com"; 29 | 30 | WiFiClient espClient; 31 | PubSubClient client(espClient); 32 | unsigned long lastMsg = 0; 33 | #define MSG_BUFFER_SIZE (50) 34 | char msg[MSG_BUFFER_SIZE]; 35 | int value = 0; 36 | 37 | void setup_wifi() { 38 | 39 | delay(10); 40 | // We start by connecting to a WiFi network 41 | Serial.println(); 42 | Serial.print("Connecting to "); 43 | Serial.println(ssid); 44 | 45 | WiFi.mode(WIFI_STA); 46 | WiFi.begin(ssid, password); 47 | 48 | while (WiFi.status() != WL_CONNECTED) { 49 | delay(500); 50 | Serial.print("."); 51 | } 52 | 53 | randomSeed(micros()); 54 | 55 | Serial.println(""); 56 | Serial.println("WiFi connected"); 57 | Serial.println("IP address: "); 58 | Serial.println(WiFi.localIP()); 59 | } 60 | 61 | void callback(char* topic, byte* payload, unsigned int length) { 62 | Serial.print("Message arrived ["); 63 | Serial.print(topic); 64 | Serial.print("] "); 65 | for (int i = 0; i < length; i++) { 66 | Serial.print((char)payload[i]); 67 | } 68 | Serial.println(); 69 | 70 | // Switch on the LED if an 1 was received as first character 71 | if ((char)payload[0] == '1') { 72 | digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level 73 | // but actually the LED is on; this is because 74 | // it is active low on the ESP-01) 75 | } else { 76 | digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH 77 | } 78 | 79 | } 80 | 81 | void reconnect() { 82 | // Loop until we're reconnected 83 | while (!client.connected()) { 84 | Serial.print("Attempting MQTT connection..."); 85 | // Create a random client ID 86 | String clientId = "ESP8266Client-"; 87 | clientId += String(random(0xffff), HEX); 88 | // Attempt to connect 89 | if (client.connect(clientId.c_str())) { 90 | Serial.println("connected"); 91 | // Once connected, publish an announcement... 92 | client.publish("outTopic", "hello world"); 93 | // ... and resubscribe 94 | client.subscribe("inTopic"); 95 | } else { 96 | Serial.print("failed, rc="); 97 | Serial.print(client.state()); 98 | Serial.println(" try again in 5 seconds"); 99 | // Wait 5 seconds before retrying 100 | delay(5000); 101 | } 102 | } 103 | } 104 | 105 | void setup() { 106 | pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output 107 | Serial.begin(115200); 108 | setup_wifi(); 109 | client.setServer(mqtt_server, 1883); 110 | client.setCallback(callback); 111 | } 112 | 113 | void loop() { 114 | 115 | if (!client.connected()) { 116 | reconnect(); 117 | } 118 | client.loop(); 119 | 120 | unsigned long now = millis(); 121 | if (now - lastMsg > 2000) { 122 | lastMsg = now; 123 | ++value; 124 | snprintf (msg, MSG_BUFFER_SIZE, "hello world #%ld", value); 125 | Serial.print("Publish message: "); 126 | Serial.println(msg); 127 | client.publish("outTopic", msg); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/examples/mqtt_large_message/mqtt_large_message.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Long message ESP8266 MQTT example 3 | 4 | This sketch demonstrates sending arbitrarily large messages in combination 5 | with the ESP8266 board/library. 6 | 7 | It connects to an MQTT server then: 8 | - publishes "hello world" to the topic "outTopic" 9 | - subscribes to the topic "greenBottles/#", printing out any messages 10 | it receives. NB - it assumes the received payloads are strings not binary 11 | - If the sub-topic is a number, it publishes a "greenBottles/lyrics" message 12 | with a payload consisting of the lyrics to "10 green bottles", replacing 13 | 10 with the number given in the sub-topic. 14 | 15 | It will reconnect to the server if the connection is lost using a blocking 16 | reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to 17 | achieve the same result without blocking the main loop. 18 | 19 | To install the ESP8266 board, (using Arduino 1.6.4+): 20 | - Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs": 21 | http://arduino.esp8266.com/stable/package_esp8266com_index.json 22 | - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266" 23 | - Select your ESP8266 in "Tools -> Board" 24 | 25 | */ 26 | 27 | #include <ESP8266WiFi.h> 28 | #include <PubSubClient.h> 29 | 30 | // Update these with values suitable for your network. 31 | 32 | const char* ssid = "........"; 33 | const char* password = "........"; 34 | const char* mqtt_server = "broker.mqtt-dashboard.com"; 35 | 36 | WiFiClient espClient; 37 | PubSubClient client(espClient); 38 | long lastMsg = 0; 39 | char msg[50]; 40 | int value = 0; 41 | 42 | void setup_wifi() { 43 | 44 | delay(10); 45 | // We start by connecting to a WiFi network 46 | Serial.println(); 47 | Serial.print("Connecting to "); 48 | Serial.println(ssid); 49 | 50 | WiFi.begin(ssid, password); 51 | 52 | while (WiFi.status() != WL_CONNECTED) { 53 | delay(500); 54 | Serial.print("."); 55 | } 56 | 57 | randomSeed(micros()); 58 | 59 | Serial.println(""); 60 | Serial.println("WiFi connected"); 61 | Serial.println("IP address: "); 62 | Serial.println(WiFi.localIP()); 63 | } 64 | 65 | void callback(char* topic, byte* payload, unsigned int length) { 66 | Serial.print("Message arrived ["); 67 | Serial.print(topic); 68 | Serial.print("] "); 69 | for (int i = 0; i < length; i++) { 70 | Serial.print((char)payload[i]); 71 | } 72 | Serial.println(); 73 | 74 | // Find out how many bottles we should generate lyrics for 75 | String topicStr(topic); 76 | int bottleCount = 0; // assume no bottles unless we correctly parse a value from the topic 77 | if (topicStr.indexOf('/') >= 0) { 78 | // The topic includes a '/', we'll try to read the number of bottles from just after that 79 | topicStr.remove(0, topicStr.indexOf('/')+1); 80 | // Now see if there's a number of bottles after the '/' 81 | bottleCount = topicStr.toInt(); 82 | } 83 | 84 | if (bottleCount > 0) { 85 | // Work out how big our resulting message will be 86 | int msgLen = 0; 87 | for (int i = bottleCount; i > 0; i--) { 88 | String numBottles(i); 89 | msgLen += 2*numBottles.length(); 90 | if (i == 1) { 91 | msgLen += 2*String(" green bottle, standing on the wall\n").length(); 92 | } else { 93 | msgLen += 2*String(" green bottles, standing on the wall\n").length(); 94 | } 95 | msgLen += String("And if one green bottle should accidentally fall\nThere'll be ").length(); 96 | switch (i) { 97 | case 1: 98 | msgLen += String("no green bottles, standing on the wall\n\n").length(); 99 | break; 100 | case 2: 101 | msgLen += String("1 green bottle, standing on the wall\n\n").length(); 102 | break; 103 | default: 104 | numBottles = i-1; 105 | msgLen += numBottles.length(); 106 | msgLen += String(" green bottles, standing on the wall\n\n").length(); 107 | break; 108 | }; 109 | } 110 | 111 | // Now we can start to publish the message 112 | client.beginPublish("greenBottles/lyrics", msgLen, false); 113 | for (int i = bottleCount; i > 0; i--) { 114 | for (int j = 0; j < 2; j++) { 115 | client.print(i); 116 | if (i == 1) { 117 | client.print(" green bottle, standing on the wall\n"); 118 | } else { 119 | client.print(" green bottles, standing on the wall\n"); 120 | } 121 | } 122 | client.print("And if one green bottle should accidentally fall\nThere'll be "); 123 | switch (i) { 124 | case 1: 125 | client.print("no green bottles, standing on the wall\n\n"); 126 | break; 127 | case 2: 128 | client.print("1 green bottle, standing on the wall\n\n"); 129 | break; 130 | default: 131 | client.print(i-1); 132 | client.print(" green bottles, standing on the wall\n\n"); 133 | break; 134 | }; 135 | } 136 | // Now we're done! 137 | client.endPublish(); 138 | } 139 | } 140 | 141 | void reconnect() { 142 | // Loop until we're reconnected 143 | while (!client.connected()) { 144 | Serial.print("Attempting MQTT connection..."); 145 | // Create a random client ID 146 | String clientId = "ESP8266Client-"; 147 | clientId += String(random(0xffff), HEX); 148 | // Attempt to connect 149 | if (client.connect(clientId.c_str())) { 150 | Serial.println("connected"); 151 | // Once connected, publish an announcement... 152 | client.publish("outTopic", "hello world"); 153 | // ... and resubscribe 154 | client.subscribe("greenBottles/#"); 155 | } else { 156 | Serial.print("failed, rc="); 157 | Serial.print(client.state()); 158 | Serial.println(" try again in 5 seconds"); 159 | // Wait 5 seconds before retrying 160 | delay(5000); 161 | } 162 | } 163 | } 164 | 165 | void setup() { 166 | pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output 167 | Serial.begin(115200); 168 | setup_wifi(); 169 | client.setServer(mqtt_server, 1883); 170 | client.setCallback(callback); 171 | } 172 | 173 | void loop() { 174 | 175 | if (!client.connected()) { 176 | reconnect(); 177 | } 178 | client.loop(); 179 | } 180 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Publishing in the callback 3 | 4 | - connects to an MQTT server 5 | - subscribes to the topic "inTopic" 6 | - when a message is received, republishes it to "outTopic" 7 | 8 | This example shows how to publish messages within the 9 | callback function. The callback function header needs to 10 | be declared before the PubSubClient constructor and the 11 | actual callback defined afterwards. 12 | This ensures the client reference in the callback function 13 | is valid. 14 | 15 | */ 16 | 17 | #include <SPI.h> 18 | #include <Ethernet.h> 19 | #include <PubSubClient.h> 20 | 21 | // Update these with values suitable for your network. 22 | byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; 23 | IPAddress ip(172, 16, 0, 100); 24 | IPAddress server(172, 16, 0, 2); 25 | 26 | // Callback function header 27 | void callback(char* topic, byte* payload, unsigned int length); 28 | 29 | EthernetClient ethClient; 30 | PubSubClient client(server, 1883, callback, ethClient); 31 | 32 | // Callback function 33 | void callback(char* topic, byte* payload, unsigned int length) { 34 | // In order to republish this payload, a copy must be made 35 | // as the orignal payload buffer will be overwritten whilst 36 | // constructing the PUBLISH packet. 37 | 38 | // Allocate the correct amount of memory for the payload copy 39 | byte* p = (byte*)malloc(length); 40 | // Copy the payload to the new buffer 41 | memcpy(p,payload,length); 42 | client.publish("outTopic", p, length); 43 | // Free the memory 44 | free(p); 45 | } 46 | 47 | void setup() 48 | { 49 | 50 | Ethernet.begin(mac, ip); 51 | if (client.connect("arduinoClient")) { 52 | client.publish("outTopic","hello world"); 53 | client.subscribe("inTopic"); 54 | } 55 | } 56 | 57 | void loop() 58 | { 59 | client.loop(); 60 | } 61 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Reconnecting MQTT example - non-blocking 3 | 4 | This sketch demonstrates how to keep the client connected 5 | using a non-blocking reconnect function. If the client loses 6 | its connection, it attempts to reconnect every 5 seconds 7 | without blocking the main loop. 8 | 9 | */ 10 | 11 | #include <SPI.h> 12 | #include <Ethernet.h> 13 | #include <PubSubClient.h> 14 | 15 | // Update these with values suitable for your hardware/network. 16 | byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; 17 | IPAddress ip(172, 16, 0, 100); 18 | IPAddress server(172, 16, 0, 2); 19 | 20 | void callback(char* topic, byte* payload, unsigned int length) { 21 | // handle message arrived 22 | } 23 | 24 | EthernetClient ethClient; 25 | PubSubClient client(ethClient); 26 | 27 | long lastReconnectAttempt = 0; 28 | 29 | boolean reconnect() { 30 | if (client.connect("arduinoClient")) { 31 | // Once connected, publish an announcement... 32 | client.publish("outTopic","hello world"); 33 | // ... and resubscribe 34 | client.subscribe("inTopic"); 35 | } 36 | return client.connected(); 37 | } 38 | 39 | void setup() 40 | { 41 | client.setServer(server, 1883); 42 | client.setCallback(callback); 43 | 44 | Ethernet.begin(mac, ip); 45 | delay(1500); 46 | lastReconnectAttempt = 0; 47 | } 48 | 49 | 50 | void loop() 51 | { 52 | if (!client.connected()) { 53 | long now = millis(); 54 | if (now - lastReconnectAttempt > 5000) { 55 | lastReconnectAttempt = now; 56 | // Attempt to reconnect 57 | if (reconnect()) { 58 | lastReconnectAttempt = 0; 59 | } 60 | } 61 | } else { 62 | // Client connected 63 | 64 | client.loop(); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/examples/mqtt_stream/mqtt_stream.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Example of using a Stream object to store the message payload 3 | 4 | Uses SRAM library: https://github.com/ennui2342/arduino-sram 5 | but could use any Stream based class such as SD 6 | 7 | - connects to an MQTT server 8 | - publishes "hello world" to the topic "outTopic" 9 | - subscribes to the topic "inTopic" 10 | */ 11 | 12 | #include <SPI.h> 13 | #include <Ethernet.h> 14 | #include <PubSubClient.h> 15 | #include <SRAM.h> 16 | 17 | // Update these with values suitable for your network. 18 | byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; 19 | IPAddress ip(172, 16, 0, 100); 20 | IPAddress server(172, 16, 0, 2); 21 | 22 | SRAM sram(4, SRAM_1024); 23 | 24 | void callback(char* topic, byte* payload, unsigned int length) { 25 | sram.seek(1); 26 | 27 | // do something with the message 28 | for(uint8_t i=0; i<length; i++) { 29 | Serial.write(sram.read()); 30 | } 31 | Serial.println(); 32 | 33 | // Reset position for the next message to be stored 34 | sram.seek(1); 35 | } 36 | 37 | EthernetClient ethClient; 38 | PubSubClient client(server, 1883, callback, ethClient, sram); 39 | 40 | void setup() 41 | { 42 | Ethernet.begin(mac, ip); 43 | if (client.connect("arduinoClient")) { 44 | client.publish("outTopic","hello world"); 45 | client.subscribe("inTopic"); 46 | } 47 | 48 | sram.begin(); 49 | sram.seek(1); 50 | 51 | Serial.begin(9600); 52 | } 53 | 54 | void loop() 55 | { 56 | client.loop(); 57 | } 58 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For PubSubClient 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | PubSubClient KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | connect KEYWORD2 16 | disconnect KEYWORD2 17 | publish KEYWORD2 18 | publish_P KEYWORD2 19 | beginPublish KEYWORD2 20 | endPublish KEYWORD2 21 | write KEYWORD2 22 | subscribe KEYWORD2 23 | unsubscribe KEYWORD2 24 | loop KEYWORD2 25 | connected KEYWORD2 26 | setServer KEYWORD2 27 | setCallback KEYWORD2 28 | setClient KEYWORD2 29 | setStream KEYWORD2 30 | setKeepAlive KEYWORD2 31 | setBufferSize KEYWORD2 32 | setSocketTimeout KEYWORD2 33 | 34 | ####################################### 35 | # Constants (LITERAL1) 36 | ####################################### 37 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PubSubClient", 3 | "keywords": "ethernet, mqtt, m2m, iot", 4 | "description": "A client library for MQTT messaging. MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/knolleary/pubsubclient.git" 8 | }, 9 | "version": "2.8", 10 | "exclude": "tests", 11 | "examples": "examples/*/*.ino", 12 | "frameworks": "arduino", 13 | "platforms": [ 14 | "atmelavr", 15 | "espressif8266", 16 | "espressif32" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/library.properties: -------------------------------------------------------------------------------- 1 | name=PubSubClient 2 | version=2.8 3 | author=Nick O'Leary <nick.oleary@gmail.com> 4 | maintainer=Nick O'Leary <nick.oleary@gmail.com> 5 | sentence=A client library for MQTT messaging. 6 | paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000. 7 | category=Communication 8 | url=http://pubsubclient.knolleary.net 9 | architectures=esp8266,esp32 10 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/src/PubSubClient.h: -------------------------------------------------------------------------------- 1 | /* 2 | PubSubClient.h - A simple client for MQTT. 3 | Nick O'Leary 4 | http://knolleary.net 5 | */ 6 | 7 | #ifndef PubSubClient_h 8 | #define PubSubClient_h 9 | 10 | #include <Arduino.h> 11 | #include "IPAddress.h" 12 | #include "Client.h" 13 | #include "Stream.h" 14 | 15 | #define MQTT_VERSION_3_1 3 16 | #define MQTT_VERSION_3_1_1 4 17 | 18 | // MQTT_VERSION : Pick the version 19 | //#define MQTT_VERSION MQTT_VERSION_3_1 20 | #ifndef MQTT_VERSION 21 | #define MQTT_VERSION MQTT_VERSION_3_1_1 22 | #endif 23 | 24 | // MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize(). 25 | #ifndef MQTT_MAX_PACKET_SIZE 26 | //#define MQTT_MAX_PACKET_SIZE 256 27 | #define MQTT_MAX_PACKET_SIZE 1200 // Tasmota v8.1.0.8 28 | #endif 29 | 30 | // MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive() 31 | #ifndef MQTT_KEEPALIVE 32 | #define MQTT_KEEPALIVE 15 33 | #endif 34 | 35 | // MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout() 36 | #ifndef MQTT_SOCKET_TIMEOUT 37 | #define MQTT_SOCKET_TIMEOUT 15 38 | #endif 39 | 40 | // MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client 41 | // in each write call. Needed for the Arduino Wifi Shield. Leave undefined to 42 | // pass the entire MQTT packet in each write call. 43 | //#define MQTT_MAX_TRANSFER_SIZE 80 44 | 45 | // Possible values for client.state() 46 | #define MQTT_CONNECTION_TIMEOUT -4 47 | #define MQTT_CONNECTION_LOST -3 48 | #define MQTT_CONNECT_FAILED -2 49 | #define MQTT_DISCONNECTED -1 50 | #define MQTT_CONNECTED 0 51 | #define MQTT_CONNECT_BAD_PROTOCOL 1 52 | #define MQTT_CONNECT_BAD_CLIENT_ID 2 53 | #define MQTT_CONNECT_UNAVAILABLE 3 54 | #define MQTT_CONNECT_BAD_CREDENTIALS 4 55 | #define MQTT_CONNECT_UNAUTHORIZED 5 56 | 57 | #define MQTTCONNECT 1 << 4 // Client request to connect to Server 58 | #define MQTTCONNACK 2 << 4 // Connect Acknowledgment 59 | #define MQTTPUBLISH 3 << 4 // Publish message 60 | #define MQTTPUBACK 4 << 4 // Publish Acknowledgment 61 | #define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1) 62 | #define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2) 63 | #define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3) 64 | #define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request 65 | #define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment 66 | #define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request 67 | #define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment 68 | #define MQTTPINGREQ 12 << 4 // PING Request 69 | #define MQTTPINGRESP 13 << 4 // PING Response 70 | #define MQTTDISCONNECT 14 << 4 // Client is Disconnecting 71 | #define MQTTReserved 15 << 4 // Reserved 72 | 73 | #define MQTTQOS0 (0 << 1) 74 | #define MQTTQOS1 (1 << 1) 75 | #define MQTTQOS2 (2 << 1) 76 | 77 | // Maximum size of fixed header and variable length size header 78 | #define MQTT_MAX_HEADER_SIZE 5 79 | 80 | #if defined(ESP8266) || defined(ESP32) 81 | #include <functional> 82 | #define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback 83 | #else 84 | #define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int) 85 | #endif 86 | 87 | #define CHECK_STRING_LENGTH(l,s) if (l+2+strnlen(s, this->bufferSize) > this->bufferSize) {_client->stop();return false;} 88 | 89 | class PubSubClient : public Print { 90 | private: 91 | Client* _client; 92 | uint8_t* buffer; 93 | uint16_t bufferSize; 94 | uint16_t keepAlive; 95 | uint16_t socketTimeout; 96 | uint16_t nextMsgId; 97 | unsigned long lastOutActivity; 98 | unsigned long lastInActivity; 99 | bool pingOutstanding; 100 | MQTT_CALLBACK_SIGNATURE; 101 | uint32_t readPacket(uint8_t*); 102 | boolean readByte(uint8_t * result); 103 | boolean readByte(uint8_t * result, uint16_t * index); 104 | boolean write(uint8_t header, uint8_t* buf, uint16_t length); 105 | uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); 106 | // Build up the header ready to send 107 | // Returns the size of the header 108 | // Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start 109 | // (MQTT_MAX_HEADER_SIZE - <returned size>) bytes into the buffer 110 | size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length); 111 | IPAddress ip; 112 | 113 | // Start Tasmota patch 114 | // const char* domain; 115 | 116 | String domain; 117 | // End Tasmota patch 118 | 119 | uint16_t port; 120 | Stream* stream; 121 | int _state; 122 | public: 123 | PubSubClient(); 124 | PubSubClient(Client& client); 125 | PubSubClient(IPAddress, uint16_t, Client& client); 126 | PubSubClient(IPAddress, uint16_t, Client& client, Stream&); 127 | PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); 128 | PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); 129 | PubSubClient(uint8_t *, uint16_t, Client& client); 130 | PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); 131 | PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); 132 | PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); 133 | PubSubClient(const char*, uint16_t, Client& client); 134 | PubSubClient(const char*, uint16_t, Client& client, Stream&); 135 | PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); 136 | PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); 137 | 138 | ~PubSubClient(); 139 | 140 | PubSubClient& setServer(IPAddress ip, uint16_t port); 141 | PubSubClient& setServer(uint8_t * ip, uint16_t port); 142 | PubSubClient& setServer(const char * domain, uint16_t port); 143 | PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); 144 | PubSubClient& setClient(Client& client); 145 | PubSubClient& setStream(Stream& stream); 146 | PubSubClient& setKeepAlive(uint16_t keepAlive); 147 | PubSubClient& setSocketTimeout(uint16_t timeout); 148 | 149 | boolean setBufferSize(uint16_t size); 150 | uint16_t getBufferSize(); 151 | 152 | boolean connect(const char* id); 153 | boolean connect(const char* id, const char* user, const char* pass); 154 | boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); 155 | boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); 156 | boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession); 157 | 158 | // Start Tasmota patch 159 | // void disconnect(); 160 | 161 | void disconnect(bool disconnect_package = false); 162 | // End Tasmota patch 163 | 164 | boolean publish(const char* topic, const char* payload); 165 | boolean publish(const char* topic, const char* payload, boolean retained); 166 | boolean publish(const char* topic, const uint8_t * payload, unsigned int plength); 167 | boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); 168 | boolean publish_P(const char* topic, const char* payload, boolean retained); 169 | boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); 170 | // Start to publish a message. 171 | // This API: 172 | // beginPublish(...) 173 | // one or more calls to write(...) 174 | // endPublish() 175 | // Allows for arbitrarily large payloads to be sent without them having to be copied into 176 | // a new buffer and held in memory at one time 177 | // Returns 1 if the message was started successfully, 0 if there was an error 178 | boolean beginPublish(const char* topic, unsigned int plength, boolean retained); 179 | // Finish off this publish message (started with beginPublish) 180 | // Returns 1 if the packet was sent successfully, 0 if there was an error 181 | int endPublish(); 182 | // Write a single byte of payload (only to be used with beginPublish/endPublish) 183 | virtual size_t write(uint8_t); 184 | // Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish) 185 | // Returns the number of bytes written 186 | virtual size_t write(const uint8_t *buffer, size_t size); 187 | boolean subscribe(const char* topic); 188 | boolean subscribe(const char* topic, uint8_t qos); 189 | boolean unsubscribe(const char* topic); 190 | boolean loop(); 191 | boolean connected(); 192 | int state(); 193 | 194 | }; 195 | 196 | 197 | #endif 198 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | tmpbin 3 | logs 4 | *.pyc 5 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/Makefile: -------------------------------------------------------------------------------- 1 | SRC_PATH=./src 2 | OUT_PATH=./bin 3 | TEST_SRC=$(wildcard ${SRC_PATH}/*_spec.cpp) 4 | TEST_BIN= $(TEST_SRC:${SRC_PATH}/%.cpp=${OUT_PATH}/%) 5 | VPATH=${SRC_PATH} 6 | SHIM_FILES=${SRC_PATH}/lib/*.cpp 7 | PSC_FILE=../src/PubSubClient.cpp 8 | CC=g++ 9 | CFLAGS=-I${SRC_PATH}/lib -I../src 10 | 11 | all: $(TEST_BIN) 12 | 13 | ${OUT_PATH}/%: ${SRC_PATH}/%.cpp ${PSC_FILE} ${SHIM_FILES} 14 | mkdir -p ${OUT_PATH} 15 | ${CC} ${CFLAGS} $^ -o $@ 16 | 17 | clean: 18 | @rm -rf ${OUT_PATH} 19 | 20 | test: 21 | @bin/connect_spec 22 | @bin/publish_spec 23 | @bin/receive_spec 24 | @bin/subscribe_spec 25 | @bin/keepalive_spec 26 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/README.md: -------------------------------------------------------------------------------- 1 | # Arduino Client for MQTT Test Suite 2 | 3 | This is a regression test suite for the `PubSubClient` library. 4 | 5 | There are two parts: 6 | 7 | - Tests that can be compiled and run on any machine 8 | - Tests that build the example sketches using the Arduino IDE 9 | 10 | 11 | It is a work-in-progress and is subject to complete refactoring as the whim takes 12 | me. 13 | 14 | 15 | ## Local tests 16 | 17 | These are a set of executables that can be run to test specific areas of functionality. 18 | They do not require a real Arduino to be attached, nor the use of the Arduino IDE. 19 | 20 | The tests include a set of mock files to stub out the parts of the Arduino environment the library 21 | depends on. 22 | 23 | ### Dependencies 24 | 25 | - g++ 26 | 27 | ### Running 28 | 29 | Build the tests using the provided `Makefile`: 30 | 31 | $ make 32 | 33 | This will create a set of executables in `./bin/`. Run each of these executables to test the corresponding functionality. 34 | 35 | *Note:* the `connect_spec` and `keepalive_spec` tests involve testing keepalive timers so naturally take a few minutes to run through. 36 | 37 | ## Arduino tests 38 | 39 | *Note:* INO Tool doesn't currently play nicely with Arduino 1.5. This has broken this test suite. 40 | 41 | Without a suitable arduino plugged in, the test suite will only check the 42 | example sketches compile cleanly against the library. 43 | 44 | With an arduino plugged in, each sketch that has a corresponding python 45 | test case is built, uploaded and then the tests run. 46 | 47 | ### Dependencies 48 | 49 | - Python 2.7+ 50 | - [INO Tool](http://inotool.org/) - this provides command-line build/upload of Arduino sketches 51 | 52 | ### Running 53 | 54 | The test suite _does not_ run an MQTT server - it is assumed to be running already. 55 | 56 | $ python testsuite.py 57 | 58 | A summary of activity is printed to the console. More comprehensive logs are written 59 | to the `logs` directory. 60 | 61 | ### What it does 62 | 63 | For each sketch in the library's `examples` directory, e.g. `mqtt_basic.ino`, the suite looks for a matching test case 64 | `testcases/mqtt_basic.py`. 65 | 66 | The test case must follow these conventions: 67 | - sub-class `unittest.TestCase` 68 | - provide the class methods `setUpClass` and `tearDownClass` (TODO: make this optional) 69 | - all test method names begin with `test_` 70 | 71 | The suite will call the `setUpClass` method _before_ uploading the sketch. This 72 | allows any test setup to be performed before the sketch runs - such as connecting 73 | a client and subscribing to topics. 74 | 75 | 76 | ### Settings 77 | 78 | The file `testcases/settings.py` is used to config the test environment. 79 | 80 | - `server_ip` - the IP address of the broker the client should connect to (the broker port is assumed to be 1883). 81 | - `arduino_ip` - the IP address the arduino should use (when not testing DHCP). 82 | 83 | Before each sketch is compiled, these values are automatically substituted in. To 84 | do this, the suite looks for lines that _start_ with the following: 85 | 86 | byte server[] = { 87 | byte ip[] = { 88 | 89 | and replaces them with the appropriate values. 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/keepalive_spec.cpp: -------------------------------------------------------------------------------- 1 | #include "PubSubClient.h" 2 | #include "ShimClient.h" 3 | #include "Buffer.h" 4 | #include "BDDTest.h" 5 | #include "trace.h" 6 | #include <unistd.h> 7 | 8 | byte server[] = { 172, 16, 0, 2 }; 9 | 10 | void callback(char* topic, byte* payload, unsigned int length) { 11 | // handle message arrived 12 | } 13 | 14 | 15 | int test_keepalive_pings_idle() { 16 | IT("keeps an idle connection alive (takes 1 minute)"); 17 | 18 | ShimClient shimClient; 19 | shimClient.setAllowConnect(true); 20 | 21 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 22 | shimClient.respond(connack,4); 23 | 24 | PubSubClient client(server, 1883, callback, shimClient); 25 | int rc = client.connect((char*)"client_test1"); 26 | IS_TRUE(rc); 27 | 28 | byte pingreq[] = { 0xC0,0x0 }; 29 | shimClient.expect(pingreq,2); 30 | byte pingresp[] = { 0xD0,0x0 }; 31 | shimClient.respond(pingresp,2); 32 | 33 | for (int i = 0; i < 50; i++) { 34 | sleep(1); 35 | if ( i == 15 || i == 31 || i == 47) { 36 | shimClient.expect(pingreq,2); 37 | shimClient.respond(pingresp,2); 38 | } 39 | rc = client.loop(); 40 | IS_TRUE(rc); 41 | } 42 | 43 | IS_FALSE(shimClient.error()); 44 | 45 | END_IT 46 | } 47 | 48 | int test_keepalive_pings_with_outbound_qos0() { 49 | IT("keeps a connection alive that only sends qos0 (takes 1 minute)"); 50 | 51 | ShimClient shimClient; 52 | shimClient.setAllowConnect(true); 53 | 54 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 55 | shimClient.respond(connack,4); 56 | 57 | PubSubClient client(server, 1883, callback, shimClient); 58 | int rc = client.connect((char*)"client_test1"); 59 | IS_TRUE(rc); 60 | 61 | byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 62 | 63 | for (int i = 0; i < 50; i++) { 64 | TRACE(i<<":"); 65 | shimClient.expect(publish,16); 66 | rc = client.publish((char*)"topic",(char*)"payload"); 67 | IS_TRUE(rc); 68 | IS_FALSE(shimClient.error()); 69 | sleep(1); 70 | if ( i == 15 || i == 31 || i == 47) { 71 | byte pingreq[] = { 0xC0,0x0 }; 72 | shimClient.expect(pingreq,2); 73 | byte pingresp[] = { 0xD0,0x0 }; 74 | shimClient.respond(pingresp,2); 75 | } 76 | rc = client.loop(); 77 | IS_TRUE(rc); 78 | IS_FALSE(shimClient.error()); 79 | } 80 | 81 | END_IT 82 | } 83 | 84 | int test_keepalive_pings_with_inbound_qos0() { 85 | IT("keeps a connection alive that only receives qos0 (takes 1 minute)"); 86 | 87 | ShimClient shimClient; 88 | shimClient.setAllowConnect(true); 89 | 90 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 91 | shimClient.respond(connack,4); 92 | 93 | PubSubClient client(server, 1883, callback, shimClient); 94 | int rc = client.connect((char*)"client_test1"); 95 | IS_TRUE(rc); 96 | 97 | byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 98 | 99 | for (int i = 0; i < 50; i++) { 100 | TRACE(i<<":"); 101 | sleep(1); 102 | if ( i == 15 || i == 31 || i == 47) { 103 | byte pingreq[] = { 0xC0,0x0 }; 104 | shimClient.expect(pingreq,2); 105 | byte pingresp[] = { 0xD0,0x0 }; 106 | shimClient.respond(pingresp,2); 107 | } 108 | shimClient.respond(publish,16); 109 | rc = client.loop(); 110 | IS_TRUE(rc); 111 | IS_FALSE(shimClient.error()); 112 | } 113 | 114 | END_IT 115 | } 116 | 117 | int test_keepalive_no_pings_inbound_qos1() { 118 | IT("does not send pings for connections with inbound qos1 (takes 1 minute)"); 119 | 120 | ShimClient shimClient; 121 | shimClient.setAllowConnect(true); 122 | 123 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 124 | shimClient.respond(connack,4); 125 | 126 | PubSubClient client(server, 1883, callback, shimClient); 127 | int rc = client.connect((char*)"client_test1"); 128 | IS_TRUE(rc); 129 | 130 | byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 131 | byte puback[] = {0x40,0x2,0x12,0x34}; 132 | 133 | for (int i = 0; i < 50; i++) { 134 | shimClient.respond(publish,18); 135 | shimClient.expect(puback,4); 136 | sleep(1); 137 | rc = client.loop(); 138 | IS_TRUE(rc); 139 | IS_FALSE(shimClient.error()); 140 | } 141 | 142 | END_IT 143 | } 144 | 145 | int test_keepalive_disconnects_hung() { 146 | IT("disconnects a hung connection (takes 30 seconds)"); 147 | 148 | ShimClient shimClient; 149 | shimClient.setAllowConnect(true); 150 | 151 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 152 | shimClient.respond(connack,4); 153 | 154 | PubSubClient client(server, 1883, callback, shimClient); 155 | int rc = client.connect((char*)"client_test1"); 156 | IS_TRUE(rc); 157 | 158 | byte pingreq[] = { 0xC0,0x0 }; 159 | shimClient.expect(pingreq,2); 160 | 161 | for (int i = 0; i < 32; i++) { 162 | sleep(1); 163 | rc = client.loop(); 164 | } 165 | IS_FALSE(rc); 166 | 167 | int state = client.state(); 168 | IS_TRUE(state == MQTT_CONNECTION_TIMEOUT); 169 | 170 | IS_FALSE(shimClient.error()); 171 | 172 | END_IT 173 | } 174 | 175 | int main() 176 | { 177 | SUITE("Keep-alive"); 178 | test_keepalive_pings_idle(); 179 | test_keepalive_pings_with_outbound_qos0(); 180 | test_keepalive_pings_with_inbound_qos0(); 181 | test_keepalive_no_pings_inbound_qos1(); 182 | test_keepalive_disconnects_hung(); 183 | 184 | FINISH 185 | } 186 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/Arduino.h: -------------------------------------------------------------------------------- 1 | #ifndef Arduino_h 2 | #define Arduino_h 3 | 4 | #include <stdint.h> 5 | #include <stdlib.h> 6 | #include <string.h> 7 | #include <math.h> 8 | #include "Print.h" 9 | 10 | 11 | extern "C"{ 12 | typedef uint8_t byte ; 13 | typedef uint8_t boolean ; 14 | 15 | /* sketch */ 16 | extern void setup( void ) ; 17 | extern void loop( void ) ; 18 | uint32_t millis( void ); 19 | } 20 | 21 | #define PROGMEM 22 | #define pgm_read_byte_near(x) *(x) 23 | 24 | #define yield(x) {} 25 | 26 | #endif // Arduino_h 27 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/BDDTest.cpp: -------------------------------------------------------------------------------- 1 | #include "BDDTest.h" 2 | #include "trace.h" 3 | #include <sstream> 4 | #include <iostream> 5 | #include <string> 6 | #include <list> 7 | 8 | int testCount = 0; 9 | int testPasses = 0; 10 | const char* testDescription; 11 | 12 | std::list<std::string> failureList; 13 | 14 | void bddtest_suite(const char* name) { 15 | LOG(name << "\n"); 16 | } 17 | 18 | int bddtest_test(const char* file, int line, const char* assertion, int result) { 19 | if (!result) { 20 | LOG("✗\n"); 21 | std::ostringstream os; 22 | os << " ! "<<testDescription<<"\n " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]"; 23 | failureList.push_back(os.str()); 24 | } 25 | return result; 26 | } 27 | 28 | void bddtest_start(const char* description) { 29 | LOG(" - "<<description<<" "); 30 | testDescription = description; 31 | testCount ++; 32 | } 33 | void bddtest_end() { 34 | LOG("✓\n"); 35 | testPasses ++; 36 | } 37 | 38 | int bddtest_summary() { 39 | for (std::list<std::string>::iterator it = failureList.begin(); it != failureList.end(); it++) { 40 | LOG("\n"); 41 | LOG(*it); 42 | LOG("\n"); 43 | } 44 | 45 | LOG(std::dec << testPasses << "/" << testCount << " tests passed\n\n"); 46 | if (testPasses == testCount) { 47 | return 0; 48 | } 49 | return 1; 50 | } 51 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/BDDTest.h: -------------------------------------------------------------------------------- 1 | #ifndef bddtest_h 2 | #define bddtest_h 3 | 4 | void bddtest_suite(const char* name); 5 | int bddtest_test(const char*, int, const char*, int); 6 | void bddtest_start(const char*); 7 | void bddtest_end(); 8 | int bddtest_summary(); 9 | 10 | #define SUITE(x) { bddtest_suite(x); } 11 | #define TEST(x) { if (!bddtest_test(__FILE__, __LINE__, #x, (x))) return false; } 12 | 13 | #define IT(x) { bddtest_start(x); } 14 | #define END_IT { bddtest_end();return true;} 15 | 16 | #define FINISH { return bddtest_summary(); } 17 | 18 | #define IS_TRUE(x) TEST(x) 19 | #define IS_FALSE(x) TEST(!(x)) 20 | #define IS_EQUAL(x,y) TEST(x==y) 21 | #define IS_NOT_EQUAL(x,y) TEST(x!=y) 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/Buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "Buffer.h" 2 | #include "Arduino.h" 3 | 4 | Buffer::Buffer() { 5 | this->pos = 0; 6 | this->length = 0; 7 | } 8 | 9 | Buffer::Buffer(uint8_t* buf, size_t size) { 10 | this->pos = 0; 11 | this->length = 0; 12 | this->add(buf,size); 13 | } 14 | bool Buffer::available() { 15 | return this->pos < this->length; 16 | } 17 | 18 | uint8_t Buffer::next() { 19 | if (this->available()) { 20 | return this->buffer[this->pos++]; 21 | } 22 | return 0; 23 | } 24 | 25 | void Buffer::reset() { 26 | this->pos = 0; 27 | } 28 | 29 | void Buffer::add(uint8_t* buf, size_t size) { 30 | uint16_t i = 0; 31 | for (;i<size;i++) { 32 | this->buffer[this->length++] = buf[i]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/Buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef buffer_h 2 | #define buffer_h 3 | 4 | #include "Arduino.h" 5 | 6 | class Buffer { 7 | private: 8 | uint8_t buffer[2048]; 9 | uint16_t pos; 10 | uint16_t length; 11 | 12 | public: 13 | Buffer(); 14 | Buffer(uint8_t* buf, size_t size); 15 | 16 | virtual bool available(); 17 | virtual uint8_t next(); 18 | virtual void reset(); 19 | 20 | virtual void add(uint8_t* buf, size_t size); 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/Client.h: -------------------------------------------------------------------------------- 1 | #ifndef client_h 2 | #define client_h 3 | #include "IPAddress.h" 4 | 5 | class Client { 6 | public: 7 | virtual int connect(IPAddress ip, uint16_t port) =0; 8 | virtual int connect(const char *host, uint16_t port) =0; 9 | virtual size_t write(uint8_t) =0; 10 | virtual size_t write(const uint8_t *buf, size_t size) =0; 11 | virtual int available() = 0; 12 | virtual int read() = 0; 13 | virtual int read(uint8_t *buf, size_t size) = 0; 14 | virtual int peek() = 0; 15 | virtual void flush() = 0; 16 | virtual void stop() = 0; 17 | virtual uint8_t connected() = 0; 18 | virtual operator bool() = 0; 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/IPAddress.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include <Arduino.h> 3 | #include <IPAddress.h> 4 | 5 | IPAddress::IPAddress() 6 | { 7 | memset(_address, 0, sizeof(_address)); 8 | } 9 | 10 | IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) 11 | { 12 | _address[0] = first_octet; 13 | _address[1] = second_octet; 14 | _address[2] = third_octet; 15 | _address[3] = fourth_octet; 16 | } 17 | 18 | IPAddress::IPAddress(uint32_t address) 19 | { 20 | memcpy(_address, &address, sizeof(_address)); 21 | } 22 | 23 | IPAddress::IPAddress(const uint8_t *address) 24 | { 25 | memcpy(_address, address, sizeof(_address)); 26 | } 27 | 28 | IPAddress& IPAddress::operator=(const uint8_t *address) 29 | { 30 | memcpy(_address, address, sizeof(_address)); 31 | return *this; 32 | } 33 | 34 | IPAddress& IPAddress::operator=(uint32_t address) 35 | { 36 | memcpy(_address, (const uint8_t *)&address, sizeof(_address)); 37 | return *this; 38 | } 39 | 40 | bool IPAddress::operator==(const uint8_t* addr) 41 | { 42 | return memcmp(addr, _address, sizeof(_address)) == 0; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/IPAddress.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * MIT License: 4 | * Copyright (c) 2011 Adrian McEwen 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * adrianm@mcqn.com 1/1/2011 24 | */ 25 | 26 | #ifndef IPAddress_h 27 | #define IPAddress_h 28 | 29 | 30 | // A class to make it easier to handle and pass around IP addresses 31 | 32 | class IPAddress { 33 | private: 34 | uint8_t _address[4]; // IPv4 address 35 | // Access the raw byte array containing the address. Because this returns a pointer 36 | // to the internal structure rather than a copy of the address this function should only 37 | // be used when you know that the usage of the returned uint8_t* will be transient and not 38 | // stored. 39 | uint8_t* raw_address() { return _address; }; 40 | 41 | public: 42 | // Constructors 43 | IPAddress(); 44 | IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); 45 | IPAddress(uint32_t address); 46 | IPAddress(const uint8_t *address); 47 | 48 | // Overloaded cast operator to allow IPAddress objects to be used where a pointer 49 | // to a four-byte uint8_t array is expected 50 | operator uint32_t() { return *((uint32_t*)_address); }; 51 | bool operator==(const IPAddress& addr) { return (*((uint32_t*)_address)) == (*((uint32_t*)addr._address)); }; 52 | bool operator==(const uint8_t* addr); 53 | 54 | // Overloaded index operator to allow getting and setting individual octets of the address 55 | uint8_t operator[](int index) const { return _address[index]; }; 56 | uint8_t& operator[](int index) { return _address[index]; }; 57 | 58 | // Overloaded copy operators to allow initialisation of IPAddress objects from other types 59 | IPAddress& operator=(const uint8_t *address); 60 | IPAddress& operator=(uint32_t address); 61 | 62 | 63 | friend class EthernetClass; 64 | friend class UDP; 65 | friend class Client; 66 | friend class Server; 67 | friend class DhcpClass; 68 | friend class DNSClient; 69 | }; 70 | 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/Print.h: -------------------------------------------------------------------------------- 1 | /* 2 | Print.h - Base class that provides print() and println() 3 | Copyright (c) 2008 David A. Mellis. All right reserved. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifndef Print_h 21 | #define Print_h 22 | 23 | class Print { 24 | public: 25 | virtual size_t write(uint8_t) = 0; 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/ShimClient.cpp: -------------------------------------------------------------------------------- 1 | #include "ShimClient.h" 2 | #include "trace.h" 3 | #include <iostream> 4 | #include <Arduino.h> 5 | #include <ctime> 6 | 7 | extern "C" { 8 | uint32_t millis(void) { 9 | return time(0)*1000; 10 | } 11 | } 12 | 13 | ShimClient::ShimClient() { 14 | this->responseBuffer = new Buffer(); 15 | this->expectBuffer = new Buffer(); 16 | this->_allowConnect = true; 17 | this->_connected = false; 18 | this->_error = false; 19 | this->expectAnything = true; 20 | this->_received = 0; 21 | this->_expectedPort = 0; 22 | } 23 | 24 | int ShimClient::connect(IPAddress ip, uint16_t port) { 25 | if (this->_allowConnect) { 26 | this->_connected = true; 27 | } 28 | if (this->_expectedPort !=0) { 29 | // if (memcmp(ip,this->_expectedIP,4) != 0) { 30 | // TRACE( "ip mismatch\n"); 31 | // this->_error = true; 32 | // } 33 | if (port != this->_expectedPort) { 34 | TRACE( "port mismatch\n"); 35 | this->_error = true; 36 | } 37 | } 38 | return this->_connected; 39 | } 40 | int ShimClient::connect(const char *host, uint16_t port) { 41 | if (this->_allowConnect) { 42 | this->_connected = true; 43 | } 44 | if (this->_expectedPort !=0) { 45 | if (strcmp(host,this->_expectedHost) != 0) { 46 | TRACE( "host mismatch\n"); 47 | this->_error = true; 48 | } 49 | if (port != this->_expectedPort) { 50 | TRACE( "port mismatch\n"); 51 | this->_error = true; 52 | } 53 | 54 | } 55 | return this->_connected; 56 | } 57 | size_t ShimClient::write(uint8_t b) { 58 | this->_received += 1; 59 | TRACE(std::hex << (unsigned int)b); 60 | if (!this->expectAnything) { 61 | if (this->expectBuffer->available()) { 62 | uint8_t expected = this->expectBuffer->next(); 63 | if (expected != b) { 64 | this->_error = true; 65 | TRACE("!=" << (unsigned int)expected); 66 | } 67 | } else { 68 | this->_error = true; 69 | } 70 | } 71 | TRACE("\n"<< std::dec); 72 | return 1; 73 | } 74 | size_t ShimClient::write(const uint8_t *buf, size_t size) { 75 | this->_received += size; 76 | TRACE( "[" << std::dec << (unsigned int)(size) << "] "); 77 | uint16_t i=0; 78 | for (;i<size;i++) { 79 | if (i>0) { 80 | TRACE(":"); 81 | } 82 | TRACE(std::hex << (unsigned int)(buf[i])); 83 | 84 | if (!this->expectAnything) { 85 | if (this->expectBuffer->available()) { 86 | uint8_t expected = this->expectBuffer->next(); 87 | if (expected != buf[i]) { 88 | this->_error = true; 89 | TRACE("!=" << (unsigned int)expected); 90 | } 91 | } else { 92 | this->_error = true; 93 | } 94 | } 95 | } 96 | TRACE("\n"<<std::dec); 97 | return size; 98 | } 99 | int ShimClient::available() { 100 | return this->responseBuffer->available(); 101 | } 102 | int ShimClient::read() { return this->responseBuffer->next(); } 103 | int ShimClient::read(uint8_t *buf, size_t size) { 104 | uint16_t i = 0; 105 | for (;i<size;i++) { 106 | buf[i] = this->read(); 107 | } 108 | return size; 109 | } 110 | int ShimClient::peek() { return 0; } 111 | void ShimClient::flush() {} 112 | void ShimClient::stop() { 113 | this->setConnected(false); 114 | } 115 | uint8_t ShimClient::connected() { return this->_connected; } 116 | ShimClient::operator bool() { return true; } 117 | 118 | 119 | ShimClient* ShimClient::respond(uint8_t *buf, size_t size) { 120 | this->responseBuffer->add(buf,size); 121 | return this; 122 | } 123 | 124 | ShimClient* ShimClient::expect(uint8_t *buf, size_t size) { 125 | this->expectAnything = false; 126 | this->expectBuffer->add(buf,size); 127 | return this; 128 | } 129 | 130 | void ShimClient::setConnected(bool b) { 131 | this->_connected = b; 132 | } 133 | void ShimClient::setAllowConnect(bool b) { 134 | this->_allowConnect = b; 135 | } 136 | 137 | bool ShimClient::error() { 138 | return this->_error; 139 | } 140 | 141 | uint16_t ShimClient::received() { 142 | return this->_received; 143 | } 144 | 145 | void ShimClient::expectConnect(IPAddress ip, uint16_t port) { 146 | this->_expectedIP = ip; 147 | this->_expectedPort = port; 148 | } 149 | 150 | void ShimClient::expectConnect(const char *host, uint16_t port) { 151 | this->_expectedHost = host; 152 | this->_expectedPort = port; 153 | } 154 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/ShimClient.h: -------------------------------------------------------------------------------- 1 | #ifndef shimclient_h 2 | #define shimclient_h 3 | 4 | #include "Arduino.h" 5 | #include "Client.h" 6 | #include "IPAddress.h" 7 | #include "Buffer.h" 8 | 9 | 10 | class ShimClient : public Client { 11 | private: 12 | Buffer* responseBuffer; 13 | Buffer* expectBuffer; 14 | bool _allowConnect; 15 | bool _connected; 16 | bool expectAnything; 17 | bool _error; 18 | uint16_t _received; 19 | IPAddress _expectedIP; 20 | uint16_t _expectedPort; 21 | const char* _expectedHost; 22 | 23 | public: 24 | ShimClient(); 25 | virtual int connect(IPAddress ip, uint16_t port); 26 | virtual int connect(const char *host, uint16_t port); 27 | virtual size_t write(uint8_t); 28 | virtual size_t write(const uint8_t *buf, size_t size); 29 | virtual int available(); 30 | virtual int read(); 31 | virtual int read(uint8_t *buf, size_t size); 32 | virtual int peek(); 33 | virtual void flush(); 34 | virtual void stop(); 35 | virtual uint8_t connected(); 36 | virtual operator bool(); 37 | 38 | virtual ShimClient* respond(uint8_t *buf, size_t size); 39 | virtual ShimClient* expect(uint8_t *buf, size_t size); 40 | 41 | virtual void expectConnect(IPAddress ip, uint16_t port); 42 | virtual void expectConnect(const char *host, uint16_t port); 43 | 44 | virtual uint16_t received(); 45 | virtual bool error(); 46 | 47 | virtual void setAllowConnect(bool b); 48 | virtual void setConnected(bool b); 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/Stream.cpp: -------------------------------------------------------------------------------- 1 | #include "Stream.h" 2 | #include "trace.h" 3 | #include <iostream> 4 | #include <Arduino.h> 5 | 6 | Stream::Stream() { 7 | this->expectBuffer = new Buffer(); 8 | this->_error = false; 9 | this->_written = 0; 10 | } 11 | 12 | size_t Stream::write(uint8_t b) { 13 | this->_written++; 14 | TRACE(std::hex << (unsigned int)b); 15 | if (this->expectBuffer->available()) { 16 | uint8_t expected = this->expectBuffer->next(); 17 | if (expected != b) { 18 | this->_error = true; 19 | TRACE("!=" << (unsigned int)expected); 20 | } 21 | } else { 22 | this->_error = true; 23 | } 24 | TRACE("\n"<< std::dec); 25 | return 1; 26 | } 27 | 28 | 29 | bool Stream::error() { 30 | return this->_error; 31 | } 32 | 33 | void Stream::expect(uint8_t *buf, size_t size) { 34 | this->expectBuffer->add(buf,size); 35 | } 36 | 37 | uint16_t Stream::length() { 38 | return this->_written; 39 | } 40 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/Stream.h: -------------------------------------------------------------------------------- 1 | #ifndef Stream_h 2 | #define Stream_h 3 | 4 | #include "Arduino.h" 5 | #include "Buffer.h" 6 | 7 | class Stream { 8 | private: 9 | Buffer* expectBuffer; 10 | bool _error; 11 | uint16_t _written; 12 | 13 | public: 14 | Stream(); 15 | virtual size_t write(uint8_t); 16 | 17 | virtual bool error(); 18 | virtual void expect(uint8_t *buf, size_t size); 19 | virtual uint16_t length(); 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/lib/trace.h: -------------------------------------------------------------------------------- 1 | #ifndef trace_h 2 | #define trace_h 3 | #include <iostream> 4 | 5 | #include <stdlib.h> 6 | 7 | #define LOG(x) {std::cout << x << std::flush; } 8 | #define TRACE(x) {if (getenv("TRACE")) { std::cout << x << std::flush; }} 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/publish_spec.cpp: -------------------------------------------------------------------------------- 1 | #include "PubSubClient.h" 2 | #include "ShimClient.h" 3 | #include "Buffer.h" 4 | #include "BDDTest.h" 5 | #include "trace.h" 6 | 7 | 8 | byte server[] = { 172, 16, 0, 2 }; 9 | 10 | void callback(char* topic, byte* payload, unsigned int length) { 11 | // handle message arrived 12 | } 13 | 14 | int test_publish() { 15 | IT("publishes a null-terminated string"); 16 | ShimClient shimClient; 17 | shimClient.setAllowConnect(true); 18 | 19 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 20 | shimClient.respond(connack,4); 21 | 22 | PubSubClient client(server, 1883, callback, shimClient); 23 | int rc = client.connect((char*)"client_test1"); 24 | IS_TRUE(rc); 25 | 26 | byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 27 | shimClient.expect(publish,16); 28 | 29 | rc = client.publish((char*)"topic",(char*)"payload"); 30 | IS_TRUE(rc); 31 | 32 | IS_FALSE(shimClient.error()); 33 | 34 | END_IT 35 | } 36 | 37 | 38 | int test_publish_bytes() { 39 | IT("publishes a byte array"); 40 | ShimClient shimClient; 41 | shimClient.setAllowConnect(true); 42 | 43 | byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; 44 | int length = 5; 45 | 46 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 47 | shimClient.respond(connack,4); 48 | 49 | PubSubClient client(server, 1883, callback, shimClient); 50 | int rc = client.connect((char*)"client_test1"); 51 | IS_TRUE(rc); 52 | 53 | byte publish[] = {0x30,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; 54 | shimClient.expect(publish,14); 55 | 56 | rc = client.publish((char*)"topic",payload,length); 57 | IS_TRUE(rc); 58 | 59 | IS_FALSE(shimClient.error()); 60 | 61 | END_IT 62 | } 63 | 64 | 65 | int test_publish_retained() { 66 | IT("publishes retained - 1"); 67 | ShimClient shimClient; 68 | shimClient.setAllowConnect(true); 69 | 70 | byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; 71 | int length = 5; 72 | 73 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 74 | shimClient.respond(connack,4); 75 | 76 | PubSubClient client(server, 1883, callback, shimClient); 77 | int rc = client.connect((char*)"client_test1"); 78 | IS_TRUE(rc); 79 | 80 | byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; 81 | shimClient.expect(publish,14); 82 | 83 | rc = client.publish((char*)"topic",payload,length,true); 84 | IS_TRUE(rc); 85 | 86 | IS_FALSE(shimClient.error()); 87 | 88 | END_IT 89 | } 90 | 91 | int test_publish_retained_2() { 92 | IT("publishes retained - 2"); 93 | ShimClient shimClient; 94 | shimClient.setAllowConnect(true); 95 | 96 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 97 | shimClient.respond(connack,4); 98 | 99 | PubSubClient client(server, 1883, callback, shimClient); 100 | int rc = client.connect((char*)"client_test1"); 101 | IS_TRUE(rc); 102 | 103 | byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,'A','B','C','D','E'}; 104 | shimClient.expect(publish,14); 105 | 106 | rc = client.publish((char*)"topic",(char*)"ABCDE",true); 107 | IS_TRUE(rc); 108 | 109 | IS_FALSE(shimClient.error()); 110 | 111 | END_IT 112 | } 113 | 114 | int test_publish_not_connected() { 115 | IT("publish fails when not connected"); 116 | ShimClient shimClient; 117 | 118 | PubSubClient client(server, 1883, callback, shimClient); 119 | 120 | int rc = client.publish((char*)"topic",(char*)"payload"); 121 | IS_FALSE(rc); 122 | 123 | IS_FALSE(shimClient.error()); 124 | 125 | END_IT 126 | } 127 | 128 | int test_publish_too_long() { 129 | IT("publish fails when topic/payload are too long"); 130 | ShimClient shimClient; 131 | shimClient.setAllowConnect(true); 132 | 133 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 134 | shimClient.respond(connack,4); 135 | 136 | PubSubClient client(server, 1883, callback, shimClient); 137 | client.setBufferSize(128); 138 | int rc = client.connect((char*)"client_test1"); 139 | IS_TRUE(rc); 140 | 141 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 142 | rc = client.publish((char*)"topic",(char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); 143 | IS_FALSE(rc); 144 | 145 | IS_FALSE(shimClient.error()); 146 | 147 | END_IT 148 | } 149 | 150 | int test_publish_P() { 151 | IT("publishes using PROGMEM"); 152 | ShimClient shimClient; 153 | shimClient.setAllowConnect(true); 154 | 155 | byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; 156 | int length = 5; 157 | 158 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 159 | shimClient.respond(connack,4); 160 | 161 | PubSubClient client(server, 1883, callback, shimClient); 162 | int rc = client.connect((char*)"client_test1"); 163 | IS_TRUE(rc); 164 | 165 | byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; 166 | shimClient.expect(publish,14); 167 | 168 | rc = client.publish_P((char*)"topic",payload,length,true); 169 | IS_TRUE(rc); 170 | 171 | IS_FALSE(shimClient.error()); 172 | 173 | END_IT 174 | } 175 | 176 | 177 | 178 | 179 | int main() 180 | { 181 | SUITE("Publish"); 182 | test_publish(); 183 | test_publish_bytes(); 184 | test_publish_retained(); 185 | test_publish_retained_2(); 186 | test_publish_not_connected(); 187 | test_publish_too_long(); 188 | test_publish_P(); 189 | 190 | FINISH 191 | } 192 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/receive_spec.cpp: -------------------------------------------------------------------------------- 1 | #include "PubSubClient.h" 2 | #include "ShimClient.h" 3 | #include "Buffer.h" 4 | #include "BDDTest.h" 5 | #include "trace.h" 6 | 7 | 8 | byte server[] = { 172, 16, 0, 2 }; 9 | 10 | bool callback_called = false; 11 | char lastTopic[1024]; 12 | char lastPayload[1024]; 13 | unsigned int lastLength; 14 | 15 | void reset_callback() { 16 | callback_called = false; 17 | lastTopic[0] = '\0'; 18 | lastPayload[0] = '\0'; 19 | lastLength = 0; 20 | } 21 | 22 | void callback(char* topic, byte* payload, unsigned int length) { 23 | TRACE("Callback received topic=[" << topic << "] length=" << length << "\n") 24 | callback_called = true; 25 | strcpy(lastTopic,topic); 26 | memcpy(lastPayload,payload,length); 27 | lastLength = length; 28 | } 29 | 30 | int test_receive_callback() { 31 | IT("receives a callback message"); 32 | reset_callback(); 33 | 34 | ShimClient shimClient; 35 | shimClient.setAllowConnect(true); 36 | 37 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 38 | shimClient.respond(connack,4); 39 | 40 | PubSubClient client(server, 1883, callback, shimClient); 41 | int rc = client.connect((char*)"client_test1"); 42 | IS_TRUE(rc); 43 | 44 | byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 45 | shimClient.respond(publish,16); 46 | 47 | rc = client.loop(); 48 | 49 | IS_TRUE(rc); 50 | 51 | IS_TRUE(callback_called); 52 | IS_TRUE(strcmp(lastTopic,"topic")==0); 53 | IS_TRUE(memcmp(lastPayload,"payload",7)==0); 54 | IS_TRUE(lastLength == 7); 55 | 56 | IS_FALSE(shimClient.error()); 57 | 58 | END_IT 59 | } 60 | 61 | int test_receive_stream() { 62 | IT("receives a streamed callback message"); 63 | reset_callback(); 64 | 65 | Stream stream; 66 | stream.expect((uint8_t*)"payload",7); 67 | 68 | ShimClient shimClient; 69 | shimClient.setAllowConnect(true); 70 | 71 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 72 | shimClient.respond(connack,4); 73 | 74 | PubSubClient client(server, 1883, callback, shimClient, stream); 75 | int rc = client.connect((char*)"client_test1"); 76 | IS_TRUE(rc); 77 | 78 | byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 79 | shimClient.respond(publish,16); 80 | 81 | rc = client.loop(); 82 | 83 | IS_TRUE(rc); 84 | 85 | IS_TRUE(callback_called); 86 | IS_TRUE(strcmp(lastTopic,"topic")==0); 87 | IS_TRUE(lastLength == 7); 88 | 89 | IS_FALSE(stream.error()); 90 | IS_FALSE(shimClient.error()); 91 | 92 | END_IT 93 | } 94 | 95 | int test_receive_max_sized_message() { 96 | IT("receives an max-sized message"); 97 | reset_callback(); 98 | 99 | ShimClient shimClient; 100 | shimClient.setAllowConnect(true); 101 | 102 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 103 | shimClient.respond(connack,4); 104 | 105 | PubSubClient client(server, 1883, callback, shimClient); 106 | int length = 80; // If this is changed to > 128 then the publish packet below 107 | // is no longer valid as it assumes the remaining length 108 | // is a single-byte. Don't make that mistake like I just 109 | // did and lose a whole evening tracking down the issue. 110 | client.setBufferSize(length); 111 | int rc = client.connect((char*)"client_test1"); 112 | IS_TRUE(rc); 113 | 114 | 115 | byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 116 | byte bigPublish[length]; 117 | memset(bigPublish,'A',length); 118 | bigPublish[length] = 'B'; 119 | memcpy(bigPublish,publish,16); 120 | shimClient.respond(bigPublish,length); 121 | 122 | rc = client.loop(); 123 | 124 | IS_TRUE(rc); 125 | 126 | IS_TRUE(callback_called); 127 | IS_TRUE(strcmp(lastTopic,"topic")==0); 128 | IS_TRUE(lastLength == length-9); 129 | IS_TRUE(memcmp(lastPayload,bigPublish+9,lastLength)==0); 130 | 131 | IS_FALSE(shimClient.error()); 132 | 133 | END_IT 134 | } 135 | 136 | int test_receive_oversized_message() { 137 | IT("drops an oversized message"); 138 | reset_callback(); 139 | 140 | ShimClient shimClient; 141 | shimClient.setAllowConnect(true); 142 | 143 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 144 | shimClient.respond(connack,4); 145 | 146 | int length = 80; // See comment in test_receive_max_sized_message before changing this value 147 | 148 | PubSubClient client(server, 1883, callback, shimClient); 149 | client.setBufferSize(length-1); 150 | int rc = client.connect((char*)"client_test1"); 151 | IS_TRUE(rc); 152 | 153 | byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 154 | byte bigPublish[length]; 155 | memset(bigPublish,'A',length); 156 | bigPublish[length] = 'B'; 157 | memcpy(bigPublish,publish,16); 158 | shimClient.respond(bigPublish,length); 159 | 160 | rc = client.loop(); 161 | 162 | IS_TRUE(rc); 163 | 164 | IS_FALSE(callback_called); 165 | 166 | IS_FALSE(shimClient.error()); 167 | 168 | END_IT 169 | } 170 | 171 | int test_drop_invalid_remaining_length_message() { 172 | IT("drops invalid remaining length message"); 173 | reset_callback(); 174 | 175 | ShimClient shimClient; 176 | shimClient.setAllowConnect(true); 177 | 178 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 179 | shimClient.respond(connack,4); 180 | 181 | PubSubClient client(server, 1883, callback, shimClient); 182 | int rc = client.connect((char*)"client_test1"); 183 | IS_TRUE(rc); 184 | 185 | byte publish[] = {0x30,0x92,0x92,0x92,0x92,0x01,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 186 | shimClient.respond(publish,20); 187 | 188 | rc = client.loop(); 189 | 190 | IS_FALSE(rc); 191 | 192 | IS_FALSE(callback_called); 193 | 194 | IS_FALSE(shimClient.error()); 195 | 196 | END_IT 197 | } 198 | 199 | int test_resize_buffer() { 200 | IT("receives a message larger than the default maximum"); 201 | reset_callback(); 202 | 203 | ShimClient shimClient; 204 | shimClient.setAllowConnect(true); 205 | 206 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 207 | shimClient.respond(connack,4); 208 | 209 | int length = 80; // See comment in test_receive_max_sized_message before changing this value 210 | 211 | PubSubClient client(server, 1883, callback, shimClient); 212 | client.setBufferSize(length-1); 213 | int rc = client.connect((char*)"client_test1"); 214 | IS_TRUE(rc); 215 | 216 | byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 217 | byte bigPublish[length]; 218 | memset(bigPublish,'A',length); 219 | bigPublish[length] = 'B'; 220 | memcpy(bigPublish,publish,16); 221 | // Send it twice 222 | shimClient.respond(bigPublish,length); 223 | shimClient.respond(bigPublish,length); 224 | 225 | rc = client.loop(); 226 | IS_TRUE(rc); 227 | 228 | // First message fails as it is too big 229 | IS_FALSE(callback_called); 230 | 231 | // Resize the buffer 232 | client.setBufferSize(length); 233 | 234 | rc = client.loop(); 235 | IS_TRUE(rc); 236 | 237 | IS_TRUE(callback_called); 238 | 239 | IS_TRUE(strcmp(lastTopic,"topic")==0); 240 | IS_TRUE(lastLength == length-9); 241 | IS_TRUE(memcmp(lastPayload,bigPublish+9,lastLength)==0); 242 | 243 | IS_FALSE(shimClient.error()); 244 | 245 | END_IT 246 | } 247 | 248 | 249 | int test_receive_oversized_stream_message() { 250 | IT("receive an oversized streamed message"); 251 | reset_callback(); 252 | 253 | Stream stream; 254 | 255 | ShimClient shimClient; 256 | shimClient.setAllowConnect(true); 257 | 258 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 259 | shimClient.respond(connack,4); 260 | 261 | int length = 80; // See comment in test_receive_max_sized_message before changing this value 262 | 263 | PubSubClient client(server, 1883, callback, shimClient, stream); 264 | client.setBufferSize(length-1); 265 | int rc = client.connect((char*)"client_test1"); 266 | IS_TRUE(rc); 267 | 268 | byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 269 | 270 | byte bigPublish[length]; 271 | memset(bigPublish,'A',length); 272 | bigPublish[length] = 'B'; 273 | memcpy(bigPublish,publish,16); 274 | 275 | shimClient.respond(bigPublish,length); 276 | stream.expect(bigPublish+9,length-9); 277 | 278 | rc = client.loop(); 279 | 280 | IS_TRUE(rc); 281 | 282 | IS_TRUE(callback_called); 283 | IS_TRUE(strcmp(lastTopic,"topic")==0); 284 | 285 | IS_TRUE(lastLength == length-10); 286 | 287 | IS_FALSE(stream.error()); 288 | IS_FALSE(shimClient.error()); 289 | 290 | END_IT 291 | } 292 | 293 | int test_receive_qos1() { 294 | IT("receives a qos1 message"); 295 | reset_callback(); 296 | 297 | ShimClient shimClient; 298 | shimClient.setAllowConnect(true); 299 | 300 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 301 | shimClient.respond(connack,4); 302 | 303 | PubSubClient client(server, 1883, callback, shimClient); 304 | int rc = client.connect((char*)"client_test1"); 305 | IS_TRUE(rc); 306 | 307 | byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; 308 | shimClient.respond(publish,18); 309 | 310 | byte puback[] = {0x40,0x2,0x12,0x34}; 311 | shimClient.expect(puback,4); 312 | 313 | rc = client.loop(); 314 | 315 | IS_TRUE(rc); 316 | 317 | IS_TRUE(callback_called); 318 | IS_TRUE(strcmp(lastTopic,"topic")==0); 319 | IS_TRUE(memcmp(lastPayload,"payload",7)==0); 320 | IS_TRUE(lastLength == 7); 321 | 322 | IS_FALSE(shimClient.error()); 323 | 324 | END_IT 325 | } 326 | 327 | int main() 328 | { 329 | SUITE("Receive"); 330 | test_receive_callback(); 331 | test_receive_stream(); 332 | test_receive_max_sized_message(); 333 | test_drop_invalid_remaining_length_message(); 334 | test_receive_oversized_message(); 335 | test_resize_buffer(); 336 | test_receive_oversized_stream_message(); 337 | test_receive_qos1(); 338 | 339 | FINISH 340 | } 341 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/src/subscribe_spec.cpp: -------------------------------------------------------------------------------- 1 | #include "PubSubClient.h" 2 | #include "ShimClient.h" 3 | #include "Buffer.h" 4 | #include "BDDTest.h" 5 | #include "trace.h" 6 | 7 | 8 | byte server[] = { 172, 16, 0, 2 }; 9 | 10 | void callback(char* topic, byte* payload, unsigned int length) { 11 | // handle message arrived 12 | } 13 | 14 | int test_subscribe_no_qos() { 15 | IT("subscribe without qos defaults to 0"); 16 | ShimClient shimClient; 17 | shimClient.setAllowConnect(true); 18 | 19 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 20 | shimClient.respond(connack,4); 21 | 22 | PubSubClient client(server, 1883, callback, shimClient); 23 | int rc = client.connect((char*)"client_test1"); 24 | IS_TRUE(rc); 25 | 26 | byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x0 }; 27 | shimClient.expect(subscribe,12); 28 | byte suback[] = { 0x90,0x3,0x0,0x2,0x0 }; 29 | shimClient.respond(suback,5); 30 | 31 | rc = client.subscribe((char*)"topic"); 32 | IS_TRUE(rc); 33 | 34 | IS_FALSE(shimClient.error()); 35 | 36 | END_IT 37 | } 38 | 39 | int test_subscribe_qos_1() { 40 | IT("subscribes qos 1"); 41 | ShimClient shimClient; 42 | shimClient.setAllowConnect(true); 43 | 44 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 45 | shimClient.respond(connack,4); 46 | 47 | PubSubClient client(server, 1883, callback, shimClient); 48 | int rc = client.connect((char*)"client_test1"); 49 | IS_TRUE(rc); 50 | 51 | byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1 }; 52 | shimClient.expect(subscribe,12); 53 | byte suback[] = { 0x90,0x3,0x0,0x2,0x1 }; 54 | shimClient.respond(suback,5); 55 | 56 | rc = client.subscribe((char*)"topic",1); 57 | IS_TRUE(rc); 58 | 59 | IS_FALSE(shimClient.error()); 60 | 61 | END_IT 62 | } 63 | 64 | int test_subscribe_not_connected() { 65 | IT("subscribe fails when not connected"); 66 | ShimClient shimClient; 67 | 68 | PubSubClient client(server, 1883, callback, shimClient); 69 | 70 | int rc = client.subscribe((char*)"topic"); 71 | IS_FALSE(rc); 72 | 73 | IS_FALSE(shimClient.error()); 74 | 75 | END_IT 76 | } 77 | 78 | int test_subscribe_invalid_qos() { 79 | IT("subscribe fails with invalid qos values"); 80 | ShimClient shimClient; 81 | shimClient.setAllowConnect(true); 82 | 83 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 84 | shimClient.respond(connack,4); 85 | 86 | PubSubClient client(server, 1883, callback, shimClient); 87 | int rc = client.connect((char*)"client_test1"); 88 | IS_TRUE(rc); 89 | 90 | rc = client.subscribe((char*)"topic",2); 91 | IS_FALSE(rc); 92 | rc = client.subscribe((char*)"topic",254); 93 | IS_FALSE(rc); 94 | 95 | IS_FALSE(shimClient.error()); 96 | 97 | END_IT 98 | } 99 | 100 | int test_subscribe_too_long() { 101 | IT("subscribe fails with too long topic"); 102 | ShimClient shimClient; 103 | shimClient.setAllowConnect(true); 104 | 105 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 106 | shimClient.respond(connack,4); 107 | 108 | PubSubClient client(server, 1883, callback, shimClient); 109 | client.setBufferSize(128); 110 | int rc = client.connect((char*)"client_test1"); 111 | IS_TRUE(rc); 112 | 113 | // max length should be allowed 114 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 115 | rc = client.subscribe((char*)"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); 116 | IS_TRUE(rc); 117 | 118 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 119 | rc = client.subscribe((char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); 120 | IS_FALSE(rc); 121 | 122 | IS_FALSE(shimClient.error()); 123 | 124 | END_IT 125 | } 126 | 127 | 128 | int test_unsubscribe() { 129 | IT("unsubscribes"); 130 | ShimClient shimClient; 131 | shimClient.setAllowConnect(true); 132 | 133 | byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; 134 | shimClient.respond(connack,4); 135 | 136 | PubSubClient client(server, 1883, callback, shimClient); 137 | int rc = client.connect((char*)"client_test1"); 138 | IS_TRUE(rc); 139 | 140 | byte unsubscribe[] = { 0xA2,0x9,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63 }; 141 | shimClient.expect(unsubscribe,12); 142 | byte unsuback[] = { 0xB0,0x2,0x0,0x2 }; 143 | shimClient.respond(unsuback,4); 144 | 145 | rc = client.unsubscribe((char*)"topic"); 146 | IS_TRUE(rc); 147 | 148 | IS_FALSE(shimClient.error()); 149 | 150 | END_IT 151 | } 152 | 153 | int test_unsubscribe_not_connected() { 154 | IT("unsubscribe fails when not connected"); 155 | ShimClient shimClient; 156 | 157 | PubSubClient client(server, 1883, callback, shimClient); 158 | 159 | int rc = client.unsubscribe((char*)"topic"); 160 | IS_FALSE(rc); 161 | 162 | IS_FALSE(shimClient.error()); 163 | 164 | END_IT 165 | } 166 | 167 | int main() 168 | { 169 | SUITE("Subscribe"); 170 | test_subscribe_no_qos(); 171 | test_subscribe_qos_1(); 172 | test_subscribe_not_connected(); 173 | test_subscribe_invalid_qos(); 174 | test_subscribe_too_long(); 175 | test_unsubscribe(); 176 | test_unsubscribe_not_connected(); 177 | FINISH 178 | } 179 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/testcases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarecrash/Solar2MQTT/dcb6b39a7d139c9b2e8a240caab1f3bd3bac7da5/lib/pubsubclient-2.8.13/tests/testcases/__init__.py -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/testcases/mqtt_basic.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import settings 3 | import time 4 | import mosquitto 5 | 6 | 7 | def on_message(mosq, obj, msg): 8 | obj.message_queue.append(msg) 9 | 10 | 11 | class mqtt_basic(unittest.TestCase): 12 | 13 | message_queue = [] 14 | 15 | @classmethod 16 | def setUpClass(self): 17 | self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self) 18 | self.client.connect(settings.server_ip) 19 | self.client.on_message = on_message 20 | self.client.subscribe("outTopic", 0) 21 | 22 | @classmethod 23 | def tearDownClass(self): 24 | self.client.disconnect() 25 | 26 | def test_one(self): 27 | i = 30 28 | while len(self.message_queue) == 0 and i > 0: 29 | self.client.loop() 30 | time.sleep(0.5) 31 | i -= 1 32 | self.assertTrue(i > 0, "message receive timed-out") 33 | self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") 34 | msg = self.message_queue[0] 35 | self.assertEqual(msg.mid, 0, "message id not 0") 36 | self.assertEqual(msg.topic, "outTopic", "message topic incorrect") 37 | self.assertEqual(msg.payload, "hello world") 38 | self.assertEqual(msg.qos, 0, "message qos not 0") 39 | self.assertEqual(msg.retain, False, "message retain flag incorrect") 40 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/testcases/mqtt_publish_in_callback.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import settings 3 | import time 4 | import mosquitto 5 | 6 | 7 | def on_message(mosq, obj, msg): 8 | obj.message_queue.append(msg) 9 | 10 | 11 | class mqtt_publish_in_callback(unittest.TestCase): 12 | 13 | message_queue = [] 14 | 15 | @classmethod 16 | def setUpClass(self): 17 | self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self) 18 | self.client.connect(settings.server_ip) 19 | self.client.on_message = on_message 20 | self.client.subscribe("outTopic", 0) 21 | 22 | @classmethod 23 | def tearDownClass(self): 24 | self.client.disconnect() 25 | 26 | def test_connect(self): 27 | i = 30 28 | while len(self.message_queue) == 0 and i > 0: 29 | self.client.loop() 30 | time.sleep(0.5) 31 | i -= 1 32 | self.assertTrue(i > 0, "message receive timed-out") 33 | self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") 34 | msg = self.message_queue.pop(0) 35 | self.assertEqual(msg.mid, 0, "message id not 0") 36 | self.assertEqual(msg.topic, "outTopic", "message topic incorrect") 37 | self.assertEqual(msg.payload, "hello world") 38 | self.assertEqual(msg.qos, 0, "message qos not 0") 39 | self.assertEqual(msg.retain, False, "message retain flag incorrect") 40 | 41 | def test_publish(self): 42 | self.assertEqual(len(self.message_queue), 0, "message queue not empty") 43 | payload = "abcdefghij" 44 | self.client.publish("inTopic", payload) 45 | 46 | i = 30 47 | while len(self.message_queue) == 0 and i > 0: 48 | self.client.loop() 49 | time.sleep(0.5) 50 | i -= 1 51 | 52 | self.assertTrue(i > 0, "message receive timed-out") 53 | self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") 54 | msg = self.message_queue.pop(0) 55 | self.assertEqual(msg.mid, 0, "message id not 0") 56 | self.assertEqual(msg.topic, "outTopic", "message topic incorrect") 57 | self.assertEqual(msg.payload, payload) 58 | self.assertEqual(msg.qos, 0, "message qos not 0") 59 | self.assertEqual(msg.retain, False, "message retain flag incorrect") 60 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/testcases/settings.py: -------------------------------------------------------------------------------- 1 | server_ip = "172.16.0.2" 2 | arduino_ip = "172.16.0.100" 3 | -------------------------------------------------------------------------------- /lib/pubsubclient-2.8.13/tests/testsuite.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import os.path 4 | import sys 5 | import shutil 6 | from subprocess import call 7 | import importlib 8 | import unittest 9 | import re 10 | 11 | from testcases import settings 12 | 13 | 14 | class Workspace(object): 15 | 16 | def __init__(self): 17 | self.root_dir = os.getcwd() 18 | self.build_dir = os.path.join(self.root_dir, "tmpbin") 19 | self.log_dir = os.path.join(self.root_dir, "logs") 20 | self.tests_dir = os.path.join(self.root_dir, "testcases") 21 | self.examples_dir = os.path.join(self.root_dir, "../PubSubClient/examples") 22 | self.examples = [] 23 | self.tests = [] 24 | if not os.path.isdir("../PubSubClient"): 25 | raise Exception("Cannot find PubSubClient library") 26 | try: 27 | return __import__('ino') 28 | except ImportError: 29 | raise Exception("ino tool not installed") 30 | 31 | def init(self): 32 | if os.path.isdir(self.build_dir): 33 | shutil.rmtree(self.build_dir) 34 | os.mkdir(self.build_dir) 35 | if os.path.isdir(self.log_dir): 36 | shutil.rmtree(self.log_dir) 37 | os.mkdir(self.log_dir) 38 | 39 | os.chdir(self.build_dir) 40 | call(["ino", "init"]) 41 | 42 | shutil.copytree("../../PubSubClient", "lib/PubSubClient") 43 | 44 | filenames = [] 45 | for root, dirs, files in os.walk(self.examples_dir): 46 | filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")] 47 | filenames.sort() 48 | for e in filenames: 49 | self.examples.append(Sketch(self, e)) 50 | 51 | filenames = [] 52 | for root, dirs, files in os.walk(self.tests_dir): 53 | filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")] 54 | filenames.sort() 55 | for e in filenames: 56 | self.tests.append(Sketch(self, e)) 57 | 58 | def clean(self): 59 | shutil.rmtree(self.build_dir) 60 | 61 | 62 | class Sketch(object): 63 | def __init__(self, wksp, fn): 64 | self.w = wksp 65 | self.filename = fn 66 | self.basename = os.path.basename(self.filename) 67 | self.build_log = os.path.join(self.w.log_dir, "%s.log" % (os.path.basename(self.filename),)) 68 | self.build_err_log = os.path.join(self.w.log_dir, "%s.err.log" % (os.path.basename(self.filename),)) 69 | self.build_upload_log = os.path.join(self.w.log_dir, "%s.upload.log" % (os.path.basename(self.filename),)) 70 | 71 | def build(self): 72 | sys.stdout.write(" Build: ") 73 | sys.stdout.flush() 74 | 75 | # Copy sketch over, replacing IP addresses as necessary 76 | fin = open(self.filename, "r") 77 | lines = fin.readlines() 78 | fin.close() 79 | fout = open(os.path.join(self.w.build_dir, "src", "sketch.ino"), "w") 80 | for l in lines: 81 | if re.match(r"^byte server\[\] = {", l): 82 | fout.write("byte server[] = { %s };\n" % (settings.server_ip.replace(".", ", "),)) 83 | elif re.match(r"^byte ip\[\] = {", l): 84 | fout.write("byte ip[] = { %s };\n" % (settings.arduino_ip.replace(".", ", "),)) 85 | else: 86 | fout.write(l) 87 | fout.flush() 88 | fout.close() 89 | 90 | # Run build 91 | fout = open(self.build_log, "w") 92 | ferr = open(self.build_err_log, "w") 93 | rc = call(["ino", "build"], stdout=fout, stderr=ferr) 94 | fout.close() 95 | ferr.close() 96 | if rc == 0: 97 | sys.stdout.write("pass") 98 | sys.stdout.write("\n") 99 | return True 100 | else: 101 | sys.stdout.write("fail") 102 | sys.stdout.write("\n") 103 | with open(self.build_err_log) as f: 104 | for line in f: 105 | print(" " + line) 106 | return False 107 | 108 | def upload(self): 109 | sys.stdout.write(" Upload: ") 110 | sys.stdout.flush() 111 | fout = open(self.build_upload_log, "w") 112 | rc = call(["ino", "upload"], stdout=fout, stderr=fout) 113 | fout.close() 114 | if rc == 0: 115 | sys.stdout.write("pass") 116 | sys.stdout.write("\n") 117 | return True 118 | else: 119 | sys.stdout.write("fail") 120 | sys.stdout.write("\n") 121 | with open(self.build_upload_log) as f: 122 | for line in f: 123 | print(" " + line) 124 | return False 125 | 126 | def test(self): 127 | # import the matching test case, if it exists 128 | try: 129 | basename = os.path.basename(self.filename)[:-4] 130 | i = importlib.import_module("testcases." + basename) 131 | except: 132 | sys.stdout.write(" Test: no tests found") 133 | sys.stdout.write("\n") 134 | return 135 | c = getattr(i, basename) 136 | 137 | testmethods = [m for m in dir(c) if m.startswith("test_")] 138 | testmethods.sort() 139 | tests = [] 140 | for m in testmethods: 141 | tests.append(c(m)) 142 | 143 | result = unittest.TestResult() 144 | c.setUpClass() 145 | if self.upload(): 146 | sys.stdout.write(" Test: ") 147 | sys.stdout.flush() 148 | for t in tests: 149 | t.run(result) 150 | print(str(result.testsRun - len(result.failures) - len(result.errors)) + "/" + str(result.testsRun)) 151 | if not result.wasSuccessful(): 152 | if len(result.failures) > 0: 153 | for f in result.failures: 154 | print("-- " + str(f[0])) 155 | print(f[1]) 156 | if len(result.errors) > 0: 157 | print(" Errors:") 158 | for f in result.errors: 159 | print("-- " + str(f[0])) 160 | print(f[1]) 161 | c.tearDownClass() 162 | 163 | 164 | if __name__ == '__main__': 165 | run_tests = True 166 | 167 | w = Workspace() 168 | w.init() 169 | 170 | for e in w.examples: 171 | print("--------------------------------------") 172 | print("[" + e.basename + "]") 173 | if e.build() and run_tests: 174 | e.test() 175 | for e in w.tests: 176 | print("--------------------------------------") 177 | print("[" + e.basename + "]") 178 | if e.build() and run_tests: 179 | e.test() 180 | 181 | w.clean() 182 | -------------------------------------------------------------------------------- /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] 12 | platform = espressif8266@4.2.1 13 | framework = arduino 14 | monitor_speed = 115200 15 | custom_prog_version = 1.2.0-Pre4A6 16 | build_flags = 17 | -DVERSION=${this.custom_prog_version} 18 | -DPIO_SRC_NAM="Solar2MQTT" 19 | -DESP8266 -DATOMIC_FS_UPDATE 20 | 21 | extra_scripts = pre:tools/mini_html.py 22 | pre:tools/pre_compile.py 23 | post:tools/post_compile.py 24 | lib_deps = 25 | ;bblanchon/ArduinoJson @ ^6.21.2 26 | bblanchon/ArduinoJson @ ^7.2.0 27 | esphome/ESPAsyncTCP-esphome @ 2.0.0 28 | mathieucarbou/ESPAsyncWebServer @ ^3.3.16 29 | mathieucarbou/WebSerialLite@^6.2.0 30 | alanswx/ESPAsyncWiFiManager @ ^0.31.0 31 | plerup/EspSoftwareSerial @ ^8.2.0 32 | https://github.com/dok-net/ghostl 33 | robtillaart/CRC@^1.0.3 34 | 4-20ma/ModbusMaster@^2.0.1 35 | paulstoffregen/OneWire@^2.3.7 36 | milesburton/DallasTemperature@^3.11.0 37 | 38 | [env:d1_mini] 39 | board = d1_mini 40 | board_build.ldscript = eagle.flash.4m.ld 41 | build_flags = ${env.build_flags} 42 | custom_hardwareserial = false 43 | monitor_filters = esp8266_exception_decoder, default, time, printable, colorize 44 | upload_speed = 921600 45 | 46 | [env:WiFi-Dongle] 47 | board = esp12e 48 | board_build.ldscript = eagle.flash.4m.ld 49 | custom_hardwareserial = true 50 | build_flags = ${env.build_flags} 51 | custom_prog_version = ${env.custom_prog_version} 52 | monitor_filters = esp8266_exception_decoder, default, time, printable, colorize 53 | upload_speed = 921600 54 | 55 | [env:esp01_1m] 56 | board = esp01_1m 57 | board_build.ldscript = eagle.flash.1m.ld 58 | custom_hardwareserial = true 59 | build_flags = ${env.build_flags} 60 | custom_prog_version = ${env.custom_prog_version} 61 | monitor_filters = esp8266_exception_decoder, default, time, printable, colorize 62 | -------------------------------------------------------------------------------- /src/PI_Serial/PI_Serial.h: -------------------------------------------------------------------------------- 1 | #include "SoftwareSerial.h" 2 | #ifndef PI_SERIAL_H 3 | #define PI_SERIAL_H 4 | #include "vector" 5 | #include <ArduinoJson.h> 6 | #include <modbus/modbus.h> 7 | extern JsonObject deviceJson; 8 | extern JsonObject staticData; 9 | extern JsonObject liveData; 10 | 11 | class PI_Serial 12 | { 13 | public: 14 | const char *startChar = "("; 15 | const char *delimiter = " "; 16 | bool requestStaticData = true; 17 | byte protocol = NoD; 18 | bool connection = false; 19 | 20 | struct 21 | { 22 | struct 23 | { 24 | // static 25 | String qpi; 26 | String qall; 27 | String qpiri; 28 | String qmn; 29 | String qflag; 30 | // dynamic 31 | String q1; 32 | String qpigs; 33 | String qpigs2; 34 | String qmod; 35 | String qt; 36 | String qet; 37 | String qey; 38 | String qem; 39 | String qed; 40 | String qlt; 41 | String qly; 42 | String qlm; 43 | String qld; 44 | String commandAnswer; 45 | String qpiws; 46 | } raw; 47 | 48 | } get; 49 | 50 | struct 51 | { 52 | 53 | } alarm; 54 | 55 | /** 56 | * @brief Construct a new PI_Serial object 57 | * 58 | * @param serialIntf UART interface BMS is connected to 59 | */ 60 | PI_Serial(int rx, int tx); 61 | 62 | /** 63 | * @brief Initializes this driver 64 | * @details Configures the serial peripheral and pre-loads the transmit buffer with command-independent bytes 65 | */ 66 | bool Init(); 67 | 68 | /** 69 | * @brief Updating the Data from the BMS 70 | */ 71 | bool loop(); 72 | 73 | /** 74 | * @brief Send custom command to the device 75 | */ 76 | String sendCommand(String command); 77 | 78 | /** 79 | * @brief 80 | * 81 | */ 82 | bool sendCustomCommand(); 83 | 84 | /** 85 | * @brief callback function 86 | * 87 | */ 88 | void callback(std::function<void()> func); 89 | std::function<void()> requestCallback; 90 | 91 | enum protocolType 92 | { 93 | NoD, 94 | PI18, 95 | PI30, 96 | MODBUS_MUST 97 | }; 98 | 99 | private: 100 | unsigned int serialIntfBaud; 101 | 102 | unsigned long previousTime = 0; 103 | unsigned long delayTime = 100; 104 | byte requestCounter = 0; 105 | 106 | long long int connectionCounter = 0; 107 | 108 | byte qexCounter = 0; 109 | 110 | String customCommandBuffer; 111 | 112 | MODBUS *modbus; 113 | 114 | /** 115 | * @brief get the crc from a string 116 | */ 117 | uint16_t getCRC(String data); 118 | 119 | /** 120 | * @brief get the crc from a string 121 | */ 122 | byte getCHK(String data); 123 | 124 | /** 125 | * @brief function for autodetect the inverter 126 | * @details ask all modes and sort it to a protocol 127 | */ 128 | void autoDetect(); 129 | 130 | /** 131 | * @brief Sends a complete packet with the specified command 132 | * @details calculates the checksum and sends the command over the specified serial connection 133 | */ 134 | String requestData(String command); 135 | 136 | /** 137 | * @brief accept a achar and get back the operation mode as string 138 | */ 139 | char *getModeDesc(char mode); 140 | 141 | /** 142 | * @brief Serial interface used for communication 143 | * @details This is set in the constructor 144 | */ 145 | SoftwareSerial *my_serialIntf; 146 | // dynamic requests 147 | bool PIXX_Q1(); 148 | bool PIXX_QPIGS(); 149 | bool PIXX_QPIGS2(); 150 | bool PIXX_QMOD(); 151 | bool PIXX_QEX(); 152 | bool PIXX_QPIWS(); 153 | // static reqeuests 154 | bool PIXX_QPIRI(); 155 | bool PIXX_QPI(); 156 | bool PIXX_QMN(); 157 | bool PIXX_QFLAG(); 158 | 159 | bool isModbus(); 160 | 161 | static bool checkQFLAG(const String& flags, char symbol); 162 | }; 163 | 164 | #endif -------------------------------------------------------------------------------- /src/PI_Serial/QEX.h: -------------------------------------------------------------------------------- 1 | bool PI_Serial::PIXX_QEX() 2 | { 3 | if (protocol == PI30) 4 | { 5 | String commandAnswer = this->requestData("QET"); 6 | get.raw.qet = commandAnswer; 7 | if (commandAnswer == "NAK") 8 | { 9 | return true; 10 | } 11 | if (commandAnswer == "ERCRC") 12 | { 13 | return false; 14 | } 15 | liveData["PV_generation_sum"] = commandAnswer.toInt(); 16 | commandAnswer = this->requestData("QT"); 17 | get.raw.qt = commandAnswer.substring(0, 8); 18 | if (commandAnswer == "ERCRC") 19 | { 20 | return false; 21 | } 22 | if (commandAnswer == "NAK") 23 | { 24 | return true; 25 | } 26 | else 27 | { 28 | commandAnswer = this->requestData("QEY" + get.raw.qt.substring(0, 4)); 29 | get.raw.qey = commandAnswer; 30 | if (commandAnswer == "ERCRC") 31 | return false; 32 | if (commandAnswer == "NAK") 33 | return true; 34 | liveData["PV_generation_year"] = commandAnswer.toInt(); 35 | 36 | commandAnswer = this->requestData("QEM" + get.raw.qt.substring(0, 6)); 37 | get.raw.qem = commandAnswer; 38 | if (commandAnswer == "ERCRC") 39 | return false; 40 | if (commandAnswer == "NAK") 41 | return true; 42 | liveData["PV_generation_month"] = commandAnswer.toInt(); 43 | 44 | commandAnswer = this->requestData("QED" + get.raw.qt.substring(0, 8)); 45 | get.raw.qed = commandAnswer; 46 | if (commandAnswer == "ERCRC") 47 | return false; 48 | if (commandAnswer == "NAK") 49 | return true; 50 | liveData["PV_generation_day"] = commandAnswer.toInt(); 51 | 52 | commandAnswer = this->requestData("QLT"); 53 | get.raw.qlt = commandAnswer; 54 | if (commandAnswer == "ERCRC") 55 | return false; 56 | if (commandAnswer == "NAK") 57 | return true; 58 | liveData["AC_in_generation_sum"] = commandAnswer.toInt(); 59 | 60 | commandAnswer = this->requestData("QLY" + get.raw.qt.substring(0, 4)); 61 | get.raw.qly = commandAnswer; 62 | if (commandAnswer == "ERCRC") 63 | return false; 64 | if (commandAnswer == "NAK") 65 | return true; 66 | liveData["AC_in_generation_year"] = commandAnswer.toInt(); 67 | 68 | commandAnswer = this->requestData("QLM" + get.raw.qt.substring(0, 6)); 69 | get.raw.qlm = commandAnswer; 70 | if (commandAnswer == "ERCRC") 71 | return false; 72 | if (commandAnswer == "NAK") 73 | return true; 74 | liveData["AC_in_generation_month"] = commandAnswer.toInt(); 75 | 76 | commandAnswer = this->requestData("QLD" + get.raw.qt.substring(0, 8)); 77 | get.raw.qld = commandAnswer; 78 | if (commandAnswer == "ERCRC") 79 | return false; 80 | if (commandAnswer == "NAK") 81 | return true; 82 | liveData["AC_in_generation_day"] = commandAnswer.toInt(); 83 | 84 | return true; 85 | } 86 | } 87 | else if (protocol == PI18) 88 | { 89 | String commandAnswer = ""; 90 | switch (qexCounter) 91 | { 92 | case 0: 93 | commandAnswer = this->requestData("^P004T"); 94 | if (commandAnswer == "ERCRC" || commandAnswer == "NAK" || commandAnswer == "" || commandAnswer.toInt() < 1) 95 | { 96 | qexCounter = 0; 97 | get.raw.qt = ""; 98 | return true; 99 | } 100 | else 101 | { 102 | get.raw.qt = commandAnswer; 103 | qexCounter++; 104 | } 105 | break; 106 | case 1: 107 | commandAnswer = this->requestData("^P013ED" + get.raw.qt.substring(0, 8)); 108 | if (commandAnswer == "ERCRC" || commandAnswer == "NAK" || commandAnswer == "" || get.raw.qt == "" /*|| commandAnswer.toInt() < 1*/) 109 | { 110 | qexCounter = 0; 111 | return true; 112 | } 113 | else 114 | { 115 | get.raw.qed = commandAnswer; 116 | liveData["PV_generation_day"] = commandAnswer.toInt(); 117 | qexCounter++; 118 | } 119 | break; 120 | case 2: 121 | commandAnswer = this->requestData("^P011EM" + get.raw.qt.substring(0, 6)); 122 | if (commandAnswer == "ERCRC" || commandAnswer == "NAK" || commandAnswer == "" || get.raw.qt == "" /*|| commandAnswer.toInt() < 1*/) 123 | { 124 | qexCounter = 0; 125 | return true; 126 | } 127 | else 128 | { 129 | get.raw.qem = commandAnswer; 130 | liveData["PV_generation_month"] = commandAnswer.toInt(); 131 | qexCounter++; 132 | } 133 | break; 134 | case 3: 135 | commandAnswer = this->requestData("^P009EY" + get.raw.qt.substring(0, 4)); 136 | if (commandAnswer == "ERCRC" || commandAnswer == "NAK" || commandAnswer == "" || get.raw.qt == "" /*|| commandAnswer.toInt() < 1*/) 137 | { 138 | qexCounter = 0; 139 | return true; 140 | } 141 | else 142 | { 143 | get.raw.qey = commandAnswer; 144 | liveData["PV_generation_year"] = commandAnswer.toInt(); 145 | qexCounter++; 146 | } 147 | break; 148 | case 4: 149 | commandAnswer = this->requestData("^P005ET"); 150 | if (commandAnswer == "ERCRC" || commandAnswer == "NAK" || commandAnswer == "" /*|| commandAnswer.toInt() < 1*/) 151 | { 152 | return true; 153 | } 154 | else 155 | { 156 | get.raw.qet = commandAnswer; 157 | liveData["PV_generation_sum"] = commandAnswer.toInt(); 158 | qexCounter = 0; 159 | } 160 | break; 161 | 162 | default: 163 | break; 164 | } // return true; 165 | 166 | return true; 167 | } 168 | else if (protocol == NoD) 169 | { 170 | return false; 171 | } 172 | else 173 | { 174 | return false; 175 | } 176 | } -------------------------------------------------------------------------------- /src/PI_Serial/QFLAG.h: -------------------------------------------------------------------------------- 1 | bool PI_Serial::PIXX_QFLAG() 2 | { 3 | if (protocol == PI30) 4 | { 5 | String commandAnswer = this->requestData("QFLAG"); 6 | get.raw.qflag = commandAnswer; 7 | byte commandAnswerLength = commandAnswer.length(); 8 | if (commandAnswer == "NAK") 9 | { 10 | return true; 11 | } 12 | if (commandAnswer == "ERCRC") 13 | { 14 | return false; 15 | } 16 | if (commandAnswerLength == 11) 17 | { 18 | staticData["Buzzer_Enabled"] = checkQFLAG(commandAnswer, 'a'); 19 | 20 | staticData["Buzzer_Enabled"] = checkQFLAG(commandAnswer, 'a'); 21 | staticData["Overload_bypass_Enabled"] = checkQFLAG(commandAnswer, 'b'); 22 | staticData["Power_saving_Enabled"] = checkQFLAG(commandAnswer, 'j'); 23 | staticData["LCD_reset_to_default_Enabled"] = checkQFLAG(commandAnswer, 'k'); 24 | staticData["Overload_restart_Enabled"] = checkQFLAG(commandAnswer, 'u'); 25 | staticData["Over_temperature_restart_Enabled"] = checkQFLAG(commandAnswer, 'v'); 26 | staticData["LCD_backlight_Enabled"] = checkQFLAG(commandAnswer, 'x'); 27 | staticData["Primary_source_interrupt_alarm_Enabled"] = checkQFLAG(commandAnswer, 'y'); 28 | staticData["Record_fault_code_Enabled"] = checkQFLAG(commandAnswer, 'z'); 29 | } 30 | return true; 31 | } 32 | else if (protocol == PI18) 33 | { 34 | String commandAnswer = this->requestData("^P007FLAG"); 35 | get.raw.qflag = commandAnswer; 36 | byte commandAnswerLength = commandAnswer.length(); 37 | if (commandAnswer == "NAK") 38 | { 39 | return true; 40 | } 41 | if (commandAnswer == "ERCRC") 42 | { 43 | return false; 44 | } 45 | //[C: ^P007FLAG][CR: 190F][CC: 190F][L: 17] 46 | //QFLAG 1,1,0,0,0,1,1,1,0 47 | if (commandAnswerLength == 17) 48 | { 49 | staticData["Buzzer_Enabled"] = ((String)commandAnswer.charAt(0) == "1") ? true : false; 50 | staticData["Overload_bypass_Enabled"] = ((String)(commandAnswer.charAt(2)) == "1") ? true : false; 51 | staticData["LCD_reset_to_default_Enabled"] = ((String)(commandAnswer.charAt(4)) == "1") ? true : false; 52 | staticData["Overload_restart_Enabled"] = ((String)(commandAnswer.charAt(6)) == "1") ? true : false; 53 | staticData["Over_temperature_restart_Enabled"] = ((String)(commandAnswer.charAt(8)) == "1") ? true : false; 54 | staticData["LCD_backlight_Enabled"] = ((String)(commandAnswer.charAt(10)) == "1") ? true : false; 55 | staticData["Primary_source_interrupt_alarm_Enabled"] = ((String)(commandAnswer.charAt(12)) == "1") ? true : false; 56 | staticData["Record_fault_code_Enabled"] = ((String)(commandAnswer.charAt(14)) == "1") ? true : false; 57 | 58 | } else { 59 | get.raw.qflag = "Wrong Length(" + (String)get.raw.qflag.length() + "), Contact Dev:" +get.raw.qflag; 60 | } 61 | return true; 62 | } 63 | else if (protocol == NoD) 64 | { 65 | return false; 66 | } 67 | else 68 | { 69 | return false; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/PI_Serial/QMN.h: -------------------------------------------------------------------------------- 1 | bool PI_Serial::PIXX_QMN() 2 | { 3 | if (protocol == PI30) 4 | { 5 | String commandAnswer = this->requestData("QMN"); 6 | get.raw.qmn = commandAnswer; 7 | // calculate the length with https://elmar-eigner.de/text-zeichen-laenge.html 8 | if (commandAnswer == "NAK") 9 | { 10 | return true; 11 | } 12 | if (commandAnswer == "ERCRC") 13 | { 14 | return false; 15 | } 16 | if (commandAnswer.length() > 3 && commandAnswer.length() < 50) 17 | { 18 | staticData["Device_Model"] = commandAnswer; 19 | return true; 20 | } 21 | return true; 22 | } 23 | else if (protocol == PI18) 24 | { 25 | return true; 26 | } 27 | else if (protocol == NoD) 28 | { 29 | return false; 30 | } 31 | else 32 | { 33 | return false; 34 | } 35 | } -------------------------------------------------------------------------------- /src/PI_Serial/QMOD.h: -------------------------------------------------------------------------------- 1 | bool PI_Serial::PIXX_QMOD() 2 | { 3 | if (protocol == PI30) 4 | { 5 | String commandAnswer = this->requestData("QMOD"); 6 | get.raw.qmod = commandAnswer; 7 | if (commandAnswer == "NAK") 8 | { 9 | return true; 10 | } 11 | if (commandAnswer == "ERCRC") 12 | { 13 | return false; 14 | } 15 | if (commandAnswer.length() == 1) 16 | { 17 | liveData["Inverter_Operation_Mode"] = getModeDesc((char)commandAnswer.charAt(0)); 18 | } 19 | return true; 20 | } 21 | else if (protocol == PI18) 22 | { 23 | String commandAnswer = this->requestData("^P006MOD"); 24 | get.raw.qmod = commandAnswer; 25 | if (commandAnswer == "NAK") 26 | { 27 | return true; 28 | } 29 | if (commandAnswer == "ERCRC") 30 | { 31 | return false; 32 | } 33 | if (commandAnswer.length() == 2) 34 | { 35 | switch (commandAnswer.toInt()) 36 | { 37 | case 0: 38 | liveData["Inverter_Operation_Mode"] = "Power on"; 39 | break; 40 | case 1: 41 | liveData["Inverter_Operation_Mode"] = "Standby"; 42 | break; 43 | case 2: 44 | liveData["Inverter_Operation_Mode"] = "Bypass"; 45 | break; 46 | case 3: 47 | liveData["Inverter_Operation_Mode"] = "Battery"; 48 | break; 49 | case 4: 50 | liveData["Inverter_Operation_Mode"] = "Fault"; 51 | break; 52 | case 5: 53 | liveData["Inverter_Operation_Mode"] = "Hybrid"; 54 | break; 55 | default: 56 | liveData["Inverter_Operation_Mode"] = "No data"; 57 | break; 58 | } 59 | 60 | } 61 | return true; 62 | } 63 | else if (protocol == NoD) 64 | { 65 | return false; 66 | } 67 | else 68 | { 69 | return false; 70 | } 71 | } -------------------------------------------------------------------------------- /src/PI_Serial/QPI.h: -------------------------------------------------------------------------------- 1 | bool PI_Serial::PIXX_QPI() 2 | { 3 | if (protocol == PI30) 4 | { 5 | String commandAnswer = this->requestData("QPI"); 6 | get.raw.qpi = commandAnswer; 7 | if (commandAnswer == "NAK") 8 | { 9 | return true; 10 | } 11 | if (commandAnswer == "ERCRC") 12 | { 13 | return false; 14 | } 15 | staticData["Protocol_ID"] = commandAnswer; 16 | return true; 17 | } 18 | else if (protocol == PI18) 19 | { 20 | String commandAnswer = this->requestData("^P005PI"); 21 | get.raw.qpi = commandAnswer; 22 | if (commandAnswer == "NAK") 23 | { 24 | return true; 25 | } 26 | if (commandAnswer == "ERCRC") 27 | { 28 | return false; 29 | } 30 | staticData["Protocol_ID"] = commandAnswer.toInt(); 31 | return true; 32 | } 33 | else if (protocol == NoD) 34 | { 35 | return false; 36 | } 37 | else 38 | { 39 | return false; 40 | } 41 | } -------------------------------------------------------------------------------- /src/PI_Serial/QPIGS.h: -------------------------------------------------------------------------------- 1 | static const char *const qpigsList[][24] = { 2 | // [PI34 / MPPT-3000], [PI30 HS MS MSX], [PI30 Revo], [PI30 PIP], [PI41 / LV5048] 3 | { 4 | "AC_in_Voltage", // BBB.B 5 | "AC_in_Frequenz", // CC.C 6 | "AC_out_Voltage", // DDD.D 7 | "AC_out_Frequenz", // EE.E 8 | "AC_out_VA", // FFFF 9 | "AC_out_Watt", // GGGG 10 | "AC_out_percent", // HHH 11 | "Inverter_Bus_Voltage", // III 12 | "Battery_Voltage", // JJ.JJ 13 | "Battery_Charge_Current", // KKK 14 | "Battery_Percent", // OOO 15 | "Inverter_Bus_Temperature", // TTTT 16 | "PV_Input_Current", // EE.E 17 | "PV_Input_Voltage", // UUU.U 18 | "Battery_SCC_Volt", // WW.WW 19 | "Battery_Discharge_Current", // PPPP 20 | "Status_Flag", // b0-b7 21 | "Battery_voltage_offset_fans_on", // QQ 22 | "EEPROM_Version", // VV 23 | "PV_Charging_Power", // MMMM 24 | "Device_Status", // b8-b10 25 | "Solar_feed_to_Grid_status", // Y 26 | "Country", // ZZ 27 | "Solar_feed_to_grid_power", // AAAA 28 | }, 29 | // [PI16] 30 | { 31 | "Grid_voltage", // AAA.A 32 | "Output_power", // BBBBBB 33 | "Grid_frequency", // CC.C 34 | "Output_current", // DDDD.D 35 | "AC_output_voltage", // EEE.E 36 | "AC_output_power", // FFFFF 37 | "AC_output_frequency", // GG.G 38 | "AC_output_current", // HHH.H 39 | "Output_load_percent", // III 40 | "PBUS_voltage", // JJJ.J 41 | "SBUS_voltage", // KKK.K 42 | "Positive_battery_voltage", // LLL.L 43 | "Negative_battery_voltage", // MMM.M 44 | "Battery_capacity", // NNN 45 | "PV1_input_power", // OOOOO 46 | "PV2_input_power", // PPPPP 47 | "PV3_input_power", // QQQQQ 48 | "PV1_input_voltage", // RRR.R 49 | "PV2_input_voltage", // SSS.S 50 | "PV3_input_voltage", // TTT.T 51 | "Max_temperature", // UUU.U 52 | }, 53 | }; 54 | static const char *const qallList[] = { 55 | // [PI30 Revo] 56 | "AC_in_Voltage", // BBB.B 57 | "AC_in_Frequenz", // CC.C 58 | "AC_out_Voltage", // DDD.D 59 | "AC_out_Frequenz", // EE.E 60 | "AC_out_VA", // FFFF 61 | "AC_out_percent", // GGG 62 | "Battery_Voltage", // HH.H 63 | "Battery_Percent", // III 64 | "Battery_Charge_Current", // JJJ 65 | "Battery_Discharge_Current", // KKK 66 | "PV_Input_Voltage", // LLL 67 | "PV_Input_Current", // MM.M 68 | "PV_Charging_Power", // NNNN 69 | "PV_generation_day", // OOOOOO 70 | "PV_generation_sum", // PPPPPP 71 | "Inverter_Operation_Mode", // Q 72 | "Warning_Code", // KK 73 | "Fault_code", // SS 74 | }; 75 | static const char *const P005GS[][28] = { 76 | {"AC_in_Voltage", "10"}, // AAAA 77 | {"AC_in_Frequenz", "10"}, // BBB 78 | {"AC_out_Voltage", "10"}, // CCCC 79 | {"AC_out_Frequenz", "10"}, // DDD 80 | {"AC_out_VA", "0"}, // EEEE 81 | {"AC_out_Watt", "0"}, // FFFF 82 | {"AC_out_percent", "0"}, // GGGG 83 | {"Battery_Voltage", "10"}, // HHHH 84 | {"Battery_SCC_Volt", "10"}, // III 85 | {"Battery_SCC2_Volt", "10"}, // JJJ 86 | {"Battery_Discharge_Current", "0"}, // KKK 87 | {"Battery_Charge_Current", "0"}, // LLL 88 | {"Battery_Percent", "0"}, // MMM 89 | {"Inverter_Bus_Temperature", "0"}, // NNN 90 | {"MPPT1_Charger_Temperature", "0"}, // OOO 91 | {"MPPT2_Charger_Temperature", "0"}, // PPP 92 | {"PV1_Input_Power", "0"}, // QQQQ 93 | {"PV2_Input_Power", "0"}, // RRRR 94 | {"PV1_Input_Voltage", "10"}, // SSSS 95 | {"PV2_Input_Voltage", "10"}, // TTTT 96 | {"Configuration_State", "0"}, // U 97 | {"MPPT1_Charger_Status", "0"}, // V 98 | {"MPPT2_CHarger_Status", "0"}, // W 99 | {"Load_Connection", "0"}, // X 100 | {"Battery_Power_Direction", "0"}, // Y 101 | {"ACDC_Power_Direction", "0"}, // Z 102 | {"Line_Power_Direction", "0"}, // a 103 | {"Local_Parallel_ID", "0"}, // b 104 | }; 105 | 106 | bool PI_Serial::PIXX_QPIGS() 107 | { 108 | if (protocol == PI30) 109 | { 110 | byte protocolNum = 0; // for future use 111 | get.raw.qall = ""; 112 | String commandAnswerQALL = this->requestData("QALL"); 113 | get.raw.qall = commandAnswerQALL; 114 | 115 | //get.raw.qall = "NAK"; 116 | // if (commandAnswerQALL == "ERCRC") 117 | //{ 118 | // return false; 119 | //} 120 | // 121 | String commandAnswerQPIGS = this->requestData("QPIGS"); 122 | get.raw.qpigs = commandAnswerQPIGS; 123 | if (commandAnswerQPIGS == "NAK") 124 | return true; 125 | if (commandAnswerQPIGS == "ERCRC") 126 | return false; 127 | byte commandAnswerLength = commandAnswerQPIGS.length(); 128 | 129 | // calculate the length with https://elmar-eigner.de/text-zeichen-laenge.html 130 | if (commandAnswerLength >= 60 && commandAnswerLength <= 140) 131 | { 132 | if (commandAnswerLength <= 116) 133 | { 134 | protocolNum = 0; 135 | } 136 | else if (commandAnswerLength > 131) 137 | { 138 | protocolNum = 1; 139 | } 140 | 141 | // Split the string into substrings 142 | String strs[30]; // buffer for string splitting 143 | int StringCount = 0; 144 | while (commandAnswerQPIGS.length() > 0) 145 | { 146 | int index = commandAnswerQPIGS.indexOf(delimiter); 147 | if (index == -1) // No space found 148 | { 149 | strs[StringCount++] = commandAnswerQPIGS; 150 | break; 151 | } 152 | else 153 | { 154 | strs[StringCount++] = commandAnswerQPIGS.substring(0, index); 155 | commandAnswerQPIGS = commandAnswerQPIGS.substring(index + 1); 156 | } 157 | } 158 | 159 | for (unsigned int i = 0; i < sizeof qpigsList[protocolNum] / sizeof qpigsList[protocolNum][0]; i++) 160 | { 161 | if (!strs[i].isEmpty() && strcmp(qpigsList[protocolNum][i], "") != 0) 162 | liveData[qpigsList[protocolNum][i]] = (int)(strs[i].toFloat() * 100 + 0.5) / 100.0; 163 | } 164 | // make some things pretty 165 | liveData["Battery_Load"] = (liveData["Battery_Charge_Current"].as<unsigned short>() - liveData["Battery_Discharge_Current"].as<unsigned short>()); 166 | liveData["PV_Input_Power"] = (liveData["PV_Input_Voltage"].as<unsigned short>() * liveData["PV_Input_Current"].as<unsigned short>()); 167 | } 168 | 169 | if (get.raw.qall.length() > 10 /*get.raw.qall != "NAK" || get.raw.qall != "ERCRC" || get.raw.qall != ""*/) 170 | { 171 | String strsQALL[30]; 172 | // Split the string into substrings 173 | int StringCountQALL = 0; 174 | while (commandAnswerQALL.length() > 0) 175 | { 176 | int index = commandAnswerQALL.indexOf(delimiter); 177 | if (index == -1) // No space found 178 | { 179 | strsQALL[StringCountQALL++] = commandAnswerQALL; 180 | break; 181 | } 182 | else 183 | { 184 | strsQALL[StringCountQALL++] = commandAnswerQALL.substring(0, index); 185 | commandAnswerQALL = commandAnswerQALL.substring(index + 1); 186 | } 187 | } 188 | 189 | for (unsigned int i = 0; i < sizeof qallList / sizeof qallList[0]; i++) 190 | { 191 | if (!strsQALL[i].isEmpty() && strcmp(qallList[i], "") != 0) 192 | liveData[qallList[i]] = (int)(strsQALL[i].toFloat() * 100 + 0.5) / 100.0; 193 | } 194 | liveData["Inverter_Operation_Mode"] = getModeDesc((char)liveData["Inverter_Operation_Mode"].as<String>().charAt(0)); 195 | liveData["Battery_Load"] = (liveData["Battery_Charge_Current"].as<unsigned short>() - liveData["Battery_Discharge_Current"].as<unsigned short>()); 196 | } 197 | 198 | return true; 199 | } 200 | else if (protocol == PI18) 201 | { 202 | String commandAnswer = this->requestData("^P005GS"); 203 | get.raw.qpigs = commandAnswer; 204 | if (commandAnswer == "NAK") 205 | return true; 206 | if (commandAnswer == "ERCRC") 207 | return false; 208 | byte commandAnswerLength = commandAnswer.length(); 209 | 210 | // calculate the length with https://elmar-eigner.de/text-zeichen-laenge.html 211 | if (commandAnswerLength >= 60 && commandAnswerLength <= 140) 212 | { 213 | // Split the string into substrings 214 | String strs[30]; // buffer for string splitting 215 | int StringCount = 0; 216 | while (commandAnswer.length() > 0) 217 | { 218 | int index = commandAnswer.indexOf(delimiter); 219 | if (index == -1) // No space found 220 | { 221 | strs[StringCount++] = commandAnswer; 222 | break; 223 | } 224 | else 225 | { 226 | strs[StringCount++] = commandAnswer.substring(0, index); 227 | commandAnswer = commandAnswer.substring(index + 1); 228 | } 229 | } 230 | 231 | for (unsigned int i = 0; i < sizeof P005GS[0] / sizeof P005GS[0][0]; i++) 232 | { 233 | if (!strs[i].isEmpty() && strcmp(P005GS[i][0], "") != 0) 234 | { 235 | if (atoi(P005GS[i][1]) > 0) 236 | { 237 | liveData[P005GS[i][0]] = (int)((strs[i].toFloat() / atoi(P005GS[i][1])) * 100 + 0.5) / 100.0; 238 | } 239 | else if (atoi(P005GS[i][1]) == 0) 240 | { 241 | liveData[P005GS[i][0]] = strs[i].toInt(); 242 | } 243 | else 244 | { 245 | liveData[P005GS[i][0]] = strs[i]; 246 | } 247 | } 248 | } 249 | // make some things pretty 250 | 251 | liveData["PV_Input_Voltage"] = (liveData["PV1_Input_Voltage"].as<unsigned short>() + liveData["PV2_Input_Voltage"].as<unsigned short>()); 252 | liveData["PV_Charging_Power"] = (liveData["PV1_Input_Power"].as<unsigned short>() + liveData["PV2_Input_Power"].as<unsigned short>()); 253 | liveData["PV_Input_Current"] = (int)((liveData["PV_Charging_Power"].as<unsigned short>() / (liveData["PV_Input_Voltage"].as<unsigned short>()+0.5)) * 100) / 100.0; 254 | liveData["Battery_Load"] = (liveData["Battery_Charge_Current"].as<unsigned short>() - liveData["Battery_Discharge_Current"].as<unsigned short>()); 255 | } 256 | return true; 257 | } 258 | else if (protocol == NoD) 259 | { 260 | return false; 261 | } 262 | else 263 | { 264 | return false; 265 | } 266 | } -------------------------------------------------------------------------------- /src/PI_Serial/QPIGS2.h: -------------------------------------------------------------------------------- 1 | static const char *const qpigs2List[] = { 2 | // [PI34 / MPPT-3000], [PI30 HS MS MSX], [PI30 Revo], [PI30 PIP], [PI41 / LV5048] 3 | "PV2_Input_Current", // BBB.B 4 | "PV2_Input_Voltage", // CCC.C 5 | "PV2_Charging_Power", // DDDD 6 | }; 7 | 8 | bool PI_Serial::PIXX_QPIGS2() 9 | { 10 | if (protocol == PI30) 11 | { 12 | String commandAnswer = this->requestData("QPIGS2"); 13 | get.raw.qpigs2 = commandAnswer; 14 | byte commandAnswerLength = commandAnswer.length(); 15 | String strs[30]; // buffer for string splitting 16 | if (commandAnswer == "NAK") 17 | { 18 | return true; 19 | } 20 | if (commandAnswer == "ERCRC") 21 | { 22 | return false; 23 | } 24 | 25 | // calculate the length with https://elmar-eigner.de/text-zeichen-laenge.html 26 | if (commandAnswerLength >= 10 && commandAnswerLength <= 20) 27 | { 28 | 29 | // Split the string into substrings 30 | int StringCount = 0; 31 | while (commandAnswer.length() > 0) 32 | { 33 | int index = commandAnswer.indexOf(delimiter); 34 | if (index == -1) // No space found 35 | { 36 | strs[StringCount++] = commandAnswer; 37 | break; 38 | } 39 | else 40 | { 41 | strs[StringCount++] = commandAnswer.substring(0, index); 42 | commandAnswer = commandAnswer.substring(index + 1); 43 | } 44 | } 45 | 46 | for (unsigned int i = 0; i < sizeof qpigs2List / sizeof qpigs2List[0]; i++) 47 | { 48 | if (!strs[i].isEmpty() && strcmp(qpigs2List[i], "") != 0) 49 | liveData[qpigs2List[i]] = (int)(strs[i].toFloat() * 100 + 0.5) / 100.0; 50 | } 51 | // make some things pretty 52 | liveData["PV2_Input_Power"] = (liveData["PV2_Input_Voltage"].as<unsigned short>() * liveData["PV2_Input_Current"].as<unsigned short>()); 53 | } 54 | return true; 55 | } 56 | else if (protocol == PI18) 57 | { 58 | return true; 59 | } 60 | else if (protocol == NoD) 61 | { 62 | return false; 63 | } 64 | else 65 | { 66 | return false; 67 | } 68 | } -------------------------------------------------------------------------------- /src/PI_Serial/QPIWS.h: -------------------------------------------------------------------------------- 1 | bool PI_Serial::PIXX_QPIWS() 2 | { 3 | if (protocol == PI30) 4 | { 5 | String commandAnswer = this->requestData("QPIWS"); 6 | //String commandAnswer = "10000000001010000000000000000000"; 7 | get.raw.qpiws = commandAnswer; 8 | if (commandAnswer == "NAK") 9 | { 10 | return true; 11 | } 12 | if (commandAnswer == "ERCRC") 13 | { 14 | return false; 15 | } 16 | if (commandAnswer.length() == 32) 17 | { 18 | std::vector<String> qpiwsStrings; 19 | if ((char)commandAnswer.charAt(1) == '1') qpiwsStrings.emplace_back("Inverter fault"); // 2 20 | if ((char)commandAnswer.charAt(2) == '1') qpiwsStrings.emplace_back("Bus over fault"); // 3 21 | if ((char)commandAnswer.charAt(3) == '1') qpiwsStrings.emplace_back("Bus under fault"); // 4 22 | if ((char)commandAnswer.charAt(4) == '1') qpiwsStrings.emplace_back("Bus soft fail fault"); // 5 23 | if ((char)commandAnswer.charAt(5) == '1') qpiwsStrings.emplace_back("Line fail warning"); // 6 24 | if ((char)commandAnswer.charAt(6) == '1') qpiwsStrings.emplace_back("OPV short warning"); // 7 25 | if ((char)commandAnswer.charAt(7) == '1') qpiwsStrings.emplace_back("Inverter voltage too low fault"); // 8 26 | if ((char)commandAnswer.charAt(8) == '1') qpiwsStrings.emplace_back("Inverter voltage too high fault"); // 9 27 | if ((char)commandAnswer.charAt(9) == '1') qpiwsStrings.emplace_back("Over temperature fault"); // 10 28 | if ((char)commandAnswer.charAt(10) == '1') qpiwsStrings.emplace_back("Fan locked fault"); // 11 29 | if ((char)commandAnswer.charAt(11) == '1') qpiwsStrings.emplace_back("Battery voltage too high fault"); // 12 30 | if ((char)commandAnswer.charAt(12) == '1') qpiwsStrings.emplace_back("Battery low alarm warning"); // 13 31 | if ((char)commandAnswer.charAt(14) == '1') qpiwsStrings.emplace_back("Battery under shutdown warning"); // 15 32 | if ((char)commandAnswer.charAt(16) == '1') qpiwsStrings.emplace_back("Overload fault"); // 17 33 | if ((char)commandAnswer.charAt(17) == '1') qpiwsStrings.emplace_back("EEPROM fault"); // 18 34 | if ((char)commandAnswer.charAt(18) == '1') qpiwsStrings.emplace_back("Inverter over current fault"); // 19 35 | if ((char)commandAnswer.charAt(19) == '1') qpiwsStrings.emplace_back("Inverter soft fail fault"); // 20 36 | if ((char)commandAnswer.charAt(20) == '1') qpiwsStrings.emplace_back("Self test fail fault"); // 21 37 | if ((char)commandAnswer.charAt(21) == '1') qpiwsStrings.emplace_back("OP DC voltage over fault"); // 22 38 | if ((char)commandAnswer.charAt(22) == '1') qpiwsStrings.emplace_back("Battery open fault"); // 23 39 | if ((char)commandAnswer.charAt(23) == '1') qpiwsStrings.emplace_back("Current sensor fail fault"); // 24 40 | if ((char)commandAnswer.charAt(24) == '1') qpiwsStrings.emplace_back("Battery short fault"); // 25 41 | if ((char)commandAnswer.charAt(25) == '1') qpiwsStrings.emplace_back("Power limit warning"); // 26 42 | if ((char)commandAnswer.charAt(26) == '1') qpiwsStrings.emplace_back("PV voltage high warning"); // 27 43 | if ((char)commandAnswer.charAt(27) == '1') qpiwsStrings.emplace_back("MPPT overload fault"); // 28 44 | if ((char)commandAnswer.charAt(28) == '1') qpiwsStrings.emplace_back("MPPT overload warning"); // 29 45 | if ((char)commandAnswer.charAt(29) == '1') qpiwsStrings.emplace_back("Battery too low to charge warning"); // 30 46 | if (!qpiwsStrings.empty()) 47 | { 48 | String qpiwsStr = ""; 49 | for (size_t i = 0; i < qpiwsStrings.size(); i++) { 50 | qpiwsStr += qpiwsStrings[i]; 51 | if (i < qpiwsStrings.size() - 1) { 52 | qpiwsStr += "; "; 53 | } 54 | } 55 | liveData["Fault_code"] = qpiwsStr; 56 | } 57 | else 58 | { 59 | liveData["Fault_code"] = "Ok"; 60 | } 61 | } 62 | return true; 63 | } 64 | else if(protocol == PI18){ 65 | String commandAnswer = this->requestData("^P005FWS"); 66 | get.raw.qpiws = commandAnswer; 67 | if (commandAnswer == "NAK") 68 | { 69 | return true; 70 | } 71 | if (commandAnswer == "ERCRC") 72 | { 73 | return false; 74 | } 75 | //[C: ^P005FWS][CR: B69E][CC: B69E][L: 36] from valqk 76 | //QPIWS 00,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 77 | if (commandAnswer.length() == 36) 78 | { 79 | std::vector<String> qpiwsStrings; 80 | if (commandAnswer.substring(0,2) == "01") qpiwsStrings.emplace_back("Fan is locked"); // 2 81 | if (commandAnswer.substring(0,2) == "02") qpiwsStrings.emplace_back("Over temperature"); // 2 82 | if (commandAnswer.substring(0,2) == "03") qpiwsStrings.emplace_back("Battery voltage is too high"); // 2 83 | if (commandAnswer.substring(0,2) == "04") qpiwsStrings.emplace_back("Battery voltage is too low"); // 2 84 | if (commandAnswer.substring(0,2) == "05") qpiwsStrings.emplace_back("Output short circuited or Over temperature"); // 2 85 | if (commandAnswer.substring(0,2) == "06") qpiwsStrings.emplace_back("Output voltage is too high"); // 2 86 | if (commandAnswer.substring(0,2) == "07") qpiwsStrings.emplace_back("Over load time out"); // 2 87 | if (commandAnswer.substring(0,2) == "08") qpiwsStrings.emplace_back("Bus voltage is too high"); // 2 88 | if (commandAnswer.substring(0,2) == "09") qpiwsStrings.emplace_back("Bus soft start failed"); // 2 89 | if (commandAnswer.substring(0,2) == "11") qpiwsStrings.emplace_back("Main relay failed"); // 2 90 | if (commandAnswer.substring(0,2) == "51") qpiwsStrings.emplace_back("Over current inverter"); // 2 91 | if (commandAnswer.substring(0,2) == "52") qpiwsStrings.emplace_back("Bus soft start failed"); // 2 92 | if (commandAnswer.substring(0,2) == "53") qpiwsStrings.emplace_back("Inverter soft start failed"); // 2 93 | if (commandAnswer.substring(0,2) == "54") qpiwsStrings.emplace_back("Self-test failed"); // 2 94 | if (commandAnswer.substring(0,2) == "55") qpiwsStrings.emplace_back("Over DC voltage on output of inverter"); // 2 95 | if (commandAnswer.substring(0,2) == "56") qpiwsStrings.emplace_back("Battery connection is open"); // 2 96 | if (commandAnswer.substring(0,2) == "57") qpiwsStrings.emplace_back("Current sensor failed"); // 2 97 | if (commandAnswer.substring(0,2) == "58") qpiwsStrings.emplace_back("Output voltage is too low"); // 2 98 | if (commandAnswer.substring(0,2) == "60") qpiwsStrings.emplace_back("Inverter negative power"); // 2 99 | if (commandAnswer.substring(0,2) == "71") qpiwsStrings.emplace_back("Parallel version different"); // 2 100 | if (commandAnswer.substring(0,2) == "72") qpiwsStrings.emplace_back("Output circuit failed"); // 2 101 | if (commandAnswer.substring(0,2) == "80") qpiwsStrings.emplace_back("CAN communication failed"); // 2 102 | if (commandAnswer.substring(0,2) == "81") qpiwsStrings.emplace_back("Parallel host line lost"); // 2 103 | if (commandAnswer.substring(0,2) == "82") qpiwsStrings.emplace_back("Parallel synchronized signal lost"); // 2 104 | if (commandAnswer.substring(0,2) == "83") qpiwsStrings.emplace_back("Parallel battery voltage detect different"); // 2 105 | if (commandAnswer.substring(0,2) == "84") qpiwsStrings.emplace_back("Parallel Line voltage or frequency detect different"); // 2 106 | if (commandAnswer.substring(0,2) == "85") qpiwsStrings.emplace_back("Parallel Line input current unbalanced"); // 2 107 | if (commandAnswer.substring(0,2) == "86") qpiwsStrings.emplace_back("Parallel output setting different"); // 2 108 | 109 | if ((char)commandAnswer.charAt(3) == '1') qpiwsStrings.emplace_back("Line fail"); // 2 110 | if ((char)commandAnswer.charAt(5) == '1') qpiwsStrings.emplace_back("Over temperature"); // 20 111 | if ((char)commandAnswer.charAt(7) == '1') qpiwsStrings.emplace_back("Output circuit short"); // 3 112 | if ((char)commandAnswer.charAt(9) == '1') qpiwsStrings.emplace_back("Inverter over temperature"); // 4 113 | if ((char)commandAnswer.charAt(11) == '1') qpiwsStrings.emplace_back("Fan lock"); // 5 114 | if ((char)commandAnswer.charAt(13) == '1') qpiwsStrings.emplace_back("Battery voltage high"); // 6 115 | if ((char)commandAnswer.charAt(15) == '1') qpiwsStrings.emplace_back("Battery low"); // 7 116 | if ((char)commandAnswer.charAt(17) == '1') qpiwsStrings.emplace_back("Battery under"); // 8 117 | if ((char)commandAnswer.charAt(19) == '1') qpiwsStrings.emplace_back("Over load"); // 9 118 | if ((char)commandAnswer.charAt(21) == '1') qpiwsStrings.emplace_back("Eeprom fail"); // 10 119 | if ((char)commandAnswer.charAt(23) == '1') qpiwsStrings.emplace_back("Power limit"); // 11 120 | if ((char)commandAnswer.charAt(25) == '1') qpiwsStrings.emplace_back("PV1 voltage high"); // 12 121 | if ((char)commandAnswer.charAt(27) == '1') qpiwsStrings.emplace_back("PV2 voltage high"); // 13 122 | if ((char)commandAnswer.charAt(29) == '1') qpiwsStrings.emplace_back("MPPT1 overload warning"); // 15 123 | if ((char)commandAnswer.charAt(31) == '1') qpiwsStrings.emplace_back("MPPT2 overload warning"); // 17 124 | if ((char)commandAnswer.charAt(33) == '1') qpiwsStrings.emplace_back("Battery too low to charge for SCC1"); // 18 125 | if ((char)commandAnswer.charAt(35) == '1') qpiwsStrings.emplace_back("Battery too low to charge for SCC2"); // 19 126 | 127 | if (!qpiwsStrings.empty()) 128 | { 129 | String qpiwsStr = ""; 130 | for (size_t i = 0; i < qpiwsStrings.size(); i++) { 131 | qpiwsStr += qpiwsStrings[i]; 132 | if (i < qpiwsStrings.size() - 1) { 133 | qpiwsStr += "; "; 134 | } 135 | } 136 | liveData["Fault_code"] = qpiwsStr; 137 | } 138 | else 139 | { 140 | liveData["Fault_code"] = "Ok"; 141 | } 142 | } 143 | return true; 144 | } 145 | else if (protocol == NoD) 146 | { 147 | return false; 148 | } 149 | else 150 | { 151 | return false; 152 | } 153 | } -------------------------------------------------------------------------------- /src/Settings.h: -------------------------------------------------------------------------------- 1 | #include <Arduino.h> 2 | #include <EEPROM.h> 3 | 4 | #define EEPROM_SIZE 1024 5 | // Settings: Stores persistant settings, loads and saves to EEPROM 6 | 7 | class Settings 8 | { 9 | // change eeprom config version ONLY when new parameter is added and need reset the parameter 10 | unsigned int configVersion = 11; 11 | 12 | public: 13 | String deviceNameStr; 14 | struct Data 15 | { // do not re-sort this struct 16 | unsigned int coVers; // config version, if changed, previus config will erased 17 | char deviceName[40]; // device name 18 | char mqttServer[40]; // mqtt Server adress 19 | char mqttUser[40]; // mqtt Username 20 | char mqttPassword[40]; // mqtt Password 21 | char mqttTopic[40]; // mqtt publish topic 22 | char mqttTriggerPath[80]; // MQTT Data Trigger Path 23 | unsigned int mqttPort; // mqtt port 24 | unsigned int mqttRefresh; // mqtt refresh time 25 | unsigned int deviceQuantity; // Quantity of Devices 26 | bool mqttJson; // switch between classic mqtt and json 27 | bool webUIdarkmode; // Flag for color mode in webUI 28 | char httpUser[40]; // http basic auth username 29 | char httpPass[40]; // http basic auth password 30 | bool haDiscovery; // HomeAssistant Discovery switch 31 | } data; 32 | 33 | void load() 34 | { 35 | data = {}; // clear bevor load data 36 | EEPROM.begin(EEPROM_SIZE); 37 | EEPROM.get(0, data); 38 | EEPROM.end(); 39 | coVersCheck(); 40 | sanitycheck(); 41 | deviceNameStr = data.deviceName; 42 | } 43 | 44 | void save() 45 | { 46 | sanitycheck(); 47 | EEPROM.begin(EEPROM_SIZE); 48 | EEPROM.put(0, data); 49 | EEPROM.commit(); 50 | EEPROM.end(); 51 | } 52 | 53 | void reset() 54 | { 55 | data = {}; 56 | save(); 57 | } 58 | 59 | private: 60 | // check the variables from eeprom 61 | 62 | void sanitycheck() 63 | { 64 | if (strlen(data.deviceName) == 0 || strlen(data.deviceName) >= 40) 65 | { 66 | strcpy(data.deviceName, "Solar2MQTT"); 67 | } 68 | if (strlen(data.mqttServer) == 0 || strlen(data.mqttServer) >= 40) 69 | { 70 | strcpy(data.mqttServer, ""); 71 | } 72 | if (strlen(data.mqttUser) == 0 || strlen(data.mqttUser) >= 40) 73 | { 74 | strcpy(data.mqttUser, ""); 75 | } 76 | if (strlen(data.mqttPassword) == 0 || strlen(data.mqttPassword) >= 40) 77 | { 78 | strcpy(data.mqttPassword, ""); 79 | } 80 | if (strlen(data.mqttTopic) == 0 || strlen(data.mqttTopic) >= 40) 81 | { 82 | strcpy(data.mqttTopic, "Solar"); 83 | } 84 | if (data.mqttPort <= 0 || data.mqttPort >= 65530) 85 | { 86 | data.mqttPort = 0; 87 | } 88 | if (data.mqttRefresh <= 1 || data.mqttRefresh >= 65530) 89 | { 90 | data.mqttRefresh = 0; 91 | } 92 | if (data.mqttJson && !data.mqttJson) 93 | { 94 | data.mqttJson = false; 95 | } 96 | if (data.deviceQuantity < 1 || data.deviceQuantity >= 10) 97 | { 98 | data.deviceQuantity = 1; 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 | } 121 | void coVersCheck() 122 | { 123 | if (data.coVers != configVersion) 124 | { 125 | data.coVers = configVersion; 126 | strcpy(data.deviceName, "Solar2MQTT"); 127 | strcpy(data.mqttServer, ""); 128 | strcpy(data.mqttUser, ""); 129 | strcpy(data.mqttPassword, ""); 130 | strcpy(data.mqttTopic, "Solar"); 131 | strcpy(data.mqttTriggerPath, ""); 132 | data.deviceQuantity = 1; 133 | data.mqttPort = 0; 134 | data.mqttRefresh = 300; 135 | data.mqttJson = false; 136 | data.webUIdarkmode = false; 137 | strcpy(data.httpUser, ""); 138 | strcpy(data.httpPass, ""); 139 | data.haDiscovery = false; 140 | save(); 141 | load(); 142 | } 143 | } 144 | }; 145 | -------------------------------------------------------------------------------- /src/htmlProzessor.h: -------------------------------------------------------------------------------- 1 | String htmlProcessor(const String &var) 2 | { 3 | extern Settings settings; 4 | if (var == F("pre_head_template")) 5 | return (FPSTR(HTML_HEAD)); 6 | if (var == F("pre_foot_template")) 7 | return (FPSTR(HTML_FOOT)); 8 | if (var == F("pre_software_version")) 9 | return (SOFTWARE_VERSION); 10 | if (var == F("pre_swversion")) 11 | return (SWVERSION); 12 | if (var == F("pre_flash_size")) 13 | return (String(ESP.getFreeSketchSpace()).c_str()); 14 | // if (var == F("pre_esp01")) 15 | // return (String(ESP01).c_str()); 16 | if (var == F("pre_device_name")) 17 | return (settings.data.deviceName); 18 | if (var == F("pre_mqtt_server")) 19 | return (settings.data.mqttServer); 20 | if (var == F("pre_mqtt_port")) 21 | return (String(settings.data.mqttPort).c_str()); 22 | if (var == F("pre_mqtt_user")) 23 | return (settings.data.mqttUser); 24 | if (var == F("pre_mqtt_pass")) 25 | return (settings.data.mqttPassword); 26 | if (var == F("pre_mqtt_topic")) 27 | return (settings.data.mqttTopic); 28 | if (var == F("pre_mqtt_refresh")) 29 | return (String(settings.data.mqttRefresh).c_str()); 30 | if (var == F("pre_mqtt_json")) 31 | return (settings.data.mqttJson ? "checked" : ""); 32 | if (var == F("pre_mqtt_mqtttrigger")) 33 | return (settings.data.mqttTriggerPath); 34 | if (var == F("pre_darkmode")) 35 | return (settings.data.webUIdarkmode ? "dark" : "light"); 36 | if (var == F("pre_webuidarkmode")) 37 | return (settings.data.webUIdarkmode ? "checked" : ""); 38 | if (var == F("pre_http_user")) 39 | return (settings.data.httpUser); 40 | if (var == F("pre_http_pass")) 41 | return (settings.data.httpPass); 42 | if (var == F("pre_hadiscovery")) 43 | return (settings.data.haDiscovery ? "checked" : ""); 44 | return String(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #include <WebSerialLite.h> 2 | #define ARDUINOJSON_USE_DOUBLE 1 3 | #define ARDUINOJSON_USE_LONG_LONG 1 4 | #define JSON_BUFFER 2048 5 | 6 | 7 | #ifdef isUART_HARDWARE 8 | #define INVERTER_TX 1 9 | #define INVERTER_RX 3 10 | #define LED_COM 5 11 | #define LED_SRV 0 12 | #define LED_NET 4 13 | #else 14 | #define INVERTER_TX 13 15 | #define INVERTER_RX 12 16 | #define TEMPSENS_PIN 4 // DS18B20 Pin; D2 on Wemos D1 Mini 17 | #endif 18 | 19 | #define LED_PIN 02 // D4 with the LED on Wemos D1 Mini 20 | 21 | 22 | #define DBG_BAUD 115200 23 | #define DBG_WEBLN(...) WebSerial.println(__VA_ARGS__) 24 | #define SOFTWARE_VERSION SWVERSION 25 | #define DBG Serial 26 | #define DBG_BEGIN(...) DBG.begin(__VA_ARGS__) 27 | #define DBG_PRINTLN(...) DBG.println(__VA_ARGS__) 28 | 29 | 30 | /** 31 | * @brief callback function for wifimanager save config data 32 | * 33 | */ 34 | void saveConfigCallback(); 35 | 36 | /** 37 | * @brief callback function for data 38 | * 39 | */ 40 | bool prozessData(); 41 | 42 | /** 43 | * @brief fires up the websocket and send data to the clients 44 | * 45 | */ 46 | void notifyClients(); 47 | 48 | /** 49 | * @brief build the topic string and return 50 | * 51 | */ 52 | char *topicBuilder(char *buffer, char const *path, char const *numering); 53 | 54 | /** 55 | * @brief mqtt connect function, check if connection etablished and reconnect and subscribe to spezific topics if needed 56 | * 57 | */ 58 | bool connectMQTT(); 59 | 60 | /** 61 | * @brief send the data to mqtt 62 | * 63 | */ 64 | bool sendtoMQTT(); 65 | 66 | /** 67 | * @brief get the basic device data 68 | * 69 | */ 70 | // void getJsonDevice(); 71 | 72 | /** 73 | * @brief read the data from bms and put it in the json 74 | */ 75 | void getJsonData(); 76 | 77 | /** 78 | * @brief callback function, watch the sunscribed topics and process the data 79 | * 80 | */ 81 | void mqttcallback(char *top, unsigned char *payload, unsigned int length); 82 | 83 | bool sendHaDiscovery(); 84 | 85 | /** 86 | * @brief this function act like s/n/printf() and give the output to the configured serial and webserial 87 | * 88 | */ 89 | void writeLog(const char* format, ...); 90 | 91 | static const char *const haStaticDescriptor[][4]{ 92 | // state_topic, icon, unit_ofmeasurement, class 93 | {"AC_in_rating_current", "current-ac", "A", "current"}, 94 | {"AC_in_rating_voltage", "flash-triangle-outline", "V", "voltage"}, 95 | {"AC_out_rating_active_power", "sine-wave", "W", "power"}, 96 | {"AC_out_rating_apparent_power", "sine-wave", "W", "power"}, 97 | {"AC_out_rating_current", "current-ac", "A", "current"}, 98 | {"AC_out_rating_frequency", "sine-wave", "Hz", "frequency"}, 99 | {"AC_out_rating_voltage", "flash-triangle-outline", "V", "voltage"}, 100 | {"Battery_bulk_voltage", "car-battery", "V", "voltage"}, 101 | {"Battery_float_voltage", "car-battery", "V", "voltage"}, 102 | {"Battery_rating_voltage", "car-battery", "V", "voltage"}, 103 | {"Battery_re-charge_voltage", "battery-charging-high", "V", "voltage"}, 104 | {"Battery_re-discharge_voltage", "battery-charging-outline", "V", "voltage"}, 105 | {"Battery_type", "car-battery", "", ""}, 106 | {"Battery_under_voltage", "battery-remove-outline", "V", "voltage"}, 107 | {"Charger_source_priority", "ev-station", "", ""}, 108 | {"Current_max_AC_charging_current", "current-ac", "A", "current"}, 109 | {"Current_max_charging_current", "battery-charging", "A", "current"}, 110 | {"Device_Model", "battery-charging", "", ""}, 111 | {"Input_voltage_range", "flash-triangle-outline", "", ""}, 112 | {"Machine_type", "state-machine", "", ""}, 113 | {"Max_charging_time_at_CV_stage", "clock-time-eight-outli", "s", "duration"}, 114 | {"Max_discharging_current", "battery-outline", "A", "current"}, 115 | {"MPPT_string", "string-lights", "", ""}, 116 | {"Operation_Logic", "access-point", "", ""}, 117 | {"Output_mode", "export", "", ""}, 118 | {"Output_source_priority", "export", "", ""}, 119 | //{"Parallel_max_num","","",""}, 120 | {"Protocol_ID", "protocol", "", ""}, 121 | //{"PV_OK_condition_for_parallel","solar-panel","",""}, 122 | {"PV_power_balance", "solar-panel", "", ""}, 123 | {"Solar_power_priority", "priority-high", "", ""}, 124 | {"Topology", "earth", "", ""}, 125 | {"Buzzer_Enabled", "tune-variant", "", ""}, 126 | {"Overload_bypass_Enabled", "tune-variant", "", ""}, 127 | {"Power_saving_Enabled", "tune-variant", "", ""}, 128 | {"LCD_reset_to_default_Enabled", "tune-variant", "", ""}, 129 | {"Overload_restart_Enabled", "tune-variant", "", ""}, 130 | {"Over_temperature_restart_Enabled", "tune-variant", "", ""}, 131 | {"LCD_backlight_Enabled", "tune-variant", "", ""}, 132 | {"Primary_source_interrupt_alarm_Enabled", "tune-variant", "", ""}, 133 | {"Record_fault_code_Enabled", "tune-variant", "", ""}}; 134 | static const char *const haLiveDescriptor[][4]{ 135 | // state_topic, icon, unit_ofmeasurement, class 136 | {"AC_in_Frequenz", "import", "Hz", "frequency"}, 137 | {"AC_in_generation_day", "import", "Wh", "energy"}, 138 | {"AC_in_generation_month", "import", "Wh", "energy"}, 139 | {"AC_in_generation_sum", "import", "Wh", "energy"}, 140 | {"AC_in_generation_year", "import", "Wh", "energy"}, 141 | {"AC_in_Voltage", "import", "V", "voltage"}, 142 | {"AC_out_Frequenz", "export", "Hz", "frequency"}, 143 | {"AC_out_percent", "export", "%", "power_factor"}, 144 | {"AC_out_VA", "export", "VA", "apparent_power"}, 145 | {"AC_out_Voltage", "export", "V", "voltage"}, 146 | {"AC_out_Watt", "export", "W", "power"}, 147 | {"AC_output_current", "export", "A", "current"}, 148 | {"AC_output_frequency", "export", "Hz", "frequency"}, 149 | {"AC_output_power", "export", "W", "power"}, 150 | {"AC_output_voltage", "export", "V", "voltage"}, 151 | //{"ACDC_Power_Direction","sign-direction","",""}, 152 | {"Battery_capacity", "battery-high", "%", "battery"}, 153 | //{"Battery_Charge_Current","battery-charging-high","A","current"}, 154 | //{"Battery_Discharge_Current","battery-charging-outli","A","current"}, 155 | {"Battery_Load", "battery-charging-high", "A", "current"}, 156 | {"Battery_Percent", "battery-charging-high", "%", "battery"}, 157 | {"Battery_Power_Direction", "battery-charging-high", "", ""}, 158 | //{"Battery_SCC_Volt","battery-high","V","voltage"}, 159 | //{"Battery_SCC2_Volt","battery-high","V","voltage"}, 160 | {"Battery_temperature", "thermometer-lines", "°C", "temperature"}, 161 | {"Battery_Voltage", "battery-high", "V", "voltage"}, 162 | //{"Battery_voltage_offset_fans_on","fan","",""}, 163 | //{"Configuration_State","state-machine","",""}, 164 | //{"Country","earth","",""}, 165 | //{"Device_Status","state-machine","",""}, 166 | //{"EEPROM_Version","chip","",""}, 167 | {"Fan_speed","fan","%",""}, 168 | {"Fault_code","alert-outline","",""}, 169 | {"Grid_frequency", "import", "Hz", "frequency"}, 170 | {"Grid_voltage", "import", "V", "voltage"}, 171 | {"Inverter_Bus_Temperature", "thermometer-lines", "°C", "temperature"}, 172 | {"Inverter_Bus_Voltage", "flash-triangle-outline", "V", "voltage"}, 173 | //{"Inverter_charge_state","car-turbocharger","",""}, 174 | {"Inverter_Operation_Mode", "car-turbocharger", "", ""}, 175 | {"Inverter_temperature", "thermometer-lines", "°C", "temperature"}, 176 | //{"Line_Power_Direction","transmission-tower","",""}, 177 | //{"Load_Connection","connection","",""}, 178 | {"Local_Parallel_ID", "card-account-details-outline", "", ""}, 179 | //{"Max_temperature","thermometer-plus","C","temperature"}, 180 | //{"MPPT1_Charger_Status","car-turbocharger","",""}, 181 | {"MPPT1_Charger_Temperature", "thermometer-lines", "°C", "temperature"}, 182 | //{"MPPT2_CHarger_Status","car-turbocharger","",""}, 183 | {"MPPT2_Charger_Temperature", "thermometer-lines", "°C", "temperature"}, 184 | {"Negative_battery_voltage", "battery-minus-outline", "V", "voltage"}, 185 | {"Output_current", "export", "A", "current"}, 186 | {"Output_load_percent", "export", "%", "battery"}, 187 | {"Output_power", "export", "W", "power"}, 188 | //{"PBUS_voltage","","V","voltage"}, 189 | {"Positive_battery_voltage", "car-battery", "V", "voltage"}, 190 | {"PV_Charging_Power", "solar-power-variant", "W", "power"}, 191 | {"PV_generation_day", "solar-power-variant", "Wh", "energy"}, 192 | {"PV_generation_month", "solar-power-variant", "Wh", "energy"}, 193 | {"PV_generation_sum", "solar-power-variant", "Wh", "energy"}, 194 | {"PV_generation_year", "solar-power-variant", "Wh", "energy"}, 195 | {"PV_Input_Current", "solar-power-variant", "A", "current"}, 196 | {"PV_Input_Power", "solar-power-variant", "W", "power"}, 197 | {"PV_Input_Voltage", "solar-power-variant", "V", "voltage"}, 198 | {"PV1_input_power", "solar-power-variant", "W", "power"}, 199 | {"PV1_input_voltage", "solar-power-variant", "V", "voltage"}, 200 | {"PV2_Charging_Power", "solar-power-variant", "W", "power"}, 201 | {"PV2_Input_Current", "solar-power-variant", "A", "current"}, 202 | {"PV2_input_power", "solar-power-variant", "W", "power"}, 203 | {"PV2_input_voltage", "solar-power-variant", "V", "voltage"}, 204 | {"PV3_input_power", "solar-power-variant", "W", "power"}, 205 | {"PV3_input_voltage", "solar-power-variant", "V", "voltage"}, 206 | //{"SBUS_voltage","flash-triangle-outline","V","voltage"}, 207 | {"Solar_feed_to_grid_power", "solar-power-variant", "W", "power"}, 208 | {"Solar_feed_to_Grid_status", "solar-power-variant", "", ""}, 209 | //{"Status_Flag","flag","",""}, 210 | //{"Time_until_absorb_charge","solar-power-variant","s","duration"}, 211 | //{"Time_until_float_charge","solar-power-variant","s","duration"}, 212 | {"Tracker_temperature", "thermometer-lines", "°C", "temperature"}, 213 | {"Transformer_temperature", "thermometer-lines", "°C", "temperature"}, 214 | {"Warning_Code", "alert-outline", "", ""}}; -------------------------------------------------------------------------------- /src/modbus/modbus.h: -------------------------------------------------------------------------------- 1 | #include "SoftwareSerial.h" 2 | #ifndef MODBUS_H 3 | #define MODBUS_H 4 | 5 | #include <ArduinoJson.h> 6 | #include <ModbusMaster.h> 7 | #include "modbus_registers.h" 8 | extern JsonObject deviceJson; 9 | extern JsonObject staticData; 10 | extern JsonObject liveData; 11 | 12 | #define RS485_DIR_PIN 14 // D5 13 | #define RS485_ESP01_DIR_PIN 0 14 | 15 | #define RS485_BAUDRATE 19200 16 | 17 | #define INVERTER_MODBUS_ADDR 4 18 | 19 | #define MODBUS_RETRIES 2 20 | 21 | typedef enum 22 | { 23 | READ_FAIL = 0, 24 | READ_OK = 1, 25 | } response_type_t; 26 | 27 | typedef struct 28 | { 29 | JsonObject *variant; 30 | const modbus_register_t *registers; 31 | uint8_t array_size; 32 | uint8_t curr_register; 33 | } modbus_register_info_t; 34 | 35 | class MODBUS 36 | { 37 | public: 38 | const uint8_t MAX_CONNECTION_ATTEMPTS = 10; 39 | bool requestStaticData = true; 40 | bool connection = false; 41 | modbus_register_info_t live_info; 42 | modbus_register_info_t static_info; 43 | MODBUS(SoftwareSerial *port); 44 | 45 | /** 46 | * @brief Initializes this driver 47 | * @details Configures the serial peripheral and pre-loads the transmit buffer with command-independent bytes 48 | */ 49 | bool Init(); 50 | 51 | /** 52 | * @brief Updating the Data from the inverter 53 | */ 54 | void loop(); 55 | 56 | /** 57 | * @brief Send custom command to the device 58 | */ 59 | bool sendCommand(String command); 60 | 61 | /** 62 | * @brief callback function 63 | * 64 | */ 65 | void callback(std::function<void()> func); 66 | std::function<void()> requestCallback; 67 | bool readModbusRegisterToJson(const modbus_register_t *reg, JsonObject *variant); 68 | response_type_t parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error = true); 69 | bool isAllRegistersRead(modbus_register_info_t ®ister_info); 70 | bool autoDetect(); 71 | /** 72 | * @brief Sends a complete packet with the specified command 73 | * @details sends the command over the specified serial connection 74 | */ 75 | String requestData(String command); 76 | 77 | private: 78 | bool device_found = false; 79 | unsigned long previousTime = 0; 80 | unsigned long cmdDelayTime = 100; 81 | 82 | byte requestCounter = 0; 83 | 84 | long long int connectionCounter = 0; 85 | 86 | byte qexCounter = 0; 87 | 88 | static void preTransmission(); 89 | static void postTransmission(); 90 | String toBinary(uint16_t input); 91 | bool decodeDiematicDecimal(uint16_t int_input, int8_t decimals, float *value_ptr); 92 | bool getModbusResultMsg(uint8_t result); 93 | bool getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr); 94 | /** 95 | * @brief get the crc from a string 96 | */ 97 | uint16_t getCRC(String data); 98 | 99 | /** 100 | * @brief get the crc from a string 101 | */ 102 | byte getCHK(String data); 103 | 104 | String retrieveModel(); 105 | 106 | /** 107 | * @brief Serial interface used for communication 108 | * @details This is set in the constructor 109 | */ 110 | SoftwareSerial *my_serialIntf; 111 | 112 | ModbusMaster mb; 113 | }; 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /src/modbus/modbus_registers.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MODBUS_REGISTERS_H_ 2 | #define SRC_MODBUS_REGISTERS_H_ 3 | #include "Arduino.h" 4 | 5 | typedef enum 6 | { 7 | MODBUS_TYPE_HOLDING = 0x00, /*!< Modbus Holding register. */ 8 | // MODBUS_TYPE_INPUT, /*!< Modbus Input register. */ 9 | // MODBUS_TYPE_COIL, /*!< Modbus Coils. */ 10 | // MODBUS_TYPE_DISCRETE, /*!< Modbus Discrete bits. */ 11 | // MODBUS_TYPE_COUNT, 12 | // MODBUS_TYPE_UNKNOWN = 0xFF 13 | } modbus_entity_t; 14 | 15 | typedef enum 16 | { 17 | // REGISTER_TYPE_U8 = 0x00, /*!< Unsigned 8 */ 18 | REGISTER_TYPE_U16 = 0x01, /*!< Unsigned 16 */ 19 | REGISTER_TYPE_INT16 = 0x02, /*!< Signed 16 */ 20 | // REGISTER_TYPE_U32 = 0x02, /*!< Unsigned 32 */ 21 | // REGISTER_TYPE_FLOAT = 0x03, /*!< Float type */ 22 | REGISTER_TYPE_ASCII = 0x04, /*!< ASCII type */ 23 | REGISTER_TYPE_DIEMATIC_ONE_DECIMAL = 0x05, 24 | REGISTER_TYPE_DIEMATIC_TWO_DECIMAL = 0x06, 25 | REGISTER_TYPE_BITFIELD = 0x07, 26 | REGISTER_TYPE_DEBUG = 0x08, 27 | REGISTER_TYPE_CUSTOM_VAL_NAME = 0x09, 28 | } register_type_t; 29 | 30 | typedef union 31 | { 32 | const char *bitfield[16]; 33 | } optional_param_t; 34 | 35 | typedef struct 36 | { 37 | uint16_t id; 38 | modbus_entity_t modbus_entity; /*!< Type of modbus parameter */ 39 | register_type_t type; /*!< Float, U8, U16, U32, ASCII, etc. */ 40 | const char *name; 41 | optional_param_t optional_param; 42 | } modbus_register_t; 43 | 44 | const modbus_register_t registers_live[] = { 45 | 46 | {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Inverter_Operation_Mode", {.bitfield = { 47 | "Power On", 48 | "Self Test", 49 | "OffGrid", 50 | "GridTie", 51 | "ByPass", 52 | "Stop", 53 | "GridCharging", 54 | }}}, 55 | 56 | {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_Voltage"}, 57 | {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage"}, 58 | {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage"}, 59 | {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage"}, 60 | {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz"}, 61 | {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz"}, 62 | 63 | {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent"}, 64 | {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV_Input_Voltage"}, 65 | {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power"}, 66 | {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV_Input_Current"}, 67 | {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature"}, 68 | {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature"}, 69 | {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature"}, 70 | 71 | {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt"}, //W 72 | {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent"}, //% 73 | 74 | {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load"}, 75 | }; 76 | 77 | const modbus_register_t registers_static[] = { 78 | 79 | {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", {.bitfield = { 80 | "No choose", 81 | "User defined", 82 | "Lithium", 83 | "Sealed Lead", 84 | "AGM", 85 | "GEL", 86 | "Flooded", 87 | }}}, 88 | {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage"}, 89 | }; 90 | 91 | 92 | #define DEVICE_MODEL_HIGH "Device_Model_Hight" 93 | #define DEVICE_MODEL_LOW "Device_Model_Low" 94 | 95 | const modbus_register_t registers_device_model[] = { 96 | {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "Device_Model_Hight"}, 97 | {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Device_Model_Low"} 98 | }; 99 | 100 | #endif // SRC_MODBUS_REGISTERS_H_ -------------------------------------------------------------------------------- /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 Inverter Connection 8 | 3x no MQTT Connection 9 | 4x no WiFi Connection 10 | 11 | 12 | //#define LED_BUILTIN 5 13 | const int PIN_LED = 5; // COM 14 | const int PIN_LED2 = 0; // SRV 15 | const int PIN_LED4 = 4; // NET 16 | 17 | 18 | */ 19 | unsigned int ledPin = 0; 20 | unsigned int ledTimer = 0; 21 | unsigned int repeatTime = 5000; 22 | unsigned int cycleTime = 250; 23 | unsigned int cycleMillis = 0; 24 | byte ledState = 0; 25 | 26 | // bool waveHelper = false; 27 | void notificationLED() 28 | { 29 | 30 | if (millis() >= (ledTimer + repeatTime) && ledState == 0) 31 | { 32 | if (WiFi.status() != WL_CONNECTED) 33 | ledState = 4; 34 | else if (!mqttclient.connected() && strcmp(settings.data.mqttServer, "") != 0) 35 | ledState = 3; 36 | else if (!mppClient.connection) 37 | ledState = 2; 38 | else if (WiFi.status() == WL_CONNECTED && mqttclient.connected() && mppClient.connection) 39 | ledState = 1; 40 | 41 | #ifdef isUART_HARDWARE 42 | digitalWrite(LED_COM, !mppClient.connection); // make it blink blink when communication 43 | digitalWrite(LED_SRV, !mqttclient.connected()); // make it blinky when sending data 44 | digitalWrite(LED_NET, !(WiFi.status() == WL_CONNECTED) ? true : false); 45 | #endif 46 | } 47 | 48 | if (ledState > 0) 49 | { 50 | if (millis() >= (cycleMillis + cycleTime) /*&& relaisOn != true*/) 51 | { 52 | if (ledPin == 0) 53 | { 54 | ledPin = 255; 55 | } 56 | else 57 | { 58 | ledPin = 0; 59 | ledState--; 60 | } 61 | cycleMillis = millis(); 62 | if (ledState == 0) 63 | { 64 | ledTimer = millis(); 65 | } 66 | } 67 | /* make it later 68 | if (millis() >= (cycleMillis + cycleTime) && relaisOn == true) 69 | { 70 | //ledPin = 127.0 + 128.0 * sin((millis() / (float)(cycleTime * 2)) * 2.0 * PI); 71 | ledPin = (cos((millis() / (float)(cycleTime/4)) - PI)*0.5+0.5)*255; 72 | 73 | if (ledPin == 254 && waveHelper == false) 74 | { 75 | waveHelper = true; 76 | } 77 | if (ledPin == 0 && waveHelper == true) 78 | { 79 | ledState--; 80 | waveHelper = false; 81 | } 82 | 83 | 84 | if (ledState == 0) 85 | { 86 | ledTimer = millis(); 87 | } 88 | } 89 | */ 90 | } 91 | analogWrite(LED_PIN, 255 - ledPin); 92 | } -------------------------------------------------------------------------------- /src/webpages/HTML_CONFIRM_RESET.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 | 3 | <figure class="text-center"><h1>Erase all Data?</h1></figure> 4 | <div class="d-grid gap-2"> 5 | <a class="btn btn-danger" href="/reset" role="button">Yes</a> 6 | <a class="btn btn-primary" href="/settings" role="button">No</a> 7 | </div> 8 | 9 | %pre_foot_template% 10 | <p hidden></p> -------------------------------------------------------------------------------- /src/webpages/HTML_FOOT.html: -------------------------------------------------------------------------------- 1 | 2 | <!-- 3 | https://bootsnipp.com/snippets/bx9Ne 4 | https://write.corbpie.com/sonar-ping-dot-with-css/ 5 | https://www.amitmerchant.com/ping-animation-with-minimal-css/ 6 | --> 7 | 8 | <figure class="text-center"> 9 | Solar2MQTT <a id="software_version">%pre_software_version%</a> By <a href="https://github.com/softwarecrash/Solar2MQTT" 10 | target="_blank">Softwarecrash</a> 11 | <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/" target="_blank"><img 12 | alt="Creative Commons License" style="border-width:0" 13 | src="https://licensebuttons.net/l/by-nc-nd/4.0/80x15.png" /></a> 14 | </figure> 15 | </div> 16 | 17 | </div> 18 | <div id="update_alert" style="display: none;"> 19 | <figure class="text-center"><a id="fwdownload" target="_blank">Download the latest version <b 20 | id="gitversion"></b></a></figure> 21 | </div> 22 | 23 | <script> 24 | $(document).ready(function () { 25 | $.getJSON("https://api.github.com/repos/softwarecrash/Solar2MQTT/releases/latest", function () { 26 | }) 27 | .done(function (data) { 28 | console.log("get data from github done success"); 29 | $('#fwdownload').attr('href', data.html_url); 30 | $('#gitversion').text(data.tag_name.substring(1)); 31 | let x = data.tag_name.substring(1).split('.').map(e => parseInt(e)); 32 | let y = "%pre_swversion%".split('.').map(e => parseInt(e)); 33 | let z = ""; 34 | 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"; } } 35 | if (!z.match(/[l|m]/g)) { 36 | console.log("Git-Version equal, nothing to do."); 37 | } else if (z.split('e').join('')[0] == "m") { 38 | console.log("Git-Version higher, activate notification."); 39 | document.getElementById("update_alert").style.display = ''; 40 | } else { console.log("Git-Version lower, nothing to do."); } 41 | }) 42 | .fail(function () { 43 | console.log("error can not get version"); 44 | }); 45 | }); 46 | </script> 47 | 48 | </body> 49 | 50 | </html> -------------------------------------------------------------------------------- /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 | <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css" rel="stylesheet"> 12 | <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> 13 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" 14 | integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" 15 | crossorigin="anonymous"></script> 16 | 17 | <script src=" https://cdn.jsdelivr.net/npm/chart.js@4.3.0/dist/chart.umd.min.js "></script> 18 | 19 | <title>%pre_device_name% 20 | 21 | 22 | 23 | 27 |
28 | -------------------------------------------------------------------------------- /src/webpages/HTML_MAIN.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 | 3 |
4 |

%pre_device_name%

5 |
6 | 14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 | 23 |
24 |
Solar:
25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 |
Solar2:
34 |
35 | 36 | 37 | 38 |
39 |
40 | 41 |
42 |
Grid:
43 |
44 | 45 | 46 |
47 |
48 | 49 |
50 |
AC out:
51 |
52 | 53 | 54 | 55 | 56 |
57 |
58 | 59 |
60 |
Temperature:
61 |
62 | 63 |
64 |
65 | 66 |
67 |
Battery:
68 |
69 | 70 | 71 | 72 |
73 |
74 | 75 |
76 |
Inverter mode:
77 |
78 | 79 |
80 |
81 | 82 |
83 | Settings 84 |
85 | 86 | 87 | 232 | 233 | %pre_foot_template% 234 | -------------------------------------------------------------------------------- /src/webpages/HTML_REBOOT.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 | 3 |
4 |

Rebooting

5 |

.

6 |
7 |
8 | Main 9 |
10 | 11 | 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 | 11 |
12 |
13 | 14 | 22 | Configure 23 | Reboot 24 | Reset ESP 25 | WebSerial 26 | Back 27 |
28 | 29 | 80 | 81 | %pre_foot_template% 82 | -------------------------------------------------------------------------------- /src/webpages/HTML_SETTINGS_EDIT.html: -------------------------------------------------------------------------------- 1 | %pre_head_template% 2 | 3 |
4 |

Edit Configuration

5 |
6 |
7 |
8 | Device Name 9 | 11 |
12 |
13 | MQTT Server 14 | 16 |
17 |
18 | MQTT Port 19 | 21 |
22 |
23 | MQTT User 24 | 26 |
27 |
28 | MQTT Password 29 | 31 |
32 |
33 | MQTT Topic 34 | 36 |
37 |
38 | MQTT Refresh (sec) 39 | 41 |
42 |
43 | MQTT Data Trigger Path 44 | 46 |
47 |
48 | MQTT Json Style 49 |
50 | 52 |
53 |
54 |
55 | HA Discovery 56 |
57 | 59 |
60 |
61 |
62 | WebUI Dark Mode 63 |
64 | 66 |
67 |
68 | 69 |
70 | HTTP Username 71 | 73 |
74 |
75 | HTTP Password 76 | 78 |
79 | 80 |
81 | 82 | Back 83 |
84 |
85 | 86 | 98 | 99 | %pre_foot_template% 100 | -------------------------------------------------------------------------------- /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 | 8 | def install(package): 9 | if hasattr(pip, 'main'): 10 | pip.main(['install', package]) 11 | else: 12 | pip._internal.main(['install', package]) 13 | 14 | try: 15 | import minify_html 16 | except ImportError: 17 | install('minify_html') 18 | 19 | filePath = 'src/webpages/' 20 | try: 21 | print("==========================") 22 | print("Generating webpage") 23 | print("==========================") 24 | print("Preparing html.h file from source") 25 | print(" -insert header") 26 | cpp_output = "#pragma once\n\n#include // PROGMEM\n\n" 27 | print(" -insert html") 28 | 29 | for x in glob.glob(filePath+"*.html"): 30 | print("prozessing file:" + Path(x).stem) 31 | print(Path(x).stem) 32 | cpp_output += "static const char "+Path(x).stem+"[] PROGMEM = R\"rawliteral(" 33 | f = open(x, "r") 34 | if env.GetProjectOption("build_type") == "debug": 35 | cpp_output += f.read() 36 | else: 37 | #cpp_output += f.read() #disable compression until fixed that the compressor remove %VARIABLE% 38 | cpp_output += minify_html.minify(f.read(), minify_js=True) 39 | 40 | f.close() 41 | cpp_output += ")rawliteral\";\n" 42 | 43 | f = open ("./src/html.h", "w") 44 | f.write(cpp_output) 45 | f.close() 46 | print("==========================\n") 47 | 48 | except SyntaxError as e: 49 | 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 | if env.GetProjectOption("custom_hardwareserial") == "true": 9 | env.Append(CPPDEFINES=[ 10 | ("isUART_HARDWARE", env.StringifyMacro(env.GetBuildType())), 11 | ]) 12 | 13 | env.Replace(PROGNAME="Solar2MQTT_%s_%s" % (str(env["PIOENV"]), env.GetProjectOption("custom_prog_version"))) 14 | --------------------------------------------------------------------------------