diff --git a/SmartEVSE-3/.github/ISSUE_TEMPLATE/bug_report.md b/SmartEVSE-3/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 19620f9..0000000 --- a/SmartEVSE-3/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**Upload your config** -Press the "raw" button in the SmartEVSE webserver and upload the json settings file, so we can look at your configuration. - -**Upload a debug log** -Flash the debug version of the firmware (see https://github.com/dingo35/SmartEVSE-3.5/blob/master/SmartEVSE-3/HowToFlash.txt), telnet to your device, capture the debug log, cut it so that it shows JUST BEFORE and JUST AFTER the problem arises, and upload it here. - -**To Reproduce** -Steps to reproduce the behavior: -1. What Mode are you in? Normal, Smart or Solar? -2. What steps do you take so the problem arises? - -**Expected behavior** -A clear and concise description of what you expected to happen; expected Charging Current? - -**Screenshots** -Only add screenshots if it adds any information, e.g. when you are reporting a problem on the webserver screen. diff --git a/SmartEVSE-3/.github/ISSUE_TEMPLATE/feature_request.md b/SmartEVSE-3/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 1b87256..0000000 --- a/SmartEVSE-3/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -ALSO, if your feature request relates to any reported issues previously, please LINK them here! - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/SmartEVSE-3/.github/workflows/pio-build.yaml b/SmartEVSE-3/.github/workflows/pio-build.yaml deleted file mode 100644 index 198a913..0000000 --- a/SmartEVSE-3/.github/workflows/pio-build.yaml +++ /dev/null @@ -1,130 +0,0 @@ -name: PlatformIO CI -on: - workflow_call: - inputs: - major: - required: true - type: string - minor: - required: true - type: string - patch: - required: true - type: string - prerelease: - required: false - type: string - outputs: - artifact: - description: "The first output string" - value: ${{ jobs.build.outputs.artifact }} - push: - branches: - - master - - release/* -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Get current branch - id: branch - run: echo "branch=${GITHUB_REF##*/}" >> $GITHUB_OUTPUT - - name: Get current datetime - id: datetime - run: echo "datetime=$(date +'%Y%m%dT%H%M%S')" >> $GITHUB_OUTPUT - - name: Get current time - id: time - run: echo "time=$(date +'%H%M%S')" >> $GITHUB_OUTPUT - - name: Get version - id: version - shell: bash - run: | - if [ -z "${{ inputs.major }}" ] - then - echo "version=${{ steps.time.outputs.time }}" >> $GITHUB_OUTPUT - else - if [ -z "${{ inputs.prerelease }}" ] - then - echo "version=${{ inputs.major }}.${{ inputs.minor }}.${{ inputs.patch }}" >> $GITHUB_OUTPUT - else - echo "version=${{ inputs.major }}.${{ inputs.minor }}.${{ inputs.patch }}-${{ inputs.prerelease }}" >> $GITHUB_OUTPUT - fi - fi - - uses: actions/checkout@v3 - - name: Cache pip - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: '${{ runner.os }}-pip-${{ hashFiles(''**/requirements.txt'') }}' - restore-keys: | - ${{ runner.os }}-pip- - - name: Cache PlatformIO - uses: actions/cache@v3 - with: - path: ~/.platformio - key: '${{ runner.os }}-${{ hashFiles(''**/lockfiles'') }}' - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - - name: Install libs - run: | - cd SmartEVSE-3 - pio lib install - - name: Build normal version - env: - super_secret: ${{ secrets.SECRET_RSA_KEY }} - run: | - PLATFORMIO_BUILD_FLAGS='-DVERSION=\"v${{ steps.version.outputs.version }}\" -DDBG=0' pio run -d SmartEVSE-3/ - # Create a temporary file - secret_file=$(mktemp) - # Write the secret to the temporary file, because passing it as command line argument might reveal it for users using ps -a - echo "$super_secret" > "$secret_file" - # Create signature file - openssl dgst -sign "$secret_file" -keyform PEM -sha256 -out firmware.sign -binary ./SmartEVSE-3/.pio/build/release/firmware.bin - # throw it all in one file - cat firmware.sign ./SmartEVSE-3/.pio/build/release/firmware.bin > ./SmartEVSE-3/.pio/build/release/firmware.signed.bin - # Remove the temporary file - rm -f "$secret_file" - - name: Upload firmware.bin - uses: actions/upload-artifact@v3 - with: - name: dists_zip - path: ./SmartEVSE-3/.pio/build/release/*.bin - retention-days: 10 - - name: Upload HowToFlash.txt - uses: actions/upload-artifact@v3 - with: - name: dists_zip - path: ./SmartEVSE-3/HowToFlash.txt - retention-days: 10 - - name: Build debug version - env: - super_secret: ${{ secrets.SECRET_RSA_KEY }} - run: | - PLATFORMIO_BUILD_FLAGS='-DVERSION=\"v${{ steps.version.outputs.version }}\" -DDBG=1' pio run -d SmartEVSE-3/ - # Create a temporary file - secret_file=$(mktemp) - # Write the secret to the temporary file, because passing it as command line argument might reveal it for users using ps -a - echo "$super_secret" > "$secret_file" - # Create signature file - openssl dgst -sign "$secret_file" -keyform PEM -sha256 -out firmware.sign -binary ./SmartEVSE-3/.pio/build/release/firmware.bin - # throw it all in one file - cat firmware.sign ./SmartEVSE-3/.pio/build/release/firmware.bin > ./SmartEVSE-3/.pio/build/release/firmware.signed.bin - # Remove the temporary file - rm -f "$secret_file" - mv ./SmartEVSE-3/.pio/build/release/firmware.bin ./SmartEVSE-3/.pio/build/release/firmware.debug.bin - mv ./SmartEVSE-3/.pio/build/release/firmware.signed.bin ./SmartEVSE-3/.pio/build/release/firmware.debug.signed.bin - - name: Upload Artifact debug firmware - uses: actions/upload-artifact@v3 - with: - name: dists_zip - path: ./SmartEVSE-3/.pio/build/release/*.bin - retention-days: 10 - - outputs: - artifact: dists_zip diff --git a/SmartEVSE-3/.github/workflows/pio-release.yaml b/SmartEVSE-3/.github/workflows/pio-release.yaml deleted file mode 100644 index 680cb4f..0000000 --- a/SmartEVSE-3/.github/workflows/pio-release.yaml +++ /dev/null @@ -1,62 +0,0 @@ -on: - push: - tags: - - 'v*' - -name: Create Release -jobs: - version: - runs-on: ubuntu-latest - steps: - - id: get_version - uses: battila7/get-version-action@v2 - outputs: - major: ${{ steps.get_version.outputs.major }} - minor: ${{ steps.get_version.outputs.minor }} - patch: ${{ steps.get_version.outputs.patch }} - prerelease: ${{ steps.get_version.outputs.prerelease }} - build: - needs: version - uses: ./.github/workflows/pio-build.yaml - with: - major: ${{ needs.version.outputs.major }} - minor: ${{ needs.version.outputs.minor }} - patch: ${{ needs.version.outputs.patch }} - prerelease: ${{ needs.version.outputs.prerelease }} - secrets: inherit - release: - permissions: write-all - needs: build - name: Upload Release Asset - runs-on: ubuntu-latest - steps: - - name: Get current tag - id: tag - run: echo "tag=${GITHUB_REF##*/}" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 - id: download - with: - path: dist - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: true - - name: Zip - run: zip -j ${{ steps.tag.outputs.tag }}-dist.zip ./dist/${{ needs.build.outputs.artifact }}/* - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./${{ steps.tag.outputs.tag }}-dist.zip - asset_name: ${{ steps.tag.outputs.tag }}-dist.zip - asset_content_type: application/zip diff --git a/SmartEVSE-3/.gitignore b/SmartEVSE-3/.gitignore deleted file mode 100644 index add810d..0000000 --- a/SmartEVSE-3/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -.pio -.log -sdkconfig.* -*/.vscode/.browse.c_cpp.db* -*/.vscode/c_cpp_properties.json -*/.vscode/launch.json -*/.vscode/ipch - -.DS_Store -*/.DS_Store -.idea diff --git a/SmartEVSE-3/LICENSE b/SmartEVSE-3/LICENSE deleted file mode 100644 index 99ea426..0000000 --- a/SmartEVSE-3/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 SmartEVSE - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/SmartEVSE-3/README.md b/SmartEVSE-3/README.md deleted file mode 100644 index bae3886..0000000 --- a/SmartEVSE-3/README.md +++ /dev/null @@ -1,65 +0,0 @@ -SmartEVSE v3 -========= - -Smart Electric Vehicle Charge Controller - -![Image of SmartEVSE](/pictures/SmartEVSEv3.png) - -# What is it? - -It's an open source EVSE (Electric Vehicle Supply Equipment). It supports 1-3 phase charging, fixed charging cable or charging socket. Locking actuator support (5 different types). And it can directly drive a mains contactor for supplying power to the EV. It features a display from which all module parameters can be configured.
-Up to 8 modules can be connected together to charge up to eight EV's from one mains connection without overloading it.
-The mains connection can be monitored by the (optional) sensorbox or a modbus kWh meter. This allows smart charging. -Communication between the SmartEVSE(s) / Sensorbox or kWh meters is done over RS485(modbus). - - -# Features - -- Fits into a standard DIN rail enclosure. -- Measures the current consumption of other appliances, and automatically lowers or increases the charging current to the EV. (sensorbox required) -- The load balancing feature let's you connect up to 8 SmartEVSE's to one mains supply. -- Two switched 230VAC outputs, for contactors. -- Powered RS485 communication bus for sensorbox / Modbus kWh Meters. -- Can be used with fixed cable, or socket and charging cable. -- Automatically selects current capacity of the connected cable (13/16/32A) -- Locking actuator support, locks the charging cable in the socket. -- RFID reader support, restrict the use of the charging station to max 20 RFID cards. -- An optional modbus kWh meter will measure power and charged energy, and display this on the LCD. -- Built-in temperature sensor. -- RGB led output for status information while charging. -- All module parameters can be configured using the display and buttons. -- WiFi status page. -- Firmware upgradable through USB-C port or through the built in webserver. -- REST API for communication with external software (e.g. HomeAssistant) -- MQTT API -- Rudimentary support for home batteries -- Supporting delayed charging - -# Connecting the SmartESVE to WiFi - -In order to connect the SmartEVSE to your local WiFi network, a temporarily hotspot is created by the SmartESVE to which you can connect using a phone/tablet. -Here you can then scan for your local WiFi, and enter your Wifi network password. Then the SmartEVSE will use this information to connect to your local Wifi network. - -The steps to connect the SmartEVSE to Wifi are as follows: -- in the SmartEVSE menu, go to the option WIFI, then select SetupWiFi. -- after 10 seconds, a hotspot/access point SmartESVE-xxxx is started. (xxxx is the serial nr of your SmartEVSE) -- Using a phone or tablet, connect to this access point. -- You will be asked to enter a password. This password is visible on the top right corner of the SmartEVSE's display. (PW:xxxxxxxx) -- Once connected you will be able to select your local WiFi network, and enter the password for this network. -- click SAVE, the SmartEVSE will try to connect to your local WiFi network. -- Enter the menu of your SmartEVSE again. The SmartEVSE should now display the IP address on the top row of the display. -- use this IP address in a webbrowser to connect to the webserver of the controller. You can also use http://smartevse-xxxx.local (replace xxxx with the serial nr of your controller) - -# Updating Firmware - -Connect the SmartEVSE controller to your WiFi network (using the menu of the SmartEVSE), and then browse to http://IPaddress/update where IPaddress is the IP which is shown on the display. -You can also use http://smartevse-xxxx.local/update where xxxx is the serial nr of your controller.
-Here you can select the firmware.bin and press update to update the firmware.
-It's also possible to update the spiffs partition from this page. (for v3.0.1 this is not needed)
-After updating the firmware, you can access the status page again using the normal url: http://smartevse-xxxx.local (replace xxxx with the serial nr of your controller)
- -# Documentation - -[Hardware installation](docs/installation.md)
-[Configuration](docs/configuration.md)
-[Operation](docs/operation.md)
diff --git a/SmartEVSE-3/SmartEVSE-3/.gitignore b/SmartEVSE-3/SmartEVSE-3/.gitignore deleted file mode 100644 index 89cc49c..0000000 --- a/SmartEVSE-3/SmartEVSE-3/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.pio -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/ipch diff --git a/SmartEVSE-3/SmartEVSE-3/CMakeLists.txt b/SmartEVSE-3/SmartEVSE-3/CMakeLists.txt deleted file mode 100644 index 73d6dae..0000000 --- a/SmartEVSE-3/SmartEVSE-3/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -cmake_minimum_required(VERSION 3.16.0) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(SmartEVSE32) diff --git a/SmartEVSE-3/SmartEVSE-3/HowToFlash.txt b/SmartEVSE-3/SmartEVSE-3/HowToFlash.txt deleted file mode 100644 index 0587456..0000000 --- a/SmartEVSE-3/SmartEVSE-3/HowToFlash.txt +++ /dev/null @@ -1,5 +0,0 @@ --surf to http://your-smartevse/update --select the firmware.bin from this archive, OR if you want the debug version (via telnet over your wifi), - rename firmware.debug.bin to firmware.bin and select that. YOU CANNOT FLASH A FILE WITH ANOTHER NAME! --if you get FAIL, check your wifi connection and try again; --after OK, wait 10-30 seconds and your new firmware including the webserver should be online! diff --git a/SmartEVSE-3/SmartEVSE-3/data/cert.pem b/SmartEVSE-3/SmartEVSE-3/data/cert.pem deleted file mode 100644 index b6fa7a5..0000000 --- a/SmartEVSE-3/SmartEVSE-3/data/cert.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB4DCCAYWgAwIBAgIUZNoNpf/c2H+t54PnIoe4PgiahV0wCgYIKoZIzj0EAwIw -RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu -dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA1MDQwNjEyMDNaFw0yNDA2MDMw -NjEyMDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD -VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO -PQMBBwNCAAQLQqPNIgyYn4J8SE3LRBVhkDyLAT6cumGL73QGB+OzEsj6cv2B0kGs -jpvUrsclwmCVLVo0T6FxHR4Pw/Fy3qgvo1MwUTAdBgNVHQ4EFgQUpqjPZrPqhA23 -ghR5bdmj0gceVRgwHwYDVR0jBBgwFoAUpqjPZrPqhA23ghR5bdmj0gceVRgwDwYD -VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEA2IitVasCjmRsbz9ZcG4U -jn3tudAVCXs9Fet7AVLNvDkCIQDJGRWuWmctvD/7DBPUra822bIlWhwFKljWpNTW -wZAdFg== ------END CERTIFICATE----- diff --git a/SmartEVSE-3/SmartEVSE-3/data/favicon.ico b/SmartEVSE-3/SmartEVSE-3/data/favicon.ico deleted file mode 100644 index 0c6b947..0000000 Binary files a/SmartEVSE-3/SmartEVSE-3/data/favicon.ico and /dev/null differ diff --git a/SmartEVSE-3/SmartEVSE-3/data/index.html b/SmartEVSE-3/SmartEVSE-3/data/index.html deleted file mode 100644 index 26f051f..0000000 --- a/SmartEVSE-3/SmartEVSE-3/data/index.html +++ /dev/null @@ -1,1035 +0,0 @@ - - - - Smart EVSE V3 - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
- -
-
-
-
-

Smart EVSE V3

-

Version:

-

SmartEVSE-

-
-
-
- -
- - -
-
-
-
-
-
EVSE
-
-
- Mode: -
-
-
-
-
-
-
- Power Sharing: -
-
-
-
-
-
-
- Duty cycle: -
-
-
-
-
-
-
- Connected: -
-
-
-
-
-
-
- State: -
-
-
-
-
-
-
- Error: -
-
-
-
-
-
-
- RFID: -
-
-
-
-
-
-
- Charge: -
-
-
-
-
-
-
- Temp: -
-
-
-
-
-
-
- MQTT: -
-
-
-
-
-
-
- Contactor 2: -
-
-
-
-
-
-
- StartTime: -
-
-
-
-
-
-
-
-
-
- StopTime: -
-
-
-
-
-
-
-
-
-
- Repeat: -
-
-
-
-
-
- -
-
-
-
- - - -
- -
- -
-
-
-
-
-
EV State
-
-
- EVCCID -
-
-
-
-
-
-
- Current SoC -
-
-
-
-
-
-
- Initial SoC -
-
-
-
-
-
-
- Full SoC -
-
-
-
-
-
-
- Est. full at -
-
-
-
-
-
-
- Capacity -
-
-
-
-
-
-
- - - - - -
-
-
-
-
- -
-
-
-
-
-
Current Details
-
-
- Override: -
-
-
-
-
-
-
- Min: -
-
-
-
-
-
-
- Max: -
-
-
-
-
-
-
- - - - - -
-
-
-
-
- - - -
-
-
-
-
-
Phase Details (Original)
-
-
- Total: -
-
-
-
-
-
-
- L1: -
-
-
-
-
-
-
- L2: -
-
-
-
-
-
-
- L3: -
-
-
-
-
-
-
- - - - - -
-
-
-
- -
- - - - - - - - -
- -
- - - - -
-
-
-
-
-
Control
- -
-
- Mode: -
-
-
- - -
- - -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-
-
- Current: -
-
- -
-
-
-
- Actions: -
-
- - - -
-
-
-
- Override PWM: -
-
-
- - - - -
-
-
-
-
- AutoCharge: -
-
-
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
Config
-
-
- MQTT: -
-
- - -
-
-
-
-
-
-
-
-
-
-
-
- - - diff --git a/SmartEVSE-3/SmartEVSE-3/data/key.pem b/SmartEVSE-3/SmartEVSE-3/data/key.pem deleted file mode 100644 index d37ec4c..0000000 --- a/SmartEVSE-3/SmartEVSE-3/data/key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEICtA0BJEavg6pVMl6lcPOA5I7qqm4W/CoFbJSP/osM3poAoGCCqGSM49 -AwEHoUQDQgAEC0KjzSIMmJ+CfEhNy0QVYZA8iwE+nLphi+90BgfjsxLI+nL9gdJB -rI6b1K7HJcJglS1aNE+hcR0eD8Pxct6oLw== ------END EC PRIVATE KEY----- diff --git a/SmartEVSE-3/SmartEVSE-3/data/rsa_key.pub b/SmartEVSE-3/SmartEVSE-3/data/rsa_key.pub deleted file mode 100644 index dad4fb1..0000000 --- a/SmartEVSE-3/SmartEVSE-3/data/rsa_key.pub +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtjEWhkfKPAUrtX1GueYq -JmDp4qSHBG6ndwikAHvteKgWQABDpwaemZdxh7xVCuEdjEkaecinNOZ0LpSCF3QO -qflnXkvpYVxjdTpKBxo7vP5QEa3I6keJfwpoMzGuT8XOK7id6FHJhtYEXcaufALi -mR/NXT11ikHLtluATymPdoSscMiwry0qX03yIek91lDypBNl5uvD2jxn9smlijfq -9j0lwtpLBWJPU8vsU0uzuj7Qq5pWZFKsjiNWfbvNJXuLsupOazf5sh0yeQzL1CBL -RUsBlYVoChTmSOyvi6kO5vW/6GLOafJF0FTdOQ+Gf3/IB6M1ErSxlqxQhHq0pb7Y -INl7+aFCmlRjyLlMjb8xdtuedlZKv8mLd37AyPAihrq9gV74xq6c7w2y+h9213p8 -jgcmo/HvOlGaXEIOVCUu102teOckXjTni2yhEtFISCaWuaIdb5P9e0uBIy1e+Bi6 -/7A3aut5MQP07DO99BFETXyFF6EixhTF8fpwVZ5vXeIDvKKEDUGuzAziUEGIZpic -UQ2fmTzIaTBbNlCMeTQFIpZCosM947aGKNBp672wdf996SRwg9E2VWzW2Z1UuwWV -BPVQkHb1Hsy7C9fg5JcLKB9zEfyUH0Tm9Iur1vsuA5++JNl2+T55192wqyF0R9sb -YtSTUJNSiSwqWt1m0FLOJD0CAwEAAQ== ------END PUBLIC KEY----- diff --git a/SmartEVSE-3/SmartEVSE-3/data/styling.css b/SmartEVSE-3/SmartEVSE-3/data/styling.css deleted file mode 100644 index 903c9d1..0000000 --- a/SmartEVSE-3/SmartEVSE-3/data/styling.css +++ /dev/null @@ -1,10 +0,0 @@ -/*! - * Start Bootstrap - SB Admin 2 v4.1.3 (https://startbootstrap.com/theme/sb-admin-2) - * Copyright 2013-2021 Start Bootstrap - * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin-2/blob/master/LICENSE) - *//*! - * Bootstrap v4.6.0 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--blue:#4e73df;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#e74a3b;--orange:#fd7e14;--yellow:#f6c23e;--green:#1cc88a;--teal:#20c9a6;--cyan:#36b9cc;--white:#fff;--gray:#858796;--gray-dark:#5a5c69;--primary:#4e73df;--secondary:#858796;--success:#1cc88a;--info:#36b9cc;--warning:#f6c23e;--danger:#e74a3b;--light:#f8f9fc;--dark:#5a5c69;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:"Nunito",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:Nunito,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#858796;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#4e73df;text-decoration:none;background-color:transparent}a:hover{color:#224abe;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#858796;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:400;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#858796}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dddfeb;border-radius:.35rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#858796}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#3a3b45;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#3a3b45}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:.75rem;padding-left:.75rem;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:flex;flex-wrap:wrap;margin-right:-.75rem;margin-left:-.75rem}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:.75rem;padding-left:.75rem}.col{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.33333%;max-width:8.33333%}.col-2{flex:0 0 16.66667%;max-width:16.66667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333%;max-width:33.33333%}.col-5{flex:0 0 41.66667%;max-width:41.66667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333%;max-width:58.33333%}.col-8{flex:0 0 66.66667%;max-width:66.66667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333%;max-width:83.33333%}.col-11{flex:0 0 91.66667%;max-width:91.66667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-5{margin-left:41.66667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333%}.offset-8{margin-left:66.66667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333%}.offset-11{margin-left:91.66667%}@media (min-width:576px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.66667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333%}.offset-sm-5{margin-left:41.66667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333%}.offset-sm-8{margin-left:66.66667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333%}.offset-sm-11{margin-left:91.66667%}}@media (min-width:768px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.66667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333%}.offset-md-5{margin-left:41.66667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333%}.offset-md-8{margin-left:66.66667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333%}.offset-md-11{margin-left:91.66667%}}@media (min-width:992px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.66667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333%}.offset-lg-5{margin-left:41.66667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333%}.offset-lg-8{margin-left:66.66667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333%}.offset-lg-11{margin-left:91.66667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.66667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333%}.offset-xl-5{margin-left:41.66667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333%}.offset-xl-8{margin-left:66.66667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333%}.offset-xl-11{margin-left:91.66667%}}.table{width:100%;margin-bottom:1rem;color:#858796}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #e3e6f0}.table thead th{vertical-align:bottom;border-bottom:2px solid #e3e6f0}.table tbody+tbody{border-top:2px solid #e3e6f0}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #e3e6f0}.table-bordered td,.table-bordered th{border:1px solid #e3e6f0}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#858796;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#cdd8f6}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#a3b6ee}.table-hover .table-primary:hover{background-color:#b7c7f2}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#b7c7f2}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#dddde2}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#c0c1c8}.table-hover .table-secondary:hover{background-color:#cfcfd6}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#cfcfd6}.table-success,.table-success>td,.table-success>th{background-color:#bff0de}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#89e2c2}.table-hover .table-success:hover{background-color:#aaebd3}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#aaebd3}.table-info,.table-info>td,.table-info>th{background-color:#c7ebf1}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#96dbe4}.table-hover .table-info:hover{background-color:#b3e4ec}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#b3e4ec}.table-warning,.table-warning>td,.table-warning>th{background-color:#fceec9}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#fadf9b}.table-hover .table-warning:hover{background-color:#fbe6b1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#fbe6b1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f8ccc8}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#f3a199}.table-hover .table-danger:hover{background-color:#f5b7b1}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f5b7b1}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfd}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#d1d1d5}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#a9aab1}.table-hover .table-dark:hover{background-color:#c4c4c9}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#c4c4c9}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#5a5c69;border-color:#6c6e7e}.table .thead-light th{color:#6e707e;background-color:#eaecf4;border-color:#e3e6f0}.table-dark{color:#fff;background-color:#5a5c69}.table-dark td,.table-dark th,.table-dark thead th{border-color:#6c6e7e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#6e707e;background-color:#fff;background-clip:padding-box;border:1px solid #d1d3e2;border-radius:.35rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #6e707e}.form-control:focus{color:#6e707e;background-color:#fff;border-color:#bac8f3;outline:0;box-shadow:0 0 0 .2rem rgba(78,115,223,.25)}.form-control::-webkit-input-placeholder{color:#858796;opacity:1}.form-control::-moz-placeholder{color:#858796;opacity:1}.form-control:-ms-input-placeholder{color:#858796;opacity:1}.form-control::-ms-input-placeholder{color:#858796;opacity:1}.form-control::placeholder{color:#858796;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#eaecf4;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#6e707e;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#858796;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#858796}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#1cc88a}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(28,200,138,.9);border-radius:.35rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#1cc88a;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%231cc88a' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#1cc88a;box-shadow:0 0 0 .2rem rgba(28,200,138,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#1cc88a;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%235a5c69' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%231cc88a' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#1cc88a;box-shadow:0 0 0 .2rem rgba(28,200,138,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#1cc88a}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#1cc88a}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#1cc88a}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34e3a4;background-color:#34e3a4}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(28,200,138,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#1cc88a}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#1cc88a}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#1cc88a;box-shadow:0 0 0 .2rem rgba(28,200,138,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#e74a3b}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(231,74,59,.9);border-radius:.35rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#e74a3b;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23e74a3b' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e74a3b' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#e74a3b;box-shadow:0 0 0 .2rem rgba(231,74,59,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#e74a3b;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%235a5c69' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23e74a3b' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e74a3b' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#e74a3b;box-shadow:0 0 0 .2rem rgba(231,74,59,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#e74a3b}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#e74a3b}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#e74a3b}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#ed7468;background-color:#ed7468}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(231,74,59,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#e74a3b}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#e74a3b}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#e74a3b;box-shadow:0 0 0 .2rem rgba(231,74,59,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:flex;align-items:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:flex;flex:0 0 auto;flex-flow:row wrap;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#858796;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.35rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#858796;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(78,115,223,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#4e73df;border-color:#4e73df}.btn-primary:hover{color:#fff;background-color:#2e59d9;border-color:#2653d4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#2e59d9;border-color:#2653d4;box-shadow:0 0 0 .2rem rgba(105,136,228,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#4e73df;border-color:#4e73df}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#2653d4;border-color:#244ec9}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(105,136,228,.5)}.btn-secondary{color:#fff;background-color:#858796;border-color:#858796}.btn-secondary:hover{color:#fff;background-color:#717384;border-color:#6b6d7d}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#717384;border-color:#6b6d7d;box-shadow:0 0 0 .2rem rgba(151,153,166,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#858796;border-color:#858796}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#6b6d7d;border-color:#656776}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(151,153,166,.5)}.btn-success{color:#fff;background-color:#1cc88a;border-color:#1cc88a}.btn-success:hover{color:#fff;background-color:#17a673;border-color:#169b6b}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#17a673;border-color:#169b6b;box-shadow:0 0 0 .2rem rgba(62,208,156,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#1cc88a;border-color:#1cc88a}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#169b6b;border-color:#149063}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(62,208,156,.5)}.btn-info{color:#fff;background-color:#36b9cc;border-color:#36b9cc}.btn-info:hover{color:#fff;background-color:#2c9faf;border-color:#2a96a5}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#2c9faf;border-color:#2a96a5;box-shadow:0 0 0 .2rem rgba(84,196,212,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#36b9cc;border-color:#36b9cc}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#2a96a5;border-color:#278c9b}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(84,196,212,.5)}.btn-warning{color:#fff;background-color:#f6c23e;border-color:#f6c23e}.btn-warning:hover{color:#fff;background-color:#f4b619;border-color:#f4b30d}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#f4b619;border-color:#f4b30d;box-shadow:0 0 0 .2rem rgba(247,203,91,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#f6c23e;border-color:#f6c23e}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#f4b30d;border-color:#e9aa0b}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(247,203,91,.5)}.btn-danger{color:#fff;background-color:#e74a3b;border-color:#e74a3b}.btn-danger:hover{color:#fff;background-color:#e02d1b;border-color:#d52a1a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#e02d1b;border-color:#d52a1a;box-shadow:0 0 0 .2rem rgba(235,101,88,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#e74a3b;border-color:#e74a3b}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#d52a1a;border-color:#ca2819}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(235,101,88,.5)}.btn-light{color:#3a3b45;background-color:#f8f9fc;border-color:#f8f9fc}.btn-light:hover{color:#3a3b45;background-color:#dde2f1;border-color:#d4daed}.btn-light.focus,.btn-light:focus{color:#3a3b45;background-color:#dde2f1;border-color:#d4daed;box-shadow:0 0 0 .2rem rgba(220,221,225,.5)}.btn-light.disabled,.btn-light:disabled{color:#3a3b45;background-color:#f8f9fc;border-color:#f8f9fc}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#3a3b45;background-color:#d4daed;border-color:#cbd3e9}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,221,225,.5)}.btn-dark{color:#fff;background-color:#5a5c69;border-color:#5a5c69}.btn-dark:hover{color:#fff;background-color:#484a54;border-color:#42444e}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#484a54;border-color:#42444e;box-shadow:0 0 0 .2rem rgba(115,116,128,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#5a5c69;border-color:#5a5c69}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#42444e;border-color:#3d3e47}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(115,116,128,.5)}.btn-outline-primary{color:#4e73df;border-color:#4e73df}.btn-outline-primary:hover{color:#fff;background-color:#4e73df;border-color:#4e73df}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(78,115,223,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#4e73df;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#4e73df;border-color:#4e73df}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(78,115,223,.5)}.btn-outline-secondary{color:#858796;border-color:#858796}.btn-outline-secondary:hover{color:#fff;background-color:#858796;border-color:#858796}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(133,135,150,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#858796;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#858796;border-color:#858796}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(133,135,150,.5)}.btn-outline-success{color:#1cc88a;border-color:#1cc88a}.btn-outline-success:hover{color:#fff;background-color:#1cc88a;border-color:#1cc88a}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(28,200,138,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#1cc88a;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#1cc88a;border-color:#1cc88a}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(28,200,138,.5)}.btn-outline-info{color:#36b9cc;border-color:#36b9cc}.btn-outline-info:hover{color:#fff;background-color:#36b9cc;border-color:#36b9cc}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(54,185,204,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#36b9cc;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#36b9cc;border-color:#36b9cc}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(54,185,204,.5)}.btn-outline-warning{color:#f6c23e;border-color:#f6c23e}.btn-outline-warning:hover{color:#fff;background-color:#f6c23e;border-color:#f6c23e}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(246,194,62,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#f6c23e;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#f6c23e;border-color:#f6c23e}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(246,194,62,.5)}.btn-outline-danger{color:#e74a3b;border-color:#e74a3b}.btn-outline-danger:hover{color:#fff;background-color:#e74a3b;border-color:#e74a3b}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(231,74,59,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#e74a3b;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#e74a3b;border-color:#e74a3b}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(231,74,59,.5)}.btn-outline-light{color:#f8f9fc;border-color:#f8f9fc}.btn-outline-light:hover{color:#3a3b45;background-color:#f8f9fc;border-color:#f8f9fc}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,252,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fc;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#3a3b45;background-color:#f8f9fc;border-color:#f8f9fc}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,252,.5)}.btn-outline-dark{color:#5a5c69;border-color:#5a5c69}.btn-outline-dark:hover{color:#fff;background-color:#5a5c69;border-color:#5a5c69}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(90,92,105,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#5a5c69;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#5a5c69;border-color:#5a5c69}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(90,92,105,.5)}.btn-link{font-weight:400;color:#4e73df;text-decoration:none}.btn-link:hover{color:#224abe;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#858796;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .15s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:.85rem;color:#858796;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #e3e6f0;border-radius:.35rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #eaecf4}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#3a3b45;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#2e2f37;text-decoration:none;background-color:#eaecf4}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#4e73df}.dropdown-item.disabled,.dropdown-item:disabled{color:#b7b9cc;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#858796;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#3a3b45}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:first-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label::after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label::after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#6e707e;text-align:center;white-space:nowrap;background-color:#eaecf4;border:1px solid #d1d3e2;border-radius:.35rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#4e73df;background-color:#4e73df}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(78,115,223,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#bac8f3}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#e5ebfa;border-color:#e5ebfa}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#858796}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#eaecf4}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#b7b9cc solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label::before{border-radius:.35rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#4e73df;background-color:#4e73df}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(78,115,223,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(78,115,223,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(78,115,223,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#b7b9cc;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(78,115,223,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#6e707e;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%235a5c69' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #d1d3e2;border-radius:.35rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#bac8f3;outline:0;box-shadow:0 0 0 .2rem rgba(78,115,223,.25)}.custom-select:focus::-ms-value{color:#6e707e;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#858796;background-color:#eaecf4}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #6e707e}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#bac8f3;box-shadow:0 0 0 .2rem rgba(78,115,223,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#eaecf4}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;overflow:hidden;font-weight:400;line-height:1.5;color:#6e707e;background-color:#fff;border:1px solid #d1d3e2;border-radius:.35rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#6e707e;content:"Browse";background-color:#eaecf4;border-left:inherit;border-radius:0 .35rem .35rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(78,115,223,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(78,115,223,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(78,115,223,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#4e73df;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#e5ebfa}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dddfeb;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#4e73df;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#e5ebfa}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dddfeb;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#4e73df;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#e5ebfa}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dddfeb;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dddfeb;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#b7b9cc}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#b7b9cc}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#b7b9cc}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#858796;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dddfeb}.nav-tabs .nav-link{margin-bottom:-1px;border:1px solid transparent;border-top-left-radius:.35rem;border-top-right-radius:.35rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#eaecf4 #eaecf4 #dddfeb}.nav-tabs .nav-link.disabled{color:#858796;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#6e707e;background-color:#fff;border-color:#dddfeb #dddfeb #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.35rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#4e73df}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.35rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid #e3e6f0;border-radius:.35rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.35rem - 1px);border-top-right-radius:calc(.35rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.35rem - 1px);border-bottom-left-radius:calc(.35rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:#f8f9fc;border-bottom:1px solid #e3e6f0}.card-header:first-child{border-radius:calc(.35rem - 1px) calc(.35rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:#f8f9fc;border-top:1px solid #e3e6f0}.card-footer:last-child{border-radius:0 0 calc(.35rem - 1px) calc(.35rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.35rem - 1px)}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.35rem - 1px);border-top-right-radius:calc(.35rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.35rem - 1px);border-bottom-left-radius:calc(.35rem - 1px)}.card-deck .card{margin-bottom:.75rem}@media (min-width:576px){.card-deck{display:flex;flex-flow:row wrap;margin-right:-.75rem;margin-left:-.75rem}.card-deck .card{flex:1 0 0%;margin-right:.75rem;margin-bottom:0;margin-left:.75rem}}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-moz-column-count:3;column-count:3;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#eaecf4;border-radius:.35rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#858796;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#858796}.pagination{display:flex;padding-left:0;list-style:none;border-radius:.35rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#4e73df;background-color:#fff;border:1px solid #dddfeb}.page-link:hover{z-index:2;color:#224abe;text-decoration:none;background-color:#eaecf4;border-color:#dddfeb}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(78,115,223,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.35rem;border-bottom-left-radius:.35rem}.page-item:last-child .page-link{border-top-right-radius:.35rem;border-bottom-right-radius:.35rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#4e73df;border-color:#4e73df}.page-item.disabled .page-link{color:#858796;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dddfeb}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.35rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#4e73df}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#2653d4}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(78,115,223,.5)}.badge-secondary{color:#fff;background-color:#858796}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#6b6d7d}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(133,135,150,.5)}.badge-success{color:#fff;background-color:#1cc88a}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#169b6b}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(28,200,138,.5)}.badge-info{color:#fff;background-color:#36b9cc}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#2a96a5}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(54,185,204,.5)}.badge-warning{color:#fff;background-color:#f6c23e}a.badge-warning:focus,a.badge-warning:hover{color:#fff;background-color:#f4b30d}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(246,194,62,.5)}.badge-danger{color:#fff;background-color:#e74a3b}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#d52a1a}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(231,74,59,.5)}.badge-light{color:#3a3b45;background-color:#f8f9fc}a.badge-light:focus,a.badge-light:hover{color:#3a3b45;background-color:#d4daed}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,252,.5)}.badge-dark{color:#fff;background-color:#5a5c69}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#42444e}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(90,92,105,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#eaecf4;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.35rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#293c74;background-color:#dce3f9;border-color:#cdd8f6}.alert-primary hr{border-top-color:#b7c7f2}.alert-primary .alert-link{color:#1c294e}.alert-secondary{color:#45464e;background-color:#e7e7ea;border-color:#dddde2}.alert-secondary hr{border-top-color:#cfcfd6}.alert-secondary .alert-link{color:#2d2e33}.alert-success{color:#0f6848;background-color:#d2f4e8;border-color:#bff0de}.alert-success hr{border-top-color:#aaebd3}.alert-success .alert-link{color:#093b29}.alert-info{color:#1c606a;background-color:#d7f1f5;border-color:#c7ebf1}.alert-info hr{border-top-color:#b3e4ec}.alert-info .alert-link{color:#113b42}.alert-warning{color:#806520;background-color:#fdf3d8;border-color:#fceec9}.alert-warning hr{border-top-color:#fbe6b1}.alert-warning .alert-link{color:#574516}.alert-danger{color:#78261f;background-color:#fadbd8;border-color:#f8ccc8}.alert-danger hr{border-top-color:#f5b7b1}.alert-danger .alert-link{color:#4f1915}.alert-light{color:#818183;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686869}.alert-dark{color:#2f3037;background-color:#dedee1;border-color:#d1d1d5}.alert-dark hr{border-top-color:#c4c4c9}.alert-dark .alert-link{color:#18181c}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#eaecf4;border-radius:.35rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#4e73df;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.35rem}.list-group-item-action{width:100%;color:#6e707e;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#6e707e;text-decoration:none;background-color:#f8f9fc}.list-group-item-action:active{color:#858796;background-color:#eaecf4}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#858796;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#4e73df;border-color:#4e73df}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.35rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.35rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.35rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.35rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.35rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.35rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.35rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.35rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.35rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.35rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#293c74;background-color:#cdd8f6}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#293c74;background-color:#b7c7f2}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#293c74;border-color:#293c74}.list-group-item-secondary{color:#45464e;background-color:#dddde2}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#45464e;background-color:#cfcfd6}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#45464e;border-color:#45464e}.list-group-item-success{color:#0f6848;background-color:#bff0de}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f6848;background-color:#aaebd3}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f6848;border-color:#0f6848}.list-group-item-info{color:#1c606a;background-color:#c7ebf1}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#1c606a;background-color:#b3e4ec}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#1c606a;border-color:#1c606a}.list-group-item-warning{color:#806520;background-color:#fceec9}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#806520;background-color:#fbe6b1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#806520;border-color:#806520}.list-group-item-danger{color:#78261f;background-color:#f8ccc8}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#78261f;background-color:#f5b7b1}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#78261f;border-color:#78261f}.list-group-item-light{color:#818183;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818183;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818183;border-color:#818183}.list-group-item-dark{color:#2f3037;background-color:#d1d1d5}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#2f3037;background-color:#c4c4c9}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#2f3037;border-color:#2f3037}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#858796;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #e3e6f0;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #e3e6f0;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:Nunito,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.35rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:Nunito,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#858796}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#4e73df!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#2653d4!important}.bg-secondary{background-color:#858796!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#6b6d7d!important}.bg-success{background-color:#1cc88a!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#169b6b!important}.bg-info{background-color:#36b9cc!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#2a96a5!important}.bg-warning{background-color:#f6c23e!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#f4b30d!important}.bg-danger{background-color:#e74a3b!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#d52a1a!important}.bg-light{background-color:#f8f9fc!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#d4daed!important}.bg-dark{background-color:#5a5c69!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#42444e!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #e3e6f0!important}.border-top{border-top:1px solid #e3e6f0!important}.border-right{border-right:1px solid #e3e6f0!important}.border-bottom{border-bottom:1px solid #e3e6f0!important}.border-left{border-left:1px solid #e3e6f0!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#4e73df!important}.border-secondary{border-color:#858796!important}.border-success{border-color:#1cc88a!important}.border-info{border-color:#36b9cc!important}.border-warning{border-color:#f6c23e!important}.border-danger{border-color:#e74a3b!important}.border-light{border-color:#f8f9fc!important}.border-dark{border-color:#5a5c69!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.35rem!important}.rounded-top{border-top-left-radius:.35rem!important;border-top-right-radius:.35rem!important}.rounded-right{border-top-right-radius:.35rem!important;border-bottom-right-radius:.35rem!important}.rounded-bottom{border-bottom-right-radius:.35rem!important;border-bottom-left-radius:.35rem!important}.rounded-left{border-top-left-radius:.35rem!important;border-bottom-left-radius:.35rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.85714%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:576px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;-ms-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem 0 rgba(58,59,69,.2)!important}.shadow{box-shadow:0 .15rem 1.75rem 0 rgba(58,59,69,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.dropdown .dropdown-menu .dropdown-header,.sidebar .sidebar-heading,.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#4e73df!important}a.text-primary:focus,a.text-primary:hover{color:#224abe!important}.text-secondary{color:#858796!important}a.text-secondary:focus,a.text-secondary:hover{color:#60616f!important}.text-success{color:#1cc88a!important}a.text-success:focus,a.text-success:hover{color:#13855c!important}.text-info{color:#36b9cc!important}a.text-info:focus,a.text-info:hover{color:#258391!important}.text-warning{color:#f6c23e!important}a.text-warning:focus,a.text-warning:hover{color:#dda20a!important}.text-danger{color:#e74a3b!important}a.text-danger:focus,a.text-danger:hover{color:#be2617!important}.text-light{color:#f8f9fc!important}a.text-light:focus,a.text-light:hover{color:#c2cbe5!important}.text-dark{color:#5a5c69!important}a.text-dark:focus,a.text-dark:hover{color:#373840!important}.text-body{color:#858796!important}.text-muted{color:#858796!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #b7b9cc;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dddfeb!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#e3e6f0}.table .thead-dark th{color:inherit;border-color:#e3e6f0}}html{position:relative;min-height:100%}body{height:100%}a:focus{outline:0}#wrapper{display:flex}#wrapper #content-wrapper{background-color:#f8f9fc;width:100%;overflow-x:hidden}#wrapper #content-wrapper #content{flex:1 0 auto}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{padding-left:1.5rem;padding-right:1.5rem}.scroll-to-top{position:fixed;right:1rem;bottom:1rem;display:none;width:2.75rem;height:2.75rem;text-align:center;color:#fff;background:rgba(90,92,105,.5);line-height:46px}.scroll-to-top:focus,.scroll-to-top:hover{color:#fff}.scroll-to-top:hover{background:#5a5c69}.scroll-to-top i{font-weight:800}@-webkit-keyframes growIn{0%{transform:scale(.9);opacity:0}100%{transform:scale(1);opacity:1}}@keyframes growIn{0%{transform:scale(.9);opacity:0}100%{transform:scale(1);opacity:1}}.animated--grow-in,.sidebar .nav-item .collapse{-webkit-animation-name:growIn;animation-name:growIn;-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-timing-function:transform cubic-bezier(.18,1.25,.4,1),opacity cubic-bezier(0,1,.4,1);animation-timing-function:transform cubic-bezier(.18,1.25,.4,1),opacity cubic-bezier(0,1,.4,1)}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.animated--fade-in{-webkit-animation-name:fadeIn;animation-name:fadeIn;-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-timing-function:opacity cubic-bezier(0,1,.4,1);animation-timing-function:opacity cubic-bezier(0,1,.4,1)}.bg-gradient-primary{background-color:#4e73df;background-image:linear-gradient(180deg,#4e73df 10%,#224abe 100%);background-size:cover}.bg-gradient-secondary{background-color:#858796;background-image:linear-gradient(180deg,#858796 10%,#60616f 100%);background-size:cover}.bg-gradient-success{background-color:#1cc88a;background-image:linear-gradient(180deg,#1cc88a 10%,#13855c 100%);background-size:cover}.bg-gradient-info{background-color:#36b9cc;background-image:linear-gradient(180deg,#36b9cc 10%,#258391 100%);background-size:cover}.bg-gradient-warning{background-color:#f6c23e;background-image:linear-gradient(180deg,#f6c23e 10%,#dda20a 100%);background-size:cover}.bg-gradient-danger{background-color:#e74a3b;background-image:linear-gradient(180deg,#e74a3b 10%,#be2617 100%);background-size:cover}.bg-gradient-light{background-color:#f8f9fc;background-image:linear-gradient(180deg,#f8f9fc 10%,#c2cbe5 100%);background-size:cover}.bg-gradient-dark{background-color:#5a5c69;background-image:linear-gradient(180deg,#5a5c69 10%,#373840 100%);background-size:cover}.bg-gray-100{background-color:#f8f9fc!important}.bg-gray-200{background-color:#eaecf4!important}.bg-gray-300{background-color:#dddfeb!important}.bg-gray-400{background-color:#d1d3e2!important}.bg-gray-500{background-color:#b7b9cc!important}.bg-gray-600{background-color:#858796!important}.bg-gray-700{background-color:#6e707e!important}.bg-gray-800{background-color:#5a5c69!important}.bg-gray-900{background-color:#3a3b45!important}.o-hidden{overflow:hidden!important}.text-xs{font-size:.7rem}.text-lg{font-size:1.2rem}.text-gray-100{color:#f8f9fc!important}.text-gray-200{color:#eaecf4!important}.text-gray-300{color:#dddfeb!important}.text-gray-400{color:#d1d3e2!important}.text-gray-500{color:#b7b9cc!important}.text-gray-600{color:#858796!important}.text-gray-700{color:#6e707e!important}.text-gray-800{color:#5a5c69!important}.text-gray-900{color:#3a3b45!important}.icon-circle{height:2.5rem;width:2.5rem;border-radius:100%;display:flex;align-items:center;justify-content:center}.border-left-primary{border-left:.25rem solid #4e73df!important}.border-bottom-primary{border-bottom:.25rem solid #4e73df!important}.border-left-secondary{border-left:.25rem solid #858796!important}.border-bottom-secondary{border-bottom:.25rem solid #858796!important}.border-left-success{border-left:.25rem solid #1cc88a!important}.border-bottom-success{border-bottom:.25rem solid #1cc88a!important}.border-left-info{border-left:.25rem solid #36b9cc!important}.border-bottom-info{border-bottom:.25rem solid #36b9cc!important}.border-left-warning{border-left:.25rem solid #f6c23e!important}.border-bottom-warning{border-bottom:.25rem solid #f6c23e!important}.border-left-danger{border-left:.25rem solid #e74a3b!important}.border-bottom-danger{border-bottom:.25rem solid #e74a3b!important}.border-left-light{border-left:.25rem solid #f8f9fc!important}.border-bottom-light{border-bottom:.25rem solid #f8f9fc!important}.border-left-dark{border-left:.25rem solid #5a5c69!important}.border-bottom-dark{border-bottom:.25rem solid #5a5c69!important}.progress-sm{height:.5rem}.rotate-15{transform:rotate(15deg)}.rotate-n-15{transform:rotate(-15deg)}.dropdown .dropdown-menu{font-size:.85rem}.dropdown .dropdown-menu .dropdown-header{font-weight:800;font-size:.65rem;color:#b7b9cc}.dropdown.no-arrow .dropdown-toggle::after{display:none}.sidebar .nav-item.dropdown .dropdown-toggle::after,.topbar .nav-item.dropdown .dropdown-toggle::after{width:1rem;text-align:center;float:right;vertical-align:0;border:0;font-weight:900;content:'\f105';font-family:'Font Awesome 5 Free'}.sidebar .nav-item.dropdown.show .dropdown-toggle::after,.topbar .nav-item.dropdown.show .dropdown-toggle::after{content:'\f107'}.sidebar .nav-item .nav-link,.topbar .nav-item .nav-link{position:relative}.sidebar .nav-item .nav-link .badge-counter,.topbar .nav-item .nav-link .badge-counter{position:absolute;transform:scale(.7);transform-origin:top right;right:.25rem;margin-top:-.25rem}.sidebar .nav-item .nav-link .img-profile,.topbar .nav-item .nav-link .img-profile{height:2rem;width:2rem}.topbar{height:4.375rem}.topbar #sidebarToggleTop{height:2.5rem;width:2.5rem}.topbar #sidebarToggleTop:hover{background-color:#eaecf4}.topbar #sidebarToggleTop:active{background-color:#dddfeb}.topbar .navbar-search{width:25rem}.topbar .navbar-search input{font-size:.85rem;height:auto}.topbar .topbar-divider{width:0;border-right:1px solid #e3e6f0;height:calc(4.375rem - 2rem);margin:auto 1rem}.topbar .nav-item .nav-link{height:4.375rem;display:flex;align-items:center;padding:0 .75rem}.topbar .nav-item .nav-link:focus{outline:0}.topbar .nav-item:focus{outline:0}.topbar .dropdown{position:static}.topbar .dropdown .dropdown-menu{width:calc(100% - 1.5rem);right:.75rem}.topbar .dropdown-list{padding:0;border:none;overflow:hidden}.topbar .dropdown-list .dropdown-header{background-color:#4e73df;border:1px solid #4e73df;padding-top:.75rem;padding-bottom:.75rem;color:#fff}.topbar .dropdown-list .dropdown-item{white-space:normal;padding-top:.5rem;padding-bottom:.5rem;border-left:1px solid #e3e6f0;border-right:1px solid #e3e6f0;border-bottom:1px solid #e3e6f0;line-height:1.3rem}.topbar .dropdown-list .dropdown-item .dropdown-list-image{position:relative;height:2.5rem;width:2.5rem}.topbar .dropdown-list .dropdown-item .dropdown-list-image img{height:2.5rem;width:2.5rem}.topbar .dropdown-list .dropdown-item .dropdown-list-image .status-indicator{background-color:#eaecf4;height:.75rem;width:.75rem;border-radius:100%;position:absolute;bottom:0;right:0;border:.125rem solid #fff}.topbar .dropdown-list .dropdown-item .text-truncate{max-width:10rem}.topbar .dropdown-list .dropdown-item:active{background-color:#eaecf4;color:#3a3b45}@media (min-width:576px){.topbar .dropdown{position:relative}.topbar .dropdown .dropdown-menu{width:auto;right:0}.topbar .dropdown-list{width:20rem!important}.topbar .dropdown-list .dropdown-item .text-truncate{max-width:13.375rem}}.topbar.navbar-dark .navbar-nav .nav-item .nav-link{color:rgba(255,255,255,.8)}.topbar.navbar-dark .navbar-nav .nav-item .nav-link:hover{color:#fff}.topbar.navbar-dark .navbar-nav .nav-item .nav-link:active{color:#fff}.topbar.navbar-light .navbar-nav .nav-item .nav-link{color:#d1d3e2}.topbar.navbar-light .navbar-nav .nav-item .nav-link:hover{color:#b7b9cc}.topbar.navbar-light .navbar-nav .nav-item .nav-link:active{color:#858796}.sidebar{width:6.5rem;min-height:100vh}.sidebar .nav-item{position:relative}.sidebar .nav-item:last-child{margin-bottom:1rem}.sidebar .nav-item .nav-link{text-align:center;padding:.75rem 1rem;width:6.5rem}.sidebar .nav-item .nav-link span{font-size:.65rem;display:block}.sidebar .nav-item.active .nav-link{font-weight:700}.sidebar .nav-item .collapse{position:absolute;left:calc(6.5rem + 1.5rem / 2);z-index:1;top:2px}.sidebar .nav-item .collapse .collapse-inner{border-radius:.35rem;box-shadow:0 .15rem 1.75rem 0 rgba(58,59,69,.15)}.sidebar .nav-item .collapsing{display:none;transition:none}.sidebar .nav-item .collapse .collapse-inner,.sidebar .nav-item .collapsing .collapse-inner{padding:.5rem 0;min-width:10rem;font-size:.85rem;margin:0 0 1rem 0}.sidebar .nav-item .collapse .collapse-inner .collapse-header,.sidebar .nav-item .collapsing .collapse-inner .collapse-header{margin:0;white-space:nowrap;padding:.5rem 1.5rem;text-transform:uppercase;font-weight:800;font-size:.65rem;color:#b7b9cc}.sidebar .nav-item .collapse .collapse-inner .collapse-item,.sidebar .nav-item .collapsing .collapse-inner .collapse-item{padding:.5rem 1rem;margin:0 .5rem;display:block;color:#3a3b45;text-decoration:none;border-radius:.35rem;white-space:nowrap}.sidebar .nav-item .collapse .collapse-inner .collapse-item:hover,.sidebar .nav-item .collapsing .collapse-inner .collapse-item:hover{background-color:#eaecf4}.sidebar .nav-item .collapse .collapse-inner .collapse-item:active,.sidebar .nav-item .collapsing .collapse-inner .collapse-item:active{background-color:#dddfeb}.sidebar .nav-item .collapse .collapse-inner .collapse-item.active,.sidebar .nav-item .collapsing .collapse-inner .collapse-item.active{color:#4e73df;font-weight:700}.sidebar #sidebarToggle{width:2.5rem;height:2.5rem;text-align:center;margin-bottom:1rem;cursor:pointer}.sidebar #sidebarToggle::after{font-weight:900;content:'\f104';font-family:'Font Awesome 5 Free';margin-right:.1rem}.sidebar #sidebarToggle:hover{text-decoration:none}.sidebar #sidebarToggle:focus{outline:0}.sidebar.toggled{width:0!important;overflow:hidden}.sidebar.toggled #sidebarToggle::after{content:'\f105';font-family:'Font Awesome 5 Free';margin-left:.25rem}.sidebar.toggled .sidebar-card{display:none}.sidebar .sidebar-brand{height:4.375rem;text-decoration:none;font-size:1rem;font-weight:800;padding:1.5rem 1rem;text-align:center;text-transform:uppercase;letter-spacing:.05rem;z-index:1}.sidebar .sidebar-brand .sidebar-brand-icon i{font-size:2rem}.sidebar .sidebar-brand .sidebar-brand-text{display:none}.sidebar hr.sidebar-divider{margin:0 1rem 1rem}.sidebar .sidebar-heading{text-align:center;padding:0 1rem;font-weight:800;font-size:.65rem}.sidebar .sidebar-card{display:flex;flex-direction:column;align-items:center;font-size:.875rem;border-radius:.35rem;color:rgba(255,255,255,.8);margin-left:1rem;margin-right:1rem;margin-bottom:1rem;padding:1rem;background-color:rgba(0,0,0,.1)}.sidebar .sidebar-card .sidebar-card-illustration{height:3rem;display:block}.sidebar .sidebar-card .sidebar-card-title{font-weight:700}.sidebar .sidebar-card p{font-size:.75rem;color:rgba(255,255,255,.5)}@media (min-width:768px){.sidebar{width:14rem!important}.sidebar .nav-item .collapse{position:relative;left:0;z-index:1;top:0;-webkit-animation:none;animation:none}.sidebar .nav-item .collapse .collapse-inner{border-radius:0;box-shadow:none}.sidebar .nav-item .collapsing{display:block;transition:height .15s ease}.sidebar .nav-item .collapse,.sidebar .nav-item .collapsing{margin:0 1rem}.sidebar .nav-item .nav-link{display:block;width:100%;text-align:left;padding:1rem;width:14rem}.sidebar .nav-item .nav-link i{font-size:.85rem;margin-right:.25rem}.sidebar .nav-item .nav-link span{font-size:.85rem;display:inline}.sidebar .nav-item .nav-link[data-toggle=collapse]::after{width:1rem;text-align:center;float:right;vertical-align:0;border:0;font-weight:900;content:'\f107';font-family:'Font Awesome 5 Free'}.sidebar .nav-item .nav-link[data-toggle=collapse].collapsed::after{content:'\f105'}.sidebar .sidebar-brand .sidebar-brand-icon i{font-size:2rem}.sidebar .sidebar-brand .sidebar-brand-text{display:inline}.sidebar .sidebar-heading{text-align:left}.sidebar.toggled{overflow:visible;width:6.5rem!important}.sidebar.toggled .nav-item .collapse{position:absolute;left:calc(6.5rem + 1.5rem / 2);z-index:1;top:2px;-webkit-animation-name:growIn;animation-name:growIn;-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-timing-function:transform cubic-bezier(.18,1.25,.4,1),opacity cubic-bezier(0,1,.4,1);animation-timing-function:transform cubic-bezier(.18,1.25,.4,1),opacity cubic-bezier(0,1,.4,1)}.sidebar.toggled .nav-item .collapse .collapse-inner{box-shadow:0 .15rem 1.75rem 0 rgba(58,59,69,.15);border-radius:.35rem}.sidebar.toggled .nav-item .collapsing{display:none;transition:none}.sidebar.toggled .nav-item .collapse,.sidebar.toggled .nav-item .collapsing{margin:0}.sidebar.toggled .nav-item:last-child{margin-bottom:1rem}.sidebar.toggled .nav-item .nav-link{text-align:center;padding:.75rem 1rem;width:6.5rem}.sidebar.toggled .nav-item .nav-link span{font-size:.65rem;display:block}.sidebar.toggled .nav-item .nav-link i{margin-right:0}.sidebar.toggled .nav-item .nav-link[data-toggle=collapse]::after{display:none}.sidebar.toggled .sidebar-brand .sidebar-brand-icon i{font-size:2rem}.sidebar.toggled .sidebar-brand .sidebar-brand-text{display:none}.sidebar.toggled .sidebar-heading{text-align:center}}.sidebar-light .sidebar-brand{color:#6e707e}.sidebar-light hr.sidebar-divider{border-top:1px solid #eaecf4}.sidebar-light .sidebar-heading{color:#b7b9cc}.sidebar-light .nav-item .nav-link{color:#858796}.sidebar-light .nav-item .nav-link i{color:#d1d3e2}.sidebar-light .nav-item .nav-link:active,.sidebar-light .nav-item .nav-link:focus,.sidebar-light .nav-item .nav-link:hover{color:#6e707e}.sidebar-light .nav-item .nav-link:active i,.sidebar-light .nav-item .nav-link:focus i,.sidebar-light .nav-item .nav-link:hover i{color:#6e707e}.sidebar-light .nav-item .nav-link[data-toggle=collapse]::after{color:#b7b9cc}.sidebar-light .nav-item.active .nav-link{color:#6e707e}.sidebar-light .nav-item.active .nav-link i{color:#6e707e}.sidebar-light #sidebarToggle{background-color:#eaecf4}.sidebar-light #sidebarToggle::after{color:#b7b9cc}.sidebar-light #sidebarToggle:hover{background-color:#dddfeb}.sidebar-dark .sidebar-brand{color:#fff}.sidebar-dark hr.sidebar-divider{border-top:1px solid rgba(255,255,255,.15)}.sidebar-dark .sidebar-heading{color:rgba(255,255,255,.4)}.sidebar-dark .nav-item .nav-link{color:rgba(255,255,255,.8)}.sidebar-dark .nav-item .nav-link i{color:rgba(255,255,255,.3)}.sidebar-dark .nav-item .nav-link:active,.sidebar-dark .nav-item .nav-link:focus,.sidebar-dark .nav-item .nav-link:hover{color:#fff}.sidebar-dark .nav-item .nav-link:active i,.sidebar-dark .nav-item .nav-link:focus i,.sidebar-dark .nav-item .nav-link:hover i{color:#fff}.sidebar-dark .nav-item .nav-link[data-toggle=collapse]::after{color:rgba(255,255,255,.5)}.sidebar-dark .nav-item.active .nav-link{color:#fff}.sidebar-dark .nav-item.active .nav-link i{color:#fff}.sidebar-dark #sidebarToggle{background-color:rgba(255,255,255,.2)}.sidebar-dark #sidebarToggle::after{color:rgba(255,255,255,.5)}.sidebar-dark #sidebarToggle:hover{background-color:rgba(255,255,255,.25)}.sidebar-dark.toggled #sidebarToggle::after{color:rgba(255,255,255,.5)}.btn-circle{border-radius:100%;height:2.5rem;width:2.5rem;font-size:1rem;display:inline-flex;align-items:center;justify-content:center}.btn-circle.btn-sm,.btn-group-sm>.btn-circle.btn{height:1.8rem;width:1.8rem;font-size:.75rem}.btn-circle.btn-lg,.btn-group-lg>.btn-circle.btn{height:3.5rem;width:3.5rem;font-size:1.35rem}.btn-icon-split{padding:0;overflow:hidden;display:inline-flex;align-items:stretch;justify-content:center}.btn-icon-split .icon{background:rgba(0,0,0,.15);display:inline-block;padding:.375rem .75rem}.btn-icon-split .text{display:inline-block;padding:.375rem .75rem}.btn-group-sm>.btn-icon-split.btn .icon,.btn-icon-split.btn-sm .icon{padding:.25rem .5rem}.btn-group-sm>.btn-icon-split.btn .text,.btn-icon-split.btn-sm .text{padding:.25rem .5rem}.btn-group-lg>.btn-icon-split.btn .icon,.btn-icon-split.btn-lg .icon{padding:.5rem 1rem}.btn-group-lg>.btn-icon-split.btn .text,.btn-icon-split.btn-lg .text{padding:.5rem 1rem}.card .card-header .dropdown{line-height:1}.card .card-header .dropdown .dropdown-menu{line-height:1.5}.card .card-header[data-toggle=collapse]{text-decoration:none;position:relative;padding:.75rem 3.25rem .75rem 1.25rem}.card .card-header[data-toggle=collapse]::after{position:absolute;right:0;top:0;padding-right:1.725rem;line-height:51px;font-weight:900;content:'\f107';font-family:'Font Awesome 5 Free';color:#d1d3e2}.card .card-header[data-toggle=collapse].collapsed{border-radius:.35rem}.card .card-header[data-toggle=collapse].collapsed::after{content:'\f105'}.chart-area{position:relative;height:10rem;width:100%}@media (min-width:768px){.chart-area{height:20rem}}.chart-bar{position:relative;height:10rem;width:100%}@media (min-width:768px){.chart-bar{height:20rem}}.chart-pie{position:relative;height:15rem;width:100%}@media (min-width:768px){.chart-pie{height:calc(20rem - 43px)!important}}.bg-login-image{background:url(https://source.unsplash.com/K4mSJ7kc0As/600x800);background-position:center;background-size:cover}.bg-register-image{background:url(https://source.unsplash.com/Mv9hjnEUHR4/600x800);background-position:center;background-size:cover}.bg-password-image{background:url(https://source.unsplash.com/oWTW-jNGl9I/600x800);background-position:center;background-size:cover}form.user .custom-checkbox.small label{line-height:1.5rem}form.user .form-control-user{font-size:.8rem;border-radius:10rem;padding:1.5rem 1rem}form.user .btn-user{font-size:.8rem;border-radius:10rem;padding:.75rem 1rem}.btn-google{color:#fff;background-color:#ea4335;border-color:#fff}.btn-google:hover{color:#fff;background-color:#e12717;border-color:#e6e6e6}.btn-google.focus,.btn-google:focus{color:#fff;background-color:#e12717;border-color:#e6e6e6;box-shadow:0 0 0 .2rem rgba(255,255,255,.5)}.btn-google.disabled,.btn-google:disabled{color:#fff;background-color:#ea4335;border-color:#fff}.btn-google:not(:disabled):not(.disabled).active,.btn-google:not(:disabled):not(.disabled):active,.show>.btn-google.dropdown-toggle{color:#fff;background-color:#d62516;border-color:#dfdfdf}.btn-google:not(:disabled):not(.disabled).active:focus,.btn-google:not(:disabled):not(.disabled):active:focus,.show>.btn-google.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,255,255,.5)}.btn-facebook{color:#fff;background-color:#3b5998;border-color:#fff}.btn-facebook:hover{color:#fff;background-color:#30497c;border-color:#e6e6e6}.btn-facebook.focus,.btn-facebook:focus{color:#fff;background-color:#30497c;border-color:#e6e6e6;box-shadow:0 0 0 .2rem rgba(255,255,255,.5)}.btn-facebook.disabled,.btn-facebook:disabled{color:#fff;background-color:#3b5998;border-color:#fff}.btn-facebook:not(:disabled):not(.disabled).active,.btn-facebook:not(:disabled):not(.disabled):active,.show>.btn-facebook.dropdown-toggle{color:#fff;background-color:#2d4373;border-color:#dfdfdf}.btn-facebook:not(:disabled):not(.disabled).active:focus,.btn-facebook:not(:disabled):not(.disabled):active:focus,.show>.btn-facebook.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,255,255,.5)}.error{color:#5a5c69;font-size:7rem;position:relative;line-height:1;width:12.5rem}@-webkit-keyframes noise-anim{0%{clip:rect(49px,9999px,40px,0)}5%{clip:rect(75px,9999px,72px,0)}10%{clip:rect(97px,9999px,93px,0)}15%{clip:rect(15px,9999px,9px,0)}20%{clip:rect(14px,9999px,92px,0)}25%{clip:rect(18px,9999px,94px,0)}30%{clip:rect(17px,9999px,20px,0)}35%{clip:rect(71px,9999px,59px,0)}40%{clip:rect(42px,9999px,84px,0)}45%{clip:rect(56px,9999px,25px,0)}50%{clip:rect(46px,9999px,14px,0)}55%{clip:rect(47px,9999px,1px,0)}60%{clip:rect(64px,9999px,58px,0)}65%{clip:rect(89px,9999px,92px,0)}70%{clip:rect(56px,9999px,39px,0)}75%{clip:rect(80px,9999px,71px,0)}80%{clip:rect(8px,9999px,13px,0)}85%{clip:rect(66px,9999px,68px,0)}90%{clip:rect(68px,9999px,4px,0)}95%{clip:rect(56px,9999px,14px,0)}100%{clip:rect(28px,9999px,53px,0)}}@keyframes noise-anim{0%{clip:rect(49px,9999px,40px,0)}5%{clip:rect(75px,9999px,72px,0)}10%{clip:rect(97px,9999px,93px,0)}15%{clip:rect(15px,9999px,9px,0)}20%{clip:rect(14px,9999px,92px,0)}25%{clip:rect(18px,9999px,94px,0)}30%{clip:rect(17px,9999px,20px,0)}35%{clip:rect(71px,9999px,59px,0)}40%{clip:rect(42px,9999px,84px,0)}45%{clip:rect(56px,9999px,25px,0)}50%{clip:rect(46px,9999px,14px,0)}55%{clip:rect(47px,9999px,1px,0)}60%{clip:rect(64px,9999px,58px,0)}65%{clip:rect(89px,9999px,92px,0)}70%{clip:rect(56px,9999px,39px,0)}75%{clip:rect(80px,9999px,71px,0)}80%{clip:rect(8px,9999px,13px,0)}85%{clip:rect(66px,9999px,68px,0)}90%{clip:rect(68px,9999px,4px,0)}95%{clip:rect(56px,9999px,14px,0)}100%{clip:rect(28px,9999px,53px,0)}}.error:after{content:attr(data-text);position:absolute;left:2px;text-shadow:-1px 0 #e74a3b;top:0;color:#5a5c69;background:#f8f9fc;overflow:hidden;clip:rect(0,900px,0,0);animation:noise-anim 2s infinite linear alternate-reverse}@-webkit-keyframes noise-anim-2{0%{clip:rect(16px,9999px,10px,0)}5%{clip:rect(22px,9999px,29px,0)}10%{clip:rect(6px,9999px,68px,0)}15%{clip:rect(85px,9999px,95px,0)}20%{clip:rect(65px,9999px,91px,0)}25%{clip:rect(93px,9999px,68px,0)}30%{clip:rect(10px,9999px,27px,0)}35%{clip:rect(37px,9999px,25px,0)}40%{clip:rect(12px,9999px,23px,0)}45%{clip:rect(40px,9999px,18px,0)}50%{clip:rect(19px,9999px,71px,0)}55%{clip:rect(2px,9999px,35px,0)}60%{clip:rect(16px,9999px,69px,0)}65%{clip:rect(8px,9999px,65px,0)}70%{clip:rect(30px,9999px,57px,0)}75%{clip:rect(14px,9999px,4px,0)}80%{clip:rect(39px,9999px,30px,0)}85%{clip:rect(22px,9999px,35px,0)}90%{clip:rect(58px,9999px,71px,0)}95%{clip:rect(34px,9999px,90px,0)}100%{clip:rect(67px,9999px,68px,0)}}@keyframes noise-anim-2{0%{clip:rect(16px,9999px,10px,0)}5%{clip:rect(22px,9999px,29px,0)}10%{clip:rect(6px,9999px,68px,0)}15%{clip:rect(85px,9999px,95px,0)}20%{clip:rect(65px,9999px,91px,0)}25%{clip:rect(93px,9999px,68px,0)}30%{clip:rect(10px,9999px,27px,0)}35%{clip:rect(37px,9999px,25px,0)}40%{clip:rect(12px,9999px,23px,0)}45%{clip:rect(40px,9999px,18px,0)}50%{clip:rect(19px,9999px,71px,0)}55%{clip:rect(2px,9999px,35px,0)}60%{clip:rect(16px,9999px,69px,0)}65%{clip:rect(8px,9999px,65px,0)}70%{clip:rect(30px,9999px,57px,0)}75%{clip:rect(14px,9999px,4px,0)}80%{clip:rect(39px,9999px,30px,0)}85%{clip:rect(22px,9999px,35px,0)}90%{clip:rect(58px,9999px,71px,0)}95%{clip:rect(34px,9999px,90px,0)}100%{clip:rect(67px,9999px,68px,0)}}.error:before{content:attr(data-text);position:absolute;left:-2px;text-shadow:1px 0 #4e73df;top:0;color:#5a5c69;background:#f8f9fc;overflow:hidden;clip:rect(0,900px,0,0);animation:noise-anim-2 3s infinite linear alternate-reverse}footer.sticky-footer{padding:2rem 0;flex-shrink:0}footer.sticky-footer .copyright{line-height:1;font-size:.8rem}body.sidebar-toggled footer.sticky-footer{width:100%} \ No newline at end of file diff --git a/SmartEVSE-3/SmartEVSE-3/data/update2.html b/SmartEVSE-3/SmartEVSE-3/data/update2.html deleted file mode 100644 index 7c2099f..0000000 --- a/SmartEVSE-3/SmartEVSE-3/data/update2.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - SmartEVSEv3 - - - - - - - -
-
- Current version: - ======================================================= - DOWNLOADING -

- - - - - - - - - - - - - - - - - - - - -
Distribution:Status:Github repo:Latest version:Flash:
Factorystable
- -
Community          bleeding edge       
- -
-
- ======================================================= -
- FLASHING -

- Flash one of: - You should only flash files with those exact names.
No need to flash spiffs.bin for versions 3.6.0-RC1 and newer!
No need to rename firmware.debug.bin anymore for versions 3.6.0-RC2 and newer!
Signed firmware is verified to be original and can be handled by versions 3.6.2 and newer. -
-
- - -
-
-
- - - - diff --git a/SmartEVSE-3/SmartEVSE-3/data/update3.html b/SmartEVSE-3/SmartEVSE-3/data/update3.html deleted file mode 100644 index 21ef75d..0000000 --- a/SmartEVSE-3/SmartEVSE-3/data/update3.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - SmartEVSEv3 - - - - - -
- - diff --git a/SmartEVSE-3/SmartEVSE-3/include/OneWire.h b/SmartEVSE-3/SmartEVSE-3/include/OneWire.h deleted file mode 100644 index 6a8e944..0000000 --- a/SmartEVSE-3/SmartEVSE-3/include/OneWire.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -; Project: Smart EVSE -; -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. -*/ - - -#ifndef __ONEWIRE_H -#define __ONEWIRE_H - -extern Preferences preferences; - -void ReadRFIDlist(void); -void DeleteAllRFID(void); -void CheckRFID(void); -void LoadandStoreRFID(unsigned int *RFIDparam); - -#endif // #ifndef __ONEWIRE_H diff --git a/SmartEVSE-3/SmartEVSE-3/include/README b/SmartEVSE-3/SmartEVSE-3/include/README deleted file mode 100644 index 194dcd4..0000000 --- a/SmartEVSE-3/SmartEVSE-3/include/README +++ /dev/null @@ -1,39 +0,0 @@ - -This directory is intended for project header files. - -A header file is a file containing C declarations and macro definitions -to be shared between several project source files. You request the use of a -header file in your project source file (C, C++, etc) located in `src` folder -by including it, with the C preprocessing directive `#include'. - -```src/main.c - -#include "header.h" - -int main (void) -{ - ... -} -``` - -Including a header file produces the same results as copying the header file -into each source file that needs it. Such copying would be time-consuming -and error-prone. With a header file, the related declarations appear -in only one place. If they need to be changed, they can be changed in one -place, and programs that include the header file will automatically use the -new version when next recompiled. The header file eliminates the labor of -finding and changing all the copies as well as the risk that a failure to -find one copy will result in inconsistencies within a program. - -In C, the usual convention is to give header files names that end with `.h'. -It is most portable to use only letters, digits, dashes, and underscores in -header file names, and at most one dot. - -Read more about using header files in official GCC documentation: - -* Include Syntax -* Include Operation -* Once-Only Headers -* Computed Includes - -https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/SmartEVSE-3/SmartEVSE-3/include/evse.h b/SmartEVSE-3/SmartEVSE-3/include/evse.h deleted file mode 100644 index b60d73f..0000000 --- a/SmartEVSE-3/SmartEVSE-3/include/evse.h +++ /dev/null @@ -1,592 +0,0 @@ -/* -; Project: Smart EVSE v3 -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. - */ - -#ifndef __EVSE_MAIN - -#define __EVSE_MAIN - -#ifndef DBG -//the wifi-debugger is available by telnetting to your SmartEVSE device -#define DBG 0 //comment or set to 0 for production release, 0 = no debug 1 = debug over telnet, 2 = debug over usb serial -#endif - -#ifndef FAKE_RFID -//set FAKE_RFID to 1 to emulate an rfid reader with rfid of card = 123456 -//showing the rfid card is simulated by executing http://smartevse-xxx.lan/debug?showrfid=1 -//don't forget to first store the card before it can activate charging -#define FAKE_RFID 0 -#endif - -#ifndef AUTOMATED_TESTING -//set AUTOMATED_TESTING to 1 to make hardware-related paramaters like MaxCurrent and MaxCircuit updatable via REST API -//e.g. by executing curl -X POST http://smartevse-xxx.lan/automated_testing?maxcurrent=100 -#define AUTOMATED_TESTING 0 -#endif - -#ifndef FAKE_SUNNY_DAY -//set this to 1 to emulate a sunny day where your solar charger is injecting current in the grid: -#define FAKE_SUNNY_DAY 0 -//disclaimer: might not work for CT1 calibration/uncalibration stuff, since I can't test that -//the number of Amperes you want to have fake injected into Lx -#endif - -#if FAKE_SUNNY_DAY -#define INJECT_CURRENT_L1 10 -#define INJECT_CURRENT_L2 0 -#define INJECT_CURRENT_L3 0 -#endif - -#ifndef MQTT -#define MQTT 1 // Uncomment or set to 0 to disable MQTT support in code -#endif - -#ifndef MODEM -//the wifi-debugger is available by telnetting to your SmartEVSE device -#define MODEM 0 //0 = no modem 1 = modem -#endif - -#ifndef VERSION -//please note that this version will only be displayed with the correct time/date if the program is recompiled -//so the webserver will show correct version if evse.cpp is recompiled -//the lcd display will show correct version if glcd.cpp is recompiled -#define VERSION (__TIME__ " @" __DATE__) -#endif - - -#if DBG == 0 -//used to steer RemoteDebug -#define DEBUG_DISABLED 1 -#define _LOG_W( ... ) //dummy -#define _LOG_I( ... ) //dummy -#define _LOG_D( ... ) //dummy -#define _LOG_V( ... ) //dummy -#define _LOG_A( ... ) //dummy -#define _LOG_W_NO_FUNC( ... ) //dummy -#define _LOG_I_NO_FUNC( ... ) //dummy -#define _LOG_D_NO_FUNC( ... ) //dummy -#define _LOG_V_NO_FUNC( ... ) //dummy -#define _LOG_A_NO_FUNC( ... ) //dummy -#endif - -#if DBG == 1 -#define _LOG_A(fmt, ...) if (Debug.isActive(Debug.ANY)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) //Always = Errors!!! -#define _LOG_P(fmt, ...) if (Debug.isActive(Debug.PROFILER)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) -#define _LOG_V(fmt, ...) if (Debug.isActive(Debug.VERBOSE)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) //Verbose -#define _LOG_D(fmt, ...) if (Debug.isActive(Debug.DEBUG)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) //Debug -#define _LOG_I(fmt, ...) if (Debug.isActive(Debug.INFO)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) //Info -#define _LOG_W(fmt, ...) if (Debug.isActive(Debug.WARNING)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) //Warning -#define _LOG_E(fmt, ...) if (Debug.isActive(Debug.ERROR)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) //Error not used! -#define _LOG_A_NO_FUNC(fmt, ...) if (Debug.isActive(Debug.ANY)) Debug.printf(fmt, ##__VA_ARGS__) -#define _LOG_P_NO_FUNC(fmt, ...) if (Debug.isActive(Debug.PROFILER)) Debug.printf(fmt, ##__VA_ARGS__) -#define _LOG_V_NO_FUNC(fmt, ...) if (Debug.isActive(Debug.VERBOSE)) Debug.printf(fmt, ##__VA_ARGS__) -#define _LOG_D_NO_FUNC(fmt, ...) if (Debug.isActive(Debug.DEBUG)) Debug.printf(fmt, ##__VA_ARGS__) -#define _LOG_I_NO_FUNC(fmt, ...) if (Debug.isActive(Debug.INFO)) Debug.printf(fmt, ##__VA_ARGS__) -#define _LOG_W_NO_FUNC(fmt, ...) if (Debug.isActive(Debug.WARNING)) Debug.printf(fmt, ##__VA_ARGS__) -#define _LOG_E_NO_FUNC(fmt, ...) if (Debug.isActive(Debug.ERROR)) Debug.printf(fmt, ##__VA_ARGS__) -#include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug -extern RemoteDebug Debug; -#endif - -#if DBG == 2 -#define DEBUG_DISABLED 1 -#define _LOG_W( ... ) log_w ( __VA_ARGS__ ) -#define _LOG_I( ... ) log_i ( __VA_ARGS__ ) -#define _LOG_D( ... ) log_d ( __VA_ARGS__ ) -#define _LOG_V( ... ) log_v ( __VA_ARGS__ ) -#define _LOG_A( ... ) log_n ( __VA_ARGS__ ) -#define _LOG_W_NO_FUNC( ... ) Serial.printf ( __VA_ARGS__ ) -#define _LOG_I_NO_FUNC( ... ) Serial.printf ( __VA_ARGS__ ) -#define _LOG_D_NO_FUNC( ... ) Serial.printf ( __VA_ARGS__ ) -#define _LOG_V_NO_FUNC( ... ) Serial.printf ( __VA_ARGS__ ) -#define _LOG_A_NO_FUNC( ... ) Serial.printf ( __VA_ARGS__ ) -#endif - -// Pin definitions left side ESP32 -#define PIN_TEMP 36 -#define PIN_CP_IN 39 -#define PIN_PP_IN 34 -#define PIN_LOCK_IN 35 -#define PIN_SSR 32 -#define PIN_LCD_SDO_B3 33 // = SPI_MOSI -#define PIN_LCD_A0_B2 25 -#define PIN_LCD_CLK 26 // = SPI_SCK -#define PIN_SSR2 27 -#define PIN_LCD_LED 14 -#define PIN_LEDB 12 -#define PIN_RCM_FAULT 13 - -// Pin definitions right side ESP32 -#define PIN_RS485_RX 23 -#define PIN_RS485_DIR 22 -//#define PIN_RXD -//#define PIN_TXD -#define PIN_RS485_TX 21 -#define PIN_CP_OUT 19 -#define PIN_ACTB 18 -#define PIN_LCD_RST 5 -#define PIN_ACTA 17 -#define PIN_SW_IN 16 -#define PIN_LEDG 4 -#define PIN_IO0_B1 0 -#define PIN_LEDR 2 -#define PIN_CPOFF 15 - -#define SPI_MOSI 33 // SPI connections to LCD -#define SPI_MISO -1 -#define SPI_SCK 26 -#define SPI_SS -1 - -#define CP_CHANNEL 0 -#define RED_CHANNEL 2 // PWM channel 2 (0 and 1 are used by CP signal) -#define GREEN_CHANNEL 3 -#define BLUE_CHANNEL 4 -#define LCD_CHANNEL 5 // LED Backlight LCD - -#define PWM_5 50 // 5% of PWM -#define PWM_95 950 // 95% of PWM -#define PWM_100 1000 // 100% of PWM - -#define ICAL 1024 // Irms Calibration value (for Current transformers) -#define MAX_MAINS 25 // max Current the Mains connection can supply -#define MAX_SUMMAINS 600 // only used for capacity rate limiting, max current over the sum of all phases -#define MAX_CURRENT 13 // max charging Current for the EV -#ifndef MIN_CURRENT -#define MIN_CURRENT 6 // minimum Current the EV will accept -#endif -#define MODE 0 // Normal EVSE mode -#define LOCK 0 // No Cable lock -#define MAX_CIRCUIT 16 // Max current of the EVSE circuit breaker -#define CONFIG 0 // Configuration: 0= TYPE 2 socket, 1= Fixed Cable -#define LOADBL 0 // Load Balancing disabled -#define SWITCH 0 // 0= Charge on plugin, 1= (Push)Button on IO2 is used to Start/Stop charging. -#define RC_MON 0 // Residual Current Monitoring on IO3. Disabled=0, RCM14=1 -#define CHARGEDELAY 60 // Seconds to wait after overcurrent, before trying again -#define BACKLIGHT 120 // Seconds delay for the LCD backlight to turn off. -#define RFIDLOCKTIME 60 // Seconds delay for the EVSE to lock again (RFIDreader = EnableOne) -#define START_CURRENT 4 // Start charging when surplus current on sum of all phases exceeds 4A (Solar) -#define STOP_TIME 10 // Stop charging after 10 minutes at MIN charge current (Solar) -#define IMPORT_CURRENT 0 // Allow the use of grid power when solar charging (Amps) -#define MAINS_METER 0 // Mains Meter, 0=Disabled, 1= Sensorbox, 2=Phoenix, 3= Finder, 4= Eastron, 5=Custom -#define GRID 0 // Grid, 0= 4-Wire CW, 1= 4-Wire CCW, 2= 3-Wire CW, 3= 3-Wire CCW -#define MAINS_METER_ADDRESS 10 -#define PV_METER 0 -#define PV_METER_ADDRESS 11 -#define EV_METER 0 -#define EV_METER_ADDRESS 12 -#define MIN_METER_ADDRESS 10 -#define MAX_METER_ADDRESS 247 -#define EMCUSTOM_ENDIANESS 0 -#define EMCUSTOM_DATATYPE 0 -#define EMCUSTOM_FUNCTION 4 -#define EMCUSTOM_UREGISTER 0 -#define EMCUSTOM_UDIVISOR 8 -#define EMCUSTOM_IREGISTER 0 -#define EMCUSTOM_IDIVISOR 8 -#define EMCUSTOM_PREGISTER 0 -#define EMCUSTOM_PDIVISOR 8 -#define EMCUSTOM_EREGISTER 0 -#define EMCUSTOM_EDIVISOR 8 -#define RFID_READER 0 -#define ACCESS_BIT 1 -#define WIFI_MODE 0 -#define AP_PASSWORD "00000000" -#define CARD_OFFSET 0 -#define INITIALIZED 0 -#define ENABLE_C2 ALWAYS_ON -#define MAX_TEMPERATURE 65 -#define DELAYEDSTARTTIME 0 // The default StartTime for delayed charged, 0 = not delaying -#define DELAYEDSTOPTIME 0 // The default StopTime for delayed charged, 0 = not stopping -#define SOLARSTARTTIME 40 // Seconds to keep chargecurrent at 6A -#define PUBLIC_KEY "5c7a848c3445793002487608a65fa259cefb16790f7c2f4a1d10af702393f7db\0"; - - -// Mode settings -#define MODE_NORMAL 0 -#define MODE_SMART 1 -#define MODE_SOLAR 2 - -#define MODBUS_BAUDRATE 9600 -#define MODBUS_TIMEOUT 4 -#define ACK_TIMEOUT 1000 // 1000ms timeout -#define NR_EVSES 8 -#define BROADCAST_ADR 0x09 -#define COMM_TIMEOUT 11 // Timeout for MainsMeter -#define COMM_EVTIMEOUT 8*NR_EVSES // Timeout for EV Energy Meters - -#define STATE_A 0 // A Vehicle not connected -#define STATE_B 1 // B Vehicle connected / not ready to accept energy -#define STATE_C 2 // C Vehicle connected / ready to accept energy / ventilation not required -#define STATE_D 3 // D Vehicle connected / ready to accept energy / ventilation required (not implemented) -#define STATE_COMM_B 4 // E State change request A->B (set by node) -#define STATE_COMM_B_OK 5 // F State change A->B OK (set by master) -#define STATE_COMM_C 6 // G State change request B->C (set by node) -#define STATE_COMM_C_OK 7 // H State change B->C OK (set by master) -#define STATE_ACTSTART 8 // I Activation mode in progress -#define STATE_B1 9 // J Vehicle connected / EVSE not ready to deliver energy: no PWM signal -#define STATE_C1 10 // K Vehicle charging / EVSE not ready to deliver energy: no PWM signal (temp state when stopping charge from EVSE) -#define STATE_MODEM_REQUEST 11 // L Vehicle connected / requesting ISO15118 communication, 0% duty -#define STATE_MODEM_WAIT 12 // M Vehicle connected / requesting ISO15118 communication, 5% duty -#define STATE_MODEM_DONE 13 // Modem communication succesful, SoCs extracted. Here, re-plug vehicle -#define STATE_MODEM_DENIED 14 // Modem access denied based on EVCCID, re-plug vehicle and try again - -#define NOSTATE 255 - -#define PILOT_12V 1 // State A - vehicle disconnected -#define PILOT_9V 2 // State B - vehicle connected -#define PILOT_6V 3 // State C - EV charge -#define PILOT_3V 4 -#define PILOT_DIODE 5 -#define PILOT_NOK 0 - - -#define NO_ERROR 0 -#define LESS_6A 1 -#define CT_NOCOMM 2 -#define TEMP_HIGH 4 -#define EV_NOCOMM 8 -#define RCM_TRIPPED 16 // RCM tripped. >6mA DC residual current detected. -#define NO_SUN 32 -#define Test_IO 64 -#define BL_FLASH 128 - -#define STATE_A_LED_BRIGHTNESS 40 -#define STATE_B_LED_BRIGHTNESS 255 -#define ERROR_LED_BRIGHTNESS 255 -#define WAITING_LED_BRIGHTNESS 255 -#define LCD_BRIGHTNESS 255 - - -#define CP_ON digitalWrite(PIN_CPOFF, LOW); -#define CP_OFF digitalWrite(PIN_CPOFF, HIGH); - -#define PILOT_CONNECTED digitalWrite(PIN_CPOFF, LOW); -#define PILOT_DISCONNECTED digitalWrite(PIN_CPOFF, HIGH); - -#define CONTACTOR1_ON _LOG_A("Switching Contactor1 ON.\n"); digitalWrite(PIN_SSR, HIGH); -#define CONTACTOR1_OFF _LOG_A("Switching Contactor1 OFF.\n"); digitalWrite(PIN_SSR, LOW); - -#define CONTACTOR2_ON _LOG_A("Switching Contactor2 ON.\n"); digitalWrite(PIN_SSR2, HIGH); -#define CONTACTOR2_OFF _LOG_A("Switching Contactor2 OFF.\n"); digitalWrite(PIN_SSR2, LOW); - -#define BACKLIGHT_ON digitalWrite(PIN_LCD_LED, HIGH); -#define BACKLIGHT_OFF digitalWrite(PIN_LCD_LED, LOW); - -#define ACTUATOR_LOCK { _LOG_A("Locking Actuator.\n"); digitalWrite(PIN_ACTB, HIGH); digitalWrite(PIN_ACTA, LOW); } -#define ACTUATOR_UNLOCK { _LOG_A("Unlocking Actuator.\n"); digitalWrite(PIN_ACTB, LOW); digitalWrite(PIN_ACTA, HIGH); } -#define ACTUATOR_OFF { digitalWrite(PIN_ACTB, HIGH); digitalWrite(PIN_ACTA, HIGH); } - -#define ONEWIRE_LOW { digitalWrite(PIN_SW_IN, LOW); pinMode(PIN_SW_IN, OUTPUT); } // SW set to 0, set to output (driven low) -#define ONEWIRE_HIGH { digitalWrite(PIN_SW_IN, HIGH); pinMode(PIN_SW_IN, OUTPUT); } // SW set to 1, set to output (driven high) -#define ONEWIRE_FLOATHIGH pinMode(PIN_SW_IN, INPUT_PULLUP ); // SW input (floating high) - -#define RCMFAULT digitalRead(PIN_RCM_FAULT) -#define FREE(x) free(x); x = NULL; -#define FW_DOWNLOAD_PATH "http://smartevse-3.s3.eu-west-2.amazonaws.com" - -#define MODBUS_INVALID 0 -#define MODBUS_OK 1 -#define MODBUS_REQUEST 2 -#define MODBUS_RESPONSE 3 -#define MODBUS_EXCEPTION 4 - -#define MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS 0x02 -#define MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE 0x03 - -#define MODBUS_EVSE_STATUS_START 0x0000 -#define MODBUS_EVSE_STATUS_COUNT 12 -#define MODBUS_EVSE_CONFIG_START 0x0100 -#define MODBUS_EVSE_CONFIG_COUNT 10 -#define MODBUS_SYS_CONFIG_START 0x0200 -#define MODBUS_SYS_CONFIG_COUNT 26 - -#define MODBUS_MAX_REGISTER_READ MODBUS_SYS_CONFIG_COUNT -#define MODBUS_BUFFER_SIZE MODBUS_MAX_REGISTER_READ * 2 + 10 - -// EVSE status -#define STATUS_STATE 64 // 0x0000: State -#define STATUS_ERROR 65 // 0x0001: Error -#define STATUS_CURRENT 66 // 0x0002: Charging current (A * 10) -#define STATUS_MODE 67 // 0x0003: EVSE Mode -#define STATUS_SOLAR_TIMER 68 // 0x0004: Solar Timer -#define STATUS_ACCESS 69 // 0x0005: Access bit -#define STATUS_CONFIG_CHANGED 70 // 0x0006: Configuration changed -#define STATUS_MAX 71 // 0x0007: Maximum charging current (RO) -#define STATUS_PHASE_COUNT 72 // 0x0008: Number of used phases (RO) (ToDo) -#define STATUS_REAL_CURRENT 73 // 0x0009: Real charging current (RO) (ToDo) -#define STATUS_TEMP 74 // 0x000A: Temperature (RO) -#define STATUS_SERIAL 75 // 0x000B: Serial number (RO) - -// Node specific configuration -#define MENU_ENTER 1 -#define MENU_CONFIG 2 // 0x0100: Configuration -#define MENU_LOCK 3 // 0x0101: Cable lock -#define MENU_MIN 4 // 0x0102: MIN Charge Current the EV will accept -#define MENU_MAX 5 // 0x0103: MAX Charge Current for this EVSE -#define MENU_LOADBL 6 // 0x0104: Load Balance -#define MENU_SWITCH 7 // 0x0105: External Start/Stop button -#define MENU_RCMON 8 // 0x0106: Residual Current Monitor -#define MENU_RFIDREADER 9 // 0x0107: Use RFID reader -#define MENU_EVMETER 10 // 0x0108: Type of EV electric meter -#define MENU_EVMETERADDRESS 11 // 0x0109: Address of EV electric meter - -// System configuration (same on all SmartEVSE in a LoadBalancing setup) -#define MENU_MODE 12 // 0x0200: EVSE mode -#define MENU_CIRCUIT 13 // 0x0201: EVSE Circuit max Current -#define MENU_GRID 14 // 0x0202: Grid type to which the Sensorbox is connected -#define MENU_CAL 15 // 0x0203: CT calibration value -#define MENU_MAINS 16 // 0x0204: Max Mains Current -#define MENU_START 17 // 0x0205: Surplus energy start Current -#define MENU_STOP 18 // 0x0206: Stop solar charging at 6A after this time -#define MENU_IMPORT 19 // 0x0207: Allow grid power when solar charging -#define MENU_MAINSMETER 20 // 0x0208: Type of Mains electric meter -#define MENU_MAINSMETERADDRESS 21 // 0x0209: Address of Mains electric meter -#define MENU_EMCUSTOM_ENDIANESS 22 // 0x020D: Byte order of custom electric meter -#define MENU_EMCUSTOM_DATATYPE 23 // 0x020E: Data type of custom electric meter -#define MENU_EMCUSTOM_FUNCTION 24 // 0x020F: Modbus Function (3/4) of custom electric meter -#define MENU_EMCUSTOM_UREGISTER 25 // 0x0210: Register for Voltage (V) of custom electric meter -#define MENU_EMCUSTOM_UDIVISOR 26 // 0x0211: Divisor for Voltage (V) of custom electric meter (10^x) -#define MENU_EMCUSTOM_IREGISTER 27 // 0x0212: Register for Current (A) of custom electric meter -#define MENU_EMCUSTOM_IDIVISOR 28 // 0x0213: Divisor for Current (A) of custom electric meter (10^x) -#define MENU_EMCUSTOM_PREGISTER 29 // 0x0214: Register for Power (W) of custom electric meter -#define MENU_EMCUSTOM_PDIVISOR 30 // 0x0215: Divisor for Power (W) of custom electric meter (10^x) -#define MENU_EMCUSTOM_EREGISTER 31 // 0x0216: Register for Energy (kWh) of custom electric meter -#define MENU_EMCUSTOM_EDIVISOR 32 // 0x0217: Divisor for Energy (kWh) of custom electric meter (10^x) -#define MENU_EMCUSTOM_READMAX 33 // 0x0218: Maximum register read (ToDo) -#define MENU_WIFI 34 // 0x0219: WiFi mode -#define MENU_C2 35 -#define MENU_MAX_TEMP 36 -#define MENU_SUMMAINS 37 -#define MENU_OFF 38 // so access bit is reset and charging stops when pressing < button 2 seconds -#define MENU_ON 39 // so access bit is set and charging starts when pressing > button 2 seconds -#define MENU_EXIT 40 - -#define MENU_STATE 50 - -#define _RSTB_0 digitalWrite(PIN_LCD_RST, LOW); -#define _RSTB_1 digitalWrite(PIN_LCD_RST, HIGH); -#define _A0_0 digitalWrite(PIN_LCD_A0_B2, LOW); -#define _A0_1 digitalWrite(PIN_LCD_A0_B2, HIGH); - -#define EM_SENSORBOX 1 // Mains meter types -#define EM_PHOENIX_CONTACT 2 -#define EM_FINDER_7E 3 -#define EM_EASTRON3P 4 -#define EM_EASTRON3P_INV 5 -#define EM_ABB 6 -#define EM_SOLAREDGE 7 -#define EM_WAGO 8 -#define EM_API 9 -#define EM_EASTRON1P 10 -#define EM_FINDER_7M 11 -#define EM_UNUSED_SLOT1 12 -#define EM_UNUSED_SLOT2 13 -#define EM_UNUSED_SLOT3 14 -#define EM_UNUSED_SLOT4 15 -#define EM_CUSTOM 16 - -#define ENDIANESS_LBF_LWF 0 -#define ENDIANESS_LBF_HWF 1 -#define ENDIANESS_HBF_LWF 2 -#define ENDIANESS_HBF_HWF 3 - -#define OWNER_FACT "SmartEVSE" -#define REPO_FACT "SmartEVSE-3" -#define OWNER_COMM "dingo35" -#define REPO_COMM "SmartEVSE-3.5" - -typedef enum mb_datatype { - MB_DATATYPE_INT32 = 0, - MB_DATATYPE_FLOAT32 = 1, - MB_DATATYPE_INT16 = 2, - MB_DATATYPE_MAX, -} MBDataType; - - -extern portMUX_TYPE rtc_spinlock; //TODO: Will be placed in the appropriate position after the rtc module is finished. - -#define RTC_ENTER_CRITICAL() portENTER_CRITICAL(&rtc_spinlock) -#define RTC_EXIT_CRITICAL() portEXIT_CRITICAL(&rtc_spinlock) - - -extern String APpassword; -extern struct tm timeinfo; - - -extern uint16_t ICal; // CT calibration value -extern uint8_t Mode; // EVSE mode -extern uint8_t LoadBl; // Load Balance Setting (Disable, Master or Node) -extern uint8_t Grid; -extern uint8_t MainsMeterAddress; -extern uint8_t EVMeter; // Type of EV electric meter (0: Disabled / Constants EM_*) -extern uint8_t EVMeterAddress; -#if FAKE_RFID -extern uint8_t Show_RFID; -#endif - -extern int16_t Irms[3]; // Momentary current per Phase (Amps *10) (23 = 2.3A) -extern int16_t Irms_EV[3]; // Momentary current per Phase (Amps *10) (23 = 2.3A) - -extern uint8_t State; -extern uint8_t ErrorFlags; -extern uint8_t NextState; - -extern int16_t Isum; -extern uint16_t Balanced[NR_EVSES]; // Amps value per EVSE - -extern uint8_t LCDTimer; -extern uint16_t BacklightTimer; // remaining seconds the LCD backlight is active -extern uint8_t ButtonState; // Holds latest push Buttons state (LSB 2:0) -extern uint8_t OldButtonState; // Holds previous push Buttons state (LSB 2:0) -extern uint8_t LCDNav; -extern uint8_t LCDupdate; -extern uint8_t SubMenu; -extern uint32_t ScrollTimer; -extern uint8_t ChargeDelay; // Delays charging in seconds. -extern uint8_t TestState; -extern uint8_t Access_bit; -extern uint16_t CardOffset; - -extern uint8_t GridActive; // When the CT's are used on Sensorbox2, it enables the GRID menu option. -extern uint8_t CalActive; // When the CT's are used on Sensorbox(1.5 or 2), it enables the CAL menu option. -extern uint16_t Iuncal; -extern uint16_t SolarStopTimer; -extern int32_t EnergyCharged; -extern int32_t EnergyCapacity; -extern int16_t PowerMeasured; -extern uint8_t RFIDstatus; -extern bool LocalTimeSet; -extern uint32_t serialnr; - -extern uint8_t MenuItems[MENU_EXIT]; - -enum EnableC2_t { NOT_PRESENT, ALWAYS_OFF, SOLAR_OFF, ALWAYS_ON, AUTO }; -enum Modem_t { NOTPRESENT, EXPERIMENT }; -const static char StrEnableC2[][12] = { "Not present", "Always Off", "Solar Off", "Always On", "Auto" }; -const static char StrModem[][12] = { "Not present", "Experiment" }; -enum Single_Phase_t { FALSE, GOING_TO_SWITCH, AFTER_SWITCH }; -extern Single_Phase_t Switching_To_Single_Phase; -extern uint8_t Nr_Of_Phases_Charging; - -const struct { - char LCD[10]; - char Desc[52]; - uint16_t Min; - uint16_t Max; - uint16_t Default; -} MenuStr[MENU_EXIT + 1] = { - {"", "Not in menu", 0, 0, 0}, - {"", "Hold 2 sec", 0, 0, 0}, - - // Node specific configuration - /* LCD, Desc, Min, Max, Default */ - {"CONFIG", "Fixed Cable or Type 2 Socket", 0, 1, CONFIG}, - {"LOCK", "Cable locking actuator type", 0, 2, LOCK}, - {"MIN", "MIN Charge Current the EV will accept (per phase)", MIN_CURRENT, 16, MIN_CURRENT}, - {"MAX", "MAX Charge Current for this EVSE (per phase)", 6, 80, MAX_CURRENT}, - {"PWR SHARE", "Share Power between multiple SmartEVSEs (2-8)", 0, NR_EVSES, LOADBL}, - {"SWITCH", "Switch function control on pin SW", 0, 4, SWITCH}, - {"RCMON", "Residual Current Monitor on pin RCM", 0, 1, RC_MON}, - {"RFID", "RFID reader, learn/remove cards", 0, 5, RFID_READER}, - {"EV METER","Type of EV electric meter", 0, EM_CUSTOM, EV_METER}, - {"EV ADDR", "Address of EV electric meter", MIN_METER_ADDRESS, MAX_METER_ADDRESS, EV_METER_ADDRESS}, - - // System configuration - /* LCD, Desc, Min, Max, Default */ - {"MODE", "Normal, Smart or Solar EVSE mode", 0, 2, MODE}, - {"CIRCUIT", "EVSE Circuit max Current", 10, 160, MAX_CIRCUIT}, - {"GRID", "Grid type to which the Sensorbox is connected", 0, 1, GRID}, - {"CAL", "Calibrate CT1 (CT2+3 will also change)", (unsigned int) (ICAL * 0.3), (unsigned int) (ICAL * 2.0), ICAL}, // valid range is 0.3 - 2.0 times measured value - {"MAINS", "Max MAINS Current (per phase)", 10, 200, MAX_MAINS}, - {"START", "Surplus energy start Current (sum of phases)", 0, 48, START_CURRENT}, - {"STOP", "Stop solar charging at 6A after this time", 0, 60, STOP_TIME}, - {"IMPORT", "Allow grid power when solar charging (sum of phase)",0, 48, IMPORT_CURRENT}, - {"MAINS MET","Type of mains electric meter", 0, EM_CUSTOM, MAINS_METER}, - {"MAINS ADR","Address of mains electric meter", MIN_METER_ADDRESS, MAX_METER_ADDRESS, MAINS_METER_ADDRESS}, - {"BYTE ORD","Byte order of custom electric meter", 0, 3, EMCUSTOM_ENDIANESS}, - {"DATA TYPE","Data type of custom electric meter", 0, MB_DATATYPE_MAX - 1, EMCUSTOM_DATATYPE}, - {"FUNCTION","Modbus Function of custom electric meter", 3, 4, EMCUSTOM_FUNCTION}, - {"VOL REGI","Register for Voltage (V) of custom electric meter", 0, 65530, EMCUSTOM_UREGISTER}, - {"VOL DIVI","Divisor for Voltage (V) of custom electric meter", 0, 7, EMCUSTOM_UDIVISOR}, - {"CUR REGI","Register for Current (A) of custom electric meter", 0, 65530, EMCUSTOM_IREGISTER}, - {"CUR DIVI","Divisor for Current (A) of custom electric meter", 0, 7, EMCUSTOM_IDIVISOR}, - {"POW REGI","Register for Power (W) of custom electric meter", 0, 65534, EMCUSTOM_PREGISTER}, - {"POW DIVI","Divisor for Power (W) of custom electric meter", 0, 7, EMCUSTOM_PDIVISOR}, - {"ENE REGI","Register for Energy (kWh) of custom electric meter", 0, 65534, EMCUSTOM_EREGISTER}, - {"ENE DIVI","Divisor for Energy (kWh) of custom electric meter", 0, 7, EMCUSTOM_EDIVISOR}, - {"READ MAX","Max register read at once of custom electric meter", 3, 255, 3}, - {"WIFI", "Connect to WiFi access point", 0, 2, WIFI_MODE}, - {"CONTACT 2","Contactor2 (C2) behaviour", 0, sizeof(StrEnableC2) / sizeof(StrEnableC2[0])-1, ENABLE_C2}, - {"MAX TEMP","Maximum temperature for the EVSE module", 40, 75, MAX_TEMPERATURE}, - {"SUM MAINS","Capacity Rate limit on sum of MAINS Current (A)", 10, 600, MAX_SUMMAINS}, - {"", "Hold 2 sec to stop charging", 0, 0, 0}, - {"", "Hold 2 sec to start charging", 0, 0, 0}, - - {"EXIT", "EXIT", 0, 0, 0} -}; - - -struct EMstruct { - uint8_t Desc[10]; - uint8_t Endianness; // 0: low byte first, low word first, 1: low byte first, high word first, 2: high byte first, low word first, 3: high byte first, high word first - uint8_t Function; // 3: holding registers, 4: input registers - MBDataType DataType; // How data is represented on this Modbus meter - uint16_t URegister; // Single phase voltage (V) - int8_t UDivisor; // 10^x - uint16_t IRegister; // Single phase current (A) - int8_t IDivisor; // 10^x - uint16_t PRegister; // Total power (W) -- only used for EV/PV meter momentary power - int8_t PDivisor; // 10^x - uint16_t ERegister; // Total imported energy (kWh); equals total energy if meter doesnt support exported energy - int8_t EDivisor; // 10^x - uint16_t ERegister_Exp; // Total exported energy (kWh) - int8_t EDivisor_Exp; // 10^x -}; - -extern struct EMstruct EMConfig[EM_CUSTOM + 1]; - -struct DelayedTimeStruct { - uint32_t epoch2; // in case of Delayed Charging the StartTime in epoch2; if zero we are NOT Delayed Charging - // epoch2 is the number of seconds since 1/1/2023 00:00 UTC, which equals epoch 1672531200 - // we avoid using epoch so we don't need expensive 64bits arithmetics with difftime - // and we can store dates until 7/2/2159 - int32_t diff; // StartTime minus current time in seconds -}; - -#define EPOCH2_OFFSET 1672531200 - -extern struct DelayedTimeStruct DelayedStartTime; - -void CheckAPpassword(void); -void read_settings(); -void write_settings(void); -void setSolarStopTimer(uint16_t Timer); -void setState(uint8_t NewState); -void setAccess(bool Access); -void SetCPDuty(uint32_t DutyCycle); -uint8_t setItemValue(uint8_t nav, uint16_t val); -uint16_t getItemValue(uint8_t nav); -void ConfigureModbusMode(uint8_t newmode); - -void handleWIFImode(void); - -#endif diff --git a/SmartEVSE-3/SmartEVSE-3/include/glcd.h b/SmartEVSE-3/SmartEVSE-3/include/glcd.h deleted file mode 100644 index 861db85..0000000 --- a/SmartEVSE-3/SmartEVSE-3/include/glcd.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -; Project: Smart EVSE -; -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. -*/ - -#ifndef __GLCD_H -#define __GLCD_H - -#define GLCD_MERGE 0b00001000 -#define GLCD_HIRES_FONT -#define GLCD_FULL_CHARSET -#define GLCD_ALIGN_LEFT 0 -#define GLCD_ALIGN_CENTER 1 -#define GLCD_ALIGN_RIGHT 2 - -extern void GLCDHelp(void); -extern void GLCD(void); -extern void GLCDMenu(unsigned char Buttons); -extern void GLCD_init(void); -extern void GLCD_version(void); - - -#endif // #ifndef __GLCD_H \ No newline at end of file diff --git a/SmartEVSE-3/SmartEVSE-3/include/modbus.h b/SmartEVSE-3/SmartEVSE-3/include/modbus.h deleted file mode 100644 index 15f64ba..0000000 --- a/SmartEVSE-3/SmartEVSE-3/include/modbus.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -; Project: Smart EVSE -; -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. -*/ - -#ifndef __EVSE_MODBUS -#define __EVSE_MODBUS - -struct ModBus { - uint8_t Address; - uint8_t Function; - uint16_t Register; - uint16_t RegisterCount; - uint16_t Value; - uint8_t *Data; - uint8_t DataLength; - uint8_t Type; - uint8_t RequestAddress; - uint8_t RequestFunction; - uint16_t RequestRegister; - uint8_t Exception; -}; - -// definition of MBserver / MBclient class is done in evse.cpp -extern ModbusServerRTU MBserver; -extern ModbusClientRTU MBclient; - -void RS485SendBuf(uint8_t *buffer, uint8_t len); -uint8_t mapModbusRegister2ItemID(); - -// ########################### Modbus main functions ########################### - -void ModbusReadInputRequest(uint8_t address, uint8_t function, uint16_t reg, uint16_t quantity); -void ModbusReadInputResponse(uint8_t address, uint8_t function, uint16_t *values, uint8_t count); -void ModbusWriteSingleRequest(uint8_t address, uint16_t reg, uint16_t value); -void ModbusWriteMultipleRequest(uint8_t address, uint16_t reg, uint16_t *values, uint8_t count); -void ModbusException(uint8_t address, uint8_t function, uint8_t exception); -void ModbusDecode(uint8_t *buf, uint8_t len); - -// ########################### EVSE modbus functions ########################### - -signed int receiveMeasurement(uint8_t *buf, uint8_t pos, uint8_t Endianness, MBDataType dataType, signed char Divisor); -void requestMeasurement(uint8_t Meter, uint8_t Address, uint16_t Register, uint8_t Count); -void requestCurrentMeasurement(uint8_t Meter, uint8_t Address); -uint8_t receiveCurrentMeasurement(uint8_t *buf, uint8_t Meter, signed int *var); - -//void ReadItemValueResponse(void); -//void WriteItemValueResponse(void); -//void WriteMultipleItemValueResponse(void); - - -#endif diff --git a/SmartEVSE-3/SmartEVSE-3/include/mongoose.h b/SmartEVSE-3/SmartEVSE-3/include/mongoose.h deleted file mode 100644 index 9ae5071..0000000 --- a/SmartEVSE-3/SmartEVSE-3/include/mongoose.h +++ /dev/null @@ -1,2930 +0,0 @@ -// Copyright (c) 2004-2013 Sergey Lyubka -// Copyright (c) 2013-2024 Cesanta Software Limited -// All rights reserved -// -// This software is dual-licensed: you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 2 as -// published by the Free Software Foundation. For the terms of this -// license, see http://www.gnu.org/licenses/ -// -// You are free to use this software under the terms of the GNU General -// Public License, but WITHOUT ANY WARRANTY; without even the implied -// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// Alternatively, you can license this software under a commercial -// license, as set out in https://www.mongoose.ws/licensing/ -// -// SPDX-License-Identifier: GPL-2.0-only or commercial - -#ifndef MONGOOSE_H -#define MONGOOSE_H - -#define MG_VERSION "7.13" - -#ifdef __cplusplus -extern "C" { -#endif - - -#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h -#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ... -#define MG_ARCH_WIN32 2 // Windows -#define MG_ARCH_ESP32 3 // ESP32 -#define MG_ARCH_ESP8266 4 // ESP8266 -#define MG_ARCH_FREERTOS 5 // FreeRTOS -#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS -#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS -#define MG_ARCH_NEWLIB 8 // Bare metal ARM -#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX) -#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS -#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040 -#define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard -#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS) -#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS - -#if !defined(MG_ARCH) -#if defined(__unix__) || defined(__APPLE__) -#define MG_ARCH MG_ARCH_UNIX -#elif defined(_WIN32) -#define MG_ARCH MG_ARCH_WIN32 -#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR) -#define MG_ARCH MG_ARCH_ESP8266 -#elif defined(__ZEPHYR__) -#define MG_ARCH MG_ARCH_ZEPHYR -#elif defined(ESP_PLATFORM) -#define MG_ARCH MG_ARCH_ESP32 -#elif defined(FREERTOS_IP_H) -#define MG_ARCH MG_ARCH_FREERTOS -#define MG_ENABLE_FREERTOS_TCP 1 -#elif defined(AZURE_RTOS_THREADX) -#define MG_ARCH MG_ARCH_AZURERTOS -#elif defined(PICO_TARGET_NAME) -#define MG_ARCH MG_ARCH_RP2040 -#elif defined(__RTTHREAD__) -#define MG_ARCH MG_ARCH_RTTHREAD -#endif -#endif // !defined(MG_ARCH) - -#if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM) -#include "mongoose_custom.h" // keep this include -#endif - -#if !defined(MG_ARCH) -#error "MG_ARCH is not specified and we couldn't guess it. Set -D MG_ARCH=..." -#endif - -// http://esr.ibiblio.org/?p=5095 -#define MG_BIG_ENDIAN (*(uint16_t *) "\0\xff" < 0x100) - - - - - - - - - - - - - - - -#if MG_ARCH == MG_ARCH_AZURERTOS - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#define PATH_MAX FX_MAXIMUM_PATH -#define MG_DIRSEP '\\' - -#define socklen_t int -#define closesocket(x) soc_close(x) - -#undef FOPEN_MAX - -#endif - - -#if MG_ARCH == MG_ARCH_ESP32 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define MG_PATH_MAX 128 - -#endif - - -#if MG_ARCH == MG_ARCH_ESP8266 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define MG_PATH_MAX 128 - -#endif - - -#if MG_ARCH == MG_ARCH_FREERTOS - -#include -#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP -#include -#endif -#include -#include -#include -#include -#include -#include // rand(), strtol(), atoi() -#include -#if defined(__ARMCC_VERSION) -#define mode_t size_t -#include -#include -#else -#include -#endif - -#include -#include - -#ifndef MG_IO_SIZE -#define MG_IO_SIZE 512 -#endif - -#define calloc(a, b) mg_calloc(a, b) -#define free(a) vPortFree(a) -#define malloc(a) pvPortMalloc(a) -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr) - -// Re-route calloc/free to the FreeRTOS's functions, don't use stdlib -static inline void *mg_calloc(size_t cnt, size_t size) { - void *p = pvPortMalloc(cnt * size); - if (p != NULL) memset(p, 0, size * cnt); - return p; -} - -#define mkdir(a, b) mg_mkdir(a, b) -static inline int mg_mkdir(const char *path, mode_t mode) { - (void) path, (void) mode; - return -1; -} - -#endif // MG_ARCH == MG_ARCH_FREERTOS - - -#if MG_ARCH == MG_ARCH_NEWLIB -#define _POSIX_TIMERS - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MG_PATH_MAX 100 -#define MG_ENABLE_SOCKET 0 -#define MG_ENABLE_DIRLIST 0 - -#endif - - -#if MG_ARCH == MG_ARCH_RP2040 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -int mkdir(const char *, mode_t); -#endif - - -#if MG_ARCH == MG_ARCH_RTTHREAD - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef MG_IO_SIZE -#define MG_IO_SIZE 1460 -#endif - -#endif // MG_ARCH == MG_ARCH_RTTHREAD - - -#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \ - MG_ARCH == MG_ARCH_CMSIS_RTOS2 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if MG_ARCH == MG_ARCH_CMSIS_RTOS1 -#include "cmsis_os.h" // keep this include -// https://developer.arm.com/documentation/ka003821/latest -extern uint32_t rt_time_get(void); -#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2 -#include "cmsis_os2.h" // keep this include -#endif - -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr) - -#if defined(__ARMCC_VERSION) -#define mode_t size_t -#define mkdir(a, b) mg_mkdir(a, b) -static inline int mg_mkdir(const char *path, mode_t mode) { - (void) path, (void) mode; - return -1; -} -#endif - -#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH == MG_ARCH_CMSIS_RTOS2) && \ - !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \ - (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP) -#define MG_ENABLE_RL 1 -#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE -#define MG_SOCK_LISTEN_BACKLOG_SIZE 3 -#endif -#endif - -#endif - - -#if MG_ARCH == MG_ARCH_TIRTOS - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#endif - - -#if MG_ARCH == MG_ARCH_UNIX - -#define _DARWIN_UNLIMITED_SELECT 1 // No limit on file descriptors - -#if defined(__APPLE__) -#include -#endif - -#if !defined(MG_ENABLE_EPOLL) && defined(__linux__) -#define MG_ENABLE_EPOLL 1 -#elif !defined(MG_ENABLE_POLL) -#define MG_ENABLE_POLL 1 -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(MG_ENABLE_EPOLL) && MG_ENABLE_EPOLL -#include -#elif defined(MG_ENABLE_POLL) && MG_ENABLE_POLL -#include -#else -#include -#endif - -#include -#include -#include -#include -#include -#include - -#ifndef MG_ENABLE_DIRLIST -#define MG_ENABLE_DIRLIST 1 -#endif - -#ifndef MG_PATH_MAX -#define MG_PATH_MAX FILENAME_MAX -#endif - -#endif - - -#if MG_ARCH == MG_ARCH_WIN32 - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#endif - -#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS -#define _WINSOCK_DEPRECATED_NO_WARNINGS -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) && _MSC_VER < 1700 -#define __func__ "" -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; -typedef unsigned char uint8_t; -typedef char int8_t; -typedef unsigned short uint16_t; -typedef short int16_t; -typedef unsigned int uint32_t; -typedef int int32_t; -typedef enum { false = 0, true = 1 } bool; -#else -#include -#include -#include -#endif - -#include -#include -#include - -// Protect from calls like std::snprintf in app code -// See https://github.com/cesanta/mongoose/issues/1047 -#ifndef __cplusplus -#define snprintf _snprintf -#define vsnprintf _vsnprintf -#ifndef strdup // For MSVC with _DEBUG, see #1359 -#define strdup(x) _strdup(x) -#endif -#endif - -#define MG_INVALID_SOCKET INVALID_SOCKET -#define MG_SOCKET_TYPE SOCKET -typedef unsigned long nfds_t; -#if defined(_MSC_VER) -#pragma comment(lib, "ws2_32.lib") -#ifndef alloca -#define alloca(a) _alloca(a) -#endif -#endif -#define poll(a, b, c) WSAPoll((a), (b), (c)) -#define closesocket(x) closesocket(x) - -typedef int socklen_t; -#define MG_DIRSEP '\\' - -#ifndef MG_PATH_MAX -#define MG_PATH_MAX FILENAME_MAX -#endif - -#ifndef SO_EXCLUSIVEADDRUSE -#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR)) -#endif - -#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError() : 0) - -#define MG_SOCK_PENDING(errcode) \ - (((errcode) < 0) && \ - (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || \ - WSAGetLastError() == WSAEWOULDBLOCK)) - -#define MG_SOCK_RESET(errcode) \ - (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET)) - -#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) -#define sleep(x) Sleep((x) *1000) -#define mkdir(a, b) _mkdir(a) -#define timegm(x) _mkgmtime(x) - -#ifndef S_ISDIR -#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR) -#endif - -#ifndef MG_ENABLE_DIRLIST -#define MG_ENABLE_DIRLIST 1 -#endif - -#ifndef SIGPIPE -#define SIGPIPE 0 -#endif - -#endif - - -#if MG_ARCH == MG_ARCH_ZEPHYR - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MG_PUTCHAR(x) printk("%c", x) -#ifndef strdup -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr) -#endif -#define strerror(x) zsock_gai_strerror(x) - -#ifndef FD_CLOEXEC -#define FD_CLOEXEC 0 -#endif - -#ifndef F_SETFD -#define F_SETFD 0 -#endif - -#define MG_ENABLE_SSI 0 - -int rand(void); -int sscanf(const char *, const char *, ...); - -#endif - - -#if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP - -#include -#include - -#include -#include -#include // contents to be moved and file removed, some day - -#define MG_SOCKET_TYPE Socket_t -#define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET - -// Why FreeRTOS-TCP did not implement a clean BSD API, but its own thing -// with FreeRTOS_ prefix, is beyond me -#define IPPROTO_TCP FREERTOS_IPPROTO_TCP -#define IPPROTO_UDP FREERTOS_IPPROTO_UDP -#define AF_INET FREERTOS_AF_INET -#define SOCK_STREAM FREERTOS_SOCK_STREAM -#define SOCK_DGRAM FREERTOS_SOCK_DGRAM -#define SO_BROADCAST 0 -#define SO_ERROR 0 -#define SOL_SOCKET 0 -#define SO_REUSEADDR 0 - -#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0) - -#define MG_SOCK_PENDING(errcode) \ - ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \ - (errcode) == -pdFREERTOS_ERRNO_EISCONN || \ - (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \ - (errcode) == -pdFREERTOS_ERRNO_EAGAIN) - -#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN) - -// actually only if optional timeout is enabled -#define MG_SOCK_INTR(fd) (fd == NULL) - -#define sockaddr_in freertos_sockaddr -#define sockaddr freertos_sockaddr -#define accept(a, b, c) FreeRTOS_accept((a), (b), (c)) -#define connect(a, b, c) FreeRTOS_connect((a), (b), (c)) -#define bind(a, b, c) FreeRTOS_bind((a), (b), (c)) -#define listen(a, b) FreeRTOS_listen((a), (b)) -#define socket(a, b, c) FreeRTOS_socket((a), (b), (c)) -#define send(a, b, c, d) FreeRTOS_send((a), (b), (c), (d)) -#define recv(a, b, c, d) FreeRTOS_recv((a), (b), (c), (d)) -#define setsockopt(a, b, c, d, e) FreeRTOS_setsockopt((a), (b), (c), (d), (e)) -#define sendto(a, b, c, d, e, f) FreeRTOS_sendto((a), (b), (c), (d), (e), (f)) -#define recvfrom(a, b, c, d, e, f) \ - FreeRTOS_recvfrom((a), (b), (c), (d), (e), (f)) -#define closesocket(x) FreeRTOS_closesocket(x) -#define gethostbyname(x) FreeRTOS_gethostbyname(x) -#define getsockname(a, b, c) mg_getsockname((a), (b), (c)) -#define getpeername(a, b, c) mg_getpeername((a), (b), (c)) - -static inline int mg_getsockname(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) { - (void) fd, (void) buf, (void) len; - return -1; -} - -static inline int mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) { - (void) fd, (void) buf, (void) len; - return 0; -} -#endif - - -#if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP - -#if defined(__GNUC__) && !defined(__ARMCC_VERSION) -#include -#endif - -struct timeval; - -#include - -#if !LWIP_TIMEVAL_PRIVATE -#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang sets both -#include -#else -struct timeval { - time_t tv_sec; - long tv_usec; -}; -#endif -#endif - -#if LWIP_SOCKET != 1 -// Sockets support disabled in LWIP by default -#error Set LWIP_SOCKET variable to 1 (in lwipopts.h) -#endif -#endif - - -#if defined(MG_ENABLE_RL) && MG_ENABLE_RL -#include - -#define closesocket(x) closesocket(x) - -#define TCP_NODELAY SO_KEEPALIVE - -#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0) - -#define MG_SOCK_PENDING(errcode) \ - ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \ - (errcode) == BSD_EINPROGRESS) - -#define MG_SOCK_RESET(errcode) \ - ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET) - -// In blocking mode, which is enabled by default, accept() waits for a -// connection request. In non blocking mode, you must call accept() -// again if the error code BSD_EWOULDBLOCK is returned. -#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK) - -#define socklen_t int -#endif - - -#ifndef MG_ENABLE_LOG -#define MG_ENABLE_LOG 1 -#endif - -#ifndef MG_ENABLE_CUSTOM_LOG -#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG -#endif - -#ifndef MG_ENABLE_TCPIP -#define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack -#endif - -#ifndef MG_ENABLE_LWIP -#define MG_ENABLE_LWIP 0 // lWIP network stack -#endif - -#ifndef MG_ENABLE_FREERTOS_TCP -#define MG_ENABLE_FREERTOS_TCP 0 // Amazon FreeRTOS-TCP network stack -#endif - -#ifndef MG_ENABLE_RL -#define MG_ENABLE_RL 0 // ARM MDK network stack -#endif - -#ifndef MG_ENABLE_SOCKET -#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP -#endif - -#ifndef MG_ENABLE_POLL -#define MG_ENABLE_POLL 0 -#endif - -#ifndef MG_ENABLE_EPOLL -#define MG_ENABLE_EPOLL 0 -#endif - -#ifndef MG_ENABLE_FATFS -#define MG_ENABLE_FATFS 0 -#endif - -#ifndef MG_ENABLE_SSI -#define MG_ENABLE_SSI 0 -#endif - -#ifndef MG_ENABLE_IPV6 -#define MG_ENABLE_IPV6 0 -#endif - -#ifndef MG_IPV6_V6ONLY -#define MG_IPV6_V6ONLY 0 // IPv6 socket binds only to V6, not V4 address -#endif - -#ifndef MG_ENABLE_MD5 -#define MG_ENABLE_MD5 1 -#endif - -// Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP) -#ifndef MG_ENABLE_WINSOCK -#define MG_ENABLE_WINSOCK 1 -#endif - -#ifndef MG_ENABLE_DIRLIST -#define MG_ENABLE_DIRLIST 0 -#endif - -#ifndef MG_ENABLE_CUSTOM_RANDOM -#define MG_ENABLE_CUSTOM_RANDOM 0 -#endif - -#ifndef MG_ENABLE_CUSTOM_MILLIS -#define MG_ENABLE_CUSTOM_MILLIS 0 -#endif - -#ifndef MG_ENABLE_PACKED_FS -#define MG_ENABLE_PACKED_FS 0 -#endif - -#ifndef MG_ENABLE_ASSERT -#define MG_ENABLE_ASSERT 0 -#endif - -#ifndef MG_IO_SIZE -#define MG_IO_SIZE 2048 // Granularity of the send/recv IO buffer growth -#endif - -#ifndef MG_MAX_RECV_SIZE -#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL) // Maximum recv IO buffer size -#endif - -#ifndef MG_DATA_SIZE -#define MG_DATA_SIZE 32 // struct mg_connection :: data size -#endif - -#ifndef MG_MAX_HTTP_HEADERS -#define MG_MAX_HTTP_HEADERS 30 -#endif - -#ifndef MG_HTTP_INDEX -#define MG_HTTP_INDEX "index.html" -#endif - -#ifndef MG_PATH_MAX -#ifdef PATH_MAX -#define MG_PATH_MAX PATH_MAX -#else -#define MG_PATH_MAX 128 -#endif -#endif - -#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE -#define MG_SOCK_LISTEN_BACKLOG_SIZE 128 -#endif - -#ifndef MG_DIRSEP -#define MG_DIRSEP '/' -#endif - -#ifndef MG_ENABLE_POSIX_FS -#if defined(FOPEN_MAX) -#define MG_ENABLE_POSIX_FS 1 -#else -#define MG_ENABLE_POSIX_FS 0 -#endif -#endif - -#ifndef MG_INVALID_SOCKET -#define MG_INVALID_SOCKET (-1) -#endif - -#ifndef MG_SOCKET_TYPE -#define MG_SOCKET_TYPE int -#endif - -#ifndef MG_SOCKET_ERRNO -#define MG_SOCKET_ERRNO errno -#endif - -#if MG_ENABLE_EPOLL -#define MG_EPOLL_ADD(c) \ - do { \ - struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}}; \ - epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_ADD, (int) (size_t) c->fd, &ev); \ - } while (0) -#define MG_EPOLL_MOD(c, wr) \ - do { \ - struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}}; \ - if (wr) ev.events |= EPOLLOUT; \ - epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_MOD, (int) (size_t) c->fd, &ev); \ - } while (0) -#else -#define MG_EPOLL_ADD(c) -#define MG_EPOLL_MOD(c, wr) -#endif - -#ifndef MG_ENABLE_PROFILE -#define MG_ENABLE_PROFILE 0 -#endif - - - - -struct mg_str { - const char *ptr; // Pointer to string data - size_t len; // String len -}; - -#define MG_C_STR(a) \ - { (a), sizeof(a) - 1 } - -// Using macro to avoid shadowing C++ struct constructor, see #1298 -#define mg_str(s) mg_str_s(s) - -struct mg_str mg_str(const char *s); -struct mg_str mg_str_n(const char *s, size_t n); -int mg_lower(const char *s); -int mg_ncasecmp(const char *s1, const char *s2, size_t len); -int mg_casecmp(const char *s1, const char *s2); -int mg_vcmp(const struct mg_str *s1, const char *s2); -int mg_vcasecmp(const struct mg_str *str1, const char *str2); -int mg_strcmp(const struct mg_str str1, const struct mg_str str2); -struct mg_str mg_strstrip(struct mg_str s); -struct mg_str mg_strdup(const struct mg_str s); -const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); -bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps); -bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n); -bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char delim); -char *mg_hex(const void *buf, size_t len, char *dst); -void mg_unhex(const char *buf, size_t len, unsigned char *to); -unsigned long mg_unhexn(const char *s, size_t len); -bool mg_path_is_sane(const char *path); - - - - -// Single producer, single consumer non-blocking queue - -struct mg_queue { - char *buf; - size_t size; - volatile size_t tail; - volatile size_t head; -}; - -void mg_queue_init(struct mg_queue *, char *, size_t); // Init queue -size_t mg_queue_book(struct mg_queue *, char **buf, size_t); // Reserve space -void mg_queue_add(struct mg_queue *, size_t); // Add new message -size_t mg_queue_next(struct mg_queue *, char **); // Get oldest message -void mg_queue_del(struct mg_queue *, size_t); // Delete oldest message - - - - -typedef void (*mg_pfn_t)(char, void *); // Output function -typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *); // %M printer - -size_t mg_vxprintf(void (*)(char, void *), void *, const char *fmt, va_list *); -size_t mg_xprintf(void (*fn)(char, void *), void *, const char *fmt, ...); - - - - - - -// Convenience wrappers around mg_xprintf -size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap); -size_t mg_snprintf(char *, size_t, const char *fmt, ...); -char *mg_vmprintf(const char *fmt, va_list *ap); -char *mg_mprintf(const char *fmt, ...); -size_t mg_queue_vprintf(struct mg_queue *, const char *fmt, va_list *); -size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...); - -// %M print helper functions -size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap); -size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap); -size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap); -size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap); -size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap); -size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap); -size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap); -size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap); - -// Various output functions -void mg_pfn_iobuf(char ch, void *param); // param: struct mg_iobuf * -void mg_pfn_stdout(char c, void *param); // param: ignored - -// A helper macro for printing JSON: mg_snprintf(buf, len, "%m", MG_ESC("hi")) -#define MG_ESC(str) mg_print_esc, 0, (str) - - - - - - -enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE }; -extern int mg_log_level; // Current log level, one of MG_LL_* - -void mg_log(const char *fmt, ...); -void mg_log_prefix(int ll, const char *file, int line, const char *fname); -// bool mg_log2(int ll, const char *file, int line, const char *fmt, ...); -void mg_hexdump(const void *buf, size_t len); -void mg_log_set_fn(mg_pfn_t fn, void *param); - -#define mg_log_set(level_) mg_log_level = (level_) - -#if MG_ENABLE_LOG -#define MG_LOG(level, args) \ - do { \ - if ((level) <= mg_log_level) { \ - mg_log_prefix((level), __FILE__, __LINE__, __func__); \ - mg_log args; \ - } \ - } while (0) -#else -#define MG_LOG(level, args) \ - do { \ - if (0) mg_log args; \ - } while (0) -#endif - -#define MG_ERROR(args) MG_LOG(MG_LL_ERROR, args) -#define MG_INFO(args) MG_LOG(MG_LL_INFO, args) -#define MG_DEBUG(args) MG_LOG(MG_LL_DEBUG, args) -#define MG_VERBOSE(args) MG_LOG(MG_LL_VERBOSE, args) - - - - -struct mg_timer { - unsigned long id; // Timer ID - uint64_t period_ms; // Timer period in milliseconds - uint64_t expire; // Expiration timestamp in milliseconds - unsigned flags; // Possible flags values below -#define MG_TIMER_ONCE 0 // Call function once -#define MG_TIMER_REPEAT 1 // Call function periodically -#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set - void (*fn)(void *); // Function to call - void *arg; // Function argument - struct mg_timer *next; // Linkage -}; - -void mg_timer_init(struct mg_timer **head, struct mg_timer *timer, - uint64_t milliseconds, unsigned flags, void (*fn)(void *), - void *arg); -void mg_timer_free(struct mg_timer **head, struct mg_timer *); -void mg_timer_poll(struct mg_timer **head, uint64_t new_ms); -bool mg_timer_expired(uint64_t *expiration, uint64_t period, uint64_t now); - - - - - -enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 }; - -// Filesystem API functions -// st() returns MG_FS_* flags and populates file size and modification time -// ls() calls fn() for every directory entry, allowing to list a directory -// -// NOTE: UNIX-style shorthand names for the API functions are deliberately -// chosen to avoid conflicts with some libraries that make macros for e.g. -// stat(), write(), read() calls. -struct mg_fs { - int (*st)(const char *path, size_t *size, time_t *mtime); // stat file - void (*ls)(const char *path, void (*fn)(const char *, void *), - void *); // List directory entries: call fn(file_name, fn_data) - // for each directory entry - void *(*op)(const char *path, int flags); // Open file - void (*cl)(void *fd); // Close file - size_t (*rd)(void *fd, void *buf, size_t len); // Read file - size_t (*wr)(void *fd, const void *buf, size_t len); // Write file - size_t (*sk)(void *fd, size_t offset); // Set file position - bool (*mv)(const char *from, const char *to); // Rename file - bool (*rm)(const char *path); // Delete file - bool (*mkd)(const char *path); // Create directory -}; - -extern struct mg_fs mg_fs_posix; // POSIX open/close/read/write/seek -extern struct mg_fs mg_fs_packed; // Packed FS, see examples/device-dashboard -extern struct mg_fs mg_fs_fat; // FAT FS - -// File descriptor -struct mg_fd { - void *fd; - struct mg_fs *fs; -}; - -struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags); -void mg_fs_close(struct mg_fd *fd); -bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len); -struct mg_str mg_file_read(struct mg_fs *fs, const char *path); -bool mg_file_write(struct mg_fs *fs, const char *path, const void *, size_t); -bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...); - -// Packed API -const char *mg_unpack(const char *path, size_t *size, time_t *mtime); -const char *mg_unlist(size_t no); // Get no'th packed filename -struct mg_str mg_unpacked(const char *path); // Packed file as mg_str - - - - - - - -#if MG_ENABLE_ASSERT -#include -#elif !defined(assert) -#define assert(x) -#endif - -void mg_bzero(volatile unsigned char *buf, size_t len); -void mg_random(void *buf, size_t len); -char *mg_random_str(char *buf, size_t len); -uint16_t mg_ntohs(uint16_t net); -uint32_t mg_ntohl(uint32_t net); -uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); -uint64_t mg_millis(void); // Return milliseconds since boot -uint64_t mg_now(void); // Return milliseconds since Epoch - -#define mg_htons(x) mg_ntohs(x) -#define mg_htonl(x) mg_ntohl(x) - -#define MG_U32(a, b, c, d) \ - (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \ - ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255)) - -// For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip)) -#define MG_U8P(ADDR) ((uint8_t *) (ADDR)) -#define MG_IPADDR_PARTS(ADDR) \ - MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3] - -#define MG_REG(x) ((volatile uint32_t *) (x))[0] -#define MG_BIT(x) (((uint32_t) 1U) << (x)) -#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | (SETMASK) - -#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) / (a)) * (a))) -#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a))) - -#ifdef __GNUC__ -#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory") -#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory") -#else -#define MG_ARM_DISABLE_IRQ() -#define MG_ARM_ENABLE_IRQ() -#endif - -#if defined(__CC_ARM) -#define MG_DSB() __dsb(0xf) -#elif defined(__ARMCC_VERSION) -#define MG_DSB() __builtin_arm_dsb(0xf) -#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__) -#define MG_DSB() asm("DSB 0xf") -#elif defined(__ICCARM__) -#define MG_DSB() __iar_builtin_DSB() -#else -#define MG_DSB() -#endif - -struct mg_addr; -int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip); - -// Linked list management macros -#define LIST_ADD_HEAD(type_, head_, elem_) \ - do { \ - (elem_)->next = (*head_); \ - *(head_) = (elem_); \ - } while (0) - -#define LIST_ADD_TAIL(type_, head_, elem_) \ - do { \ - type_ **h = head_; \ - while (*h != NULL) h = &(*h)->next; \ - *h = (elem_); \ - } while (0) - -#define LIST_DELETE(type_, head_, elem_) \ - do { \ - type_ **h = head_; \ - while (*h != (elem_)) h = &(*h)->next; \ - *h = (elem_)->next; \ - } while (0) - - - -unsigned short mg_url_port(const char *url); -int mg_url_is_ssl(const char *url); -struct mg_str mg_url_host(const char *url); -struct mg_str mg_url_user(const char *url); -struct mg_str mg_url_pass(const char *url); -const char *mg_url_uri(const char *url); - - - - -struct mg_iobuf { - unsigned char *buf; // Pointer to stored data - size_t size; // Total size available - size_t len; // Current number of bytes - size_t align; // Alignment during allocation -}; - -int mg_iobuf_init(struct mg_iobuf *, size_t, size_t); -int mg_iobuf_resize(struct mg_iobuf *, size_t); -void mg_iobuf_free(struct mg_iobuf *); -size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t); -size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len); - - -size_t mg_base64_update(unsigned char input_byte, char *buf, size_t len); -size_t mg_base64_final(char *buf, size_t len); -size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, size_t); -size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t); - - - - -typedef struct { - uint32_t buf[4]; - uint32_t bits[2]; - unsigned char in[64]; -} mg_md5_ctx; - -void mg_md5_init(mg_md5_ctx *c); -void mg_md5_update(mg_md5_ctx *c, const unsigned char *data, size_t len); -void mg_md5_final(mg_md5_ctx *c, unsigned char[16]); - - - - -typedef struct { - uint32_t state[5]; - uint32_t count[2]; - unsigned char buffer[64]; -} mg_sha1_ctx; - -void mg_sha1_init(mg_sha1_ctx *); -void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len); -void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *); - - - - -typedef struct { - uint32_t state[8]; - uint64_t bits; - uint32_t len; - unsigned char buffer[64]; -} mg_sha256_ctx; - -void mg_sha256_init(mg_sha256_ctx *); -void mg_sha256_update(mg_sha256_ctx *, const unsigned char *data, size_t len); -void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *); -void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, - size_t datasz); -/****************************************************************************** - * - * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL - * - * This is a simple and straightforward implementation of the AES Rijndael - * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus - * of this work was correctness & accuracy. It is written in 'C' without any - * particular focus upon optimization or speed. It should be endian (memory - * byte order) neutral since the few places that care are handled explicitly. - * - * This implementation of Rijndael was created by Steven M. Gibson of GRC.com. - * - * It is intended for general purpose use, but was written in support of GRC's - * reference implementation of the SQRL (Secure Quick Reliable Login) client. - * - * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html - * - * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE - * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. - * - *******************************************************************************/ - -#ifndef AES_HEADER -#define AES_HEADER - -/******************************************************************************/ -#define AES_DECRYPTION 1 // whether AES decryption is supported -/******************************************************************************/ - -#define ENCRYPT 1 // specify whether we're encrypting -#define DECRYPT 0 // or decrypting - - - -typedef unsigned char uchar; // add some convienent shorter types -typedef unsigned int uint; - -/****************************************************************************** - * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use - ******************************************************************************/ -void aes_init_keygen_tables(void); - -/****************************************************************************** - * AES_CONTEXT : cipher context / holds inter-call data - ******************************************************************************/ -typedef struct { - int mode; // 1 for Encryption, 0 for Decryption - int rounds; // keysize-based rounds count - uint32_t *rk; // pointer to current round key - uint32_t buf[68]; // key expansion buffer -} aes_context; - -/****************************************************************************** - * AES_SETKEY : called to expand the key for encryption or decryption - ******************************************************************************/ -int aes_setkey(aes_context *ctx, // pointer to context - int mode, // 1 or 0 for Encrypt/Decrypt - const uchar *key, // AES input key - uint keysize); // size in bytes (must be 16, 24, 32 for - // 128, 192 or 256-bit keys respectively) - // returns 0 for success - -/****************************************************************************** - * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data - ******************************************************************************/ -int aes_cipher(aes_context *ctx, // pointer to context - const uchar input[16], // 128-bit block to en/decipher - uchar output[16]); // 128-bit output result block - // returns 0 for success - -#endif /* AES_HEADER */ -/****************************************************************************** - * - * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL - * - * This is a simple and straightforward implementation of AES-GCM authenticated - * encryption. The focus of this work was correctness & accuracy. It is written - * in straight 'C' without any particular focus upon optimization or speed. It - * should be endian (memory byte order) neutral since the few places that care - * are handled explicitly. - * - * This implementation of AES-GCM was created by Steven M. Gibson of GRC.com. - * - * It is intended for general purpose use, but was written in support of GRC's - * reference implementation of the SQRL (Secure Quick Reliable Login) client. - * - * See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf - * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \ - * gcm/gcm-revised-spec.pdf - * - * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE - * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. - * - *******************************************************************************/ -#ifndef GCM_HEADER -#define GCM_HEADER - - -#define GCM_AUTH_FAILURE 0x55555555 // authentication failure - -/****************************************************************************** - * GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx - ******************************************************************************/ -typedef struct { - int mode; // cipher direction: encrypt/decrypt - uint64_t len; // cipher data length processed so far - uint64_t add_len; // total add data length - uint64_t HL[16]; // precalculated lo-half HTable - uint64_t HH[16]; // precalculated hi-half HTable - uchar base_ectr[16]; // first counter-mode cipher output for tag - uchar y[16]; // the current cipher-input IV|Counter value - uchar buf[16]; // buf working value - aes_context aes_ctx; // cipher context used -} gcm_context; - -/****************************************************************************** - * GCM_CONTEXT : MUST be called once before ANY use of this library - ******************************************************************************/ -int gcm_initialize(void); - -/****************************************************************************** - * GCM_SETKEY : sets the GCM (and AES) keying material for use - ******************************************************************************/ -int gcm_setkey(gcm_context *ctx, // caller-provided context ptr - const uchar *key, // pointer to cipher key - const uint keysize // size in bytes (must be 16, 24, 32 for - // 128, 192 or 256-bit keys respectively) -); // returns 0 for success - -/****************************************************************************** - * - * GCM_CRYPT_AND_TAG - * - * This either encrypts or decrypts the user-provided data and, either - * way, generates an authentication tag of the requested length. It must be - * called with a GCM context whose key has already been set with GCM_SETKEY. - * - * The user would typically call this explicitly to ENCRYPT a buffer of data - * and optional associated data, and produce its an authentication tag. - * - * To reverse the process the user would typically call the companion - * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided - * authentication tag. The GCM_AUTH_DECRYPT function calls this function - * to perform its decryption and tag generation, which it then compares. - * - ******************************************************************************/ -int gcm_crypt_and_tag( - gcm_context *ctx, // gcm context with key already setup - int mode, // cipher direction: ENCRYPT (1) or DECRYPT (0) - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - uchar *tag, // pointer to the tag to be generated - size_t tag_len); // byte length of the tag to be generated - -/****************************************************************************** - * - * GCM_AUTH_DECRYPT - * - * This DECRYPTS a user-provided data buffer with optional associated data. - * It then verifies a user-supplied authentication tag against the tag just - * re-created during decryption to verify that the data has not been altered. - * - * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption - * and authentication tag generation. - * - ******************************************************************************/ -int gcm_auth_decrypt( - gcm_context *ctx, // gcm context with key already setup - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - const uchar *tag, // pointer to the tag to be authenticated - size_t tag_len); // byte length of the tag <= 16 - -/****************************************************************************** - * - * GCM_START - * - * Given a user-provided GCM context, this initializes it, sets the encryption - * mode, and preprocesses the initialization vector and additional AEAD data. - * - ******************************************************************************/ -int gcm_start( - gcm_context *ctx, // pointer to user-provided GCM context - int mode, // ENCRYPT (1) or DECRYPT (0) - const uchar *iv, // pointer to initialization vector - size_t iv_len, // IV length in bytes (should == 12) - const uchar *add, // pointer to additional AEAD data (NULL if none) - size_t add_len); // length of additional AEAD data (bytes) - -/****************************************************************************** - * - * GCM_UPDATE - * - * This is called once or more to process bulk plaintext or ciphertext data. - * We give this some number of bytes of input and it returns the same number - * of output bytes. If called multiple times (which is fine) all but the final - * invocation MUST be called with length mod 16 == 0. (Only the final call can - * have a partial block length of < 128 bits.) - * - ******************************************************************************/ -int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context - size_t length, // length, in bytes, of data to process - const uchar *input, // pointer to source data - uchar *output); // pointer to destination data - -/****************************************************************************** - * - * GCM_FINISH - * - * This is called once after all calls to GCM_UPDATE to finalize the GCM. - * It performs the final GHASH to produce the resulting authentication TAG. - * - ******************************************************************************/ -int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM context - uchar *tag, // ptr to tag buffer - NULL if tag_len = 0 - size_t tag_len); // length, in bytes, of the tag-receiving buf - -/****************************************************************************** - * - * GCM_ZERO_CTX - * - * The GCM context contains both the GCM context and the AES context. - * This includes keying and key-related material which is security- - * sensitive, so it MUST be zeroed after use. This function does that. - * - ******************************************************************************/ -void gcm_zero_ctx(gcm_context *ctx); - -#endif /* GCM_HEADER */ -// -// aes-gcm.h -// MKo -// -// Created by Markus Kosmal on 20/11/14. -// -// - -#ifndef mko_aes_gcm_h -#define mko_aes_gcm_h - -int aes_gcm_encrypt(unsigned char *output, const unsigned char *input, - size_t input_length, const unsigned char *key, - const size_t key_len, const unsigned char *iv, - const size_t iv_len, unsigned char *aead, size_t aead_len, - unsigned char *tag, const size_t tag_len); - -int aes_gcm_decrypt(unsigned char *output, const unsigned char *input, - size_t input_length, const unsigned char *key, - const size_t key_len, const unsigned char *iv, - const size_t iv_len); - -#endif - -// End of aes128 PD - - - -#define uECC_SUPPORTS_secp256r1 1 -/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_H_ -#define _UECC_H_ - -/* Platform selection options. -If uECC_PLATFORM is not defined, the code will try to guess it based on compiler -macros. Possible values for uECC_PLATFORM are defined below: */ -#define uECC_arch_other 0 -#define uECC_x86 1 -#define uECC_x86_64 2 -#define uECC_arm 3 -#define uECC_arm_thumb 4 -#define uECC_arm_thumb2 5 -#define uECC_arm64 6 -#define uECC_avr 7 - -/* If desired, you can define uECC_WORD_SIZE as appropriate for your platform -(1, 4, or 8 bytes). If uECC_WORD_SIZE is not explicitly defined then it will be -automatically set based on your platform. */ - -/* Optimization level; trade speed for code size. - Larger values produce code that is faster but larger. - Currently supported values are 0 - 4; 0 is unusably slow for most - applications. Optimization level 4 currently only has an effect ARM platforms - where more than one curve is enabled. */ -#ifndef uECC_OPTIMIZATION_LEVEL -#define uECC_OPTIMIZATION_LEVEL 2 -#endif - -/* uECC_SQUARE_FUNC - If enabled (defined as nonzero), this will cause a -specific function to be used for (scalar) squaring instead of the generic -multiplication function. This can make things faster somewhat faster, but -increases the code size. */ -#ifndef uECC_SQUARE_FUNC -#define uECC_SQUARE_FUNC 0 -#endif - -/* uECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), this will -switch to native little-endian format for *all* arrays passed in and out of the -public API. This includes public and private keys, shared secrets, signatures -and message hashes. Using this switch reduces the amount of call stack memory -used by uECC, since less intermediate translations are required. Note that this -will *only* work on native little-endian processors and it will treat the -uint8_t arrays passed into the public API as word arrays, therefore requiring -the provided byte arrays to be word aligned on architectures that do not support -unaligned accesses. IMPORTANT: Keys and signatures generated with -uECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys and signatures -generated with uECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use the same -endianness. */ -#ifndef uECC_VLI_NATIVE_LITTLE_ENDIAN -#define uECC_VLI_NATIVE_LITTLE_ENDIAN 0 -#endif - -/* Curve support selection. Set to 0 to remove that curve. */ -#ifndef uECC_SUPPORTS_secp160r1 -#define uECC_SUPPORTS_secp160r1 0 -#endif -#ifndef uECC_SUPPORTS_secp192r1 -#define uECC_SUPPORTS_secp192r1 0 -#endif -#ifndef uECC_SUPPORTS_secp224r1 -#define uECC_SUPPORTS_secp224r1 0 -#endif -#ifndef uECC_SUPPORTS_secp256r1 -#define uECC_SUPPORTS_secp256r1 1 -#endif -#ifndef uECC_SUPPORTS_secp256k1 -#define uECC_SUPPORTS_secp256k1 0 -#endif - -/* Specifies whether compressed point format is supported. - Set to 0 to disable point compression/decompression functions. */ -#ifndef uECC_SUPPORT_COMPRESSED_POINT -#define uECC_SUPPORT_COMPRESSED_POINT 1 -#endif - -struct uECC_Curve_t; -typedef const struct uECC_Curve_t *uECC_Curve; - -#ifdef __cplusplus -extern "C" { -#endif - -#if uECC_SUPPORTS_secp160r1 -uECC_Curve uECC_secp160r1(void); -#endif -#if uECC_SUPPORTS_secp192r1 -uECC_Curve uECC_secp192r1(void); -#endif -#if uECC_SUPPORTS_secp224r1 -uECC_Curve uECC_secp224r1(void); -#endif -#if uECC_SUPPORTS_secp256r1 -uECC_Curve uECC_secp256r1(void); -#endif -#if uECC_SUPPORTS_secp256k1 -uECC_Curve uECC_secp256k1(void); -#endif - -/* uECC_RNG_Function type -The RNG function should fill 'size' random bytes into 'dest'. It should return 1 -if 'dest' was filled with random data, or 0 if the random data could not be -generated. The filled-in values should be either truly random, or from a -cryptographically-secure PRNG. - -A correctly functioning RNG function must be set (using uECC_set_rng()) before -calling uECC_make_key() or uECC_sign(). - -Setting a correctly functioning RNG function improves the resistance to -side-channel attacks for uECC_shared_secret() and uECC_sign_deterministic(). - -A correct RNG function is set by default when building for Windows, Linux, or OS -X. If you are building on another POSIX-compliant system that supports -/dev/random or /dev/urandom, you can define uECC_POSIX to use the predefined -RNG. For embedded platforms there is no predefined RNG function; you must -provide your own. -*/ -typedef int (*uECC_RNG_Function)(uint8_t *dest, unsigned size); - -/* uECC_set_rng() function. -Set the function that will be used to generate random bytes. The RNG function -should return 1 if the random data was generated, or 0 if the random data could -not be generated. - -On platforms where there is no predefined RNG function (eg embedded platforms), -this must be called before uECC_make_key() or uECC_sign() are used. - -Inputs: - rng_function - The function that will be used to generate random bytes. -*/ -void uECC_set_rng(uECC_RNG_Function rng_function); - -/* uECC_get_rng() function. - -Returns the function that will be used to generate random bytes. -*/ -uECC_RNG_Function uECC_get_rng(void); - -/* uECC_curve_private_key_size() function. - -Returns the size of a private key for the curve in bytes. -*/ -int uECC_curve_private_key_size(uECC_Curve curve); - -/* uECC_curve_public_key_size() function. - -Returns the size of a public key for the curve in bytes. -*/ -int uECC_curve_public_key_size(uECC_Curve curve); - -/* uECC_make_key() function. -Create a public/private key pair. - -Outputs: - public_key - Will be filled in with the public key. Must be at least 2 * -the curve size (in bytes) long. For example, if the curve is secp256r1, -public_key must be 64 bytes long. private_key - Will be filled in with the -private key. Must be as long as the curve order; this is typically the same as -the curve size, except for secp160r1. For example, if the curve is secp256r1, -private_key must be 32 bytes long. - - For secp160r1, private_key must be 21 bytes long! Note that -the first byte will almost always be 0 (there is about a 1 in 2^80 chance of it -being non-zero). - -Returns 1 if the key pair was generated successfully, 0 if an error occurred. -*/ -int uECC_make_key(uint8_t *public_key, uint8_t *private_key, uECC_Curve curve); - -/* uECC_shared_secret() function. -Compute a shared secret given your secret key and someone else's public key. If -the public key is not from a trusted source and has not been previously -verified, you should verify it first using uECC_valid_public_key(). Note: It is -recommended that you hash the result of uECC_shared_secret() before using it for -symmetric encryption or HMAC. - -Inputs: - public_key - The public key of the remote party. - private_key - Your private key. - -Outputs: - secret - Will be filled in with the shared secret value. Must be the same -size as the curve size; for example, if the curve is secp256r1, secret must be -32 bytes long. - -Returns 1 if the shared secret was generated successfully, 0 if an error -occurred. -*/ -int uECC_shared_secret(const uint8_t *public_key, const uint8_t *private_key, - uint8_t *secret, uECC_Curve curve); - -#if uECC_SUPPORT_COMPRESSED_POINT -/* uECC_compress() function. -Compress a public key. - -Inputs: - public_key - The public key to compress. - -Outputs: - compressed - Will be filled in with the compressed public key. Must be at -least (curve size + 1) bytes long; for example, if the curve is secp256r1, - compressed must be 33 bytes long. -*/ -void uECC_compress(const uint8_t *public_key, uint8_t *compressed, - uECC_Curve curve); - -/* uECC_decompress() function. -Decompress a compressed public key. - -Inputs: - compressed - The compressed public key. - -Outputs: - public_key - Will be filled in with the decompressed public key. -*/ -void uECC_decompress(const uint8_t *compressed, uint8_t *public_key, - uECC_Curve curve); -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -/* uECC_valid_public_key() function. -Check to see if a public key is valid. - -Note that you are not required to check for a valid public key before using any -other uECC functions. However, you may wish to avoid spending CPU time computing -a shared secret or verifying a signature using an invalid public key. - -Inputs: - public_key - The public key to check. - -Returns 1 if the public key is valid, 0 if it is invalid. -*/ -int uECC_valid_public_key(const uint8_t *public_key, uECC_Curve curve); - -/* uECC_compute_public_key() function. -Compute the corresponding public key for a private key. - -Inputs: - private_key - The private key to compute the public key for - -Outputs: - public_key - Will be filled in with the corresponding public key - -Returns 1 if the key was computed successfully, 0 if an error occurred. -*/ -int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, - uECC_Curve curve); - -/* uECC_sign() function. -Generate an ECDSA signature for a given hash value. - -Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and -pass it in to this function along with your private key. - -Inputs: - private_key - Your private key. - message_hash - The hash of the message to sign. - hash_size - The size of message_hash in bytes. - -Outputs: - signature - Will be filled in with the signature value. Must be at least 2 * -curve size long. For example, if the curve is secp256r1, signature must be 64 -bytes long. - -Returns 1 if the signature generated successfully, 0 if an error occurred. -*/ -int uECC_sign(const uint8_t *private_key, const uint8_t *message_hash, - unsigned hash_size, uint8_t *signature, uECC_Curve curve); - -/* uECC_HashContext structure. -This is used to pass in an arbitrary hash function to uECC_sign_deterministic(). -The structure will be used for multiple hash computations; each time a new hash -is computed, init_hash() will be called, followed by one or more calls to -update_hash(), and finally a call to finish_hash() to produce the resulting -hash. - -The intention is that you will create a structure that includes uECC_HashContext -followed by any hash-specific data. For example: - -typedef struct SHA256_HashContext { - uECC_HashContext uECC; - SHA256_CTX ctx; -} SHA256_HashContext; - -void init_SHA256(uECC_HashContext *base) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Init(&context->ctx); -} - -void update_SHA256(uECC_HashContext *base, - const uint8_t *message, - unsigned message_size) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Update(&context->ctx, message, message_size); -} - -void finish_SHA256(uECC_HashContext *base, uint8_t *hash_result) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Final(hash_result, &context->ctx); -} - -... when signing ... -{ - uint8_t tmp[32 + 32 + 64]; - SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256, &finish_SHA256, 64, -32, tmp}}; uECC_sign_deterministic(key, message_hash, &ctx.uECC, signature); -} -*/ -typedef struct uECC_HashContext { - void (*init_hash)(const struct uECC_HashContext *context); - void (*update_hash)(const struct uECC_HashContext *context, - const uint8_t *message, unsigned message_size); - void (*finish_hash)(const struct uECC_HashContext *context, - uint8_t *hash_result); - unsigned - block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */ - unsigned - result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */ - uint8_t *tmp; /* Must point to a buffer of at least (2 * result_size + - block_size) bytes. */ -} uECC_HashContext; - -/* uECC_sign_deterministic() function. -Generate an ECDSA signature for a given hash value, using a deterministic -algorithm (see RFC 6979). You do not need to set the RNG using uECC_set_rng() -before calling this function; however, if the RNG is defined it will improve -resistance to side-channel attacks. - -Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and -pass it to this function along with your private key and a hash context. Note -that the message_hash does not need to be computed with the same hash function -used by hash_context. - -Inputs: - private_key - Your private key. - message_hash - The hash of the message to sign. - hash_size - The size of message_hash in bytes. - hash_context - A hash context to use. - -Outputs: - signature - Will be filled in with the signature value. - -Returns 1 if the signature generated successfully, 0 if an error occurred. -*/ -int uECC_sign_deterministic(const uint8_t *private_key, - const uint8_t *message_hash, unsigned hash_size, - const uECC_HashContext *hash_context, - uint8_t *signature, uECC_Curve curve); - -/* uECC_verify() function. -Verify an ECDSA signature. - -Usage: Compute the hash of the signed data using the same hash as the signer and -pass it to this function along with the signer's public key and the signature -values (r and s). - -Inputs: - public_key - The signer's public key. - message_hash - The hash of the signed data. - hash_size - The size of message_hash in bytes. - signature - The signature value. - -Returns 1 if the signature is valid, 0 if it is invalid. -*/ -int uECC_verify(const uint8_t *public_key, const uint8_t *message_hash, - unsigned hash_size, const uint8_t *signature, uECC_Curve curve); - -#ifdef __cplusplus -} /* end of extern "C" */ -#endif - -#endif /* _UECC_H_ */ - -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_VLI_H_ -#define _UECC_VLI_H_ - -// -// - -/* Functions for raw large-integer manipulation. These are only available - if uECC.c is compiled with uECC_ENABLE_VLI_API defined to 1. */ -#ifndef uECC_ENABLE_VLI_API -#define uECC_ENABLE_VLI_API 0 -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if uECC_ENABLE_VLI_API - -void uECC_vli_clear(uECC_word_t *vli, wordcount_t num_words); - -/* Constant-time comparison to zero - secure way to compare long integers */ -/* Returns 1 if vli == 0, 0 otherwise. */ -uECC_word_t uECC_vli_isZero(const uECC_word_t *vli, wordcount_t num_words); - -/* Returns nonzero if bit 'bit' of vli is set. */ -uECC_word_t uECC_vli_testBit(const uECC_word_t *vli, bitcount_t bit); - -/* Counts the number of bits required to represent vli. */ -bitcount_t uECC_vli_numBits(const uECC_word_t *vli, - const wordcount_t max_words); - -/* Sets dest = src. */ -void uECC_vli_set(uECC_word_t *dest, const uECC_word_t *src, - wordcount_t num_words); - -/* Constant-time comparison function - secure way to compare long integers */ -/* Returns one if left == right, zero otherwise */ -uECC_word_t uECC_vli_equal(const uECC_word_t *left, const uECC_word_t *right, - wordcount_t num_words); - -/* Constant-time comparison function - secure way to compare long integers */ -/* Returns sign of left - right, in constant time. */ -cmpresult_t uECC_vli_cmp(const uECC_word_t *left, const uECC_word_t *right, - wordcount_t num_words); - -/* Computes vli = vli >> 1. */ -void uECC_vli_rshift1(uECC_word_t *vli, wordcount_t num_words); - -/* Computes result = left + right, returning carry. Can modify in place. */ -uECC_word_t uECC_vli_add(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, wordcount_t num_words); - -/* Computes result = left - right, returning borrow. Can modify in place. */ -uECC_word_t uECC_vli_sub(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, wordcount_t num_words); - -/* Computes result = left * right. Result must be 2 * num_words long. */ -void uECC_vli_mult(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, wordcount_t num_words); - -/* Computes result = left^2. Result must be 2 * num_words long. */ -void uECC_vli_square(uECC_word_t *result, const uECC_word_t *left, - wordcount_t num_words); - -/* Computes result = (left + right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap - mod. */ -void uECC_vli_modAdd(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = (left - right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap - mod. */ -void uECC_vli_modSub(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = product % mod, where product is 2N words long. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_mmod(uECC_word_t *result, uECC_word_t *product, - const uECC_word_t *mod, wordcount_t num_words); - -/* Calculates result = product (mod curve->p), where product is up to - 2 * curve->num_words long. */ -void uECC_vli_mmod_fast(uECC_word_t *result, uECC_word_t *product, - uECC_Curve curve); - -/* Computes result = (left * right) % mod. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_modMult(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = (left * right) % curve->p. */ -void uECC_vli_modMult_fast(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, uECC_Curve curve); - -/* Computes result = left^2 % mod. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_modSquare(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *mod, wordcount_t num_words); - -/* Computes result = left^2 % curve->p. */ -void uECC_vli_modSquare_fast(uECC_word_t *result, const uECC_word_t *left, - uECC_Curve curve); - -/* Computes result = (1 / input) % mod.*/ -void uECC_vli_modInv(uECC_word_t *result, const uECC_word_t *input, - const uECC_word_t *mod, wordcount_t num_words); - -#if uECC_SUPPORT_COMPRESSED_POINT -/* Calculates a = sqrt(a) (mod curve->p) */ -void uECC_vli_mod_sqrt(uECC_word_t *a, uECC_Curve curve); -#endif - -/* Converts an integer in uECC native format to big-endian bytes. */ -void uECC_vli_nativeToBytes(uint8_t *bytes, int num_bytes, - const uECC_word_t *native); -/* Converts big-endian bytes to an integer in uECC native format. */ -void uECC_vli_bytesToNative(uECC_word_t *native, const uint8_t *bytes, - int num_bytes); - -unsigned uECC_curve_num_words(uECC_Curve curve); -unsigned uECC_curve_num_bytes(uECC_Curve curve); -unsigned uECC_curve_num_bits(uECC_Curve curve); -unsigned uECC_curve_num_n_words(uECC_Curve curve); -unsigned uECC_curve_num_n_bytes(uECC_Curve curve); -unsigned uECC_curve_num_n_bits(uECC_Curve curve); - -const uECC_word_t *uECC_curve_p(uECC_Curve curve); -const uECC_word_t *uECC_curve_n(uECC_Curve curve); -const uECC_word_t *uECC_curve_G(uECC_Curve curve); -const uECC_word_t *uECC_curve_b(uECC_Curve curve); - -int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve); - -/* Multiplies a point by a scalar. Points are represented by the X coordinate - followed by the Y coordinate in the same array, both coordinates are - curve->num_words long. Note that scalar must be curve->num_n_words long (NOT - curve->num_words). */ -void uECC_point_mult(uECC_word_t *result, const uECC_word_t *point, - const uECC_word_t *scalar, uECC_Curve curve); - -/* Generates a random integer in the range 0 < random < top. - Both random and top have num_words words. */ -int uECC_generate_random_int(uECC_word_t *random, const uECC_word_t *top, - wordcount_t num_words); - -#endif /* uECC_ENABLE_VLI_API */ - -#ifdef __cplusplus -} /* end of extern "C" */ -#endif - -#endif /* _UECC_VLI_H_ */ - -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_TYPES_H_ -#define _UECC_TYPES_H_ - -#ifndef uECC_PLATFORM -#if defined(__AVR__) && __AVR__ -#define uECC_PLATFORM uECC_avr -#elif defined(__thumb2__) || \ - defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */ -#define uECC_PLATFORM uECC_arm_thumb2 -#elif defined(__thumb__) -#define uECC_PLATFORM uECC_arm_thumb -#elif defined(__arm__) || defined(_M_ARM) -#define uECC_PLATFORM uECC_arm -#elif defined(__aarch64__) -#define uECC_PLATFORM uECC_arm64 -#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \ - defined(__I86__) -#define uECC_PLATFORM uECC_x86 -#elif defined(__amd64__) || defined(_M_X64) -#define uECC_PLATFORM uECC_x86_64 -#else -#define uECC_PLATFORM uECC_arch_other -#endif -#endif - -#ifndef uECC_ARM_USE_UMAAL -#if (uECC_PLATFORM == uECC_arm) && (__ARM_ARCH >= 6) -#define uECC_ARM_USE_UMAAL 1 -#elif (uECC_PLATFORM == uECC_arm_thumb2) && (__ARM_ARCH >= 6) && \ - (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__) -#define uECC_ARM_USE_UMAAL 1 -#else -#define uECC_ARM_USE_UMAAL 0 -#endif -#endif - -#ifndef uECC_WORD_SIZE -#if uECC_PLATFORM == uECC_avr -#define uECC_WORD_SIZE 1 -#elif (uECC_PLATFORM == uECC_x86_64 || uECC_PLATFORM == uECC_arm64) -#define uECC_WORD_SIZE 8 -#else -#define uECC_WORD_SIZE 4 -#endif -#endif - -#if (uECC_WORD_SIZE != 1) && (uECC_WORD_SIZE != 4) && (uECC_WORD_SIZE != 8) -#error "Unsupported value for uECC_WORD_SIZE" -#endif - -#if ((uECC_PLATFORM == uECC_avr) && (uECC_WORD_SIZE != 1)) -#pragma message("uECC_WORD_SIZE must be 1 for AVR") -#undef uECC_WORD_SIZE -#define uECC_WORD_SIZE 1 -#endif - -#if ((uECC_PLATFORM == uECC_arm || uECC_PLATFORM == uECC_arm_thumb || \ - uECC_PLATFORM == uECC_arm_thumb2) && \ - (uECC_WORD_SIZE != 4)) -#pragma message("uECC_WORD_SIZE must be 4 for ARM") -#undef uECC_WORD_SIZE -#define uECC_WORD_SIZE 4 -#endif - -typedef int8_t wordcount_t; -typedef int16_t bitcount_t; -typedef int8_t cmpresult_t; - -#if (uECC_WORD_SIZE == 1) - -typedef uint8_t uECC_word_t; -typedef uint16_t uECC_dword_t; - -#define HIGH_BIT_SET 0x80 -#define uECC_WORD_BITS 8 -#define uECC_WORD_BITS_SHIFT 3 -#define uECC_WORD_BITS_MASK 0x07 - -#elif (uECC_WORD_SIZE == 4) - -typedef uint32_t uECC_word_t; -typedef uint64_t uECC_dword_t; - -#define HIGH_BIT_SET 0x80000000 -#define uECC_WORD_BITS 32 -#define uECC_WORD_BITS_SHIFT 5 -#define uECC_WORD_BITS_MASK 0x01F - -#elif (uECC_WORD_SIZE == 8) - -typedef uint64_t uECC_word_t; - -#define HIGH_BIT_SET 0x8000000000000000U -#define uECC_WORD_BITS 64 -#define uECC_WORD_BITS_SHIFT 6 -#define uECC_WORD_BITS_MASK 0x03F - -#endif /* uECC_WORD_SIZE */ - -#endif /* _UECC_TYPES_H_ */ -// End of uecc BSD-2 - - -struct mg_connection; -typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, - void *ev_data); -void mg_call(struct mg_connection *c, int ev, void *ev_data); -void mg_error(struct mg_connection *c, const char *fmt, ...); - -enum { - MG_EV_ERROR, // Error char *error_message - MG_EV_OPEN, // Connection created NULL - MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis - MG_EV_RESOLVE, // Host name is resolved NULL - MG_EV_CONNECT, // Connection established NULL - MG_EV_ACCEPT, // Connection accepted NULL - MG_EV_TLS_HS, // TLS handshake succeeded NULL - MG_EV_READ, // Data received from socket long *bytes_read - MG_EV_WRITE, // Data written to socket long *bytes_written - MG_EV_CLOSE, // Connection closed NULL - MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message * - MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * - MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * - MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * - MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message * - MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message * - MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code - MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis - MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data - MG_EV_USER // Starting ID for user events -}; - - - - - - - - - -struct mg_dns { - const char *url; // DNS server URL - struct mg_connection *c; // DNS server connection -}; - -struct mg_addr { - uint8_t ip[16]; // Holds IPv4 or IPv6 address, in network byte order - uint16_t port; // TCP or UDP port in network byte order - uint8_t scope_id; // IPv6 scope ID - bool is_ip6; // True when address is IPv6 address -}; - -struct mg_mgr { - struct mg_connection *conns; // List of active connections - struct mg_dns dns4; // DNS for IPv4 - struct mg_dns dns6; // DNS for IPv6 - int dnstimeout; // DNS resolve timeout in milliseconds - bool use_dns6; // Use DNS6 server by default, see #1532 - unsigned long nextid; // Next connection ID - unsigned long timerid; // Next timer ID - void *userdata; // Arbitrary user data pointer - void *tls_ctx; // TLS context shared by all TLS sessions - uint16_t mqtt_id; // MQTT IDs for pub/sub - void *active_dns_requests; // DNS requests in progress - struct mg_timer *timers; // Active timers - int epoll_fd; // Used when MG_EPOLL_ENABLE=1 - void *priv; // Used by the MIP stack - size_t extraconnsize; // Used by the MIP stack - MG_SOCKET_TYPE pipe; // Socketpair end for mg_wakeup() -#if MG_ENABLE_FREERTOS_TCP - SocketSet_t ss; // NOTE(lsm): referenced from socket struct -#endif -}; - -struct mg_connection { - struct mg_connection *next; // Linkage in struct mg_mgr :: connections - struct mg_mgr *mgr; // Our container - struct mg_addr loc; // Local address - struct mg_addr rem; // Remote address - void *fd; // Connected socket, or LWIP data - unsigned long id; // Auto-incrementing unique connection ID - struct mg_iobuf recv; // Incoming data - struct mg_iobuf send; // Outgoing data - struct mg_iobuf prof; // Profile data enabled by MG_ENABLE_PROFILE - struct mg_iobuf rtls; // TLS only. Incoming encrypted data - mg_event_handler_t fn; // User-specified event handler function - void *fn_data; // User-specified function parameter - mg_event_handler_t pfn; // Protocol-specific handler function - void *pfn_data; // Protocol-specific function parameter - char data[MG_DATA_SIZE]; // Arbitrary connection data - void *tls; // TLS specific data - unsigned is_listening : 1; // Listening connection - unsigned is_client : 1; // Outbound (client) connection - unsigned is_accepted : 1; // Accepted (server) connection - unsigned is_resolving : 1; // Non-blocking DNS resolution is in progress - unsigned is_arplooking : 1; // Non-blocking ARP resolution is in progress - unsigned is_connecting : 1; // Non-blocking connect is in progress - unsigned is_tls : 1; // TLS-enabled connection - unsigned is_tls_hs : 1; // TLS handshake is in progress - unsigned is_udp : 1; // UDP connection - unsigned is_websocket : 1; // WebSocket connection - unsigned is_mqtt5 : 1; // For MQTT connection, v5 indicator - unsigned is_hexdumping : 1; // Hexdump in/out traffic - unsigned is_draining : 1; // Send remaining data, then close and free - unsigned is_closing : 1; // Close and free the connection immediately - unsigned is_full : 1; // Stop reads, until cleared - unsigned is_resp : 1; // Response is still being generated - unsigned is_readable : 1; // Connection is ready to read - unsigned is_writable : 1; // Connection is ready to write -}; - -void mg_mgr_poll(struct mg_mgr *, int ms); -void mg_mgr_init(struct mg_mgr *); -void mg_mgr_free(struct mg_mgr *); - -struct mg_connection *mg_listen(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data); -struct mg_connection *mg_connect(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data); -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, - mg_event_handler_t fn, void *fn_data); -void mg_connect_resolved(struct mg_connection *); -bool mg_send(struct mg_connection *, const void *, size_t); -size_t mg_printf(struct mg_connection *, const char *fmt, ...); -size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap); -bool mg_aton(struct mg_str str, struct mg_addr *addr); - -// These functions are used to integrate with custom network stacks -struct mg_connection *mg_alloc_conn(struct mg_mgr *); -void mg_close_conn(struct mg_connection *c); -bool mg_open_listener(struct mg_connection *c, const char *url); - -// Utility functions -bool mg_wakeup(struct mg_mgr *, unsigned long id, const void *buf, size_t len); -bool mg_wakeup_init(struct mg_mgr *); -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, - unsigned flags, void (*fn)(void *), void *arg); - - - - - - - - -struct mg_http_header { - struct mg_str name; // Header name - struct mg_str value; // Header value -}; - -struct mg_http_message { - struct mg_str method, uri, query, proto; // Request/response line - struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers - struct mg_str body; // Body - struct mg_str head; // Request + headers - struct mg_str message; // Request + headers + body -}; - -// Parameter for mg_http_serve_dir() -struct mg_http_serve_opts { - const char *root_dir; // Web root directory, must be non-NULL - const char *ssi_pattern; // SSI file name pattern, e.g. #.shtml - const char *extra_headers; // Extra HTTP headers to add in responses - const char *mime_types; // Extra mime types, ext1=type1,ext2=type2,.. - const char *page404; // Path to the 404 page, or NULL by default - struct mg_fs *fs; // Filesystem implementation. Use NULL for POSIX -}; - -// Parameter for mg_http_next_multipart -struct mg_http_part { - struct mg_str name; // Form field name - struct mg_str filename; // Filename for file uploads - struct mg_str body; // Part contents -}; - -int mg_http_parse(const char *s, size_t len, struct mg_http_message *); -int mg_http_get_request_len(const unsigned char *buf, size_t buf_len); -void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...); -void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len); -void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm); -struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data); -struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data); -void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, - const struct mg_http_serve_opts *); -void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, - const char *path, const struct mg_http_serve_opts *); -void mg_http_reply(struct mg_connection *, int status_code, const char *headers, - const char *body_fmt, ...); -struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name); -int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t); -int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); -size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); -void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); -bool mg_http_match_uri(const struct mg_http_message *, const char *glob); -long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, - struct mg_fs *fs, const char *dir, size_t max_size); -void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); -struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); -size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); -int mg_http_status(const struct mg_http_message *hm); -void mg_hello(const char *url); - - -void mg_http_serve_ssi(struct mg_connection *c, const char *root, - const char *fullpath); - - -#define MG_TLS_NONE 0 // No TLS support -#define MG_TLS_MBED 1 // mbedTLS -#define MG_TLS_OPENSSL 2 // OpenSSL -#define MG_TLS_BUILTIN 3 // Built-in -#define MG_TLS_CUSTOM 4 // Custom implementation - -#ifndef MG_TLS -#define MG_TLS MG_TLS_NONE -#endif - - - - - -struct mg_tls_opts { - struct mg_str ca; // PEM or DER - struct mg_str cert; // PEM or DER - struct mg_str key; // PEM or DER - struct mg_str name; // If not empty, enable host name verification -}; - -void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *opts); -void mg_tls_free(struct mg_connection *); -long mg_tls_send(struct mg_connection *, const void *buf, size_t len); -long mg_tls_recv(struct mg_connection *, void *buf, size_t len); -size_t mg_tls_pending(struct mg_connection *); -void mg_tls_handshake(struct mg_connection *); - -// Private -void mg_tls_ctx_init(struct mg_mgr *); -void mg_tls_ctx_free(struct mg_mgr *); - -// Low-level IO primives used by TLS layer -enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 }; -long mg_io_send(struct mg_connection *c, const void *buf, size_t len); -long mg_io_recv(struct mg_connection *c, void *buf, size_t len); - - - - - - - -#if MG_TLS == MG_TLS_MBED -#include -#include -#include -#include - -struct mg_tls_ctx { - int dummy; -#ifdef MBEDTLS_SSL_SESSION_TICKETS - mbedtls_ssl_ticket_context tickets; -#endif -}; - -struct mg_tls { - mbedtls_x509_crt ca; // Parsed CA certificate - mbedtls_x509_crt cert; // Parsed certificate - mbedtls_pk_context pk; // Private key context - mbedtls_ssl_context ssl; // SSL/TLS context - mbedtls_ssl_config conf; // SSL-TLS config -#ifdef MBEDTLS_SSL_SESSION_TICKETS - mbedtls_ssl_ticket_context ticket; // Session tickets context -#endif -}; -#endif - - -#if MG_TLS == MG_TLS_OPENSSL - -#include -#include - -struct mg_tls { - BIO_METHOD *bm; - SSL_CTX *ctx; - SSL *ssl; -}; -#endif - - -#define WEBSOCKET_OP_CONTINUE 0 -#define WEBSOCKET_OP_TEXT 1 -#define WEBSOCKET_OP_BINARY 2 -#define WEBSOCKET_OP_CLOSE 8 -#define WEBSOCKET_OP_PING 9 -#define WEBSOCKET_OP_PONG 10 - - - -struct mg_ws_message { - struct mg_str data; // Websocket message data - uint8_t flags; // Websocket message flags -}; - -struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data, - const char *fmt, ...); -void mg_ws_upgrade(struct mg_connection *, struct mg_http_message *, - const char *fmt, ...); -size_t mg_ws_send(struct mg_connection *, const void *buf, size_t len, int op); -size_t mg_ws_wrap(struct mg_connection *, size_t len, int op); -size_t mg_ws_printf(struct mg_connection *c, int op, const char *fmt, ...); -size_t mg_ws_vprintf(struct mg_connection *c, int op, const char *fmt, - va_list *); - - - - -struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data); -void mg_sntp_request(struct mg_connection *c); -int64_t mg_sntp_parse(const unsigned char *buf, size_t len); - - - - - -#define MQTT_CMD_CONNECT 1 -#define MQTT_CMD_CONNACK 2 -#define MQTT_CMD_PUBLISH 3 -#define MQTT_CMD_PUBACK 4 -#define MQTT_CMD_PUBREC 5 -#define MQTT_CMD_PUBREL 6 -#define MQTT_CMD_PUBCOMP 7 -#define MQTT_CMD_SUBSCRIBE 8 -#define MQTT_CMD_SUBACK 9 -#define MQTT_CMD_UNSUBSCRIBE 10 -#define MQTT_CMD_UNSUBACK 11 -#define MQTT_CMD_PINGREQ 12 -#define MQTT_CMD_PINGRESP 13 -#define MQTT_CMD_DISCONNECT 14 -#define MQTT_CMD_AUTH 15 - -#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01 -#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02 -#define MQTT_PROP_CONTENT_TYPE 0x03 -#define MQTT_PROP_RESPONSE_TOPIC 0x08 -#define MQTT_PROP_CORRELATION_DATA 0x09 -#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B -#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11 -#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12 -#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13 -#define MQTT_PROP_AUTHENTICATION_METHOD 0x15 -#define MQTT_PROP_AUTHENTICATION_DATA 0x16 -#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17 -#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18 -#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19 -#define MQTT_PROP_RESPONSE_INFORMATION 0x1A -#define MQTT_PROP_SERVER_REFERENCE 0x1C -#define MQTT_PROP_REASON_STRING 0x1F -#define MQTT_PROP_RECEIVE_MAXIMUM 0x21 -#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22 -#define MQTT_PROP_TOPIC_ALIAS 0x23 -#define MQTT_PROP_MAXIMUM_QOS 0x24 -#define MQTT_PROP_RETAIN_AVAILABLE 0x25 -#define MQTT_PROP_USER_PROPERTY 0x26 -#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27 -#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28 -#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29 -#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A - -enum { - MQTT_PROP_TYPE_BYTE, - MQTT_PROP_TYPE_STRING, - MQTT_PROP_TYPE_STRING_PAIR, - MQTT_PROP_TYPE_BINARY_DATA, - MQTT_PROP_TYPE_VARIABLE_INT, - MQTT_PROP_TYPE_INT, - MQTT_PROP_TYPE_SHORT -}; - -enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED }; - -struct mg_mqtt_prop { - uint8_t id; // Enumerated at MQTT5 Reference - uint32_t iv; // Integer value for 8-, 16-, 32-bit integers types - struct mg_str key; // Non-NULL only for user property type - struct mg_str val; // Non-NULL only for UTF-8 types and user properties -}; - -struct mg_mqtt_opts { - struct mg_str user; // Username, can be empty - struct mg_str pass; // Password, can be empty - struct mg_str client_id; // Client ID - struct mg_str topic; // message/subscription topic - struct mg_str message; // message content - uint8_t qos; // message quality of service - uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4 - uint16_t keepalive; // Keep-alive timer in seconds - bool retain; // Retain flag - bool clean; // Clean session flag - struct mg_mqtt_prop *props; // MQTT5 props array - size_t num_props; // number of props - struct mg_mqtt_prop *will_props; // Valid only for CONNECT packet (MQTT5) - size_t num_will_props; // Number of will props -}; - -struct mg_mqtt_message { - struct mg_str topic; // Parsed topic for PUBLISH - struct mg_str data; // Parsed message for PUBLISH - struct mg_str dgram; // Whole MQTT packet, including headers - uint16_t id; // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH - uint8_t cmd; // MQTT command, one of MQTT_CMD_* - uint8_t qos; // Quality of service - uint8_t ack; // CONNACK return code, 0 = success - size_t props_start; // Offset to the start of the properties (MQTT5) - size_t props_size; // Length of the properties -}; - -struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url, - const struct mg_mqtt_opts *opts, - mg_event_handler_t fn, void *fn_data); -struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data); -void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts); -void mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts); -void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts *opts); -int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *); -void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags, - uint32_t len); -void mg_mqtt_ping(struct mg_connection *); -void mg_mqtt_pong(struct mg_connection *); -void mg_mqtt_disconnect(struct mg_connection *, const struct mg_mqtt_opts *); -size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop *, - size_t ofs); - - - - - -// Mongoose sends DNS queries that contain only one question: -// either A (IPv4) or AAAA (IPv6) address lookup. -// Therefore, we expect zero or one answer. -// If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address. -struct mg_dns_message { - uint16_t txnid; // Transaction ID - bool resolved; // Resolve successful, addr is set - struct mg_addr addr; // Resolved address - char name[256]; // Host name -}; - -struct mg_dns_header { - uint16_t txnid; // Transaction ID - uint16_t flags; - uint16_t num_questions; - uint16_t num_answers; - uint16_t num_authority_prs; - uint16_t num_other_prs; -}; - -// DNS resource record -struct mg_dns_rr { - uint16_t nlen; // Name or pointer length - uint16_t atype; // Address type - uint16_t aclass; // Address class - uint16_t alen; // Address length -}; - -void mg_resolve(struct mg_connection *, const char *url); -void mg_resolve_cancel(struct mg_connection *); -bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); -size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, - bool is_question, struct mg_dns_rr *); - - - - - -#ifndef MG_JSON_MAX_DEPTH -#define MG_JSON_MAX_DEPTH 30 -#endif - -// Error return values - negative. Successful returns are >= 0 -enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 }; -int mg_json_get(struct mg_str json, const char *path, int *toklen); - -struct mg_str mg_json_get_tok(struct mg_str json, const char *path); -bool mg_json_get_num(struct mg_str json, const char *path, double *v); -bool mg_json_get_bool(struct mg_str json, const char *path, bool *v); -long mg_json_get_long(struct mg_str json, const char *path, long dflt); -char *mg_json_get_str(struct mg_str json, const char *path); -char *mg_json_get_hex(struct mg_str json, const char *path, int *len); -char *mg_json_get_b64(struct mg_str json, const char *path, int *len); - -bool mg_json_unescape(struct mg_str str, char *buf, size_t len); -size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, - struct mg_str *val); - - - - -// JSON-RPC request descriptor -struct mg_rpc_req { - struct mg_rpc **head; // RPC handlers list head - struct mg_rpc *rpc; // RPC handler being called - mg_pfn_t pfn; // Response printing function - void *pfn_data; // Response printing function data - void *req_data; // Arbitrary request data - struct mg_str frame; // Request, e.g. {"id":1,"method":"add","params":[1,2]} -}; - -// JSON-RPC method handler -struct mg_rpc { - struct mg_rpc *next; // Next in list - struct mg_str method; // Method pattern - void (*fn)(struct mg_rpc_req *); // Handler function - void *fn_data; // Handler function argument -}; - -void mg_rpc_add(struct mg_rpc **head, struct mg_str method_pattern, - void (*handler)(struct mg_rpc_req *), void *handler_data); -void mg_rpc_del(struct mg_rpc **head, void (*handler)(struct mg_rpc_req *)); -void mg_rpc_process(struct mg_rpc_req *); - -// Helper functions to print result or error frame -void mg_rpc_ok(struct mg_rpc_req *, const char *fmt, ...); -void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap); -void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...); -void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *); -void mg_rpc_list(struct mg_rpc_req *r); -// Copyright (c) 2023 Cesanta Software Limited -// All rights reserved - - - - - -#define MG_OTA_NONE 0 // No OTA support -#define MG_OTA_FLASH 1 // OTA via an internal flash -#define MG_OTA_CUSTOM 100 // Custom implementation - -#ifndef MG_OTA -#define MG_OTA MG_OTA_NONE -#endif - -#if defined(__GNUC__) && !defined(__APPLE__) -#define MG_IRAM __attribute__((section(".iram"))) -#else -#define MG_IRAM -#endif - -// Firmware update API -bool mg_ota_begin(size_t new_firmware_size); // Start writing -bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k -bool mg_ota_end(void); // Stop writing - -enum { - MG_OTA_UNAVAILABLE = 0, // No OTA information is present - MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA - MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback - MG_OTA_COMMITTED = 3 // The firmware is good -}; -enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 }; - -int mg_ota_status(int firmware); // Return firmware status MG_OTA_* -uint32_t mg_ota_crc32(int firmware); // Return firmware checksum -uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch -size_t mg_ota_size(int firmware); // Firmware size - -bool mg_ota_commit(void); // Commit current firmware -bool mg_ota_rollback(void); // Rollback to the previous firmware -MG_IRAM void mg_ota_boot(void); // Bootloader function -// Copyright (c) 2023 Cesanta Software Limited -// All rights reserved - - - - - -#define MG_DEVICE_NONE 0 // Dummy system -#define MG_DEVICE_STM32H5 1 // STM32 H5 -#define MG_DEVICE_STM32H7 2 // STM32 H7 -#define MG_DEVICE_RT1020 3 // IMXRT1020 -#define MG_DEVICE_RT1060 4 // IMXRT1060 -#define MG_DEVICE_CH32V307 100 // WCH CH32V307 -#define MG_DEVICE_CUSTOM 1000 // Custom implementation - -#ifndef MG_DEVICE -#define MG_DEVICE MG_DEVICE_NONE -#endif - -// Flash information -void *mg_flash_start(void); // Return flash start address -size_t mg_flash_size(void); // Return flash size -size_t mg_flash_sector_size(void); // Return flash sector size -size_t mg_flash_write_align(void); // Return flash write align, minimum 4 -int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2 - -// Write, erase, swap bank -bool mg_flash_write(void *addr, const void *buf, size_t len); -bool mg_flash_erase(void *sector); -bool mg_flash_swap_bank(void); - -// Convenience functions to store data on a flash sector with wear levelling -// If `sector` is NULL, then the last sector of flash is used -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len); -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len); - -void mg_device_reset(void); // Reboot device immediately - - - - - - -#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP -struct mg_tcpip_if; // Mongoose TCP/IP network interface - -struct mg_tcpip_driver { - bool (*init)(struct mg_tcpip_if *); // Init driver - size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); // Transmit frame - size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); // Receive frame - bool (*up)(struct mg_tcpip_if *); // Up/down status -}; - -// Network interface -struct mg_tcpip_if { - uint8_t mac[6]; // MAC address. Must be set to a valid MAC - uint32_t ip, mask, gw; // IP address, mask, default gateway - struct mg_str tx; // Output (TX) buffer - bool enable_dhcp_client; // Enable DCHP client - bool enable_dhcp_server; // Enable DCHP server - bool enable_get_gateway; // DCHP server sets client as gateway - bool enable_crc32_check; // Do a CRC check on RX frames and strip it - bool enable_mac_check; // Do a MAC check on RX frames - struct mg_tcpip_driver *driver; // Low level driver - void *driver_data; // Driver-specific data - struct mg_mgr *mgr; // Mongoose event manager - struct mg_queue recv_queue; // Receive queue - uint16_t mtu; // Interface MTU -#define MG_TCPIP_MTU_DEFAULT 1500 - - // Internal state, user can use it but should not change it - uint8_t gwmac[6]; // Router's MAC - uint64_t now; // Current time - uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state - uint64_t lease_expire; // Lease expiration time, in ms - uint16_t eport; // Next ephemeral port - volatile uint32_t ndrop; // Number of received, but dropped frames - volatile uint32_t nrecv; // Number of received frames - volatile uint32_t nsent; // Number of transmitted frames - volatile uint32_t nerr; // Number of driver errors - uint8_t state; // Current state -#define MG_TCPIP_STATE_DOWN 0 // Interface is down -#define MG_TCPIP_STATE_UP 1 // Interface is up -#define MG_TCPIP_STATE_REQ 2 // Interface is up and has requested an IP -#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP assigned -}; - -void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *); -void mg_tcpip_free(struct mg_tcpip_if *); -void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp); - -extern struct mg_tcpip_driver mg_tcpip_driver_stm32f; -extern struct mg_tcpip_driver mg_tcpip_driver_w5500; -extern struct mg_tcpip_driver mg_tcpip_driver_tm4c; -extern struct mg_tcpip_driver mg_tcpip_driver_stm32h; -extern struct mg_tcpip_driver mg_tcpip_driver_imxrt; -extern struct mg_tcpip_driver mg_tcpip_driver_same54; -extern struct mg_tcpip_driver mg_tcpip_driver_cmsis; - -// Drivers that require SPI, can use this SPI abstraction -struct mg_tcpip_spi { - void *spi; // Opaque SPI bus descriptor - void (*begin)(void *); // SPI begin: slave select low - void (*end)(void *); // SPI end: slave select high - uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1 byte, read reply -}; -#endif - - - -// Macros to record timestamped events that happens with a connection. -// They are saved into a c->prof IO buffer, each event is a name and a 32-bit -// timestamp in milliseconds since connection init time. -// -// Test (run in two separate terminals): -// make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1 -// curl localhost:8000 -// Output: -// 1ea1f1e7 2 net.c:150:mg_close_conn 3 profile: -// 1ea1f1e8 2 net.c:150:mg_close_conn 1ea1f1e6 init -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_OPEN -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_ACCEPT -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_READ -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_HTTP_MSG -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_WRITE -// 1ea1f1e8 2 net.c:150:mg_close_conn 1 EV_CLOSE -// -// Usage: -// Enable profiling by setting MG_ENABLE_PROFILE=1 -// Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like to measure - -#if MG_ENABLE_PROFILE -struct mg_profitem { - const char *name; // Event name - uint32_t timestamp; // Milliseconds since connection creation (MG_EV_OPEN) -}; - -#define MG_PROFILE_ALLOC_GRANULARITY 256 // Can save 32 items wih to realloc - -// Adding a profile item to the c->prof. Must be as fast as possible. -// Reallocation of the c->prof iobuf is not desirable here, that's why we -// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY. -// This macro just inits and copies 8 bytes, and calls mg_millis(), -// which should be fast enough. -#define MG_PROF_ADD(c, name_) \ - do { \ - struct mg_iobuf *io = &c->prof; \ - uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp; \ - struct mg_profitem item = {name_, (uint32_t) mg_millis() - inittime}; \ - mg_iobuf_add(io, io->len, &item, sizeof(item)); \ - } while (0) - -// Initialising profile for a new connection. Not time sensitive -#define MG_PROF_INIT(c) \ - do { \ - struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \ - mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY); \ - mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first)); \ - } while (0) - -#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof) - -// Dumping the profile. Not time sensitive -#define MG_PROF_DUMP(c) \ - do { \ - struct mg_iobuf *io = &c->prof; \ - struct mg_profitem *p = (struct mg_profitem *) io->buf; \ - struct mg_profitem *e = &p[io->len / sizeof(*p)]; \ - MG_INFO(("%lu profile:", c->id)); \ - while (p < e) { \ - MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \ - p++; \ - } \ - } while (0) - -#else -#define MG_PROF_INIT(c) -#define MG_PROF_FREE(c) -#define MG_PROF_ADD(c, name) -#define MG_PROF_DUMP(c) -#endif - - -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && MG_ENABLE_DRIVER_CMSIS - -#include "Driver_ETH_MAC.h" // keep this include -#include "Driver_ETH_PHY.h" // keep this include - -#endif - - -struct mg_tcpip_driver_imxrt_data { - // MDC clock divider. MDC clock is derived from IPS Bus clock (ipg_clk), - // must not exceed 2.5MHz. Configuration for clock range 2.36~2.50 MHz - // 37.5.1.8.2, Table 37-46 : f = ipg_clk / (2(mdc_cr + 1)) - // ipg_clk mdc_cr VALUE - // -------------------------- - // -1 <-- TODO() tell driver to guess the value - // 25 MHz 4 - // 33 MHz 6 - // 40 MHz 7 - // 50 MHz 9 - // 66 MHz 13 - int mdc_cr; // Valid values: -1 to 63 - - uint8_t phy_addr; // PHY address -}; - - -struct mg_tcpip_driver_same54_data { - int mdc_cr; -}; - - -struct mg_tcpip_driver_stm32f_data { - // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz - // HCLK range DIVIDER mdc_cr VALUE - // ------------------------------------- - // -1 <-- tell driver to guess the value - // 60-100 MHz HCLK/42 0 - // 100-150 MHz HCLK/62 1 - // 20-35 MHz HCLK/16 2 - // 35-60 MHz HCLK/26 3 - // 150-216 MHz HCLK/102 4 <-- value for Nucleo-F* on max speed - // 216-310 MHz HCLK/124 5 - // 110, 111 Reserved - int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 - - uint8_t phy_addr; // PHY address -}; - - -struct mg_tcpip_driver_stm32h_data { - // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz - // HCLK range DIVIDER mdc_cr VALUE - // ------------------------------------- - // -1 <-- tell driver to guess the value - // 60-100 MHz HCLK/42 0 - // 100-150 MHz HCLK/62 1 - // 20-35 MHz HCLK/16 2 - // 35-60 MHz HCLK/26 3 - // 150-250 MHz HCLK/102 4 <-- value for Nucleo-H* on max speed driven by HSI - // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on max speed driven by CSI - // 110, 111 Reserved - int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 -}; - - -struct mg_tcpip_driver_tm4c_data { - // MDC clock divider. MDC clock is derived from SYSCLK, must not exceed 2.5MHz - // SYSCLK range DIVIDER mdc_cr VALUE - // ------------------------------------- - // -1 <-- tell driver to guess the value - // 60-100 MHz SYSCLK/42 0 - // 100-150 MHz SYSCLK/62 1 <-- value for EK-TM4C129* on max speed - // 20-35 MHz SYSCLK/16 2 - // 35-60 MHz SYSCLK/26 3 - // 0x4-0xF Reserved - int mdc_cr; // Valid values: -1, 0, 1, 2, 3 -}; - -#ifdef __cplusplus -} -#endif -#endif // MONGOOSE_H diff --git a/SmartEVSE-3/SmartEVSE-3/include/utils.h b/SmartEVSE-3/SmartEVSE-3/include/utils.h deleted file mode 100644 index c30e34e..0000000 --- a/SmartEVSE-3/SmartEVSE-3/include/utils.h +++ /dev/null @@ -1,40 +0,0 @@ -/* -; Project: Smart EVSE -; -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. - */ - -// This is a guard condition so that contents of this file are not included -// more than once. -#ifndef UTILS_H -#define UTILS_H - -extern unsigned long pow_10[10]; - -uint32_t MacId(); -unsigned char crc8(unsigned char *buf, unsigned char len); -unsigned int crc16(unsigned char *buf, unsigned char len); -void sprintfl(char *str, const char *Format, signed long Value, unsigned char Divisor, unsigned char Decimal); -unsigned char triwave8(unsigned char in); -unsigned char scale8(unsigned char i, unsigned char scale); -unsigned char ease8InOutQuad(unsigned char i); - -#endif /* UTILS_H */ diff --git a/SmartEVSE-3/SmartEVSE-3/lib/README b/SmartEVSE-3/SmartEVSE-3/lib/README deleted file mode 100644 index 58415f5..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/README +++ /dev/null @@ -1,45 +0,0 @@ -This directory is intended for project specific (private) libraries. -PlatformIO will compile them to static libraries and link into executable file. - -The source code of each library should be placed in a an own separate directory -("lib/your_library_name/[here are source files]"). - -For example, see a structure of the following two libraries `Foo` and `Bar`: - -|--lib -| | -| |--Bar -| | |--docs -| | |--examples -| | |--src -| | |- Bar.c -| | |- Bar.h -| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html -| | -| |--Foo -| | |- Foo.c -| | |- Foo.h -| | -| |- README --> THIS FILE -| -|- platformio.ini -|--src - |- main.c - -and a contents of `src/main.c`: -``` -#include -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.github/ISSUE_TEMPLATE/bug_report.md b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index daf40f6..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. -2. -3. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Arduino Information:** - - OS: [e.g. Windows, MacOS, Linux] - - IDE [e.g. Arduino IDE, Eclipse, VSCode] - - IDE Version [e.g. 1.8.6] - - Board [e.g ESP32] - -**Additional context** -Add any other context about the problem here. diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.github/ISSUE_TEMPLATE/feature_request.md b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 066b2d9..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.gitignore b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.gitignore deleted file mode 100644 index e43b0f9..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/LICENSE.txt b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/LICENSE.txt deleted file mode 100755 index 6a981c5..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Joao Lopes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/README.md b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/README.md deleted file mode 100755 index d524463..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/README.md +++ /dev/null @@ -1,737 +0,0 @@ -# RemoteDebug Library - -A library for Arduino to debug projects over WiFi, with web app or telnet client, -with Print commands like Serial Monitor. - -![logo](extras/readme_media/logo.png) - -[![arduino-library-badge](https://www.ardu-badge.com/badge/RemoteDebug.svg?)](https://www.ardu-badge.com/RemoteDebug) -[![GitHub release](https://img.shields.io/github/release/JoaoLopesF/RemoteDebug.svg)](#releases) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/3eadfd19246f4808907cf53599a6b9f0)](https://www.codacy.com/app/JoaoLopesF/RemoteDebug?utm_source=github.com&utm_medium=referral&utm_content=JoaoLopesF/RemoteDebug&utm_campaign=Badge_Grade) -[![platform badge](https://img.shields.io/badge/platform-Arduino|Espressif-orange.svg)](https://github.com/arduino) -[![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/JoaoLopesF/RemoteDebug/blob/master/LICENSE.txt) -[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#github) -[![GitHub issues](https://img.shields.io/github/issues/JoaoLopesF/RemoteDebug.svg)](http://github.com/JoaoLopesF/RemoteDebug/issues) -[![star this repo](http://githubbadges.com/star.svg?user=JoaoLopesF&repo=RemoteDebug)](http://github.com/JoaoLopesF/RemoteDebug) - - -![remotedebugapp](extras/readme_media/remotedebugapp.png) - -## A library to remotely debug over a WiFi connection by telnet or web browser - -### RemoteDebug setup a TCP/IP server, that you connect to debugging, as an alternative to the serial connection - -## Contents - -- [About](#about) -- [How it looks](#how-it-looks) -- [Github](#github) -- [News](#news) -- [Benefits](#benefits) -- [HTML5 web app](#web-app) -- [Telnet client](#telnet) -- [Wishlist](#wishlist) -- [Install](#install) -- [Using](#usage) -- [Known issues](#known-issues) -- [Releases](#releases) -- [Thanks](#thanks) - -## About - -By default the Arduino only has as debug possibility via the Serial port. -This has a few disadvantages: - -- requires a physical cable to the Arduino device (if the device is far away or in a remote location this is not easy) -- debugging multiple Arduinos at the same time requires many serial ports and a lot of cables - -With the ESP8266 (NodeMCU) or ESP32 we now have network connectivity (WiFi) which can be used for streaming debugging information in real-time. - -This library is good for IoT projects, home automation, mobile robots (can debug it in moviment with a cable ?) or -another WiFi projects. - -In fact, this library was born of a need to debug an IoT project of home automation. -In this project there was a central module and three auxiliary modules, -and these were far from each other. One was hard to reach, under the roof of the house. -To debug this project, accompanying the exchange of messages in realtime, -is impossible to do with traditional way, by USB cable. - -The MiP_ESP8266_Library and my ESP32 WiFi robot are example of projects that uses __RemoteDebug__. -See it in: [MiP_ESP8266_Library](https://github.com/Tiogaplanet/MiP_ESP8266_Library) and [ESPlorer_v1](https://github.com/JoaoLopesF/ESPlorer_v1) - -__RemoteDebug__ is improved with client buffering (is last send is <= 10ms), -to avoid mysterious delays of WiFi networking on ESP32 and ESP8266 boards - -Note: If your project not use WiFi, you can use my another library, -the __[SerialDebug](https://github.com/JoaoLopesF/SerialDebug)__ library, -this library works with any Arduino board. - -Note II: __RemoteDebug__ library is now only to Espressif boards, as ESP32 and ESP8266, -If need for another WiFi boards, please add an issue about this -and we will see if it is possible made the port for your board. - -## How it looks - -Image: In RemoteDebugApp (web app) - -![webapp](extras/readme_media/remotedebug_webapp.png) - -Image: In telnet client - -![remotedebug_v2](extras/readme_media/remotedebug_v2.png) - -Youtube (RemoteDebug v2): - -[![youtube1](https://img.youtube.com/vi/T4nxdsFUGgg/0.jpg)](https://youtu.be/T4nxdsFUGgg) - -Youtube (3 telnet connections with RemoteDebug) v1: - -[![youtube2](http://img.youtube.com/vi/lOo-MAD8gPo/0.jpg)](http://www.youtube.com/watch?v=lOo-MAD8gPo) - -## Github - -Contribute to this library development by creating an account on GitHub. - -Please give a star, if you find this library useful, -this help an another people, discover it too. - -Please add an issue for problems or suggestion. - -## News - -- RemoteDebugApp Beta - - - Now have another repository, [RemoteDebugApp](https://github.com/JoaoLopesF/RemoteDebugApp) - It is for local copy of web app in internet. - It is updated with lastest version of web app, - after it is publised in web server: [http://joaolopesf.net/remotedebugapp](http://joaolopesf.net/remotedebugapp/). - Download it, for use when internet is offline. - As it is a local copy, the app will check for new versions periodically. - - - An HTML5 web app to use for debugging in web browser, instead telnet client, - that uses web socket to comunicate. - - - Now RemoteDebug v3 have a web socket server too, - to support the RemoteDebugApp connection. - - - RemoteDebugApp is in beta, - if you have any problems or suggestions, please add issue about this. - - - The telnet connection remains, to any want this, - or to internet offline uses. - -- Version 2.1.1 - - - Now __RemoteDebug__ have a code converter, for help you to convert your codes: - to do it, please access the [RemoteDebugConverter](https://github.com/JoaoLopesF/RemoteDebugConverter) - -- Version 2.0.0 - - - Now __RemoteDebug__ can have the same simple software debugger, that __SerialDebug__ library have. - This is done, installing another library, the __[RemoteDebugger](https://github.com/JoaoLopesF/RemoteDebugger)__ - The __RemoteDebugger__ act as an add on to __RemoteDebug__. - To support this addon, the changes in __RemoteDebug__, is minimum, just a few callbacks - It is done to no add extra overhead to projects that no need an debugger. - To more informations please access the __[RemoteDebugger](https://github.com/JoaoLopesF/RemoteDebugger)__ github repository. - - - Now __RemoteDebug__ have a new color system, using more colors, as done in __SerialDebugApp__ - - Note: due the __RemoteDebug__ library, is migrate to Arduino 1.5 format, with folder "src", - please delete and reinstall the library to avoid duplication of RemoteDebug sources files. - -- Version 1.5.* - - In 1.5.0 version, we have debug* and rdebug* macros (see below), that put automatically, - the name of function that called, and core id (core id is only for ESP32) - -## Benefits - -__SerialDebug__ is better than Arduino default debugging by Serial.print commands: - -### This is more __optimized__ - - Being or not debugging via USB cable, - the Serial.print command allways is processed, - waste CPU time on microcontroller. - In other words, the debug commands are processed, - with someone connected in the serial or not. - - With __RemoteDebug__, all debug output is processed only - if exists anyone debugging via telnet or web app connection. - - And with the debug levels of resource, the volume displayed - of messages are reduced for higher levels. - For example, only process all messages, - if the level is the lowest, the verbose, - - __RemoteDebug__ is otimized to reduce overheads, - in CPU and memory and include client buffering feature. - -### Have __debug levels__ - - During the development, we can put a lot of debug messages... - - But with __RemoteDebug__, we can put a level in each one. - - For all messages (except levels always (debugA) or error (debugE), - the message only is processed and showed, - if debug level is equal or higher than it level - - __RemoteDebug__ have 6 debug levels, in order of priority: - - Alway showed: - - __Error__: Critical errors - - __Always__: Important messages - - Another levels (showed if level is equal or higher that actual one): - - __Warning__: Error conditions but not critical - - __Info__: Information messages - - __Debug__: Extra information - - __Verbose__: More information than the usual - - So We can change the level to verbose, to see all messages. - Or to debug to see only debug or higher level, etc. - - Is very good to reduce a quantity of messages that a project can generate, - to help debugging. - -### It is __easy__ to migrate - - __RemoteDebug__ have a converter to help migrate your Arduino codes, - from Serial.prints to this library. - - [RemoteDebugConverter](https://github.com/JoaoLopesF/RemoteDebugConverter) - - Even if you want to do this manually, it's very simple. Please see topic [Using](#usage) above. - -### Have __auto__ function name and simple __profiler__ - - A simple debug: - - ```cpp - debugV("* Run time: %02u:%02u:%02u (VERBOSE)", mRunHours, mRunMinutes, mRunSeconds); - ```` - - Can generate this output in serial monitor: - - (V p:3065 loop C1) * Run time: 00:41:23 (VERBOSE) - - Where: V: is the level - p: is a profiler time, elased, between this and previous debug - loop: is a function name, that executed this debug - C1: is a core that executed this debug (and a function of this) (only for ESP32) - The remaining is the message formatted (printf) - - For ESP32, the core id in each debug is very good to optimizer multicore programming. - -### Have __commands__ to execute from telnet or web app - - For example: - -- Show help (__?__) -- Change the level of debug (__v__,__d__,__i__,__w__,__e__), - to show less or more messages. -- See memory (__m__) -- Reset the board (__reset__) - - See about __RemoteDebug__ commands below. - - You can add your own commands, see the examples please - - If your project have __[RemoteDebugger](https://github.com/JoaoLopesF/RemoteDebugger)__ installed, - have a new commands, e.g. call a function, see/change variables, ... - -### Have a simple __software debugger__ - - Now __RemoteDebug__ (version >= 2.0.0), have an simple software debuggger, - based in codes of SerialDebug library. - - This is another library, that act as an addon to __RemoteDebug__. - - Please acess the RemoteDebugger repository to more informations: __[RemoteDebugger](https://github.com/JoaoLopesF/RemoteDebugger)__ - -### Ready for __production__ (release compiler) - - For release your device, just uncomment DEBUG_DISABLED in your project - Done this, and no more debug processing. - And better for DEBUG_DISABLED, __RemoteDebug__ have ZERO overhead, - due is nothing of this is compiled. - -## Web app - -As SerialDebug, now RemoteDebug (v3) have an app,the RemoteDebugApp, -to debug in web browser. - -This app is an HTM5 web app, with websocket to comunicate to Arduino board. -For it, RemoteDebug v3 have a web socket server (can be disabled). -It used a local copy of [arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) library, -due it not in Arduino Library manager. - -As a large web page on web server, the solution for Arduino is save it in a storage, -like SPIFFS. But not have automatically updates new version in data saved this way, -this SPIFFS data is good for a project but not for a library. - -Due it, this app not is stored and served by board, -instead the app is in web: [http://joaolopesf.net/remotedebugapp](http://joaolopesf.net/remotedebugapp) -Note: this not uses SSL (https), due web server socket on Arduino, not supports SSL (wss). -But after page load, all traffic is in local network, no data is exposed on internet. - -The RemoteDebugApp is a modern HTML5 and needs a modern browsers to work. -Internet Explorer 11 and Safari 10 are an examples that not supported. -But you can use anothers, as Chrome, Edge, Firefox. - -The web app is in beta, please add an issue, -for problems or suggestions. - -Now have another repository, [RemoteDebugApp](https://github.com/JoaoLopesF/RemoteDebugApp) -It is for local copy of web app in internet. -It is updated with lastest version of web app, -after it is publised in web server: [http://joaolopesf.net/remotedebugapp](http://joaolopesf.net/remotedebugapp/). -Download it, for use when internet is offline. -As it is a local copy, the app will check for new versions periodically, -for you can download a new version. - -The telnet remains work, for when want this, -or for fails on web app. - -## Telnet - -Telnet is a standard way of remotely connecting to a server and -is supported on all operating systems (Windows, Mac, Linux...). - -MacOSx and Linux have a native telnet client. - -For Windows, a typical telnet client is the __Putty__: [putty](https://www.putty.org/) . - -Have a good tool for mobiles: the __Fing__, please find it in your mobile store. -Its show all devices in local network (WiFi), show ports opened and can execute the telnet client too (external App) - -__RemoteDebug__ sets-up a telnet server which is listening to any telnet client that wants to connect. After connection, logging is streamed to the telnet client. - -__RemoteDebug__ is very simple to use, after a few lines of initialization code, you can use the well-known "print" commands to stream your logging to the remote client. - -### Debug levels - -__RemoteDebug__ supports the filtering of logging based on __debug levels__: - -Only show for it actual debug level: - -- Verbose -- Debug -- Info -- Warnings - -Note: These levels are in the order of most-logging -> least-logging. - -Or for always show (not depends of actual debug level): - -- Any -- Errors - -Note: All debugs is processed and showed only if have a client connection. - -The telnet client or web app can set the debug level by typing a few simple commands. - -### Profiler - -__RemoteDebug__ includes a simple profiler. It can be enabled by the connected client (telnet or web app) -or the Arduino code itself. - -When enabled, it shows the time between 2 debug statements, using different colors depending on the elapsed time. - -A typical example would be to insert logging just before and after a function after which you can see how much the is spent in the function. - -### Lightweight - -__RemoteDebug__ is designed to give minimal overhead (connected or not) and -only process debugs,if there is a client (telnet or web app) connected. - -### Custom commands - -__RemoteDebug__ supports custom commands that can be entered in the client (telnet or web app). - These trigger the execution of a custom function in the Arduino code. For example this can be used to send back a status on request of the client. - -### DISCLAIMER - -The current version of __RemoteDebug__ does not yet include any encrypted authentication, -only plain text and is intended only for development, not use in production/release. - -Future versions, if is possible, will include a secure way for authentication and further testing to support production environments. - -## Wishlist - - - An app to RemoteDebug like SerialDebug have. - - Http page to begin/stop the telnet server or websocket server. - - Authentication as telnet support (kerberos, etc.) to support production environment - -## Install - -Just download or clone this repository. - -Or for Arduino IDE, you can use the library manager to install and update the library. - -For install help, please click on this: [![arduino-library-badge](https://www.ardu-badge.com/badge/RemoteDebug.svg?)](https://www.ardu-badge.com/RemoteDebug) - - - -For another IDE, or not using the library manager of Arduino IDE, -I suggest you use a [Github Desktop](https://desktop.github.com/) app to clone,it help to keep updated. - -Please open the projects in example folder, to see it working. - -## Usage - -### includes - -```cpp -#include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug -``` - -### instance - -RemoteDebug Debug; - -### setup - -In the setup function after WiFi initialization - -```cpp -// Initialize the server (telnet or web socket) of RemoteDebug - -Debug.begin(HOST_NAME); - -// OR - -Debug.begin(HOST_NAME, startingDebugLevel); - -// Options - -Debug.setResetCmdEnabled(true); // Enable the reset command - -// Debug.showProfiler(true); // To show profiler - time between messages of Debug - -``` - -Note: to enable the debugger, by RemoteDebugger, please acess this github repository: -__[RemoteDebugger](https://github.com/JoaoLopesF/RemoteDebugger)__ - -In the tail of loop function - -```cpp -// Remote debug over WiFi - -Debug.handle(); - -// Or - -debugHandle(); // Equal to SerialDebug - -``` - -In any place of you code: - -```cpp -#ifndef DEBUG_DISABLED -if (Debug.isActive(Debug.)) { - Debug.printf("bla bla bla: %d %s", number, str); // OR - Debug.printf("bla bla bla: %d %s", number, str.c_str()); // Note: if type is String need c_str() // OR - Debug.println("bla bla bla 2 ln"); - Debug.printf("float: %f\n", value); // Not works in ESP8266 :-( - // Note: to show floats with printf (ESP8266 only), - // you can use my ArduinoUtil library -> https://github.com/JoaoLopesF/ArduinoUtil - Debug.printf("float: %s\n", Util.formatFloat(value, 0, 5).c_str()); -} -#endif -``` - -Note: Using isActive, you need surround the code by DEBUG_DISABLE precompile condition, - to avoid compile it for production/release - -Or short way (equal to SerialDebug) (prefered if only one debug at time): - -```cpp -debugA("This is a any (always showed) - var %d", var); -debugV("This is a verbose - var %d", var); -debugD("This is a debug - var %d", var); -debugI("This is a information - var %d", var); -debugW("This is a warning - var %d", var); -debugE("This is a error - var %d", var); - -debugV("This is a println"); -``` - -Or if your project uses several Serial.print commands to generate a single debug message -for example: - -```cpp -Serial.print("a = "); -Serial.print(a); -Serial.print(" b = "); -Serial.print(b); -Serial.print(" c = "); -Serial.println(c); -``` - -can be use rdebug* macros: - -```cpp -rdebugV("a = "); -rdebugV(a); -rdebugV(" b = "); -rdebugV(b); -rdebugV(" c = "); -rdebugVln(c); -``` - -Note: in future, I suggest that you migrate this to a single debug command: - -```cpp -debugV(a = %d b = %d c = %d", a, b, c); -``` - -An example of use debug levels: (supposing the data is a lot of characters) - -```cpp -if (Debug.isActive(Debug.VERBOSE)) { // Debug message long - Debug.printf("routine: data received: %s\n", data.c_str()); // Note: if type is String need c_str() -} else if (Debug.isActive(Debug.DEBUG)) { // Debug message short - Debug.printf("routine: data received: %s ...\n", data.substring(0, 20).c_str()); // %.20s not working :-| -} -``` - -Starting at version 1.5.0, debug macros (debug* and rdebug*), automatically put the name of function that called the macro, -and core id (core id only for ESP32). - -So: - -```cpp -void foo() { - - uint8_t var = 1; - debugV("this is a debug - var %u", var); -} - -``` - - It will show in client (telnet or web app): - - (V p:^0000ms) (foo)(C1) this is a debug - var 1 - - Where: - - V -> verbose - p -> profiler time - (foo) -> this is a function name that calls the debug macro - (C1) -> It is running it Core 1 (only for ESP32) - -An example of use debug with serial enabled - - Useful to see messages if setup or - in cause the ESP8266/ESP32 is rebooting (client connection stop before received all messages) - Only for this purposes I suggest it - -```cpp -// Setup after Debug.begin - -Debug.setSerialEnabled(true); // All messages too send to serial too, and can be see in serial monitor -``` - -For reduce overheads RemoteDebug is disconnect the client (telnet or web app), if it not active. - - - Please press enter or any key if you need keep the connection - - The default is 5 minutes (You can change it in RemoteDebug.h) - - You can use mDNS to register each node with different name, it helps to connect without know the IP. - -Please not forget to use if clause with Debug.isActive (if not using debug macros) - - ---> This is very important to reduce overheads and work of debug levels - -Please see the samples, basic or advanced, to learn how to use - -In advanced sample, I used WifiManager library, ArduinoOTA and mDNS, please see it. - -## Releases - -### 3.0.5 - 2019-03-23 - - - Ajustment on debugA macro, thanks @jetpax and @cmidgley to add this issue. - -### 3.0.4 - 2019-03-19 - - - All public configurations (#defines) have moved to RemoteDebugCfg.h, to facilitate changes for anybody. - - Changed examples, with warnings on change any #define in project, - with workarounds if it not work. (thanks to @22MarioZ for added this issue) - -### 3.0.3 - 2019-03-18 - - - Adjustments if web socket is disabled - -### 3.0.2 - 2019-03-16 - - - Adjustments in examples, added one for debugger - -### 3.0.1 - 2019-03-13 - - - Adjustments in silente mode - - Commands from RemoteDebugApp now is treated - - Adjusts to RemoteDebugger support connection by web sockets - -### 3.0.0 - 2019-03-10 - - - If not disabled, add a web socket server to comunicate with RemoteDebugApp (HTML5 web app) - - The standard telnet still working, to debug with internet offline - - Ajustment on debugA macro, thanks @jetpax to add this issue - -### 2.1.2 - 2019-03-08 - - - Add empty rprint* macros, if debug is disabled - -### 2.1.1 - 2019-03-06 - - - Create option DEBUG_DISABLE_AUTO_FUNC - - Create macros to be used for code converter: rprint and rprintln - RemoteDebug now have an code converters to help migrate codes - -### 2.1.0 - 2019-03-04 - - - Create precompiler DEBUG_DISABLED to compile for production/release, - equal that have in SerialDebug - - Adjustments in examples - -### 2.0.1 - 2019-03-01 - - - Adjustments for the debugger: it still disable until dbg command, equal to SerialDebug - - The callback will to be called before print debug messages now - - And only if debugger is enabled in RemoteDebugger (command dbg) - - Changed handle debugger logic - -### 2.0.0 - 2019-02-28 - - - Added support to RemoteDebug addon library: the RemoteDebugger, an simple software debugger, based on SerialDebug - - New color system - -### 1.5.9 - 2019-02-18 - - - Bug -> sometimes the command is process twice - - Workaround -> check time - -### 1.5.8 - 2019-02-08 - - - New macros to compatibility with SerialDebug (can use RemoteDebug or SerialDebug) thanks to @phrxmd - -### 1.5.7 - 2018-11-03 - - - Fixed bug for MAX_TIME_INACTIVE, thanks to @achuchev to add this issue - -### 1.5.6 - 2018-10-19 - - - Adjustments based on pull request from @jeroenst (to allow serial output with telnet password and setPassword method) - -### 1.5.5 - 2018-10-19 - - - Serial output is now not allowed if telnet password is enabled - - Few adjustments - -### 1.5.4 - 2018-10-05 - - - Few adjustment in write logic - -### 1.5.3 - 2018-09-04 - - - Serial output adjustments (due bug in password logic) - -### 1.5.2 - - - Correct rdebug macro (thanks @stritti) - -### 1.5.1 - 2018-08-28 - - - New silent mode (command s) - -### 1.5.0 - 2018-08=26 - - - Auto function name and ESP32 core id for rdebug* macros - - begin method have a option for port number - - Few adjustments - - Added new rdebug?ln to put auto new line - -### 1.4.0 - 2018-08-18 - - - Simple text password request feature (disabled by default) - - Notes: - It is very simple feature, only text, no cryptography, - and the password is echoed in screen (I not discovery yet how disable it) - - telnet use advanced authentication (kerberos, etc.) - Such as now RemoteDebug is not for production (releases), - this kind of authentication will not be done now. - -### 1.3.1 - 2018-08-18 - - - Adjustments in precompiler macros - -### 1.3.0 - 2018-08-17 - - - Bug in write with latest ESP8266 SDK - - Port number can be modified in project Arduino (.ino file) - - Few adjustments as ESP32 includes - -### 1.2.2 - - - Adjustments, as avoid ESP32 include errors - - Telnet port of server can be modified by project - Just put it in your .ino, before the include: - -### 1.2.0 - - - Shortcuts and client buffering to avoid mysterious delay of ESP networking - -### 1.1.0 - - - Adjustments and now runs in Esp32 too. - -### 1.0.0 - - - Adjustments and improvements from Beta versions. - - New features: - - - Filter - - Colors - - Support to Windows telnet client - -### 0.9 - - - First Beta - -## Known issues - - - Sometimes (rarely) the connection over telnet becomes very slow. - Especially right after uploading firmware. - Reset command in telnet connection or turn off/on can be resolve it. - But I need find why it occurs - -## Thanks - - First thanks a lot for Igrr for bring to us the Arduino ESP8266 and to Espressif to Arduino ESP32 - - Thanks to Links2004 for a good web server socket, used for web app connection. - - For the logo: thanks to a freepik and pngtree sites for free icons that have in logo - - Resources: - - - Example of TelnetServer code in http://www.rudiswiki.de/wiki9/WiFiTelnetServer - - arduinoWebSockets library in https://github.com/Links2004/arduinoWebSockets - -## End of README - -Hit counter on this file, starting at 2019-03-03: -[![HitCount](http://hits.dwyl.io/JoaoLopesF/RemoteDebug.svg)](http://hits.dwyl.io/JoaoLopesF/RemoteDebug) diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/examples/RemoteDebug_Advanced/RemoteDebug_Advanced.ino b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/examples/RemoteDebug_Advanced/RemoteDebug_Advanced.ino deleted file mode 100644 index c96f1f7..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/examples/RemoteDebug_Advanced/RemoteDebug_Advanced.ino +++ /dev/null @@ -1,634 +0,0 @@ -//////// -// Library: Remote debug - debug over WiFi - for Esp8266 (NodeMCU) or ESP32 -// Author : Joao Lopes -// File : RemoteDebug_Advanced.ino -// Notes : -// -// Attention: This library is only for help development. Please not use this in production -// -// Sample to show how to use advanced features of Arduino and RemoteDebug library -// -// Example of use: -// -//#ifndef DEBUG_DISABLED -// if (Debug.isActive(Debug.)) { // <--- This is very important to reduce overheads and work of debug levels -// Debug.printf("bla bla bla: %d %s\n", number, str); -// Debug.println("bla bla bla"); -// } -//#endif -// -// Or short way (prefered if only one debug at time) -// -// debugA("This is a any (always showed) - var %d", var); -// -// debugV("This is a verbose - var %d", var); -// debugD("This is a debug - var %d", var); -// debugI("This is a information - var %d", var); -// debugW("This is a warning - var %d", var); -// debugE("This is a error - var %d", var); -// -// debugV("This is println"); -// -/////// - -////// Defines - -// Host name (please change it) - -#define HOST_NAME "remotedebug" - -// Board especific libraries - -#if defined ESP8266 || defined ESP32 - -// Use mDNS ? (comment this do disable it) - -#define USE_MDNS true - -// Arduino OTA (uncomment this to enable) - -//#define USE_ARDUINO_OTA true - -#else - -// RemoteDebug library is now only to Espressif boards, -// as ESP32 and ESP82266, -// If need for another WiFi boards, -// please add an issue about this -// and we will see if it is possible made the port for your board. -// access: https://github.com/JoaoLopesF/RemoteDebug/issues - -#error "The board must be ESP8266 or ESP32" - -#endif // ESP - -// Web server (uncomment this to need this) - -//#define WEB_SERVER_ENABLED true - -////// Includes - -#if defined ESP8266 - -// Includes of ESP8266 - -#include - -#ifdef USE_MDNS -#include -#include -#endif - -#ifdef WEB_SERVER_ENABLED -#include -#endif - -#elif defined ESP32 - -// Includes of ESP32 - -#include - -#ifdef USE_MDNS -#include -#include "ESPmDNS.h" -#endif - -#ifdef WEB_SERVER_ENABLED -#include -#endif - -#else - -#error "For now, RemoteDebug support only boards Espressif, as ESP8266 and ESP32" - -#endif // ESP - -// Arduino OTA - -#ifdef USE_ARDUINO_OTA -#include -#endif - -// HTTP Web server - -#ifdef WEB_SERVER_ENABLED - -#if defined ESP8266 - -ESP8266WebServer HTTPServer(80); - -#elif defined ESP32 - -WebServer HTTPServer(80); - -#endif - -#endif // WEB_SERVER_ENABLED - -///// Remote debug over WiFi - not recommended for production/release, only for development - -// Options for RemoteDebug of this project - -// Attention: read this, before you change any option -// -// If yot changed it and not works, the compiler is using catching old compiled files -// To workaround this: -// - If have a clean project option in IDE (as Eclipse/Platformio), do it -// - or force compiler to compiler all (changing any configuration of board) -// - or to this change globally in RemoteDebugCfg.h (on library directory) -// - And upload again -// -// thanks to @22MarioZ for added this issue - -// Disable all debug ? -// Important to compile for prodution/release -// Disable all debug ? Good to release builds (production) -// as nothing of RemoteDebug is compiled, zero overhead :-) -// Uncomment the line below, to do it: -//#define DEBUG_DISABLED true - -// Disable te auto function feature of RemoteDebug -// Good if your code already have func name on debug messages -// Uncomment the line below, to do it: -//#define DEBUG_DISABLE_AUTO_FUNC true - -// Disable Websocket? This is used with RemoteDebugApp connection -// Uncomment the line below, to do it: -//#define WEBSOCKET_DISABLED true - -#ifndef WEBSOCKET_DISABLED // Only if Web socket enabled (RemoteDebugApp) -// If enabled, you can change the port here (8232 is default) -// Uncomment the line below, to do it: -//#define WEBSOCKET_PORT 8232 - -// Internally, the RemoteDebug uses a local copy of the arduinoWebSockets library (https://github.com/Links2004/arduinoWebSockets) -// Due it not in Arduino Library Manager -// If your project already use this library, -// Uncomment the line below, to do it: -//#define USE_LIB_WEBSOCKET true -#endif - -// Include libraries - -#include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug - -#ifndef DEBUG_DISABLED // Only if debug is not disabled (for production/release) - -// Instance of RemoteDebug - -RemoteDebug Debug; - -#endif - -// WiFi credentials -// Note: if commented, is used the smartConfig -// That allow to it in mobile app -// See more details in http://www.iotsharing.com/2017/05/how-to-use-smartconfig-on-esp32.html - -//#define WIFI_SSID "..." // your network SSID (name) -//#define WIFI_PASS "..." // your network key - -/////// Variables - -// Time - -uint32_t mTimeToSec = 0; -uint32_t mTimeSeconds = 0; - -////// Setup - -void setup() { - - // Initialize the Serial (use only in setup codes) - - Serial.begin(230400); - - // Buildin led - -#ifdef LED_BUILTIN - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LOW); -#endif - - // Connect WiFi - - connectWiFi(); - - // Host name of WiFi - -#ifdef ESP8266 - WiFi.hostname(HOST_NAME); -#endif - -#ifdef USE_ARDUINO_OTA - // Update over air (OTA) - - initializeOTA(); -#endif - - // Register host name in mDNS - -#if defined USE_MDNS && defined HOST_NAME - - if (MDNS.begin(HOST_NAME)) { - Serial.print("* MDNS responder started. Hostname -> "); - Serial.println(HOST_NAME); - } - - // Register the services - -#ifdef WEB_SERVER_ENABLED - MDNS.addService("http", "tcp", 80); // Web server -#endif - -#ifndef DEBUG_DISABLED - MDNS.addService("telnet", "tcp", 23); // Telnet server of RemoteDebug, register as telnet -#endif - -#endif // MDNS - - // HTTP web server - -#ifdef WEB_SERVER_ENABLED - HTTPServer.on("/", handleRoot); - - HTTPServer.onNotFound(handleNotFound); - - HTTPServer.begin(); - - Serial.println("* HTTP server started"); -#endif - -#ifndef DEBUG_DISABLED // Only for development - - // Initialize RemoteDebug - - Debug.begin(HOST_NAME); // Initialize the WiFi server - - //Debug.setPassword("r3m0t0."); // Password for WiFi client connection (telnet or webapp) ? - - Debug.setResetCmdEnabled(true); // Enable the reset command - - Debug.showProfiler(true); // Profiler (Good to measure times, to optimize codes) - - Debug.showColors(true); // Colors - - // Debug.setSerialEnabled(true); // if you wants serial echo - only recommended if ESP is plugged in USB - - // Project commands - - String helpCmd = "bench1 - Benchmark 1\n"; - helpCmd.concat("bench2 - Benchmark 2"); - - Debug.setHelpProjectsCmds(helpCmd); - Debug.setCallBackProjectCmds(&processCmdRemoteDebug); - - // End of setup - show IP - - Serial.println("* Arduino RemoteDebug Library"); - Serial.println("*"); - Serial.print("* WiFI connected. IP address: "); - Serial.println(WiFi.localIP()); - Serial.println("*"); - Serial.println("* Please use the telnet client (telnet for Mac/Unix or putty and others for Windows)"); - Serial.println("* or the RemoteDebugApp (in browser: http://joaolopesf.net/remotedebugapp)"); - Serial.println("*"); - Serial.println("* This sample will send messages of debug in all levels."); - Serial.println("*"); - Serial.println("* Please try change debug level in client (telnet or web app), to see how it works"); - Serial.println("*"); - -#endif - -} - -void loop() { - -#ifndef DEBUG_DISABLED - // Time of begin of this loop - uint32_t timeBeginLoop = millis(); -#endif - - - // Each second - - if (millis() >= mTimeToSec) { - - // Time - - mTimeToSec = millis() + 1000; - - mTimeSeconds++; - - // Blink the led - -#ifdef LED_BUILTIN - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); -#endif - - // Debug the time (verbose level) - - debugV("* Time: %u seconds (VERBOSE)", mTimeSeconds); - - if (mTimeSeconds % 5 == 0) { // Each 5 seconds - - // Debug levels - - debugV("* This is a message of debug level VERBOSE"); - debugD("* This is a message of debug level DEBUG"); - debugI("* This is a message of debug level INFO"); - debugW("* This is a message of debug level WARNING"); - debugE("* This is a message of debug level ERROR"); - - - // RemoteDebug isActive? Use this RemoteDebug sintaxe if you need process anything only for debug - // It is good to avoid overheads (this is only use that is suggest to use isActive) - // Note this need be surrounded by DEBUG_DISABLED precompiler condition to not compile for production/release - -#ifndef DEBUG_DISABLED - - if (Debug.isActive(Debug.VERBOSE)) { - - debugV("Calling a foo function"); - debugV("At time of %d sec.\n", mTimeSeconds); - - // Call a function - - foo(); - } -#endif - - } - } - - ////// Services on Wifi - -#ifdef USE_ARDUINO_OTA - // Update over air (OTA) - - ArduinoOTA.handle(); -#endif - -#ifdef WEB_SERVER_ENABLED - // Web server - - HTTPServer.handleClient(); -#endif - -#ifndef DEBUG_DISABLED - // RemoteDebug handle (for WiFi connections) - - Debug.handle(); -#endif - - // Give a time for ESP - - yield(); - -#ifndef DEBUG_DISABLED - // Show a debug - warning if time of these loop is over 50 (info) or 100 ms (warning) - - uint32_t time = (millis() - timeBeginLoop); - - if (time > 100) { - debugI("* Time elapsed for the loop: %u ms.", time); - } else if (time > 200) { - debugW("* Time elapsed for the loop: %u ms.", time); - } -#endif - -} - - -// Function example to show a new auto function name of debug* macros - -void foo() { - - uint8_t var = 1; - - debugV("this is a debug - var %u", var); - debugV("This is a println"); -} - -#ifndef DEBUG_DISABLED - -// Process commands from RemoteDebug - -void processCmdRemoteDebug() { - - String lastCmd = Debug.getLastCommand(); - - if (lastCmd == "bench1") { - - // Benchmark 1 - Printf - - debugA("* Benchmark 1 - one Printf"); - - - uint32_t timeBegin = millis(); - uint8_t times = 50; - - for (uint8_t i = 1; i <= times; i++) { - debugA("%u - 1234567890 - AAAA", i); - - } - - debugA("* Time elapsed for %u printf: %ld ms.\n", times, - (millis() - timeBegin)); - - } else if (lastCmd == "bench2") { - - // Benchmark 2 - Print/println - - debugA("* Benchmark 2 - Print/Println"); - - uint32_t timeBegin = millis(); - uint8_t times = 50; - - for (uint8_t i = 1; i <= times; i++) { - if (Debug.isActive(Debug.ANY)) { - Debug.print(i); - Debug.print(" - 1234567890"); - Debug.println(" - AAAA"); - } - } - - debugA("* Time elapsed for %u printf: %ld ms.\n", times, - (millis() - timeBegin)); - } -} -#endif - -////// WiFi - -void connectWiFi() { - - ////// Connect WiFi - -#ifdef EM_DEPURACAO - Serial.println("*** connectWiFi: begin conection ..."); -#endif - -#ifdef ESP32 - // ESP32 // TODO: is really necessary ? - WiFi.enableSTA(true); - delay(100); -#endif - - // Connect with SSID and password stored - -#ifndef WIFI_SSID - WiFi.begin(); -#else - WiFi.begin(WIFI_SSID, WIFI_PASS); -#endif - - // Wait connection - - uint32_t timeout = millis() + 20000; // Time out - - while (WiFi.status() != WL_CONNECTED && millis() < timeout) { - delay(250); - Serial.print("."); - } - - // Not connected yet? - - if (WiFi.status() != WL_CONNECTED) { - -#ifndef WIFI_SSID - // SmartConfig - - WiFi.beginSmartConfig(); - - // Wait for SmartConfig packet from mobile - - Serial.println("connectWiFi: Waiting for SmartConfig."); - - while (!WiFi.smartConfigDone()) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("connectWiFi: SmartConfig received."); - - // Wait for WiFi to connect to AP - - Serial.println("connectWiFi: Waiting for WiFi"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } -#else - Serial.println("Not possible connect to WiFi, rebooting"); - ESP.restart(); -#endif - } - - // End - - Serial.println(""); - Serial.print("connectWiFi: connect a "); - Serial.println(WiFi.SSID()); - Serial.print("IP: "); - Serial.println(WiFi.localIP().toString()); - -} - -#ifdef USE_ARDUINO_OTA - -// Initialize o Arduino OTA - -void initializeOTA() { - - // TODO: option to authentication (password) - -#if defined ESP8266 - - ArduinoOTA.onStart([]() { - Serial.println("* OTA: Start"); - }); - ArduinoOTA.onEnd([]() { - Serial.println("\n*OTA: End"); - }); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - Serial.printf("*OTA: Progress: %u%%\r", (progress / (total / 100))); - }); - ArduinoOTA.onError([](ota_error_t error) { - Serial.printf("*OTA: Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); - else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); - else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); - else if (error == OTA_END_ERROR) Serial.println("End Failed"); - }); - -#elif defined ESP32 - - // ArduinoOTA - - ArduinoOTA.onStart([]() { - String type; - if (ArduinoOTA.getCommand() == U_FLASH) - type = "sketch"; - else // U_SPIFFS - type = "filesystem"; - Serial.println("Start updating " + type); - }).onEnd([]() { - Serial.println("\nEnd"); - }).onProgress([](unsigned int progress, unsigned int total) { - Serial.printf("Progress: %u%%\r", (progress / (total / 100))); - }).onError([](ota_error_t error) { - Serial.printf("Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); - else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); - else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); - else if (error == OTA_END_ERROR) Serial.println("End Failed"); - }); - -#endif - - // Begin - - ArduinoOTA.begin(); - -} - -#endif - -#ifdef WEB_SERVER_ENABLED - -/////////// Handles - - void handleRoot() { - - // Root web page - - HTTPServer.send(200, "text/plain", "hello from esp - RemoteDebug Sample!"); - } - - void handleNotFound(){ - - // Page not Found - - String message = "File Not Found\n\n"; - message.concat("URI: "); - message.concat(HTTPServer.uri()); - message.concat("\nMethod: "); - message.concat((HTTPServer.method() == HTTP_GET)?"GET":"POST"); - message.concat("\nArguments: "); - message.concat(HTTPServer.args()); - message.concat("\n"); - for (uint8_t i=0; i)) { // <--- This is very important to reduce overheads and work of debug levels -// Debug.printf("bla bla bla: %d %s\n", number, str); -// Debug.println("bla bla bla"); -// } -//#endif -// -// Or short way (prefered if only one debug at time) -// -// debugA("This is a any (always showed) - var %d", var); -// -// debugV("This is a verbose - var %d", var); -// debugD("This is a debug - var %d", var); -// debugI("This is a information - var %d", var); -// debugW("This is a warning - var %d", var); -// debugE("This is a error - var %d", var); -// -// debugV("This is println"); -// -// -/////// - -////// Defines - -// Host name (please change it) - -#define HOST_NAME "remotedebug" - -// Board especific libraries - -#if defined ESP8266 || defined ESP32 - -// Use mDNS ? (comment this do disable it) - -#define USE_MDNS true - -// Arduino OTA (uncomment this to enable) - -//#define USE_ARDUINO_OTA true - -#else - -// RemoteDebug library is now only to Espressif boards, -// as ESP32 and ESP82266, -// If need for another WiFi boards, -// please add an issue about this -// and we will see if it is possible made the port for your board. -// access: https://github.com/JoaoLopesF/RemoteDebug/issues - -#error "The board must be ESP8266 or ESP32" - -#endif // ESP - -//////// Libraries - -#if defined ESP8266 - -// Includes of ESP8266 - -#include - -#ifdef USE_MDNS -#include -#include -#endif - -#elif defined ESP32 - -// Includes of ESP32 - -#include - -#ifdef USE_MDNS -#include -#include "ESPmDNS.h" -#endif - -#endif // ESP - -// Remote debug over WiFi - not recommended for production, only for development - -#include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug - -RemoteDebug Debug; - -// SSID and password - -const char* ssid = "........"; -const char* password = "........"; - -// Time - -uint32_t mLastTime = 0; -uint32_t mTimeSeconds = 0; - -////// Setup - -void setup() { - - // Initialize the Serial (use only in setup codes) - - Serial.begin(230400); - - // Buildin led of ESP - - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LOW); - - // Debug - - Serial.println("**** Setup: initializing ..."); - - // WiFi connection - - WiFi.begin(ssid, password); - Serial.println(""); - - // Wait for connection - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.print("Connected to "); - Serial.println(ssid); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - // Register host name in WiFi and mDNS - - String hostNameWifi = HOST_NAME; - hostNameWifi.concat(".local"); - -#ifdef ESP8266 // Only for it - WiFi.hostname(hostNameWifi); -#endif - -#ifdef USE_MDNS // Use the MDNS ? - - if (MDNS.begin(HOST_NAME)) { - Serial.print("* MDNS responder started. Hostname -> "); - Serial.println(HOST_NAME); - } - - MDNS.addService("telnet", "tcp", 23); - -#endif - - // Initialize RemoteDebug - - Debug.begin(HOST_NAME); // Initialize the WiFi server - - Debug.setResetCmdEnabled(true); // Enable the reset command - - Debug.showProfiler(true); // Profiler (Good to measure times, to optimize codes) - Debug.showColors(true); // Colors - - // End off setup - - Serial.println("* Arduino RemoteDebug Library"); - Serial.println("*"); - Serial.print("* WiFI connected. IP address: "); - Serial.println(WiFi.localIP()); - Serial.println("*"); - Serial.println("* Please use the telnet client (telnet for Mac/Unix or putty and others for Windows)"); - Serial.println("* or the RemoteDebugApp (in browser: http://joaolopesf.net/remotedebugapp)"); - Serial.println("*"); - Serial.println("* This sample will send messages of debug in all levels."); - Serial.println("*"); - Serial.println("* Please try change debug level in client (telnet or web app), to see how it works"); - Serial.println("*"); - -} - -void loop() -{ - // Each second - - if ((millis() - mLastTime) >= 1000) { - - // Time - - mLastTime = millis(); - - mTimeSeconds++; - - // Blink the led - - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); - - // Debug the time (verbose level) - - debugV("* Time: %u seconds (VERBOSE)", mTimeSeconds); - - if (mTimeSeconds % 5 == 0) { // Each 5 seconds - - // Debug levels - - debugV("* This is a message of debug level VERBOSE"); - debugD("* This is a message of debug level DEBUG"); - debugI("* This is a message of debug level INFO"); - debugW("* This is a message of debug level WARNING"); - debugE("* This is a message of debug level ERROR"); - - // Call a function - - foo(); - } - } - - // RemoteDebug handle - - Debug.handle(); - - // Give a time for ESP - - yield(); - -} - -// Function example to show a new auto function name of debug* macros - -void foo() { - - uint8_t var = 1; - - debugV("this is a debug - var %u", var); - debugV("This is a println"); -} - -/////////// End diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/examples/RemoteDebug_Debugger/RemoteDebug_Debugger.ino b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/examples/RemoteDebug_Debugger/RemoteDebug_Debugger.ino deleted file mode 100644 index 88b52a9..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/examples/RemoteDebug_Debugger/RemoteDebug_Debugger.ino +++ /dev/null @@ -1,893 +0,0 @@ -//////// -// Library: Remote debug - debug over WiFi - for Esp8266 (NodeMCU) or ESP32 -// Author : Joao Lopes -// File : RemoteDebugger.ino -// Notes : -// -// Attention: This library is only for help development. Please not use this in production -// -// This example use the RemoteDebugger library: ao addon to Remotedebug with an simple software debug, based on SerialDebug library -// Attention: this library must be installed too -// Please open github repo to informations: //https://github.com/JoaoLopesF/RemoteDebugger -// -// Example of use: -// -//#ifndef DEBUG_DISABLED -// if (Debug.isActive(Debug.)) { // <--- This is very important to reduce overheads and work of debug levels -// Debug.printf("bla bla bla: %d %s\n", number, str); -// Debug.println("bla bla bla"); -// } -//#endif -// -// Or short way (prefered if only one debug at time) -// -// debugA("This is a any (always showed) - var %d", var); -// -// debugV("This is a verbose - var %d", var); -// debugD("This is a debug - var %d", var); -// debugI("This is a information - var %d", var); -// debugW("This is a warning - var %d", var); -// debugE("This is a error - var %d", var); -// -// debugV("This is println"); -// -/////// - -////// Defines - -// Host name (please change it) - -#define HOST_NAME "remotedebug" - -// Board especific libraries - -#if defined ESP8266 || defined ESP32 - -// Use mDNS ? (comment this do disable it) - -#define USE_MDNS true - -// Arduino OTA (uncomment this to enable) - -//#define USE_ARDUINO_OTA true - -#else - -// RemoteDebug library is now only to Espressif boards, -// as ESP32 and ESP82266, -// If need for another WiFi boards, -// please add an issue about this -// and we will see if it is possible made the port for your board. -// access: https://github.com/JoaoLopesF/RemoteDebug/issues - -#error "The board must be ESP8266 or ESP32" - -#endif // ESP - -// Web server (uncomment this to need this) - -//#define WEB_SERVER_ENABLED true - -////// Includes - -#if defined ESP8266 - -// Includes of ESP8266 - -#include - -#ifdef USE_MDNS -#include -#include -#endif - -#ifdef WEB_SERVER_ENABLED -#include -#endif - -#elif defined ESP32 - -// Includes of ESP32 - -#include - -#ifdef USE_MDNS -#include -#include "ESPmDNS.h" -#endif - -#ifdef WEB_SERVER_ENABLED -#include -#endif - -#else - -#error "Now RemoteDebug support only boards Espressif, as ESP8266 and ESP32" - -#endif // ESP - -// Arduino OTA - -#ifdef USE_ARDUINO_OTA -#include -#endif - -// HTTP Web server - -#ifdef WEB_SERVER_ENABLED - -#if defined ESP8266 - -ESP8266WebServer HTTPServer(80); - -#elif defined ESP32 - -WebServer HTTPServer(80); - -#endif - -#endif // WEB_SERVER_ENABLED - -// Remote debug over WiFi - not recommended for production/release, only for development - -// Options for RemoteDebug of this project - -// Attention: read this, before you change any option -// -// If yot changed it and not works, the compiler is using catching old compiled files -// To workaround this: -// - If have a clean project option (as Eclipse/Platformio), do it -// - or force compiler to compiler all (changing any configuration of board) -// - or to this change globally in RemoteDebugCfg.h (on library directory) -// - And upload again -// -// thanks to @22MarioZ for added this issue - -// Disable all debug ? -// Important to compile for prodution/release -// Disable all debug ? Good to release builds (production) -// as nothing of RemoteDebug is compiled, zero overhead :-) -// Uncomment the line below, to do it: -//#define DEBUG_DISABLED true - -// Disable te auto function feature of RemoteDebug -// Good if your code already have func name on debug messages -// Uncomment the line below, to do it: -//#define DEBUG_DISABLE_AUTO_FUNC true - -// Disable Websocket? This is used with RemoteDebugApp connection -// Uncomment the line below, to do it: -//#define WEBSOCKET_DISABLED true - -#ifndef WEBSOCKET_DISABLED // Only if Web socket enabled (RemoteDebugApp) -// If enabled, you can change the port here (8232 is default) -// Uncomment the line below, to do it: -//#define WEBSOCKET_PORT 8232 - -// Internally, the RemoteDebug uses a local copy of the arduinoWebSockets library (https://github.com/Links2004/arduinoWebSockets) -// Due it not in Arduino Library Manager -// If your project already use this library, -// Uncomment the line below, to do it: -//#define USE_LIB_WEBSOCKET true -#endif - - -#include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug - -#ifndef DEBUG_DISABLED // Only if debug is not disabled (for production/release) - -// RemoteDebug addon library: RemoteDebugger, an Simple software debugger - based on SerialDebug Library - -#include "RemoteDebugger.h" //https://github.com/JoaoLopesF/RemoteDebugger - -// Instance of RemoteDebug - -RemoteDebug Debug; - -#endif - -// WiFi credentials -// Note: if commented, is used the smartConfig -// That allow to it in mobile app -// See more details in http://www.iotsharing.com/2017/05/how-to-use-smartconfig-on-esp32.html - -//#define WIFI_SSID "..." // your network SSID (name) -//#define WIFI_PASS "..." // your network key - -/////// Variables - -// Time - -uint32_t mTimeToSec = 0; - -uint8_t mRunSeconds = 0; -uint8_t mRunMinutes = 0; -uint8_t mRunHours = 0; - -// Globals for example of debugger - -boolean mBoolean = false; -char mChar = 'X'; -byte mByte = 'Y'; -int mInt = 1; -unsigned int mUInt = 2; -long mLong = 3; -unsigned long mULong = 4; -float mFloat = 5.0f; -double mDouble = 6.0; - -String mString = "This is a string"; -String mStringLarge = "This is a large stringggggggggggggggggggggggggggggggggggggggggggggg"; - -char mCharArray[] = "This is a char array"; -char mCharArrayLarge[] = "This is a large char arrayyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"; - -int mIntArray[5] = {1 ,2 ,3, 4, 5}; - -//const char mCharArrayConst[] = "This is const"; - -////// Setup - -void setup() { - - // Initialize the Serial (use only in setup codes) - - Serial.begin(230400); - - // Buildin led - -#ifdef LED_BUILTIN - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LOW); -#endif - - // Connect WiFi - - connectWiFi(); - - // Host name of WiFi - -#ifdef ESP8266 - WiFi.hostname(HOST_NAME); -#endif - -#ifdef USE_ARDUINO_OTA - // Update over air (OTA) - - initializeOTA(); -#endif - - // Register host name in mDNS - -#if defined USE_MDNS && defined HOST_NAME - - if (MDNS.begin(HOST_NAME)) { - Serial.print("* MDNS responder started. Hostname -> "); - Serial.println(HOST_NAME); - } - - // Register the services - -#ifdef WEB_SERVER_ENABLED - MDNS.addService("http", "tcp", 80); // Web server -#endif - -#ifndef DEBUG_DISABLED - MDNS.addService("telnet", "tcp", 23);// Telnet server RemoteDebug -#endif - -#endif // MDNS - - // HTTP web server - -#ifdef WEB_SERVER_ENABLED - HTTPServer.on("/", handleRoot); - - HTTPServer.onNotFound(handleNotFound); - - HTTPServer.begin(); - - Serial.println("* HTTP server started"); -#endif - -#ifndef DEBUG_DISABLED // Only for development - - // Initialize RemoteDebug - - Debug.begin(HOST_NAME); // Initialize the WiFi server - - //Debug.setPassword("r3m0t0."); // Password on telnet connection ? - - Debug.setResetCmdEnabled(true); // Enable the reset command - - Debug.showProfiler(true); // Profiler (Good to measure times, to optimize codes) - - Debug.showColors(true); // Colors - - // Debug.setSerialEnabled(true); // if you wants serial echo - only recommended if ESP is plugged in USB - - // Project commands - - String helpCmd = "bench1 - Benchmark 1\n"; - helpCmd.concat("bench2 - Benchmark 2"); - - Debug.setHelpProjectsCmds(helpCmd); - Debug.setCallBackProjectCmds(&processCmdRemoteDebug); - - // Init the simple software debugger, based on SerialDebug library - -#ifndef DEBUG_DISABLE_DEBUGGER - - Debug.initDebugger(debugGetDebuggerEnabled, debugHandleDebugger, debugGetHelpDebugger, debugProcessCmdDebugger); // Set the callbacks - - debugInitDebugger(&Debug); // Init the debugger - - // Add Functions and global variables to RemoteDebuggger - - // Notes: descriptions is optionals - - // Add functions that can called from SerialDebug - - if (debugAddFunctionVoid("benchInt", &benchInt) >= 0) { - debugSetLastFunctionDescription("To run a benchmark of integers"); - } - if (debugAddFunctionVoid("benchFloat", &benchFloat) >= 0) { - debugSetLastFunctionDescription("To run a benchmark of float"); - } - if (debugAddFunctionVoid("benchGpio", &benchGpio) >= 0) { - debugSetLastFunctionDescription("To run a benchmark of Gpio operations"); - } - if (debugAddFunctionVoid("benchAll", &benchAll) >= 0) { - debugSetLastFunctionDescription("To run all benchmarks"); - } - - if (debugAddFunctionStr("funcArgStr", &funcArgStr) >= 0) { - debugSetLastFunctionDescription("To run with String arg"); - } - if (debugAddFunctionChar("funcArgChar", &funcArgChar) >= 0) { - debugSetLastFunctionDescription("To run with Character arg"); - } - if (debugAddFunctionInt("funcArgInt", &funcArgInt) >= 0) { - debugSetLastFunctionDescription("To run with Integer arg"); - } - - // Add global variables that can showed/changed from SerialDebug - // Note: Only global, if pass local for SerialDebug, can be dangerous - - if (debugAddGlobalUInt8_t("mRunSeconds", &mRunSeconds) >= 0) { - debugSetLastGlobalDescription("Seconds of run time"); - } - if (debugAddGlobalUInt8_t("mRunMinutes", &mRunMinutes) >= 0) { - debugSetLastGlobalDescription("Minutes of run time"); - } - if (debugAddGlobalUInt8_t("mRunHours", &mRunHours) >= 0) { - debugSetLastGlobalDescription("Hours of run time"); - } - - // Note: easy way, no descriptions .... - - debugAddGlobalBoolean("mBoolean", &mBoolean); - debugAddGlobalChar("mChar", &mChar); - debugAddGlobalByte("mByte", &mByte); - debugAddGlobalInt("mInt", &mInt); - debugAddGlobalUInt("mUInt", &mUInt); - debugAddGlobalLong("mLong", &mLong); - debugAddGlobalULong("mULong", &mULong); - debugAddGlobalFloat("mFloat", &mFloat); - debugAddGlobalDouble("mDouble", &mDouble); - - debugAddGlobalString("mString", &mString); - - // Note: For char arrays, not use the '&' - - debugAddGlobalCharArray("mCharArray", mCharArray); - - // Note, here inform to show only 20 characteres of this string or char array - - debugAddGlobalString("mStringLarge", &mStringLarge, 20); - - debugAddGlobalCharArray("mCharArrayLarge", - mCharArrayLarge, 20); - - // For arrays, need add for each item (not use loop for it, due the name can not by a variable) - // Notes: Is good added arrays in last order, to help see another variables - // In next versions, we can have a helper to do it in one command - - debugAddGlobalInt("mIntArray[0]", &mIntArray[0]); - debugAddGlobalInt("mIntArray[1]", &mIntArray[1]); - debugAddGlobalInt("mIntArray[2]", &mIntArray[2]); - debugAddGlobalInt("mIntArray[3]", &mIntArray[3]); - debugAddGlobalInt("mIntArray[4]", &mIntArray[4]); - - // Add watches for some global variables - // Note: watches can be added/changed in serial monitor too - - // Watch -> mBoolean when changed (put 0 on value) - - debugAddWatchBoolean("mBoolean", DEBUG_WATCH_CHANGED, 0); - - // Watch -> mRunSeconds == 10 - - debugAddWatchUInt8_t("mRunSeconds", DEBUG_WATCH_EQUAL, 10); - - // Watch -> mRunMinutes > 3 - - debugAddWatchUInt8_t("mRunMinutes", DEBUG_WATCH_GREAT, 3); - - // Watch -> mRunMinutes == mRunSeconds (just for test) - - debugAddWatchCross("mRunMinutes", DEBUG_WATCH_EQUAL, "mRunSeconds"); - -#endif - - // End of setup - show IP - - Serial.println("* Arduino RemoteDebug Library"); - Serial.println("*"); - Serial.print("* WiFI connected. IP address: "); - Serial.println(WiFi.localIP()); - Serial.println("*"); - Serial.println("* Please use the telnet client (telnet for Mac/Unix or putty and others for Windows)"); - Serial.println("* or the RemoteDebugApp (in browser: http://joaolopesf.net/remotedebugapp)"); - Serial.println("*"); - Serial.println("* This sample will send messages of debug in all levels."); - Serial.println("*"); - Serial.println("* Please try change debug level in client (telnet or web app), to see how it works"); - Serial.println("*"); - -#endif - -} - -void loop() { - -#ifndef DEBUG_DISABLED - // Time of begin of this loop - uint32_t timeBeginLoop = millis(); -#endif - - // Each second - - if (millis() >= mTimeToSec) { - - // Time - - mTimeToSec = millis() + 1000; - - // Count run time (just a test - for real suggest the TimeLib and NTP, if board have WiFi) - - mRunSeconds++; - - if (mRunSeconds == 60) { - mRunMinutes++; - mRunSeconds = 0; - } - if (mRunMinutes == 60) { - mRunHours++; - mRunMinutes = 0; - } - if (mRunHours == 24) { - mRunHours = 0; - } - - // Blink the led - -#ifdef LED_BUILTIN - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); -#endif - - // Debug the time (verbose level) - - debugV("* Time: %u seconds (VERBOSE)", mRunSeconds); - - if (mRunSeconds % 5 == 0) { // Each 5 seconds - - // Debug levels - - debugV("* This is a message of debug level VERBOSE"); - debugD("* This is a message of debug level DEBUG"); - debugI("* This is a message of debug level INFO"); - debugW("* This is a message of debug level WARNING"); - debugE("* This is a message of debug level ERROR"); - - - // RemoteDebug isActive? Use this RemoteDebug sintaxe if you need process anything only for debug - // It is good to avoid overheads (this is only use that is suggest to use isActive) - // Note this need be surrounded by DEBUG_DISABLED precompiler condition to not compile for production/release - -#ifndef DEBUG_DISABLED - - if (Debug.isActive(Debug.VERBOSE)) { - - Debug.println("Calling a foo function"); - Debug.printf("At time of %d sec.\n", mRunSeconds); - - // Call a function - - foo(); - } -#endif - - } - } - - ////// Services on Wifi - -#ifdef USE_ARDUINO_OTA - // Update over air (OTA) - - ArduinoOTA.handle(); -#endif - -#ifdef WEB_SERVER_ENABLED - // Web server - - HTTPServer.handleClient(); -#endif - -#ifndef DEBUG_DISABLED - // Remote debug over telnet - - Debug.handle(); -#endif - - // Give a time for ESP - - yield(); - -#ifndef DEBUG_DISABLED - // Show a debug - warning if time of these loop is over 50 (info) or 100 ms (warning) - - uint32_t time = (millis() - timeBeginLoop); - - if (time > 100) { - debugI("* Time elapsed for the loop: %u ms.", time); - } else if (time > 200) { - debugW("* Time elapsed for the loop: %u ms.", time); - } -#endif - -} - - -// Function example to show a new auto function name of debug* macros - -void foo() { - - uint8_t var = 1; - - debugV("this is a debug - var %u", var); - debugV("This is a println"); -} - -#ifndef DEBUG_DISABLED - -// Process commands from RemoteDebug - -void processCmdRemoteDebug() { - - String lastCmd = Debug.getLastCommand(); - - if (lastCmd == "bench1") { - - // Benchmark 1 - Printf - - debugA("* Benchmark 1 - one Printf"); - - - uint32_t timeBegin = millis(); - uint8_t times = 50; - - for (uint8_t i = 1; i <= times; i++) { - debugA("%u - 1234567890 - AAAA", i); - - } - - debugA("* Time elapsed for %u printf: %ld ms.\n", times, - (millis() - timeBegin)); - - } else if (lastCmd == "bench2") { - - // Benchmark 2 - Print/println - - debugA("* Benchmark 2 - Print/Println"); - - uint32_t timeBegin = millis(); - uint8_t times = 50; - - for (uint8_t i = 1; i <= times; i++) { - if (Debug.isActive(Debug.ANY)) { - Debug.print(i); - Debug.print(" - 1234567890"); - Debug.println(" - AAAA"); - } - } - - debugA("* Time elapsed for %u printf: %ld ms.\n", times, - (millis() - timeBegin)); - } -} -#endif - -////// Benchmarks - simple - -// Note: how it as called by SerialDebug, must be return type void and no args -// Note: Flash F variables is not used during the tests, due it is slow to use in loops - -#define BENCHMARK_EXECS 10000 - -// Simple benckmark of integers - -void benchInt() { - - int test = 0; - - for (int i = 0; i < BENCHMARK_EXECS; i++) { - - // Some integer operations - - test++; - test += 2; - test -= 2; - test *= 2; - test /= 2; - } - - // Note: Debug always is used here - - debugA("*** Benchmark of integers. %u exec.", BENCHMARK_EXECS); - -} - -// Simple benckmark of floats - -void benchFloat() { - - float test = 0; - - for (int i = 0; i < BENCHMARK_EXECS; i++) { - - // Some float operations - - test++; - test += 2; - test -= 2; - test *= 2; - test /= 2; - } - - // Note: Debug always is used here - - debugA("*** Benchmark of floats, %u exec.", BENCHMARK_EXECS); - -} - -// Simple benckmark of GPIO - -void benchGpio() { - -// const int execs = (BENCHMARK_EXECS / 10); // Reduce it - const int execs = BENCHMARK_EXECS; - - for (int i = 0; i < execs; i++) { - - // Some GPIO operations - - digitalWrite(LED_BUILTIN, HIGH); - digitalRead(LED_BUILTIN); - digitalWrite(LED_BUILTIN, LOW); - - analogRead(A0); - analogRead(A0); - analogRead(A0); - - } - - // Note: Debug always is used here - - debugA("*** Benchmark of GPIO. %u exec.", execs); - -} - -// Run all benchmarks - -void benchAll() { - - benchInt(); - benchFloat(); - benchGpio(); - - // Note: Debug always is used here - - debugA("*** All Benchmark done."); - -} - -// Example functions with argument (only 1) to call from serial monitor -// Note others types is not yet available in this version of SerialDebug - -void funcArgStr (String str) { - - debugA("*** called with arg.: %s", str.c_str()); -} -void funcArgChar (char character) { - - debugA("*** called with arg.: %c", character); -} -void funcArgInt (int number) { - - debugA("*** called with arg.: %d", number); -} - -////// WiFi - -void connectWiFi() { - - ////// Connect WiFi - -#ifdef EM_DEPURACAO - Serial.println("*** connectWiFi: begin conection ..."); -#endif - -#ifdef ESP32 - // ESP32 // TODO: is really necessary ? - WiFi.enableSTA(true); - delay(100); -#endif - - // Connect with SSID and password stored - -#ifndef WIFI_SSID - WiFi.begin(); -#else - WiFi.begin(WIFI_SSID, WIFI_PASS); -#endif - - // Wait connection - - uint32_t timeout = millis() + 20000; // Time out - - while (WiFi.status() != WL_CONNECTED && millis() < timeout) { - delay(250); - Serial.print("."); - } - - // Not connected yet? - - if (WiFi.status() != WL_CONNECTED) { - -#ifndef WIFI_SSID - // SmartConfig - - WiFi.beginSmartConfig(); - - // Wait for SmartConfig packet from mobile - - Serial.println("connectWiFi: Waiting for SmartConfig."); - - while (!WiFi.smartConfigDone()) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("connectWiFi: SmartConfig received."); - - // Wait for WiFi to connect to AP - - Serial.println("connectWiFi: Waiting for WiFi"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } -#else - Serial.println("Not possible connect to WiFi, rebooting"); - ESP.restart(); -#endif - } - - // End - - Serial.println(""); - Serial.print("connectWiFi: connect a "); - Serial.println(WiFi.SSID()); - Serial.print("IP: "); - Serial.println(WiFi.localIP().toString()); - -} - -#ifdef USE_ARDUINO_OTA - -// Initialize o Arduino OTA - -void initializeOTA() { - - // TODO: option to authentication (password) - -#if defined ESP8266 - - ArduinoOTA.onStart([]() { - Serial.println("* OTA: Start"); - }); - ArduinoOTA.onEnd([]() { - Serial.println("\n*OTA: End"); - }); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - Serial.printf("*OTA: Progress: %u%%\r", (progress / (total / 100))); - }); - ArduinoOTA.onError([](ota_error_t error) { - Serial.printf("*OTA: Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); - else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); - else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); - else if (error == OTA_END_ERROR) Serial.println("End Failed"); - }); - -#elif defined ESP32 - - // ArduinoOTA - - ArduinoOTA.onStart([]() { - String type; - if (ArduinoOTA.getCommand() == U_FLASH) - type = "sketch"; - else // U_SPIFFS - type = "filesystem"; - Serial.println("Start updating " + type); - }).onEnd([]() { - Serial.println("\nEnd"); - }).onProgress([](unsigned int progress, unsigned int total) { - Serial.printf("Progress: %u%%\r", (progress / (total / 100))); - }).onError([](ota_error_t error) { - Serial.printf("Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); - else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); - else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); - else if (error == OTA_END_ERROR) Serial.println("End Failed"); - }); - -#endif - - // Begin - - ArduinoOTA.begin(); - -} - -#endif - -#ifdef WEB_SERVER_ENABLED - -/////////// Handles - - void handleRoot() { - - // Root web page - - HTTPServer.send(200, "text/plain", "hello from esp - RemoteDebug Sample!"); - } - - void handleNotFound(){ - - // Page not Found - - String message = "File Not Found\n\n"; - message.concat("URI: "); - message.concat(HTTPServer.uri()); - message.concat("\nMethod: "); - message.concat((HTTPServer.method() == HTTP_GET)?"GET":"POST"); - message.concat("\nArguments: "); - message.concat(HTTPServer.args()); - message.concat("\n"); - for (uint8_t i=0; i sometimes the processCommand is executed twice. Workaround> check time - * 1.5.8 2019-02-08 New macros to compatibility with SerialDebug (can use RemoteDebug or SerialDebug) thanks to @phrxmd - * 1.5.7 2018-11-03 Fixed bug for MAX_TIME_INACTIVE - * 1.5.6 2018-10-19 Adjustments based on pull request from @jeroenst (to allow serial output with telnet password and setPassword method) - * 1.5.5 ? Serial output is now not allowed if telnet password is enabled - * 1.5.4 ? Serial output not depending of telnet password (thanks @jeroenst for suggestion) - * 1.5.3 ? Serial output adjustments (due bug in password logic) - * 1.5.2 ? Correct rdebug macro (thanks @stritti) - * 1.5.1 ? New command: silence - * Added new rdebug?ln to put auto new line - * Auto function and core if (for ESP32) in rdebug macros - * Class destructor implemented - * 1.5.0 ? Port can be pass in begin method (thanks @PjotrekSE for suggestion) - * Few adjustments - * this kind of authentication will not be done now. - * Such as RemoteDebug now is not for production releases, - * Note: telnet use advanced authentication (kerberos, etc.) - * 1.4.0 ? A simple text password request, if enabled (thanks @jeroenst for suggestion) - * 1.3.1 ? Retired # from VARGS precompiler macros - * Few adjustments as ESP32 includes - * Port number can be modified in project Arduino (.ino file) - * 1.3.0 Aug 2018 Bug in write with latest ESP8266 SDK - * 1.2.1 ? Adjusts to not cause error in Arduino - * 1.2.0 ? Added shortcuts and buffering to avoid delays - * 1.1.1 2017-11-24 Added support for the pass through of commands, and default debug levels thanks B. Harville - * 1.1.0 Aug 2017 Support to ESP32 - * New commands for CPU frequencies - * New level> profiler and auto-profiler - * 1.0.1 Aug 2017 New connection logic - * 1.0.0 Jan 2017 First RC - * 0.9.1 Oct 2016 Beta 2 - * 0.9.0 Aug 2016 Beta 1 - * - */ - -/* - * TODO: - Page HTML for begin/stop Telnet server - * - Add support to another Arduino WiFi boards (if have demand on it) - */ - -///// RemoteDebug configuration - -#include "RemoteDebugCfg.h" - -///// Debug disable for compile to production/release ? -///// as nothing of RemotedDebug is compiled, zero overhead :-) - -#ifndef DEBUG_DISABLED - -///// Defines - -#define VERSION "3.0.5" - -///// Includes - -#include "stdint.h" - -#if defined(ESP8266) -// ESP8266 SDK -extern "C" { -bool system_update_cpu_freq(uint8_t freq); -} -#endif - -#include "Arduino.h" -#include "Print.h" - -#ifdef SERIAL_DEBUG_H -// Cannot used with SerialDebug at same time -#error "RemoteDebug cannot be used with SerialDebug" -#endif - -// ESP8266 or ESP32 ? - -#if defined(ESP8266) - -#include - -#elif defined(ESP32) - -#include - -#else - -#error Only for ESP8266 or ESP32 - -#endif - -#include "RemoteDebug.h" // This library - -#ifdef ALPHA_VERSION // In test, not good yet -#include "telnet.h" -#endif - -// Support to websocket connection with RemoteDebugApp -// Note: you must install the arduinoWebSocket library before - -#ifndef WEBSOCKET_DISABLED // Only if Web socket enabled (RemoteDebugApp) -#include "RemoteDebugWS.h" -#endif - -// Internal print macros for send messages to client - -#ifndef WEBSOCKET_DISABLED // Only if Web socket enabled (RemoteDebugApp) - -#define debugPrintf(fmt, ...) { \ - if (_connected) TelnetClient.printf(fmt, ##__VA_ARGS__);\ - else if (_connectedWS) DebugWS.printf(fmt, ##__VA_ARGS__);\ -} -#define debugPrintln(str) { \ - if (_connected) TelnetClient.println(str);\ - else if (_connectedWS) DebugWS.println(str);\ -} -#define debugPrint(str) { \ - if (_connected) TelnetClient.print(str);\ - else if (_connectedWS) DebugWS.print(str);\ -} - -#else // With web socket too - -#define debugPrintf(fmt, ...) { \ - if (_connected) TelnetClient.printf(fmt, ##__VA_ARGS__);\ -} -#define debugPrintln(str) { \ - if (_connected) TelnetClient.println(str);\ -} -#define debugPrint(str) { \ - if (_connected) TelnetClient.print(str);\ -} - -#endif - -// Internal debug macro - recommended stay disable - -#define D(fmt, ...) // Without this -//#define D(fmt, ...) Serial.printf("rd: " fmt "\n", ##__VA_ARGS__) // Serial debug - -////// Variables - -// Instance - -static RemoteDebug* _instance; - -// WiFi server (telnet) - -static WiFiServer TelnetServer(TELNET_PORT); // @suppress("Abstract class cannot be instantiated") -static WiFiClient TelnetClient; // @suppress("Abstract class cannot be instantiated") - -// Support to websocket connection with RemoteDebugApp - -#ifndef WEBSOCKET_DISABLED - -// Instance of RemoteDebugWS - -static RemoteDebugWS DebugWS; // @suppress("Abstract class cannot be instantiated") - -static boolean _connectedWS = false; // Connected : - -// Callbacks - -class MyRemoteDebugCallbacks: public RemoteDebugWSCallbacks { - void onConnect() { - // Web socket (app) connected - - D("rd: onconnect"); - - _connectedWS = true; - - // Is telnet connected -> disconnect it, due reduce overheads - - if (_instance->isConnected()) { - - _instance->disconnect(true); - } - - // Call same routine that telnet - - _instance->onConnection(true); - } - void onDisconnect() { - // Web socket (app) disconnected - - D("rd: ondisconnect"); - - _connectedWS = false; - } - void onReceive(const char *message) { - // Receive a message - - D("rd: onreceive"); - - _instance->wsOnReceive(message); - } -}; - -#endif // WEBSOCKET_DISABLED - -////// Methods / routines - -// Constructor - -RemoteDebug::RemoteDebug() { - - // Save the instance - - _instance = this; -} - -// Initialize the telnet server - -bool RemoteDebug::begin(String hostName, uint8_t startingDebugLevel) { - return begin(hostName, TELNET_PORT, startingDebugLevel); -} - -bool RemoteDebug::begin(String hostName, uint16_t port, uint8_t startingDebugLevel) { - - // Initialize server telnet - - if (port != TELNET_PORT) { // Bug: not more can use begin(port).. - return false; - } - - TelnetServer.begin(); - TelnetServer.setNoDelay(true); - -#ifndef WEBSOCKET_DISABLED - // Initialize web socket (for RemoteDebugApp) - - DebugWS.begin(new MyRemoteDebugCallbacks()); - -#endif - - // Reserve space to buffer of print writes - - _bufferPrint.reserve(BUFFER_PRINT); - -#ifdef CLIENT_BUFFERING - // Reserve space to buffer of send - - _bufferPrint.reserve(MAX_SIZE_SEND); - -#endif - - // Host name of this device - - _hostName = hostName; - - // Debug level - - _clientDebugLevel = startingDebugLevel; - _lastDebugLevel = startingDebugLevel; - - return true; -} - -#ifdef DEBUGGER_ENABLED -// Simple software debugger - based on SerialDebug Library -void RemoteDebug::initDebugger(boolean (*callbackEnabled)(), void (*callbackHandle)(const boolean), String (*callbackGetHelp)(), void (*callbackProcessCmd)()) { - - // Init callbacks for the debugger - - _callbackDbgEnabled = callbackEnabled; - _callbackDbgHandle = callbackHandle; - _callbackDbgHelp = callbackGetHelp; - _callbackDbgProcessCmd = callbackProcessCmd; - -} - -WiFiClient* RemoteDebug::getTelnetClient() { - - return &TelnetClient; -} - -#endif - -// Set the password for telnet - thanks @jeroenst for suggest thist method - -void RemoteDebug::setPassword(String password) { - - _password = password; - -} - -// Destructor - -RemoteDebug::~RemoteDebug() { - - // Flush - - if (TelnetClient && TelnetClient.connected()) { - TelnetClient.flush(); - } - - // Stop - - stop(); -} - -// Stop the server - -void RemoteDebug::stop() { - - // Stop Client - - if (TelnetClient && TelnetClient.connected()) { - TelnetClient.stop(); - } - - // Stop server - - TelnetServer.stop(); - -#ifndef WEBSOCKET_DISABLED - // Stop web socket (RemoteDebugApp) - - DebugWS.stop(); // stop the websocket server -#endif - -} - -// Handle the connection (in begin of loop in sketch) -// TODO: optimize when loop not have a large delay - -void RemoteDebug::handle() { - -#ifdef ALPHA_VERSION // In test, not good yet - static uint32_t lastTime = millis(); -#endif - -#ifdef DEBUGGER_ENABLED - static uint32_t dbgTimeHandle = millis(); // To avoid call the handler desnecessary - static boolean dbgLastConnected = false; // Last is connected ? -#endif - - // Silence timeout ? - - if (_silence && _silenceTimeout > 0 && millis() >= _silenceTimeout) { - - // Get out of silence mode - - silence(false, true); - } - - // Debug level is profiler -> set the level before - - if (_clientDebugLevel == PROFILER) { - if (millis() > _levelProfilerDisable) { - _clientDebugLevel = _levelBeforeProfiler; - debugPrintln("* Debug level profile inactive now"); - } - } - -#ifdef ALPHA_VERSION // In test, not good yet - - // Automatic change to profiler level if time between handles is greater than n millis - - if (_autoLevelProfiler > 0 && _clientDebugLevel != PROFILER) { - - uint32_t diff = (millis() - lastTime); - - if (diff >= _autoLevelProfiler) { - _levelBeforeProfiler = _clientDebugLevel; - _clientDebugLevel = PROFILER; - _levelProfilerDisable = 1000; // Disable it at 1 sec - - debugPrintf("* Debug level profile active now - time between handels: %u\r\n", diff); - } - - lastTime = millis(); - } -#endif - - // look for Client connect trial - - if (TelnetServer.hasClient()) { - - // Old connection logic - -// if (!TelnetClient || !TelnetClient.connected()) { -// -// if (TelnetClient) { // Close the last connect - only one supported -// -// TelnetClient.stop(); -// -// } - - // New connection logic - 10/08/17 - - if (TelnetClient && TelnetClient.connected()) { - - // Verify if the IP is same than actual conection - - WiFiClient newClient; // @suppress("Abstract class cannot be instantiated") - newClient = TelnetServer.available(); - String ip = newClient.remoteIP().toString(); - - if (ip == TelnetClient.remoteIP().toString()) { - - // Reconnect - - TelnetClient.stop(); - TelnetClient = newClient; - - } else { - - // Disconnect (not allow more than one connection) - - newClient.stop(); - - return; - - } - - } else { - - // New TCP client - - TelnetClient = TelnetServer.available(); - - // Password request ? - 18/07/18 - - if (_password != "") { - - #ifdef ALPHA_VERSION // In test, not good yet - // Send command to telnet client to not do local echos - // Experimental code ! - - sendTelnetCommand(TELNET_WONT, TELNET_ECHO); - #endif - - } - } - - if (!TelnetClient) { // No client yet ??? - return; - } - - // Set client - - TelnetClient.setNoDelay(true); // More faster - TelnetClient.flush(); // clear input buffer, else you get strange characters - - // Empty buffer - - delay(100); - - while (TelnetClient.available()) { - TelnetClient.read(); - } - - // Connection event - - onConnection(true); - - } - - // Is client connected ? (to reduce overhead in active) - - _connected = (TelnetClient && TelnetClient.connected()); - - // Get command over telnet - - if (_connected) { - - char last = ' '; // To avoid process two times the "\r\n" - - while (TelnetClient.available()) { // get data from Client - - // Get character - - char character = TelnetClient.read(); - - // Newline (CR or LF) - once one time if (\r\n) - 26/07/17 - - if (isCRLF(character) == true) { - - if (isCRLF(last) == false) { - - // Process the command - - if (_command.length() > 0) { - - _lastCommand = _command; // Store the last command - processCommand(); - - } - } - - _command = ""; // Init it for next command - - } else if (isPrintable(character)) { - - // Concat - - _command.concat(character); - - } - - // Last char - - last = character; - } - - } - - // Client connected ? - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - boolean connected = (_connected || _connectedWS); -#else // By telnet - boolean connected = _connected; -#endif - - if (connected) { - -#ifdef CLIENT_BUFFERING - // Client buffering - send data in intervals to avoid delays or if its is too big - - if ((millis() - _lastTimeSend) >= DELAY_TO_SEND || _sizeBufferSend >= MAX_SIZE_SEND) { - debugPrint(_bufferSend); - _bufferSend = ""; - _sizeBufferSend = 0; - _lastTimeSend = millis(); - } -#endif - -#ifdef MAX_TIME_INACTIVE -#if MAX_TIME_INACTIVE > 0 - - // Inactivity - close connection if not received commands from user in telnet - // For reduce overheads - - uint32_t maxTime = MAX_TIME_INACTIVE; // Normal - - if (_password != "" && !_passwordOk) { // Request password - 18/08/08 - maxTime = 60000; // One minute to password - } - - if ((millis() - _lastTimeCommand) > maxTime) { - - debugPrintln("* Closing session by inactivity"); - - // Disconnect - - disconnect(); - return; - } -#endif -#endif - } - -#ifndef WEBSOCKET_DISABLED // For websocket server - - // Web socket server handle - - DebugWS.handle(); - -#endif - -#ifdef DEBUGGER_ENABLED - - // For Simple software debugger - based on SerialDebug Library - - // Changed handle debugger logic - 2018-03-01 - - if (_callbackDbgEnabled && _callbackDbgHandle) { // Calbacks ok ? - - boolean callHandle = false; - - if (dbgLastConnected != connected) { // Change connection -> always call - - dbgLastConnected = connected; - callHandle = true; - - } else if (millis() >= dbgTimeHandle) { - - if (_callbackDbgEnabled()) { // Only if it is enabled - callHandle = true; - } - } - - if (callHandle) { - - // Call the handle - - _callbackDbgHandle(true); - - // Save time - - dbgTimeHandle = millis() + DEBUGGER_HANDLE_TIME; - } - } -#endif - - //DV("*handle time: ", (millis() - timeBegin)); -} - - -// Disconnect client - -void RemoteDebug::disconnect(boolean onlyTelnetClient) { - - // Disconnect - - if (onlyTelnetClient) { - if (_connected) { - TelnetClient.println("* Closing client connection ..."); // this is to web app new conn not receive it - } - } else { - debugPrintln("* Closing client connection ..."); - } - - _silence = false; - _silenceTimeout = 0; - - if (_connected) { // By telnet - TelnetClient.stop(); - _connected = false; - } -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - if (_connectedWS && !onlyTelnetClient) { - DebugWS.disconnect(); // Disconnect client - _connectedWS = false; - } -#endif -} - -// Connection/disconnection event - -void RemoteDebug::onConnection(boolean connected) { - - // Clear variables - - D("rd onconn %d", connected); - - _bufferPrint = ""; // Clean buffer - - _lastTimeCommand = millis(); // To mark time for inactivity - - _command = ""; // Clear command - _lastCommand = ""; // Clear las command - - _lastTimePrint = millis(); // Clear the time - - _silence = false; // No silence - _silenceTimeout = 0; - -#ifdef CLIENT_BUFFERING - // Client buffering - send data in intervals to avoid delays or if its is too big - _bufferSend = ""; - _sizeBufferSend = 0; - _lastTimeSend = millis(); -#endif - - // Password request ? - 18/07/18 - - if (_password != "") { - - _passwordOk = false; - -#ifdef REMOTEDEBUG_PWD_ATTEMPTS - _passwordAttempt = 1; -#endif - } - - // Save it - - _connected = connected; - - // Process - - if (connected) { // Connected ? - - - // Callback - - if (_callbackNewClient) { - _callbackNewClient(); - } - - // Show the initial message - -#if SHOW_HELP - showHelp(); -#endif - } -} - -boolean RemoteDebug::isConnected() { - - // Is connected - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - return (_connected || _connectedWS); -#else - return _connected; -#endif -} - -// Send to serial too (use only if need) - -void RemoteDebug::setSerialEnabled(boolean enable) { - - _serialEnabled = enable; - _showColors = false; // Disable it for Serial - -} - -// Allow ESP reset over telnet client - -void RemoteDebug::setResetCmdEnabled(boolean enable) { - _resetCommandEnabled = enable; -} - -// Show time in millis - -void RemoteDebug::showTime(boolean show) { - _showTime = show; -} - -// Show profiler - time in millis between messages of debug - -void RemoteDebug::showProfiler(boolean show, uint32_t minTime) { - _showProfiler = show; - _minTimeShowProfiler = minTime; -} - -#ifdef ALPHA_VERSION // In test, not good yet -// Automatic change to profiler level if time between handles is greater than n mills (0 - disable) - -void RemoteDebug::autoProfilerLevel(uint32_t millisElapsed) { - _autoLevelProfiler = millisElapsed; -} -#endif - -// Show debug level - -void RemoteDebug::showDebugLevel(boolean show) { - _showDebugLevel = show; -} - -// Show colors - -void RemoteDebug::showColors(boolean show) { - if (_serialEnabled == false) { - _showColors = show; - } else { - _showColors = false; // Disable it for Serial - } -} - -// Show in raw mode - only data ? - -void RemoteDebug::showRaw(boolean show) { - _showRaw = show; -} - - -// Is active ? client telnet connected and level of debug equal or greater then set by user in telnet - -boolean RemoteDebug::isActive(uint8_t debugLevel) { - - // Active -> - // Not in silence (new) - // Debug level ok and - // Telnet connected or - // Serial enabled (use only if need) - // Password ok (if enabled) - 18/08/18 - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - - boolean ret = (debugLevel >= _clientDebugLevel && - !_silence && - (_connected || _connectedWS || _serialEnabled)); - -#else // Telnet only - - boolean ret = (debugLevel >= _clientDebugLevel && - !_silence && - (_connected || _serialEnabled)); - -#endif - - - if (ret) { - _lastDebugLevel = debugLevel; - } - - return ret; - -} - -// Set help for commands over telnet set by sketch - -void RemoteDebug::setHelpProjectsCmds(String help) { - - _helpProjectCmds = help; - -} - -// Set callback of sketch function to process project messages - -void RemoteDebug::setCallBackProjectCmds(void (*callback)()) { - _callbackProjectCmds = callback; -} - -void RemoteDebug::setCallBackNewClient(void (*callback)()) { - _callbackNewClient = callback; -} - -// Print - -size_t RemoteDebug::write(const uint8_t *buffer, size_t size) { - - // Process buffer - // Insert due a write bug w/ latest Esp8266 SDK - 17/08/18 - - for(size_t i=0; i 1) { - show.concat(COLOR_RESET); - } - } - } - - // Show time in millis - - if (_showTime) { - if (show != "") - show.concat(" "); - show.concat("t:"); - show.concat(millis()); - show.concat("ms"); - } - - // Show profiler (time between messages) - - if (_showProfiler) { - elapsed = (millis() - _lastTimePrint); - boolean resetColors = false; - if (show != "") - show.concat(" "); - if (_showColors) { - if (elapsed < 250) { - ; // not color this - } else if (elapsed < 1000) { - show.concat(COLOR_BACKGROUND_CYAN); - resetColors = true; - } else if (elapsed < 3000) { - show.concat(COLOR_BACKGROUND_YELLOW); - resetColors = true; - } else if (elapsed < 5000) { - show.concat(COLOR_BACKGROUND_MAGENTA); - resetColors = true; - } else { - show.concat(COLOR_BACKGROUND_RED); - resetColors = true; - } - } - show.concat("p:^"); - show.concat(formatNumber(elapsed, 4)); - show.concat("ms"); - if (resetColors) { - show.concat(COLOR_RESET); - } - _lastTimePrint = millis(); - } - -#endif - - } else { // Raw mode - only data - e.g. used for debugger messages - -#ifdef COLOR_NEW_SYSTEM - show.concat(COLOR_RAW); -#endif - } - - // Show anything ? - - if (show != "") { - - if (!_showRaw) { - show.concat(") "); - } - - // Write to telnet buffered - - if (connected || _serialEnabled) { // send data to Client - _bufferPrint = show; - } - } - - _newLine = false; - - } - - // Print ? - - boolean doPrint = false; - - // New line ? - - if (character == '\n') { - - _bufferPrint.concat("\r"); // Para clientes windows - 29/01/17 - - _newLine = true; - doPrint = true; - - } else if (_bufferPrint.length() == BUFFER_PRINT) { // Limit of buffer - - doPrint = true; - - } - - // Write to telnet Buffered - - _bufferPrint.concat((char) character); - - // Send the characters buffered by print.h - - if (doPrint) { // Print the buffer - - boolean noPrint = false; - - if (_showProfiler && elapsed < _minTimeShowProfiler) { // Profiler time Minimal - noPrint = true; - } else if (_filterActive) { // Check filter before print - - String aux = _bufferPrint; - aux.toLowerCase(); - - if (aux.indexOf(_filter) == -1) { // not find -> no print - noPrint = true; - } - } - - if (noPrint == false) { - -#ifdef COLOR_NEW_SYSTEM - _bufferPrint.concat(COLOR_RESET); -#endif - // Send to telnet or websocket (buffered) - - boolean sendToClient = connected; - - if (_password != "" && !_passwordOk) { // With no password -> no telnet output - 2018-10-19 - sendToClient = false; - } - - if (sendToClient) { // send data to Client - - -#ifndef CLIENT_BUFFERING - debugPrint(_bufferPrint); -#else // Cliente buffering - - uint8_t size = _bufferPrint.length(); - - // Buffer too big ? - - if ((_sizeBufferSend + size) >= MAX_SIZE_SEND) { - - // Send it - - debugPrint(_bufferSend); - _bufferSend = ""; - _sizeBufferSend = 0; - _lastTimeSend = millis(); - } - - // Add to buffer of send - - _bufferSend.concat(_bufferPrint); - _sizeBufferSend+=size; - - // Client buffering - send data in intervals to avoid delays or if its is too big - // Not for raw mode - if (_showRaw || (millis() - _lastTimeSend) >= DELAY_TO_SEND) { - debugPrint(_bufferSend); - _bufferSend = ""; - _sizeBufferSend = 0; - _lastTimeSend = millis(); - } -#endif - } - - // Echo to serial (not buffering it) - - if (_serialEnabled) { - Serial.print(_bufferPrint); - } - } - - // Empty the buffer - - ret = _bufferPrint.length(); - _bufferPrint = ""; - } - - // Retorna - - return ret; -} - -////// Private - - -// Show help of commands - -void RemoteDebug::showHelp() { - - // Show the initial message - - String help = ""; - - // Password request ? - 04/03/18 - - if (_password != "" && !_passwordOk) { - - help.concat("\r\n"); - help.concat("* Please enter with a password to access"); -#ifdef REMOTEDEBUG_PWD_ATTEMPTS - help.concat(" (attempt "); - help.concat(_passwordAttempt); - help.concat(" of "); - help.concat(REMOTEDEBUG_PWD_ATTEMPTS); - help.concat(")"); -#endif - help.concat(':'); - help.concat("\r\n"); - - debugPrint(help); - - return; -} - - // Show help - -#if defined(ESP8266) - help.concat("*** Remote debug - over telnet - for ESP8266 (NodeMCU) - version "); -#elif defined(ESP32) - help.concat("*** Remote debug - over telnet - for ESP32 - version "); -#endif - help.concat(VERSION); - help.concat("\r\n"); - help.concat("* Host name: "); - help.concat(_hostName); - help.concat(" IP:"); - help.concat(WiFi.localIP().toString()); - help.concat(" Mac address:"); - help.concat(WiFi.macAddress()); - help.concat("\r\n"); - help.concat("* Free Heap RAM: "); - help.concat(ESP.getFreeHeap()); - help.concat("\r\n"); - help.concat("* ESP SDK version: "); - help.concat(ESP.getSdkVersion()); - help.concat("\r\n"); - help.concat("******************************************************\r\n"); - help.concat("* Commands:\r\n"); - help.concat(" ? or help -> display these help of commands\r\n"); - help.concat(" q -> quit (close this connection)\r\n"); - help.concat(" m -> display memory available\r\n"); - help.concat(" v -> set debug level to verbose\r\n"); - help.concat(" d -> set debug level to debug\r\n"); - help.concat(" i -> set debug level to info\r\n"); - help.concat(" w -> set debug level to warning\r\n"); - help.concat(" e -> set debug level to errors\r\n"); - help.concat(" s -> set debug silence on/off\r\n"); - help.concat(" l -> show debug level\r\n"); - help.concat(" t -> show time (millis)\r\n"); - help.concat(" profiler:\r\n"); - help.concat( - " p -> show time between actual and last message (in millis)\r\n"); - help.concat(" p min -> show only if time is this minimal\r\n"); - help.concat(" P time -> set debug level to profiler\r\n"); -#ifdef ALPHA_VERSION // In test, not good yet - help.concat(" A time -> set auto debug level to profiler\r\n"); -#endif - help.concat(" c -> show colors\r\n"); - help.concat(" filter:\r\n"); - help.concat(" filter -> show only debugs with this\r\n"); - help.concat(" nofilter -> disable the filter\r\n"); -#if defined(ESP8266) - help.concat(" cpu80 -> ESP8266 CPU a 80MHz\r\n"); - help.concat(" cpu160 -> ESP8266 CPU a 160MHz\r\n"); - if (_resetCommandEnabled) { - help.concat(" reset -> reset the ESP8266\r\n"); - } -#elif defined(ESP32) - if (_resetCommandEnabled) { - help.concat(" reset -> reset the ESP32\r\n"); - } -#endif - - // Callbacks - - if (_helpProjectCmds != "" && (_callbackProjectCmds)) { - help.concat("\r\n"); - help.concat(" * Project commands:\r\n"); - String show = "\r\n"; - show.concat(_helpProjectCmds); - show.replace("\n", "\n "); // ident this - help.concat(show); - } - -#ifdef DEBUGGER_ENABLED - // Get help for the debugger - - if (_callbackDbgHelp) { - help.concat("\r\n"); - help.concat(_callbackDbgHelp()); - } -#endif - help.concat("\r\n"); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - if (!_connectedWS) { // For telnet only - help.concat("****\r\n"); - help.concat("* New features available:\r\n"); - help.concat("* - Now you can debug in web browser too.\r\n"); - help.concat("* Please access: http://joaolopesf.net/remotedebugapp\r\n"); - help.concat("* - Now you can add an simple software debuggger.\r\n"); - help.concat("* Please access: https://github.com/JoaoLopesF/RemoteDebugger\r\n"); - help.concat("****\r\n"); - } -#endif - - help.concat("\r\n"); - help.concat( - "* Please type the command and press enter to execute.(? or h for this help)\r\n"); - help.concat("***\r\n"); - - // Send to client - - debugPrint(help); -} - -// Get last command received - -String RemoteDebug::getLastCommand() { - - return _lastCommand; -} - -// Clear the last command received - -void RemoteDebug::clearLastCommand() { - _lastCommand = ""; -} - -// Process user command over telnet or web socket - -void RemoteDebug::processCommand() { - - static uint32_t lastTime = 0; - - // Bug -> sometimes the command is process twice - // Workaround -> check time - // TODO: see correction for this - - if (lastTime > 0 && (millis() - lastTime) < 500) { - debugPrintln("* Bug workaround: ignoring command repeating"); - return; - } - lastTime = millis(); - - D("cmd: %s" , _command.c_str()); - - // Password request ? - 18/07/18 - - if (_password != "" && !_passwordOk) { // Process the password - 18/08/18 - adjust in 04/09/08 and 2018-10-19 - - if (_command == _password) { - - debugPrintln("* Password ok, allowing access now..."); - - _passwordOk = true; - -#ifdef ALPHA_VERSION // In test, not good yet - sendTelnetCommand(TELNET_WILL, TELNET_ECHO); // Send a command to telnet to restore echoes = 18/08/18 -#endif - showHelp(); - - } else { - - debugPrintln("* Wrong password!"); - - #ifdef REMOTEDEBUG_PWD_ATTEMPTS - - _passwordAttempt++; - - if (_passwordAttempt > REMOTEDEBUG_PWD_ATTEMPTS) { - - debugPrintln("* Many attempts. Closing session now."); - - // Disconnect - - disconnect(); - - } else { - - showHelp(); - } - - #endif - } - - return; - - } - - // Process commands - - debugPrint("* Debug: Command received: "); - debugPrintln(_command); - - String options = ""; - uint8_t pos = _command.indexOf(" "); - if (pos > 0) { - options = _command.substring(pos + 1); - } - - // Set time of last command received - - _lastTimeCommand = millis(); - - // Get out of silent mode - - if (_command != "s" && _silence) { - - silence(false, true); - } - - // Process the command - - if (_command == "h" || _command == "?") { - - // Show help - - showHelp(); - - } else if (_command == "q") { - - // Quit - - debugPrintln("* Closing client connection ..."); - - TelnetClient.stop(); - - } else if (_command == "m") { - - uint32_t free = ESP.getFreeHeap(); - - debugPrint("* Free Heap RAM: "); - debugPrintln(free); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - - // Send status to app - - if (_connectedWS) { - DebugWS.printf("$app:M:%lu:\n", free); - } - -#endif - -#if defined(ESP8266) - - } else if (_command == "cpu80") { - - // Change ESP8266 CPU para 80 MHz - - system_update_cpu_freq(80); - debugPrintln("CPU ESP8266 changed to: 80 MHz"); - - } else if (_command == "cpu160") { - - // Change ESP8266 CPU para 160 MHz - - system_update_cpu_freq(160); - debugPrintln("CPU ESP8266 changed to: 160 MHz"); - -#endif - - } else if (_command == "v") { - - // Debug level - - _clientDebugLevel = VERBOSE; - - debugPrintln("* Debug level set to Verbose"); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - wsSendLevelInfo(); -#endif - - } else if (_command == "d") { - - // Debug level - - _clientDebugLevel = DEBUG; - - debugPrintln("* Debug level set to Debug"); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - wsSendLevelInfo(); -#endif - - } else if (_command == "i") { - - // Debug level - - _clientDebugLevel = INFO; - - debugPrintln("* Debug level set to Info"); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - wsSendLevelInfo(); -#endif - - } else if (_command == "w") { - - // Debug level - - _clientDebugLevel = WARNING; - - debugPrintln("* Debug level set to Warning"); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - wsSendLevelInfo(); -#endif - - } else if (_command == "e") { - - // Debug level - - _clientDebugLevel = ERROR; - - debugPrintln("* Debug level set to Error"); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - wsSendLevelInfo(); -#endif - - } else if (_command == "l") { - - // Show debug level - - _showDebugLevel = !_showDebugLevel; - - debugPrintf("* Show debug level: %s\r\n", - (_showDebugLevel) ? "On" : "Off"); - - } else if (_command == "t") { - - // Show time - - _showTime = !_showTime; - - debugPrintf("* Show time: %s\r\n", (_showTime) ? "On" : "Off"); - - } else if (_command == "s") { - - // Toogle silence (new) = 28/08/18 - - silence(!_silence); - - } else if (_command == "p") { - - // Show profiler - - _showProfiler = !_showProfiler; - _minTimeShowProfiler = 0; - - debugPrintf("* Show profiler: %s\r\n", - (_showProfiler) ? "On" : "Off"); - - } else if (_command.startsWith("p ")) { - - // Show profiler with minimal time - - if (options.length() > 0) { // With minimal time - int32_t aux = options.toInt(); - if (aux > 0) { // Valid number - _showProfiler = true; - _minTimeShowProfiler = aux; - debugPrintf( - "* Show profiler: On (with minimal time: %u)\r\n", - _minTimeShowProfiler); - } - } - - } else if (_command == "P") { - - // Debug level profile - - _levelBeforeProfiler = _clientDebugLevel; - _clientDebugLevel = PROFILER; - - if (_showProfiler == false) { - _showProfiler = true; - } - - _levelProfilerDisable = 1000; // Default - - if (options.length() > 0) { // With time of disable - int32_t aux = options.toInt(); - if (aux > 0) { // Valid number - _levelProfilerDisable = millis() + aux; - } - } - - debugPrintf( - "* Debug level set to Profiler (disable in %u millis)\r\n", - _levelProfilerDisable); - - } else if (_command == "A") { - - // Auto debug level profile - - _autoLevelProfiler = 1000; // Default - - if (options.length() > 0) { // With time of disable - int32_t aux = options.toInt(); - if (aux > 0) { // Valid number - _autoLevelProfiler = aux; - } - } - - debugPrintf( - "* Auto profiler debug level active (time >= %u millis)\r\n", - _autoLevelProfiler); - - } else if (_command == "c") { - - // Show colors - - _showColors = !_showColors; - - debugPrintf("* Show colors: %s\r\n", - (_showColors) ? "On" : "Off"); - - } else if (_command.startsWith("filter ") && options.length() > 0) { - - setFilter(options); - - } else if (_command == "nofilter") { - - setNoFilter(); - } else if (_command == "reset" && _resetCommandEnabled) { - - debugPrintln("* Reset ..."); - - debugPrintln("* Closing client connection ..."); - -#if defined(ESP8266) - debugPrintln("* Resetting the ESP8266 ..."); -#elif defined(ESP32) - debugPrintln("* Resetting the ESP32 ..."); -#endif - - TelnetClient.stop(); - TelnetServer.stop(); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - DebugWS.stop(); -#endif - - delay(500); - - // Reset - - ESP.restart(); - -#ifdef DEBUGGER_ENABLED - - } else if (!_callbackDbgProcessCmd && _command.startsWith("dbg")) { - - // Show a message of debugger not is active - - debugPrintln("* RemoteDebugger not activate for this project"); - debugPrintln("* Please access it to see how activate this:"); - debugPrintln("* https://github.com/JoaoLopesF/RemoteDebugger"); - -#endif - - } else { - - // Callbacks - -#ifdef DEBUGGER_ENABLED - // Process commands for the debugger - - if (_callbackDbgProcessCmd) { - _callbackDbgProcessCmd(); - } -#endif - - // Project commands - set by programmer - - if (_callbackProjectCmds) { - - _callbackProjectCmds(); - - } - } -} - -// Filter - -void RemoteDebug::setFilter(String filter) { - - _filter = filter; - _filter.toLowerCase(); // TODO: option to case insensitive ? - _filterActive = true; - - debugPrint("* Debug: Filter active: "); - debugPrintln(_filter); - -} - -void RemoteDebug::setNoFilter() { - - _filter = ""; - _filterActive = false; - - debugPrintln("* Debug: Filter disabled"); - -} - -// Silence - -void RemoteDebug::silence(boolean activate, boolean showMessage, boolean fromBreak, uint32_t timeout) { - - // Set silence and timeout - - if (showMessage) { - - if (activate) { - - debugPrintln("* Debug now is in silent mode!"); -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - if (_connectedWS) { - debugPrintln("* Press button \"Silence\" or another command to return show debugs"); - } else { - debugPrintln("* Press s again or another command to return show debugs"); - } -#else - debugPrintln("* Press s again or another command to return show debugs"); -#endif - } else { - - debugPrintln("* Debug now exit from silent mode!"); - } - } - - // Set it - - _silence = activate; - _silenceTimeout = (timeout == 0)?0:(millis() + timeout); - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - - // Send status to app - - if (_connectedWS) { - DebugWS.printf("$app:S:%c\n", ((_silence)? '1':'0')); - } - -#endif - -} - -boolean RemoteDebug::isSilence() { - - return _silence; -} - -// Format numbers - -String RemoteDebug::formatNumber(uint32_t value, uint8_t size, char insert) { - - // Putting zeroes in left - - String ret = ""; - - for (uint8_t i = 1; i <= size; i++) { - uint32_t max = pow(10, i); - if (value < max) { - for (uint8_t j = (size - i); j > 0; j--) { - ret.concat(insert); - } - break; - } - } - - ret.concat(value); - - return ret; -} - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - -/////// Web socket routines - -// Process user command over telnet or web socket - -void RemoteDebug::wsOnReceive(const char* command) { // @suppress("Unused function declaration") - - // Process the command - - _command = command; - _lastCommand = _command; // Store the last command - - D("cmd: %s", command); - - // Is app commands - - if (_command == "$app") { - - // RemoteDebug connected, send info - - wsSendInfo (); - - } else { // Normal commands - - processCommand(); - } -} - -// Send info to RemoteDebugApp - -void RemoteDebug::wsSendInfo() { - - // Send version, board, debugger disabled and if is low or enough memory board - - char features; - char dbgEnabled; - - // Not connected ? - - if (!_connectedWS) { - return; - } - - // Features - -#ifndef DEBUGGER_ENABLED - features = 'M'; // Disabled - dbgEnabled = 'D'; -#else - if (_callbackDbgProcessCmd) { - features = 'E'; // Enough - dbgEnabled = 'E'; - } else { - features = 'M'; // Medium - dbgEnabled = 'D'; - } -#endif - - // Send info - - String version = String(VERSION); - String board; - -#ifdef ESP32 - board = "ESP32"; -#else - board = "ESP8266"; -#endif - - DebugWS.println(); // Workaround to not get dirty "[0m" ??? - DebugWS.printf("$app:V:%s:%s:%c:%lu:%c:N\n", version.c_str(), board.c_str(), features, getFreeMemory(), dbgEnabled); - - // Status of debug level - - wsSendLevelInfo(); - - // Send status of debugger - - // TODO: made it - -} - -void RemoteDebug::wsSendLevelInfo() { - - // Send debug level info to app - - if (_connectedWS) { - DebugWS.printf("$app:L:%u\n", _clientDebugLevel); - } -} - -#endif // WEBSOCKET_DISABLED - -boolean RemoteDebug::wsIsConnected() { - - // Web socket is connected (RemoteDebugApp) - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - return _connectedWS; -#else - return false; -#endif -} - -/////// Utilities - -// Get free memory - -uint32_t RemoteDebug::getFreeMemory() { - - return ESP.getFreeHeap(); - -} - -// Is CR or LF ? - -boolean RemoteDebug::isCRLF(char character) { - - return (character == '\r' || character == '\n'); - -} - -// Expand characters as CR/LF to \\r, \\n -// TODO: make this for another chars not printable - -String RemoteDebug::expand(String string) { - - string.replace("\r", "\\r"); - string.replace("\n", "\\n"); - - return string; -} - -#ifdef ALPHA_VERSION // In test, not good yet -// Send telnet commands (as used with password request) - 18/08/18 -// Experimental code ! - -void RemoteDebug::sendTelnetCommand(uint8_t command, uint8_t option) { - - // Send a command to the telnet client - - debugPrintf("%c%c%c", TELNET_IAC, command, option); - TelnetClient.flush(); -} -#endif - -#else // DEBUG_DISABLED - -/////// All debug is disabled, this include is to define empty debug macros - -#include "RemoteDebug.h" // This library - -#endif // DEBUG_DISABLED - -/////// End diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/RemoteDebug.h b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/RemoteDebug.h deleted file mode 100644 index 902cd4d..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/RemoteDebug.h +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Header for RemoteDebug - * - * MIT License - * - * Copyright (c) 2019 Joao Lopes - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * - */ - -#ifndef REMOTEDEBUG_H -#define REMOTEDEBUG_H -#pragma once - -///// RemoteDebug configuration - -#include "RemoteDebugCfg.h" - -// Debug enabled ? - -#ifndef DEBUG_DISABLED - -//////// Defines - -// New color system (comment this to return to old system) - 2019-02-27 - -#define COLOR_NEW_SYSTEM true - -// ANSI Colors - -#define COLOR_RESET "\x1B[0m" - -#define COLOR_BLACK "\x1B[0;30m" -#define COLOR_RED "\x1B[0;31m" -#define COLOR_GREEN "\x1B[0;32m" -#define COLOR_YELLOW "\x1B[0;33m" -#define COLOR_BLUE "\x1B[0;34m" -#define COLOR_MAGENTA "\x1B[0;35m" -#define COLOR_CYAN "\x1B[0;36m" -#define COLOR_WHITE "\x1B[0;37m" - -#define COLOR_DARK_BLACK "\x1B[1;30m" - -#define COLOR_LIGHT_RED "\x1B[1;31m" -#define COLOR_LIGHT_GREEN "\x1B[1;32m" -#define COLOR_LIGHT_YELLOW "\x1B[1;33m" -#define COLOR_LIGHT_BLUE "\x1B[1;34m" -#define COLOR_LIGHT_MAGENTA "\x1B[1;35m" -#define COLOR_LIGHT_CYAN "\x1B[1;36m" -#define COLOR_LIGHT_WHITE "\x1B[1;37m" - -#define COLOR_BACKGROUND_BLACK "\x1B[40m" -#define COLOR_BACKGROUND_RED "\x1B[41m" -#define COLOR_BACKGROUND_GREEN "\x1B[42m" -#define COLOR_BACKGROUND_YELLOW "\x1B[43m" -#define COLOR_BACKGROUND_BLUE "\x1B[44m" -#define COLOR_BACKGROUND_MAGENTA "\x1B[45m" -#define COLOR_BACKGROUND_CYAN "\x1B[46m" -#define COLOR_BACKGROUND_WHITE "\x1B[47m" - -#ifdef COLOR_NEW_SYSTEM -// New system of Colors -// Note: this colors is not equals to SerialDebug colors, due using standard 16 colors of Ansi, for compatibility -#define COLOR_VERBOSE COLOR_GREEN -#define COLOR_DEBUG COLOR_LIGHT_GREEN -//#define COLOR_INFO COLOR_YELLOW -//#define COLOR_WARNING COLOR_CYAN -//#define COLOR_ERROR COLOR_RED -#define COLOR_INFO COLOR_LIGHT_YELLOW -#define COLOR_WARNING COLOR_LIGHT_CYAN -#define COLOR_ERROR COLOR_LIGHT_RED -#define COLOR_RAW COLOR_WHITE // COLOR_MAGENTA -#endif - -//////// Includes - -#include "Arduino.h" -#include "Print.h" - -// ESP8266 or ESP32 ? - -#if defined(ESP8266) - -#include - -#elif defined(ESP32) - -#include - -#else - -#error "Only for ESP8266 or ESP32" - -#endif - -////// Shortcuts macros - -// Auto function for debug macros? - -#ifndef DEBUG_DISABLE_AUTO_FUNC // With auto func - - #ifdef ESP32 - - // ESP32 -> Multicore - show core id ? - - #define DEBUG_AUTO_CORE true // debug show core id ? (comment to disable it) - - #ifdef DEBUG_AUTO_CORE - - #define rdebugA(fmt, ...) if (Debug.isActive(Debug.ANY)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) - #define rdebugP(fmt, ...) if (Debug.isActive(Debug.PROFILER)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) - #define rdebugV(fmt, ...) if (Debug.isActive(Debug.VERBOSE)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) - #define rdebugD(fmt, ...) if (Debug.isActive(Debug.DEBUG)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) - #define rdebugI(fmt, ...) if (Debug.isActive(Debug.INFO)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) - #define rdebugW(fmt, ...) if (Debug.isActive(Debug.WARNING)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) - #define rdebugE(fmt, ...) if (Debug.isActive(Debug.ERROR)) Debug.printf("(%s)(C%d) " fmt, __func__, xPortGetCoreID(), ##__VA_ARGS__) - - #endif - - #endif - - #ifndef DEBUG_AUTO_CORE // No auto core or for ESP8266 - - #define rdebugA(fmt, ...) if (Debug.isActive(Debug.ANY)) Debug.printf("(%s) " fmt, __func__, ##__VA_ARGS__) - #define rdebugP(fmt, ...) if (Debug.isActive(Debug.PROFILER)) Debug.printf("(%s) " fmt, __func__, ##__VA_ARGS__) - #define rdebugV(fmt, ...) if (Debug.isActive(Debug.VERBOSE)) Debug.printf("(%s) " fmt, __func__, ##__VA_ARGS__) - #define rdebugD(fmt, ...) if (Debug.isActive(Debug.DEBUG)) Debug.printf("(%s) " fmt, __func__, ##__VA_ARGS__) - #define rdebugI(fmt, ...) if (Debug.isActive(Debug.INFO)) Debug.printf("(%s) " fmt, __func__, ##__VA_ARGS__) - #define rdebugW(fmt, ...) if (Debug.isActive(Debug.WARNING)) Debug.printf("(%s) " fmt, __func__, ##__VA_ARGS__) - #define rdebugE(fmt, ...) if (Debug.isActive(Debug.ERROR)) Debug.printf("(%s) " fmt, __func__, ##__VA_ARGS__) - - #endif - -#else // Without auto func - - #define rdebugA(fmt, ...) if (Debug.isActive(Debug.ANY)) Debug.printf(fmt, ##__VA_ARGS__) - #define rdebugP(fmt, ...) if (Debug.isActive(Debug.PROFILER)) Debug.printf(fmt, ##__VA_ARGS__) - #define rdebugV(fmt, ...) if (Debug.isActive(Debug.VERBOSE)) Debug.printf(fmt, ##__VA_ARGS__) - #define rdebugD(fmt, ...) if (Debug.isActive(Debug.DEBUG)) Debug.printf(fmt, ##__VA_ARGS__) - #define rdebugI(fmt, ...) if (Debug.isActive(Debug.INFO)) Debug.printf(fmt, ##__VA_ARGS__) - #define rdebugW(fmt, ...) if (Debug.isActive(Debug.WARNING)) Debug.printf(fmt, ##__VA_ARGS__) - #define rdebugE(fmt, ...) if (Debug.isActive(Debug.ERROR)) Debug.printf(fmt, ##__VA_ARGS__) - -#endif - -// With newline - -#define rdebugAln(fmt, ...) rdebugA(fmt "\n", ##__VA_ARGS__) -#define rdebugPln(fmt, ...) rdebugP(fmt "\n", ##__VA_ARGS__) -#define rdebugVln(fmt, ...) rdebugV(fmt "\n", ##__VA_ARGS__) -#define rdebugDln(fmt, ...) rdebugD(fmt "\n", ##__VA_ARGS__) -#define rdebugIln(fmt, ...) rdebugI(fmt "\n", ##__VA_ARGS__) -#define rdebugWln(fmt, ...) rdebugW(fmt "\n", ##__VA_ARGS__) -#define rdebugEln(fmt, ...) rdebugE(fmt "\n", ##__VA_ARGS__) - -// For old versions compatibility - -#define rdebug(fmt, ...) rdebugA(fmt, ##__VA_ARGS__) - -// Another way - for compatibility - -#define DEBUG(fmt, ...) rdebugA(fmt, ##__VA_ARGS__) - -#define DEBUG_A(fmt, ...) rdebugA(fmt, ##__VA_ARGS__) -#define DEBUG_P(fmt, ...) rdebugP(fmt, ##__VA_ARGS__) -#define DEBUG_V(fmt, ...) rdebugV(fmt, ##__VA_ARGS__) -#define DEBUG_D(fmt, ...) rdebugD(fmt, ##__VA_ARGS__) -#define DEBUG_I(fmt, ...) rdebugI(fmt, ##__VA_ARGS__) -#define DEBUG_W(fmt, ...) rdebugW(fmt, ##__VA_ARGS__) -#define DEBUG_E(fmt, ...) rdebugE(fmt, ##__VA_ARGS__) - -// New way: To compatibility with SerialDebug (can use RemoteDebug or SerialDebug) -// This is my favorite :) - -#define debugV(fmt, ...) rdebugVln(fmt, ##__VA_ARGS__) -#define debugD(fmt, ...) rdebugDln(fmt, ##__VA_ARGS__) -#define debugI(fmt, ...) rdebugIln(fmt, ##__VA_ARGS__) -#define debugW(fmt, ...) rdebugWln(fmt, ##__VA_ARGS__) -#define debugE(fmt, ...) rdebugEln(fmt, ##__VA_ARGS__) -#define debugA(fmt, ...) rdebugAln(fmt, ##__VA_ARGS__) - -#define debugHandle() Debug.handle() - -// Macros used by code converter for codes with several prints to only message -// due the converter cannot -// convert severals Serial.print in one debug* macro. - -#define rprintV(x, ...) if (Debug.isActive(Debug.VERBOSE)) Debug.print(x, ##__VA_ARGS__) -#define rprintD(x, ...) if (Debug.isActive(Debug.DEBUG)) Debug.print(x, ##__VA_ARGS__) -#define rprintI(x, ...) if (Debug.isActive(Debug.INFO)) Debug.print(x, ##__VA_ARGS__) -#define rprintW(x, ...) if (Debug.isActive(Debug.WARNING)) Debug.print(x, ##__VA_ARGS__) -#define rprintE(x, ...) if (Debug.isActive(Debug.ERROR)) Debug.print(x, ##__VA_ARGS__) -#define rprintA(x, ...) if (Debug.isActive(Debug.ANY)) Debug.print(x, ##__VA_ARGS__) - - -#define rprintVln(x, ...) if (Debug.isActive(Debug.VERBOSE)) Debug.println(x, ##__VA_ARGS__) -#define rprintDln(x, ...) if (Debug.isActive(Debug.DEBUG)) Debug.println(x, ##__VA_ARGS__) -#define rprintIln(x, ...) if (Debug.isActive(Debug.INFO)) Debug.println(x, ##__VA_ARGS__) -#define rprintWln(x, ...) if (Debug.isActive(Debug.WARNING)) Debug.println(x, ##__VA_ARGS__) -#define rprintEln(x, ...) if (Debug.isActive(Debug.ERROR)) Debug.println(x, ##__VA_ARGS__) -#define rprintAln(x, ...) if (Debug.isActive(Debug.ANY)) Debug.println(x, ##__VA_ARGS__) - -///// Class - -class RemoteDebug: public Print -{ - public: - - // Constructor - - RemoteDebug(); - - // Methods - - bool begin(String hostName, uint16_t port, uint8_t startingDebugLevel = DEBUG); - bool begin(String hostName, uint8_t startingDebugLevel = DEBUG); - - void setPassword(String password); - - void stop(); - - void handle(); - - void disconnect(boolean onlyTelnetClient = false); - - void setSerialEnabled(boolean enable); - - void setResetCmdEnabled(boolean enable); - - void setHelpProjectsCmds(String help); - void setCallBackProjectCmds(void (*callback)()); - String getLastCommand(); - void clearLastCommand(); - - void showTime(boolean show); - void showProfiler(boolean show, uint32_t minTime = 0); - void showDebugLevel(boolean show); - void showColors(boolean show); - - void showRaw(boolean show); - - void setCallBackNewClient(void (*callback)()); - -#ifdef ALPHA_VERSION // In test, not good yet - void autoProfilerLevel(uint32_t millisElapsed); -#endif - - void setFilter(String filter); - void setNoFilter(); - - boolean isActive(uint8_t debugLevel = DEBUG); - - void silence(boolean activate, boolean showMessage = true, boolean fromBreak = false, uint32_t timeout = 0); - boolean isSilence(); - - void onConnection(boolean connected); - - boolean isConnected(); - -#ifdef DEBUGGER_ENABLED - // For Simple software debugger - based on SerialDebug Library - void initDebugger(boolean (*callbackEnabled)(), void (*callbackHandle)(const boolean), String (*callbackGetHelp)(), void (*callbackProcessCmd)()); - WiFiClient* getTelnetClient(); -#endif - -#ifndef WEBSOCKET_DISABLED // For web socket server (app) - void wsOnReceive(const char* command); - void wsSendInfo(); - void wsSendLevelInfo(); -#endif - boolean wsIsConnected(); - - // Print - - virtual size_t write(uint8_t); - - virtual size_t write(const uint8_t *buffer, size_t size); // Insert due a write bug w/ latest Esp8266 SDK - 17/08/18 - - // Debug levels - - static const uint8_t PROFILER = 0; // Used for show time of execution of pieces of code(profiler) - static const uint8_t VERBOSE = 1; // Used for show verboses messages - static const uint8_t DEBUG = 2; // Used for show debug messages - static const uint8_t INFO = 3; // Used for show info messages - static const uint8_t WARNING = 4; // Used for show warning messages - static const uint8_t ERROR = 5; // Used for show error messages - static const uint8_t ANY = 6; // Used for show always messages, for any current debug level - - // Expand characters as CR/LF to \\r, \\n - - String expand(String string); - - // Destructor - - ~RemoteDebug(); - -private: - - // Variables - - String _hostName = ""; // Host name - - boolean _connected = false; // Client is connected ? - - String _password = ""; // Password - - boolean _passwordOk = false; // Password request ? - 18/07/18 - uint8_t _passwordAttempt = 0; - - boolean _silence = false; // Silence mode ? - uint32_t _silenceTimeout = 0; // Silence timeout - - uint8_t _clientDebugLevel = DEBUG; // Level setted by user in web app or telnet client - uint8_t _lastDebugLevel = DEBUG; // Last Level setted by active() - - uint32_t _lastTimePrint = millis(); // Last time print a line - - uint8_t _levelBeforeProfiler=DEBUG; // Last Level before Profiler level - uint32_t _levelProfilerDisable = 0; // time in millis to disable the profiler level - uint32_t _autoLevelProfiler = 0; // Automatic change to profiler level if time between handles is greater than n millis - - boolean _showTime = false; // Show time in millis - - boolean _showProfiler = false; // Show time between messages - uint32_t _minTimeShowProfiler = 0; // Minimal time to show profiler - - boolean _showDebugLevel = true; // Show debug Level - - boolean _showColors = false; // Show colors - - boolean _showRaw = false; // Show in raw mode ? - - boolean _serialEnabled = false; // Send to serial too (not recommended) - - boolean _resetCommandEnabled=false; // Enable command to reset the board - - boolean _newLine = true; // New line write ? - - String _command = ""; // Command received - String _lastCommand = ""; // Last Command received - uint32_t _lastTimeCommand = millis();// Last time command received - String _helpProjectCmds = ""; // Help of comands setted by project (sketch) - void (*_callbackProjectCmds)() = NULL; // Callable for projects commands - void (*_callbackNewClient)() = NULL; // Callable for when have a new client connected - - String _filter = ""; // Filter - boolean _filterActive = false; - - String _bufferPrint = ""; // Buffer of print write to WiFi - -#ifdef CLIENT_BUFFERING - String _bufferSend = ""; // Buffer to send data to web app or telnet client - uint16_t _sizeBufferSend = 0; // Size of it - uint32_t _lastTimeSend = 0; // Last time command send data -#endif - -#ifdef DEBUGGER_ENABLED -// // For Simple software debugger - based on SerialDebug Library - boolean (*_callbackDbgEnabled)() = NULL;// Callable for debugger enabled - void (*_callbackDbgHandle)(const boolean) = NULL; // Callable for handle of debugger - String (*_callbackDbgHelp)() = NULL; // Callable for get debugger help - void (*_callbackDbgProcessCmd)() = NULL;// Callable for process commands of debugger -#endif - - //////// Privates - - void showHelp(); - void processCommand(); - String formatNumber(uint32_t value, uint8_t size, char insert='0'); - boolean isCRLF(char character); - uint32_t getFreeMemory(); - -#ifdef ALPHA_VERSION // In test, not good yet - void sendTelnetCommand(uint8_t command, uint8_t option); -#endif - -}; - -#else // DEBUG_DISABLED - -// Disable debug macros - -#define rdebugAln(...) -#define rdebugPln(...) -#define rdebugVln(...) -#define rdebugDln(...) -#define rdebugIln(...) -#define rdebugWln(...) -#define rdebugEln(...) -#define rdebug(...) - -#define DEBUG(...) - -#define DEBUG_A(...) -#define DEBUG_P(...) -#define DEBUG_V(...) -#define DEBUG_D(...) -#define DEBUG_I(...) -#define DEBUG_W(...) -#define DEBUG_E(...) - -#define debugA(...) -#define debugP(...) -#define debugV(...) -#define debugD(...) -#define debugI(...) -#define debugW(...) -#define debugE(...) - -#define rprintA(...) -#define rprintV(...) -#define rprintD(...) -#define rprintI(...) -#define rprintW(...) -#define rprintE(...) - -#define rprintAln(...) -#define rprintVln(...) -#define rprintDln(...) -#define rprintIln(...) -#define rprintWln(...) -#define rprintEln(...) - -#define debugHandle() - -// Note all of Debug. codes need uses "#ifndef DEBUG_DISABLED" -// For example, the initialization codes and: - -//#ifndef DEBUG_DISABLED -//if (Debug.isActive(Debug.VERBOSE)) { -// Debug.printf("bla bla bla: %d %s", number, str); // OR -// Debug.printf("bla bla bla: %d %s", number, str.c_str()); // Note: if type is String need c_str() // OR -// Debug.println("bla bla bla 2 ln"); -//} -//#endif - -#endif // DEBUG_DISABLED - -#endif // H - -//////// End diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/RemoteDebugCfg.h b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/RemoteDebugCfg.h deleted file mode 100644 index 215c898..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/RemoteDebugCfg.h +++ /dev/null @@ -1,135 +0,0 @@ - -/* - * Header for RemoteDebugCfg - * - * MIT License - * - * Copyright (c) 2019 Joao Lopes - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * - */ - -/* - * All configurations have moved to RemoteDebugCfg.h, - * to facilitate changes - */ - -///////////// User config, to not lost in library updates - -#ifndef REMOTEDEBUGCFG_H_ -#define REMOTEDEBUGCFG_H_ -#pragma once - -///////////// For RemoteDebug /////////////////// - -///// Debug disable for compile to production/release -///// as nothing of RemotedDebug is compiled, zero overhead :-) -//#define DEBUG_DISABLED true - -// Debug enabled ? - -#ifndef DEBUG_DISABLED - -///// Port for telnet server -#define TELNET_PORT 23 - -// Disable auto function for debug macros? (uncomment this if not want this) -//#define DEBUG_DISABLE_AUTO_FUNC true - -// Simple password request - left commented if not need this - 18/07/18 -// Notes: -// It is very simple feature, only text, no cryptography, -// and the password is echoed in screen (I not discovery yet how disable it) -// telnet use advanced authentication (kerberos, etc.) -// Such now as RemoteDebug now is not for production releases, -// this kind of authentication will not be done now. -// Can be by project, just call setPassword method -#define REMOTEDEBUG_PWD_ATTEMPTS 3 - -// Maximum time for inactivity (em milliseconds) -// Default: 10 minutes -// Comment it if you not want this -// Can be by project, just define it before include this file -//#define MAX_TIME_INACTIVE 600000 - -// Buffered print write to WiFi -> length of buffer -// Can be by project, just define it before include this file -#define BUFFER_PRINT 170 - -// Should the help text be displayed on connection. -// Enabled by default, comment to disable -#define SHOW_HELP true - -// Buffering (sends in interval of time to avoid ESP misterious delays) -// Uncomment this to disable it -#define CLIENT_BUFFERING true -#ifdef CLIENT_BUFFERING -#define DELAY_TO_SEND 10 // Time to send buffer -#define MAX_SIZE_SEND 1460 // Maximum size of packet (limit of TCP/IP) -#endif - -// Enable if you test features yet in development -//#define ALPHA_VERSION true - -// Debugger support enabled ? -// Comment this to disable it -#define DEBUGGER_ENABLED true -#ifdef DEBUGGER_ENABLED -#define DEBUGGER_HANDLE_TIME 850 // Interval to call handle of debugger - equal to implemented in debugger -// App have debugger elements on screen ? -// Note: app not have it yet -//#define DEBUGGER_SEND_INFO true -#endif - -///// Websocket server to support debug over web browser (RemoteDebugApp) -// Uncomment this to disable it -#define WEBSOCKET_DISABLED true - -///////////// For RemoteDebugWS /////////////////// - -#ifndef WEBSOCKET_DISABLED - -// Websocket port -#define WEBSOCKET_PORT 8232 - -// Library arduinoWebSockets already installed, uncomment to use it -// Do this if you receive errors of multiple definition ... -//#define USE_LIB_WEBSOCKET true -#endif - -///////////// For RemoteDebugger /////////////////// - -// Enable Flash variables support - F() -// Used internally in SerialDebug and in public API -// If is a low memory board, like AVR, all strings in SerialDebug is using flash memory -// If have RAM memory, this is more fast than flash -//#define DEBUG_USE_FLASH_F true - -// For Espressif boards, default is not flash support for printf, -// due it have a lot of memory and Serial.printf is not compatible with it -// If you need more memory, can force it: -//#define DEBUG_USE_FLASH_F true - -#endif /* DEBUG_DISABLED */ - -#endif /* REMOTEDEBUGCFG_H_ */ - -////// End diff --git a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/telnet.h b/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/telnet.h deleted file mode 100644 index 474dcde..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/RemoteDebug/src/telnet.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * telnet.h - Telnet Defines - * - * Note: only used is uncommented - * */ - -#ifndef TELNET_H -#define TELNET_TELNET_H - -#define TELNET_IAC 255 -#define TELNET_DONT 254 -#define TELNET_DO 253 -#define TELNET_WONT 252 -#define TELNET_WILL 251 - -// #define TELNET_SE 240 // Subnegotiation End -// #define TELNET_NOP 241 // No Operation -// #define TELNET_DM 242 // Data Mark -// #define TELNET_BRK 243 // Break -// #define TELNET_IP 244 // Interrupt process -// #define TELNET_AO 245 // Abort output -// #define TELNET_AYT 246 // Are You There -// #define TELNET_EC 247 // Erase Character -// #define TELNET_EL 248 // Erase Line -//#define TELNET_GA 249 // Go Ahead -// #define TELNET_SB 250 // Subnegotiation Begin - -// #define TELNET_BINARY 0 // 8-bit data path -#define TELNET_ECHO 1 // echo -// #define TELNET_RCP 2 // prepare to reconnect -#define TELNET_SGA 3 // suppress go ahead -// #define TELNET_NAMS 4 // approximate message size -// #define TELNET_STATUS 5 // give status -// #define TELNET_TM 6 // timing mark -// #define TELNET_RCTE 7 // remote controlled transmission and echo -// #define TELNET_NAOL 8 // negotiate about output line width -// #define TELNET_NAOP 9 // negotiate about output page size -// #define TELNET_NAOCRD 10 // negotiate about CR disposition -// #define TELNET_NAOHTS 11 // negotiate about horizontal tabstops -// #define TELNET_NAOHTD 12 // negotiate about horizontal tab disposition -// #define TELNET_NAOFFD 13 // negotiate about formfeed disposition -// #define TELNET_NAOVTS 14 // negotiate about vertical tab stops -// #define TELNET_NAOVTD 15 // negotiate about vertical tab disposition -// #define TELNET_NAOLFD 16 // negotiate about output LF disposition -// #define TELNET_XASCII 17 // extended ascii character set -// #define TELNET_LOGOUT 18 // force logout -// #define TELNET_BM 19 // byte macro -// #define TELNET_DET 20 // data entry terminal -// #define TELNET_SUPDUP 21 // supdup protocol -// #define TELNET_SUPDUPOUTPUT 22 // supdup output -// #define TELNET_SNDLOC 23 // send location -// #define TELNET_TTYPE 24 // terminal type -// #define TELNET_EOR 25 // end or record -// #define TELNET_TUID 26 // TACACS user identification -// #define TELNET_OUTMRK 27 // output marking -// #define TELNET_TTYLOC 28 // terminal location number -// #define TELNET_VT3270REGIME 29 // 3270 regime -// #define TELNET_X3PAD 30 // X.3 PAD -// #define TELNET_NAWS 31 // window size -// #define TELNET_TSPEED 32 // terminal speed -// #define TELNET_LFLOW 33 // remote flow control -// #define TELNET_LINEMODE 34 // Linemode option -// #define TELNET_XDISPLOC 35 // X Display Location -// #define TELNET_OLD_ENVIRON 36 // Old - Environment variables -// #define TELNET_AUTHENTICATION 37 // Authenticate -// #define TELNET_ENCRYPT 38 // Encryption option -// #define TELNET_NEW_ENVIRON 39 // New - Environment variables -// #define TELNET_TN3270E 40 // TN3270E -// #define TELNET_XAUTH 41 // XAUTH -// #define TELNET_CHARSET 42 // CHARSET -// #define TELNET_RSP 43 // Telnet Remote Serial Port -// #define TELNET_COM_PORT_OPTION 44 // Com Port Control Option -#define TELNET_SUPPRESS_LOCAL_ECHO 45 // Telnet Suppress Local Echo -// #define TELNET_TLS 46 // Telnet Start TLS -// #define TELNET_KERMIT 47 // KERMIT -// #define TELNET_SEND_URL 48 // SEND-URL -// #define TELNET_FORWARD_X 49 // FORWARD_X -// #define TELNET_PRAGMA_LOGON 138 // TELOPT PRAGMA LOGON -// #define TELNET_SSPI_LOGON 139 // TELOPT SSPI LOGON -// #define TELNET_PRAGMA_HEARTBEAT 140 // TELOPT PRAGMA HEARTBEAT -// #define TELNET_EXOPL 255 // Extended-Options-List -// #define TELNET_NOOPT 0 - -// #define TELNET_IS 0 -// #define TELNET_SEND 1 - -#endif // TELNET_H - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/LICENSE b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/LICENSE deleted file mode 100644 index cf1ab25..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/README.md b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/README.md deleted file mode 100644 index 7dc5190..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/README.md +++ /dev/null @@ -1,518 +0,0 @@ -[![PlatformIO](https://github.com/chrisjoyce911/esp32FOTA/workflows/PlatformIO/badge.svg)](https://github.com/chrisjoyce911/esp32FOTA/actions/) - -[![arduino-library-badge](https://www.ardu-badge.com/badge/esp32FOTA.svg?)](https://www.ardu-badge.com/esp32FOTA) -[![PlatformIO Registry](https://badges.registry.platformio.org/packages/chrisjoyce911/library/esp32FOTA.svg)](https://registry.platformio.org/libraries/chrisjoyce911/esp32FOTA) - - - -# esp32FOTA library for Arduino - -## Purpose - -A simple library to add support for Over-The-Air (OTA) updates to your project. - -## Features - -- [x] Zlib or gzip compressed firmware support -- [x] SPIFFS/LittleFS partition Update [#25], [#47], [#60], [#92] (thanks to all participants) -- [x] Any fs::FS support (SPIFFS/LITTLEFS/SD) for cert/signature storage [#79], [#74], [#91], [#92] (thanks to all participants) -- [x] Seamless http/https -- [x] Web update (requires web server) -- [x] Batch firmware sync -- [x] Force firmware update [#8] -- [x] https support [#26] ( Thanks to @fbambusi ) -- [x] Signature check of downloaded firmware-image [#65] -- [x] Signature verification -- [x] Semantic versioning support -- [ ] Checking for update via bin headers [#15] - -## How it works - -This library tries to access a JSON file hosted on a webserver, and reviews it to decide if a newer firmware has been published, if so it will download it and install it. - -There are a few things that need to be in place for an update to work. - -- A webserver with the firmware information in a JSON file -- Firmware version -- Firmware type -- Firmware bin (can optionnally be compressed with zlib or gzip) -- For https or signature check: SPIFFS with root_ca.pem (https) and rsa_key.pem (signature check) - -You can supply http or https URLs. If you are using https, you need the root_ca.pem in your SPIFFS partition. For the actual firmware it will use https when you define port 443 or 4433. Otherwise it will use plain http. - -## Usage - -### Hosted JSON - -This is hosted by a webserver and contains information about the latest firmware: - -```json -{ - "type": "esp32-fota-http", - "version": 2, - "host": "192.168.0.100", - "port": 80, - "bin": "/fota/esp32-fota-http-2.bin" -} -``` - -Version information can be either a single number or a semantic version string. Alternatively, a full URL path can be provided: - -```json -{ - "type": "esp32-fota-http", - "version": "2.5.1", - "url": "http://192.168.0.100/fota/esp32-fota-http-2.bin" -} -``` - -A single JSON file can provide information on multiple firmware types by combining them together into an array. When this is loaded, the firmware manifest with a type matching the one passed to the esp32FOTA constructor will be selected: - -```json -[ - { - "type":"esp32-fota-http", - "version":"0.0.2", - "url":"http://192.168.0.100/fota/esp32-fota-http-2.bin" - }, - { - "type":"esp32-other-hardware", - "version":"0.0.3", - "url":"http://192.168.0.100/fota/esp32-other-hardware.bin" - } -] -``` - - -A single JSON file can also contain several versions of a single firmware type: - -```json -[ - { - "type":"esp32-fota-http", - "version":"0.0.2", - "url":"http://192.168.0.100/fota/esp32-fota-0.0.2.bin" - }, - { - "type":"esp32-fota-http", - "version":"0.0.3", - "url":"http://192.168.0.100/fota/esp32-fota-0.0.3.bin", - "spiffs":"http://192.168.0.100/fota/esp32-fota-0.0.3.spiffs.bin" - } -] -``` - - - - -#### Filesystem image (spiffs/littlefs) - -Adding `spiffs` key to the JSON entry will end up with the filesystem being updated first, then the firmware. - -Obviously don't use the filesystem you're updating to store certificates needed by the update, spiffs partition -doesn't have redundancy like OTA0/OTA1 and won't recover from a failed update without a restart and format. - -```json -{ - "type": "esp32-fota-http", - "version": 2, - "host": "192.168.0.100", - "port": 80, - "bin": "/fota/esp32-fota-http-2.bin", - "spiffs": "/fota/default_spiffs.bin" -} -``` - -Other accepted keys for filesystems are `spiffs`, `littlefs` and `fatfs`. -Picking one or another doesn't make any difference yet. - - -#### Firmware types - -Types are used to compare with the current loaded firmware, this is used to make sure that when loaded, the device will still do the intended job. - -As an example, a device used as a data logger should ony be updated with new versions of the data logger. - -##### examples - -- TTGO-T8-ESP32-Logger -- TTGO-T8-ESP32-Temp -- TTGO-T8-ESP32-Relay - - -### Debug - -Messages depends of build level. If you pass -D CORE_DEBUG_LEVEL=3 to build flags, it enable the messages - -### Sketch - -In this early init example, a version 1 of 'esp32-fota-http' is in use, it would be updated when using the JSON example. - -```cpp -#include - -const char *ssid = ""; -const char *password = ""; - -esp32FOTA esp32FOTA("esp32-fota-http", "1.0.0"); - -const char* manifest_url = "http://server/fota/fota.json"; - -void setup() -{ - Serial.begin(115200); - setup_wifi(); - esp32FOTA.setManifestURL( manifest_url ); - // esp32FOTA.useDeviceId( true ); // optionally append the device ID to the HTTP query -} - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to "); - WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } -} - -void loop() -{ - esp32FOTA.handle(); - // or ... - // bool updatedNeeded = esp32FOTA.execHTTPcheck(); - // if (updatedNeeded) { - // esp32FOTA.execOTA(); - // } - delay(2000); -} -``` - - -Late init is possible using `FOTAConfig_t`, allowing more complex configurations: - -```cpp -#include // include filesystem *before* esp32FOTA librart -#include - -esp32FOTA FOTA; - -const char* manifest_url = "http://server/fota/fota.json"; -const char* fota_name = "esp32-fota-http"; - -// CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SPIFFS ); -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &SD ); - -void setup() -{ - Serial.begin( 115200 ); - setup_wifi(); - - { - auto cfg = FOTA.getConfig(); - cfg.name = fota_name; - cfg.manifest_url = manifest_url; - cfg.sem = SemverClass( 1, 0, 0 ); // major, minor, patch - cfg.check_sig = false; // verify signed firmware with rsa public key - cfg.unsafe = true; // disable certificate check when using TLS - //cfg.root_ca = MyRootCA; - //cfg.pub_key = MyRSAKey; - //cfg.use_device_id = false; - FOTA.setConfig( cfg ); - } -} - -void loop() -{ - esp32FOTA.handle(); - // or ... - // bool updatedNeeded = esp32FOTA.execHTTPcheck(); - // if (updatedNeeded) { - // esp32FOTA.execOTA(); - // } - delay(2000); -} - -``` - - -### Zlib/gzip support - -⚠️ This feature cannot be used with signature check. - - -For firmwares compressed with `pigz` utility (see , file extension must be `.zz`: - -```cpp -#include // http://github.com/vortigont/esp32-flashz -#include -``` - -```bash -$ pigz -9kzc esp32-fota-http-2.bin > esp32-fota-http-2.bin.zz -``` - -```json -{ - "type": "esp32-fota-http", - "version": "2.5.1", - "url": "http://192.168.0.100/fota/esp32-fota-http-2.bin.zz" -} -``` - - -For firmwares compressed with `gzip` utility, file extension must be `.gz` - -```cpp -#include // http://github.com/tobozo/ESP32-targz -#include -``` - -```bash -$ gzip -c esp32-fota-http-2.bin > esp32-fota-http-2.bin.gz -``` - -```json -{ - "type": "esp32-fota-http", - "version": "2.5.1", - "url": "http://192.168.0.100/fota/esp32-fota-http-2.bin.gz" -} -``` - - - - - - -### Root Certificates - -Certificates and signatures can be stored in different places: any fs::FS filesystem or progmem as const char*. - -Filesystems: - -```C++ -CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SPIFFS ); -``` - -```C++ -CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &LittleFS ); -``` - -```C++ -CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SD ); -``` - -Progmem: - -```C++ -const char* root_ca = R"ROOT_CA( ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- -)ROOT_CA"; - -// mixed sources is possible -CryptoMemAsset *MyRootCA = new CryptoMemAsset("Root CA", root_ca, strlen(root_ca)+1 ); -CryptoFileAsset *MyPubKey = new CryptoFileAsset("RSA Key", "/rsa_key.pub", &SD); - -``` - -Then later in the `setup()`: - -```C++ - -const char* manifest_url = "http://server/fota/fota.json"; - -void setup() -{ - // (...) - esp32FOTA.setManifestURL( manifest_url ); - esp32FOTA.setRootCA( MyRootCA ); - esp32FOTA.setPubKey( MyPubKey ); -} - -``` - - -# Update callbacks - - -## Progress callback - -Can be used to draw a progress bar e.g. on a TFT. - -The callback signature is: `void my_progress_callback( size_t progress, size_t size);`, lambda functions are accepted. - -Use `esp32FOTA.setProgressCb( my_progress_callback )` to attach the callback. - -This method is aliased to Update.h `onProgress()` feature and defaults to printing dots in the serial console. - -```C++ -void my_progress_callback( size_t progress, size_t size ) -{ - if( progress == size || progress == 0 ) Serial.println(); - Serial.print("."); -} - -void setup() -{ - // (...) - - // usage with callback function: - esp32FOTA.setProgressCb( my_progress_callback ) ; - - // usage with lambda function: - esp32FOTA.setProgressCb( [](size_t progress, size_t size) { - if( progress == size || progress == 0 ) Serial.println(); - Serial.print("."); - }); -} -``` - - -## Update begin-fail callback - -- Description: fired when Update.begin() failed -- Callback type: `void(int partition)` -- Callback setter: `setUpdateBeginFailCb( cb )` -- Usage: - -```cpp -esp32FOTA.setUpdateBeginFailCb( [](int partition) { - Serial.printf("Update could not begin with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" ); -}); -``` - -## Update end callback - -- Description: fired after Update.end() and before signature check -- Callback type: `void(int partition)` -- Callback setter: `setUpdateEndCb( cb )` -- Usage: - -```cpp -esp32FOTA.setUpdateEndCb( [](int partition) { - Serial.printf("Update could not finish with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" ); -}); -``` - - -## Update check-fail callback - -- Description: fired when partition or signature check failed -- Callback type: `void(int partition, int update_error_code)` -- Callback setter: `setUpdateCheckFailCb( cb )` -- Usage: - -```cpp -esp32FOTA.setUpdateCheckFailCb( [](int partition, int error_code) { - Serial.printf("Update could validate %s partition (error %d)\n", partition==U_SPIFFS ? "spiffs" : "firmware", error_code ); - // error codes: - // -1 : partition not found - // -2 : validation (signature check) failed -}); -``` - - -## Update finished callback - -- Description: fired update is complete -- Callback type: `void(int partition, bool needs_restart)` -- Callback setter: `setUpdateFinishedCb( cb )` -- Usage: - -```cpp -esp32FOTA.setUpdateFinishedCb( [](int partition, bool restart_after) { - Serial.printf("Update could not begin with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" ); - // do some stuff e.g. notify a MQTT server the update completed successfully - if( restart_after ) { - ESP.restart(); - } -}); -``` - - - - - - -### Verified images via signature - -You can now sign your firmware image with an RSA public/private key pair and have the ESP32 check if the signature is correct before -it switches over to the new image. - -In order to use this feature just set the boolean `validate` to `true` in the constructor. Next create a key-pair to sign your firmware image: -``` -openssl genrsa -out priv_key.pem 4096 -openssl rsa -in priv_key.pem -pubout > rsa_key.pub -``` - -Compile your code so you get your OTA update file (e.g. `firmware.bin`). Now it's time to create the signature: -``` -# Create signature file -openssl dgst -sign priv_key.pem -keyform PEM -sha256 -out firmware.sign -binary firmware.bin - -# throw it all in one file -cat firmware.sign firmware.bin > firmware.img -``` - -Upload `firmware.img` to your OTA server and point to it in your `firmware.json` - -Last step, create an SPIFFS partition with your `rsa_key.pub` in it. The OTA update should not touch this partition during the update. You'll only need to distribute this partition once. - -On the next update-check the ESP32 will download the `firmware.img` extract the first 512 bytes with the signature and check it together with the public key against the new image. If the signature check runs OK, it'll reset into the new firmware. - - - -[#8]: https://github.com/chrisjoyce911/esp32FOTA/issues/8 -[#15]: https://github.com/chrisjoyce911/esp32FOTA/issues/15 -[#25]: https://github.com/chrisjoyce911/esp32FOTA/issues/25 -[#26]: https://github.com/chrisjoyce911/esp32FOTA/issues/26 -[#60]: https://github.com/chrisjoyce911/esp32FOTA/issues/60 -[#65]: https://github.com/chrisjoyce911/esp32FOTA/issues/65 -[#74]: https://github.com/chrisjoyce911/esp32FOTA/issues/74 -[#47]: https://github.com/chrisjoyce911/esp32FOTA/pull/47 -[#79]: https://github.com/chrisjoyce911/esp32FOTA/pull/79 -[#91]: https://github.com/chrisjoyce911/esp32FOTA/pull/91 -[#92]: https://github.com/chrisjoyce911/esp32FOTA/pull/92 - - -### Libraries - -This library relies on [semver.c by h2non](https://github.com/h2non/semver.c) for semantic versioning support. semver.c is licensed under [MIT](https://github.com/h2non/semver.c/blob/master/LICENSE). - -Optional dependencies (zlib/gzip support): -* [esp32-flashz](https://github.com/vortigont/esp32-flashz) -* [esp32-targz](https://github.com/tobozo/ESP32-targz) - - -### Thanks to - -* @nuclearcat -* @thinksilicon -* @tuan-karma -* @hpsaturn -* @tobozo -* @vortigont - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTP/HTTP.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTP/HTTP.ino deleted file mode 100644 index 0f58694..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTP/HTTP.ino +++ /dev/null @@ -1,60 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update from a bin located on a webserver (HTTP Only) - - Setup: - Step 1 : Set your WiFi (ssid & password) - Step 2 : set esp32fota() - - Upload: - Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder) - Step 2 : Upload it to your webserver - Step 3 : Update your firmware JSON file ( see firwmareupdate ) - -*/ - -#include -#include - - -// esp32fota esp32fota("", , ); -esp32FOTA esp32FOTA("esp32-fota-http", 1, false); - -const char* manifest_url = "http://server/fota/fota.json"; - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi"); - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - -void setup() -{ - Serial.begin(115200); - esp32FOTA.setManifestURL( manifest_url ); - esp32FOTA.printConfig(); - setup_wifi(); -} - -void loop() -{ - - bool updatedNeeded = esp32FOTA.execHTTPcheck(); - if (updatedNeeded) - { - esp32FOTA.execOTA(); - } - - delay(2000); -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTPS/HTTPS.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTPS/HTTPS.ino deleted file mode 100644 index dfbab0e..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTPS/HTTPS.ino +++ /dev/null @@ -1,71 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update from a bin located on a webserver (HTTPS) - - Setup: - Step 1 : Set your WiFi (ssid & password) - Step 2 : set esp32fota() - Step 3 : Provide SPIFFS filesystem with root_ca.pem of your webserver - - Upload: - Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder) - Step 2 : Upload it to your webserver - Step 3 : Update your firmware JSON file ( see firwmareupdate ) - -*/ - -#include - -#include - -#include -#include -#include - - -// esp32fota esp32fota("", , ); -esp32FOTA esp32FOTA("esp32-fota-http", 1, false); -const char* manifest_url = "https://server/fota/fota.json"; - -CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SPIFFS ); - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi"); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - -void setup() -{ - Serial.begin(115200); - // Provide spiffs with root_ca.pem to validate server certificate - SPIFFS.begin(true); - esp32FOTA.setManifestURL( manifest_url ); - esp32FOTA.setRootCA( MyRootCA ); - esp32FOTA.printConfig(); - setup_wifi(); -} - -void loop() -{ - - bool updatedNeeded = esp32FOTA.execHTTPcheck(); - if (updatedNeeded) - { - esp32FOTA.execOTA(); - } - - delay(2000); -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTPS_without_root_cert/HTTPS_without_root_cert.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTPS_without_root_cert/HTTPS_without_root_cert.ino deleted file mode 100644 index ac19647..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTPS_without_root_cert/HTTPS_without_root_cert.ino +++ /dev/null @@ -1,65 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update from a bin located on a webserver (HTTPS) without having a root cert - - Setup: - Step 1 : Set your WiFi (ssid & password) - Step 2 : set esp32fota() - - Upload: - Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder) - Step 2 : Upload it to your webserver - Step 3 : Update your firmware JSON file ( see firwmareupdate ) - -*/ - -#include - -#include - -#include -#include -#include - - -// esp32fota esp32fota("", , , ); -esp32FOTA esp32FOTA("esp32-fota-http", 1, false, true); -const char* manifest_url = "http://server/fota/fota.json"; - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi"); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - -void setup() -{ - Serial.begin(115200); - esp32FOTA.setManifestURL( manifest_url ); - esp32FOTA.printConfig(); - setup_wifi(); -} - -void loop() -{ - - bool updatedNeeded = esp32FOTA.execHTTPcheck(); - if (updatedNeeded) - { - esp32FOTA.execOTA(); - } - - delay(2000); -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTP_signature_check/HTTP_signature_check.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTP_signature_check/HTTP_signature_check.ino deleted file mode 100644 index ae56dff..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/HTTP/HTTP_signature_check/HTTP_signature_check.ino +++ /dev/null @@ -1,70 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update from a bin located on a webserver (HTTP Only) - - Setup: - Step 1 : Set your WiFi (ssid & password) - Step 2 : set esp32fota() - - Upload: - Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder) - Step 2 : Upload it to your webserver - Step 3 : Update your firmware JSON file ( see firwmareupdate ) - -*/ -#include -#include - -#include -#include - -#include - - -// esp32fota esp32fota("", , ); -esp32FOTA esp32FOTA("esp32-fota-http", 1, true); - -const char* manifest_url = "http://server/fota/fota.json"; -CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &SPIFFS ); - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi"); - - // Need to provide SPIFFS with rsa_key.pub inside. - SPIFFS.begin( true ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - -void setup() -{ - Serial.begin(115200); - esp32FOTA.setManifestURL( manifest_url ); - esp32FOTA.setPubKey( MyRSAKey ); - esp32FOTA.printConfig(); - setup_wifi(); -} - -void loop() -{ - - bool updatedNeeded = esp32FOTA.execHTTPcheck(); - if (updatedNeeded) - { - esp32FOTA.execOTA(); - } - - delay(2000); -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/anyFS.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/anyFS.ino deleted file mode 100644 index e2cb853..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/anyFS.ino +++ /dev/null @@ -1,129 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update from a bin located on a webserver (HTTPS) - -*/ - -// declare filesystem first ! - -//#include -//#include -//#include -#include -//#include - -//#include // optional esp32-flashz for zlib compressed firmwares -//#include // optional ESP32-targz for gzip compressed firmwares -#include // fota pulls WiFi library - - - -// esp32fota settings -const int firmware_version = 1; -#if !defined FOTA_URL - #define FOTA_URL "http://server/fota/fota.json" -#endif -const char* firmware_name = "esp32-fota-http"; -const bool check_signature = false; -const bool disable_security = false; -// for debug only -const char* description = "Basic example with any filesystem"; - -CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &LittleFS ); -// CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SPIFFS ); -// CryptoMemAsset *MyRootCA = new CryptoMemAsset("Certificates Chain", root_ca, strlen(root_ca)+1 ); - -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &SPIFFS ); -CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &LittleFS ); -// CryptoMemAsset *MyRSAKey = new CryptoMemAsset("RSA Public Key", rsa_key_pub, strlen(rsa_key_pub)+1 ); - - -esp32FOTA FOTA; // empty constructor - - -bool WiFiConnected() -{ - return (WiFi.status() == WL_CONNECTED); -} - - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi "); - Serial.println( WiFi.macAddress() ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while ( !WiFiConnected() ) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); - -} - - -void setup() -{ - Serial.begin(115200); - // Provide filesystem with root_ca.pem to validate server certificate - if( ! LittleFS.begin( false ) ) { - Serial.println("LittleFS Mounting failed, aborting!"); - while(1) vTaskDelay(1); - } - - - { - auto cfg = FOTA.getConfig(); - - cfg.name = (char*)firmware_name; - cfg.manifest_url = (char*)FOTA_URL; - cfg.sem = SemverClass( firmware_version ); - cfg.check_sig = check_signature; - cfg.unsafe = disable_security; - cfg.root_ca = MyRootCA; - cfg.pub_key = MyRSAKey; - - FOTA.setConfig( cfg ); - } - - FOTA.printConfig(); - // FOTA.setStatusChecker( WiFiConnected ); - - - // /!\ FOTA.checkURL is deprecated, use setManifestURL( String ) instead - //FOTA.setManifestURL( FOTA_URL ); - //FOTA.setRootCA( MyRootCA ); - //FOTA.setPubKey( MyRSAKey ); - // use this when more than one filesystem is used in the sketch - // FOTA.setCertFileSystem( &SD ); - - // show progress when an update occurs (e.g. on a TFT display) - FOTA.setProgressCb( [](size_t progress, size_t size) { - if( progress == size || progress == 0 ) Serial.println(); - Serial.print("."); - }); - - // add some custom headers to the http queries - FOTA.setExtraHTTPHeader("Authorization", "Basic "); - - setup_wifi(); -} - -void loop() -{ - - bool updatedNeeded = FOTA.execHTTPcheck(); - if (updatedNeeded) - { - FOTA.execOTA(); - } - - delay(20000); -} - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/data/root_ca.pem b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/data/root_ca.pem deleted file mode 100644 index 342ecfe..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/data/root_ca.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.1.nosecurity/1.1.nosecurity.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.1.nosecurity/1.1.nosecurity.ino deleted file mode 100644 index 6942cf2..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.1.nosecurity/1.1.nosecurity.ino +++ /dev/null @@ -1,84 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update to both firmware and filesystem from binaries located - on a webserver (HTTPS) without checking for certificate validity - - Usage: If the ESP32 had a previous successful WiFi connection, then no need to set the ssid/password - to run this sketch, the credentials are still cached :-) - Sketch 1 will FOTA to Sketch 2, then Sketch 3, and so on until all versions in firmware.json are - exhausted. - - -*/ - -#include // optional ESP32-targz for gzip compressed firmwares -#include -#include - -// esp32fota settings -int firmware_version_major = 1; -int firmware_version_minor = 1; -int firmware_version_patch = 0; - -// #define FOTA_URL "http://server/fota/fota.json" - -const char* firmware_name = "esp32-fota-http"; -const bool check_signature = false; -const bool disable_security = true; -// for debug only -const char* title = "1.1"; -const char* description = "Basic example with no security and no filesystem"; - - -esp32FOTA FOTA; - - -void setup_wifi() -{ - delay(10); - - Serial.print("MAC Address "); - Serial.println( WiFi.macAddress() ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - - -void setup() -{ - Serial.begin(115200); - - PrintFOTAInfo(); - - { - auto cfg = FOTA.getConfig(); - cfg.name = (char*)firmware_name; - cfg.manifest_url = (char*)FOTA_URL; - cfg.sem = SemverClass( firmware_version_major, firmware_version_minor, firmware_version_patch ); - cfg.check_sig = check_signature; - cfg.unsafe = disable_security; - //cfg.root_ca = MyRootCA; - //cfg.pub_key = MyRSAKey; - FOTA.setConfig( cfg ); - } - FOTA.printConfig(); - - setup_wifi(); -} - - -void loop() -{ - FOTA.handle(); - delay(20000); -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.2.nosecurity.gz/1.2.nosecurity.gz.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.2.nosecurity.gz/1.2.nosecurity.gz.ino deleted file mode 100644 index 35a422a..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.2.nosecurity.gz/1.2.nosecurity.gz.ino +++ /dev/null @@ -1,88 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update to both firmware and filesystem from binaries located - on a webserver (HTTPS) without checking for certificate validity - - Usage: If the ESP32 had a previous successful WiFi connection, then no need to set the ssid/password - to run this sketch, the credentials are still cached :-) - Sketch 1 will FOTA to Sketch 2, then Sketch 3, and so on until all versions in firmware.json are - exhausted. - - -*/ - -#include // optional esp32-flashz for gzipped firmwares -#include -#include - -// esp32fota settings -int firmware_version_major = 1; -int firmware_version_minor = 2; -int firmware_version_patch = 0; - -// #define FOTA_URL "http://server/fota/fota.json" - -const char* firmware_name = "esp32-fota-http"; -const bool check_signature = false; -const bool disable_security = true; -// for debug only -const char* title = "1.2"; -const char* description = "Basic *gzipped* example with no security and no filesystem"; - - -esp32FOTA FOTA; - - -void setup_wifi() -{ - delay(10); - - Serial.print("MAC Address "); - Serial.println( WiFi.macAddress() ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - - -void setup() -{ - Serial.begin(115200); - - PrintFOTAInfo(); - - { - auto cfg = FOTA.getConfig(); - cfg.name = (char*)firmware_name; - cfg.manifest_url = (char*)FOTA_URL; - cfg.sem = SemverClass( firmware_version_major, firmware_version_minor, firmware_version_patch ); - cfg.check_sig = check_signature; - cfg.unsafe = disable_security; - //cfg.root_ca = MyRootCA; - //cfg.pub_key = MyRSAKey; - FOTA.setConfig( cfg ); - } - FOTA.printConfig(); - - setup_wifi(); -} - -void loop() -{ - bool updatedNeeded = FOTA.execHTTPcheck(); - if (updatedNeeded) { - FOTA.execOTA(); - } - - delay(20000); -} - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.3.nosecurity.zz/1.3.nosecurity.zz.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.3.nosecurity.zz/1.3.nosecurity.zz.ino deleted file mode 100644 index ff46a3d..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/1.3.nosecurity.zz/1.3.nosecurity.zz.ino +++ /dev/null @@ -1,87 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update to both firmware and filesystem from binaries located - on a webserver (HTTPS) without checking for certificate validity - - Usage: If the ESP32 had a previous successful WiFi connection, then no need to set the ssid/password - to run this sketch, the credentials are still cached :-) - Sketch 1 will FOTA to Sketch 2, then Sketch 3, and so on until all versions in firmware.json are - exhausted. - - -*/ - -#include -#include - -// esp32fota settings -int firmware_version_major = 1; -int firmware_version_minor = 3; -int firmware_version_patch = 0; - -// #define FOTA_URL "http://server/fota/fota.json" - -const char* firmware_name = "esp32-fota-http"; -const bool check_signature = false; -const bool disable_security = true; -// for debug only -const char* title = "1.3"; -const char* description = "Basic *gzipped* example with no security and no filesystem"; - - -esp32FOTA FOTA; - - -void setup_wifi() -{ - delay(10); - - Serial.print("MAC Address "); - Serial.println( WiFi.macAddress() ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - - -void setup() -{ - Serial.begin(115200); - - PrintFOTAInfo(); - - { - auto cfg = FOTA.getConfig(); - cfg.name = (char*)firmware_name; - cfg.manifest_url = (char*)FOTA_URL; - cfg.sem = SemverClass( firmware_version_major, firmware_version_minor, firmware_version_patch ); - cfg.check_sig = check_signature; - cfg.unsafe = disable_security; - //cfg.root_ca = MyRootCA; - //cfg.pub_key = MyRSAKey; - FOTA.setConfig( cfg ); - } - FOTA.printConfig(); - - setup_wifi(); -} - -void loop() -{ - bool updatedNeeded = FOTA.execHTTPcheck(); - if (updatedNeeded) { - FOTA.execOTA(); - } - - delay(20000); -} - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/2.cert-in-spiffs/2.cert-in-spiffs.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/2.cert-in-spiffs/2.cert-in-spiffs.ino deleted file mode 100644 index e6aeadc..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/2.cert-in-spiffs/2.cert-in-spiffs.ino +++ /dev/null @@ -1,99 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update firmware from a bin located on a webserver (HTTPS) - while using filesystem to check for certificate validity - -*/ - -#include // include filesystem **before** esp32fota !! -#include -#include - -// esp32fota settings -int firmware_version_major = 2; -int firmware_version_minor = 0; -int firmware_version_patch = 0; - -// #define FOTA_URL "http://server/fota/fota.json" - -const char* firmware_name = "esp32-fota-http"; -const bool check_signature = false; -const bool disable_security = false; -// for debug only -const char* title = "2"; -const char* description = "SPIFFS example with security"; - - -esp32FOTA FOTA; - - -// CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &LittleFS ); -CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SPIFFS ); -// CryptoMemAsset *MyRootCA = new CryptoMemAsset("Certificates Chain", root_ca, strlen(root_ca)+1 ); - -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &SPIFFS ); -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &LittleFS ); -// CryptoMemAsset *MyRSAKey = new CryptoMemAsset("RSA Public Key", pub_key, strlen(pub_key)+1 ); - - -void setup_wifi() -{ - delay(10); - Serial.print("MAC Address "); - Serial.println( WiFi.macAddress() ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - - -void setup() -{ - Serial.begin(115200); - - PrintFOTAInfo(); - - // Provide filesystem with root_ca.pem to validate server certificate - if( ! SPIFFS.begin( false ) ) { - Serial.println("SPIFFS Mounting failed, aborting!"); - while(1) vTaskDelay(1); - } - - { - auto cfg = FOTA.getConfig(); - cfg.name = (char*)firmware_name; - cfg.manifest_url = (char*)FOTA_URL; - cfg.sem = SemverClass( firmware_version_major, firmware_version_minor, firmware_version_patch ); - cfg.check_sig = check_signature; - cfg.unsafe = disable_security; - cfg.root_ca = MyRootCA; - //cfg.pub_key = MyRSAKey; - FOTA.setConfig( cfg ); - } - FOTA.printConfig(); - - setup_wifi(); -} - - -void loop() -{ - bool updatedNeeded = FOTA.execHTTPcheck(); - if (updatedNeeded) { - FOTA.execOTA(); - } - - delay(20000); -} - - - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/2.cert-in-spiffs/data/root_ca.pem b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/2.cert-in-spiffs/data/root_ca.pem deleted file mode 100644 index 342ecfe..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/2.cert-in-spiffs/data/root_ca.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/3.cert-in-progmem/3.cert-in-progmem.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/3.cert-in-progmem/3.cert-in-progmem.ino deleted file mode 100644 index 8d15a3f..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/3.cert-in-progmem/3.cert-in-progmem.ino +++ /dev/null @@ -1,93 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update to both firmware and filesystem from binaries located - on a webserver (HTTPS) while using progmem to check for certificate validity - - -*/ - -#include -#include - -#include "root_ca.h" - -// esp32fota settings -int firmware_version_major = 3; -int firmware_version_minor = 0; -int firmware_version_patch = 0; - -// #define FOTA_URL "http://server/fota/fota.json" - -const char* firmware_name = "esp32-fota-http"; -const bool check_signature = false; -const bool disable_security = false; -// for debug only -const char* title = "3"; -const char* description = "PROGMEM example with security"; - - -esp32FOTA FOTA; - -// CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &LittleFS ); -// CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SPIFFS ); -CryptoMemAsset *MyRootCA = new CryptoMemAsset("Certificates Chain", root_ca, strlen(root_ca)+1 ); - -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &SPIFFS ); -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &LittleFS ); -// CryptoMemAsset *MyRSAKey = new CryptoMemAsset("RSA Public Key", pub_key, strlen(pub_key)+1 ); - - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi "); - Serial.println( WiFi.macAddress() ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); - - FOTA.setRootCA( MyRootCA ); -} - - -void setup() -{ - Serial.begin(115200); - - PrintFOTAInfo(); - - { - auto cfg = FOTA.getConfig(); - cfg.name = (char*)firmware_name; - cfg.manifest_url = (char*)FOTA_URL; - cfg.sem = SemverClass( firmware_version_major, firmware_version_minor, firmware_version_patch ); - cfg.check_sig = check_signature; - cfg.unsafe = disable_security; - cfg.root_ca = MyRootCA; - //cfg.pub_key = MyRSAKey; - FOTA.setConfig( cfg ); - } - FOTA.printConfig(); - - setup_wifi(); -} - -void loop() -{ - bool updatedNeeded = FOTA.execHTTPcheck(); - if (updatedNeeded) { - FOTA.execOTA(); - } - - delay(20000); -} - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/3.cert-in-progmem/root_ca.h b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/3.cert-in-progmem/root_ca.h deleted file mode 100644 index 60b1c9f..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/3.cert-in-progmem/root_ca.h +++ /dev/null @@ -1,25 +0,0 @@ - -const char* /*github_*/root_ca = R"ROOT_CA( ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- -)ROOT_CA"; diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/4.cert-in-littlefs/4.cert-in-littlefs.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/4.cert-in-littlefs/4.cert-in-littlefs.ino deleted file mode 100644 index 8aa1663..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/4.cert-in-littlefs/4.cert-in-littlefs.ino +++ /dev/null @@ -1,99 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update firmware from a bin located on a webserver (HTTPS) - while using filesystem to check for certificate validity - -*/ - -#include // include filesystem **before** esp32fota !! -#include -#include - -// esp32fota settings -int firmware_version_major = 4; -int firmware_version_minor = 0; -int firmware_version_patch = 0; - -// #define FOTA_URL "http://server/fota/fota.json" - -const char* firmware_name = "esp32-fota-http"; -const bool check_signature = true; -const bool disable_security = false; -// for debug only -const char* title = "4"; -const char* description = "LittleFS example with enforced security"; - - -esp32FOTA FOTA; - -CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &LittleFS ); -// CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SPIFFS ); -// CryptoMemAsset *MyRootCA = new CryptoMemAsset("Certificates Chain", root_ca, strlen(root_ca)+1 ); - -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &SPIFFS ); -CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &LittleFS ); -// CryptoMemAsset *MyRSAKey = new CryptoMemAsset("RSA Public Key", pub_key, strlen(pub_key)+1 ); - - -void setup_wifi() -{ - delay(10); - - Serial.print("MAC Address "); - Serial.println( WiFi.macAddress() ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - - -void setup() -{ - Serial.begin(115200); - - PrintFOTAInfo(); - - // Provide filesystem with root_ca.pem to validate server certificate - if( ! LittleFS.begin( false ) ) { - Serial.println("LittleFS Mounting failed, aborting!"); - while(1) vTaskDelay(1); - } - - { - auto cfg = FOTA.getConfig(); - cfg.name = (char*)firmware_name; - cfg.manifest_url = (char*)FOTA_URL; - cfg.sem = SemverClass( firmware_version_major, firmware_version_minor, firmware_version_patch ); - cfg.check_sig = check_signature; - cfg.unsafe = disable_security; - cfg.root_ca = MyRootCA; - cfg.pub_key = MyRSAKey; - FOTA.setConfig( cfg ); - } - FOTA.printConfig(); - - setup_wifi(); - -} - -void loop() -{ - bool updatedNeeded = FOTA.execHTTPcheck(); - if (updatedNeeded) { - FOTA.execOTA(); - } - - delay(20000); -} - - - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/4.cert-in-littlefs/data/root_ca.pem b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/4.cert-in-littlefs/data/root_ca.pem deleted file mode 100644 index 342ecfe..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/4.cert-in-littlefs/data/root_ca.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/5.sig-in-progmem.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/5.sig-in-progmem.ino deleted file mode 100644 index a80b081..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/5.sig-in-progmem.ino +++ /dev/null @@ -1,93 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update to both firmware and filesystem from binaries located - on a webserver (HTTPS) while using progmem to check for certificate validity - - -*/ - -#include -#include - -#include "root_ca.h" -#include "pub_key.h" - -// esp32fota settings -int firmware_version_major = 5; -int firmware_version_minor = 0; -int firmware_version_patch = 0; - -// #define FOTA_URL "http://server/fota/fota.json" - -const char* firmware_name = "esp32-fota-http"; -const bool check_signature = true; -const bool disable_security = false; -// for debug only -const char* title = "5"; -const char* description = "PROGMEM example with enforced security"; - - -esp32FOTA FOTA; - -// CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &LittleFS ); -// CryptoFileAsset *MyRootCA = new CryptoFileAsset( "/root_ca.pem", &SPIFFS ); -CryptoMemAsset *MyRootCA = new CryptoMemAsset("Certificates Chain", root_ca, strlen(root_ca)+1 ); - -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &SPIFFS ); -// CryptoFileAsset *MyRSAKey = new CryptoFileAsset( "/rsa_key.pub", &LittleFS ); -CryptoMemAsset *MyRSAKey = new CryptoMemAsset("RSA Public Key", pub_key, strlen(pub_key)+1 ); - - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi "); - Serial.println( WiFi.macAddress() ); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - - -void setup() -{ - Serial.begin(115200); - - PrintFOTAInfo(); - - { - auto cfg = FOTA.getConfig(); - cfg.name = (char*)firmware_name; - cfg.manifest_url = (char*)FOTA_URL; - cfg.sem = SemverClass( firmware_version_major, firmware_version_minor, firmware_version_patch ); - cfg.check_sig = check_signature; - cfg.unsafe = disable_security; - cfg.root_ca = MyRootCA; - cfg.pub_key = MyRSAKey; - FOTA.setConfig( cfg ); - } - FOTA.printConfig(); - - setup_wifi(); -} - -void loop() -{ - bool updatedNeeded = FOTA.execHTTPcheck(); - if (updatedNeeded) { - FOTA.execOTA(); - } - - delay(20000); -} - - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/pub_key.h b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/pub_key.h deleted file mode 100644 index 2349fdc..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/pub_key.h +++ /dev/null @@ -1,25 +0,0 @@ - -const char* /*github_*/pub_key = R"ROOT_CA( ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- -)ROOT_CA"; diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/root_ca.h b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/root_ca.h deleted file mode 100644 index 60b1c9f..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/5.sig-in-progmem/root_ca.h +++ /dev/null @@ -1,25 +0,0 @@ - -const char* /*github_*/root_ca = R"ROOT_CA( ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- -)ROOT_CA"; diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/99.final-stage/99.final-stage.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/99.final-stage/99.final-stage.ino deleted file mode 100644 index db91e37..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/anyFS/test/99.final-stage/99.final-stage.ino +++ /dev/null @@ -1,20 +0,0 @@ -// Just a dummy script prevent an endless OTA loop to occur -// It validates the last step in the test suite by printing a message in the serial. - - -void setup() -{ - Serial.begin( 115200 ); - Serial.println(); - Serial.println(); - Serial.println(); - Serial.println("**************************"); - Serial.println("Test suite COMPLETE :-)"); - Serial.println(); - Serial.println(); -} - -void loop() -{ - -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/forceUpdate/forceUpdate.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/forceUpdate/forceUpdate.ino deleted file mode 100644 index 23c6e6c..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/forceUpdate/forceUpdate.ino +++ /dev/null @@ -1,61 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update from a bin located on a webserver - - Setup: - Step 1 : Set your WiFi (ssid & password) - Step 2 : set esp32fota() - - Upload: - Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder) - Step 2 : Upload it to your webserver - Step 3 : Update your firmware JSON file ( see firwmareupdate ) - -*/ - -#include -#include - - -// esp32fota esp32fota("", , ); -esp32FOTA esp32FOTA("esp32-fota-http", 1, false); - -const char* manifest_url = "http://server/fota/fota.json"; - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi"); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - - -void setup() -{ - Serial.begin(115200); - esp32FOTA.setManifestURL( manifest_url ); - esp32FOTA.printConfig(); - setup_wifi(); -} - - -void loop() -{ - delay(2000); - esp32FOTA.forceUpdate("192.168.0.100", 80, "/fota/esp32-fota-http-2.bin", true ); // check signature: true - - // Alternatively, forceUpdate can be called with a complete URL: - //esp32FOTA.forceUpdate("http://192.168.0.100/fota/esp32-fota-http-2.bin", true ); // check signature: true - -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/withDeviceID/withDeviceID.ino b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/withDeviceID/withDeviceID.ino deleted file mode 100644 index 7849ac0..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/examples/withDeviceID/withDeviceID.ino +++ /dev/null @@ -1,62 +0,0 @@ -/** - esp32 firmware OTA - - Purpose: Perform an OTA update from a bin located on a webserver (HTTP Only) - - Setup: - Step 1 : Set your WiFi (ssid & password) - Step 2 : set esp32fota() - - Upload: - Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder) - Step 2 : Upload it to your webserver - Step 3 : Update your firmware JSON file ( see firwmareupdate ) - -*/ - -#include -#include - - -// esp32fota esp32fota("", , ); -esp32FOTA esp32FOTA("esp32-fota-http", 1, false); -const char* manifest_url = "http://server/fota/fota.json"; - -void setup_wifi() -{ - delay(10); - Serial.print("Connecting to WiFi"); - - WiFi.begin(); // no WiFi creds in this demo :-) - - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println(WiFi.localIP()); -} - - -void setup() -{ - Serial.begin(115200); - esp32FOTA.setManifestURL( manifest_url ); - esp32FOTA.useDeviceId( true ); - esp32FOTA.printConfig(); - setup_wifi(); -} - - -void loop() -{ - bool updatedNeeded = esp32FOTA.execHTTPcheck(); - if (updatedNeeded) - { - esp32FOTA.execOTA(); - } - - delay(2000); -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/fota/firmware.json b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/fota/firmware.json deleted file mode 100644 index 0476084..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/fota/firmware.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "esp32-fota-http", - "version": 2, - "host": "192.168.0.100", - "port": 80, - "bin": "/fota/esp32-fota-http-2.bin" -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/keywords.txt b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/keywords.txt deleted file mode 100644 index 58b1057..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/keywords.txt +++ /dev/null @@ -1,22 +0,0 @@ -####################################### -# Syntax Coloring Map For esp32FOTA -####################################### - -####################################### -# Datatypes (KEYWORD1) -####################################### - -esp32FOTA KEYWORD1 -useDeviceID KEYWORD1 -checkURL KEYWORD1 - -####################################### -# Methods and Functions (KEYWORD2) -####################################### - -execOTA KEYWORD2 -execHTTPcheck KEYWORD2 - -####################################### -# Constants (LITERAL1) -####################################### diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/library.json b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/library.json deleted file mode 100644 index e666db7..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/library.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "esp32FOTA", - "version": "0.2.7", - "keywords": "firmware, OTA, Over The Air Updates, ArduinoOTA", - "description": "Allows for firmware to be updated from a webserver, the device can check for updates at any time. Uses a simple JSON file to outline if a new firmware is avaiable.", - "examples": "examples/*/*.ino", - "repository": { - "type": "git", - "url": "https://github.com/chrisjoyce911/esp32FOTA.git" - }, - "authors": [ - { - "name": "Chris Joyce", - "email": "chris@joyce.id.au", - "url": "https://github.com/chrisjoyce911", - "maintainer": true - } - ], - "frameworks": "arduino", - "headers": "esp32FOTA.hpp", - "platforms": [ - "esp32", - "espressif32" - ], - "dependencies": [ - "WiFiClientSecure", - "HTTPClient", - "ArduinoJson" - ] -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/library.properties b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/library.properties deleted file mode 100644 index 11a9da3..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/library.properties +++ /dev/null @@ -1,11 +0,0 @@ -name=esp32FOTA -version=0.2.7 -author=Chris Joyce -maintainer=Chris Joyce -sentence=A simple library for firmware OTA updates -paragraph=Allows for firmware to be updated from a webserver, the device can check for updates at any time. Uses a simple JSON file to outline if a new firmware is available. -category=Communication -url=https://github.com/chrisjoyce911/esp32FOTA -architectures=esp32,espressif32 -includes=esp32FOTA.hpp -depends=ArduinoJson diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/platformio.ini b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/platformio.ini deleted file mode 100644 index 1d1380a..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/platformio.ini +++ /dev/null @@ -1,52 +0,0 @@ -; CanAirIO Sensorlib -; -; Full guide and details: https://github.com/kike-canaries/canairio_sensorlib - - -[platformio] -src_dir = ./tests/ -;lib_dir = ./ - -[env] -framework = arduino -upload_speed = 1500000 -monitor_speed = 115200 -monitor_filters = time -build_flags = - -D CORE_DEBUG_LEVEL=0 -lib_deps = - bblanchon/ArduinoJson @ ^6 - esp32FOTA -; vortigont/esp32-flashz -; tobozo/ESP32-targz - -[esp32_common] -platform = espressif32 -board = esp32dev -framework = ${env.framework} -upload_speed = ${env.upload_speed} -monitor_speed = ${env.monitor_speed} -lib_deps = ${env.lib_deps} -build_flags = - ${env.build_flags} - -[env:esp32_http] -extends = esp32_common -build_src_filter = -<*> + - -[env:esp32_http_debug] -extends = esp32_common -build_flags = - -D CORE_DEBUG_LEVEL=4 -build_src_filter = -<*> + - -[env:esp32_https] -extends = esp32_common -build_src_filter = -<*> + - -[env:esp32_https_debug] -extends = esp32_common -build_flags = - -D CORE_DEBUG_LEVEL=4 -build_src_filter = -<*> + - diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/debug/test_fota_common.h b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/debug/test_fota_common.h deleted file mode 100644 index 67e54ab..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/debug/test_fota_common.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -// esp32fota settings -extern int firmware_version_major; -extern int firmware_version_minor; -extern int firmware_version_patch; - -#if !defined FOTA_URL - #define FOTA_URL "http://server/fota/fota.json" -#endif - -extern const char* firmware_name; -extern const bool check_signature; -extern const bool disable_security; -// for debug only -extern const char* description; -extern const char* title; - -extern esp32FOTA FOTA; - -const char* fota_debug_fmt = R"DBG_FMT( - -***************** STAGE %s ***************** - - Description : %s - Firmware type : %s - Firmware version : %i.%i.%i - Signature check : %s - TLS Cert check : %s - Compression : %s - -******************************************** - -)DBG_FMT"; - - -void PrintFOTAInfo() -{ - Serial.printf( fota_debug_fmt, - title, - description, - firmware_name, - firmware_version_major, - firmware_version_minor, - firmware_version_patch, - check_signature ?"Enabled":"Disabled", - disable_security ?"Disabled":"Enabled", - FOTA.zlibSupported() ?"Enabled":"Disabled" - ); -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32FOTA.cpp b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32FOTA.cpp deleted file mode 100644 index a92b991..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32FOTA.cpp +++ /dev/null @@ -1,1081 +0,0 @@ -/* - esp32 firmware OTA - Date: December 2018 - Author: Chris Joyce - Purpose: Perform an OTA update from a bin located on a webserver (HTTP Only) - - Date: 2021-12-21 - Author: Moritz Meintker - Remarks: Re-written/removed a bunch of functions around HTTPS. The library is - now URL-agnostic. This means if you provide an https://-URL it will - use the root_ca.pem (needs to be provided via PROGMEM/SPIFFS/LittleFS or SD) - to verify the server certificate and then download the ressource through an - encrypted connection unless you set the allow_insecure_https option. - Otherwise it will just use plain HTTP which will still offer to sign - your firmware image. - - Date: 2022-09-12 - Author: tobozo - Changes: - - Abstracted away filesystem - - Refactored some code blocks - - Added spiffs/littlefs/fatfs updatability - - Made crypto assets (pub key, rootca) loadable from multiple sources - Roadmap: - - Firmware/FlashFS update order (SPIFFS/LittleFS first or last?) - - Archive support for gz/targz formats - - firmware.gz + spiffs.gz in manifest - - bundle.tar.gz [ firmware + filesystem ] in manifest - - Update from Stream (e.g deported update via SD, http or gzupdater) - -*/ - -#include "esp32FOTA.hpp" - -#include "mbedtls/pk.h" -#include "mbedtls/md.h" -#include "mbedtls/md_internal.h" -#include "esp_ota_ops.h" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - - - -static int64_t getHTTPStream( esp32FOTA* fota, int partition ); -static int64_t getFileStream( esp32FOTA* fota, int partition ); -static int64_t getSerialStream( esp32FOTA* fota, int partition ); -static bool WiFiStatusCheck(); - - -SemverClass::SemverClass( const char* version ) -{ - assert(version); - if (semver_parse_version(version, &_ver)) { - log_w( "Invalid semver string '%s' passed to constructor. Defaulting to 0", version ); - _ver = semver_t{0,0,0}; - } -} - -SemverClass::SemverClass( int major, int minor, int patch ) -{ - _ver = semver_t{major, minor, patch}; -} - -semver_t* SemverClass::ver() -{ - return &_ver; - -} - - - - -// Filesystem helper for signature check and pem validation -// This is abstracted away to allow storage alternatives such -// as PROGMEM, SD, SPIFFS, LittleFS or FatFS - -bool CryptoFileAsset::fs_read_file() -{ - File file = fs->open( path ); - // if( file->size() > ESP.getFreeHeap() ) return false; - if( !file ) { - log_e( "Failed to open %s for reading", path ); - return false; - } - contents = ""; // make sure the output bucket is empty - len = file.size()+1; - while( file.available() ) { - contents.push_back( file.read() ); - } - file.close(); - return len>0; -} - - -size_t CryptoFileAsset::size() -{ - if( len > 0 ) { // already stored, no need to access filesystem - return len; - } - if( fs ) { // fetch file contents - if( ! fs_read_file() ) { - log_e("Invalid contents!"); - return 0; - } - } else { - log_e("No filesystem was set for %s!", path); - return 0; - } - return len; -} - - - -esp32FOTA::esp32FOTA(){} -esp32FOTA::~esp32FOTA(){} - - -esp32FOTA::esp32FOTA( FOTAConfig_t cfg ) -{ - setConfig( cfg ); -} - - -void esp32FOTA::setString( char **dest, const char* src ) -{ - if( !src ) { - log_e("Can't set string to empty source"); - return; - } - if( *dest != nullptr ) free( *dest ); - *dest = (char*)malloc( strlen(src)+1 ); - if( *dest == NULL ) { - log_e("Unable to allocate %d bytes", strlen(src)+1); - return; - } - //strcpy( dest, src); - memcpy( *dest, src, strlen(src)+1); - log_d("Assigned value: %s <= %s", *dest, src ); -} - - - -esp32FOTA::esp32FOTA(const char* firmwareType, int firmwareVersion, bool validate, bool allow_insecure_https) -{ - setString( &_cfg.name, firmwareType ); - //_cfg.name = (char*)firmwareType; - _cfg.sem = SemverClass( firmwareVersion ); - _cfg.check_sig = validate; - _cfg.unsafe = allow_insecure_https; - - setupCryptoAssets(); - debugSemVer("Current firmware version", _cfg.sem.ver() ); -} - - -esp32FOTA::esp32FOTA(const char* firmwareType, const char* firmwareSemanticVersion, bool validate, bool allow_insecure_https) -{ - setString( &_cfg.name, firmwareType ); - //_cfg.name = (char*)firmwareType; - _cfg.check_sig = validate; - _cfg.unsafe = allow_insecure_https; - _cfg.sem = SemverClass( firmwareSemanticVersion ); - - setupCryptoAssets(); - debugSemVer("Current firmware version", _cfg.sem.ver() ); -} - - - -void esp32FOTA::setConfig( FOTAConfig_t cfg ) -{ - setString( &_cfg.name, cfg.name ); - setString( &_cfg.manifest_url, cfg.manifest_url ); - - _cfg.sem = cfg.sem; - _cfg.check_sig = cfg.check_sig; - _cfg.unsafe = cfg.unsafe; - _cfg.use_device_id = cfg.use_device_id; - _cfg.root_ca = cfg.root_ca; - _cfg.pub_key = cfg.pub_key; - _cfg.signature_len = cfg.signature_len; -} - - -void esp32FOTA::printConfig( FOTAConfig_t *cfg ) -{ - if( cfg == nullptr ) cfg = &_cfg; - log_d("Name: %s\nManifest URL:%s\nSemantic Version: %d.%d.%d\nCheck Sig: %s\nUnsafe: %s\nUse Device ID: %s\nRootCA: %s\nPubKey: %s\nSignatureLen: %d\n", - cfg->name ? cfg->name : "None", - cfg->manifest_url ? cfg->manifest_url : "None", - cfg->sem.ver()->major, - cfg->sem.ver()->minor, - cfg->sem.ver()->patch, - cfg->check_sig ?"true":"false", - cfg->unsafe ?"true":"false", - cfg->use_device_id ?"true":"false", - cfg->root_ca ?"true":"false", - cfg->pub_key ?"true":"false", - cfg->signature_len - ); -} - - -void esp32FOTA::setSignatureLen( size_t len ) -{ - _cfg.signature_len = len; -} - - -void esp32FOTA::setCertFileSystem( fs::FS *cert_filesystem ) -{ - _fs = cert_filesystem; - setupCryptoAssets(); -} - - -// Used for legacy behaviour when SPIFFS and RootCa/PubKey had default values -// New recommended method is to use setPubKey() and setRootCA() with CryptoMemAsset ot CryptoFileAsset objects. -void esp32FOTA::setupCryptoAssets() -{ - if( _fs ) { - _cfg.pub_key = (CryptoAsset*)(new CryptoFileAsset( rsa_key_pub_default_path, _fs )); - _cfg.root_ca = (CryptoAsset*)(new CryptoFileAsset( root_ca_pem_default_path, _fs )); - } -} - - - -void esp32FOTA::handle() -{ - if ( execHTTPcheck() ) { - execOTA(); - } -} - - -// SHA-Verify the OTA partition after it's been written -// https://techtutorialsx.com/2018/05/10/esp32-arduino-mbed-tls-using-the-sha-256-algorithm/ -// https://github.com/ARMmbed/mbedtls/blob/development/programs/pkey/rsa_verify.c -bool esp32FOTA::validate_sig( const esp_partition_t* partition, unsigned char *signature, uint32_t firmware_size ) -{ - if( !partition ) { - log_e( "Could not find update partition!" ); - return false; - } - size_t pubkeylen = _cfg.pub_key ? _cfg.pub_key->size() : 0; - - if( pubkeylen <= 1 ) { - log_e("Public key empty, can't validate!"); - return false; - } - - const char* pubkeystr = _cfg.pub_key->get(); - - if( !pubkeystr ) { - log_e("Unable to get public key, can't validate!"); - return false; - } - - log_d("Creating mbedtls context"); - - mbedtls_pk_context pk; - mbedtls_md_context_t rsa; - mbedtls_pk_init( &pk ); - - log_d("Parsing public key"); - - int ret; - if( ( ret = mbedtls_pk_parse_public_key( &pk, (const unsigned char*)pubkeystr, pubkeylen ) ) != 0 ) { - log_e( "Parsing public key failed\n ! mbedtls_pk_parse_public_key %d (%d bytes)\n%s", ret, pubkeylen, pubkeystr ); - return false; - } - - if( !mbedtls_pk_can_do( &pk, MBEDTLS_PK_RSA ) ) { - log_e( "Public key is not an rsa key -0x%x", -ret ); - return false; - } - - log_d("Initing mbedtls"); - - const mbedtls_md_info_t *mdinfo = mbedtls_md_info_from_type( MBEDTLS_MD_SHA256 ); - mbedtls_md_init( &rsa ); - mbedtls_md_setup( &rsa, mdinfo, 0 ); - mbedtls_md_starts( &rsa ); - - int bytestoread = SPI_FLASH_SEC_SIZE; - int bytesread = 0; - int size = firmware_size; - - uint8_t *_buffer = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE); - if(!_buffer){ - log_e( "malloc failed" ); - return false; - } - - log_d("Parsing content"); - - log_v( "Reading partition (%i sectors, sec_size: %i)", size, bytestoread ); - while( bytestoread > 0 ) { - log_v( "Left: %i (%i) \r", size, bytestoread ); - - if( ESP.partitionRead( partition, bytesread, (uint32_t*)_buffer, bytestoread ) ) { - // Debug output for the purpose of comparing with file - // DINGO disable this since it blocks -DCORE_DEBUG_LEVEL=5 analysis of source app -/* for( int i = 0; i < bytestoread; i++ ) { - if( ( i % 16 ) == 0 ) { - log_v( "\r\n0x%08x\t", i + bytesread ); - } - log_v( "%02x ", (uint8_t*)_buffer[i] ); - } -*/ - mbedtls_md_update( &rsa, (uint8_t*)_buffer, bytestoread ); - - bytesread = bytesread + bytestoread; - size = size - bytestoread; - - if( size <= SPI_FLASH_SEC_SIZE ) { - bytestoread = size; - } - } else { - log_e( "partitionRead failed!" ); - return false; - } - } - - free( _buffer ); - - unsigned char *hash = (unsigned char*)malloc( mdinfo->size ); - if(!hash){ - log_e( "malloc failed" ); - return false; - } - mbedtls_md_finish( &rsa, hash ); - - ret = mbedtls_pk_verify( &pk, MBEDTLS_MD_SHA256, hash, mdinfo->size, (unsigned char*)signature, _cfg.signature_len ); - - free( hash ); - mbedtls_md_free( &rsa ); - mbedtls_pk_free( &pk ); - if( ret == 0 ) { - return true; - } - - // validation failed, overwrite the first few bytes so this partition won't boot! - log_w( "Validation failed, erasing the invalid partition" ); - ESP.partitionEraseRange( partition, 0, ENCRYPTED_BLOCK_SIZE); - - return false; -} - - - - -bool esp32FOTA::setupHTTP( const char* url ) -{ - const char* rootcastr = nullptr; - _http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - - log_i("Connecting to: %s", url ); - if( String(url).startsWith("https") ) { - if (!_cfg.unsafe) { - if( !_cfg.root_ca ) { - log_e("A strict security context has been set but no RootCA was provided"); - return false; - } - if( _cfg.root_ca->size() == 0 ) { - log_e("A strict security context has been set but an empty RootCA was provided"); - return false; - } - rootcastr = _cfg.root_ca->get(); - if( !rootcastr ) { - log_e("Unable to get RootCA, aborting"); - log_e("rootcastr=%s", rootcastr); - return false; - } - log_d("Loading root_ca.pem"); - _client.setCACert( rootcastr ); - } else { - // We're downloading from a secure URL, but we don't want to validate the root cert. - _client.setInsecure(); - } - _http.begin( _client, url ); - } else { - _http.begin( url ); - } - - if( extraHTTPHeaders.size() > 0 ) { - // add custom headers provided by user e.g. _http.addHeader("Authorization", "Basic " + auth) - for( const auto &header : extraHTTPHeaders ) { - _http.addHeader(header.first, header.second); - } - } - - // TODO: add more watched headers e.g. Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",signature="Base64(RSA-SHA256(signing string))" - const char* get_headers[] = { "Content-Length", "Content-type", "Accept-Ranges" }; - _http.collectHeaders( get_headers, sizeof(get_headers)/sizeof(const char*) ); - - return true; -} - - - - -void esp32FOTA::setupStream() -{ - if(!getStream) { - switch( _stream_type ) { - case FOTA_FILE_STREAM: - setStreamGetter( getFileStream ); - break; - case FOTA_SERIAL_STREAM: - setStreamGetter( getSerialStream ); - break; - case FOTA_HTTP_STREAM: - default: - setStreamGetter( getHTTPStream ); - break; - } - } - - if( !isConnected ) { - setStatusChecker( WiFiStatusCheck ); - } -} - - -void esp32FOTA::stopStream() -{ - if( endStream ) { // user function provided via ::setStreamEnder( fn ) - endStream( this ); - return; - } - - // no user function provided, apply default behaviour - switch( _stream_type ) { - case FOTA_FILE_STREAM: - if( _file ) _file.close(); - break; - case FOTA_HTTP_STREAM: - _http.end(); - break; - case FOTA_SERIAL_STREAM: - default: - break; - } - -} - - - -// OTA Logic -bool esp32FOTA::execOTA() -{ - setupStream(); - - if( !_flashFileSystemUrl.isEmpty() ) { // a data partition was specified in the json manifest, handle the spiffs partition first - if( _fs ) { // Possible risk of overwriting certs and signatures, cancel flashing! - log_e("Cowardly refusing to overwrite U_SPIFFS with %s. Use setCertFileSystem(nullptr) along with setPubKey()/setCAPem() to enable this feature.", _flashFileSystemUrl); - return false; - } else { - log_i("Will check if U_SPIFFS needs updating"); - if( !execOTA( U_SPIFFS, false ) ) return false; - } - } else { - log_i("This update is for U_FLASH only"); - } - // handle the application partition and restart on success - bool ret = execOTA( U_FLASH, true ); - - stopStream(); - - return ret; -} - -// OTA Logic -bool esp32FOTA::execSPIFFSOTA() -{ - bool ret; - setupStream(); - - if( !_flashFileSystemUrl.isEmpty() ) { // a data partition was specified in the json manifest, handle the spiffs partition first - if( _fs ) { // Possible risk of overwriting certs and signatures, cancel flashing! - log_e("Cowardly refusing to overwrite U_SPIFFS with %s. Use setCertFileSystem(nullptr) along with setPubKey()/setCAPem() to enable this feature.", _flashFileSystemUrl); - return false; - } else { - log_i("Will check if U_SPIFFS needs updating"); - ret = execOTA( U_SPIFFS, false ); - } - } else { - log_i("This update is for U_FLASH only"); - } - stopStream(); - return ret; -} - - -bool esp32FOTA::execOTA( int partition, bool restart_after ) -{ - // health checks - if( partition != U_SPIFFS && partition != U_FLASH ) { - log_e("Bad partition number: %i or empty URL, aborting", partition); - return false; - } - if( partition == U_SPIFFS && _flashFileSystemUrl.isEmpty() ) { - log_i("[SKIP] No spiffs/littlefs/fatfs partition was specified"); - return true; // data partition is optional, so not an error - } - if ( partition == U_FLASH && _firmwareUrl.isEmpty() ) { - log_e("No firmware URL, aborting"); - return false; // app partition is mandatory - } - - // call getHTTPStream - int64_t updateSize = getStream( this, partition ); - - if( updateSize<=0 || _stream == nullptr ) { - log_e("HTTP Error"); - return false; - } - - // some network streams (e.g. Ethernet) can be laggy and need to 'breathe' - if( ! _stream->available() ) { - uint32_t timeout = millis() + _stream_timeout; - while( ! _stream->available() ) { - if( millis()>timeout ) { - log_e("Stream timed out"); - return false; - } - vTaskDelay(1); - } - } - - mode_z = F_isZlibStream(); - - log_d("compression: %s", mode_z ? "enabled" : "disabled" ); - - if( _cfg.check_sig ) { - if( mode_z ) { - log_e("Compressed && signed image is not (yet) supported"); - return false; - } - if( updateSize == UPDATE_SIZE_UNKNOWN || updateSize <= _cfg.signature_len ) { - log_e("Malformed signature+fw combo"); - return false; - } - updateSize -= _cfg.signature_len; - } - - // If using compression, the size is implicitely unknown - size_t fwsize = mode_z ? UPDATE_SIZE_UNKNOWN : updateSize; // fw_size is unknown if we have a compressed image - - bool canBegin = F_canBegin(); - - if( !canBegin ) { - log_e("Not enough space to begin OTA, partition size mismatch?"); - F_abort(); - if( onUpdateBeginFail ) onUpdateBeginFail( partition ); - return false; - } - - if( onOTAProgress ) { - F_Update.onProgress( onOTAProgress ); - } else { - F_Update.onProgress( [](size_t progress, size_t size) { - if( progress >= size ) Serial.println(); - else if( progress > 0) Serial.print("."); - }); - } - - unsigned char* signature = new unsigned char[_cfg.signature_len]; - if( _cfg.check_sig ) { - _stream->readBytes( signature, _cfg.signature_len ); - } - - log_i("Begin %s OTA. This may take 2 - 5 mins to complete. Things might be quiet for a while.. Patience!", partition==U_FLASH?"Firmware":"Filesystem"); - - // Some activity may appear in the Serial monitor during the update (depends on Update.onProgress) - size_t written = F_writeStream(); - - if (fwsize == UPDATE_SIZE_UNKNOWN) // match compressed fw size to responce length - fwsize = updateSize; - - if ( written == fwsize ) { - log_d("Written : %d successfully", written); - updateSize = written; // flatten value to prevent overflow when checking signature - } else { - log_e("Written only : %d/%d Premature end of stream?", written, updateSize); - F_abort(); - delete[] signature; - return false; - } - - if (!F_UpdateEnd()) { - log_e("An Update Error Occurred. Error #: %d", F_Update.getError()); - delete[] signature; - return false; - } - - if( onUpdateEnd ) onUpdateEnd( partition ); - - if( _cfg.check_sig ) { // check signature - - log_i("Checking partition %d to validate", partition); - - getPartition( partition ); // updated partition => '_target_partition' pointer - - #define CHECK_SIG_ERROR_PARTITION_NOT_FOUND -1 - #define CHECK_SIG_ERROR_VALIDATION_FAILED -2 - - if( !_target_partition ) { - log_e("Can't access partition #%d to check signature!", partition); - if( onUpdateCheckFail ) onUpdateCheckFail( partition, CHECK_SIG_ERROR_PARTITION_NOT_FOUND ); - delete[] signature; - return false; - } - - log_d("Checking signature for partition %d...", partition); - - const esp_partition_t* running_partition = esp_ota_get_running_partition(); - - if( partition == U_FLASH ) { - // /!\ An OTA partition is automatically set as bootable after being successfully - // flashed by the Update library. - // Since we want to validate before enabling the partition, we need to cancel that - // by temporarily reassigning the bootable flag to the running-partition instead - // of the next-partition. - esp_ota_set_boot_partition( running_partition ); - // By doing so the ESP will NOT boot any unvalidated partition should a reset occur - // during signature validation (crash, oom, power failure). - } - - if( !validate_sig( _target_partition, signature, updateSize ) ) { - delete[] signature; - // erase partition - esp_partition_erase_range( _target_partition, _target_partition->address, _target_partition->size ); - - if( onUpdateCheckFail ) onUpdateCheckFail( partition, CHECK_SIG_ERROR_VALIDATION_FAILED ); - - log_e("Signature check failed!"); - return false; - } else { - delete[] signature; - log_d("Signature check successful!"); - if( partition == U_FLASH ) { - // Set updated partition as bootable now that it's been verified - esp_ota_set_boot_partition( _target_partition ); - } - } - } - log_d("OTA Update complete!"); - if (F_Update.isFinished()) { - - if( onUpdateFinished ) onUpdateFinished( partition, restart_after ); - - log_i("Update successfully completed."); - if( restart_after ) { - log_i("Rebooting."); - ESP.restart(); - } - return true; - } else { - log_e("Update not finished! Something went wrong!"); - } - return false; -} - - -void esp32FOTA::getPartition( int update_partition ) -{ - _target_partition = nullptr; - if( update_partition == U_FLASH ) { - // select the last-updated OTA partition - _target_partition = esp_ota_get_next_update_partition(NULL); - } else if( update_partition == U_SPIFFS ) { - // iterate through partitions table to find the spiffs/littlefs partition - esp_partition_iterator_t iterator = esp_partition_find( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL ); - while( iterator != NULL ) { - _target_partition = (esp_partition_t *)esp_partition_get( iterator ); - iterator = esp_partition_next( iterator ); - } - esp_partition_iterator_release( iterator ); - } else { - // wut ? - log_e("Unhandled partition type #%i, must be one of U_FLASH / U_SPIFFS", update_partition ); - } -} - - - -bool esp32FOTA::checkJSONManifest(JsonVariant doc) -{ - if(strcmp(doc["type"].as(), _cfg.name) != 0) { - log_d("Payload type in manifest %s doesn't match current firmware %s", doc["type"].as(), _cfg.name ); - log_d("Doesn't match type: %s", _cfg.name ); - return false; // Move to the next entry in the manifest - } - log_i("Payload type in manifest %s matches current firmware %s", doc["type"].as(), _cfg.name ); - - _flashFileSystemUrl.clear(); - _firmwareUrl.clear(); - - if(doc["version"].is()) { - uint16_t v = doc["version"].as(); - log_d("JSON version: %d (int)", v); - _payload_sem = SemverClass(v); - } else if (doc["version"].is()) { - const char* c = doc["version"].as(); - _payload_sem = SemverClass(c); - log_d("JSON version: %s (semver)", c ); - } else { - log_e( "Invalid semver format received in manifest. Defaulting to 0" ); - _payload_sem = SemverClass(0); - } - - debugSemVer("Payload firmware version", _payload_sem.ver() ); - - // Memoize some values to help with the decision tree - bool has_url = doc["url"].is(); - bool has_firmware = doc["bin"].is(); - bool has_hostname = doc["host"].is(); - //bool has_signature = doc["sig"].is(); - bool has_port = doc["port"].is(); - uint16_t portnum = doc["port"].as(); - bool has_tls = has_port ? (portnum == 443 || portnum == 4433) : false; - bool has_spiffs = doc["spiffs"].is(); - bool has_littlefs = doc["littlefs"].is(); - bool has_fatfs = doc["fatfs"].is(); - bool has_filesystem = has_littlefs || has_spiffs || has_fatfs; - - String protocol(has_tls ? "https" : "http"); - String flashFSPath = - has_filesystem - ? ( - has_littlefs - ? doc["littlefs"].as() - : has_spiffs - ? doc["spiffs"].as() - : doc["fatfs"].as() - ) - : ""; - - log_i("JSON manifest provided keys: url=%s, host: %s, port: %s, bin: %s, fs: [%s]", - has_url?"true":"false", - has_hostname?"true":"false", - has_port?"true":"false", - has_firmware?"true":"false", - flashFSPath.c_str() - ); - - if( has_url ) { // Basic scenario: a complete URL was provided in the JSON manifest, all other keys will be ignored - _firmwareUrl = doc["url"].as(); - if( has_hostname ) { // If the manifest provides both, warn the user - log_w("Manifest provides both url and host - Using URL"); - } - } else if( has_firmware && has_hostname && has_port ) { // Precise scenario: Hostname, Port and Firmware Path were provided - _firmwareUrl = protocol + "://" + doc["host"].as() + ":" + portnum + doc["bin"].as(); - if( has_filesystem ) { // More complex scenario: the manifest also provides a [spiffs, littlefs or fatfs] partition - _flashFileSystemUrl = protocol + "://" + doc["host"].as() + ":" + portnum + flashFSPath; - } - } else { // JSON was malformed - no firmware target was provided - log_e("JSON manifest was missing one of the required keys :(" ); - String prettyJson; - serializeJsonPretty(doc, prettyJson); - log_d("%s", prettyJson); - return false; - } - - if (semver_compare(*_payload_sem.ver(), *_cfg.sem.ver()) == 1) { - return true; - } - - return false; -} - - - - -bool esp32FOTA::execHTTPcheck() -{ - String useURL = String( _cfg.manifest_url ); - - if( useURL.isEmpty() ) { - log_e("No manifest_url provided in config, aborting!"); - return false; - } - - // being deprecated, soon unsupported! - // if( useURL.isEmpty() && !checkURL.isEmpty() ) { - // log_w("checkURL will soon be unsupported, use FOTAConfig_t::manifest_url instead!!"); - // useURL = checkURL; - // } - - // // being deprecated, soon unsupported! - // if( useDeviceID ) { - // log_w("useDeviceID will soon be unsupported, use FOTAConfig_t::use_device_id instead!!"); - // _cfg.use_device_id = useDeviceID; - // } - - if (_cfg.use_device_id) { - // URL may already have GET values - String argseparator = (useURL.indexOf('?') != -1 ) ? "&" : "?"; - useURL += argseparator + "id=" + getDeviceID(); - } - - if ( isConnected && !isConnected() ) { // Check the current connection status - log_i("Connection check requested but network not ready - skipping"); - return false; // WiFi not connected - } - - log_i("Getting HTTP: %s", useURL.c_str()); - - if(! setupHTTP( useURL.c_str() ) ) { - log_e("Unable to setup http, aborting!"); - return false; - } - - int httpCode = _http.GET(); //Make the request - - // only handle 200/301, fail on everything else - if( httpCode != HTTP_CODE_OK && httpCode != HTTP_CODE_MOVED_PERMANENTLY ) { - // This error may be a false positive or a consequence of the network being disconnected. - // Since the network is controlled from outside this class, only significant error messages are reported. - if( httpCode > 0 ) { - log_e("Error on HTTP request (httpCode=%i)", httpCode); - } else { - log_d("Unknown HTTP response"); - } - _http.end(); - return false; - } - - // TODO: use payload.length() to speculate on JSONResult buffer size - #define JSON_FW_BUFF_SIZE 2048 - DynamicJsonDocument JSONResult( JSON_FW_BUFF_SIZE ); - DeserializationError err = deserializeJson( JSONResult, _http.getStream() ); - - if (err) { // Check for errors in parsing, or JSON length may exceed buffer size - log_e("JSON Parsing failed (%s, in=%d bytes, buff=%d bytes):", err.c_str(), _http.getSize(), JSON_FW_BUFF_SIZE ); - return false; - } - - _http.end(); // We're done with HTTP - free the resources - - if (JSONResult.is()) { - // Although improbable given the size on JSONResult buffer, we already received an array of multiple firmware types and/or versions - JsonArray arr = JSONResult.as(); - for (JsonVariant JSONDocument : arr) { - if(checkJSONManifest(JSONDocument)) { - // TODO: filter "highest vs next" version number for JSON with only one firmware type but several version numbers - return true; - } - } - } else if (JSONResult.is()) { - if(checkJSONManifest(JSONResult.as())) - return true; - } - - return false; // We didn't get a hit against the above, return false -} - - -String esp32FOTA::getDeviceID() -{ - char deviceid[21]; - uint64_t chipid; - chipid = ESP.getEfuseMac(); - sprintf(deviceid, "%" PRIu64, chipid); - String thisID(deviceid); - return thisID; -} - - -// Force a firmware update regardless on current version -bool esp32FOTA::forceUpdate(const char* firmwareURL, bool validate ) -{ - _firmwareUrl = firmwareURL; - _cfg.check_sig = validate; - return execOTA(); -} - -// Force a firmware update regardless on current version -bool esp32FOTA::forceUpdateSPIFFS(const char* firmwareURL, bool validate ) -{ - _firmwareUrl = firmwareURL; - _flashFileSystemUrl = firmwareURL; - _cfg.check_sig = validate; - return execSPIFFSOTA(); -} - - -bool esp32FOTA::forceUpdate(const char* firmwareHost, uint16_t firmwarePort, const char* firmwarePath, bool validate ) -{ - static String firmwareURL("http"); - if ( firmwarePort == 443 || firmwarePort == 4433 ) firmwareURL += "s"; - firmwareURL += String(firmwareHost); - firmwareURL += ":"; - firmwareURL += String(firmwarePort); - firmwareURL += firmwarePath; - return forceUpdate( firmwareURL.c_str(), validate ); -} - - -bool esp32FOTA::forceUpdate(bool validate ) -{ - // Forces an update from a manifest, ignoring the version check - if(!execHTTPcheck()) { - if (!_firmwareUrl) { - // execHTTPcheck returns false when the manifest is malformed or when the version isn't - // an upgrade. If _firmwareUrl isn't set we can't force an upgrade. - log_e("forceUpdate called, but unable to get _firmwareUrl from manifest via execHTTPcheck."); - return false; - } - } - _cfg.check_sig = validate; - return execOTA(); -} - - -/** - * This function return the new version of new firmware - */ -int esp32FOTA::getPayloadVersion() -{ - log_w( "This function only returns the MAJOR version. For complete depth use getPayloadVersion(char *)." ); - return _payload_sem.ver()->major; -} - - -void esp32FOTA::getPayloadVersion(char * version_string) -{ - semver_render( _payload_sem.ver(), version_string ); -} - - -void esp32FOTA::debugSemVer( const char* label, semver_t* version ) -{ - char version_no[256] = {'\0'}; - semver_render( version, version_no ); - log_i("%s: %s", label, version_no ); -} - - - - - - -static int64_t getHTTPStream( esp32FOTA* fota, int partition ) -{ - - const char* url = partition==U_SPIFFS ? fota->getFlashFS_URL() : fota->getFirmwareURL(); - - log_d("Opening item %s\n", url ); - - if(! fota->setupHTTP( url ) ) { // certs - log_e("unable to setup http, aborting!"); - return -1; - } - - int64_t updateSize = 0; - int httpCode = fota->getHTTPCLient()->GET(); - String contentType; - - fota->setFotaStream( nullptr ); - - if( httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY ) { - updateSize = fota->getHTTPCLient()->getSize(); - contentType = fota->getHTTPCLient()->header( "Content-type" ); - String acceptRange = fota->getHTTPCLient()->header( "Accept-Ranges" ); - if( acceptRange == "bytes" ) { - log_v("This server supports resume!" ); - } else { - log_v("This server does not support resume!" ); - } - } else { - switch( httpCode ) { - // 1xx = Hold on - // 2xx = Here you go - // 3xx = Go away - // 4xx = You fucked up - // 5xx = I fucked up - - case 204: log_e("Status: 204 (No contents), "); break; - case 401: log_e("Status: 401 (Unauthorized), check setExtraHTTPHeader() values"); break; - case 403: log_e("Status: 403 (Forbidden), check path on webserver?"); break; - case 404: log_e("Status: 404 (Not Found), also a palindrom, check path in manifest?"); break; - case 418: log_e("Status: 418 (I'm a teapot), Brit alert!"); break; - case 429: log_e("Status: 429 (Too many requests), throttle things down?"); break; - case 500: log_e("Status: 500 (Internal Server Error), you broke the webs!"); break; - default: - // This error may be a false positive or a consequence of the network being disconnected. - // Since the network is controlled from outside this class, only significant error messages are reported. - if( httpCode > 0 ) { - log_e("Server responded with HTTP Status '%i'. Please check your setup", httpCode ); - } else { - log_d("Unknown HTTP response"); - } - break; - } - - return -1; - } - - // TODO: Not all streams respond with a content length. - // TODO: Set updateSize to UPDATE_SIZE_UNKNOWN when content type is valid. - - // check updateSize and content type - if( updateSize<=0 ) { - log_e("There was no content in the http response: (length: %" PRId64 ", contentType: %s)\n", updateSize, contentType.c_str()); - return -1; - } - - log_d("updateSize : %" PRId64 ", contentType: %s", updateSize, contentType.c_str()); - - fota->setFotaStream( fota->getHTTPCLient()->getStreamPtr() ); - - return updateSize; -} - - - -static int64_t getFileStream( esp32FOTA* fota, int partition) -{ - fs::FS* fs = fota->getFotaFS(); - - if(!fs ) { - log_e("No filesystem defined, use ::setCertFileSystem( &SD ) "); - return -1; - } - - const char* path = partition==U_SPIFFS ? fota->getFlashFS_URL() : fota->getFirmwareURL(); - log_d("Opening item %s\n", path ); - - fs::File* file = (fs::File*)fota->getFotaStreamPtr(); - *file = fs->open( path ); - - if(! file ) { - log_e("unable to access filesystem, aborting!"); - return -1; - } - - int64_t updateSize = file->size(); - - // check updateSize and content type - if( !updateSize ) { - log_e("Empty file"); - file->close(); - fota->setFotaStream( nullptr ); - return -1; - } - - log_d("updateSize : %i", updateSize); - - return updateSize; -} - - - -static int64_t getSerialStream( esp32FOTA* fota, int partition) -{ - return -1; -} - - - -static bool WiFiStatusCheck() -{ - return (WiFi.status() == WL_CONNECTED); -} - -/* -static bool EthernetStatusCheck() -{ - return eth_connected; -} -*/ - - - -#pragma GCC diagnostic pop diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32FOTA.hpp b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32FOTA.hpp deleted file mode 100644 index 3ae345e..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32FOTA.hpp +++ /dev/null @@ -1,395 +0,0 @@ -/* - esp32 firmware OTA - Date: December 2018 - Author: Chris Joyce - Purpose: Perform an OTA update from a bin located on a webserver (HTTP Only) - - Date: 2021-12-21 - Author: Moritz Meintker - Remarks: Re-written/removed a bunch of functions around HTTPS. The library is - now URL-agnostic. This means if you provide an https://-URL it will - use the root_ca.pem (needs to be provided via PROGMEM/SPIFFS/LittleFS or SD) - to verify the server certificate and then download the ressource through an - encrypted connection unless you set the allow_insecure_https option. - Otherwise it will just use plain HTTP which will still offer to sign - your firmware image. - - Date: 2022-09-12 - Author: tobozo - Changes: - - Abstracted away filesystem - - Refactored some code blocks - - Added spiffs/littlefs/fatfs updatability - - Made crypto assets (pub key, rootca) loadable from multiple sources - Roadmap: - - Firmware/FlashFS update order (SPIFFS/LittleFS first or last?) - - Archive support for gz/targz formats - - firmware.gz + spiffs.gz in manifest - - bundle.tar.gz [ firmware + filesystem ] in manifest - - Update from Stream (e.g deported update via SD, http or gzupdater) -*/ - -#pragma once - -#define esp32fota_h - -extern "C" { - #include "semver/semver.h" -} - -#include -#include -#include -#include -#include - -// inherit includes from sketch, detect SPIFFS first for legacy support -#if __has_include() || defined _SPIFFS_H_ - #if !defined(DISABLE_ALL_LIBRARY_WARNINGS) - #pragma message "Using SPIFFS for certificate validation" - #endif - #include - #define FOTA_FS &SPIFFS -#elif __has_include() || defined _LiffleFS_H_ - #if !defined(DISABLE_ALL_LIBRARY_WARNINGS) - #pragma message "Using LittleFS for certificate validation" - #endif - #include - #define FOTA_FS &LittleFS -#elif __has_include() || defined _SD_H_ - #if !defined(DISABLE_ALL_LIBRARY_WARNINGS) - #pragma message "Using SD for certificate validation" - #endif - #include - #define FOTA_FS &SD -#elif __has_include() || defined _SD_MMC_H_ - #if !defined(DISABLE_ALL_LIBRARY_WARNINGS) - #pragma message "Using SD_MMC for certificate validation" - #endif - #include - #define FOTA_FS &SD_MMC -#elif defined _LIFFLEFS_H_ // older externally linked, hard to identify and unsupported versions of SPIFFS - #if !defined(DISABLE_ALL_LIBRARY_WARNINGS) - #pragma message "this version of LittleFS is unsupported, use #include instead, if using platformio add LittleFS(esp32)@^2.0.0 to lib_deps" - #endif -#elif __has_include() || defined _PSRAMFS_H_ - #if !defined(DISABLE_ALL_LIBRARY_WARNINGS) - #pragma message "Using PSRamFS for certificate validation" - #endif - #include - #define FOTA_FS &PSRamFS -#else - //#if !defined(DISABLE_ALL_LIBRARY_WARNINGS) - // #pragma message "No filesystem provided, certificate validation will be unavailable (hint: include SD, SPIFFS or LittleFS before including this library)" - //#endif - #define FOTA_FS nullptr -#endif - - - - -#if __has_include() - #pragma message "Using FlashZ as Update agent" - #include - #define F_Compression "zlib" - #define F_hasZlib() true - #define F_Update FlashZ::getInstance() - #define F_UpdateEnd() (mode_z ? F_Update.endz() : F_Update.end()) - #define F_abort() if (mode_z) F_Update.abortz(); else F_Update.abort() - #define F_writeStream() (mode_z ? F_Update.writezStream(*_stream, updateSize) : F_Update.writeStream(*_stream)) - // #define DEBUG_ESP32_FLASHZ - #if !defined DEBUG_ESP32_FLASHZ - #define F_isZlibStream() (_stream->peek() == ZLIB_HEADER && ((partition == U_SPIFFS && _flashFileSystemUrl.indexOf("zz")>-1) || (partition == U_FLASH && _firmwareUrl.indexOf("zz")>-1))) - #define F_canBegin() (mode_z ? F_Update.beginz(UPDATE_SIZE_UNKNOWN, partition) : F_Update.begin(updateSize, partition)) - #else - __attribute__((unused)) static bool F_canBegin_cb(bool mode_z, int updateSize, int partition) { // implement debug here - return (mode_z ? F_Update.beginz(UPDATE_SIZE_UNKNOWN, partition) : F_Update.begin(updateSize, partition)); - } - __attribute__((unused)) static bool F_isZlibStream_cb( Stream* stream, int partition, String flashFileSystemUrl, String firmwareUrl ) { // implement debug here - return (stream->peek() == ZLIB_HEADER && ((partition == U_SPIFFS && flashFileSystemUrl.indexOf("zz")>-1) || (partition == U_FLASH && firmwareUrl.indexOf("zz")>-1))); - } - #define F_isZlibStream() F_isZlibStream_cb( _stream, partition, _flashFileSystemUrl, _firmwareUrl ) - #define F_canBegin() F_canBegin_cb(mode_z, updateSize, partition) - #endif - -#elif __has_include("ESP32-targz.h") - #pragma message "Using GzUpdateClass as Update agent" - #include - #define F_Compression "gzip" - #define F_hasZlib() true - #define F_Update GzUpdateClass::getInstance() - #define F_UpdateEnd() (mode_z ? F_Update.endgz() : F_Update.end()) - #define F_abort() if (mode_z) F_Update.abortgz(); else F_Update.abort() - #define F_writeStream() (mode_z ? F_Update.writeGzStream(*_stream, updateSize) : F_Update.writeStream(*_stream)) - // #define DEBUG_ESP32_TARGZ - #if !defined DEBUG_ESP32_TARGZ - #define F_isZlibStream() (_stream->peek() == 0x1f && ((partition == U_SPIFFS && _flashFileSystemUrl.indexOf("gz")>-1) || (partition == U_FLASH && _firmwareUrl.indexOf("gz")>-1)) ) - #define F_canBegin() (mode_z ? F_Update.begingz(UPDATE_SIZE_UNKNOWN, partition) : F_Update.begin(updateSize, partition)) - #else - __attribute__((unused)) static bool F_canBegin_cb(bool mode_z, int updateSize, int partition) { // implement debug here - return (mode_z ? F_Update.begingz(UPDATE_SIZE_UNKNOWN, partition) : F_Update.begin(updateSize, partition)); - } - __attribute__((unused)) static bool F_isZlibStream_cb( Stream* stream, int partition, String flashFileSystemUrl, String firmwareUrl ) { // implement debug here - return (stream->peek() == 0x1f && ((partition == U_SPIFFS && flashFileSystemUrl.indexOf("gz")>-1) || (partition == U_FLASH && firmwareUrl.indexOf("gz")>-1)) ); - } - #define F_isZlibStream() F_isZlibStream_cb( _stream, partition, _flashFileSystemUrl, _firmwareUrl ) - #define F_canBegin() F_canBegin_cb(mode_z, updateSize, partition) - #endif -#else - #include - #define F_Compression "none" - #define F_Update Update - #define F_hasZlib() false - #define F_isZlibStream() false - #define F_canBegin() F_Update.begin(updateSize, partition) - #define F_UpdateEnd() F_Update.end() - #define F_abort() F_Update.abort() - #define F_writeStream() F_Update.writeStream(*_stream); -#endif - -#define FW_SIGNATURE_LENGTH 512 - -struct SemverClass -{ -public: - SemverClass( const char* version ); - SemverClass( int major, int minor=0, int patch=0 ); - ~SemverClass() { semver_free(&_ver); } - semver_t* ver(); -private: - semver_t _ver = semver_t(); -}; - - - - -// Filesystem/memory helper for signature check and pem validation. -// This is abstracted away to allow storage alternatives such as -// PROGMEM, SD, SPIFFS, LittleFS or FatFS -// Intended to be used by esp32FOTA.setPubKey() and esp32FOTA.setRootCA() -class CryptoAsset -{ -public: - virtual size_t size() = 0; - virtual const char* get() = 0; -}; - -class CryptoFileAsset : public CryptoAsset -{ -public: - CryptoFileAsset( const char* _path, fs::FS* _fs ) : path(_path), fs(_fs), contents(""), len(0) { } - size_t size(); - const char* get() { return contents.c_str(); } -private: - const char* path; - fs::FS* fs; - std::string contents; - size_t len; - bool fs_read_file(); -}; - -class CryptoMemAsset : public CryptoAsset -{ -public: - CryptoMemAsset( const char* _name, const char* _bytes, size_t _len ) : name(_name), bytes(_bytes), len(_len) { } - size_t size() { return len; }; - const char* get() { return bytes; } -private: - const char* name; - const char* bytes; - size_t len; -}; - - -struct FOTAConfig_t -{ - char* name { nullptr }; - char* manifest_url { nullptr }; - SemverClass sem {0}; - bool check_sig { false }; - bool unsafe { false }; - bool use_device_id { false }; - CryptoAsset* root_ca { nullptr }; - CryptoAsset* pub_key { nullptr }; - size_t signature_len {FW_SIGNATURE_LENGTH}; - FOTAConfig_t() = default; -}; - - -enum FOTAStreamType_t -{ - FOTA_HTTP_STREAM, - FOTA_FILE_STREAM, - FOTA_SERIAL_STREAM -}; - - -// Main Class -class esp32FOTA -{ -public: - - esp32FOTA(); - ~esp32FOTA(); - - esp32FOTA( FOTAConfig_t cfg ); - esp32FOTA(const char* firwmareType, int firwmareVersion, bool validate = false, bool allow_insecure_https = false ); - esp32FOTA(const String &firwmareType, int firwmareVersion, bool validate = false, bool allow_insecure_https = false ) - : esp32FOTA(firwmareType.c_str(), firwmareVersion, validate, allow_insecure_https){}; - esp32FOTA(const char* firwmareType, const char* firmwareSemanticVersion, bool validate = false, bool allow_insecure_https = false ); - esp32FOTA(const String &firwmareType, const String &firmwareSemanticVersion, bool validate = false, bool allow_insecure_https = false ) - : esp32FOTA(firwmareType.c_str(), firmwareSemanticVersion.c_str(), validate, allow_insecure_https){}; - - template void setPubKey( T* asset ) { _cfg.pub_key = (CryptoAsset*)asset; _cfg.check_sig = true; } - template void setRootCA( T* asset ) { _cfg.root_ca = (CryptoAsset*)asset; _cfg.unsafe = false; } - - bool forceUpdate(const char* firmwareHost, uint16_t firmwarePort, const char* firmwarePath, bool validate ); - bool forceUpdate(const char* firmwareURL, bool validate ); - bool forceUpdate(bool validate ); - - bool forceUpdateSPIFFS(const char* firmwareURL, bool validate ); - - void handle(); - - bool execOTA(); - bool execSPIFFSOTA(); - bool execOTA( int partition, bool restart_after = true ); - bool execHTTPcheck(); - - void useDeviceId( bool use=true ) { _cfg.use_device_id = use; } - - // config setter - void setConfig( FOTAConfig_t cfg ); - void printConfig( FOTAConfig_t *cfg=nullptr ); - - // Manually specify the manifest url, this is provided as a transition between legagy and new config system - void setManifestURL( const char* manifest_url ) { setString( &_cfg.manifest_url, manifest_url ); } - void setManifestURL( const String &manifest_url ) { setManifestURL( manifest_url.c_str() ); } - - // use this to set "Authorization: Basic" or other specific headers to be sent with the queries - void setExtraHTTPHeader( String name, String value ) { extraHTTPHeaders[name] = value; } - - // set the signature len - void setSignatureLen( size_t len ); - - // /!\ Only use this to change filesystem for **default** RootCA and PubKey paths. - // Otherwise use setPubKey() and setRootCA() - void setCertFileSystem( fs::FS *cert_filesystem = nullptr ); - - // this is passed to Update.onProgress() - typedef std::function ProgressCallback_cb; // size_t progress, size_t size - void setProgressCb(ProgressCallback_cb fn) { onOTAProgress = fn; } // callback setter - - // when Update.begin() returned false - typedef std::function UpdateBeginFail_cb; // int partition (U_FLASH or U_SPIFFS) - void setUpdateBeginFailCb(UpdateBeginFail_cb fn) { onUpdateBeginFail = fn; } // callback setter - - // after Update.end() and before validate_sig() - typedef std::function UpdateEnd_cb; // int partition (U_FLASH or U_SPIFFS) - void setUpdateEndCb(UpdateEnd_cb fn) { onUpdateEnd = fn; } // callback setter - - // validate_sig() error handling, mixed situations - typedef std::function UpdateCheckFail_cb; // int partition (U_FLASH or U_SPIFFS), int error_code - void setUpdateCheckFailCb(UpdateCheckFail_cb fn) { onUpdateCheckFail = fn; } // callback setter - - // update successful - typedef std::function UpdateFinished_cb; // int partition (U_FLASH or U_SPIFFS), bool restart_after - void setUpdateFinishedCb(UpdateFinished_cb fn) { onUpdateFinished = fn; } // callback setter - - // stream getter - typedef std::function getStream_cb; // esp32FOTA* this, int partition (U_FLASH or U_SPIFFS), returns stream size - void setStreamGetter( getStream_cb fn ) { getStream = fn; } // callback setter - - // stream ender - typedef std::function endStream_cb; // esp32FOTA* this - void setStreamEnder( endStream_cb fn ) { endStream = fn; } // callback setter - - // connection check - typedef std::function isConnected_cb; // - void setStatusChecker( isConnected_cb fn ) { isConnected = fn; } // callback setter - - // updating from a File or from Serial? - void setStreamType( FOTAStreamType_t stream_type ) { _stream_type = stream_type; } - void setStreamTimeout( uint32_t timeout ) { _stream_timeout = timeout; } - - const char* getManifestURL() { return _manifestUrl.c_str(); } - const char* getFirmwareURL() { return _firmwareUrl.c_str(); } - const char* getFlashFS_URL() { return _flashFileSystemUrl.c_str(); } - const char* getPath(int part) { return part==U_SPIFFS ? getFlashFS_URL() : getFirmwareURL(); } - - bool zlibSupported() { return mode_z; } - - int getPayloadVersion(); - void getPayloadVersion(char * version_string); - - FOTAConfig_t getConfig() { return _cfg; }; - FOTAStreamType_t getStreamType() { return _stream_type; } - HTTPClient* getHTTPCLient() { return &_http; } - WiFiClientSecure* getWiFiClient() { return &_client; } - fs::File* getFotaFilePtr() { return &_file; } - Stream* getFotaStreamPtr() { return _stream; } - fs::FS* getFotaFS() { return _fs; } - - // internals but need to be exposed to the callbacks - bool setupHTTP( const char* url ); - void setFotaStream( Stream* stream ) { _stream = stream; } - - //[[deprecated("Use setManifestURL( String ) or cfg.manifest_url with setConfig( FOTAConfig_t )")]] String checkURL = ""; - //[[deprecated("Use cfg.use_device_id with setConfig( FOTAConfig_t )")]] bool useDeviceID = false; - - bool validate_sig( const esp_partition_t* partition, unsigned char *signature, uint32_t firmware_size ); - -private: - - HTTPClient _http; - WiFiClientSecure _client; - Stream *_stream; - fs::File _file; - - bool mode_z = F_hasZlib(); - - FOTAStreamType_t _stream_type = FOTA_HTTP_STREAM; // defaults to HTTP - uint32_t _stream_timeout = 10000; // max wait for stream->available() - - void setupStream(); - void stopStream(); - void setString( char **dest, const char* src ); // mem allocator - - FOTAConfig_t _cfg; - - SemverClass _payload_sem = SemverClass(0,0,0); - - String _manifestUrl; - String _firmwareUrl; - String _flashFileSystemUrl; - - fs::FS *_fs = FOTA_FS; // default filesystem for certificate validation - - // custom callbacks provided by user - ProgressCallback_cb onOTAProgress; // this is passed to Update.onProgress() - UpdateBeginFail_cb onUpdateBeginFail; // when Update.begin() returned false - UpdateEnd_cb onUpdateEnd; // after Update.end() and before validate_sig() - UpdateCheckFail_cb onUpdateCheckFail; // validate_sig() error handling, mixed situations - UpdateFinished_cb onUpdateFinished; // update successful - getStream_cb getStream; // optional stream getter, defaults to http.getStreamPtr() - endStream_cb endStream; // optional stream closer, defaults to http.end() - isConnected_cb isConnected; // optional connection checker, defaults to WiFi.status()==WL_CONNECTED - - std::map extraHTTPHeaders; // this holds the extra http headers defined by the user - - String getDeviceID(); - bool checkJSONManifest(JsonVariant JSONDocument); - void debugSemVer( const char* label, semver_t* version ); - void getPartition( int update_partition ); - - - // temporary partition holder for signature check operations - const esp_partition_t* _target_partition = nullptr; - - // This is kept for legacy behaviour, use setPubKey() and setRootCA() with - // CryptoMemAsset ot CryptoFileAsset instead - void setupCryptoAssets(); - const char* rsa_key_pub_default_path = "/rsa_key.pub"; - const char* root_ca_pem_default_path = "/root_ca.pem"; - -}; diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32fota.h b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32fota.h deleted file mode 100644 index 41f9e04..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/esp32fota.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -// This is kept for legacy but should not be used in new releases -// as filename an project name don't match (esp32FOTA vs esp32fota) - -#ifdef __cplusplus - - #include "esp32FOTA.hpp" - -#else - - #error esp32FOTA requires a C++ compiler, please change file extension to .cc or .cpp - -#endif diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/README.md b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/README.md deleted file mode 100644 index 6a1bc53..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/README.md +++ /dev/null @@ -1,233 +0,0 @@ -# semver.c [![Build Status](https://travis-ci.org/h2non/semver.c.png)](https://travis-ci.org/h2non/semver.c) [![GitHub release](https://img.shields.io/github/tag/h2non/semver.c.svg)](https://github.com/h2non/semver.c/releases) - -[Semantic version](http://semver.org) v2.0 parser and render written in [ANSI C](https://en.wikipedia.org/wiki/ANSI_C) with zero dependencies. - -## Features - -- [x] Standard compliant (otherwise, open an issue) -- [x] Version metadata parsing -- [x] Version prerelease parsing -- [x] Version comparison helpers -- [x] Supports comparison operators -- [x] Version render -- [x] Version bump -- [x] Version sanitizer -- [x] 100% test coverage -- [x] No regexp (ANSI C doesn't support it) -- [x] Numeric conversion for sorting/filtering - -## Versions - -- [v0](https://github.com/h2non/semver.c/tree/89e66f36544e0250def32640b84b7e15c8585da4) - Legacy version. Beta. Not maintained anymore. -- [v1](https://github.com/h2non/semver.c) - Current stable version. - -## Usage - -Basic comparison: -```c -#include -#include - -char current[] = "1.5.10"; -char compare[] = "2.3.0"; - -int -main(int argc, char *argv[]) { - semver_t current_version = {}; - semver_t compare_version = {}; - - if (semver_parse(current, ¤t_version) - || semver_parse(compare, &compare_version)) { - fprintf(stderr,"Invalid semver string\n"); - return -1; - } - - int resolution = semver_compare(compare_version, current_version); - - if (resolution == 0) { - printf("Versions %s is equal to: %s\n", compare, current); - } - else if (resolution == -1) { - printf("Version %s is lower than: %s\n", compare, current); - } - else { - printf("Version %s is higher than: %s\n", compare, current); - } - - // Free allocated memory when we're done - semver_free(¤t_version); - semver_free(&compare_version); - return 0; -} -``` - -Satisfies version: - -```c -#include -#include - -semver_t current = {}; -semver_t compare = {}; - -int -main(int argc, char *argv[]) { - semver_parse("1.3.10", ¤t); - semver_parse("1.5.2", &compare); - - // Use caret operator for the comparison - char operator[] = "^"; - - if (semver_satisfies(current, compare, operator)) { - printf("Version %s can be satisfied by %s", "1.3.10", "1.5.2"); - } - - // Free allocated memory when we're done - semver_free(¤t); - semver_free(&compare); - return 0; -} -``` - -## Installation - -Clone this repository: - -```bash -$ git clone https://github.com/h2non/semver.c -``` - -Or install with [clib](https://github.com/clibs/clib): - -```bash -$ clib install h2non/semver.c -``` - -## API - -#### struct semver_t { int major, int minor, int patch, char * prerelease, char * metadata } - -semver base struct. - -#### semver_parse(const char *str, semver_t *ver) => int - -Parses a string as semver expression. - -**Returns**: - -- `-1` - In case of invalid semver or parsing error. -- `0` - All was fine! - -#### semver_compare(semver_t a, semver_t b) => int - -Compare versions `a` with `b`. - -Returns: -- `-1` in case of lower version. -- `0` in case of equal versions. -- `1` in case of higher version. - -#### semver_satisfies(semver_t a, semver_t b, char *operator) => int - -Checks if both versions can be satisfied -based on the given comparison operator. - -**Allowed operators**: - -- `=` - Equality -- `>=` - Higher or equal to -- `<=` - Lower or equal to -- `<` - Lower than -- `>` - Higher than -- `^` - Caret operator comparison ([more info](https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4)) -- `~` - Tilde operator comparison ([more info](https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1)) - -**Returns**: - -- `1` - Can be satisfied -- `0` - Cannot be satisfied - -#### semver_satisfies_caret(semver_t a, semver_t b) => int - -Checks if version `x` can be satisfied by `y` -performing a comparison with caret operator. - -See: https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4 - -**Returns**: - -- `1` - Can be satisfied -- `0` - Cannot be satisfied - -#### semver_satisfies_patch(semver_t a, semver_t b) => int - -Checks if version `x` can be satisfied by `y` -performing a comparison with tilde operator. - -See: https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1 - -**Returns**: - -- `1` - Can be satisfied -- `0` - Cannot be satisfied - - -#### semver_eq(semver_t a, semver_t b) => int - -Equality comparison. - -#### semver_ne(semver_t a, semver_t b) => int - -Non equal comparison. - -#### semver_gt(semver_t a, semver_t b) => int - -Greater than comparison. - -#### semver_lt(semver_t a, semver_t b) => int - -Lower than comparison. - -#### semver_gte(semver_t a, semver_t b) => int - -Greater than or equal comparison. - -#### semver_lte(semver_t a, semver_t b) => int - -Lower than or equal comparison. - -#### semver_render(semver_t *v, char *dest) => void - -Render as string. - -#### semver_numeric(semver_t *v) => int - -Render as numeric value. Useful for ordering and filtering. - -#### semver_bump(semver_t *a) => void - -Bump major version. - -#### semver_bump_minor(semver_t *a) => void - -Bump minor version. - -#### semver_bump_patch(semver_t *a) => void - -Bump patch version. - -#### semver_free(semver_t *a) => void - -Helper to free allocated memory from heap. - -#### semver_is_valid(char *str) => int - -Checks if the given string is a valid semver expression. - -#### semver_clean(char *str) => int - -Removes invalid semver characters in a given string. - -## License - -MIT - Tomas Aparicio diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/semver.c b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/semver.c deleted file mode 100644 index 5cae0db..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/semver.c +++ /dev/null @@ -1,638 +0,0 @@ -/* - * semver.c - * - * Copyright (c) 2015-2017 Tomas Aparicio - * MIT licensed - */ - -#include -#include -#include -#include "semver.h" - -#define SLICE_SIZE 50 -#define DELIMITER "." -#define PR_DELIMITER "-" -#define MT_DELIMITER "+" -#define NUMBERS "0123456789" -#define ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -#define DELIMITERS DELIMITER PR_DELIMITER MT_DELIMITER -#define VALID_CHARS NUMBERS ALPHA DELIMITERS - -static const size_t MAX_SIZE = sizeof(char) * 255; -static const int MAX_SAFE_INT = (unsigned int) -1 >> 1; - -/** - * Define comparison operators, storing the - * ASCII code per each symbol in hexadecimal notation. - */ - -enum operators { - SYMBOL_GT = 0x3e, - SYMBOL_LT = 0x3c, - SYMBOL_EQ = 0x3d, - SYMBOL_TF = 0x7e, - SYMBOL_CF = 0x5e -}; - -/** - * Private helpers - */ - -/* - * Remove [begin:len-begin] from str by moving len data from begin+len to begin. - * If len is negative cut out to the end of the string. - */ -static int -strcut (char *str, int begin, int len) { - size_t l; - l = strlen(str); - - if((int)l < 0 || (int)l > MAX_SAFE_INT) return -1; - - if (len < 0) len = l - begin + 1; - if (begin + len > (int)l) len = l - begin; - memmove(str + begin, str + begin + len, l - len + 1 - begin); - - return len; -} - -static int -contains (const char c, const char *matrix, size_t len) { - size_t x; - for (x = 0; x < len; x++) - if ((char) matrix[x] == c) return 1; - return 0; -} - -static int -has_valid_chars (const char *str, const char *matrix) { - size_t i, len, mlen; - len = strlen(str); - mlen = strlen(matrix); - - for (i = 0; i < len; i++) - if (contains(str[i], matrix, mlen) == 0) - return 0; - - return 1; -} - -static int -binary_comparison (int x, int y) { - if (x == y) return 0; - if (x > y) return 1; - return -1; -} - -static int -parse_int (const char *s) { - int valid, num; - valid = has_valid_chars(s, NUMBERS); - if (valid == 0) return -1; - - num = strtol(s, NULL, 10); - if (num > MAX_SAFE_INT) return -1; - - return num; -} - -/* - * Return a string allocated on the heap with the content from sep to end and - * terminate buf at sep. - */ -static char * -parse_slice (char *buf, char sep) { - char *pr, *part; - int plen; - - /* Find separator in buf */ - pr = strchr(buf, sep); - if (pr == NULL) return NULL; - /* Length from separator to end of buf */ - plen = strlen(pr); - - /* Copy from buf into new string */ - part = (char*)calloc(plen + 1, sizeof(*part)); - if (part == NULL) return NULL; - memcpy(part, pr + 1, plen); - /* Null terminate new string */ - part[plen] = '\0'; - - /* Terminate buf where separator was */ - *pr = '\0'; - - return part; -} - -/** - * Parses a string as semver expression. - * - * Returns: - * - * `0` - Parsed successfully - * `-1` - In case of error - */ - -int -semver_parse (const char *str, semver_t *ver) { - int valid, res; - size_t len; - char *buf; - valid = semver_is_valid(str); - if (!valid) return -1; - - len = strlen(str); - buf = (char*)calloc(len + 1, sizeof(*buf)); - if (buf == NULL) return -1; - strcpy(buf, str); - - ver->metadata = parse_slice(buf, MT_DELIMITER[0]); - ver->prerelease = parse_slice(buf, PR_DELIMITER[0]); - - res = semver_parse_version(buf, ver); - free(buf); -#if DEBUG > 0 - printf("[debug] semver.c %s = %d.%d.%d, %s %s\n", str, ver->major, ver->minor, ver->patch, ver->prerelease, ver->metadata); -#endif - return res; -} - -/** - * Parses a given string as semver expression. - * - * Returns: - * - * `0` - Parsed successfully - * `-1` - Parse error or invalid - */ - -int -semver_parse_version (const char *str, semver_t *ver) { - size_t len; - int index, value; - char *slice, *next, *endptr; - slice = (char *) str; - index = 0; - - while (slice != NULL && index++ < 4) { - next = strchr(slice, DELIMITER[0]); - if (next == NULL) - len = strlen(slice); - else - len = next - slice; - if (len > SLICE_SIZE) return -1; - - /* Cast to integer and store */ - value = strtol(slice, &endptr, 10); - if (endptr != next && *endptr != '\0') return -1; - - switch (index) { - case 1: ver->major = value; break; - case 2: ver->minor = value; break; - case 3: ver->patch = value; break; - } - - /* Continue with the next slice */ - if (next == NULL) - slice = NULL; - else - slice = next + 1; - } - - return 0; -} - -static int -compare_prerelease (char *x, char *y) { - char *lastx, *lasty, *xptr, *yptr, *endptr; - int xlen, ylen, xisnum, yisnum, xnum, ynum; - int xn, yn, min, res; - if (x == NULL && y == NULL) return 0; - if (y == NULL && x) return -1; - if (x == NULL && y) return 1; - - lastx = x; - lasty = y; - xlen = strlen(x); - ylen = strlen(y); - - while (1) { - if ((xptr = strchr(lastx, DELIMITER[0])) == NULL) - xptr = x + xlen; - if ((yptr = strchr(lasty, DELIMITER[0])) == NULL) - yptr = y + ylen; - - xnum = strtol(lastx, &endptr, 10); - xisnum = endptr == xptr ? 1 : 0; - ynum = strtol(lasty, &endptr, 10); - yisnum = endptr == yptr ? 1 : 0; - - if (xisnum && !yisnum) return -1; - if (!xisnum && yisnum) return 1; - - if (xisnum && yisnum) { - /* Numerical comparison */ - if (xnum != ynum) return xnum < ynum ? -1 : 1; - } else { - /* String comparison */ - xn = xptr - lastx; - yn = yptr - lasty; - min = xn < yn ? xn : yn; - if ((res = strncmp(lastx, lasty, min))) return res < 0 ? -1 : 1; - if (xn != yn) return xn < yn ? -1 : 1; - } - - lastx = xptr + 1; - lasty = yptr + 1; - if (lastx == x + xlen + 1 && lasty == y + ylen + 1) break; - if (lastx == x + xlen + 1) return -1; - if (lasty == y + ylen + 1) return 1; - } - - return 0; -} - -int -semver_compare_prerelease (semver_t x, semver_t y) { - return compare_prerelease(x.prerelease, y.prerelease); -} - -/** - * Performs a major, minor and patch binary comparison (x, y). - * This function is mostly used internally - * - * Returns: - * - * `0` - If versiona are equal - * `1` - If x is higher than y - * `-1` - If x is lower than y - */ - -int -semver_compare_version (semver_t x, semver_t y) { - int res; - - if ((res = binary_comparison(x.major, y.major)) == 0) { - if ((res = binary_comparison(x.minor, y.minor)) == 0) { - return binary_comparison(x.patch, y.patch); - } - } - - return res; -} - -/** - * Compare two semantic versions (x, y). - * - * Returns: - * - `1` if x is higher than y - * - `0` if x is equal to y - * - `-1` if x is lower than y - */ - -int -semver_compare (semver_t x, semver_t y) { - int res; - - if ((res = semver_compare_version(x, y)) == 0) { - return semver_compare_prerelease(x, y); - } - - return res; -} - -/** - * Performs a `greater than` comparison - */ - -int -semver_gt (semver_t x, semver_t y) { - return semver_compare(x, y) == 1; -} - -/** - * Performs a `lower than` comparison - */ - -int -semver_lt (semver_t x, semver_t y) { - return semver_compare(x, y) == -1; -} - -/** - * Performs a `equality` comparison - */ - -int -semver_eq (semver_t x, semver_t y) { - return semver_compare(x, y) == 0; -} - -/** - * Performs a `non equal to` comparison - */ - -int -semver_neq (semver_t x, semver_t y) { - return semver_compare(x, y) != 0; -} - -/** - * Performs a `greater than or equal` comparison - */ - -int -semver_gte (semver_t x, semver_t y) { - return semver_compare(x, y) >= 0; -} - -/** - * Performs a `lower than or equal` comparison - */ - -int -semver_lte (semver_t x, semver_t y) { - return semver_compare(x, y) <= 0; -} - -/** - * Checks if version `x` can be satisfied by `y` - * performing a comparison with caret operator. - * - * See: https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4 - * - * Returns: - * - * `1` - Can be satisfied - * `0` - Cannot be satisfied - */ - -int -semver_satisfies_caret (semver_t x, semver_t y) { - /* Major versions must always match. */ - if (x.major == y.major) { - /* If major version is 0, minor versions must match */ - if (x.major == 0) { - /* If minor version is 0, patch must match */ - if (x.minor == 0){ - return (x.minor == y.minor) && (x.patch == y.patch); - } - /* If minor version is not 0, patch must be >= */ - else if (x.minor == y.minor){ - return x.patch >= y.patch; - } - else{ - return 0; - } - } - else if (x.minor > y.minor){ - return 1; - } - else if (x.minor == y.minor) - { - return x.patch >= y.patch; - } - else { - return 0; - } - } - return 0; -} - -/** - * Checks if version `x` can be satisfied by `y` - * performing a comparison with tilde operator. - * - * See: https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1 - * - * Returns: - * - * `1` - Can be satisfied - * `0` - Cannot be satisfied - */ - -int -semver_satisfies_patch (semver_t x, semver_t y) { - return x.major == y.major - && x.minor == y.minor; -} - -/** - * Checks if both versions can be satisfied - * based on the given comparison operator. - * - * Allowed operators: - * - * - `=` - Equality - * - `>=` - Higher or equal to - * - `<=` - Lower or equal to - * - `<` - Lower than - * - `>` - Higher than - * - `^` - Caret comparison (see https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4) - * - `~` - Tilde comparison (see https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1) - * - * Returns: - * - * `1` - Can be satisfied - * `0` - Cannot be satisfied - */ - -int -semver_satisfies (semver_t x, semver_t y, const char *op) { - int first, second; - /* Extract the comparison operator */ - first = op[0]; - second = op[1]; - - /* Caret operator */ - if (first == SYMBOL_CF) - return semver_satisfies_caret(x, y); - - /* Tilde operator */ - if (first == SYMBOL_TF) - return semver_satisfies_patch(x, y); - - /* Strict equality */ - if (first == SYMBOL_EQ) - return semver_eq(x, y); - - /* Greater than or equal comparison */ - if (first == SYMBOL_GT) { - if (second == SYMBOL_EQ) { - return semver_gte(x, y); - } - return semver_gt(x, y); - } - - /* Lower than or equal comparison */ - if (first == SYMBOL_LT) { - if (second == SYMBOL_EQ) { - return semver_lte(x, y); - } - return semver_lt(x, y); - } - - return 0; -} - -/** - * Free heep allocated memory of a given semver. - * This is just a convenient function that you - * should call when you're done. - */ - -void -semver_free (semver_t *x) { - if (x->metadata) { - free(x->metadata); - x->metadata = NULL; - } - if (x->prerelease) { - free(x->prerelease); - x->prerelease = NULL; - } -} - -/** - * Renders - */ - -static void -concat_num (char * str, int x, const char * sep) { - char buf[SLICE_SIZE] = {0}; - if (sep == NULL) sprintf(buf, "%d", x); - else sprintf(buf, "%s%d", sep, x); - strcat(str, buf); -} - -static void -concat_char (char * str, char * x, const char * sep) { - char buf[SLICE_SIZE] = {0}; - sprintf(buf, "%s%s", sep, x); - strcat(str, buf); -} - -/** - * Render a given semver as string - */ - -void -semver_render (semver_t *x, char *dest) { - concat_num(dest, x->major, NULL); - concat_num(dest, x->minor, DELIMITER); - concat_num(dest, x->patch, DELIMITER); - if (x->prerelease) concat_char(dest, x->prerelease, PR_DELIMITER); - if (x->metadata) concat_char(dest, x->metadata, MT_DELIMITER); -} - -/** - * Version bump helpers - */ - -void -semver_bump (semver_t *x) { - x->major++; -} - -void -semver_bump_minor (semver_t *x) { - x->minor++; -} - -void -semver_bump_patch (semver_t *x) { - x->patch++; -} - -/** - * Helpers - */ - -static int -has_valid_length (const char *s) { - return strlen(s) <= MAX_SIZE; -} - -/** - * Checks if a given semver string is valid - * - * Returns: - * - * `1` - Valid expression - * `0` - Invalid - */ - -int -semver_is_valid (const char *s) { - return has_valid_length(s) - && has_valid_chars(s, VALID_CHARS); -} - -/** - * Removes non-valid characters in the given string. - * - * Returns: - * - * `0` - Valid - * `-1` - Invalid input - */ - -int -semver_clean (char *s) { - size_t i, len, mlen; - int res; - if (has_valid_length(s) == 0) return -1; - - len = strlen(s); - mlen = strlen(VALID_CHARS); - - for (i = 0; i < len; i++) { - if (contains(s[i], VALID_CHARS, mlen) == 0) { - res = strcut(s, i, 1); - if(res == -1) return -1; - --len; --i; - } - } - - return 0; -} - -static int -char_to_int (const char * str) { - int buf; - size_t i,len, mlen; - buf = 0; - len = strlen(str); - mlen = strlen(VALID_CHARS); - - for (i = 0; i < len; i++) - if (contains(str[i], VALID_CHARS, mlen)) - buf += (int) str[i]; - - return buf; -} - -/** - * Render a given semver as numeric value. - * Useful for ordering and filtering. - */ - -int -semver_numeric (semver_t *x) { - int num; - char buf[SLICE_SIZE * 3]; - memset(&buf, 0, SLICE_SIZE * 3); - - if (x->major) concat_num(buf, x->major, NULL); - if (x->major || x->minor) concat_num(buf, x->minor, NULL); - if (x->major || x->minor || x->patch) concat_num(buf, x->patch, NULL); - - num = parse_int(buf); - if(num == -1) return -1; - - if (x->prerelease) num += char_to_int(x->prerelease); - if (x->metadata) num += char_to_int(x->metadata); - - return num; -} diff --git a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/semver.h b/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/semver.h deleted file mode 100644 index e669a4a..0000000 --- a/SmartEVSE-3/SmartEVSE-3/lib/esp32FOTA/src/semver/semver.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * semver.h - * - * Copyright (c) 2015-2017 Tomas Aparicio - * MIT licensed - */ - -#ifndef __SEMVER_H -#define __SEMVER_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef SEMVER_VERSION -#define SEMVER_VERSION "0.2.0" -#endif - -/** - * semver_t struct - */ - -typedef struct semver_version_s { - int major; - int minor; - int patch; - char * metadata; - char * prerelease; -} semver_t; - -/** - * Set prototypes - */ - -int semver_satisfies (semver_t x, semver_t y, const char *op); -int semver_satisfies_caret (semver_t x, semver_t y); -int semver_satisfies_patch (semver_t x, semver_t y); -int semver_compare (semver_t x, semver_t y); -int semver_compare_version (semver_t x, semver_t y); -int semver_compare_prerelease (semver_t x, semver_t y); -int semver_gt (semver_t x, semver_t y); -int semver_gte (semver_t x, semver_t y); -int semver_lt (semver_t x, semver_t y); -int semver_lte (semver_t x, semver_t y); -int semver_eq (semver_t x, semver_t y); -int semver_neq (semver_t x, semver_t y); -int semver_parse (const char *str, semver_t *ver); -int semver_parse_version (const char *str, semver_t *ver); -void semver_render (semver_t *x, char *dest); -int semver_numeric (semver_t *x); -void semver_bump (semver_t *x); -void semver_bump_minor (semver_t *x); -void semver_bump_patch (semver_t *x); -void semver_free (semver_t *x); -int semver_is_valid (const char *s); -int semver_clean (char *s); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/SmartEVSE-3/SmartEVSE-3/pack.py b/SmartEVSE-3/SmartEVSE-3/pack.py deleted file mode 100644 index d137cac..0000000 --- a/SmartEVSE-3/SmartEVSE-3/pack.py +++ /dev/null @@ -1,117 +0,0 @@ -# Shamelessly translated mongsoose 7.13 pack.c to python to provide portability for platformio core software - -# perhaps Copyright (c) Cesanta Software Limited, I'm not sure because I'm not a lawyer -# might also be Copyright me or Copyright ChatGPT -# All rights reserved. -# but not sure by whom - -# This program is used to pack arbitrary data into a C binary. It takes -# a list of files as an input, and produces a .c data file that contains -# contents of all these files as a collection of byte arrays. -# -# Usage: -# 2. Convert list of files into single .c: -# ./pack file1.data file2.data > fs.c -# -# 3. In your application code, you can access files using this function: -# const char *mg_unpack(const char *file_name, size_t *size); -# -# 4. Build your app with fs.c: -# cc -o my_app my_app.c fs.c - -import errno -import sys -import os -import stat -import time - -code = """static int scmp(const char *a, const char *b) { - while (*a && (*a == *b)) a++, b++; - return *(const unsigned char *) a - *(const unsigned char *) b; -} -const char *mg_unlist(size_t no) { - return packed_files[no].name; -} -const char *mg_unpack(const char *name, size_t *size, time_t *mtime) { - const struct packed_file *p; - for (p = packed_files; p->name != NULL; p++) { - if (scmp(p->name, name) != 0) continue; - if (size != NULL) *size = p->size - 1; - if (mtime != NULL) *mtime = p->mtime; - return (const char *) p->data; - } - return NULL; -} -""" - -def main(argv): - i = 0 - strip_prefix = "" - - print("#include ") - print("#include ") - print("#include ") - print("") - print("#if defined(__cplusplus)\nextern \"C\" {\n#endif") - print("const char *mg_unlist(size_t no);") - print("const char *mg_unpack(const char *, size_t *, time_t *);") - print("#if defined(__cplusplus)\n}\n#endif\n\n", end='') - - while i < len(argv): - if argv[i] == "-s": - strip_prefix = argv[i + 1] - i += 2 - elif argv[i] == "-h" or argv[i] == "--help": - sys.stderr.write("Usage: %s[-s STRIP_PREFIX] files...\n" % argv[0]) - sys.exit(os.EX_USAGE) - else: - ascii = [''] * 12 - with open(argv[i], "rb") as fp: - print("static const unsigned char v%d[] = {" % (i + 1)) - j = 0 - while True: - ch = fp.read(1) - if not ch: - break - ch = ord(ch) - if j == len(ascii): - print(" // %s" % ''.join(ascii)) - j = 0 - ascii[j] = chr(ch) if ch >= 32 and ch <= 126 and ch != 92 else '.' - print(" %3u," % ch, end='') - j += 1 - print(" 0 // %s\n};" % ''.join(ascii)) - - i += 1 - - print("") - print("static const struct packed_file {") - print(" const char *name;") - print(" const unsigned char *data;") - print(" size_t size;") - print(" time_t mtime;") - print("} packed_files[] = {") - - i = 0 - while i < len(argv): - if argv[i] == "-s": - i += 1 - continue - st = os.stat(argv[i]) - name = argv[i] - n = len(strip_prefix) - if argv[i] == "-s": - i += 1 - continue - if name.startswith(strip_prefix): - name = name[n:] - print(" {\"/%s\", v%d, sizeof(v%d), %lu}," % (name, i + 1, i + 1, st.st_mtime)) - i += 1 - - print(" {NULL, NULL, 0, 0}") - print("};\n") - print(code, end='') - -if __name__ == "__main__": - main(sys.argv[1:]) - diff --git a/SmartEVSE-3/SmartEVSE-3/packfs.py b/SmartEVSE-3/SmartEVSE-3/packfs.py deleted file mode 100644 index 2ab145b..0000000 --- a/SmartEVSE-3/SmartEVSE-3/packfs.py +++ /dev/null @@ -1,34 +0,0 @@ -#this script will be run by platformio.ini from its native directory -import os, sys, gzip, shutil - -#check for the two files we need to be able to keep updating the firmware by the /update endpoint: -if not os.path.isfile("data/update2.html"): - print("Missing file: data/update2.html") - sys.exit(1) -if os.path.isdir("pack.tmp"): - shutil.rmtree('pack.tmp') -try: - filelist = [] - os.makedirs('pack.tmp/data') - # now gzip the stuff except zones.csv since this file is not served by mongoose but directly accessed: - for file in os.listdir("data"): - filename = os.fsdecode(file) - if filename == "zones.csv" or filename == "cert.pem" or filename == "key.pem" or filename == "rsa_key.pub": - shutil.copy('data/' + filename, 'pack.tmp/data/' + filename) - filelist.append('data/' + filename) - continue - else: - with open('data/' + filename, 'rb') as f_in, gzip.open('pack.tmp/data/' + filename + '.gz', 'wb') as f_out: - f_out.writelines(f_in) - filelist.append('data/' + filename + '.gz') - continue - os.chdir('pack.tmp') - cmdstring = 'python ../pack.py ' + ' '.join(filelist) - os.system(cmdstring + '>../src/packed_fs.c') - os.chdir('..') -except Exception as e: - print(f"An error occurred: {str(e)}") - sys.exit(100) -if shutil.rmtree("pack.tmp"): - print("Failed to clean up temporary files") - sys.exit(9) diff --git a/SmartEVSE-3/SmartEVSE-3/partitions_custom.csv.coredump b/SmartEVSE-3/SmartEVSE-3/partitions_custom.csv.coredump deleted file mode 100644 index 908b836..0000000 --- a/SmartEVSE-3/SmartEVSE-3/partitions_custom.csv.coredump +++ /dev/null @@ -1,8 +0,0 @@ -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x5000, -otadata, data, ota, 0xe000, 0x2000, -ota_0, app, ota_0, 0x10000, 0x1B0000, -ota_1, app, ota_1, 0x1c0000, 0x1B0000, -spiffs, data, spiffs, 0x370000, 0x80000, -coredump, data, coredump,0x3F0000, 64K -# Serialnr, per device key, (wifi) settings are stored in the nvs (arduino: preferences) partition diff --git a/SmartEVSE-3/SmartEVSE-3/platformio.ini b/SmartEVSE-3/SmartEVSE-3/platformio.ini deleted file mode 100644 index a480ac4..0000000 --- a/SmartEVSE-3/SmartEVSE-3/platformio.ini +++ /dev/null @@ -1,42 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[platformio] -description = SmartEVSE v3 (ESP32) -default_envs = release - -[env] -platform = espressif32 @ 6.6.0 -board = esp32dev -framework = arduino -monitor_port = /dev/ttyUSB0 -monitor_speed = 115200 -upload_speed = 2000000 -board_build.f_flash = 80000000L -board_build.flash_mode = dio -lib_ignore = - LittleFS_esp32 -lib_deps = - https://github.com/tzapu/WiFiManager.git - miq19/eModbus@1.7.1 - bblanchon/ArduinoJson@^6.21.4 -monitor_filters = esp32_exception_decoder -board_build.partitions = partitions_custom.csv -extra_scripts = pre:packfs.py - -[env:release] -build_flags = - -DCORE_DEBUG_LEVEL=5 - -DLOG_LEVEL=5 - -DMG_ENABLE_PACKED_FS=1 - -DMG_TLS=MG_TLS_MBED - -Wall - -Wextra - -Wunused-variable diff --git a/SmartEVSE-3/SmartEVSE-3/release b/SmartEVSE-3/SmartEVSE-3/release deleted file mode 120000 index 7c14c3a..0000000 --- a/SmartEVSE-3/SmartEVSE-3/release +++ /dev/null @@ -1 +0,0 @@ -.pio/build/release \ No newline at end of file diff --git a/SmartEVSE-3/SmartEVSE-3/src/CMakeLists.txt b/SmartEVSE-3/SmartEVSE-3/src/CMakeLists.txt deleted file mode 100644 index 483bc0c..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# This file was automatically generated for projects -# without default 'CMakeLists.txt' file. - -FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*) - -idf_component_register(SRCS ${app_sources}) diff --git a/SmartEVSE-3/SmartEVSE-3/src/OneWire.cpp b/SmartEVSE-3/SmartEVSE-3/src/OneWire.cpp deleted file mode 100644 index 1d5ffca..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/OneWire.cpp +++ /dev/null @@ -1,341 +0,0 @@ -/* -; Project: Smart EVSE -; -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. -*/ - -#include -#include -#include -#include -#include - -#include "evse.h" -#include "utils.h" -#include "OneWire.h" - -unsigned char RFID[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -unsigned char RFIDlist[600]; // holds up to 100 RFIDs - - -// ############################# OneWire functions ############################# - - - -// Reset 1-Wire device on SW input -// returns: 1 Device found -// 0 No device found -// 255 Error. Line is pulled low (short, or external button pressed?) -// -unsigned char OneWireReset(void) { - unsigned char r; - - if (digitalRead(PIN_SW_IN) == LOW) return 255; // Error, pulled low by external device? - - ONEWIRE_LOW; // Drive wire low - delayMicroseconds(480); - RTC_ENTER_CRITICAL(); // Disable interrupts - ONEWIRE_FLOATHIGH; // don't drive high, but use pullup - delayMicroseconds(70); - if (digitalRead(PIN_SW_IN) == HIGH) r = 0; // sample pin to see if there is a OneWire device.. - else r = 1; - RTC_EXIT_CRITICAL(); // Restore interrupts - delayMicroseconds(410); - return r; -} - -void OneWireWriteBit(unsigned char v) { - - if (v & 1) { // write a '1' - RTC_ENTER_CRITICAL(); // Disable interrupts - ONEWIRE_LOW; // Drive low - delayMicroseconds(10); - ONEWIRE_HIGH; // Drive high - RTC_EXIT_CRITICAL(); // Restore interrupts - delayMicroseconds(55); - } else { // write a '0' - RTC_ENTER_CRITICAL(); // Disable interrupts - ONEWIRE_LOW; // Drive low - delayMicroseconds(65); - ONEWIRE_HIGH; // Drive high - RTC_EXIT_CRITICAL(); // Restore interrupts - delayMicroseconds(5); - } -} - -unsigned char OneWireReadBit(void) { - unsigned char r; - - RTC_ENTER_CRITICAL(); // Disable interrupts - ONEWIRE_LOW; - delayMicroseconds(3); - ONEWIRE_FLOATHIGH; - delayMicroseconds(10); - if (digitalRead(PIN_SW_IN) == HIGH) r = 1; // sample pin - else r = 0; - RTC_EXIT_CRITICAL(); // Restore interrupts - delayMicroseconds(53); - return r; -} - -void OneWireWrite(unsigned char v) { - unsigned char bitmask; - for (bitmask = 0x01; bitmask ; bitmask <<= 1) { - OneWireWriteBit( (bitmask & v) ? 1u : 0); - } -} - -unsigned char OneWireRead(void) { - unsigned char bitmask, r = 0; - - for (bitmask = 0x01; bitmask ; bitmask <<= 1) { - if ( OneWireReadBit()) r |= bitmask; - } - return r; -} - -#if FAKE_RFID -unsigned char OneWireReadCardId(void) { - if (!RFIDstatus && Show_RFID) { //if ready to accept new card and it is shown - RFID[0] = 0x01; //Family Code - RFID[1] = 0x01; //RFID id = "01 02 03 04 05 06" - RFID[2] = 0x02; - RFID[3] = 0x03; - RFID[4] = 0x04; - RFID[5] = 0x05; - RFID[6] = 0x06; - RFID[7] = 0xf0; //crc8 code TODO is this ok? - Show_RFID = 0; //this makes "showing" the RFID a one shot event - return 1; - } - else - return 0; //card is already read, no new card -} - -#else -unsigned char OneWireReadCardId(void) { - unsigned char x; - - if (OneWireReset() == 1) { // RFID card detected - OneWireWrite(0x33); // OneWire ReadRom Command - for (x=0 ; x<8 ; x++) RFID[x] = OneWireRead(); // read Family code (0x01) RFID ID (6 bytes) and crc8 - if (crc8(RFID,8)) { - RFID[0] = 0; // CRC incorrect, clear first byte of RFID buffer - return 0; - } else { - for (x=1 ; x<7 ; x++) _LOG_A("%02x",RFID[x]); - _LOG_A("\r\n"); - return 1; - } - } - return 0; -} -#endif - - -// ############################## RFID functions ############################## - - - -// Write a list of 20 RFID's to the eeprom -// -void WriteRFIDlist(void) { - - if (preferences.begin("RFIDlist", false) ) { // read/write - preferences.putBytes("RFID", RFIDlist, 600); // write 600 bytes to storage - preferences.putUChar("RFIDinit", 2); // data initialized to 600byte mode - preferences.end(); - } else { - _LOG_A("Error opening preferences!\n"); - } - - - _LOG_I("\nRFID list saved\n"); -} - -// Read a list of 20 RFID's from preferences -// -void ReadRFIDlist(void) { - uint8_t initialized = 0; - - if (preferences.begin("RFIDlist", true) ) { // read only - initialized = preferences.getUChar("RFIDinit", 0); - switch (initialized) { - case 1: // we were initialized to old 120 bytes mode - preferences.getBytes("RFID", RFIDlist, 120); // read 120 bytes from storage - //we are now going to convert from RFIDlist 120bytes to RFIDlist 600bytes - for (int i = 120; i < 600; i++) RFIDlist[i] = 0xff; - preferences.remove("RFID"); - preferences.end(); - WriteRFIDlist(); - break; - case 0: - DeleteAllRFID(); // when unitialized, delete all cardIDs - setItemValue(MENU_RFIDREADER, 0); // RFID Reader Disabled - break; - case 2: // extended RFIDlist with room for 100tags of 6 bytes = 600 bytes - preferences.getBytes("RFID", RFIDlist, 600); // read 600 bytes from storage - preferences.end(); - break; - } - - } else { - _LOG_A("Error opening preferences!\n") ; - } -} - -// scan for matching RFID in RFIDlist -// returns offset+6 when found, 0 when not found -uint16_t MatchRFID(void) { - uint16_t offset = 0, r; - - do { - r = memcmp(RFID + 1, RFIDlist + offset, 6); // compare read RFID with list of stored RFID's - offset += 6; - } while (r !=0 && offset < 594); - - if (r == 0) return offset; // return offset + 6 in RFIDlist - else return 0; -} - - -// Store RFID card in memory and eeprom -// returns 1 when successful -// returns 2 when already stored -// returns 0 when all slots are full. -unsigned char StoreRFID(void) { - uint16_t offset = 0, r; - unsigned char empty[6] = {0xff,0xff,0xff,0xff,0xff,0xff}; - - // first check if the Card ID was already stored. - if ( MatchRFID() ) return 2; // already stored, that's ok. - - do { - r = memcmp(empty, RFIDlist + offset, 6); - offset += 6; - } while (r !=0 && offset < 600); - if (r != 0) return 0; // no more room to store RFID - offset -= 6; - _LOG_A("offset %u ",offset); - memcpy(RFIDlist + offset, RFID+1, 6); - - _LOG_I("\nRFIDlist:"); - for (r=0; r<600; r++) _LOG_I_NO_FUNC("%02x",RFIDlist[r]); - - WriteRFIDlist(); - return 1; -} - -//load and store parameter RFIDparm into global variable RFID -void LoadandStoreRFID(unsigned int *RFIDparam) { - for (int i = 0; i < 8; i++) - RFID[i]=RFIDparam[i]; - StoreRFID(); -} - -// Delete RFID card in memory and eeprom -// returns 1 when successful, 0 when RFID was not found -unsigned char DeleteRFID(void) { - uint16_t offset = 0, r; - - offset = MatchRFID(); // find RFID in list - if (offset) { - offset -= 6; - for (r = 0; r < 6; r++) RFIDlist[offset + r] = 0xff; - } else return 0; - - _LOG_A("deleted %u ",offset); - for (r=0; r<600; r++) _LOG_A("%02x",RFIDlist[r]); - - WriteRFIDlist(); - return 1; -} - -void DeleteAllRFID(void) { - uint16_t i; - - for (i = 0; i < 600; i++) RFIDlist[i] = 0xff; - WriteRFIDlist(); - _LOG_I("All RFID cards erased!\n"); -} - -void CheckRFID(void) { - uint16_t x; - // When RFID is enabled, a OneWire RFID reader is expected on the SW input - uint8_t RFIDReader = getItemValue(MENU_RFIDREADER); - if (RFIDReader) { // RFID Reader set to Enabled, Learn or Delete - if (OneWireReadCardId() ) { // Read card ID - switch (RFIDReader) { - case 1: // EnableAll. All learned cards accepted for locking /unlocking - x = MatchRFID(); - if (x && !RFIDstatus) { - _LOG_A("RFID card found!\n"); - if (Access_bit) { - setAccess(false); // Access Off, Switch back to state B1/C1 - } else setAccess(true); - - RFIDstatus = 1; - } else if (!x) RFIDstatus = 7; // invalid card - BacklightTimer = BACKLIGHT; - break; - case 2: // EnableOne. Only the card that unlocks, can re-lock the EVSE - x = MatchRFID(); - if (x && !RFIDstatus) { - _LOG_A("RFID card found!\n"); - if (!Access_bit) { - CardOffset = x; // store cardoffset from current card - setAccess(true); // Access On - } else if (CardOffset == x) { - setAccess(false); // Access Off, Switch back to state B1/C1 - } - RFIDstatus = 1; - } else if (!x) RFIDstatus = 7; // invalid card - BacklightTimer = BACKLIGHT; - break; - case 3: // Learn Card - x = StoreRFID(); - if (x == 1) { - _LOG_A("RFID card stored!\n"); - RFIDstatus = 2; - } else if (x == 2 && !RFIDstatus) { - _LOG_A("RFID card was already stored!\n"); - RFIDstatus = 4; - } else if (!RFIDstatus) { - _LOG_A("RFID storage full! Delete card first\n"); - RFIDstatus = 6; - } - break; - case 4: // Delete Card - x = DeleteRFID(); - if (x) { - _LOG_A("RFID card deleted!\n"); - RFIDstatus = 3; - } else if (!RFIDstatus) { - _LOG_A("RFID card not in list!\n"); - RFIDstatus = 5; - } - break; - default: - break; - } - } else RFIDstatus = 0; - } -} diff --git a/SmartEVSE-3/SmartEVSE-3/src/evse.cpp b/SmartEVSE-3/SmartEVSE-3/src/evse.cpp deleted file mode 100644 index 8faec49..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/evse.cpp +++ /dev/null @@ -1,5359 +0,0 @@ -#include -#include -#include -#include - -#include - -#include - -#include -#include "esp_ota_ops.h" - -#include -#include - -#include -#include - -#include -#include // Slave/node -#include // Master -#include - -#include -#include -#include -#include - -#include - -#include "evse.h" -#include "glcd.h" -#include "utils.h" -#include "OneWire.h" -#include "modbus.h" - -#ifndef DEBUG_DISABLED -RemoteDebug Debug; -#endif - -#define SNTP_GET_SERVERS_FROM_DHCP 1 -#include - -struct tm timeinfo; - -//mongoose stuff -#include "mongoose.h" -#include "esp_log.h" -struct mg_mgr mgr; // Mongoose event manager. Holds all connections -// end of mongoose stuff - -String APhostname = "SmartEVSE-" + String( MacId() & 0xffff, 10); // SmartEVSE access point Name = SmartEVSE-xxxxx - -#if MQTT -// MQTT connection info -String MQTTuser; -String MQTTpassword; -String MQTTprefix; -String MQTTHost = ""; -uint16_t MQTTPort; - -uint8_t lastMqttUpdate = 0; -#endif - -WiFiManager wifiManager; - -// SSID and PW for your Router -String Router_SSID; -String Router_Pass; - -// Create a ModbusRTU server, client and bridge instance on Serial1 -ModbusServerRTU MBserver(2000, PIN_RS485_DIR); // TCP timeout set to 2000 ms -ModbusClientRTU MBclient(PIN_RS485_DIR); - -hw_timer_t * timerA = NULL; -Preferences preferences; - -static esp_adc_cal_characteristics_t * adc_chars_CP; -static esp_adc_cal_characteristics_t * adc_chars_PP; -static esp_adc_cal_characteristics_t * adc_chars_Temperature; - -struct ModBus MB; // Used by SmartEVSE fuctions - -extern unsigned char RFID[8]; - -const char StrStateName[15][13] = {"A", "B", "C", "D", "COMM_B", "COMM_B_OK", "COMM_C", "COMM_C_OK", "Activate", "B1", "C1", "MODEM1", "MODEM2", "MODEM_OK", "MODEM_DENIED"}; -const char StrStateNameWeb[15][17] = {"Ready to Charge", "Connected to EV", "Charging", "D", "Request State B", "State B OK", "Request State C", "State C OK", "Activate", "Charging Stopped", "Stop Charging", "Modem Setup", "Modem Request", "Modem Done", "Modem Denied"}; -const char StrErrorNameWeb[9][20] = {"None", "No Power Available", "Communication Error", "Temperature High", "EV Meter Comm Error", "RCM Tripped", "Waiting for Solar", "Test IO", "Flash Error"}; -const char StrMode[3][8] = {"Normal", "Smart", "Solar"}; -const char StrAccessBit[2][6] = {"Deny", "Allow"}; -const char StrRFIDStatusWeb[8][20] = {"Ready to read card","Present", "Card Stored", "Card Deleted", "Card already stored", "Card not in storage", "Card Storage full", "Invalid" }; -bool shouldReboot = false; - -// Global data - - -// The following data will be updated by eeprom/storage data at powerup: -uint16_t MaxMains = MAX_MAINS; // Max Mains Amps (hard limit, limited by the MAINS connection) (A) -uint16_t MaxSumMains = MAX_SUMMAINS; // Max Mains Amps summed over all 3 phases, limit used by EU capacity rate - // see https://github.com/serkri/SmartEVSE-3/issues/215 -uint16_t MaxCurrent = MAX_CURRENT; // Max Charge current (A) -uint16_t MinCurrent = MIN_CURRENT; // Minimal current the EV is happy with (A) -uint16_t ICal = ICAL; // CT calibration value -uint8_t Mode = MODE; // EVSE mode (0:Normal / 1:Smart / 2:Solar) -uint32_t CurrentPWM = 0; // Current PWM duty cycle value (0 - 1024) -int8_t InitialSoC = -1; // State of charge of car -int8_t FullSoC = -1; // SoC car considers itself fully charged -int8_t ComputedSoC = -1; // Estimated SoC, based on charged kWh -int8_t RemainingSoC = -1; // Remaining SoC, based on ComputedSoC -int32_t TimeUntilFull = -1; // Remaining time until car reaches FullSoC, in seconds -int32_t EnergyCapacity = -1; // Car's total battery capacity -int32_t EnergyRequest = -1; // Requested amount of energy by car -char EVCCID[32]; // Car's EVCCID (EV Communication Controller Identifer) -char RequiredEVCCID[32]; // Required EVCCID before allowing charging - -bool CPDutyOverride = false; -uint8_t Lock = LOCK; // Cable lock (0:Disable / 1:Solenoid / 2:Motor) -uint16_t MaxCircuit = MAX_CIRCUIT; // Max current of the EVSE circuit (A) -uint8_t Config = CONFIG; // Configuration (0:Socket / 1:Fixed Cable) -uint8_t LoadBl = LOADBL; // Load Balance Setting (0:Disable / 1:Master / 2-8:Node) -uint8_t Switch = SWITCH; // External Switch (0:Disable / 1:Access B / 2:Access S / 3:Smart-Solar B / 4:Smart-Solar S) - // B=momentary push utton, S=toggle witch -uint8_t RCmon = RC_MON; // Residual Current Monitor (0:Disable / 1:Enable) -uint16_t StartCurrent = START_CURRENT; -uint16_t StopTime = STOP_TIME; -uint16_t ImportCurrent = IMPORT_CURRENT; -struct DelayedTimeStruct DelayedStartTime; -struct DelayedTimeStruct DelayedStopTime; -uint8_t DelayedRepeat; // 0 = no repeat, 1 = daily repeat -uint8_t MainsMeter = MAINS_METER; // Type of Mains electric meter (0: Disabled / Constants EM_*) -uint8_t MainsMeterAddress = MAINS_METER_ADDRESS; -uint8_t Grid = GRID; // Type of Grid connected to Sensorbox (0:4Wire / 1:3Wire ) -uint8_t EVMeter = EV_METER; // Type of EV electric meter (0: Disabled / Constants EM_*) -uint8_t EVMeterAddress = EV_METER_ADDRESS; -uint8_t RFIDReader = RFID_READER; // RFID Reader (0:Disabled / 1:Enabled / 2:Enable One / 3:Learn / 4:Delete / 5:Delete All) -#if FAKE_RFID -uint8_t Show_RFID = 0; -#endif -uint8_t WIFImode = WIFI_MODE; // WiFi Mode (0:Disabled / 1:Enabled / 2:Start Portal) -String APpassword = "00000000"; -uint8_t Initialized = INITIALIZED; // When first powered on, the settings need to be initialized. -String TZinfo = ""; // contains POSIX time string - -EnableC2_t EnableC2 = ENABLE_C2; // Contactor C2 -Modem_t Modem = NOTPRESENT; // Is an ISO15118 modem installed (experimental) -uint16_t maxTemp = MAX_TEMPERATURE; - -int16_t Irms[3]={0, 0, 0}; // Momentary current per Phase (23 = 2.3A) (resolution 100mA) -int16_t Irms_EV[3]={0, 0, 0}; // Momentary current per Phase (23 = 2.3A) (resolution 100mA) -uint8_t Nr_Of_Phases_Charging = 0; // 0 = Undetected, 1,2,3 = nr of phases that was detected at the start of this charging session -Single_Phase_t Switching_To_Single_Phase = FALSE; - -uint8_t State = STATE_A; -uint8_t ErrorFlags = NO_ERROR; -uint8_t NextState; -uint8_t pilot; -uint8_t prev_pilot; - -uint16_t MaxCapacity; // Cable limit (A) (limited by the wire in the charge cable, set automatically, or manually if Config=Fixed Cable) -uint16_t ChargeCurrent; // Calculated Charge Current (Amps *10) -uint16_t OverrideCurrent = 0; // Temporary assigned current (Amps *10) (modbus) -int16_t Imeasured = 0; // Max of all Phases (Amps *10) of mains power -int16_t Imeasured_EV = 0; // Max of all Phases (Amps *10) of EV power -int16_t Isum = 0; // Sum of all measured Phases (Amps *10) (can be negative) - -// Load Balance variables -int16_t IsetBalanced = 0; // Max calculated current (Amps *10) available for all EVSE's -uint16_t Balanced[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // Amps value per EVSE -uint16_t BalancedMax[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // Max Amps value per EVSE -uint8_t BalancedState[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // State of all EVSE's 0=not active (state A), 1=charge request (State B), 2= Charging (State C) -uint16_t BalancedError[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // Error state of EVSE - -struct { - uint8_t Online; - uint8_t ConfigChanged; - uint8_t EVMeter; - uint8_t EVAddress; - uint8_t MinCurrent; // 0.1A - uint8_t Phases; - uint32_t Timer; // 1s - uint32_t IntTimer; // 1s - uint16_t SolarTimer; // 1s - uint8_t Mode; -} Node[NR_EVSES] = { // 0: Master / 1: Node 1 ... - /* Config EV EV Min Used Charge Interval Solar * // Interval Time : last Charge time, reset when not charging - * Online, Changed, Meter, Address, Current, Phases, Timer, Timer, Timer, Mode */ // Min Current : minimal measured current per phase the EV consumes when starting to charge @ 6A (can be lower then 6A) - { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Used Phases : detected nr of phases when starting to charge (works with configured EVmeter meter, and might work with sensorbox) - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 } -}; - -uint8_t lock1 = 0, lock2 = 1; -uint8_t MainsMeterTimeout = COMM_TIMEOUT; // MainsMeter communication timeout (sec) -uint8_t EVMeterTimeout = COMM_EVTIMEOUT; // EV Meter communication Timeout (sec) -uint16_t BacklightTimer = 0; // Backlight timer (sec) -uint8_t BacklightSet = 0; -uint8_t LCDTimer = 0; -uint8_t AccessTimer = 0; -int8_t TempEVSE = 0; // Temperature EVSE in deg C (-50 to +125) -uint8_t ButtonState = 0x0f; // Holds latest push Buttons state (LSB 3:0) -uint8_t OldButtonState = 0x0f; // Holds previous push Buttons state (LSB 3:0) -uint8_t LCDNav = 0; -uint8_t SubMenu = 0; -uint32_t ScrollTimer = 0; -uint8_t LCDupdate = 0; // flag to update the LCD every 1000ms -uint8_t ChargeDelay = 0; // Delays charging at least 60 seconds in case of not enough current available. -uint8_t C1Timer = 0; -uint8_t ModemStage = 0; // 0: Modem states will be executed when Modem is enabled 1: Modem stages will be skipped, as SoC is already extracted -int8_t DisconnectTimeCounter = -1; // Count for how long we're disconnected, so we can more reliably throw disconnect event. -1 means counter is disabled -uint8_t ToModemWaitStateTimer = 0; // Timer used from STATE_MODEM_REQUEST to STATE_MODEM_WAIT -uint8_t ToModemDoneStateTimer = 0; // Timer used from STATE_MODEM_WAIT to STATE_MODEM_DONE -uint8_t LeaveModemDoneStateTimer = 0; // Timer used from STATE_MODEM_DONE to other, usually STATE_B -uint8_t LeaveModemDeniedStateTimer = 0; // Timer used from STATE_MODEM_DENIED to STATE_B to re-try authentication -uint8_t NoCurrent = 0; // counts overcurrent situations. -uint8_t TestState = 0; -uint8_t ModbusRequest = 0; // Flag to request Modbus information -uint8_t NodeNewMode = 0; -uint8_t MenuItems[MENU_EXIT]; -uint8_t Access_bit = 0; // 0:No Access 1:Access to SmartEVSE -uint16_t CardOffset = CARD_OFFSET; // RFID card used in Enable One mode - -uint8_t ConfigChanged = 0; -uint32_t serialnr = 0; -uint8_t GridActive = 0; // When the CT's are used on Sensorbox2, it enables the GRID menu option. -uint8_t CalActive = 0; // When the CT's are used on Sensorbox(1.5 or 2), it enables the CAL menu option. -uint16_t Iuncal = 0; // Uncalibrated CT1 measurement (resolution 10mA) - -uint16_t SolarStopTimer = 0; -int32_t EnergyCharged = 0; // kWh meter value energy charged. (Wh) (will reset if state changes from A->B) -int32_t EnergyMeterStart = 0; // kWh meter value is stored once EV is connected to EVSE (Wh) -int16_t PowerMeasured = 0; // Measured Charge power in Watt by kWh meter -uint8_t RFIDstatus = 0; -bool PilotDisconnected = false; -uint8_t PilotDisconnectTime = 0; // Time the Control Pilot line should be disconnected (Sec) - -int32_t EnergyEV = 0; // Wh -> EV_import_active_energy - EV_export_active_energy -int32_t Mains_export_active_energy = 0; // Mainsmeter exported active energy, only for API purposes so you can guard the - // enery usage of your house -int32_t Mains_import_active_energy = 0; // Mainsmeter imported active energy, only for API purposes so you can guard the - // enery usage of your house -int32_t EV_export_active_energy = 0; -int32_t EV_import_active_energy = 0; -int32_t CM[3]={0, 0, 0}; -uint8_t ResetKwh = 2; // if set, reset EV kwh meter at state transition B->C - // cleared when charging, reset to 1 when disconnected (state A) -uint8_t ActivationMode = 0, ActivationTimer = 0; -volatile uint16_t adcsample = 0; -volatile uint16_t ADCsamples[25]; // declared volatile, as they are used in a ISR -volatile uint8_t sampleidx = 0; -char str[20]; -bool LocalTimeSet = false; - -int phasesLastUpdate = 0; -bool phasesLastUpdateFlag = false; -int16_t IrmsOriginal[3]={0, 0, 0}; -int homeBatteryCurrent = 0; -int homeBatteryLastUpdate = 0; // Time in milliseconds -char *downloadUrl = NULL; -int downloadProgress = 0; -int downloadSize = 0; - - -struct EMstruct EMConfig[EM_CUSTOM + 1] = { - /* DESC, ENDIANNESS, FCT, DATATYPE, U_REG,DIV, I_REG,DIV, P_REG,DIV, E_REG_IMP,DIV, E_REG_EXP, DIV */ - {"Disabled", ENDIANESS_LBF_LWF, 0, MB_DATATYPE_INT32, 0, 0, 0, 0, 0, 0, 0, 0,0 , 0}, // First entry! - {"Sensorbox", ENDIANESS_HBF_HWF, 4, MB_DATATYPE_FLOAT32, 0xFFFF, 0, 0, 0, 0xFFFF, 0, 0xFFFF, 0,0 , 0}, // Sensorbox (Own routine for request/receive) - {"Phoenix C", ENDIANESS_HBF_LWF, 4, MB_DATATYPE_INT32, 0x0, 1, 0xC, 3, 0x28, 1, 0x3E, 1,0 , 0}, // PHOENIX CONTACT EEM-350-D-MCB (0,1V / mA / 0,1W / 0,1kWh) max read count 11 - {"Finder 7E", ENDIANESS_HBF_HWF, 4, MB_DATATYPE_FLOAT32, 0x1000, 0, 0x100E, 0, 0x1026, 0, 0x1106, 3,0x110E, 3}, // Finder 7E.78.8.400.0212 (V / A / W / Wh) max read count 127 - {"Eastron3P", ENDIANESS_HBF_HWF, 4, MB_DATATYPE_FLOAT32, 0x0, 0, 0x6, 0, 0x34, 0, 0x48 , 0,0x4A , 0}, // Eastron SDM630 (V / A / W / kWh) max read count 80 - {"InvEastrn", ENDIANESS_HBF_HWF, 4, MB_DATATYPE_FLOAT32, 0x0, 0, 0x6, 0, 0x34, 0, 0x48 , 0,0x4A , 0}, // Since Eastron SDM series are bidirectional, sometimes they are connected upsidedown, so positive current becomes negative etc.; Eastron SDM630 (V / A / W / kWh) max read count 80 - {"ABB", ENDIANESS_HBF_HWF, 3, MB_DATATYPE_INT32, 0x5B00, 1, 0x5B0C, 2, 0x5B14, 2, 0x5000, 2,0x5004, 2}, // ABB B23 212-100 (0.1V / 0.01A / 0.01W / 0.01kWh) RS485 wiring reversed / max read count 125 - {"SolarEdge", ENDIANESS_HBF_HWF, 3, MB_DATATYPE_INT16, 40196, 0, 40191, 0, 40206, 0, 40234, 3, 40226, 3}, // SolarEdge SunSpec (0.01V (16bit) / 0.1A (16bit) / 1W (16bit) / 1 Wh (32bit)) - {"WAGO", ENDIANESS_HBF_HWF, 3, MB_DATATYPE_FLOAT32, 0x5002, 0, 0x500C, 0, 0x5012, -3, 0x600C, 0,0x6018, 0}, // WAGO 879-30x0 (V / A / kW / kWh)//TODO maar WAGO heeft ook totaal - {"API", ENDIANESS_HBF_HWF, 3, MB_DATATYPE_FLOAT32, 0x5002, 0, 0x500C, 0, 0x5012, 3, 0x6000, 0,0x6018, 0}, // WAGO 879-30x0 (V / A / kW / kWh) - {"Eastron1P", ENDIANESS_HBF_HWF, 4, MB_DATATYPE_FLOAT32, 0x0, 0, 0x6, 0, 0x0C, 0, 0x48 , 0,0x4A , 0}, // Eastron SDM630 (V / A / W / kWh) max read count 80 - {"Finder 7M", ENDIANESS_HBF_HWF, 4, MB_DATATYPE_FLOAT32, 2500, 0, 2516, 0, 2536, 0, 2638, 3, 0, 0}, // Finder 7M.38.8.400.0212 (V / A / W / Wh) / Backlight 10173 - {"Unused 1", ENDIANESS_LBF_LWF, 4, MB_DATATYPE_INT32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // unused slot for future new meters - {"Unused 2", ENDIANESS_LBF_LWF, 4, MB_DATATYPE_INT32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // unused slot for future new meters - {"Unused 3", ENDIANESS_LBF_LWF, 4, MB_DATATYPE_INT32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // unused slot for future new meters - {"Unused 4", ENDIANESS_LBF_LWF, 4, MB_DATATYPE_INT32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // unused slot for future new meters - {"Custom", ENDIANESS_LBF_LWF, 4, MB_DATATYPE_INT32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Last entry! -}; - - -// Some low level stuff here to setup the ADC, and perform the conversion. -// -// -uint16_t IRAM_ATTR local_adc1_read(int channel) { - uint16_t adc_value; - - SENS.sar_read_ctrl.sar1_dig_force = 0; // switch SARADC into RTC channel - SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PU; // adc_power_on - RTCIO.hall_sens.xpd_hall = false; // disable other peripherals - - //adc_ll_amp_disable() // Close ADC AMP module if don't use it for power save. - SENS.sar_meas_wait2.force_xpd_amp = SENS_FORCE_XPD_AMP_PD; // channel is set in the convert function - // disable FSM, it's only used by the LNA. - SENS.sar_meas_ctrl.amp_rst_fb_fsm = 0; - SENS.sar_meas_ctrl.amp_short_ref_fsm = 0; - SENS.sar_meas_ctrl.amp_short_ref_gnd_fsm = 0; - SENS.sar_meas_wait1.sar_amp_wait1 = 1; - SENS.sar_meas_wait1.sar_amp_wait2 = 1; - SENS.sar_meas_wait2.sar_amp_wait3 = 1; - - // adc_hal_set_controller(ADC_NUM_1, ADC_CTRL_RTC); //Set controller - // see esp-idf/components/hal/esp32/include/hal/adc_ll.h - SENS.sar_read_ctrl.sar1_dig_force = 0; // 1: Select digital control; 0: Select RTC control. - SENS.sar_meas_start1.meas1_start_force = 1; // 1: SW control RTC ADC start; 0: ULP control RTC ADC start. - SENS.sar_meas_start1.sar1_en_pad_force = 1; // 1: SW control RTC ADC bit map; 0: ULP control RTC ADC bit map; - SENS.sar_touch_ctrl1.xpd_hall_force = 1; // 1: SW control HALL power; 0: ULP FSM control HALL power. - SENS.sar_touch_ctrl1.hall_phase_force = 1; // 1: SW control HALL phase; 0: ULP FSM control HALL phase. - - // adc_hal_convert(ADC_NUM_1, channel, &adc_value); - // see esp-idf/components/hal/esp32/include/hal/adc_ll.h - SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // select ADC channel to sample on - while (SENS.sar_slave_addr1.meas_status != 0); // wait for conversion to be idle (blocking) - SENS.sar_meas_start1.meas1_start_sar = 0; - SENS.sar_meas_start1.meas1_start_sar = 1; // start ADC conversion - while (SENS.sar_meas_start1.meas1_done_sar == 0); // wait (blocking) for conversion to finish - adc_value = SENS.sar_meas_start1.meas1_data_sar; // read ADC value from register - - return adc_value; -} - - - -// CP pin low to high transition ISR -// -// -void IRAM_ATTR onCPpulse() { - - // reset timer, these functions are in IRAM ! - timerWrite(timerA, 0); - timerAlarmEnable(timerA); -} - - - -// Timer interrupt handler -// in STATE A this is called every 1ms (autoreload) -// in STATE B/C there is a PWM signal, and the Alarm is set to 5% after the low-> high transition of the PWM signal -void IRAM_ATTR onTimerA() { - - RTC_ENTER_CRITICAL(); - adcsample = local_adc1_read(ADC1_CHANNEL_3); - - RTC_EXIT_CRITICAL(); - - ADCsamples[sampleidx++] = adcsample; - if (sampleidx == 25) sampleidx = 0; -} - - -// --------------------------- END of ISR's ----------------------------------------------------- - -// Blink the RGB LED and LCD Backlight. -// -// NOTE: need to add multiple colour schemes -// -// Task is called every 10ms -void BlinkLed(void * parameter) { - uint8_t LcdPwm = 0; - uint8_t RedPwm = 0, GreenPwm = 0, BluePwm = 0; - uint8_t LedCount = 0; // Raw Counter before being converted to PWM value - unsigned int LedPwm = 0; // PWM value 0-255 - - while(1) - { - // Backlight LCD - if (BacklightTimer > 1 && BacklightSet != 1) { // Enable LCD backlight at max brightness - // start only when fully off(0) or when we are dimming the backlight(2) - LcdPwm = LCD_BRIGHTNESS; - ledcWrite(LCD_CHANNEL, LcdPwm); - BacklightSet = 1; // 1: we have set the backlight to max brightness - } - - if (BacklightTimer == 1 && LcdPwm >= 3) { // Last second of Backlight - LcdPwm -= 3; - ledcWrite(LCD_CHANNEL, ease8InOutQuad(LcdPwm)); // fade out - BacklightSet = 2; // 2: we are dimming the backlight - } - // Note: could be simplified by removing following code if LCD_BRIGHTNESS is multiple of 3 - if (BacklightTimer == 0 && BacklightSet) { // End of LCD backlight - ledcWrite(LCD_CHANNEL, 0); // switch off LED PWM - BacklightSet = 0; // 0: backlight fully off - } - - // RGB LED - if (ErrorFlags || ChargeDelay) { - - if (ErrorFlags & (RCM_TRIPPED | CT_NOCOMM | EV_NOCOMM) ) { - LedCount += 20; // Very rapid flashing, RCD tripped or no Serial Communication. - if (LedCount > 128) LedPwm = ERROR_LED_BRIGHTNESS; // Red LED 50% of time on, full brightness - else LedPwm = 0; - RedPwm = LedPwm; - GreenPwm = 0; - BluePwm = 0; - } else { // Waiting for Solar power or not enough current to start charging - LedCount += 2; // Slow blinking. - if (LedCount > 230) LedPwm = WAITING_LED_BRIGHTNESS; // LED 10% of time on, full brightness - else LedPwm = 0; - - if (Mode == MODE_SOLAR) { // Orange - RedPwm = LedPwm; - GreenPwm = LedPwm * 2 / 3; - } else { // Green - RedPwm = 0; - GreenPwm = LedPwm; - } - BluePwm = 0; - } - - } else if (Access_bit == 0 || State == STATE_MODEM_DENIED) { // No Access, LEDs off - RedPwm = 0; - GreenPwm = 0; - BluePwm = 0; - LedPwm = 0; - } else { // State A, B or C - - if (State == STATE_A) { - LedPwm = STATE_A_LED_BRIGHTNESS; // STATE A, LED on (dimmed) - - } else if (State == STATE_B || State == STATE_B1 || State == STATE_MODEM_REQUEST || State == STATE_MODEM_WAIT) { - LedPwm = STATE_B_LED_BRIGHTNESS; // STATE B, LED on (full brightness) - LedCount = 128; // When switching to STATE C, start at full brightness - - } else if (State == STATE_C) { - if (Mode == MODE_SOLAR) LedCount ++; // Slower fading (Solar mode) - else LedCount += 2; // Faster fading (Smart mode) - LedPwm = ease8InOutQuad(triwave8(LedCount)); // pre calculate new LedPwm value - } - - if (Mode == MODE_SOLAR) { // Orange/Yellow for Solar mode - RedPwm = LedPwm; - GreenPwm = LedPwm * 2 / 3; - } else { - RedPwm = 0; // Green for Normal/Smart mode - GreenPwm = LedPwm; - } - BluePwm = 0; - - } - ledcWrite(RED_CHANNEL, RedPwm); - ledcWrite(GREEN_CHANNEL, GreenPwm); - ledcWrite(BLUE_CHANNEL, BluePwm); - - // Pause the task for 10ms - vTaskDelay(10 / portTICK_PERIOD_MS); - } // while(1) loop -} - - -// Set Charge Current -// Current in Amps * 10 (160 = 16A) -void SetCurrent(uint16_t current) { - uint32_t DutyCycle; - - if ((current >= (MIN_CURRENT * 10)) && (current <= 510)) DutyCycle = current / 0.6; - // calculate DutyCycle from current - else if ((current > 510) && (current <= 800)) DutyCycle = (current / 2.5) + 640; - else DutyCycle = 100; // invalid, use 6A - DutyCycle = DutyCycle * 1024 / 1000; // conversion to 1024 = 100% - SetCPDuty(DutyCycle); -} - -// Write duty cycle to pin -// Value in range 0 (0% duty) to 1024 (100% duty) -void SetCPDuty(uint32_t DutyCycle){ - ledcWrite(CP_CHANNEL, DutyCycle); // update PWM signal - CurrentPWM = DutyCycle; -} - - -// Sample the Temperature sensor. -// -signed char TemperatureSensor() { - uint32_t sample, voltage; - signed char Temperature; - - RTC_ENTER_CRITICAL(); - // Sample Temperature Sensor - sample = local_adc1_read(ADC1_CHANNEL_0); - RTC_EXIT_CRITICAL(); - - // voltage range is from 0-2200mV - voltage = esp_adc_cal_raw_to_voltage(sample, adc_chars_Temperature); - - // The MCP9700A temperature sensor outputs 500mV at 0C, and has a 10mV/C change in output voltage. - // so 750mV is 25C, 400mV = -10C - Temperature = (signed int)(voltage - 500)/10; - //_LOG_A("\nTemp: %i C (%u mV) ", Temperature , voltage); - - return Temperature; -} - - -// Sample the Proximity Pin, and determine the maximum current the cable can handle. -// -void ProximityPin() { - uint32_t sample, voltage; - - RTC_ENTER_CRITICAL(); - // Sample Proximity Pilot (PP) - sample = local_adc1_read(ADC1_CHANNEL_6); - RTC_EXIT_CRITICAL(); - - voltage = esp_adc_cal_raw_to_voltage(sample, adc_chars_PP); - - if (!Config) { // Configuration (0:Socket / 1:Fixed Cable) - //socket - _LOG_A("PP pin: %u (%u mV)\n", sample, voltage); - } else { - //fixed cable - _LOG_A("PP pin: %u (%u mV) (warning: fixed cable configured so PP probably disconnected, making this reading void)\n", sample, voltage); - } - - MaxCapacity = 13; // No resistor, Max cable current = 13A - if ((voltage > 1200) && (voltage < 1400)) MaxCapacity = 16; // Max cable current = 16A 680R -> should be around 1.3V - if ((voltage > 500) && (voltage < 700)) MaxCapacity = 32; // Max cable current = 32A 220R -> should be around 0.6V - if ((voltage > 200) && (voltage < 400)) MaxCapacity = 63; // Max cable current = 63A 100R -> should be around 0.3V - - if (Config) MaxCapacity = MaxCurrent; // Override with MaxCurrent when Fixed Cable is used. -} - - -// Determine the state of the Pilot signal -// -uint8_t Pilot() { - - uint32_t sample, Min = 3300, Max = 0; - uint32_t voltage; - uint8_t n; - - // calculate Min/Max of last 25 CP measurements - for (n=0 ; n<25 ;n++) { - sample = ADCsamples[n]; - voltage = esp_adc_cal_raw_to_voltage( sample, adc_chars_CP); // convert adc reading to voltage - if (voltage < Min) Min = voltage; // store lowest value - if (voltage > Max) Max = voltage; // store highest value - } - //_LOG_A("min:%u max:%u\n",Min ,Max); - - // test Min/Max against fixed levels - if (Min >= 3055 ) return PILOT_12V; // Pilot at 12V (min 11.0V) - if ((Min >= 2735) && (Max < 3055)) return PILOT_9V; // Pilot at 9V - if ((Min >= 2400) && (Max < 2735)) return PILOT_6V; // Pilot at 6V - if ((Min >= 2000) && (Max < 2400)) return PILOT_3V; // Pilot at 3V - if ((Min > 100) && (Max < 300)) return PILOT_DIODE; // Diode Check OK - return PILOT_NOK; // Pilot NOT ok -} - - -/** - * Get name of a state - * - * @param uint8_t State - * @return uint8_t[] Name - */ -const char * getStateName(uint8_t StateCode) { - if(StateCode < 15) return StrStateName[StateCode]; - else return "NOSTATE"; -} - - -const char * getStateNameWeb(uint8_t StateCode) { - if(StateCode < 15) return StrStateNameWeb[StateCode]; - else return "NOSTATE"; -} - - -uint8_t getErrorId(uint8_t ErrorCode) { - uint8_t count = 0; - //find the error bit that is set - while (ErrorCode) { - count++; - ErrorCode = ErrorCode >> 1; - } - return count; -} - - -const char * getErrorNameWeb(uint8_t ErrorCode) { - uint8_t count = 0; - count = getErrorId(ErrorCode); - if(count < 9) return StrErrorNameWeb[count]; - else return "Multiple Errors"; -} - -/** - * Set EVSE mode - * - * @param uint8_t Mode - */ -void setMode(uint8_t NewMode) { - // If mainsmeter disabled we can only run in Normal Mode - if (!MainsMeter && NewMode != MODE_NORMAL) - return; - - // Take care of extra conditionals/checks for custom features - setAccess(!DelayedStartTime.epoch2); //if DelayedStartTime not zero then we are Delayed Charging - if (NewMode == MODE_SOLAR) { - // Reset OverrideCurrent if mode is SOLAR - OverrideCurrent = 0; - } - - // when switching modes, we just keep charging at the phases we were charging at; - // it's only the regulation algorithm that is changing... - // EXCEPT when EnableC2 == Solar Off, because we would expect C2 to be off when in Solar Mode and EnableC2 == Solar Off - // and also the other way around, multiple phases might be wanted when changing from Solar to Normal or Smart - bool switchOnLater = false; - if (EnableC2 == SOLAR_OFF) { - if ((Mode != MODE_SOLAR && NewMode == MODE_SOLAR) || (Mode == MODE_SOLAR && NewMode != MODE_SOLAR)) { - //we are switching from non-solar to solar - //since we EnableC2 == SOLAR_OFF C2 is turned On now, and should be turned off - setAccess(0); //switch to OFF - switchOnLater = true; - } - } - -#if MQTT - // Update MQTT faster - lastMqttUpdate = 10; -#endif - - if (NewMode == MODE_SMART) { - ErrorFlags &= ~(NO_SUN | LESS_6A); // Clear All errors - setSolarStopTimer(0); // Also make sure the SolarTimer is disabled. - } - ChargeDelay = 0; // Clear any Chargedelay - BacklightTimer = BACKLIGHT; // Backlight ON - if (Mode != NewMode) NodeNewMode = NewMode + 1; - Mode = NewMode; - - if (switchOnLater) - setAccess(1); - - //make mode and start/stoptimes persistent on reboot - if (preferences.begin("settings", false) ) { //false = write mode - preferences.putUChar("Mode", Mode); - preferences.putULong("DelayedStartTim", DelayedStartTime.epoch2); //epoch2 only needs 4 bytes - preferences.putULong("DelayedStopTime", DelayedStopTime.epoch2); //epoch2 only needs 4 bytes - preferences.end(); - } -} -/** - * Set the solar stop timer - * - * @param unsigned int Timer (seconds) - */ -void setSolarStopTimer(uint16_t Timer) { - SolarStopTimer = Timer; -} - -/** - * Checks all parameters to determine whether - * we are going to force single phase charging - * Returns true if we are going to do single phase charging - * Returns false if we are going to do (traditional) 3 phase charing - * This is only relevant on a 3f mains and 3f car installation! - * 1f car will always charge 1f undetermined by CONTACTOR2 - */ -uint8_t Force_Single_Phase_Charging() { // abbreviated to FSPC - switch (EnableC2) { - case NOT_PRESENT: //no use trying to switch a contactor on that is not present - case ALWAYS_OFF: - return 1; - case SOLAR_OFF: - return (Mode == MODE_SOLAR); - case AUTO: - case ALWAYS_ON: - return 0; //3f charging - } - //in case we don't know, stick to 3f charging - return 0; -} - -void setStatePowerUnavailable(void) { - if (State == STATE_A) - return; - //State changes between A,B,C,D are caused by EV or by the user - //State changes between x1 and x2 are created by the EVSE - //State changes between x1 and x2 indicate availability (x2) of unavailability (x1) of power supply to the EV - if (State == STATE_C) setState(STATE_C1); // If we are charging, tell EV to stop charging - else if (State != STATE_C1) setState(STATE_B1); // If we are not in State C1, switch to State B1 -} - -void setState(uint8_t NewState) { - if (State != NewState) { - char Str[50]; - snprintf(Str, sizeof(Str), "%02d:%02d:%02d STATE %s -> %s\n",timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, getStateName(State), getStateName(NewState) ); - - _LOG_A("%s",Str); - } - - switch (NewState) { - case STATE_B1: - if (!ChargeDelay) ChargeDelay = 3; // When entering State B1, wait at least 3 seconds before switching to another state. - if (State != STATE_B1 && State != STATE_B && !PilotDisconnected) { - PILOT_DISCONNECTED; - PilotDisconnected = true; - PilotDisconnectTime = 5; // Set PilotDisconnectTime to 5 seconds - - _LOG_A("Pilot Disconnected\n"); - } - // fall through - case STATE_A: // State A1 - CONTACTOR1_OFF; - CONTACTOR2_OFF; - SetCPDuty(1024); // PWM off, channel 0, duty cycle 100% - timerAlarmWrite(timerA, PWM_100, true); // Alarm every 1ms, auto reload - if (NewState == STATE_A) { - ErrorFlags &= ~NO_SUN; - ErrorFlags &= ~LESS_6A; - ChargeDelay = 0; - // Reset Node - Node[0].Timer = 0; - Node[0].IntTimer = 0; - Node[0].Phases = 0; - Node[0].MinCurrent = 0; // Clear ChargeDelay when disconnected. - } - -#if MODEM - if (DisconnectTimeCounter == -1){ - DisconnectTimeCounter = 0; // Start counting disconnect time. If longer than 60 seconds, throw DisconnectEvent - } - break; - case STATE_MODEM_REQUEST: // After overriding PWM, and resetting the safe state is 10% PWM. To make sure communication recovers after going to normal, we do this. Ugly and temporary - ToModemWaitStateTimer = 5; - DisconnectTimeCounter = -1; // Disable Disconnect timer. Car is connected - SetCPDuty(1024); - CONTACTOR1_OFF; - CONTACTOR2_OFF; - break; - case STATE_MODEM_WAIT: - SetCPDuty(50); - ToModemDoneStateTimer = 60; - break; - case STATE_MODEM_DONE: // This state is reached via STATE_MODEM_WAIT after 60s (timeout condition, nothing received) or after REST request (success, shortcut to immediate charging). - CP_OFF; - DisconnectTimeCounter = -1; // Disable Disconnect timer. Car is connected - LeaveModemDoneStateTimer = 5; // Disconnect CP for 5 seconds, restart charging cycle but this time without the modem steps. -#endif - break; - case STATE_B: - if (Modem) - CP_ON; - CONTACTOR1_OFF; - CONTACTOR2_OFF; - if (Modem) - DisconnectTimeCounter = -1; // Disable Disconnect timer. Car is connected - timerAlarmWrite(timerA, PWM_95, false); // Enable Timer alarm, set to diode test (95%) - SetCurrent(ChargeCurrent); // Enable PWM - break; - case STATE_C: // State C2 - ActivationMode = 255; // Disable ActivationMode - - if (Switching_To_Single_Phase == GOING_TO_SWITCH) { - CONTACTOR2_OFF; - setSolarStopTimer(0); //TODO still needed? now we switched contactor2 off, review if we need to stop solar charging - //Nr_Of_Phases_Charging = 1; this will be detected automatically - Switching_To_Single_Phase = AFTER_SWITCH; // we finished the switching process, - // BUT we don't know which is the single phase - } - - CONTACTOR1_ON; - if (!Force_Single_Phase_Charging() && Switching_To_Single_Phase != AFTER_SWITCH) { // in AUTO mode we start with 3phases - CONTACTOR2_ON; // Contactor2 ON - } - LCDTimer = 0; - break; - case STATE_C1: - SetCPDuty(1024); // PWM off, channel 0, duty cycle 100% - timerAlarmWrite(timerA, PWM_100, true); // Alarm every 1ms, auto reload - // EV should detect and stop charging within 3 seconds - C1Timer = 6; // Wait maximum 6 seconds, before forcing the contactor off. - ChargeDelay = 15; - break; - default: - break; - } - - BalancedState[0] = NewState; - State = NewState; - -#if MQTT - // Update MQTT faster - lastMqttUpdate = 10; -#endif - - // BacklightTimer = BACKLIGHT; // Backlight ON -} - -void setAccess(bool Access) { - Access_bit = Access; - if (Access == 0) { - //TODO:setStatePowerUnavailable() ? - if (State == STATE_C) setState(STATE_C1); // Determine where to switch to. - else if (State != STATE_C1 && (State == STATE_B || State == STATE_MODEM_REQUEST || State == STATE_MODEM_WAIT || State == STATE_MODEM_DONE || State == STATE_MODEM_DENIED)) setState(STATE_B1); - } - - //make mode and start/stoptimes persistent on reboot - if (preferences.begin("settings", false) ) { //false = write mode - preferences.putUChar("Access", Access_bit); - preferences.putUChar("CardOffset", CardOffset); - preferences.end(); - } - -#if MQTT - // Update MQTT faster - lastMqttUpdate = 10; -#endif -} - -/** - * Returns the known battery charge rate if the data is not too old. - * Returns 0 if data is too old. - * A positive number means charging, a negative number means discharging --> this means the inverse must be used for calculations - * - * Example: - * homeBatteryCharge == 1000 --> Battery is charging using Solar - * P1 = -500 --> Solar injection to the net but nut sufficient for charging - * - * If the P1 value is added with the inverse battery charge it will inform the EVSE logic there is enough Solar --> -500 + -1000 = -1500 - * - * Note: The user who is posting battery charge data should take this into account, meaning: if he wants a minimum home battery (dis)charge rate he should substract this from the value he is sending. - */ -// -int getBatteryCurrent(void) { - int currentTime = time(NULL) - 60; // The data should not be older than 1 minute - - if (Mode == MODE_SOLAR && homeBatteryLastUpdate > (currentTime)) { - return homeBatteryCurrent; - } else { - homeBatteryCurrent = 0; - homeBatteryLastUpdate = 0; - return 0; - } -} - - - -// Is there at least 6A(configurable MinCurrent) available for a new EVSE? -// Look whether there would be place for one more EVSE if we could lower them all down to MinCurrent -// returns 1 if there is 6A available -// returns 0 if there is no current available -// only runs on the Master or when loadbalancing Disabled -char IsCurrentAvailable(void) { - uint8_t n, ActiveEVSE = 0; - int Baseload, Baseload_EV, TotalCurrent = 0; - - for (n = 0; n < NR_EVSES; n++) if (BalancedState[n] == STATE_C) // must be in STATE_C - { - ActiveEVSE++; // Count nr of active (charging) EVSE's - TotalCurrent += Balanced[n]; // Calculate total of all set charge currents - } - - // Allow solar Charging if surplus current is above 'StartCurrent' (sum of all phases) - // Charging will start after the timeout (chargedelay) period has ended - // Only when StartCurrent configured or Node MinCurrent detected or Node inactive - if (Mode == MODE_SOLAR) { // no active EVSE yet? - if (ActiveEVSE == 0 && Isum >= ((signed int)StartCurrent *-10)) { - _LOG_D("No current available StartCurrent line %d. ActiveEVSE=%i, TotalCurrent=%.1fA, StartCurrent=%iA, Isum=%.1fA, ImportCurrent=%iA.\n", __LINE__, ActiveEVSE, (float) TotalCurrent/10, StartCurrent, (float)Isum/10, ImportCurrent); - return 0; - } - else if ((ActiveEVSE * MinCurrent * 10) > TotalCurrent) { // check if we can split the available current between all active EVSE's - _LOG_D("No current available TotalCurrent line %d. ActiveEVSE=%i, TotalCurrent=%.1fA, StartCurrent=%iA, Isum=%.1fA, ImportCurrent=%iA.\n", __LINE__, ActiveEVSE, (float) TotalCurrent/10, StartCurrent, (float)Isum/10, ImportCurrent); - return 0; - } - else if (ActiveEVSE > 0 && Isum > ((signed int)ImportCurrent * 10) + TotalCurrent - (ActiveEVSE * MinCurrent * 10)) { - _LOG_D("No current available Isum line %d. ActiveEVSE=%i, TotalCurrent=%.1fA, StartCurrent=%iA, Isum=%.1fA, ImportCurrent=%iA.\n", __LINE__, ActiveEVSE, (float) TotalCurrent/10, StartCurrent, (float)Isum/10, ImportCurrent); - return 0; - } - } - - ActiveEVSE++; // Do calculations with one more EVSE - if (ActiveEVSE > NR_EVSES) ActiveEVSE = NR_EVSES; - Baseload = Imeasured - TotalCurrent; // Calculate Baseload (load without any active EVSE) - Baseload_EV = Imeasured_EV - TotalCurrent; // Load on the EV subpanel excluding any active EVSE - if (Baseload < 0) Baseload = 0; // only relevant for Smart/Solar mode - - // Check if the lowest charge current(6A) x ActiveEV's + baseload would be higher then the MaxMains. - if ((ActiveEVSE * (MinCurrent * 10) + Baseload) > (MaxMains * 10)) { - _LOG_D("No current available MaxMains line %d. ActiveEVSE=%i, Baseload=%.1fA, MinCurrent=%iA, MaxMains=%iA.\n", __LINE__, ActiveEVSE, (float) Baseload/10, MinCurrent, MaxMains); - return 0; // Not enough current available!, return with error - } - if ((ActiveEVSE * (MinCurrent * 10) + Baseload_EV) > (MaxCircuit * 10)) { - _LOG_D("No current available MaxCircuit line %d. ActiveEVSE=%i, Baseload_EV=%.1fA, MinCurrent=%iA, MaxCircuit=%iA.\n", __LINE__, ActiveEVSE, (float) Baseload_EV/10, MinCurrent, MaxCircuit); - return 0; // Not enough current available!, return with error - } - //assume the current should be available on all 3 phases - bool must_be_single_phase_charging = (EnableC2 == ALWAYS_OFF || (Mode == MODE_SOLAR && EnableC2 == SOLAR_OFF) || - (Mode == MODE_SOLAR && EnableC2 == AUTO && Switching_To_Single_Phase == AFTER_SWITCH)); - int Phases = must_be_single_phase_charging ? 1 : 3; - if ((Phases * ActiveEVSE * (MinCurrent * 10) + Isum) > (MaxSumMains * 10)) { - _LOG_D("No current available MaxSumMains line %d. ActiveEVSE=%i, MinCurrent=%iA, Isum=%.1fA, MaxSumMains=%iA.\n", __LINE__, ActiveEVSE, MinCurrent, (float)Isum/10, MaxSumMains); - return 0; // Not enough current available!, return with error - } - - _LOG_D("Current available checkpoint D. ActiveEVSE increased by one=%i, TotalCurrent=%.1fA, StartCurrent=%iA, Isum=%.1fA, ImportCurrent=%iA.\n", ActiveEVSE, (float) TotalCurrent/10, StartCurrent, (float)Isum/10, ImportCurrent); - return 1; -} - -// Set global var Nr_Of_Phases_Charging -// 0 = undetected, 1 - 3 nr of phases we are charging -void Set_Nr_of_Phases_Charging(void) { - uint32_t Max_Charging_Prob = 0; - uint32_t Charging_Prob=0; // Per phase, the probability that Charging is done at this phase - Nr_Of_Phases_Charging = 0; -#define THRESHOLD 40 -#define BOTTOM_THRESHOLD 25 - _LOG_D("Detected Charging Phases: ChargeCurrent=%u, Balanced[0]=%u, IsetBalanced=%u.\n", ChargeCurrent, Balanced[0],IsetBalanced); - for (int i=0; i<3; i++) { - if (EVMeter) { - Charging_Prob = 10 * (abs(Irms_EV[i] - IsetBalanced)) / IsetBalanced; //100% means this phase is charging, 0% mwans not charging - //TODO does this work for the slaves too? - _LOG_D("Trying to detect Charging Phases END Irms_EV[%i]=%.1f A.\n", i, (float)Irms_EV[i]/10); - } - Max_Charging_Prob = max(Charging_Prob, Max_Charging_Prob); - - //normalize percentages so they are in the range [0-100] - if (Charging_Prob >= 200) - Charging_Prob = 0; - if (Charging_Prob > 100) - Charging_Prob = 200 - Charging_Prob; - _LOG_I("Detected Charging Phases: Charging_Prob[%i]=%i.\n", i, Charging_Prob); - - if (Charging_Prob == Max_Charging_Prob) { - _LOG_D("Suspect I am charging at phase: L%i.\n", i+1); - Nr_Of_Phases_Charging++; - } - else { - if ( Charging_Prob <= BOTTOM_THRESHOLD ) { - _LOG_D("Suspect I am NOT charging at phase: L%i.\n", i+1); - } - else { - if ( Max_Charging_Prob - Charging_Prob <= THRESHOLD ) { - _LOG_D("Serious candidate for charging at phase: L%i.\n", i+1); - Nr_Of_Phases_Charging++; - } - } - } - } - - // sanity checks - if (EnableC2 != AUTO && EnableC2 != NOT_PRESENT) { // no further sanity checks possible when AUTO or NOT_PRESENT - if (Nr_Of_Phases_Charging != 1 && (EnableC2 == ALWAYS_OFF || (EnableC2 == SOLAR_OFF && Mode == MODE_SOLAR))) { - _LOG_A("Error in detecting phases: EnableC2=%s and Nr_Of_Phases_Charging=%i.\n", StrEnableC2[EnableC2], Nr_Of_Phases_Charging); - Nr_Of_Phases_Charging = 1; - _LOG_A("Setting Nr_Of_Phases_Charging to 1.\n"); - } - if (!Force_Single_Phase_Charging() && Nr_Of_Phases_Charging != 3) {//TODO 2phase charging very rare? - _LOG_A("Possible error in detecting phases: EnableC2=%s and Nr_Of_Phases_Charging=%i.\n", StrEnableC2[EnableC2], Nr_Of_Phases_Charging); - } - } - - _LOG_A("Charging at %i phases.\n", Nr_Of_Phases_Charging); -} - -// Calculates Balanced PWM current for each EVSE -// mod =0 normal -// mod =1 we have a new EVSE requesting to start charging. -// only runs on the Master or when loadbalancing Disabled -void CalcBalancedCurrent(char mod) { - int Average, MaxBalanced, Idifference, Baseload_EV; - int ActiveEVSE = 0; - signed int IsumImport; - int ActiveMax = 0, TotalCurrent = 0, Baseload; - char CurrentSet[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; - uint8_t n; - if (BalancedState[0] == STATE_C && MaxCurrent > MaxCapacity && !Config) - ChargeCurrent = MaxCapacity * 10; - else - ChargeCurrent = MaxCurrent * 10; // Instead use new variable ChargeCurrent. - - // Override current temporary if set - if (OverrideCurrent) - ChargeCurrent = OverrideCurrent; - - BalancedMax[0] = ChargeCurrent; - // update BalancedMax[0] if the MAX current was adjusted using buttons or CLI - for (n = 0; n < NR_EVSES; n++) if (BalancedState[n] == STATE_C) { - ActiveEVSE++; // Count nr of Active (Charging) EVSE's - ActiveMax += BalancedMax[n]; // Calculate total Max Amps for all active EVSEs - TotalCurrent += Balanced[n]; // Calculate total of all set charge currents - } - - _LOG_V("Checkpoint 1 Isetbalanced=%.1f A Imeasured=%.1f A MaxCircuit=%i Imeasured_EV=%.1f A, Battery Current = %.1f A, mode=%i.\n", (float)IsetBalanced/10, (float)Imeasured/10, MaxCircuit, (float)Imeasured_EV/10, (float)homeBatteryCurrent/10, Mode); - - // When Load balancing = Master, Limit total current of all EVSEs to MaxCircuit - // Also, when not in Normal Mode, if MaxCircuit is set, it will limit the total current (subpanel configuration) - Baseload_EV = Imeasured_EV - TotalCurrent; // Calculate Baseload (load without any active EVSE) - if (Baseload_EV < 0) - Baseload_EV = 0; - Baseload = Imeasured - TotalCurrent; // Calculate Baseload (load without any active EVSE) - if (Baseload < 0) - Baseload = 0; - - if (Mode == MODE_NORMAL) // Normal Mode - { - if (LoadBl == 1) // Load Balancing = Master? MaxCircuit is max current for all active EVSE's; - IsetBalanced = MaxCircuit * 10 - Baseload_EV; // subpanel option not valid in Normal Mode; - // limiting is per phase so no Nr_Of_Phases_Charging here! - else - IsetBalanced = ChargeCurrent; // No Load Balancing in Normal Mode. Set current to ChargeCurrent (fix: v2.05) - if (ActiveEVSE && mod) { // Only if we have active EVSE's and New EVSE charging - // Set max combined charge current to MaxMains - Baseload, or MaxCircuit - Baseload_EV if that is less - IsetBalanced = min((MaxMains * 10) - Baseload, (MaxCircuit * 10 ) - Baseload_EV); //TODO: why are we checking MaxMains and MaxCircuit while we are in Normal mode? - //TODO: capacity rate limiting here? - } - } //end MODE_NORMAL - else { // start MODE_SOLAR || MODE_SMART - // adapt IsetBalanced in Smart Mode, and ensure the MaxMains/MaxCircuit settings for Solar - - uint8_t Temp_Phases; - Temp_Phases = (Nr_Of_Phases_Charging ? Nr_Of_Phases_Charging : 3); // in case nr of phases not detected, assume 3 - Idifference = min((MaxMains * 10) - Imeasured, min((MaxCircuit * 10) - Imeasured_EV, ((MaxSumMains * 10) - Isum)/Temp_Phases)); - - if (!mod) { // no new EVSE's charging - // For Smart mode, no new EVSE asking for current - // But for Solar mode we _also_ have to guard MaxCircuit and Maxmains! - if (phasesLastUpdateFlag) { // only increase or decrease current if measurements are updated - _LOG_V("phaseLastUpdate=%i.\n", phasesLastUpdate); - if (Idifference > 0) { - if (Mode == MODE_SMART) IsetBalanced += (Idifference / 4); // increase with 1/4th of difference (slowly increase current) - } // in Solar mode we compute increase of current later on! - else - IsetBalanced += Idifference; // last PWM setting + difference (immediately decrease current) (Smart and Solar mode) - } - - if (IsetBalanced < 0) IsetBalanced = 0; - if (IsetBalanced > 800) IsetBalanced = 800; // hard limit 80A (added 11-11-2017) - } - _LOG_V("Checkpoint 2 Isetbalanced=%.1f A, Idifference=%.1f, mod=%i.\n", (float)IsetBalanced/10, (float)Idifference/10, mod); - - if (Mode == MODE_SOLAR) // Solar version - { - IsumImport = Isum - (10 * ImportCurrent); // Allow Import of power from the grid when solar charging - if (Idifference > 0) { // so we had some room for power as far as MaxCircuit and MaxMains are concerned - if (phasesLastUpdateFlag) { // only increase or decrease current if measurements are updated. - if (IsumImport < 0) { - // negative, we have surplus (solar) power available - if (IsumImport < -10 && Idifference > 10) - IsetBalanced = IsetBalanced + 5; // more then 1A available, increase Balanced charge current with 0.5A - else - IsetBalanced = IsetBalanced + 1; // less then 1A available, increase with 0.1A - } else { - // positive, we use more power then is generated - if (IsumImport > 20) - IsetBalanced = IsetBalanced - (IsumImport / 2); // we use atleast 2A more then available, decrease Balanced charge current. - else if (IsumImport > 10) - IsetBalanced = IsetBalanced - 5; // we use 1A more then available, decrease with 0.5A - else if (IsumImport > 3) - IsetBalanced = IsetBalanced - 1; // we still use > 0.3A more then available, decrease with 0.1A - // if we use <= 0.3A we do nothing - } - } - } // we already corrected Isetbalance in case of NOT enough power MaxCircuit/MaxMains - _LOG_V("Checkpoint 3 Isetbalanced=%.1f A, IsumImport=%.1f, Isum=%.1f, ImportCurrent=%i.\n", (float)IsetBalanced/10, (float)IsumImport/10, (float)Isum/10, ImportCurrent); - - // If IsetBalanced is below MinCurrent or negative, make sure it's set to MinCurrent. - if ( (IsetBalanced <= (ActiveEVSE * MinCurrent * 10)) || (IsetBalanced < 0) ) { - IsetBalanced = ActiveEVSE * MinCurrent * 10; - // ----------- Check to see if we have to continue charging on solar power alone ---------- - if (ActiveEVSE && StopTime && (IsumImport > 10)) { - //TODO maybe enable solar switching for loadbl = 1 - if (EnableC2 == AUTO && LoadBl == 0) - Set_Nr_of_Phases_Charging(); - if (Nr_Of_Phases_Charging > 1 && EnableC2 == AUTO && LoadBl == 0) { // when loadbalancing is enabled we don't do forced single phase charging - _LOG_A("Switching to single phase.\n"); // because we wouldnt know which currents to make available to the nodes... - // since we don't know how many phases the nodes are using... - //switching contactor2 off works ok for Skoda Enyaq but Hyundai Ioniq 5 goes into error, so we have to switch more elegantly - if (State == STATE_C) setState(STATE_C1); // tell EV to stop charging - Switching_To_Single_Phase = GOING_TO_SWITCH; - } - else { - if (SolarStopTimer == 0) setSolarStopTimer(StopTime * 60); // Convert minutes into seconds - } - } else { - _LOG_D("Checkpoint a: Resetting SolarStopTimer, IsetBalanced=%.1fA, ActiveEVSE=%i.\n", (float)IsetBalanced/10, ActiveEVSE); - setSolarStopTimer(0); - } - } else { - _LOG_D("Checkpoint b: Resetting SolarStopTimer, IsetBalanced=%.1fA, ActiveEVSE=%i.\n", (float)IsetBalanced/10, ActiveEVSE); - setSolarStopTimer(0); - } - } //end MODE_SOLAR - else { // MODE_SMART - // New EVSE charging, and only if we have active EVSE's - if (mod && ActiveEVSE) { // Set max combined charge current to MaxMains - Baseload - IsetBalanced = min((MaxMains * 10) - Baseload, min((MaxCircuit * 10 ) - Baseload_EV, ((MaxSumMains * 10) - Isum)/3)); //assume the current should be available on all 3 phases - } - } //end MODE_SMART - } // end MODE_SOLAR || MODE_SMART - - // Reset flag that keeps track of new MainsMeter measurements - phasesLastUpdateFlag = false; - - // guard MaxCircuit in all modes; slave doesnt run CalcBalancedCurrent - if (IsetBalanced > (MaxCircuit * 10) - Baseload_EV) - IsetBalanced = MaxCircuit * 10 - Baseload_EV; //limiting is per phase so no Nr_Of_Phases_Charging here! - - _LOG_V("Checkpoint 4 Isetbalanced=%.1f A.\n", (float)IsetBalanced/10); - - if (ActiveEVSE) { // Only if we have active EVSE's - if (IsetBalanced < 0 || IsetBalanced < (ActiveEVSE * MinCurrent * 10)) { - IsetBalanced = ActiveEVSE * MinCurrent * 10; // retain old software behaviour: set minimal "MinCurrent" charge per active EVSE - NoCurrent++; // Flag NoCurrent left - _LOG_I("No Current!!\n"); - } else - NoCurrent = 0; - - if (IsetBalanced > ActiveMax) IsetBalanced = ActiveMax; // limit to total maximum Amps (of all active EVSE's) - // TODO not sure if Nr_Of_Phases_Charging should be involved here - MaxBalanced = IsetBalanced; // convert to Amps - - // Calculate average current per EVSE - n = 0; - while (n < NR_EVSES && ActiveEVSE) { - Average = MaxBalanced / ActiveEVSE; // Average current for all active EVSE's - - // Active EVSE, and current not yet calculated? - if ((BalancedState[n] == STATE_C) && (!CurrentSet[n])) { - - // Check for EVSE's that are starting with Solar charging - if ((Mode == MODE_SOLAR) && (Node[n].IntTimer < SOLARSTARTTIME)) { - Balanced[n] = MinCurrent * 10; // Set to MinCurrent - _LOG_V("[S]Node %u = %u.%u A", n, Balanced[n]/10, Balanced[n]%10); - CurrentSet[n] = 1; // mark this EVSE as set. - ActiveEVSE--; // decrease counter of active EVSE's - MaxBalanced -= Balanced[n]; // Update total current to new (lower) value - IsetBalanced = TotalCurrent; - n = 0; // reset to recheck all EVSE's - continue; // ensure the loop restarts from the beginning - - // Check for EVSE's that have a Max Current that is lower then the average - } else if (Average >= BalancedMax[n]) { - Balanced[n] = BalancedMax[n]; // Set current to Maximum allowed for this EVSE - _LOG_V("[L]Node %u = %u.%u A", n, Balanced[n]/10, Balanced[n]%10); - CurrentSet[n] = 1; // mark this EVSE as set. - ActiveEVSE--; // decrease counter of active EVSE's - MaxBalanced -= Balanced[n]; // Update total current to new (lower) value - n = 0; // reset to recheck all EVSE's - continue; // ensure the loop restarts from the beginning - } - - } - n++; - } - - // All EVSE's which had a Max current lower then the average are set. - // Now calculate the current for the EVSE's which had a higher Max current - n = 0; - while (n < NR_EVSES && ActiveEVSE) { // Check for EVSE's that are not set yet - if ((BalancedState[n] == STATE_C) && (!CurrentSet[n])) { // Active EVSE, and current not yet calculated? - Balanced[n] = MaxBalanced / ActiveEVSE; // Set current to Average - _LOG_V("[H]Node %u = %u.%u A", n, Balanced[n]/10, Balanced[n]%10); - CurrentSet[n] = 1; // mark this EVSE as set. - ActiveEVSE--; // decrease counter of active EVSE's - MaxBalanced -= Balanced[n]; // Update total current to new (lower) value - } //TODO since the average has risen the other EVSE's should be checked for exceeding their MAX's too! - n++; - } - - - } // ActiveEVSE - _LOG_V("Checkpoint 5 Isetbalanced=%.1f A.\n", (float)IsetBalanced/10); - if (LoadBl == 1) { - _LOG_D("Balance: "); - for (n = 0; n < NR_EVSES; n++) { - _LOG_D_NO_FUNC("EVSE%u:%s(%.1fA) ", n, getStateName(BalancedState[n]), (float)Balanced[n]/10); - } - _LOG_D_NO_FUNC("\n"); - } -} //CalcBalancedCurrent - -/** - * Load Balancing Modbus Address LoadBl - Disabled 0x01 0x00 - Master 0x01 0x01 - Node 1 0x02 0x02 - Node 2 0x03 0x03 - Node 3 0x04 0x04 - Node 4 0x05 0x05 - Node 5 0x06 0x06 - Node 6 0x07 0x07 - Node 7 0x08 0x08 - Broadcast to all SmartEVSE with address 0x09. -**/ - -/** - * In order to keep each node happy, and not timeout with a comm-error you will have to send the chargecurrent for each node in a broadcast message to all nodes - * (address 09): - - 09 10 00 20 00 08 10 00 A0 00 00 00 3C 00 00 00 00 00 00 00 00 00 00 99 24 - Node 0 00 A0 = 160 = 16.0A - Node 1 00 00 = 0 = 0.0A - Node 2 00 3C = 60 = 6.0A - etc. - - * Each time this message is received on each node, the timeout timer is reset to 10 seconds. - * The master will usually send this message every two seconds. -**/ - -/** - * Broadcast momentary currents to all Node EVSE's - */ -void BroadcastCurrent(void) { - //prepare registers 0x0020 thru 0x002A (including) to be sent - uint8_t buf[sizeof(Balanced)+ 6], i; - uint8_t *p=buf; - memcpy(p, Balanced, sizeof(Balanced)); - p = p + sizeof(Balanced); - // Irms values, we only send the 16 least significant bits (range -327.6A to +327.6A) per phase - for ( i=0; i<3; i++) { - p[i * 2] = Irms[i] & 0xff; - p[(i * 2) + 1] = Irms[i] >> 8; - } - ModbusWriteMultipleRequest(BROADCAST_ADR, 0x0020, (uint16_t *) buf, 8 + 3); -} - -/** - * EVSE Register 0x02*: System configuration (same on all SmartEVSE in a LoadBalancing setup) - * TODO not sure if this is used anywhere in the code? -Regis Access Description Unit Values -0x0200 R/W EVSE mode 0:Normal / 1:Smart / 2:Solar -0x0201 R/W EVSE Circuit max Current A 10 - 160 -0x0202 R/W Grid type to which the Sensorbox is connected 0:4Wire / 1:3Wire -0x0203 R/W CT calibration value 0.01 Multiplier -0x0204 R/W Max Mains Current A 10 - 200 -0x0205 R/W Surplus energy start Current A 1 - 16 -0x0206 R/W Stop solar charging at 6A after this time min 0:Disable / 1 - 60 -0x0207 R/W Allow grid power when solar charging A 0 - 6 -0x0208 R/W Type of Mains electric meter * -0x0209 R/W Address of Mains electric meter 10 - 247 -//0x020A R/W What does Mains electric meter measure 0:Mains (Home+EVSE+PV) / 1:Home+EVSE -0x020B R/W Type of PV electric meter * -0x020C R/W Address of PV electric meter 10 - 247 -0x020D R/W Byte order of custom electric meter 0:LBF & LWF / 1:LBF & HWF / 2:HBF & LWF / 3:HBF & HWF -0x020E R/W Data type of custom electric meter 0:Integer / 1:Double -0x020F R/W Modbus Function (3/4) of custom electric meter -0x0210 R/W Register for Voltage (V) of custom electric meter 0 - 65530 -0x0211 R/W Divisor for Voltage (V) of custom electric meter 10x 0 - 7 -0x0212 R/W Register for Current (A) of custom electric meter 0 - 65530 -0x0213 R/W Divisor for Current (A) of custom electric meter 10x 0 - 7 -0x0214 R/W Register for Power (W) of custom electric meter 0 - 65534 -0x0215 R/W Divisor for Power (W) of custom electric meter 10x 0 - 7 / -0x0216 R/W Register for Energy (kWh) of custom electric meter 0 - 65534 -0x0217 R/W Divisor for Energy (kWh) of custom electric meter 10x 0 - 7 -0x0218 R/W Maximum register read (Not implemented) -0x0219 R/W WiFi mode -0x021A R/W Limit max current draw on MAINS (sum of phases) A 9:Disable / 10 - 200 -**/ - -/** - * Master requests Node configuration over modbus - * Master -> Node - * - * @param uint8_t NodeNr (1-7) - */ -void requestNodeConfig(uint8_t NodeNr) { - ModbusReadInputRequest(NodeNr + 1u, 4, 0x0108, 2); -} - -/** - * EVSE Node Config layout - * -Reg Access Description Unit Values -0x0100 R/W Configuration 0:Socket / 1:Fixed Cable -0x0101 R/W Cable lock 0:Disable / 1:Solenoid / 2:Motor -0x0102 R/W MIN Charge Current the EV will accept A 6 - 16 -0x0103 R/W MAX Charge Current for this EVSE A 6 - 80 -0x0104 R/W Load Balance 0:Disabled / 1:Master / 2-8:Node -0x0105 R/W External Switch on pin SW 0:Disabled / 1:Access Push-Button / 2:Access Switch / 3:Smart-Solar Push-Button / 4:Smart-Solar Switch -0x0106 R/W Residual Current Monitor on pin RCM 0:Disabled / 1:Enabled -0x0107 R/W Use RFID reader 0:Disabled / 1:Enabled -0x0108 R/W Type of EV electric meter * -0x0109 R/W Address of EV electric meter 10 - 247 -**/ - -/** - * Master receives Node configuration over modbus - * Node -> Master - * - * @param uint8_t NodeNr (1-7) - */ -void receiveNodeConfig(uint8_t *buf, uint8_t NodeNr) { - Node[NodeNr].EVMeter = buf[1]; - Node[NodeNr].EVAddress = buf[3]; - - Node[NodeNr].ConfigChanged = 0; // Reset flag on master - ModbusWriteSingleRequest(NodeNr + 1u, 0x0006, 0); // Reset flag on node -} - -/** - * Master requests Node status over modbus - * Master -> Node - * - * @param uint8_t NodeNr (1-7) - */ -void requestNodeStatus(uint8_t NodeNr) { - if(Node[NodeNr].Online) { - if(Node[NodeNr].Online-- == 1) { - // Reset Node state when node is offline - BalancedState[NodeNr] = STATE_A; - Balanced[NodeNr] = 0; - } - } - - ModbusReadInputRequest(NodeNr + 1u, 4, 0x0000, 8); -} - -/** To have full control over the nodes, you will have to read each node's status registers, and see if it requests to charge. - * for example for node 2: - - Received packet (21 bytes) 03 04 10 00 01 00 00 00 3c 00 01 00 00 00 01 00 01 00 20 4d 8c - 00 01 = state B - 00 00 = no errors - 00 3c = charge current 6.0 A - 00 01 = Smart mode - etc. - - Here the state changes to STATE_COMM_C (00 06) - Received packet (21 bytes) 03 04 10 00 06 00 00 00 3c 00 01 00 00 00 01 00 01 00 20 0a 8e - So the ESVE request to charge. - - You can respond to this request by changing the state of the node to State_C - 03 10 00 00 00 02 04 00 07 00 00 49 D6 - Here it will write 00 07 (STATE_COMM_C_OK) to register 0x0000, and reset the error register 0x0001 - - The node will respond to this by switching to STATE_C (Charging). -**/ - -/** - * EVSE Node status layout - * -Regist Access Description Unit Values -0x0000 R/W State 0:A / 1:B / 2:C / 3:D / 4:Node request B / 5:Master confirm B / 6:Node request C / - 7:Master confirm C / 8:Activation mode / 9:B1 / 10:C1 -0x0001 R/W Error Bit 1:LESS_6A / 2:NO_COMM / 4:TEMP_HIGH / 8:EV_NOCOMM / 16:RCD / 32:NO_SUN -0x0002 R/W Charging current 0.1 A 0:no current available / 6-80 -0x0003 R/W EVSE mode (without saving) 0:Normal / 1:Smart / 2:Solar -0x0004 R/W Solar Timer s -0x0005 R/W Access bit 0:No Access / 1:Access -0x0006 R/W Configuration changed (Not implemented) -0x0007 R Maximum charging current A -0x0008 R/W Number of used phases (Not implemented) 0:Undetected / 1 - 3 -0x0009 R Real charging current (Not implemented) 0.1 A -0x000A R Temperature K -0x000B R Serial number -0x0020 - 0x0027 - W Broadcast charge current. SmartEVSE uses only one value depending on the "Load Balancing" configuration - 0.1 A 0:no current available -0x0028 - 0x0030 - W Broadcast MainsMeter currents L1 - L3. - 0.1 A -**/ - -/** - * Master receives Node status over modbus - * Node -> Master - * - * @param uint8_t NodeAdr (1-7) - */ -void receiveNodeStatus(uint8_t *buf, uint8_t NodeNr) { - Node[NodeNr].Online = 5; - - BalancedState[NodeNr] = buf[1]; // Node State - BalancedError[NodeNr] = buf[3]; // Node Error status - // Update Mode when changed on Node and not Smart/Solar Switch on the Master - // Also make sure we are not in the menu. - Node[NodeNr].Mode = buf[7]; - - if ((Node[NodeNr].Mode != Mode) && Switch != 4 && !LCDNav && !NodeNewMode) { - NodeNewMode = Node[NodeNr].Mode + 1; // Store the new Mode in NodeNewMode, we'll update Mode in 'ProcessAllNodeStates' - } - Node[NodeNr].SolarTimer = (buf[8] * 256) + buf[9]; - Node[NodeNr].ConfigChanged = buf[13] | Node[NodeNr].ConfigChanged; - BalancedMax[NodeNr] = buf[15] * 10; // Node Max ChargeCurrent (0.1A) - _LOG_D("ReceivedNode[%u]Status State:%u Error:%u, BalancedMax:%u, Mode:%u, ConfigChanged:%u.\n", NodeNr, BalancedState[NodeNr], BalancedError[NodeNr], BalancedMax[NodeNr], Node[NodeNr].Mode, Node[NodeNr].ConfigChanged); -} - -/** - * Master checks node status requests, and responds with new state - * Master -> Node - * - * @param uint8_t NodeAdr (1-7) - * @return uint8_t success - */ -uint8_t processAllNodeStates(uint8_t NodeNr) { - uint16_t values[5]; - uint8_t current, write = 0, regs = 2; // registers are written when Node needs updating. - - values[0] = BalancedState[NodeNr]; - - current = IsCurrentAvailable(); - if (current) { // Yes enough current - if (BalancedError[NodeNr] & (LESS_6A|NO_SUN)) { - BalancedError[NodeNr] &= ~(LESS_6A | NO_SUN); // Clear Error flags - write = 1; - } - } - - if ((ErrorFlags & CT_NOCOMM) && !(BalancedError[NodeNr] & CT_NOCOMM)) { - BalancedError[NodeNr] |= CT_NOCOMM; // Send Comm Error on Master to Node - write = 1; - } - - // Check EVSE for request to charge states - switch (BalancedState[NodeNr]) { - case STATE_A: - // Reset Node - Node[NodeNr].IntTimer = 0; - Node[NodeNr].Timer = 0; - Node[NodeNr].Phases = 0; - Node[NodeNr].MinCurrent = 0; - break; - - case STATE_COMM_B: // Request to charge A->B - _LOG_I("Node %u State A->B request ", NodeNr); - if (current) { // check if we have enough current - // Yes enough current.. - BalancedState[NodeNr] = STATE_B; // Mark Node EVSE as active (State B) - Balanced[NodeNr] = MinCurrent * 10; // Initially set current to lowest setting - values[0] = STATE_COMM_B_OK; - write = 1; - _LOG_I("- OK!\n"); - } else { // We do not have enough current to start charging - Balanced[NodeNr] = 0; // Make sure the Node does not start charging by setting current to 0 - if ((BalancedError[NodeNr] & (LESS_6A|NO_SUN)) == 0) { // Error flags cleared? - if (Mode == MODE_SOLAR) BalancedError[NodeNr] |= NO_SUN; // Solar mode: No Solar Power available - else BalancedError[NodeNr] |= LESS_6A; // Normal or Smart Mode: Not enough current available - write = 1; - } - _LOG_I("- Not enough current!\n"); - } - break; - - case STATE_COMM_C: // request to charge B->C - _LOG_I("Node %u State B->C request\n", NodeNr); - Balanced[NodeNr] = 0; // For correct baseload calculation set current to zero - if (current) { // check if we have enough current - // Yes - BalancedState[NodeNr] = STATE_C; // Mark Node EVSE as Charging (State C) - CalcBalancedCurrent(1); // Calculate charge current for all connected EVSE's - values[0] = STATE_COMM_C_OK; - write = 1; - _LOG_I("- OK!\n"); - } else { // We do not have enough current to start charging - if ((BalancedError[NodeNr] & (LESS_6A|NO_SUN)) == 0) { // Error flags cleared? - if (Mode == MODE_SOLAR) BalancedError[NodeNr] |= NO_SUN; // Solar mode: No Solar Power available - else BalancedError[NodeNr] |= LESS_6A; // Normal or Smart Mode: Not enough current available - write = 1; - } - _LOG_I("- Not enough current!\n"); - } - break; - - default: - break; - - } - - // Here we set the Masters Mode to the one we received from a Slave/Node - if (NodeNewMode) { - setMode(NodeNewMode -1); - NodeNewMode = 0; - } - - // Error Flags - values[1] = BalancedError[NodeNr]; - // Charge Current - values[2] = 0; // This does nothing for Nodes. Currently the Chargecurrent can only be written to the Master - // Mode - if (Node[NodeNr].Mode != Mode) { - regs = 4; - write = 1; - } - values[3] = Mode; - - // SolarStopTimer - if (abs((int16_t)SolarStopTimer - (int16_t)Node[NodeNr].SolarTimer) > 3) { // Write SolarStoptimer to Node if time is off by 3 seconds or more. - regs = 5; - write = 1; - values[4] = SolarStopTimer; - } - - if (write) { - _LOG_D("processAllNode[%u]States State:%u, BalancedError:%u, Mode:%u, SolarStopTimer:%u\n",NodeNr, BalancedState[NodeNr], BalancedError[NodeNr], Mode, SolarStopTimer); - ModbusWriteMultipleRequest(NodeNr+1 , 0x0000, values, regs); // Write State, Error, Charge Current, Mode and Solar Timer to Node - } - - return write; -} - - -/** - * Check minimum and maximum of a value and set the variable - * - * @param uint8_t MENU_xxx - * @param uint16_t value - * @return uint8_t success - */ -uint8_t setItemValue(uint8_t nav, uint16_t val) { - if (nav < MENU_EXIT) { - if (val < MenuStr[nav].Min || val > MenuStr[nav].Max) return 0; - } - - switch (nav) { - case MENU_MAX_TEMP: - maxTemp = val; - break; - case MENU_C2: - EnableC2 = (EnableC2_t) val; - break; - case MENU_CONFIG: - Config = val; - break; - case STATUS_MODE: - setMode(val); - break; - case MENU_MODE: - Mode = val; - break; - case MENU_START: - StartCurrent = val; - break; - case MENU_STOP: - StopTime = val; - break; - case MENU_IMPORT: - ImportCurrent = val; - break; - case MENU_LOADBL: - ConfigureModbusMode(val); - LoadBl = val; - break; - case MENU_MAINS: - MaxMains = val; - break; - case MENU_SUMMAINS: - MaxSumMains = val; - break; - case MENU_MIN: - MinCurrent = val; - break; - case MENU_MAX: - MaxCurrent = val; - break; - case MENU_CIRCUIT: - MaxCircuit = val; - break; - case MENU_LOCK: - Lock = val; - break; - case MENU_SWITCH: - Switch = val; - break; - case MENU_RCMON: - RCmon = val; - break; - case MENU_CAL: - ICal = val; - break; - case MENU_GRID: - Grid = val; - break; - case MENU_MAINSMETER: - MainsMeter = val; - break; - case MENU_MAINSMETERADDRESS: - MainsMeterAddress = val; - break; - case MENU_EVMETER: - EVMeter = val; - break; - case MENU_EVMETERADDRESS: - EVMeterAddress = val; - break; - case MENU_EMCUSTOM_ENDIANESS: - EMConfig[EM_CUSTOM].Endianness = val; - break; - case MENU_EMCUSTOM_DATATYPE: - EMConfig[EM_CUSTOM].DataType = (mb_datatype)val; - break; - case MENU_EMCUSTOM_FUNCTION: - EMConfig[EM_CUSTOM].Function = val; - break; - case MENU_EMCUSTOM_UREGISTER: - EMConfig[EM_CUSTOM].URegister = val; - break; - case MENU_EMCUSTOM_UDIVISOR: - EMConfig[EM_CUSTOM].UDivisor = val; - break; - case MENU_EMCUSTOM_IREGISTER: - EMConfig[EM_CUSTOM].IRegister = val; - break; - case MENU_EMCUSTOM_IDIVISOR: - EMConfig[EM_CUSTOM].IDivisor = val; - break; - case MENU_EMCUSTOM_PREGISTER: - EMConfig[EM_CUSTOM].PRegister = val; - break; - case MENU_EMCUSTOM_PDIVISOR: - EMConfig[EM_CUSTOM].PDivisor = val; - break; - case MENU_EMCUSTOM_EREGISTER: - EMConfig[EM_CUSTOM].ERegister = val; - break; - case MENU_EMCUSTOM_EDIVISOR: - EMConfig[EM_CUSTOM].EDivisor = val; - break; - case MENU_RFIDREADER: - RFIDReader = val; - break; - case MENU_WIFI: - WIFImode = val; - break; - - // Status writeable - case STATUS_STATE: - if (val != State) setState(val); - break; - case STATUS_ERROR: - ErrorFlags = val; - if (ErrorFlags) { // Is there an actual Error? Maybe the error got cleared? - if (ErrorFlags & CT_NOCOMM) MainsMeterTimeout = 0; // clear MainsMeterTimeout on a CT_NOCOMM error, so the error will be immediate. - setStatePowerUnavailable(); - ChargeDelay = CHARGEDELAY; - _LOG_V("Error message received!\n"); - } else { - _LOG_V("Errors Cleared received!\n"); - } - break; - case STATUS_CURRENT: - OverrideCurrent = val; - if (LoadBl < 2) MainsMeterTimeout = COMM_TIMEOUT; // reset timeout when register is written - break; - case STATUS_SOLAR_TIMER: - setSolarStopTimer(val); - break; - case STATUS_ACCESS: - if (val == 0 || val == 1) { - setAccess(val); - } - break; - case STATUS_CONFIG_CHANGED: - ConfigChanged = val; - break; - - default: - return 0; - } - - return 1; -} - -/** - * Get the variable - * - * @param uint8_t MENU_xxx - * @return uint16_t value - */ -uint16_t getItemValue(uint8_t nav) { - switch (nav) { - case MENU_MAX_TEMP: - return maxTemp; - case MENU_C2: - return EnableC2; - case MENU_CONFIG: - return Config; - case MENU_MODE: - case STATUS_MODE: - return Mode; - case MENU_START: - return StartCurrent; - case MENU_STOP: - return StopTime; - case MENU_IMPORT: - return ImportCurrent; - case MENU_LOADBL: - return LoadBl; - case MENU_MAINS: - return MaxMains; - case MENU_SUMMAINS: - return MaxSumMains; - case MENU_MIN: - return MinCurrent; - case MENU_MAX: - return MaxCurrent; - case MENU_CIRCUIT: - return MaxCircuit; - case MENU_LOCK: - return Lock; - case MENU_SWITCH: - return Switch; - case MENU_RCMON: - return RCmon; - case MENU_CAL: - return ICal; - case MENU_GRID: - return Grid; - case MENU_MAINSMETER: - return MainsMeter; - case MENU_MAINSMETERADDRESS: - return MainsMeterAddress; - case MENU_EVMETER: - return EVMeter; - case MENU_EVMETERADDRESS: - return EVMeterAddress; - case MENU_EMCUSTOM_ENDIANESS: - return EMConfig[EM_CUSTOM].Endianness; - case MENU_EMCUSTOM_DATATYPE: - return EMConfig[EM_CUSTOM].DataType; - case MENU_EMCUSTOM_FUNCTION: - return EMConfig[EM_CUSTOM].Function; - case MENU_EMCUSTOM_UREGISTER: - return EMConfig[EM_CUSTOM].URegister; - case MENU_EMCUSTOM_UDIVISOR: - return EMConfig[EM_CUSTOM].UDivisor; - case MENU_EMCUSTOM_IREGISTER: - return EMConfig[EM_CUSTOM].IRegister; - case MENU_EMCUSTOM_IDIVISOR: - return EMConfig[EM_CUSTOM].IDivisor; - case MENU_EMCUSTOM_PREGISTER: - return EMConfig[EM_CUSTOM].PRegister; - case MENU_EMCUSTOM_PDIVISOR: - return EMConfig[EM_CUSTOM].PDivisor; - case MENU_EMCUSTOM_EREGISTER: - return EMConfig[EM_CUSTOM].ERegister; - case MENU_EMCUSTOM_EDIVISOR: - return EMConfig[EM_CUSTOM].EDivisor; - case MENU_RFIDREADER: - return RFIDReader; - case MENU_WIFI: - return WIFImode; - - // Status writeable - case STATUS_STATE: - return State; - case STATUS_ERROR: - return ErrorFlags; - case STATUS_CURRENT: - return Balanced[0]; - case STATUS_SOLAR_TIMER: - return SolarStopTimer; - case STATUS_ACCESS: - return Access_bit; - case STATUS_CONFIG_CHANGED: - return ConfigChanged; - - // Status readonly - case STATUS_MAX: - return min(MaxCapacity,MaxCurrent); - case STATUS_TEMP: - return (signed int)TempEVSE; - case STATUS_SERIAL: - return serialnr; - - default: - return 0; - } -} - - -void printStatus(void) -{ - _LOG_I ("STATE: %s Error: %u StartCurrent: -%i ChargeDelay: %u SolarStopTimer: %u NoCurrent: %u Imeasured: %.1f A IsetBalanced: %.1f A, MainsMeterTimeout=%u, EVMeterTimeout=%u.\n", getStateName(State), ErrorFlags, StartCurrent, ChargeDelay, SolarStopTimer, NoCurrent, (float)Imeasured/10, (float)IsetBalanced/10, MainsMeterTimeout, EVMeterTimeout); - _LOG_I("L1: %.1f A L2: %.1f A L3: %.1f A Isum: %.1f A\n", (float)Irms[0]/10, (float)Irms[1]/10, (float)Irms[2]/10, (float)Isum/10); -} - -// Recompute State of Charge, in case we have a known initial state of charge -// This function is called by kWh logic and after an EV state update through API, Serial or MQTT -void RecomputeSoC(void) { - if (InitialSoC > 0 && FullSoC > 0 && EnergyCapacity > 0) { - if (InitialSoC == FullSoC) { - // We're already at full SoC - ComputedSoC = FullSoC; - RemainingSoC = 0; - TimeUntilFull = -1; - } else { - int EnergyRemaining = -1; - int TargetEnergyCapacity = (FullSoC / 100.f) * EnergyCapacity; - - if (EnergyRequest > 0) { - // Attempt to use EnergyRequest to determine SoC with greater accuracy - EnergyRemaining = EnergyCharged > 0 ? (EnergyRequest - EnergyCharged) : EnergyRequest; - } else { - // We use a rough estimation based on FullSoC and EnergyCapacity - EnergyRemaining = TargetEnergyCapacity - (EnergyCharged + (InitialSoC / 100.f) * EnergyCapacity); - } - - RemainingSoC = ((FullSoC * EnergyRemaining) / TargetEnergyCapacity); - ComputedSoC = RemainingSoC > 1 ? (FullSoC - RemainingSoC) : FullSoC; - - // Only attempt to compute the SoC and TimeUntilFull if we have a EnergyRemaining and PowerMeasured - if (EnergyRemaining > -1) { - int TimeToGo = -1; - // Do a very simple estimation in seconds until car would reach FullSoC according to current charging power - if (PowerMeasured > 0) { - // Use real-time PowerMeasured data if available - TimeToGo = (3600 * EnergyRemaining) / PowerMeasured; - } else if (Nr_Of_Phases_Charging > 0) { - // Else, fall back on the theoretical maximum of the cable + nr of phases - TimeToGo = (3600 * EnergyRemaining) / (MaxCapacity * (Nr_Of_Phases_Charging * 230)); - } - - // Wait until we have a somewhat sensible estimation while still respecting granny chargers - if (TimeToGo < 100000) { - TimeUntilFull = TimeToGo; - } - } - - // We can't possibly charge to over 100% SoC - if (ComputedSoC > FullSoC) { - ComputedSoC = FullSoC; - RemainingSoC = 0; - TimeUntilFull = -1; - } - - _LOG_I("SoC: EnergyRemaining %i RemaningSoC %i EnergyRequest %i EnergyCharged %i EnergyCapacity %i ComputedSoC %i FullSoC %i TimeUntilFull %i TargetEnergyCapacity %i\n", EnergyRemaining, RemainingSoC, EnergyRequest, EnergyCharged, EnergyCapacity, ComputedSoC, FullSoC, TimeUntilFull, TargetEnergyCapacity); - } - } else { - if (TimeUntilFull != -1) TimeUntilFull = -1; - } - // There's also the possibility an external API/app is used for SoC info. In such case, we allow setting ComputedSoC directly. -} - -// EV disconnected from charger. Triggered after 60 seconds of disconnect -// This is done so we can "re-plug" the car in the Modem process without triggering disconnect events -void DisconnectEvent(void){ - _LOG_A("EV disconnected for a while. Resetting SoC states"); - ModemStage = 0; // Enable Modem states again - InitialSoC = -1; - FullSoC = -1; - RemainingSoC = -1; - ComputedSoC = -1; - EnergyCapacity = -1; - EnergyRequest = -1; - TimeUntilFull = -1; - strncpy(EVCCID, "", sizeof(EVCCID)); -} - -void CalcImeasured_EV(void) { - // Initialize Imeasured (max power used) to first channel. - Imeasured_EV = Irms_EV[0]; - for (int x = 1; x < 3; x++) { - if (Irms_EV[x] > Imeasured_EV) Imeasured_EV = Irms_EV[x]; - } -} - -void CalcIsum(void) { - phasesLastUpdate = time(NULL); - phasesLastUpdateFlag = true; // Set flag if a new Irms measurement is received. - int batteryPerPhase = getBatteryCurrent() / 3; - Isum = 0; -#if FAKE_SUNNY_DAY - int32_t temp[3]={0, 0, 0}; - temp[0] = INJECT_CURRENT_L1 * 10; //Irms is in units of 100mA - temp[1] = INJECT_CURRENT_L2 * 10; - temp[2] = INJECT_CURRENT_L3 * 10; -#endif - - // Initialize Imeasured (max power used) to first channel. - Imeasured = Irms[0]; - for (int x = 0; x < 3; x++) { -#if FAKE_SUNNY_DAY - Irms[x] = Irms[x] - temp[x]; -#endif - IrmsOriginal[x] = Irms[x]; - Irms[x] -= batteryPerPhase; - Isum = Isum + Irms[x]; - // Imeasured holds highest Irms of all channels - if (Irms[x] > Imeasured) Imeasured = Irms[x]; - } -} - - -// CheckSwitch (SW input) -// -void CheckSwitch(void) -{ - static uint8_t RB2count = 0, RB2last = 1, RB2low = 0; - static unsigned long RB2Timer = 0; // 1500ms - - // External switch changed state? - if ( (digitalRead(PIN_SW_IN) != RB2last) || RB2low) { - // make sure that noise on the input does not switch - if (RB2count++ > 20 || RB2low) { - RB2last = digitalRead(PIN_SW_IN); - if (RB2last == 0) { - // Switch input pulled low - switch (Switch) { - case 1: // Access Button - setAccess(!Access_bit); // Toggle Access bit on/off - _LOG_I("Access: %d\n", Access_bit); - break; - case 2: // Access Switch - setAccess(true); - break; - case 3: // Smart-Solar Button or hold button for 1,5 second to STOP charging - if (RB2low == 0) { - RB2low = 1; - RB2Timer = millis(); - } - if (RB2low && millis() > RB2Timer + 1500) { - if (State == STATE_C) { - setState(STATE_C1); - if (!TestState) ChargeDelay = 15; // Keep in State B for 15 seconds, so the Charge cable can be removed. - RB2low = 2; - } - } - break; - case 4: // Smart-Solar Switch - if (Mode == MODE_SOLAR) { - setMode(MODE_SMART); - setSolarStopTimer(0); // Also make sure the SolarTimer is disabled. - } - break; - default: - if (State == STATE_C) { // Menu option Access is set to Disabled - setState(STATE_C1); - if (!TestState) ChargeDelay = 15; // Keep in State B for 15 seconds, so the Charge cable can be removed. - } - break; - } - - // Reset RCM error when button is pressed - // RCM was tripped, but RCM level is back to normal - if (RCmon == 1 && (ErrorFlags & RCM_TRIPPED) && digitalRead(PIN_RCM_FAULT) == LOW) { - // Clear RCM error - ErrorFlags &= ~RCM_TRIPPED; - } - // Also light up the LCD backlight - // BacklightTimer = BACKLIGHT; // Backlight ON - - } else { - // Switch input released - switch (Switch) { - case 2: // Access Switch - setAccess(false); - break; - case 3: // Smart-Solar Button - if (RB2low != 2) { - if (Mode == MODE_SMART) { - setMode(MODE_SOLAR); - } else if (Mode == MODE_SOLAR) { - setMode(MODE_SMART); - } - ErrorFlags &= ~(NO_SUN | LESS_6A); // Clear All errors - ChargeDelay = 0; // Clear any Chargedelay - setSolarStopTimer(0); // Also make sure the SolarTimer is disabled. - LCDTimer = 0; - } - RB2low = 0; - break; - case 4: // Smart-Solar Switch - if (Mode == MODE_SMART) setMode(MODE_SOLAR); - break; - default: - break; - } - } - - RB2count = 0; - } - } else RB2count = 0; - - // Residual current monitor active, and DC current > 6mA ? - if (RCmon == 1 && digitalRead(PIN_RCM_FAULT) == HIGH) { - delay(1); - // check again, to prevent voltage spikes from tripping the RCM detection - if (digitalRead(PIN_RCM_FAULT) == HIGH) { - if (State) setState(STATE_B1); - ErrorFlags = RCM_TRIPPED; - LCDTimer = 0; // display the correct error message on the LCD - } - } - - -} - - - -// Task that handles EVSE State Changes -// Reads buttons, and updates the LCD. -// -// called every 10ms -void EVSEStates(void * parameter) { - - //uint8_t n; - uint8_t leftbutton = 5; - uint8_t DiodeCheck = 0; - uint16_t StateTimer = 0; // When switching from State B to C, make sure pilot is at 6v for 100ms - - // infinite loop - while(1) { - - - // Sample the three < o > buttons. - // As the buttons are shared with the SPI lines going to the LCD, - // we have to make sure that this does not interfere by write actions to the LCD. - // Therefore updating the LCD is also done in this task. - - pinMatrixOutDetach(PIN_LCD_SDO_B3, false, false); // disconnect MOSI pin - pinMode(PIN_LCD_SDO_B3, INPUT); - pinMode(PIN_LCD_A0_B2, INPUT); - // sample buttons < o > - if (digitalRead(PIN_LCD_SDO_B3)) ButtonState = 4; // > (right) - else ButtonState = 0; - if (digitalRead(PIN_LCD_A0_B2)) ButtonState |= 2; // o (middle) - if (digitalRead(PIN_IO0_B1)) ButtonState |= 1; // < (left) - - pinMode(PIN_LCD_SDO_B3, OUTPUT); - pinMatrixOutAttach(PIN_LCD_SDO_B3, VSPID_IN_IDX, false, false); // re-attach MOSI pin - pinMode(PIN_LCD_A0_B2, OUTPUT); - - - // When one or more button(s) are pressed, we call GLCDMenu - if ((ButtonState != 0x07) || (ButtonState != OldButtonState)) GLCDMenu(ButtonState); - - // Update/Show Helpmenu - if (LCDNav > MENU_ENTER && LCDNav < MENU_EXIT && (ScrollTimer + 5000 < millis() ) && (!SubMenu)) GLCDHelp(); - - // Left button pressed, Loadbalancing is Master or Disabled, switch is set to "Sma-Sol B" and Mode is Smart or Solar? - if ((!LCDNav || LCDNav == MENU_OFF) && ButtonState == 0x6 && Mode && !leftbutton && Switch == 3) { - setMode(~Mode & 0x3); // Change from Solar to Smart mode and vice versa. - ErrorFlags &= ~(NO_SUN | LESS_6A); // Clear All errors - ChargeDelay = 0; // Clear any Chargedelay - setSolarStopTimer(0); // Also make sure the SolarTimer is disabled. - LCDTimer = 0; - leftbutton = 5; - } else if (leftbutton && ButtonState == 0x7) leftbutton--; - - // Check the external switch and RCM sensor - CheckSwitch(); - - // sample the Pilot line - pilot = Pilot(); - - // ############### EVSE State A ################# - - if (State == STATE_A || State == STATE_COMM_B || State == STATE_B1) { - // When the pilot line is disconnected, wait for PilotDisconnectTime, then reconnect - if (PilotDisconnected) { - if (PilotDisconnectTime == 0 && pilot == PILOT_NOK ) { // Pilot should be ~ 0V when disconnected - PILOT_CONNECTED; - PilotDisconnected = false; - _LOG_A("Pilot Connected\n"); - } - } else if (pilot == PILOT_12V) { // Check if we are disconnected, or forced to State A, but still connected to the EV - // If the RFID reader is set to EnableOne or EnableAll mode, and the Charging cable is disconnected - // We start a timer to re-lock the EVSE (and unlock the cable) after 60 seconds. - if ((RFIDReader == 2 || RFIDReader == 1) && AccessTimer == 0 && Access_bit == 1) AccessTimer = RFIDLOCKTIME; - - if (State != STATE_A) setState(STATE_A); // reset state, incase we were stuck in STATE_COMM_B - ChargeDelay = 0; // Clear ChargeDelay when disconnected. - - if (!ResetKwh) ResetKwh = 1; // when set, reset EV kWh meter on state B->C change. - } else if ( pilot == PILOT_9V && ErrorFlags == NO_ERROR - && ChargeDelay == 0 && Access_bit && State != STATE_COMM_B -#if MODEM - && State != STATE_MODEM_REQUEST && State != STATE_MODEM_WAIT && State != STATE_MODEM_DONE // switch to State B ? -#endif - ) - { // Allow to switch to state C directly if STATE_A_TO_C is set to PILOT_6V (see EVSE.h) - DiodeCheck = 0; - - ProximityPin(); // Sample Proximity Pin - - _LOG_I("Cable limit: %uA Max: %uA\n", MaxCapacity, MaxCurrent); - if (MaxCurrent > MaxCapacity) ChargeCurrent = MaxCapacity * 10; // Do not modify Max Cable Capacity or MaxCurrent (fix 2.05) - else ChargeCurrent = MinCurrent * 10; // Instead use new variable ChargeCurrent - - // Load Balancing : Node - if (LoadBl > 1) { // Send command to Master, followed by Max Charge Current - setState(STATE_COMM_B); // Node wants to switch to State B - - // Load Balancing: Master or Disabled - } else if (IsCurrentAvailable()) { - BalancedMax[0] = MaxCapacity * 10; - Balanced[0] = ChargeCurrent; // Set pilot duty cycle to ChargeCurrent (v2.15) - if (Modem == EXPERIMENT && ModemStage == 0){ - setState(STATE_MODEM_REQUEST); - }else{ - setState(STATE_B); // switch to State B - } - ActivationMode = 30; // Activation mode is triggered if state C is not entered in 30 seconds. - AccessTimer = 0; - } else if (Mode == MODE_SOLAR) { // Not enough power: - ErrorFlags |= NO_SUN; // Not enough solar power - } else ErrorFlags |= LESS_6A; // Not enough power available - } else if (pilot == PILOT_9V && State != STATE_B1 && State != STATE_COMM_B && Access_bit) { - setState(STATE_B1); - } - } // State == STATE_A || State == STATE_COMM_B || State == STATE_B1 - - if (State == STATE_COMM_B_OK) { - setState(STATE_B); - ActivationMode = 30; // Activation mode is triggered if state C is not entered in 30 seconds. - AccessTimer = 0; - } - - // ############### EVSE State B ################# - - if (State == STATE_B || State == STATE_COMM_C) { - - if (pilot == PILOT_12V) { // Disconnected? - setState(STATE_A); // switch to STATE_A - - } else if (pilot == PILOT_6V && ++StateTimer > 50) { // When switching from State B to C, make sure pilot is at 6V for at least 500ms - // Fixes https://github.com/dingo35/SmartEVSE-3.5/issues/40 - if ((DiodeCheck == 1) && (ErrorFlags == NO_ERROR) && (ChargeDelay == 0)) { - if (EVMeter && ResetKwh) { - EnergyMeterStart = EnergyEV; // store kwh measurement at start of charging. - EnergyCharged = EnergyEV - EnergyMeterStart; // Calculate Energy - ResetKwh = 0; // clear flag, will be set when disconnected from EVSE (State A) - } - - // Load Balancing : Node - if (LoadBl > 1) { - if (State != STATE_COMM_C) setState(STATE_COMM_C); // Send command to Master, followed by Charge Current - - // Load Balancing: Master or Disabled - } else { - BalancedMax[0] = ChargeCurrent; - if (IsCurrentAvailable()) { - - Balanced[0] = 0; // For correct baseload calculation set current to zero - CalcBalancedCurrent(1); // Calculate charge current for all connected EVSE's - - DiodeCheck = 0; // (local variable) - setState(STATE_C); // switch to STATE_C - if (!LCDNav) GLCD(); // Don't update the LCD if we are navigating the menu - // immediately update LCD (20ms) - } - else if (Mode == MODE_SOLAR) { // Not enough power: - ErrorFlags |= NO_SUN; // Not enough solar power - } else ErrorFlags |= LESS_6A; // Not enough power available - } - } - - // PILOT_9V - } else if (pilot == PILOT_9V) { - - StateTimer = 0; // Reset State B->C transition timer - if (ActivationMode == 0) { - setState(STATE_ACTSTART); - ActivationTimer = 3; - - SetCPDuty(0); // PWM off, channel 0, duty cycle 0% - // Control pilot static -12V - } - } - if (pilot == PILOT_DIODE) { - DiodeCheck = 1; // Diode found, OK - _LOG_A("Diode OK\n"); - timerAlarmWrite(timerA, PWM_5, false); // Enable Timer alarm, set to start of CP signal (5%) - } - - } - - // ############### EVSE State C1 ################# - - if (State == STATE_C1) - { - if (pilot == PILOT_12V) - { // Disconnected or connected to EV without PWM - setState(STATE_A); // switch to STATE_A - GLCD_init(); // Re-init LCD - } - else if (pilot == PILOT_9V) - { - setState(STATE_B1); // switch to State B1 - GLCD_init(); // Re-init LCD - } - } - - - if (State == STATE_ACTSTART && ActivationTimer == 0) { - setState(STATE_B); // Switch back to State B - ActivationMode = 255; // Disable ActivationMode - } - - if (State == STATE_COMM_C_OK) { - DiodeCheck = 0; - setState(STATE_C); // switch to STATE_C - // Don't update the LCD if we are navigating the menu - if (!LCDNav) GLCD(); // immediately update LCD - } - - // ############### EVSE State C ################# - - if (State == STATE_C) { - - if (pilot == PILOT_12V) { // Disconnected ? - setState(STATE_A); // switch back to STATE_A - GLCD_init(); // Re-init LCD - - } else if (pilot == PILOT_9V) { - setState(STATE_B); // switch back to STATE_B - DiodeCheck = 0; - GLCD_init(); // Re-init LCD (200ms delay) - // Mark EVSE as inactive (still State B) - } - - } // end of State C code - - // update LCD (every 1000ms) when not in the setup menu - if (LCDupdate) { - // This is also the ideal place for debug messages that should not be printed every 10ms - //_LOG_A("EVSEStates task free ram: %u\n", uxTaskGetStackHighWaterMark( NULL )); - GLCD(); - LCDupdate = 0; - } - - // Pause the task for 10ms - vTaskDelay(10 / portTICK_PERIOD_MS); - } // while(1) loop -} - -/** - * Send Energy measurement request over modbus - * - * @param uint8_t Meter - * @param uint8_t Address - * @param bool Export (if exported energy is requested) - */ -void requestEnergyMeasurement(uint8_t Meter, uint8_t Address, bool Export) { - switch (Meter) { - case EM_SOLAREDGE: - // Note: - // - SolarEdge uses 16-bit values, except for this measurement it uses 32bit int format - // - EM_SOLAREDGE should not be used for EV Energy Measurements - if (Export) ModbusReadInputRequest(Address, EMConfig[Meter].Function, EMConfig[Meter].ERegister_Exp, 2); - else ModbusReadInputRequest(Address, EMConfig[Meter].Function, EMConfig[Meter].ERegister, 2); - break; - case EM_ABB: - // Note: - // - ABB uses 64bit values for this register (size 2) - if (Export) requestMeasurement(Meter, Address, EMConfig[Meter].ERegister_Exp, 2); - else requestMeasurement(Meter, Address, EMConfig[Meter].ERegister, 2); - break; - case EM_FINDER_7E: - case EM_EASTRON3P: - case EM_EASTRON1P: - case EM_WAGO: - if (Export) requestMeasurement(Meter, Address, EMConfig[Meter].ERegister_Exp, 1); - else requestMeasurement(Meter, Address, EMConfig[Meter].ERegister, 1); - break; - case EM_EASTRON3P_INV: - if (Export) requestMeasurement(Meter, Address, EMConfig[Meter].ERegister, 1); - else requestMeasurement(Meter, Address, EMConfig[Meter].ERegister_Exp, 1); - break; - default: - if (!Export) //refuse to do a request on exported energy if the meter doesnt support it - requestMeasurement(Meter, Address, EMConfig[Meter].ERegister, 1); - break; - } -} - - -// Task that handles the Cable Lock and modbus -// -// called every 100ms -// -void Timer100ms(void * parameter) { - -unsigned int locktimer = 0, unlocktimer = 0, energytimer = 0; -uint8_t PollEVNode = NR_EVSES, updated = 0; - - - while(1) // infinite loop - { - // Check if the cable lock is used - if (Lock) { // Cable lock enabled? - // UnlockCable takes precedence over LockCable - if ((RFIDReader == 2 && Access_bit == 0) || // One RFID card can Lock/Unlock the charging socket (like a public charging station) - State == STATE_A) { // The charging socket is unlocked when unplugged from the EV - if (unlocktimer == 0) { // 600ms pulse - ACTUATOR_UNLOCK; - } else if (unlocktimer == 6) { - ACTUATOR_OFF; - } - if (unlocktimer++ > 7) { - if (digitalRead(PIN_LOCK_IN) == lock1 ) // still locked... - { - if (unlocktimer > 50) unlocktimer = 0; // try to unlock again in 5 seconds - } else unlocktimer = 7; - } - locktimer = 0; - // Lock Cable - } else if (State != STATE_A) { // Lock cable when connected to the EV - if (locktimer == 0) { // 600ms pulse - ACTUATOR_LOCK; - } else if (locktimer == 6) { - ACTUATOR_OFF; - } - if (locktimer++ > 7) { - if (digitalRead(PIN_LOCK_IN) == lock2 ) // still unlocked... - { - if (locktimer > 50) locktimer = 0; // try to lock again in 5 seconds - } else locktimer = 7; - } - unlocktimer = 0; - } - } - - - - // Every 2 seconds, request measurements from modbus meters - if (ModbusRequest) { // Slaves all have ModbusRequest at 0 so they never enter here - switch (ModbusRequest) { // State - case 1: // PV kwh meter - ModbusRequest++; - // fall through - case 2: // Sensorbox or kWh meter that measures -all- currents - if (MainsMeter && MainsMeter != EM_API) { // we don't want modbus meter currents to conflict with EM_API currents - _LOG_D("ModbusRequest %u: Request MainsMeter Measurement\n", ModbusRequest); - requestCurrentMeasurement(MainsMeter, MainsMeterAddress); - break; - } - ModbusRequest++; - // fall through - case 3: - // Find next online SmartEVSE - do { - PollEVNode++; - if (PollEVNode >= NR_EVSES) PollEVNode = 0; - } while(!Node[PollEVNode].Online); - - // Request Configuration if changed - if (Node[PollEVNode].ConfigChanged) { - _LOG_D("ModbusRequest %u: Request Configuration Node %u\n", ModbusRequest, PollEVNode); - // This will do the following: - // - Send a modbus request to the Node for it's EVmeter - // - Node responds with the Type and Address of the EVmeter - // - Master writes configuration flag reset value to Node - // - Node acks with the exact same message - // This takes around 50ms in total - requestNodeConfig(PollEVNode); - break; - } - ModbusRequest++; - // fall through - case 4: // EV kWh meter, Energy measurement (total charged kWh) - // Request Energy if EV meter is configured - if (Node[PollEVNode].EVMeter && Node[PollEVNode].EVMeter != EM_API) { - _LOG_D("ModbusRequest %u: Request Energy Node %u\n", ModbusRequest, PollEVNode); - requestEnergyMeasurement(Node[PollEVNode].EVMeter, Node[PollEVNode].EVAddress, 0); - break; - } - ModbusRequest++; - // fall through - case 5: // EV kWh meter, Power measurement (momentary power in Watt) - // Request Power if EV meter is configured - if (Node[PollEVNode].EVMeter && Node[PollEVNode].EVMeter != EM_API) { - requestMeasurement(Node[PollEVNode].EVMeter, Node[PollEVNode].EVAddress,EMConfig[Node[PollEVNode].EVMeter].PRegister, 1); - break; - } - ModbusRequest++; - // fall through - case 6: // Node 1 - case 7: - case 8: - case 9: - case 10: - case 11: - case 12: - if (LoadBl == 1) { - requestNodeStatus(ModbusRequest - 5u); // Master, Request Node 1-8 status - break; - } - ModbusRequest = 13; - // fall through - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - // Here we write State, Error, Mode and SolarTimer to Online Nodes - updated = 0; - if (LoadBl == 1) { - do { - if (Node[ModbusRequest - 12u].Online) { // Skip if not online - if (processAllNodeStates(ModbusRequest - 12u) ) { - updated = 1; // Node updated - break; - } - } - } while (++ModbusRequest < 20); - - } else ModbusRequest = 20; - if (updated) break; // break when Node updated - // fall through - case 20: // EV kWh meter, Current measurement - // Request Current if EV meter is configured - if (Node[PollEVNode].EVMeter && Node[PollEVNode].EVMeter != EM_API) { - _LOG_D("ModbusRequest %u: Request EVMeter Current Measurement Node %u\n", ModbusRequest, PollEVNode); - requestCurrentMeasurement(Node[PollEVNode].EVMeter, Node[PollEVNode].EVAddress); - break; - } - ModbusRequest++; - // fall through - case 21: - // Request active energy if Mainsmeter is configured - if (MainsMeter && (MainsMeter != EM_API) && (MainsMeter != EM_SENSORBOX) ) { // EM_API and Sensorbox do not support energy postings - energytimer++; //this ticks approx every second?!? - if (energytimer == 30) { - _LOG_D("ModbusRequest %u: Request MainsMeter Import Active Energy Measurement\n", ModbusRequest); - requestEnergyMeasurement(MainsMeter, MainsMeterAddress, 0); - break; - } - if (energytimer >= 60) { - _LOG_D("ModbusRequest %u: Request MainsMeter Export Active Energy Measurement\n", ModbusRequest); - requestEnergyMeasurement(MainsMeter, MainsMeterAddress, 1); - energytimer = 0; - break; - } - } - ModbusRequest++; - // fall through - default: - // slave never gets here - // what about normal mode with no meters attached? - CalcBalancedCurrent(0); - // No current left, or Overload (2x Maxmains)? - if (Mode && (NoCurrent > 2 || Imeasured > (MaxMains * 20))) { // I guess we don't want to set this flag in Normal mode, we just want to charge ChargeCurrent - // STOP charging for all EVSE's - // Display error message - ErrorFlags |= LESS_6A; //NOCURRENT; - // Broadcast Error code over RS485 - ModbusWriteSingleRequest(BROADCAST_ADR, 0x0001, ErrorFlags); - NoCurrent = 0; - } - if (LoadBl == 1 && !(ErrorFlags & CT_NOCOMM) ) BroadcastCurrent(); // When there is no Comm Error, Master sends current to all connected EVSE's - - if ((State == STATE_B || State == STATE_C) && !CPDutyOverride) SetCurrent(Balanced[0]); // set PWM output for Master //mind you, the !CPDutyOverride was not checked in Smart/Solar mode, but I think this was a bug! - printStatus(); //for debug purposes - ModbusRequest = 0; - //_LOG_A("Timer100ms task free ram: %u\n", uxTaskGetStackHighWaterMark( NULL )); - break; - } //switch - if (ModbusRequest) ModbusRequest++; - } - - - // Pause the task for 100ms - vTaskDelay(100 / portTICK_PERIOD_MS); - - } //while(1) loop - -} - -#if MQTT -void mqtt_receive_callback(const String topic, const String payload) { - if (topic == MQTTprefix + "/Set/Mode") { - if (payload == "Off") { - ToModemWaitStateTimer = 0; - ToModemDoneStateTimer = 0; - LeaveModemDoneStateTimer = 0; - setAccess(0); - } else if (payload == "Normal") { - setMode(MODE_NORMAL); - } else if (payload == "Solar") { - OverrideCurrent = 0; - setMode(MODE_SOLAR); - } else if (payload == "Smart") { - OverrideCurrent = 0; - setMode(MODE_SMART); - } - } else if (topic == MQTTprefix + "/Set/CurrentOverride") { - uint16_t RequestedCurrent = payload.toInt(); - if (RequestedCurrent == 0) { - OverrideCurrent = 0; - } else if (LoadBl < 2 && (Mode == MODE_NORMAL || Mode == MODE_SMART)) { // OverrideCurrent not possible on Slave - if (RequestedCurrent >= (MinCurrent * 10) && RequestedCurrent <= (MaxCurrent * 10)) { - OverrideCurrent = RequestedCurrent; - } - } - } else if (topic == MQTTprefix + "/Set/CurrentMaxSumMains" && LoadBl < 2) { - uint16_t RequestedCurrent = payload.toInt(); - if (RequestedCurrent == 0) { - MaxSumMains = 0; - } else if (RequestedCurrent >= 10 && RequestedCurrent <= 600) { - MaxSumMains = RequestedCurrent; - } - } else if (topic == MQTTprefix + "/Set/CPPWMOverride") { - int pwm = payload.toInt(); - if (pwm == -1) { - SetCPDuty(1024); - CP_ON; - CPDutyOverride = false; - } else if (pwm == 0) { - SetCPDuty(0); - CP_OFF; - CPDutyOverride = true; - } else if (pwm <= 1024) { - SetCPDuty(pwm); - CP_ON; - CPDutyOverride = true; - } - } else if (topic == MQTTprefix + "/Set/MainsMeter") { - if (MainsMeter != EM_API || LoadBl >= 2) - return; - - int32_t L1, L2, L3; - int n = sscanf(payload.c_str(), "%d:%d:%d", &L1, &L2, &L3); - - // MainsMeter can measure -200A to +200A per phase - if (n == 3 && (L1 > -2000 && L1 < 2000) && (L2 > -2000 && L2 < 2000) && (L3 > -2000 && L3 < 2000)) { - if (LoadBl < 2) - MainsMeterTimeout = COMM_TIMEOUT; - Irms[0] = L1; - Irms[1] = L2; - Irms[2] = L3; - CalcIsum(); - } - } else if (topic == MQTTprefix + "/Set/EVMeter") { - if (EVMeter != EM_API) - return; - - int32_t L1, L2, L3, W, WH; - int n = sscanf(payload.c_str(), "%d:%d:%d:%d:%d", &L1, &L2, &L3, &W, &WH); - - // We expect 5 values (and accept -1 for unknown values) - if (n == 5) { - if ((L1 > -1 && L1 < 1000) && (L2 > -1 && L2 < 1000) && (L3 > -1 && L3 < 1000)) { - // RMS currents - Irms_EV[0] = L1; - Irms_EV[1] = L2; - Irms_EV[2] = L3; - CalcImeasured_EV(); - EVMeterTimeout = COMM_EVTIMEOUT; - } - - if (W > -1) { - // Power measurement - PowerMeasured = W; - } - - if (WH > -1) { - // Energy measurement - EnergyEV = WH; - if (ResetKwh == 2) - EnergyMeterStart = EnergyEV; // At powerup, set EnergyEV to kwh meter value - EnergyCharged = EnergyEV - EnergyMeterStart; // Calculate Energy - RecomputeSoC(); // Recalculate SoC - } - } - } else if (topic == MQTTprefix + "/Set/HomeBatteryCurrent") { - if (LoadBl >= 2) - return; - homeBatteryCurrent = payload.toInt(); - homeBatteryLastUpdate = time(NULL); - } else if (topic == MQTTprefix + "/Set/RequiredEVCCID") { - strncpy(RequiredEVCCID, payload.c_str(), sizeof(RequiredEVCCID)); - if (preferences.begin("settings", false) ) { //false = write mode - preferences.putString("RequiredEVCCID", String(RequiredEVCCID)); - preferences.end(); - } - } - - // Make sure MQTT updates directly to prevent debounces - lastMqttUpdate = 10; -} - -//wrapper so MQTTClient::Publish works -static struct mg_connection *s_conn; // Client connection -class MQTTclient_t { -private: - struct mg_mqtt_opts default_opts; -public: - //constructor - MQTTclient_t () { - memset(&default_opts, 0, sizeof(default_opts)); - default_opts.qos = 0; - default_opts.retain = false; - } - - void publish(const String &topic, const String &payload, bool retained, int qos); - void subscribe(const String &topic, int qos); - bool connected; - void disconnect(void) { mg_mqtt_disconnect(s_conn, &default_opts); }; -}; - -void MQTTclient_t::publish(const String &topic, const String &payload, bool retained, int qos) { - if (s_conn) { - struct mg_mqtt_opts opts = default_opts; - opts.topic = mg_str(topic.c_str()); - opts.message = mg_str(payload.c_str()); - opts.qos = qos; - opts.retain = retained; - mg_mqtt_pub(s_conn, &opts); - } -} - -void MQTTclient_t::subscribe(const String &topic, int qos) { - if (s_conn) { - struct mg_mqtt_opts opts = default_opts; - opts.topic = mg_str(topic.c_str()); - opts.qos = qos; - mg_mqtt_sub(s_conn, &opts); - } -} - -MQTTclient_t MQTTclient; - -void SetupMQTTClient() { - // Set up subscriptions - MQTTclient.subscribe(MQTTprefix + "/Set/#",1); - MQTTclient.publish(MQTTprefix+"/connected", "online", true, 0); - - //publish MQTT discovery topics - //we need something to make all this JSON stuff readable, without doing all this assign and serialize stuff -#define jsn(x, y) String(R"(")") + x + R"(" : ")" + y + R"(")" - //jsn(device_class, current) expands to: - // R"("device_class" : "current")" - -#define jsna(x, y) String(R"(, )") + jsn(x, y) - //json add expansion, same as above but now with a comma prepended - - //first all device stuff: - const String device_payload = String(R"("device": {)") + jsn("model","SmartEVSE v3") + jsna("identifiers", MQTTprefix) + jsna("name", MQTTprefix) + jsna("manufacturer","Stegen") + jsna("configuration_url", "http://" + WiFi.localIP().toString().c_str()) + jsna("sw_version", String(VERSION)) + "}"; - //a device SmartEVSE-1001 consists of multiple entities, and an entity can be in the domains sensor, number, select etc. - String entity_suffix, entity_name, optional_payload; - - //some self-updating variables here: -#define entity_id String(MQTTprefix + "-" + entity_suffix) -#define entity_path String(MQTTprefix + "/" + entity_suffix) -#define entity_name(x) entity_name = x; entity_suffix = entity_name; entity_suffix.replace(" ", ""); - - //create template to announce an entity in it's own domain: -#define announce(x, entity_domain) entity_name(x); \ - MQTTclient.publish("homeassistant/" + String(entity_domain) + "/" + entity_id + "/config", \ - "{" \ - + jsn("name", entity_name) \ - + jsna("object_id", entity_id) \ - + jsna("unique_id", entity_id) \ - + jsna("state_topic", entity_path) \ - + jsna("availability_topic",String(MQTTprefix+"/connected")) \ - + ", " + device_payload + optional_payload \ - + "}", \ - true, 0); // Retain + QoS 0 - - //set the parameters for and announce sensors with device class 'current': - optional_payload = jsna("device_class","current") + jsna("unit_of_measurement","A") + jsna("value_template", R"({{ value | int / 10 }})"); - announce("Charge Current", "sensor"); - announce("Max Current", "sensor"); - if (MainsMeter) { - announce("Mains Current L1", "sensor"); - announce("Mains Current L2", "sensor"); - announce("Mains Current L3", "sensor"); - } - if (EVMeter) { - announce("EV Current L1", "sensor"); - announce("EV Current L2", "sensor"); - announce("EV Current L3", "sensor"); - } - if (homeBatteryLastUpdate) { - announce("Home Battery Current", "sensor"); - } - - if (Modem) { - //set the parameters for modem/SoC sensor entities: - optional_payload = jsna("unit_of_measurement","%") + jsna("value_template", R"({{ none if (value | int == -1) else (value | int) }})"); - announce("EV Initial SoC", "sensor"); - announce("EV Full SoC", "sensor"); - announce("EV Computed SoC", "sensor"); - announce("EV Remaining SoC", "sensor"); - - optional_payload = jsna("device_class","duration") + jsna("unit_of_measurement","m") + jsna("value_template", R"({{ none if (value | int == -1) else (value | int / 60) | round }})"); - announce("EV Time Until Full", "sensor"); - - optional_payload = jsna("device_class","energy") + jsna("unit_of_measurement","Wh") + jsna("value_template", R"({{ none if (value | int == -1) else (value | int) }})"); - announce("EV Energy Capacity", "sensor"); - announce("EV Energy Request", "sensor"); - - optional_payload = jsna("value_template", R"({{ none if (value == '') else value }})"); - announce("EVCCID", "sensor"); - optional_payload = jsna("state_topic", String(MQTTprefix + "/RequiredEVCCID")) + jsna("command_topic", String(MQTTprefix + "/Set/RequiredEVCCID")); - announce("Required EVCCID", "text"); - } - - if (EVMeter) { - //set the parameters for and announce other sensor entities: - optional_payload = jsna("device_class","power") + jsna("unit_of_measurement","W"); - announce("EV Charge Power", "sensor"); - optional_payload = jsna("device_class","energy") + jsna("unit_of_measurement","Wh"); - announce("EV Energy Charged", "sensor"); - optional_payload = jsna("device_class","energy") + jsna("unit_of_measurement","Wh") + jsna("state_class","total_increasing"); - announce("EV Total Energy Charged", "sensor"); - } - - //set the parameters for and announce sensor entities without device_class or unit_of_measurement: - optional_payload = ""; - announce("EV Plug State", "sensor"); - announce("Access", "sensor"); - announce("State", "sensor"); - announce("RFID", "sensor"); - announce("RFIDLastRead", "sensor"); - - //set the parameters for and announce diagnostic sensor entities: - optional_payload = jsna("entity_category","diagnostic"); - announce("Error", "sensor"); - announce("WiFi SSID", "sensor"); - announce("WiFi BSSID", "sensor"); - optional_payload = jsna("entity_category","diagnostic") + jsna("device_class","signal_strength") + jsna("unit_of_measurement","dBm"); - announce("WiFi RSSI", "sensor"); - optional_payload = jsna("entity_category","diagnostic") + jsna("device_class","temperature") + jsna("unit_of_measurement","°C"); - announce("ESP Temp", "sensor"); - optional_payload = jsna("entity_category","diagnostic") + jsna("device_class","duration") + jsna("unit_of_measurement","s") + jsna("entity_registry_enabled_default","False"); - announce("ESP Uptime", "sensor"); - -#if MODEM - if (Modem) { - optional_payload = jsna("unit_of_measurement","%") + jsna("value_template", R"({{ (value | int / 1024 * 100) | round(0) }})"); - announce("CP PWM", "sensor"); - - optional_payload = jsna("value_template", R"({{ none if (value | int == -1) else (value | int / 1024 * 100) | round }})"); - optional_payload += jsna("command_topic", String(MQTTprefix + "/Set/CPPWMOverride")) + jsna("min", "-1") + jsna("max", "100") + jsna("mode","slider"); - optional_payload += jsna("command_template", R"({{ (value | int * 1024 / 100) | round }})"); - announce("CP PWM Override", "number"); - } -#endif - //set the parameters for and announce select entities, overriding automatic state_topic: - optional_payload = jsna("state_topic", String(MQTTprefix + "/Mode")) + jsna("command_topic", String(MQTTprefix + "/Set/Mode")); - optional_payload += String(R"(, "options" : ["Off", "Normal", "Smart", "Solar"])"); - announce("Mode", "select"); - - //set the parameters for and announce number entities: - optional_payload = jsna("command_topic", String(MQTTprefix + "/Set/CurrentOverride")) + jsna("min", "0") + jsna("max", MaxCurrent ) + jsna("mode","slider"); - optional_payload += jsna("value_template", R"({{ none if (value | int == 0) else (value | int / 10) }})") + jsna("command_template", R"({{ value | int * 10 }})"); - announce("Charge Current Override", "number"); -} - -void mqttPublishData() { - lastMqttUpdate = 0; - - if (MainsMeter) { - MQTTclient.publish(MQTTprefix + "/MainsCurrentL1", String(Irms[0]), false, 0); - MQTTclient.publish(MQTTprefix + "/MainsCurrentL2", String(Irms[1]), false, 0); - MQTTclient.publish(MQTTprefix + "/MainsCurrentL3", String(Irms[2]), false, 0); - } - if (EVMeter) { - MQTTclient.publish(MQTTprefix + "/EVCurrentL1", String(Irms_EV[0]), false, 0); - MQTTclient.publish(MQTTprefix + "/EVCurrentL2", String(Irms_EV[1]), false, 0); - MQTTclient.publish(MQTTprefix + "/EVCurrentL3", String(Irms_EV[2]), false, 0); - } - MQTTclient.publish(MQTTprefix + "/ESPUptime", String((esp_timer_get_time() / 1000000)), false, 0); - MQTTclient.publish(MQTTprefix + "/ESPTemp", String(TempEVSE), false, 0); - MQTTclient.publish(MQTTprefix + "/Mode", Access_bit == 0 ? "Off" : Mode > 3 ? "N/A" : StrMode[Mode], true, 0); - MQTTclient.publish(MQTTprefix + "/MaxCurrent", String(MaxCurrent * 10), true, 0); - MQTTclient.publish(MQTTprefix + "/ChargeCurrent", String(Balanced[0]), true, 0); - MQTTclient.publish(MQTTprefix + "/ChargeCurrentOverride", String(OverrideCurrent), true, 0); - MQTTclient.publish(MQTTprefix + "/Access", String(StrAccessBit[Access_bit]), true, 0); - MQTTclient.publish(MQTTprefix + "/RFID", !RFIDReader ? "Not Installed" : RFIDstatus >= 8 ? "NOSTATUS" : StrRFIDStatusWeb[RFIDstatus], true, 0); - if (RFIDReader) { - char buf[13]; - sprintf(buf, "%02X%02X%02X%02X%02X%02X", RFID[1], RFID[2], RFID[3], RFID[4], RFID[5], RFID[6]); - MQTTclient.publish(MQTTprefix + "/RFIDLastRead", buf, true, 0); - } - MQTTclient.publish(MQTTprefix + "/State", getStateNameWeb(State), true, 0); - MQTTclient.publish(MQTTprefix + "/Error", getErrorNameWeb(ErrorFlags), true, 0); - MQTTclient.publish(MQTTprefix + "/EVPlugState", (pilot != PILOT_12V) ? "Connected" : "Disconnected", true, 0); - MQTTclient.publish(MQTTprefix + "/WiFiSSID", String(WiFi.SSID()), true, 0); - MQTTclient.publish(MQTTprefix + "/WiFiBSSID", String(WiFi.BSSIDstr()), true, 0); - MQTTclient.publish(MQTTprefix + "/WiFiRSSI", String(WiFi.RSSI()), false, 0); -#if MODEM - if (Modem) { - MQTTclient.publish(MQTTprefix + "/CPPWM", String(CurrentPWM), false, 0); - MQTTclient.publish(MQTTprefix + "/CPPWMOverride", String(CPDutyOverride ? String(CurrentPWM) : "-1"), true, 0); - MQTTclient.publish(MQTTprefix + "/EVInitialSoC", String(InitialSoC), true, 0); - MQTTclient.publish(MQTTprefix + "/EVFullSoC", String(FullSoC), true, 0); - MQTTclient.publish(MQTTprefix + "/EVComputedSoC", String(ComputedSoC), true, 0); - MQTTclient.publish(MQTTprefix + "/EVRemainingSoC", String(RemainingSoC), true, 0); - MQTTclient.publish(MQTTprefix + "/EVTimeUntilFull", String(TimeUntilFull), false, 0); - MQTTclient.publish(MQTTprefix + "/EVEnergyCapacity", String(EnergyCapacity), true, 0); - MQTTclient.publish(MQTTprefix + "/EVEnergyRequest", String(EnergyRequest), true, 0); - MQTTclient.publish(MQTTprefix + "/EVCCID", String(EVCCID), true, 0); - MQTTclient.publish(MQTTprefix + "/RequiredEVCCID", String(RequiredEVCCID), true, 0); - } -#endif - if (EVMeter) { - MQTTclient.publish(MQTTprefix + "/EVChargePower", String(PowerMeasured), false, 0); - MQTTclient.publish(MQTTprefix + "/EVEnergyCharged", String(EnergyCharged), true, 0); - MQTTclient.publish(MQTTprefix + "/EVTotalEnergyCharged", String(EnergyEV), false, 0); - } - if (homeBatteryLastUpdate) - MQTTclient.publish(MQTTprefix + "/HomeBatteryCurrent", String(homeBatteryCurrent), false, 0); -} -#endif - -// task 1000msTimer -void Timer1S(void * parameter) { - - uint8_t Broadcast = 1; - //uint8_t Timer5sec = 0; - uint8_t x; - - while(1) { // infinite loop - - if (BacklightTimer) BacklightTimer--; // Decrease backlight counter every second. - - // wait for Activation mode to start - if (ActivationMode && ActivationMode != 255) { - ActivationMode--; // Decrease ActivationMode every second. - } - - // activation Mode is active - if (ActivationTimer) ActivationTimer--; // Decrease ActivationTimer every second. -#if MODEM - if (State == STATE_MODEM_REQUEST){ - if (ToModemWaitStateTimer) ToModemWaitStateTimer--; - else{ - setState(STATE_MODEM_WAIT); // switch to state Modem 2 - GLCD(); - } - } - - if (State == STATE_MODEM_WAIT){ - if (ToModemDoneStateTimer) ToModemDoneStateTimer--; - else{ - setState(STATE_MODEM_DONE); - GLCD(); - } - } - - if (State == STATE_MODEM_DONE){ - if (LeaveModemDoneStateTimer) LeaveModemDoneStateTimer--; - else{ - // Here's what happens: - // - State STATE_MODEM_DONE set the CP pin off, to reset connection with car. Since some cars don't support AC charging via ISO15118, SoC is extracted via DC. - // - Negotiation fails between pyPLC and car. Some cars then won't accept charge via AC it seems after, so we just "re-plug" and start charging without the modem communication protocol - // - State STATE_B will enable CP pin again, if disabled. - // This stage we are now in is just before we enable CP_PIN and resume via STATE_B - - // Reset CP to idle & turn off, it will be turned on again later for another try - SetCPDuty(1024); - CP_OFF; - - // Check whether the EVCCID matches the one required - if (strcmp(RequiredEVCCID, "") == 0 || strcmp(RequiredEVCCID, EVCCID) == 0) { - // We satisfied the EVCCID requirements, skip modem stages next time - ModemStage = 1; - - setState(STATE_B); // switch to STATE_B - GLCD(); // Re-init LCD (200ms delay) - } else { - // We actually do not want to continue charging and re-start at modem request after 60s - ModemStage = 0; - LeaveModemDeniedStateTimer = 60; - - // Change to MODEM_DENIED state - setState(STATE_MODEM_DENIED); - GLCD(); // Re-init LCD (200ms delay) - } - } - } - - if (State == STATE_MODEM_DENIED){ - if (LeaveModemDeniedStateTimer) LeaveModemDeniedStateTimer--; - else{ - LeaveModemDeniedStateTimer = -1; // reset ModemStateDeniedTimer - setState(STATE_A); // switch to STATE_B - CP_ON; - GLCD(); // Re-init LCD (200ms delay) - } - } - -#endif - if (State == STATE_C1) { - if (C1Timer) C1Timer--; // if the EV does not stop charging in 6 seconds, we will open the contactor. - else { - _LOG_A("State C1 timeout!\n"); - setState(STATE_B1); // switch back to STATE_B1 - GLCD_init(); // Re-init LCD (200ms delay) - } - } - -#if MODEM - // Normally, the modem is enabled when Modem == Experiment. However, after a succesfull communication has been set up, EVSE will restart communication by replugging car and moving back to state B. - // This time, communication is not initiated. When a car is disconnected, we want to enable the modem states again, but using 12V signal is not reliable (we just "replugged" via CP pin, remember). - // This counter just enables the state after 3 seconds of success. - if (DisconnectTimeCounter >= 0){ - DisconnectTimeCounter++; - } - - if (DisconnectTimeCounter > 3){ - pilot = Pilot(); - if (pilot == PILOT_12V){ - DisconnectTimeCounter = -1; - DisconnectEvent(); - } else{ // Run again - DisconnectTimeCounter = 0; - } - } -#endif - - // once a second, measure temperature - // range -40 .. +125C - TempEVSE = TemperatureSensor(); - - - // Check if there is a RFID card in front of the reader - CheckRFID(); - - - // When Solar Charging, once the current drops to MINcurrent a timer is started. - // Charging is stopped when the timer reaches the time set in 'StopTime' (in minutes) - // Except when Stoptime =0, then charging will continue. - - if (SolarStopTimer) { - SolarStopTimer--; - if (SolarStopTimer == 0) { - - if (State == STATE_C) setState(STATE_C1); // tell EV to stop charging - ErrorFlags |= NO_SUN; // Set error: NO_SUN - } - } - - if (ChargeDelay) ChargeDelay--; // Decrease Charge Delay counter - if (PilotDisconnectTime) PilotDisconnectTime--; // Decrease PilotDisconnectTimer - - if (AccessTimer && State == STATE_A) { - if (--AccessTimer == 0) { - setAccess(false); // re-lock EVSE - } - } else AccessTimer = 0; // Not in state A, then disable timer - - if ((TempEVSE < (maxTemp - 10)) && (ErrorFlags & TEMP_HIGH)) { // Temperature below limit? - ErrorFlags &= ~TEMP_HIGH; // clear Error - } - - if ( (ErrorFlags & (LESS_6A|NO_SUN) ) && (LoadBl < 2) && (IsCurrentAvailable())) { - ErrorFlags &= ~LESS_6A; // Clear Errors if there is enough current available, and Load Balancing is disabled or we are Master - ErrorFlags &= ~NO_SUN; - _LOG_I("No sun/current Errors Cleared.\n"); - } - - - // Charge timer - for (x = 0; x < NR_EVSES; x++) { - if (BalancedState[x] == STATE_C) { - Node[x].IntTimer++; - Node[x].Timer++; - } else Node[x].IntTimer = 0; // Reset IntervalTime when not charging - } - - if (MainsMeter) { - if ( MainsMeterTimeout == 0 && !(ErrorFlags & CT_NOCOMM) && Mode != MODE_NORMAL) { // timeout if current measurement takes > 10 secs - // In Normal mode do not timeout; there might be MainsMeter/EVMeter configured that can be retrieved through the API, - // but in Normal mode we just want to charge ChargeCurrent, irrespective of communication problems. - ErrorFlags |= CT_NOCOMM; - setStatePowerUnavailable(); - _LOG_W("Error, MainsMeter communication error!\n"); - } else { - if (MainsMeterTimeout) MainsMeterTimeout--; - } - } else - MainsMeterTimeout = COMM_TIMEOUT; - - if (EVMeter) { - if ( EVMeterTimeout == 0 && !(ErrorFlags & EV_NOCOMM) && Mode != MODE_NORMAL) { - ErrorFlags |= EV_NOCOMM; - setStatePowerUnavailable(); - _LOG_W("Error, EV Meter communication error!\n"); - } else { - if (EVMeterTimeout) EVMeterTimeout--; - } - } else - EVMeterTimeout = COMM_EVTIMEOUT; - - // Clear communication error, if present - if ((ErrorFlags & CT_NOCOMM) && MainsMeterTimeout) ErrorFlags &= ~CT_NOCOMM; - - if ((ErrorFlags & EV_NOCOMM) && EVMeterTimeout) ErrorFlags &= ~EV_NOCOMM; - - - if (TempEVSE > maxTemp && !(ErrorFlags & TEMP_HIGH)) // Temperature too High? - { - ErrorFlags |= TEMP_HIGH; - setStatePowerUnavailable(); - _LOG_W("Error, temperature %i C !\n", TempEVSE); - } - - if (ErrorFlags & (NO_SUN | LESS_6A)) { - if (ChargeDelay == 0) { - if (Mode == MODE_SOLAR) { _LOG_I("Waiting for Solar power...\n"); } - else { _LOG_I("Not enough current available!\n"); } - } - setStatePowerUnavailable(); - ChargeDelay = CHARGEDELAY; // Set Chargedelay - } - - // set flag to update the LCD once every second - LCDupdate = 1; - - // Every two seconds request measurement data from sensorbox/kwh meters. - // and send broadcast to Node controllers. - if (LoadBl < 2 && !Broadcast--) { // Load Balancing mode: Master or Disabled - if (!ModbusRequest) ModbusRequest = 1; // Start with state 1, also in Normal mode we want MainsMeter and EVmeter updated - //timeout = COMM_TIMEOUT; not sure if necessary, statement was missing in original code // reset timeout counter (not checked for Master) - Broadcast = 1; // repeat every two seconds - } - - // for Slave modbusrequest loop is never called, so we have to show debug info here... - if (LoadBl > 1) - printStatus(); //for debug purposes - - //_LOG_A("Timer1S task free ram: %u\n", uxTaskGetStackHighWaterMark( NULL )); - - -#if MQTT - if (lastMqttUpdate++ >= 10) { - // Publish latest data, every 10 seconds - // We will try to publish data faster if something has changed - mqttPublishData(); - } -#endif - - // Pause the task for 1 Sec - vTaskDelay(1000 / portTICK_PERIOD_MS); - - } // while(1) -} - -/** - * Read energy measurement from modbus - * - * @param pointer to buf - * @param uint8_t Meter - * @return signed int Energy (Wh) - */ -signed int receiveEnergyMeasurement(uint8_t *buf, uint8_t Meter) { - switch (Meter) { - case EM_ABB: - // Note: - // - ABB uses 32-bit values, except for this measurement it uses 64bit unsigned int format - // We skip the first 4 bytes (effectivaly creating uint 32). Will work as long as the value does not exeed roughly 20 million - return receiveMeasurement(buf, 1, EMConfig[Meter].Endianness, MB_DATATYPE_INT32, EMConfig[Meter].EDivisor-3); - case EM_SOLAREDGE: - // Note: - // - SolarEdge uses 16-bit values, except for this measurement it uses 32bit int format - // - EM_SOLAREDGE should not be used for EV Energy Measurements - return receiveMeasurement(buf, 0, EMConfig[Meter].Endianness, MB_DATATYPE_INT32, EMConfig[Meter].EDivisor - 3); - default: - return receiveMeasurement(buf, 0, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, EMConfig[Meter].EDivisor - 3); - } -} - -/** - * Read Power measurement from modbus - * - * @param pointer to buf - * @param uint8_t Meter - * @return signed int Power (W) - */ -signed int receivePowerMeasurement(uint8_t *buf, uint8_t Meter) { - switch (Meter) { - case EM_SOLAREDGE: - { - // Note: - // - SolarEdge uses 16-bit values, with a extra 16-bit scaling factor - // - EM_SOLAREDGE should not be used for EV power measurements, only PV power measurements are supported - int scalingFactor = -(int)receiveMeasurement( - buf, - 1, - EMConfig[Meter].Endianness, - EMConfig[Meter].DataType, - 0 - ); - return receiveMeasurement(buf, 0, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, scalingFactor); - } - case EM_EASTRON3P_INV: - return -receiveMeasurement(buf, 0, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, EMConfig[Meter].PDivisor); - default: - return receiveMeasurement(buf, 0, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, EMConfig[Meter].PDivisor); - } -} - - -// Modbus functions - -// stores energy responses; returns 0 if it stored a value, returns 1 if the response didnt match -int StoreEnergyResponse(uint8_t Meter, int32_t& Import, int32_t& Export ) { - if (MB.Register == EMConfig[Meter].ERegister) { - //import active energy - if (Meter == EM_EASTRON3P_INV) - Export = receiveEnergyMeasurement(MB.Data, Meter); - else - Import = receiveEnergyMeasurement(MB.Data, Meter); - return 0; - } - else if (MB.Register == EMConfig[Meter].ERegister_Exp) { - //export active energy - if (Meter == EM_EASTRON3P_INV) - Import = receiveEnergyMeasurement(MB.Data, Meter); - else - Export = receiveEnergyMeasurement(MB.Data, Meter); - return 0; - } - return 1; -} - -// Monitor EV Meter responses, and update Enery and Power and Current measurements -// Both the Master and Nodes will receive their own EV meter measurements here. -// Does not send any data back. -// -ModbusMessage MBEVMeterResponse(ModbusMessage request) { - - uint8_t x; - int32_t EV[3]={0, 0, 0}; - - ModbusDecode( (uint8_t*)request.data(), request.size()); - - if (MB.Type == MODBUS_RESPONSE) { - // _LOG_A("EVMeter Response\n"); - // Packet from EV electric meter - if (!StoreEnergyResponse(EVMeter, EV_import_active_energy, EV_export_active_energy)) { - // Energy measurement - EnergyEV = EV_import_active_energy - EV_export_active_energy; - if (ResetKwh == 2) EnergyMeterStart = EnergyEV; // At powerup, set EnergyEV to kwh meter value - EnergyCharged = EnergyEV - EnergyMeterStart; // Calculate Energy - if (Modem) - RecomputeSoC(); - } else if (MB.Register == EMConfig[EVMeter].PRegister) { - // Power measurement - PowerMeasured = receivePowerMeasurement(MB.Data, EVMeter); - } else if (MB.Register == EMConfig[EVMeter].IRegister) { - // Current measurement - x = receiveCurrentMeasurement(MB.Data, EVMeter, EV ); - - if (x) EVMeterTimeout = COMM_EVTIMEOUT; // only reset EVtimeout when data is ok - for (x = 0; x < 3; x++) { - // CurrentMeter and PV values are MILLI AMPERE - Irms_EV[x] = (signed int)(EV[x] / 100); // Convert to AMPERE * 10 - } - CalcImeasured_EV(); - } - } - // As this is a response to an earlier request, do not send response. - - return NIL_RESPONSE; -} - -// -// Monitor Mains Meter responses, and update Irms values -// Does not send any data back. -// Only runs on master, slave gets the MainsMeter currents via MBbroadcast -ModbusMessage MBMainsMeterResponse(ModbusMessage request) { - uint8_t x; - ModbusMessage response; // response message to be sent back - - ModbusDecode( (uint8_t*)request.data(), request.size()); - - // process only Responses, as otherwise MB.Data is unitialized, and it will throw an exception - if (MB.Type == MODBUS_RESPONSE) { - if (MB.Register == EMConfig[MainsMeter].IRegister) { - - //_LOG_A("Mains Meter Response\n"); - x = receiveCurrentMeasurement(MB.Data, MainsMeter, CM); - if (x) MainsMeterTimeout = COMM_TIMEOUT; // only reset timeout when data is ok - - // Convert Irms from mA to (A * 10) - for (x = 0; x < 3; x++) { - // Calculate difference of Mains and PV electric meter - Irms[x] = (signed int)(CM[x] / 100); // Convert to AMPERE * 10 - } - CalcIsum(); - } - else - StoreEnergyResponse(MainsMeter, Mains_import_active_energy, Mains_export_active_energy); - } - - // As this is a response to an earlier request, do not send response. - return NIL_RESPONSE; -} - - -// Request handler for modbus messages addressed to -this- Node/Slave EVSE. -// Sends response back to Master -// -ModbusMessage MBNodeRequest(ModbusMessage request) { - ModbusMessage response; // response message to be sent back - uint8_t ItemID; - uint8_t i, OK = 0; - uint16_t value, values[MODBUS_MAX_REGISTER_READ]; - - // Check if the call is for our current ServerID, or maybe for an old ServerID? - if (LoadBl != request.getServerID()) return NIL_RESPONSE; - - - ModbusDecode( (uint8_t*)request.data(), request.size()); - ItemID = mapModbusRegister2ItemID(); - - switch (MB.Function) { - case 0x03: // (Read holding register) - case 0x04: // (Read input register) - // ReadItemValueResponse(); - if (ItemID) { - response.add(MB.Address, MB.Function, (uint8_t)(MB.RegisterCount * 2)); - - _LOG_D("Node answering NodeStatus request"); - for (i = 0; i < MB.RegisterCount; i++) { - values[i] = getItemValue(ItemID + i); - response.add(values[i]); - _LOG_V_NO_FUNC(" value[%u]=%u", i, values[i]); - } - _LOG_D_NO_FUNC("\n"); - //ModbusReadInputResponse(MB.Address, MB.Function, values, MB.RegisterCount); - } else { - response.setError(MB.Address, MB.Function, ILLEGAL_DATA_ADDRESS); - } - break; - case 0x06: // (Write single register) - //WriteItemValueResponse(); - if (ItemID) { - OK = setItemValue(ItemID, MB.Value); - } - - if (OK && ItemID < STATUS_STATE) write_settings(); - - if (MB.Address != BROADCAST_ADR || LoadBl == 0) { - if (!ItemID) { - response.setError(MB.Address, MB.Function, ILLEGAL_DATA_ADDRESS); - } else if (!OK) { - response.setError(MB.Address, MB.Function, ILLEGAL_DATA_VALUE); - } else { - return ECHO_RESPONSE; - } - } - break; - case 0x10: // (Write multiple register)) - // WriteMultipleItemValueResponse(); - if (ItemID) { - for (i = 0; i < MB.RegisterCount; i++) { - value = (MB.Data[i * 2] <<8) | MB.Data[(i * 2) + 1]; - OK += setItemValue(ItemID + i, value); - } - } - - if (OK && ItemID < STATUS_STATE) write_settings(); - - if (MB.Address != BROADCAST_ADR || LoadBl == 0) { - if (!ItemID) { - response.setError(MB.Address, MB.Function, ILLEGAL_DATA_ADDRESS); - } else if (!OK) { - response.setError(MB.Address, MB.Function, ILLEGAL_DATA_VALUE); - } else { - response.add(MB.Address, MB.Function, (uint16_t)MB.Register, (uint16_t)OK); - } - } - break; - default: - break; - } - - return response; -} - -// The Node/Server receives a broadcast message from the Master -// Does not send any data back. -ModbusMessage MBbroadcast(ModbusMessage request) { - uint8_t ItemID, i, OK = 0; - uint16_t value; - int16_t combined; - - ModbusDecode( (uint8_t*)request.data(), request.size()); - ItemID = mapModbusRegister2ItemID(); - - if (MB.Type == MODBUS_REQUEST) { - - // Broadcast or addressed to this device - switch (MB.Function) { - // FC 03 and 04 are not possible with broadcast messages. - case 0x06: // (Write single register) - //WriteItemValueResponse(); - if (ItemID) { - OK = setItemValue(ItemID, MB.Value); - } - - if (OK && ItemID < STATUS_STATE) write_settings(); - _LOG_V("Broadcast received FC06 Item:%u val:%u\n",ItemID, MB.Value); - break; - case 0x10: // (Write multiple register)) - // 0x0020: Balance currents - if (MB.Register == 0x0020 && LoadBl > 1) { // Message for Node(s) - Balanced[0] = (MB.Data[(LoadBl - 1) * 2] <<8) | MB.Data[(LoadBl - 1) * 2 + 1]; - if (Balanced[0] == 0 && State == STATE_C) setState(STATE_C1); // tell EV to stop charging if charge current is zero - else if ((State == STATE_B) || (State == STATE_C)) SetCurrent(Balanced[0]); // Set charge current, and PWM output - MainsMeterTimeout = COMM_TIMEOUT; // reset 10 second timeout - _LOG_V("Broadcast received, Node %.1f A, MainsMeter Irms ", (float) Balanced[0]/10); - - //now decode registers 0x0028-0x002A - if (MB.DataLength >= 16+6) { - Isum = 0; - for (i=0; i<3; i++ ) { - combined = (MB.Data[(i * 2) + 16] <<8) + MB.Data[(i * 2) + 17]; - Isum = Isum + combined; - Irms[i] = combined; - _LOG_V_NO_FUNC("L%i=%.1fA,", i+1, (float)Irms[i]/10); - } - _LOG_V_NO_FUNC("\n"); - } - } else { - - //WriteMultipleItemValueResponse(); - if (ItemID) { - for (i = 0; i < MB.RegisterCount; i++) { - value = (MB.Data[i * 2] <<8) | MB.Data[(i * 2) + 1]; - OK += setItemValue(ItemID + i, value); - } - } - - if (OK && ItemID < STATUS_STATE) write_settings(); - _LOG_V("Other Broadcast received\n"); - } - break; - default: - break; - } - } - - // As it is a broadcast message, do not send response. - return NIL_RESPONSE; -} - -// Data handler for Master -// Responses from Slaves/Nodes are handled here -void MBhandleData(ModbusMessage msg, uint32_t token) -{ - uint8_t Address = msg.getServerID(); // returns Server ID or 0 if MM_data is shorter than 3 - if (Address == MainsMeterAddress) { - //_LOG_A("MainsMeter data\n"); - MBMainsMeterResponse(msg); - } else if (Address == EVMeterAddress) { - //_LOG_A("EV Meter data\n"); - MBEVMeterResponse(msg); - // Only responses to FC 03/04 are handled here. FC 06/10 response is only a acknowledge. - } else { - //_LOG_V("Received Packet with ServerID=%i, FunctionID=%i, token=%08x.\n", msg.getServerID(), msg.getFunctionCode(), token); - ModbusDecode( (uint8_t*)msg.data(), msg.size()); - // ModbusDecode does NOT always decodes the register correctly. - // This bug manifested itself as the : - - // (Timer100ms)(C1) ModbusRequest 4: Request Configuration Node 1 - // (D) (ModbusSend8)(C1) Sent packet address: 02, function: 04, reg: 0108, data: 0002. - // (D) (ModbusDecode)(C0) Received packet (7 bytes) 02 04 04 00 00 00 0c - // (V) (ModbusDecode)(C0) valid Modbus packet: Address 02 Function 04 Register 0000 Response - // (D) (receiveNodeStatus)(C0) ReceivedNode[1]Status State:0 Error:12, BalancedMax:2530, Mode:186, ConfigChanged:253. - - // The response is the response for a request of node config 0x0108, but is interpreted as a request for node status 0x0000 - // - // Using a global variable struct ModBus MB is not a good idea, but localizing it does not - // solve the problem. - - // Luckily we have coded the register in the token we sent.... - // token: first byte address, second byte function, third and fourth reg - uint8_t token_function = (token & 0x00FF0000) >> 16; - uint8_t token_address = token >> 24; - if (token_address != MB.Address) { - _LOG_A("ERROR: Address=%u, MB.Address=%u, token_address=%u.\n", Address, MB.Address, token_address); - } - if (token_function != MB.Function) { - _LOG_A("ERROR: MB.Function=%u, token_function=%u.\n", MB.Function, token_function); - } - uint16_t reg = (token & 0x0000FFFF); - MB.Register = reg; - - if (MB.Address > 1 && MB.Address <= NR_EVSES && (MB.Function == 03 || MB.Function == 04)) { - - // Packet from Node EVSE - if (MB.Register == 0x0000) { - // Node status - // _LOG_A("Node Status received\n"); - receiveNodeStatus(MB.Data, MB.Address - 1u); - } else if (MB.Register == 0x0108) { - // Node EV meter settings - // _LOG_A("Node EV Meter settings received\n"); - receiveNodeConfig(MB.Data, MB.Address - 1u); - } - } - } - -} - - -void MBhandleError(Error error, uint32_t token) -{ - // ModbusError wraps the error code and provides a readable error message for it - ModbusError me(error); - uint8_t address, function; - uint16_t reg; - address = token >> 24; - function = (token >> 16); - reg = token & 0xFFFF; - - if (LoadBl == 1 && address>=2 && address <=8 && function == 4 && reg == 0) { //master sends out messages to nodes 2-8, if no EVSE is connected with that address - //a timeout will be generated. This is legit! - _LOG_V("Error response: %02X - %s, address: %02x, function: %02x, reg: %04x.\n", error, (const char *)me, address, function, reg); - } - else { - _LOG_A("Error response: %02X - %s, address: %02x, function: %02x, reg: %04x.\n", error, (const char *)me, address, function, reg); - } -} - - - -void ConfigureModbusMode(uint8_t newmode) { - - _LOG_A("changing LoadBl from %u to %u\n",LoadBl, newmode); - - if ((LoadBl < 2 && newmode > 1) || (LoadBl > 1 && newmode < 2) || (newmode == 255) ) { - - if (newmode != 255 ) LoadBl = newmode; - - // Setup Modbus workers for Node - if (LoadBl > 1 ) { - - _LOG_A("Setup MBserver/Node workers, end Master/Client\n"); - // Stop Master background task (if active) - if (newmode != 255 ) MBclient.end(); - _LOG_A("ConfigureModbusMode1 task free ram: %u\n", uxTaskGetStackHighWaterMark( NULL )); - - // Register worker. at serverID 'LoadBl', all function codes - MBserver.registerWorker(LoadBl, ANY_FUNCTION_CODE, &MBNodeRequest); - // Also add handler for all broadcast messages from Master. - MBserver.registerWorker(BROADCAST_ADR, ANY_FUNCTION_CODE, &MBbroadcast); - - if (EVMeter && EVMeter != EM_API) MBserver.registerWorker(EVMeterAddress, ANY_FUNCTION_CODE, &MBEVMeterResponse); - - // Start ModbusRTU Node background task - MBserver.begin(Serial1); - - } else if (LoadBl < 2 ) { - // Setup Modbus workers as Master - // Stop Node background task (if active) - _LOG_A("Setup Modbus as Master/Client, stop Server/Node handler\n"); - - if (newmode != 255) MBserver.end(); - _LOG_A("ConfigureModbusMode2 task free ram: %u\n", uxTaskGetStackHighWaterMark( NULL )); - - MBclient.setTimeout(85); // Set modbus timeout to 85ms. 15ms lower then modbusRequestloop time of 100ms. - MBclient.onDataHandler(&MBhandleData); - MBclient.onErrorHandler(&MBhandleError); - // Start ModbusRTU Master background task - MBclient.begin(Serial1, 1); //pinning it to core1 reduces modbus problems - } - } else if (newmode > 1) { - // Register worker. at serverID 'LoadBl', all function codes - _LOG_A("Registering new LoadBl worker at id %u\n", newmode); - LoadBl = newmode; - MBserver.registerWorker(newmode, ANY_FUNCTION_CODE, &MBNodeRequest); - } - -} - - -// Generate random password for AP -void SetRandomAPpassword(void) { - uint8_t i, c; - // Set random password - for (i=0; i<8 ;i++) { - c = random(16) + '0'; - if (c > '9') c += 'a'-'9'-1; - APpassword[i] = c; - } -} - -/** - * Validate setting ranges and dependencies - */ -void validate_settings(void) { - uint8_t i; - uint16_t value; - - // If value is out of range, reset it to default value - for (i = MENU_ENTER + 1;i < MENU_EXIT; i++){ - value = getItemValue(i); - // _LOG_A("value %s set to %i\n",MenuStr[i].LCD, value ); - if (value > MenuStr[i].Max || value < MenuStr[i].Min) { - value = MenuStr[i].Default; - // _LOG_A("set default value for %s to %i\n",MenuStr[i].LCD, value ); - setItemValue(i, value); - } - } - - // Sensorbox v2 has always address 0x0A - if (MainsMeter == EM_SENSORBOX) MainsMeterAddress = 0x0A; - // set Lock variables for Solenoid or Motor - if (Lock == 1) { lock1 = LOW; lock2 = HIGH; } // Solenoid - else if (Lock == 2) { lock1 = HIGH; lock2 = LOW; } // Motor - // Erase all RFID cards from ram + eeprom if set to EraseAll - if (RFIDReader == 5) { - DeleteAllRFID(); - setItemValue(MENU_RFIDREADER, 0); // RFID Reader Disabled - } - - // We disabled CAL in the menu. - // Make sure the stored CAL value is reset to the default value - ICal = ICAL; - - // Update master node config - if (LoadBl < 2) { - Node[0].EVMeter = EVMeter; - Node[0].EVAddress = EVMeterAddress; - } - - // Check if AP password is unitialized. - // Create random AP password. - if (!Initialized) { - SetRandomAPpassword(); - Initialized = 1; - } - - // Default to modbus input registers - if (EMConfig[EM_CUSTOM].Function != 3) EMConfig[EM_CUSTOM].Function = 4; - - // Backward compatibility < 2.20 - if (EMConfig[EM_CUSTOM].IRegister == 8 || EMConfig[EM_CUSTOM].URegister == 8 || EMConfig[EM_CUSTOM].PRegister == 8 || EMConfig[EM_CUSTOM].ERegister == 8) { - EMConfig[EM_CUSTOM].DataType = MB_DATATYPE_FLOAT32; - EMConfig[EM_CUSTOM].IRegister = 0; - EMConfig[EM_CUSTOM].URegister = 0; - EMConfig[EM_CUSTOM].PRegister = 0; - EMConfig[EM_CUSTOM].ERegister = 0; - } - - // If the address of the MainsMeter or EVmeter on a Node has changed, we must re-register the Modbus workers. - if (LoadBl > 1) { - if (EVMeter && EVMeter != EM_API) MBserver.registerWorker(EVMeterAddress, ANY_FUNCTION_CODE, &MBEVMeterResponse); - } - MainsMeterTimeout = COMM_TIMEOUT; - EVMeterTimeout = COMM_TIMEOUT; // Short Delay, to clear the error message for ~10 seconds. - -} - -void read_settings() { - - // Open preferences. true = read only, false = read/write - // If "settings" does not exist, it will be created, and initialized with the default values - if (preferences.begin("settings", false) ) { - Initialized = preferences.getUChar("Initialized", INITIALIZED); - Config = preferences.getUChar("Config", CONFIG); - Lock = preferences.getUChar("Lock", LOCK); - Mode = preferences.getUChar("Mode", MODE); - Access_bit = preferences.getUChar("Access", ACCESS_BIT); - CardOffset = preferences.getUChar("CardOffset", CARD_OFFSET); - LoadBl = preferences.getUChar("LoadBl", LOADBL); - MaxMains = preferences.getUShort("MaxMains", MAX_MAINS); - MaxSumMains = preferences.getUShort("MaxSumMains", MAX_SUMMAINS); - MaxCurrent = preferences.getUShort("MaxCurrent", MAX_CURRENT); - MinCurrent = preferences.getUShort("MinCurrent", MIN_CURRENT); - MaxCircuit = preferences.getUShort("MaxCircuit", MAX_CIRCUIT); - ICal = preferences.getUShort("ICal", ICAL); - Switch = preferences.getUChar("Switch", SWITCH); - RCmon = preferences.getUChar("RCmon", RC_MON); - StartCurrent = preferences.getUShort("StartCurrent", START_CURRENT); - StopTime = preferences.getUShort("StopTime", STOP_TIME); - ImportCurrent = preferences.getUShort("ImportCurrent",IMPORT_CURRENT); - Grid = preferences.getUChar("Grid",GRID); - RFIDReader = preferences.getUChar("RFIDReader",RFID_READER); - - MainsMeter = preferences.getUChar("MainsMeter", MAINS_METER); - MainsMeterAddress = preferences.getUChar("MainsMAddress",MAINS_METER_ADDRESS); - EVMeter = preferences.getUChar("EVMeter",EV_METER); - EVMeterAddress = preferences.getUChar("EVMeterAddress",EV_METER_ADDRESS); - EMConfig[EM_CUSTOM].Endianness = preferences.getUChar("EMEndianness",EMCUSTOM_ENDIANESS); - EMConfig[EM_CUSTOM].IRegister = preferences.getUShort("EMIRegister",EMCUSTOM_IREGISTER); - EMConfig[EM_CUSTOM].IDivisor = preferences.getUChar("EMIDivisor",EMCUSTOM_IDIVISOR); - EMConfig[EM_CUSTOM].URegister = preferences.getUShort("EMURegister",EMCUSTOM_UREGISTER); - EMConfig[EM_CUSTOM].UDivisor = preferences.getUChar("EMUDivisor",EMCUSTOM_UDIVISOR); - EMConfig[EM_CUSTOM].PRegister = preferences.getUShort("EMPRegister",EMCUSTOM_PREGISTER); - EMConfig[EM_CUSTOM].PDivisor = preferences.getUChar("EMPDivisor",EMCUSTOM_PDIVISOR); - EMConfig[EM_CUSTOM].ERegister = preferences.getUShort("EMERegister",EMCUSTOM_EREGISTER); - EMConfig[EM_CUSTOM].EDivisor = preferences.getUChar("EMEDivisor",EMCUSTOM_EDIVISOR); - EMConfig[EM_CUSTOM].DataType = (mb_datatype)preferences.getUChar("EMDataType",EMCUSTOM_DATATYPE); - EMConfig[EM_CUSTOM].Function = preferences.getUChar("EMFunction",EMCUSTOM_FUNCTION); - WIFImode = preferences.getUChar("WIFImode",WIFI_MODE); - APpassword = preferences.getString("APpassword",AP_PASSWORD); - DelayedStartTime.epoch2 = preferences.getULong("DelayedStartTim", DELAYEDSTARTTIME); //epoch2 is 4 bytes long on arduino; NVS key has reached max size - DelayedStopTime.epoch2 = preferences.getULong("DelayedStopTime", DELAYEDSTOPTIME); //epoch2 is 4 bytes long on arduino - TZinfo = preferences.getString("TimezoneInfo",""); - if (TZinfo != "") { - setenv("TZ",TZinfo.c_str(),1); - tzset(); - } - - - EnableC2 = (EnableC2_t) preferences.getUShort("EnableC2", ENABLE_C2); -#if MODEM - Modem = EXPERIMENT; -#else - Modem = NOTPRESENT; -#endif - strncpy(RequiredEVCCID, preferences.getString("RequiredEVCCID", "").c_str(), sizeof(RequiredEVCCID)); - maxTemp = preferences.getUShort("maxTemp", MAX_TEMPERATURE); - -#if MQTT - MQTTpassword = preferences.getString("MQTTpassword"); - MQTTuser = preferences.getString("MQTTuser"); - MQTTprefix = preferences.getString("MQTTprefix", APhostname); - MQTTHost = preferences.getString("MQTTHost", ""); - MQTTPort = preferences.getUShort("MQTTPort", 1883); -#endif - - preferences.end(); - - // Store settings when not initialized - if (!Initialized) write_settings(); - - } else { - _LOG_A("Can not open preferences!\n"); - } -} - -void write_settings(void) { - - validate_settings(); - - if (preferences.begin("settings", false) ) { - - preferences.putUChar("Config", Config); - preferences.putUChar("Lock", Lock); - preferences.putUChar("Mode", Mode); - preferences.putUChar("Access", Access_bit); - preferences.putUChar("CardOffset", CardOffset); - preferences.putUChar("LoadBl", LoadBl); - preferences.putUShort("MaxMains", MaxMains); - preferences.putUShort("MaxSumMains", MaxSumMains); - preferences.putUShort("MaxCurrent", MaxCurrent); - preferences.putUShort("MinCurrent", MinCurrent); - preferences.putUShort("MaxCircuit", MaxCircuit); - preferences.putUShort("ICal", ICal); - preferences.putUChar("Switch", Switch); - preferences.putUChar("RCmon", RCmon); - preferences.putUShort("StartCurrent", StartCurrent); - preferences.putUShort("StopTime", StopTime); - preferences.putUShort("ImportCurrent", ImportCurrent); - preferences.putUChar("Grid", Grid); - preferences.putUChar("RFIDReader", RFIDReader); - - preferences.putUChar("MainsMeter", MainsMeter); - preferences.putUChar("MainsMAddress", MainsMeterAddress); - preferences.putUChar("EVMeter", EVMeter); - preferences.putUChar("EVMeterAddress", EVMeterAddress); - preferences.putUChar("EMEndianness", EMConfig[EM_CUSTOM].Endianness); - preferences.putUShort("EMIRegister", EMConfig[EM_CUSTOM].IRegister); - preferences.putUChar("EMIDivisor", EMConfig[EM_CUSTOM].IDivisor); - preferences.putUShort("EMURegister", EMConfig[EM_CUSTOM].URegister); - preferences.putUChar("EMUDivisor", EMConfig[EM_CUSTOM].UDivisor); - preferences.putUShort("EMPRegister", EMConfig[EM_CUSTOM].PRegister); - preferences.putUChar("EMPDivisor", EMConfig[EM_CUSTOM].PDivisor); - preferences.putUShort("EMERegister", EMConfig[EM_CUSTOM].ERegister); - preferences.putUChar("EMEDivisor", EMConfig[EM_CUSTOM].EDivisor); - preferences.putUChar("EMDataType", EMConfig[EM_CUSTOM].DataType); - preferences.putUChar("EMFunction", EMConfig[EM_CUSTOM].Function); - preferences.putUChar("WIFImode", WIFImode); - preferences.putString("APpassword", APpassword); - preferences.putUChar("Initialized", Initialized); - preferences.putULong("DelayedStartTim", DelayedStartTime.epoch2); //epoch2 only needs 4 bytes; NVS key has reached max size - preferences.putULong("DelayedStopTime", DelayedStopTime.epoch2); //epoch2 only needs 4 bytes - - preferences.putUShort("EnableC2", EnableC2); - preferences.putString("RequiredEVCCID", String(RequiredEVCCID)); - preferences.putUShort("maxTemp", maxTemp); - -#if MQTT - preferences.putString("MQTTpassword", MQTTpassword); - preferences.putString("MQTTuser", MQTTuser); - preferences.putString("MQTTprefix", MQTTprefix); - preferences.putString("MQTTHost", MQTTHost); - preferences.putUShort("MQTTPort", MQTTPort); -#endif - - preferences.end(); - - _LOG_I("settings saved\n"); - - } else { - _LOG_A("Can not open preferences!\n"); - } - - - if (LoadBl == 1) { // Master mode - uint16_t i, values[MODBUS_SYS_CONFIG_COUNT]; - for (i = 0; i < MODBUS_SYS_CONFIG_COUNT; i++) { - values[i] = getItemValue(MENU_MODE + i); - } - // Broadcast settings to other controllers - ModbusWriteMultipleRequest(BROADCAST_ADR, MODBUS_SYS_CONFIG_START, values, MODBUS_SYS_CONFIG_COUNT); - } - - ConfigChanged = 1; -} -/* -//github.com L1 - const char* root_ca_github = R"ROOT_CA( ------BEGIN CERTIFICATE----- -MIID0zCCArugAwIBAgIQVmcdBOpPmUxvEIFHWdJ1lDANBgkqhkiG9w0BAQwFADB7 -MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD -VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE -AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4 -MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5 -MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO -ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEGqxUWqn5aCPnetUkb1PGWthL -q8bVttHmc3Gu3ZzWDGH926CJA7gFFOxXzu5dP+Ihs8731Ip54KODfi2X0GHE8Znc -JZFjq38wo7Rw4sehM5zzvy5cU7Ffs30yf4o043l5o4HyMIHvMB8GA1UdIwQYMBaA -FKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1 -xmNjmjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAI -MAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5j -b20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQG -CCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEM -BQADggEBABns652JLCALBIAdGN5CmXKZFjK9Dpx1WywV4ilAbe7/ctvbq5AfjJXy -ij0IckKJUAfiORVsAYfZFhr1wHUrxeZWEQff2Ji8fJ8ZOd+LygBkc7xGEJuTI42+ -FsMuCIKchjN0djsoTI0DQoWz4rIjQtUfenVqGtF8qmchxDM6OW1TyaLtYiKou+JV -bJlsQ2uRl9EMC5MCHdK8aXdJ5htN978UeAOwproLtOGFfy/cQjutdAFI3tZs4RmY -CV4Ks2dH/hzg1cEo70qLRDEmBDeNiXQ2Lu+lIg+DdEmSx/cQwgwp+7e9un/jX9Wf -8qn0dNW44bOwgeThpWOjzOoEeJBuv/c= ------END CERTIFICATE----- -)ROOT_CA"; -*/ -HTTPClient _http; -WiFiClientSecure _client; - -/* -// get version nr. of latest release of off github -// input: -// owner_repo format: dingo35/SmartEVSE-3.5 -// asset name format: one of firmware.bin, firmware.debug.bin, firmware.signed.bin, firmware.debug.signed.bin -// output: -// version -- null terminated string with latest version of this repo -// downloadUrl -- global pointer to null terminated string with the url where this version can be downloaded -bool getLatestVersion(String owner_repo, String asset_name, char *version) { - String useURL = "https://api.github.com/repos/" + owner_repo + "/releases/latest"; - _http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - - const char* url = useURL.c_str(); - _LOG_A("Connecting to: %s.\n", url ); - if( String(url).startsWith("https") ) { - _client.setCACert(root_ca_github); // OR - //_client.setInsecure(); - _http.begin( _client, url ); - } else { - _http.begin( url ); - } - _http.addHeader("User-Agent", "SmartEVSE-v3"); - _http.addHeader("Accept", "application/vnd.github+json"); - _http.addHeader("X-GitHub-Api-Version", "2022-11-28" ); - const char* get_headers[] = { "Content-Length", "Content-type", "Accept-Ranges" }; - _http.collectHeaders( get_headers, sizeof(get_headers)/sizeof(const char*) ); - int httpCode = _http.GET(); //Make the request - - // only handle 200/301, fail on everything else - if( httpCode != HTTP_CODE_OK && httpCode != HTTP_CODE_MOVED_PERMANENTLY ) { - // This error may be a false positive or a consequence of the network being disconnected. - // Since the network is controlled from outside this class, only significant error messages are reported. - if( httpCode > 0 ) { - _LOG_A("Error on HTTP request (httpCode=%i)\n", httpCode); - } else { - _LOG_A("Unknown HTTP response"); - } - _http.end(); - return false; - } - // The filter: it contains "true" for each value we want to keep - DynamicJsonDocument filter(100); - filter["tag_name"] = true; - filter["assets"][0]["browser_download_url"] = true; - filter["assets"][0]["name"] = true; - - // Deserialize the document - DynamicJsonDocument doc2(1500); - DeserializationError error = deserializeJson(doc2, _http.getStream(), DeserializationOption::Filter(filter)); - - if (error) { - _LOG_A("deserializeJson() failed: %s\n", error.c_str()); - _http.end(); // We're done with HTTP - free the resources - return false; - } - const char* tag_name = doc2["tag_name"]; // "v3.6.1" - if (!tag_name) { - //no version found - _LOG_A("ERROR: LatestVersion of repo %s not found.\n", owner_repo.c_str()); - _http.end(); // We're done with HTTP - free the resources - return false; - } - else - //duplicate value so it won't get lost out of scope - strlcpy(version, tag_name, 32); - //strlcpy(version, tag_name, sizeof(version)); - _LOG_V("Found latest version:%s.\n", version); - for (JsonObject asset : doc2["assets"].as()) { - String name = asset["name"] | ""; - if (name == asset_name) { - const char* asset_browser_download_url = asset["browser_download_url"]; - if (!asset_browser_download_url) { - // no download url found - _LOG_A("ERROR: Downloadurl of asset %s in repo %s not found.\n", asset_name.c_str(), owner_repo.c_str()); - _http.end(); // We're done with HTTP - free the resources - return false; - } else { - asprintf(&downloadUrl, "%s", asset_browser_download_url); //will be freed in FirmwareUpdate() - _LOG_V("Found asset: name=%s, url=%s.\n", name.c_str(), downloadUrl); - _http.end(); // We're done with HTTP - free the resources - return true; - } - } - } - _LOG_A("ERROR: could not find asset %s in repo %s at version %s.\n", asset_name.c_str(), owner_repo.c_str(), version); - _http.end(); // We're done with HTTP - free the resources - return false; -} -*/ - -// esp32fota esp32fota("", , , ); -esp32FOTA FOTA("esp32-fota-http", 1, false, true); - -// put firmware update in separate task so we can feed progress to the html page -void FirmwareUpdate(void *parameter) { - _LOG_A("DINGO: url=%s.\n", downloadUrl); - if (FOTA.forceUpdate(downloadUrl, 1)) { - _LOG_A("Firmware update succesfull.\n"); - downloadProgress = -1; - } else { - _LOG_A("ERROR: Firmware update failed; rebooting.\n"); - downloadProgress = -2; - delay(5000); - ESP.restart(); - } - if (downloadUrl) free(downloadUrl); - vTaskDelete(NULL); //end this task so it will not take up resources -} - -void RunFirmwareUpdate(void) { - _LOG_V("Starting firmware update from downloadUrl=%s.\n", downloadUrl); - xTaskCreate( - FirmwareUpdate, // Function that should be called - "FirmwareUpdate",// Name of the task (for debugging) - 4096, // Stack size (bytes) - NULL, // Parameter to pass - 3, // Task priority - high - NULL // Task handle - ); -} - -// Downloads firmware, flashes it, and reboot -bool AutoUpdate(String owner, String repo, int debug) { - bool ret = true; - asprintf(&downloadUrl, "%s/%s_%s.%s", FW_DOWNLOAD_PATH, (owner == OWNER_FACT)? "fact":"comm", "firmware", debug ? "debug.signed.bin": "signed.bin"); //will be freed in FirmwareUpdate() ; format: http://s3.com/fact_firmware.debug.signed.bin -/* - if () { //github routine - // not expiring github auth for minimal rate limiting on github - char version[32] = ""; - char asset_name[32] = ""; - if (debug == 1) - strlcpy(asset_name, "firmware.debug.bin", sizeof(asset_name)); - else - strlcpy(asset_name, "firmware.bin", sizeof(asset_name)); - ret = getLatestVersion(owner + "/" + repo, asset_name, version); - } - }*/ - if (ret) { - RunFirmwareUpdate(); - } - return ret; -} - -/* Takes TimeString in format - * String = "2023-04-14T11:31" - * and store it in the DelayedTimeStruct - * returns 0 on success, 1 on failure -*/ -int StoreTimeString(String DelayedTimeStr, DelayedTimeStruct *DelayedTime) { - // Parse the time string - tm delayedtime_tm = {}; - if (strptime(DelayedTimeStr.c_str(), "%Y-%m-%dT%H:%M", &delayedtime_tm)) { - delayedtime_tm.tm_isdst = -1; //so mktime is going to figure out whether DST is there or not - DelayedTime->epoch2 = mktime(&delayedtime_tm) - EPOCH2_OFFSET; - // Compare the times - time_t now = time(nullptr); //get current local time - DelayedTime->diff = DelayedTime->epoch2 - (mktime(localtime(&now)) - EPOCH2_OFFSET); - return 0; - } - //error TODO not sure whether we keep the old time or reset it to zero? - //DelayedTime.epoch2 = 0; - //DelayedTime.diff = 0; - return 1; -} - -// takes TZname (format: Europe/Berlin) , gets TZ_INFO (posix string, format: CET-1CEST,M3.5.0,M10.5.0/3) and sets and stores timezonestring accordingly -void setTimeZone(char *tzname) { - - struct mg_fs *fs = &mg_fs_packed; - //struct mg_str mg_file_read(struct mg_fs *fs, const char *path) { - void *fp; - size_t filelen, filepos = 0; - const char *path = "/data/zones.csv"; - fs->st(path, &filelen, NULL); - if ((fp = fs->op(path, MG_FS_READ)) != NULL) { - bool found = false; - char line[80]; - int pos = 0; - char c; - do { - pos = 0; - do { - fs->rd(fp, &c, 1); - if (filepos < filelen) - line[pos]=c; - pos++; - filepos++; - } while (pos < sizeof(line) - 1 && c != '\n' && filepos < filelen); - //terminate with NULL character - line[pos]=0; - found = strstr(line, tzname); - if (found) { - char *pos = strstr(line, ","); - pos = strstr(pos, "\""); - char *tz_info = pos + 1; - pos = strstr(pos, "\"") - 1; - pos = NULL; //end string with null char - _LOG_A("Detected Timezone info: TZname = %s, tz_info=%s.\n", tzname, tz_info); - setenv("TZ",tz_info,1); - TZinfo = String(tz_info); - tzset(); - if (preferences.begin("settings", false) ) { - preferences.putString("TimezoneInfo", tz_info); - preferences.end(); - } - break; - } - } while (filepos < filelen && !found); - fs->cl(fp); - } -} - -// wrapper so hasParam and getParam still work -class webServerRequest { -private: - struct mg_http_message *hm_internal; - String _value; - char temp[64]; - -public: - void setMessage(struct mg_http_message *hm); - bool hasParam(const char *param); - webServerRequest* getParam(const char *param); // Return pointer to self - const String& value(); // Return the string value -}; - -void webServerRequest::setMessage(struct mg_http_message *hm) { - hm_internal = hm; -} - -bool webServerRequest::hasParam(const char *param) { - return (mg_http_get_var(&hm_internal->query, param, temp, sizeof(temp)) > 0); -} - -webServerRequest* webServerRequest::getParam(const char *param) { - _value = ""; // Clear previous value - if (mg_http_get_var(&hm_internal->query, param, temp, sizeof(temp)) > 0) { - _value = temp; - } - return this; // Return pointer to self -} - -const String& webServerRequest::value() { - return _value; // Return the string value -} -//end of wrapper - -struct mg_str empty = mg_str_n("", 0UL); - -#if MQTT -char s_mqtt_url[80]; -//TODO perhaps integrate multiple fn callback functions? -static void fn_mqtt(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_OPEN) { - _LOG_V("%lu CREATED", c->id); - // c->is_hexdumping = 1; - } else if (ev == MG_EV_ERROR) { - // On error, log error message - _LOG_A("%lu ERROR %s", c->id, (char *) ev_data); - } else if (ev == MG_EV_CONNECT) { - // If target URL is SSL/TLS, command client connection to use TLS - if (mg_url_is_ssl(s_mqtt_url)) { - struct mg_tls_opts opts = {.ca = empty, .cert = empty, .key = empty, .name = mg_url_host(s_mqtt_url)}; - //struct mg_tls_opts opts = {.ca = empty}; - mg_tls_init(c, &opts); - } - } else if (ev == MG_EV_MQTT_OPEN) { - // MQTT connect is successful - _LOG_V("%lu CONNECTED to %s", c->id, s_mqtt_url); - MQTTclient.connected = true; - SetupMQTTClient(); - } else if (ev == MG_EV_MQTT_MSG) { - // When we get echo response, print it - struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; - _LOG_V("%lu RECEIVED %.*s <- %.*s", c->id, (int) mm->data.len, mm->data.ptr, (int) mm->topic.len, mm->topic.ptr); - //somehow topic is not null terminated - String topic2 = String(mm->topic.ptr).substring(0,mm->topic.len); - mqtt_receive_callback(topic2, mm->data.ptr); - } else if (ev == MG_EV_CLOSE) { - _LOG_V("%lu CLOSED", c->id); - MQTTclient.connected = false; - s_conn = NULL; // Mark that we're closed - } -} - -// Timer function - recreate client connection if it is closed -static void timer_fn(void *arg) { - struct mg_mgr *mgr = (struct mg_mgr *) arg; - struct mg_mqtt_opts opts; - memset(&opts, 0, sizeof(opts)); - opts.clean = false; - // set will topic - String temp = MQTTprefix + "/connected"; - opts.topic = mg_str(temp.c_str()); - opts.message = mg_str("offline"); - opts.retain = true; - opts.keepalive = 15; // so we will timeout after 15s - opts.version = 4; - opts.client_id=mg_str(MQTTprefix.c_str()); - opts.user=mg_str(MQTTuser.c_str()); - opts.pass=mg_str(MQTTpassword.c_str()); - - //prepare MQTT url - //mqtt[s]://[username][:password]@host.domain[:port] - snprintf(s_mqtt_url, sizeof(s_mqtt_url), "mqtt://%s:%i", MQTTHost.c_str(), MQTTPort); - - if (s_conn == NULL) s_conn = mg_mqtt_connect(mgr, s_mqtt_url, &opts, fn_mqtt, NULL); -} -#endif - -//mongoose http_client for picking up the timezone -//url to get current timezone in Europe/Berlin format: -static const char *s_url = "http://worldtimeapi.org/api/ip"; -//urls for converting timezone to posix string: -//static const char *s_url = "https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv"; -static const char *s_post_data = NULL; // POST data -static const uint64_t s_timeout_ms = 1500; // Connect timeout in milliseconds - -// Print HTTP response and signal that we're done -static void fn_client(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_OPEN) { - // Connection created. Store connect expiration time in c->data - *(uint64_t *) c->data = mg_millis() + s_timeout_ms; - } else if (ev == MG_EV_POLL) { - if (mg_millis() > *(uint64_t *) c->data && (c->is_connecting || c->is_resolving)) { - mg_error(c, "Connect timeout"); - } - } else if (ev == MG_EV_CONNECT) { - // Connected to server. Extract host name from URL - struct mg_str host = mg_url_host(s_url); - - if (mg_url_is_ssl(s_url)) { - struct mg_tls_opts opts = {.ca = empty, .cert = empty, .key = empty, .name = mg_url_host(s_url)}; - mg_tls_init(c, &opts); - } - - // Send request - int content_length = s_post_data ? strlen(s_post_data) : 0; - mg_printf(c, - "%s %s HTTP/1.0\r\n" - "Host: %.*s\r\n" - "Content-Type: octet-stream\r\n" - "Content-Length: %d\r\n" - "\r\n", - s_post_data ? "POST" : "GET", mg_url_uri(s_url), (int) host.len, - host.ptr, content_length); - mg_send(c, s_post_data, content_length); - } else if (ev == MG_EV_HTTP_MSG) { - // Response is received. Print it - struct mg_http_message *hm = (struct mg_http_message *) ev_data; - if (hm->message.len > 1) { - struct mg_str json = hm->body; - char *tz = mg_json_get_str(json, "$.timezone"); - _LOG_A("Timezone detected: tz=%s.\n", tz); - setTimeZone(tz); - } else { - _LOG_A("Could not detect Timezone.\n"); - } - c->is_draining = 1; // Tell mongoose to close this connection - *(bool *) c->fn_data = true; // Tell event loop to stop - } else if (ev == MG_EV_ERROR) { - *(bool *) c->fn_data = true; // Error, tell event loop to stop - } -} - -unsigned char *signature = NULL; -#define SIGNATURE_LENGTH 512 -// Connection event handler function -// indenting lower level two spaces to stay compatible with old StartWebServer -// We use the same event handler function for HTTP and HTTPS connections -// fn_data is NULL for plain HTTP, and non-NULL for HTTPS -static void fn_http_server(struct mg_connection *c, int ev, void *ev_data) { - // close this listener if the portal is active - if (WIFImode == 2) { - _LOG_A("Closing mongoose http listener!!\n"); - c->is_closing = 1; - } - if (ev == MG_EV_ACCEPT && c->fn_data != NULL) { - struct mg_tls_opts opts = { .ca = empty, .cert = mg_unpacked("/data/cert.pem"), .key = mg_unpacked("/data/key.pem"), .name = empty}; - mg_tls_init(c, &opts); - } - if (ev == MG_EV_HTTP_MSG) { // New HTTP request received - struct mg_http_message *hm = (struct mg_http_message *) ev_data; // Parsed HTTP request - webServerRequest* request = new webServerRequest(); - request->setMessage(hm); - if (mg_http_match_uri(hm, "/erasesettings")) { - mg_http_reply(c, 200, "Content-Type: text/plain\r\n", "Erasing settings, rebooting"); - if ( preferences.begin("settings", false) ) { // our own settings - preferences.clear(); - preferences.end(); - } - if (preferences.begin("nvs.net80211", false) ) { // WiFi settings used by ESP - preferences.clear(); - preferences.end(); - } - ESP.restart(); - } else if (mg_http_match_uri(hm, "/autoupdate")) { - char url[40]; - char buf[8]; - int debug; - mg_http_get_var(&hm->query, "url", url, sizeof(url)); - mg_http_get_var(&hm->query, "debug", buf, sizeof(buf)); - debug = strtol(buf, NULL, 0); - if (!memcmp(url, "factory", sizeof("factory"))) { - if (AutoUpdate(OWNER_FACT, REPO_FACT, debug)) { - struct mg_http_serve_opts opts = {.root_dir = "/data", .ssi_pattern = NULL, .extra_headers = NULL, .mime_types = NULL, .page404 = NULL, .fs = &mg_fs_packed }; - mg_http_serve_file(c, hm, "/data/update3.html", &opts); - } else - mg_http_reply(c, 400, "Content-Type: text/plain\r\n", "Autoupdate failed."); - } else if (!memcmp(url, "community", sizeof("community"))) { - if (AutoUpdate(OWNER_COMM, REPO_COMM, debug)) { - struct mg_http_serve_opts opts = {.root_dir = "/data", .ssi_pattern = NULL, .extra_headers = NULL, .mime_types = NULL, .page404 = NULL, .fs = &mg_fs_packed }; - mg_http_serve_file(c, hm, "/data/update3.html", &opts); - } else - mg_http_reply(c, 400, "Content-Type: text/plain\r\n", "Autoupdate failed."); - } else - mg_http_reply(c, 400, "Content-Type: text/plain\r\n", "Autoupdate wrong parameter."); - } else if (mg_http_match_uri(hm, "/autoupdate_progress")) { -/* char *Str; - asprintf(&Str,"Autoupdating %i / %i.\n", downloadProgress, downloadSize); - mg_http_reply(c, 200, "Content-Type: text/plain\r\n", Str); - free(Str); -*/ - DynamicJsonDocument doc(64); // https://arduinojson.org/v6/assistant/ - doc["progress"] = downloadProgress; - doc["size"] = downloadSize; - String json; - serializeJson(doc, json); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n", json.c_str()); // Yes. Respond JSON - } else if (mg_http_match_uri(hm, "/update")) { - //modified version of mg_http_upload - char buf[20] = "0", file[40]; - size_t max_size = 0x1B0000; //from partition_custom.csv - long res = 0, offset, size; - mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); - mg_http_get_var(&hm->query, "file", file, sizeof(file)); - offset = strtol(buf, NULL, 0); - buf[0] = '0'; - mg_http_get_var(&hm->query, "size", buf, sizeof(buf)); - size = strtol(buf, NULL, 0); - if (hm->body.len == 0) { - struct mg_http_serve_opts opts = {.root_dir = "/data", .ssi_pattern = NULL, .extra_headers = NULL, .mime_types = NULL, .page404 = NULL, .fs = &mg_fs_packed }; - mg_http_serve_file(c, hm, "/data/update2.html", &opts); - } else if (file[0] == '\0') { - mg_http_reply(c, 400, "", "file required"); - res = -1; - } else if (offset < 0) { - mg_http_reply(c, 400, "", "offset required"); - res = -3; - } else if ((size_t) offset + hm->body.len > max_size) { - mg_http_reply(c, 400, "", "over max size of %lu", (unsigned long) max_size); - res = -4; - } else if (size <= 0) { - mg_http_reply(c, 400, "", "size required"); - res = -5; - } else { - if (!memcmp(file,"firmware.bin", sizeof("firmware.bin")) || !memcmp(file,"firmware.debug.bin", sizeof("firmware.debug.bin"))) { - if(!offset) { - _LOG_A("Update Start: %s\n", file); - if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000), U_FLASH) { - Update.printError(Serial); - } - } - if(!Update.hasError()) { - if(Update.write((uint8_t*) hm->body.ptr, hm->body.len) != hm->body.len) { - Update.printError(Serial); - } else { - _LOG_A("bytes written %lu\r", offset + hm->body.len); - } - } - if (offset + hm->body.len >= size) { //EOF - if(Update.end(true)) { - _LOG_A("\nUpdate Success\n"); - delay(1000); - ESP.restart(); - } else { - Update.printError(Serial); - } - } - } else //end of firmware.bin - if (!memcmp(file,"firmware.signed.bin", sizeof("firmware.signed.bin")) || !memcmp(file,"firmware.debug.signed.bin", sizeof("firmware.debug.signed.bin"))) { -#define dump(X) for (int i= 0; i< SIGNATURE_LENGTH; i++) _LOG_A_NO_FUNC("%02x", X[i]); _LOG_A_NO_FUNC(".\n"); - if(!offset) { - _LOG_A("Update Start: %s\n", file); - signature = (unsigned char *) malloc(SIGNATURE_LENGTH); //tried to free in in all exit scenarios, RISK of leakage!!! - memcpy(signature, hm->body.ptr, SIGNATURE_LENGTH); //signature is prepended to firmware.bin - hm->body.ptr = hm->body.ptr + SIGNATURE_LENGTH; - hm->body.len = hm->body.len - SIGNATURE_LENGTH; - _LOG_A("Firmware signature:"); - dump(signature); - if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000), U_FLASH) { - Update.printError(Serial); - } - } - if(!Update.hasError()) { - if(Update.write((uint8_t*) hm->body.ptr, hm->body.len) != hm->body.len) { - Update.printError(Serial); - FREE(signature); - } else { - _LOG_A("bytes written %lu\r", offset + hm->body.len); - } - } - if (offset + hm->body.len >= size) { //EOF - //esp_err_t err; - const esp_partition_t* target_partition = esp_ota_get_next_update_partition(NULL); // the newly updated partition - if (!target_partition) { - _LOG_A("ERROR: Can't access firmware partition to check signature!"); - mg_http_reply(c, 400, "", "firmware.signed.bin update failed!"); - } - const esp_partition_t* running_partition = esp_ota_get_running_partition(); - _LOG_V("Running off of partition %s, trying to update partition %s.\n", running_partition->label, target_partition->label); - esp_ota_set_boot_partition( running_partition ); // make sure we have not switched boot partitions - - bool verification_result = false; - if(Update.end(true)) { - verification_result = FOTA.validate_sig( target_partition, signature, size - SIGNATURE_LENGTH); - FREE(signature); - if (verification_result) { - _LOG_A("Signature is valid!\n"); - esp_ota_set_boot_partition( target_partition ); - _LOG_A("\nUpdate Success\n"); - shouldReboot = true; - //ESP.restart(); does not finish the call to fn_http_server, so the last POST of apps.js gets no response.... - //which results in a "verify failed" message on the /update screen AFTER the reboot :-) - } - } -_LOG_A("DINGO: boot partition=%s.\n", esp_ota_get_boot_partition()->label); - if (!verification_result) { - _LOG_A("Update failed!\n"); - Update.printError(Serial); - //Update.abort(); //not sure this does anything in this stage - //Update.rollBack(); - _LOG_V("Running off of partition %s, erasing partition %s.\n", running_partition->label, target_partition->label); -_LOG_A("DINGO: checkpoint 1 boot partition=%s.\n", esp_ota_get_boot_partition()->label); - esp_partition_erase_range( target_partition, target_partition->address, target_partition->size ); - esp_ota_set_boot_partition( running_partition ); -_LOG_A("DINGO: checkpoint 2 boot partition=%s.\n", esp_ota_get_boot_partition()->label); - mg_http_reply(c, 400, "", "firmware.signed.bin update failed!"); - } - FREE(signature); - } - } else //end of firmware.signed.bin - if (!memcmp(file,"rfid.txt", sizeof("rfid.txt"))) { - if (offset != 0) { - mg_http_reply(c, 400, "", "rfid.txt too big, only 120 rfid's allowed!"); - } - else { - //we are overwriting all stored RFID's with the ones uploaded - DeleteAllRFID(); - res = offset + hm->body.len; - unsigned int RFID_UID[8] = {1, 0, 0, 0, 0, 0, 0, 0}; - char RFIDtxtstring[18]; // 17 characters + NULL terminator - int r, pos = 0; - int beginpos = 0; - while (pos <= hm->body.len) { - char c; - c = *(hm->body.ptr + pos); - //_LOG_A_NO_FUNC("%c", c); - if (c == '\n' || pos == hm->body.len) { - strncpy(RFIDtxtstring, hm->body.ptr + beginpos, 17); // in case of DOS the 0x0D is stripped off here - RFIDtxtstring[17] = '\0'; - r = sscanf(RFIDtxtstring,"%02x%02x%02x%02x%02x%02x", &RFID_UID[1], &RFID_UID[2], &RFID_UID[3], &RFID_UID[4], &RFID_UID[5], &RFID_UID[6]); - RFID_UID[7]=crc8((unsigned char *) RFID_UID,7); - if (r == 6) { - _LOG_A("Store RFID_UID %02x%02x%02x%02x%02x%02x, crc=%02x.\n", RFID_UID[1], RFID_UID[2], RFID_UID[3], RFID_UID[4], RFID_UID[5], RFID_UID[6], RFID_UID[7]); - LoadandStoreRFID(RFID_UID); - } - beginpos = pos + 1; - } - pos++; - } - } - } else //end of rfid.txt - mg_http_reply(c, 400, "", "only allowed to flash firmware.bin, firmware.debug.bin, firmware.signed.bin, firmware.debug.signed.bin or rfid.txt"); - mg_http_reply(c, 200, "", "%ld", res); - } - } else if (mg_http_match_uri(hm, "/settings")) { // REST API call? - if (!memcmp("GET", hm->method.ptr, hm->method.len)) { // if GET - String mode = "N/A"; - int modeId = -1; - if(Access_bit == 0) { - mode = "OFF"; - modeId=0; - } else { - switch(Mode) { - case MODE_NORMAL: mode = "NORMAL"; modeId=1; break; - case MODE_SOLAR: mode = "SOLAR"; modeId=2; break; - case MODE_SMART: mode = "SMART"; modeId=3; break; - } - } - String backlight = "N/A"; - switch(BacklightSet) { - case 0: backlight = "OFF"; break; - case 1: backlight = "ON"; break; - case 2: backlight = "DIMMED"; break; - } - String evstate = StrStateNameWeb[State]; - String error = getErrorNameWeb(ErrorFlags); - int errorId = getErrorId(ErrorFlags); - - if (ErrorFlags & NO_SUN) { - evstate += " - " + error; - error = "None"; - errorId = 0; - } - - boolean evConnected = pilot != PILOT_12V; //when access bit = 1, p.ex. in OFF mode, the STATEs are no longer updated - - DynamicJsonDocument doc(1600); // https://arduinojson.org/v6/assistant/ - doc["version"] = String(VERSION); - doc["serialnr"] = serialnr; - doc["mode"] = mode; - doc["mode_id"] = modeId; - doc["car_connected"] = evConnected; - - if(WiFi.isConnected()) { - switch(WiFi.status()) { - case WL_NO_SHIELD: doc["wifi"]["status"] = "WL_NO_SHIELD"; break; - case WL_IDLE_STATUS: doc["wifi"]["status"] = "WL_IDLE_STATUS"; break; - case WL_NO_SSID_AVAIL: doc["wifi"]["status"] = "WL_NO_SSID_AVAIL"; break; - case WL_SCAN_COMPLETED: doc["wifi"]["status"] = "WL_SCAN_COMPLETED"; break; - case WL_CONNECTED: doc["wifi"]["status"] = "WL_CONNECTED"; break; - case WL_CONNECT_FAILED: doc["wifi"]["status"] = "WL_CONNECT_FAILED"; break; - case WL_CONNECTION_LOST: doc["wifi"]["status"] = "WL_CONNECTION_LOST"; break; - case WL_DISCONNECTED: doc["wifi"]["status"] = "WL_DISCONNECTED"; break; - default: doc["wifi"]["status"] = "UNKNOWN"; break; - } - - doc["wifi"]["ssid"] = WiFi.SSID(); - doc["wifi"]["rssi"] = WiFi.RSSI(); - doc["wifi"]["bssid"] = WiFi.BSSIDstr(); - } - - doc["evse"]["temp"] = TempEVSE; - doc["evse"]["temp_max"] = maxTemp; - doc["evse"]["connected"] = evConnected; - doc["evse"]["access"] = Access_bit == 1; - doc["evse"]["mode"] = Mode; - doc["evse"]["loadbl"] = LoadBl; - doc["evse"]["pwm"] = CurrentPWM; - doc["evse"]["solar_stop_timer"] = SolarStopTimer; - doc["evse"]["state"] = evstate; - doc["evse"]["state_id"] = State; - doc["evse"]["error"] = error; - doc["evse"]["error_id"] = errorId; - doc["evse"]["rfid"] = !RFIDReader ? "Not Installed" : RFIDstatus >= 8 ? "NOSTATUS" : StrRFIDStatusWeb[RFIDstatus]; - if (RFIDReader) { - char buf[13]; - sprintf(buf, "%02X%02X%02X%02X%02X%02X", RFID[1], RFID[2], RFID[3], RFID[4], RFID[5], RFID[6]); - doc["evse"]["rfid_lastread"] = buf; - } - - doc["settings"]["charge_current"] = Balanced[0]; - doc["settings"]["override_current"] = OverrideCurrent; - doc["settings"]["current_min"] = MinCurrent; - doc["settings"]["current_max"] = MaxCurrent; - doc["settings"]["current_main"] = MaxMains; - doc["settings"]["current_max_circuit"] = MaxCircuit; - doc["settings"]["current_max_sum_mains"] = MaxSumMains; - doc["settings"]["solar_max_import"] = ImportCurrent; - doc["settings"]["solar_start_current"] = StartCurrent; - doc["settings"]["solar_stop_time"] = StopTime; - doc["settings"]["enable_C2"] = StrEnableC2[EnableC2]; - doc["settings"]["modem"] = StrModem[Modem]; - doc["settings"]["mains_meter"] = EMConfig[MainsMeter].Desc; - doc["settings"]["starttime"] = (DelayedStartTime.epoch2 ? DelayedStartTime.epoch2 + EPOCH2_OFFSET : 0); - doc["settings"]["stoptime"] = (DelayedStopTime.epoch2 ? DelayedStopTime.epoch2 + EPOCH2_OFFSET : 0); - doc["settings"]["repeat"] = DelayedRepeat; -#if MODEM - if (Modem) { - doc["settings"]["required_evccid"] = RequiredEVCCID; - doc["ev_state"]["initial_soc"] = InitialSoC; - doc["ev_state"]["remaining_soc"] = RemainingSoC; - doc["ev_state"]["full_soc"] = FullSoC; - doc["ev_state"]["energy_capacity"] = EnergyCapacity > 0 ? round((float)EnergyCapacity / 100)/10 : -1; //in kWh, precision 1 decimal; - doc["ev_state"]["energy_request"] = EnergyRequest > 0 ? round((float)EnergyRequest / 100)/10 : -1; //in kWh, precision 1 decimal - doc["ev_state"]["computed_soc"] = ComputedSoC; - doc["ev_state"]["evccid"] = EVCCID; - doc["ev_state"]["time_until_full"] = TimeUntilFull; - } -#endif - -#if MQTT - doc["mqtt"]["host"] = MQTTHost; - doc["mqtt"]["port"] = MQTTPort; - doc["mqtt"]["topic_prefix"] = MQTTprefix; - doc["mqtt"]["username"] = MQTTuser; - doc["mqtt"]["password_set"] = MQTTpassword != ""; - - if (MQTTclient.connected) { - doc["mqtt"]["status"] = "Connected"; - } else { - doc["mqtt"]["status"] = "Disconnected"; - } -#endif - - doc["home_battery"]["current"] = homeBatteryCurrent; - doc["home_battery"]["last_update"] = homeBatteryLastUpdate; - - doc["ev_meter"]["description"] = EMConfig[EVMeter].Desc; - doc["ev_meter"]["address"] = EVMeterAddress; - doc["ev_meter"]["import_active_power"] = round((float)PowerMeasured / 100)/10; //in kW, precision 1 decimal - doc["ev_meter"]["total_kwh"] = round((float)EnergyEV / 100)/10; //in kWh, precision 1 decimal - doc["ev_meter"]["charged_kwh"] = round((float)EnergyCharged / 100)/10; //in kWh, precision 1 decimal - doc["ev_meter"]["currents"]["TOTAL"] = Irms_EV[0] + Irms_EV[1] + Irms_EV[2]; - doc["ev_meter"]["currents"]["L1"] = Irms_EV[0]; - doc["ev_meter"]["currents"]["L2"] = Irms_EV[1]; - doc["ev_meter"]["currents"]["L3"] = Irms_EV[2]; - doc["ev_meter"]["import_active_energy"] = round((float)EV_import_active_energy / 100)/10; //in kWh, precision 1 decimal - doc["ev_meter"]["export_active_energy"] = round((float)EV_export_active_energy / 100)/10; //in kWh, precision 1 decimal - - doc["mains_meter"]["import_active_energy"] = round((float)Mains_import_active_energy / 100)/10; //in kWh, precision 1 decimal - doc["mains_meter"]["export_active_energy"] = round((float)Mains_export_active_energy / 100)/10; //in kWh, precision 1 decimal - - doc["phase_currents"]["TOTAL"] = Irms[0] + Irms[1] + Irms[2]; - doc["phase_currents"]["L1"] = Irms[0]; - doc["phase_currents"]["L2"] = Irms[1]; - doc["phase_currents"]["L3"] = Irms[2]; - doc["phase_currents"]["last_data_update"] = phasesLastUpdate; - doc["phase_currents"]["original_data"]["TOTAL"] = IrmsOriginal[0] + IrmsOriginal[1] + IrmsOriginal[2]; - doc["phase_currents"]["original_data"]["L1"] = IrmsOriginal[0]; - doc["phase_currents"]["original_data"]["L2"] = IrmsOriginal[1]; - doc["phase_currents"]["original_data"]["L3"] = IrmsOriginal[2]; - - doc["backlight"]["timer"] = BacklightTimer; - doc["backlight"]["status"] = backlight; - - String json; - serializeJson(doc, json); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n", json.c_str()); // Yes. Respond JSON - } else if (!memcmp("POST", hm->method.ptr, hm->method.len)) { // if POST - DynamicJsonDocument doc(512); // https://arduinojson.org/v6/assistant/ - - if(request->hasParam("backlight")) { - int backlight = request->getParam("backlight")->value().toInt(); - BacklightTimer = backlight * BACKLIGHT; - doc["Backlight"] = backlight; - } - - if(request->hasParam("current_min")) { - int current = request->getParam("current_min")->value().toInt(); - if(current >= MIN_CURRENT && current <= 16 && LoadBl < 2) { - MinCurrent = current; - doc["current_min"] = MinCurrent; - write_settings(); - } else { - doc["current_min"] = "Value not allowed!"; - } - } - - if(request->hasParam("current_max_sum_mains")) { - int current = request->getParam("current_max_sum_mains")->value().toInt(); - if(current >= 10 && current <= 600 && LoadBl < 2) { - MaxSumMains = current; - doc["current_max_sum_mains"] = MaxSumMains; - write_settings(); - } else { - doc["current_max_sum_mains"] = "Value not allowed!"; - } - } - - if(request->hasParam("disable_override_current")) { - OverrideCurrent = 0; - doc["disable_override_current"] = "OK"; - } - - if(request->hasParam("mode")) { - String mode = request->getParam("mode")->value(); - - //first check if we have a delayed mode switch - if(request->hasParam("starttime")) { - String DelayedStartTimeStr = request->getParam("starttime")->value(); - //string time_str = "2023-04-14T11:31"; - if (!StoreTimeString(DelayedStartTimeStr, &DelayedStartTime)) { - //parse OK - if (DelayedStartTime.diff > 0) - setAccess(0); //switch to OFF, we are Delayed Charging - else {//we are in the past so no delayed charging - DelayedStartTime.epoch2 = DELAYEDSTARTTIME; - DelayedStopTime.epoch2 = DELAYEDSTOPTIME; - DelayedRepeat = 0; - } - } - else { - //we couldn't parse the string, so we are NOT Delayed Charging - DelayedStartTime.epoch2 = DELAYEDSTARTTIME; - DelayedStopTime.epoch2 = DELAYEDSTOPTIME; - DelayedRepeat = 0; - } - - // so now we might have a starttime and we might be Delayed Charging - if (DelayedStartTime.epoch2) { - //we only accept a DelayedStopTime if we have a valid DelayedStartTime - if(request->hasParam("stoptime")) { - String DelayedStopTimeStr = request->getParam("stoptime")->value(); - //string time_str = "2023-04-14T11:31"; - if (!StoreTimeString(DelayedStopTimeStr, &DelayedStopTime)) { - //parse OK - if (DelayedStopTime.diff <= 0 || DelayedStopTime.epoch2 <= DelayedStartTime.epoch2) - //we are in the past or DelayedStopTime before DelayedStartTime so no DelayedStopTime - DelayedStopTime.epoch2 = DELAYEDSTOPTIME; - } - else - //we couldn't parse the string, so no DelayedStopTime - DelayedStopTime.epoch2 = DELAYEDSTOPTIME; - doc["stoptime"] = (DelayedStopTime.epoch2 ? DelayedStopTime.epoch2 + EPOCH2_OFFSET : 0); - if(request->hasParam("repeat")) { - int Repeat = request->getParam("repeat")->value().toInt(); - if (Repeat >= 0 && Repeat <= 1) { //boundary check - DelayedRepeat = Repeat; - doc["repeat"] = Repeat; - } - } - } - - } - doc["starttime"] = (DelayedStartTime.epoch2 ? DelayedStartTime.epoch2 + EPOCH2_OFFSET : 0); - } else - DelayedStartTime.epoch2 = DELAYEDSTARTTIME; - - - switch(mode.toInt()) { - case 0: // OFF - ToModemWaitStateTimer = 0; - ToModemDoneStateTimer = 0; - LeaveModemDoneStateTimer = 0; - LeaveModemDeniedStateTimer = 0; - setAccess(0); - break; - case 1: - setMode(MODE_NORMAL); - break; - case 2: - setMode(MODE_SOLAR); - break; - case 3: - setMode(MODE_SMART); - break; - default: - mode = "Value not allowed!"; - } - doc["mode"] = mode; - } - - if(request->hasParam("enable_C2")) { - EnableC2 = (EnableC2_t) request->getParam("enable_C2")->value().toInt(); - write_settings(); - doc["settings"]["enable_C2"] = StrEnableC2[EnableC2]; - } - - if(request->hasParam("modem")) { - Modem = (Modem_t) request->getParam("modem")->value().toInt(); - doc["settings"]["modem"] = StrModem[Modem]; - } - - if(request->hasParam("stop_timer")) { - int stop_timer = request->getParam("stop_timer")->value().toInt(); - - if(stop_timer >= 0 && stop_timer <= 60) { - StopTime = stop_timer; - doc["stop_timer"] = true; - write_settings(); - } else { - doc["stop_timer"] = false; - } - - } - - if(Mode == MODE_NORMAL || Mode == MODE_SMART) { - if(request->hasParam("override_current")) { - int current = request->getParam("override_current")->value().toInt(); - if (LoadBl < 2 && (current == 0 || (current >= ( MinCurrent * 10 ) && current <= ( MaxCurrent * 10 )))) { //OverrideCurrent not possible on Slave - OverrideCurrent = current; - doc["override_current"] = OverrideCurrent; - } else { - doc["override_current"] = "Value not allowed!"; - } - } - } - - if(request->hasParam("solar_start_current")) { - int current = request->getParam("solar_start_current")->value().toInt(); - if(current >= 0 && current <= 48) { - StartCurrent = current; - doc["solar_start_current"] = StartCurrent; - write_settings(); - } else { - doc["solar_start_current"] = "Value not allowed!"; - } - } - - if(request->hasParam("solar_max_import")) { - int current = request->getParam("solar_max_import")->value().toInt(); - if(current >= 0 && current <= 48) { - ImportCurrent = current; - doc["solar_max_import"] = ImportCurrent; - write_settings(); - } else { - doc["solar_max_import"] = "Value not allowed!"; - } - } - - //special section to post stuff for experimenting with an ISO15118 modem - if(request->hasParam("override_pwm")) { - int pwm = request->getParam("override_pwm")->value().toInt(); - if (pwm == 0){ - CP_OFF; - CPDutyOverride = true; - } else if (pwm < 0){ - CP_ON; - CPDutyOverride = false; - pwm = 100; // 10% until next loop, to be safe, corresponds to 6A - } else{ - CP_ON; - CPDutyOverride = true; - } - - SetCPDuty(pwm); - doc["override_pwm"] = pwm; - } - - //allow basic plug 'n charge based on evccid - //if required_evccid is set to a value, SmartEVSE will only allow charging requests from said EVCCID - if(request->hasParam("required_evccid")) { - if (request->getParam("required_evccid")->value().length() <= 32) { - strncpy(RequiredEVCCID, request->getParam("required_evccid")->value().c_str(), sizeof(RequiredEVCCID)); - doc["required_evccid"] = RequiredEVCCID; - write_settings(); - } else { - doc["required_evccid"] = "EVCCID too long (max 32 char)"; - } - } - -#if MQTT - if(request->hasParam("mqtt_update")) { - if (request->getParam("mqtt_update")->value().toInt() == 1) { - - if(request->hasParam("mqtt_host")) { - MQTTHost = request->getParam("mqtt_host")->value(); - doc["mqtt_host"] = MQTTHost; - } - - if(request->hasParam("mqtt_port")) { - MQTTPort = request->getParam("mqtt_port")->value().toInt(); - if (MQTTPort == 0) MQTTPort = 1883; - doc["mqtt_port"] = MQTTPort; - } - - if(request->hasParam("mqtt_topic_prefix")) { - MQTTprefix = request->getParam("mqtt_topic_prefix")->value(); - if (!MQTTprefix || MQTTprefix == "") { - MQTTprefix = APhostname; - } - doc["mqtt_topic_prefix"] = MQTTprefix; - } - - if(request->hasParam("mqtt_username")) { - MQTTuser = request->getParam("mqtt_username")->value(); - if (!MQTTuser || MQTTuser == "") { - MQTTuser.clear(); - } - doc["mqtt_username"] = MQTTuser; - } - - if(request->hasParam("mqtt_password")) { - MQTTpassword = request->getParam("mqtt_password")->value(); - if (!MQTTpassword || MQTTpassword == "") { - MQTTpassword.clear(); - } - doc["mqtt_password_set"] = (MQTTpassword != ""); - } - // disconnect mqtt so it will automatically reconnect with then new params - MQTTclient.disconnect(); - write_settings(); - } - } -#endif - - String json; - serializeJson(doc, json); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n", json.c_str()); // Yes. Respond JSON - } else { - mg_http_reply(c, 404, "", "Not Found\n"); - } - } else if (mg_http_match_uri(hm, "/currents") && !memcmp("POST", hm->method.ptr, hm->method.len)) { - DynamicJsonDocument doc(200); - - if(request->hasParam("battery_current")) { - if (LoadBl < 2) { - homeBatteryCurrent = request->getParam("battery_current")->value().toInt(); - homeBatteryLastUpdate = time(NULL); - doc["battery_current"] = homeBatteryCurrent; - } else - doc["battery_current"] = "not allowed on slave"; - } - - if(MainsMeter == EM_API) { - if(request->hasParam("L1") && request->hasParam("L2") && request->hasParam("L3")) { - if (LoadBl < 2) { - Irms[0] = request->getParam("L1")->value().toInt(); - Irms[1] = request->getParam("L2")->value().toInt(); - Irms[2] = request->getParam("L3")->value().toInt(); - - CalcIsum(); - for (int x = 0; x < 3; x++) { - doc["original"]["L" + x] = IrmsOriginal[x]; - doc["L" + x] = Irms[x]; - } - doc["TOTAL"] = Isum; - - MainsMeterTimeout = COMM_TIMEOUT; - - } else - doc["TOTAL"] = "not allowed on slave"; - } - } - - String json; - serializeJson(doc, json); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\r\n", json.c_str()); // Yes. Respond JSON - - } else if (mg_http_match_uri(hm, "/ev_meter") && !memcmp("POST", hm->method.ptr, hm->method.len)) { - DynamicJsonDocument doc(200); - - if(EVMeter == EM_API) { - if(request->hasParam("L1") && request->hasParam("L2") && request->hasParam("L3")) { - - Irms_EV[0] = request->getParam("L1")->value().toInt(); - Irms_EV[1] = request->getParam("L2")->value().toInt(); - Irms_EV[2] = request->getParam("L3")->value().toInt(); - CalcImeasured_EV(); - EVMeterTimeout = COMM_EVTIMEOUT; - for (int x = 0; x < 3; x++) - doc["ev_meter"]["currents"]["L" + x] = Irms_EV[x]; - doc["ev_meter"]["currents"]["TOTAL"] = Irms_EV[0] + Irms_EV[1] + Irms_EV[2]; - } - - if(request->hasParam("import_active_energy") && request->hasParam("export_active_energy") && request->hasParam("import_active_power")) { - - EV_import_active_energy = request->getParam("import_active_energy")->value().toInt(); - EV_export_active_energy = request->getParam("export_active_energy")->value().toInt(); - - PowerMeasured = request->getParam("import_active_power")->value().toInt(); - - EnergyEV = EV_import_active_energy - EV_export_active_energy; - if (ResetKwh == 2) EnergyMeterStart = EnergyEV; // At powerup, set EnergyEV to kwh meter value - EnergyCharged = EnergyEV - EnergyMeterStart; // Calculate Energy - if (Modem) - RecomputeSoC(); - doc["ev_meter"]["import_active_power"] = PowerMeasured; - doc["ev_meter"]["import_active_energy"] = EV_import_active_energy; - doc["ev_meter"]["export_active_energy"] = EV_export_active_energy; - doc["ev_meter"]["total_kwh"] = EnergyEV; - doc["ev_meter"]["charged_kwh"] = EnergyCharged; - } - } - - String json; - serializeJson(doc, json); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\r\n", json.c_str()); // Yes. Respond JSON - - } else if (mg_http_match_uri(hm, "/reboot") && !memcmp("POST", hm->method.ptr, hm->method.len)) { - DynamicJsonDocument doc(20); - - ESP.restart(); - doc["reboot"] = true; - - String json; - serializeJson(doc, json); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\r\n", json.c_str()); // Yes. Respond JSON - - } else if (mg_http_match_uri(hm, "/ev_state") && !memcmp("POST", hm->method.ptr, hm->method.len)) { - DynamicJsonDocument doc(200); - - //State of charge posting - int current_soc = request->getParam("current_soc")->value().toInt(); - int full_soc = request->getParam("full_soc")->value().toInt(); - - // Energy requested by car - int energy_request = request->getParam("energy_request")->value().toInt(); - - // Total energy capacity of car's battery - int energy_capacity = request->getParam("energy_capacity")->value().toInt(); - - // Update EVCCID of car - if (request->hasParam("evccid")) { - if (request->getParam("evccid")->value().length() <= 32) { - strncpy(EVCCID, request->getParam("evccid")->value().c_str(), sizeof(EVCCID)); - doc["evccid"] = EVCCID; - } - } - - if (full_soc >= FullSoC) // Only update if we received it, since sometimes it's there, sometimes it's not - FullSoC = full_soc; - - if (energy_capacity >= EnergyCapacity) // Only update if we received it, since sometimes it's there, sometimes it's not - EnergyCapacity = energy_capacity; - - if (energy_request >= EnergyRequest) // Only update if we received it, since sometimes it's there, sometimes it's not - EnergyRequest = energy_request; - - if (current_soc >= 0 && current_soc <= 100) { - // We set the InitialSoC for our own calculations - InitialSoC = current_soc; - - // We also set the ComputedSoC to allow for app integrations - ComputedSoC = current_soc; - - // Skip waiting, charge since we have what we've got - if (State == STATE_MODEM_REQUEST || State == STATE_MODEM_WAIT || State == STATE_MODEM_DONE){ - _LOG_A("Received SoC via REST. Shortcut to State Modem Done\n"); - setState(STATE_MODEM_DONE); // Go to State B, which means in this case setting PWM - } - } - - RecomputeSoC(); - - doc["current_soc"] = current_soc; - doc["full_soc"] = full_soc; - doc["energy_capacity"] = energy_capacity; - doc["energy_request"] = energy_request; - - String json; - serializeJson(doc, json); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\r\n", json.c_str()); // Yes. Respond JSON - -#if FAKE_RFID - //this can be activated by: http://smartevse-xxx.lan/debug?showrfid=1 - } else if (mg_http_match_uri(hm, "/debug") && !memcmp("GET", hm->method.ptr, hm->method.len)) { - if(request->hasParam("showrfid")) { - Show_RFID = strtol(request->getParam("showrfid")->value().c_str(),NULL,0); - } - _LOG_A("DEBUG: Show_RFID=%u.\n",Show_RFID); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\r\n", ""); //json request needs json response -#endif - -#if AUTOMATED_TESTING - //this can be activated by: http://smartevse-xxx.lan/automated_testing?current_max=100 - //WARNING: because of automated testing, no limitations here! - //THAT IS DANGEROUS WHEN USED IN PRODUCTION ENVIRONMENT - //FOR SMARTEVSE's IN A TESTING BENCH ONLY!!!! - } else if (mg_http_match_uri(hm, "/automated_testing") && !memcmp("POST", hm->method.ptr, hm->method.len)) { - if(request->hasParam("current_max")) { - MaxCurrent = strtol(request->getParam("current_max")->value().c_str(),NULL,0); - } - if(request->hasParam("current_main")) { - MaxMains = strtol(request->getParam("current_main")->value().c_str(),NULL,0); - } - if(request->hasParam("current_max_circuit")) { - MaxCircuit = strtol(request->getParam("current_max_circuit")->value().c_str(),NULL,0); - } - if(request->hasParam("mainsmeter")) { - MainsMeter = strtol(request->getParam("mainsmeter")->value().c_str(),NULL,0); - } - if(request->hasParam("evmeter")) { - EVMeter = strtol(request->getParam("evmeter")->value().c_str(),NULL,0); - } - if(request->hasParam("config")) { - Config = strtol(request->getParam("config")->value().c_str(),NULL,0); - setState(STATE_A); // so the new value will actually be read - } - if(request->hasParam("loadbl")) { - int LBL = strtol(request->getParam("loadbl")->value().c_str(),NULL,0); - ConfigureModbusMode(LBL); - LoadBl = LBL; - } - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\r\n", ""); //json request needs json response -#endif - } else { // if everything else fails, serve static page - struct mg_http_serve_opts opts = {.root_dir = "/data", .ssi_pattern = NULL, .extra_headers = NULL, .mime_types = NULL, .page404 = NULL, .fs = &mg_fs_packed }; - //opts.fs = NULL; - mg_http_serve_dir(c, hm, &opts); - } - delete request; - } -} - -void onWifiEvent(WiFiEvent_t event) { - switch (event) { - case WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP: - _LOG_A("Connected to AP: %s\nLocal IP: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - break; - case WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED: - _LOG_A("Connected or reconnected to WiFi\n"); - delay(1000); - //load dhcp dns ip4 address into mongoose - static char dns4url[]="udp://123.123.123.123:53"; - sprintf(dns4url, "udp://%s:53", WiFi.dnsIP().toString().c_str()); - mgr.dns4.url = dns4url; - -#if MQTT - mg_timer_add(&mgr, 3000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, &mgr); -#endif - mg_log_set(MG_LL_NONE); - //mg_log_set(MG_LL_VERBOSE); - - if (TZinfo == "") { - bool done = false; // Event handler flips it to true - mg_http_connect(&mgr, s_url, fn_client, &done); // Create client connection - } - //end mongoose - mg_http_listen(&mgr, "http://0.0.0.0:80", fn_http_server, NULL); // Setup listener - mg_http_listen(&mgr, "http://0.0.0.0:443", fn_http_server, (void *) 1); // Setup listener - _LOG_A("HTTP server started\n"); - -#if DBG == 1 - // if we start RemoteDebug with no wifi credentials installed we get in a bootloop - // so we start it here - // Initialize the server (telnet or web socket) of RemoteDebug - Debug.begin(APhostname, 23, 1); - Debug.showColors(true); // Colors -#endif - break; - case WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - if (WIFImode == 1) { - delay(1500); // so mg_mgr_poll has timed out -#if MQTT - //mg_timer_free(&mgr); -#endif - _LOG_A("WiFi Disconnected. Reconnecting...\n"); - } - break; - default: break; - } -} - -// turns out getLocalTime only checks if the current year > 2016, and if so, decides NTP must have synced; -// this callback function actually checks if we are synced! -void timeSyncCallback(struct timeval *tv) -{ - LocalTimeSet = true; - _LOG_A("Synced clock to NTP server!"); // somehow adding a \n here hangs the device after printing this message ?!? -} - -// Setup Wifi -void WiFiSetup(void) { - mg_mgr_init(&mgr); // Initialise event manager - - const char* rsa_key_pub = mg_unpacked("/data/rsa_key.pub").ptr; - CryptoMemAsset *MyRSAKey = new CryptoMemAsset("RSA Key", rsa_key_pub, strlen(rsa_key_pub)+1 ); - auto cfg = FOTA.getConfig(); - //cfg.name = fota_name; - //cfg.manifest_url = "https://api.github.com/repos/dingo35/SmartEVSE-3.5/releases/latest"; - //cfg.sem = SemverClass( 1, 0, 0 ); // major, minor, patch - //cfg.check_sig = false; // verify signed firmware with rsa public key - cfg.check_sig = true; // verify signed firmware with rsa public key - cfg.unsafe = true; // disable certificate check when using TLS - //cfg.root_ca = MyRootCA; - cfg.pub_key = MyRSAKey; - //cfg.use_device_id = false; - FOTA.setConfig( cfg ); - //FOTA.printConfig(); - - // callback function prevents serial comm error - FOTA.setProgressCb( [](size_t progress, size_t size) { - _LOG_V("Firmware update progress %i/%i.\n", progress, size); - //move this data to global var - downloadProgress = progress; - downloadSize = size; - //give background tasks some air - vTaskDelay(100 / portTICK_PERIOD_MS); - }); - - // we abuse the downloadProgress indicator for status info: - // downloadProgress = -1 : success - // downloadProgress = -2 : fail - FOTA.setUpdateFinishedCb( [](int partition, bool restart_after) { - _LOG_V("Update succesfully completed at %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" ); - downloadProgress = -1; - }); - - FOTA.setUpdateBeginFailCb( [](int partition) { - _LOG_A("ERROR: Update failed at %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" ); - downloadProgress = -2; - }); - - //FOTA.setExtraHTTPHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0"); - FOTA.setExtraHTTPHeader("User-Agent", "SmartEVSE-v3"); - FOTA.setExtraHTTPHeader("Accept", "application/vnd.github+json"); - FOTA.setExtraHTTPHeader("X-GitHub-Api-Version", "2022-11-28" ); - //FOTA.setRootCA( MyRootCA ); - - //wifiManager.setDebugOutput(true); - wifiManager.setMinimumSignalQuality(-1); - WiFi.setAutoReconnect(true); - //WiFi.persistent(true); - WiFi.onEvent(onWifiEvent); - handleWIFImode(); //go into the mode that was saved in nonvolatile memory - - // Start the mDNS responder so that the SmartEVSE can be accessed using a local hostame: http://SmartEVSE-xxxxxx.local - if (!MDNS.begin(APhostname.c_str())) { - _LOG_A("Error setting up MDNS responder!\n"); - } else { - _LOG_A("mDNS responder started. http://%s.local\n",APhostname.c_str()); - MDNS.addService("http", "tcp", 80); // announce Web server - } - - // Init and get the time - // First option to get time from local ntp server blocks the second fallback option since 2021: - // See https://github.com/espressif/arduino-esp32/issues/4964 - //sntp_servermode_dhcp(1); //try to get the ntp server from dhcp - sntp_setservername(1, "europe.pool.ntp.org"); //fallback server - sntp_set_time_sync_notification_cb(timeSyncCallback); - sntp_init(); -} - -void SetupPortalTask(void * parameter) { - _LOG_A("Start Portal...\n"); - WiFi.disconnect(true); - wifiManager.setAPStaticIPConfig(IPAddress(192,168,4,1), IPAddress(192,168,4,1), IPAddress(255,255,255,0)); - //wifiManager.setTitle(String title); - - //don't show firmware update buttons in portal - std::vector wmMenuItems = { "wifi", "info", "erase", "exit" }; - wifiManager.setMenu(wmMenuItems); - wifiManager.setShowInfoUpdate(false); - wifiManager.setShowStaticFields(true); // force show static ip fields - wifiManager.setShowDnsFields(true); // force show dns field always - - wifiManager.setConfigPortalTimeout(120); // Portal will be available 2 minutes to connect to, then close. (if connected within this time, it will remain active) - mg_mgr_free(&mgr); - delay(1000); - wifiManager.startConfigPortal(APhostname.c_str(), APpassword.c_str()); - //_LOG_A("SetupPortalTask free ram: %u\n", uxTaskGetStackHighWaterMark( NULL )); - WiFi.disconnect(true); - - WIFImode = 1; - //mongoose - mg_mgr_init(&mgr); // Initialise event manager - handleWIFImode(); - write_settings(); - LCDNav = 0; - vTaskDelete(NULL); //end this task so it will not take up resources -} - -void handleWIFImode() { - - if (WIFImode == 2 && WiFi.getMode() != WIFI_AP_STA) - //now start the portal in the background, so other tasks keep running - xTaskCreate( - SetupPortalTask, // Function that should be called - "SetupPortalTask", // Name of the task (for debugging) - 10000, // Stack size (bytes) // printf needs atleast 1kb - NULL, // Parameter to pass - 1, // Task priority - NULL // Task handleCTReceive - ); - - if (WIFImode == 1 && WiFi.getMode() == WIFI_OFF) { - _LOG_A("Starting WiFi..\n"); - WiFi.mode(WIFI_STA); - WiFi.begin(); - } - - if (WIFImode == 0 && WiFi.getMode() != WIFI_OFF) { - _LOG_A("Stopping WiFi..\n"); - WiFi.disconnect(true); - } -} - - -void setup() { - - pinMode(PIN_CP_OUT, OUTPUT); // CP output - pinMode(PIN_SW_IN, INPUT); // SW Switch input - pinMode(PIN_SSR, OUTPUT); // SSR1 output - pinMode(PIN_SSR2, OUTPUT); // SSR2 output - pinMode(PIN_RCM_FAULT, INPUT_PULLUP); - - pinMode(PIN_LCD_LED, OUTPUT); // LCD backlight - pinMode(PIN_LCD_RST, OUTPUT); // LCD reset - pinMode(PIN_IO0_B1, INPUT); // < button - pinMode(PIN_LCD_A0_B2, OUTPUT); // o Select button + A0 LCD - pinMode(PIN_LCD_SDO_B3, OUTPUT); // > button + SDA/MOSI pin - - pinMode(PIN_LOCK_IN, INPUT); // Locking Solenoid input - pinMode(PIN_LEDR, OUTPUT); // Red LED output - pinMode(PIN_LEDG, OUTPUT); // Green LED output - pinMode(PIN_LEDB, OUTPUT); // Blue LED output - pinMode(PIN_ACTA, OUTPUT); // Actuator Driver output R - pinMode(PIN_ACTB, OUTPUT); // Actuator Driver output W - pinMode(PIN_CPOFF, OUTPUT); // Disable CP output (active high) - pinMode(PIN_RS485_RX, INPUT); - pinMode(PIN_RS485_TX, OUTPUT); - pinMode(PIN_RS485_DIR, OUTPUT); - - digitalWrite(PIN_LEDR, LOW); - digitalWrite(PIN_LEDG, LOW); - digitalWrite(PIN_LEDB, LOW); - digitalWrite(PIN_ACTA, LOW); - digitalWrite(PIN_ACTB, LOW); - digitalWrite(PIN_SSR, LOW); // SSR1 OFF - digitalWrite(PIN_SSR2, LOW); // SSR2 OFF - digitalWrite(PIN_LCD_LED, HIGH); // LCD Backlight ON - CP_OFF; // CP signal OFF - - - // Uart 0 debug/program port - Serial.begin(115200); - while (!Serial); - _LOG_A("\nSmartEVSE v3 powerup\n"); - - // configure SPI connection to LCD - // only the SPI_SCK and SPI_MOSI pins are used - SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI, SPI_SS); - // the ST7567's max SPI Clock frequency is 20Mhz at 3.3V/25C - // We choose 10Mhz here, to reserve some room for error. - // SPI mode is MODE3 (Idle = HIGH, clock in on rising edge) - SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE3)); - - - // The CP (control pilot) output is a fixed 1khz square-wave (+6..9v / -12v). - // It's pulse width varies between 10% and 96% indicating 6A-80A charging current. - // to detect state changes we should measure the CP signal while it's at ~5% (so 50uS after the positive pulse started) - // we use an i/o interrupt at the CP pin output, and a one shot timer interrupt to start the ADC conversion. - // would be nice if there was an easier way... - - // setup timer, and one shot timer interrupt to 50us - timerA = timerBegin(0, 80, true); - timerAttachInterrupt(timerA, &onTimerA, false); - // we start in STATE A, with a static +12V CP signal - // set alarm to trigger every 1mS, and let it reload every 1ms - timerAlarmWrite(timerA, PWM_100, true); - // when PWM is active, we sample the CP pin after 5% - timerAlarmEnable(timerA); - - - // Setup ADC on CP, PP and Temperature pin - adc1_config_width(ADC_WIDTH_BIT_10); // 10 bits ADC resolution is enough - adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11); // setup the CP pin input attenuation to 11db - adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_6); // setup the PP pin input attenuation to 6db - adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_6); // setup the Temperature input attenuation to 6db - - //Characterize the ADC at particular attentuation for each channel - adc_chars_CP = (esp_adc_cal_characteristics_t *) calloc(1, sizeof(esp_adc_cal_characteristics_t)); - adc_chars_PP = (esp_adc_cal_characteristics_t *) calloc(1, sizeof(esp_adc_cal_characteristics_t)); - adc_chars_Temperature = (esp_adc_cal_characteristics_t *) calloc(1, sizeof(esp_adc_cal_characteristics_t)); - esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_10, 1100, adc_chars_CP); - esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_10, 1100, adc_chars_PP); - esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_10, 1100, adc_chars_Temperature); - - - // Setup PWM on channel 0, 1000Hz, 10 bits resolution - ledcSetup(CP_CHANNEL, 1000, 10); // channel 0 => Group: 0, Channel: 0, Timer: 0 - // setup the RGB led PWM channels - // as PWM channel 1 is used by the same timer as the CP timer (channel 0), we start with channel 2 - ledcSetup(RED_CHANNEL, 5000, 8); // R channel 2, 5kHz, 8 bit - ledcSetup(GREEN_CHANNEL, 5000, 8); // G channel 3, 5kHz, 8 bit - ledcSetup(BLUE_CHANNEL, 5000, 8); // B channel 4, 5kHz, 8 bit - ledcSetup(LCD_CHANNEL, 5000, 8); // LCD channel 5, 5kHz, 8 bit - - // attach the channels to the GPIO to be controlled - ledcAttachPin(PIN_CP_OUT, CP_CHANNEL); - //pinMode(PIN_CP_OUT, OUTPUT); // Re-init the pin to output, required in order for attachInterrupt to work (2.0.2) - // not required/working on master branch.. - // see https://github.com/espressif/arduino-esp32/issues/6140 - ledcAttachPin(PIN_LEDR, RED_CHANNEL); - ledcAttachPin(PIN_LEDG, GREEN_CHANNEL); - ledcAttachPin(PIN_LEDB, BLUE_CHANNEL); - ledcAttachPin(PIN_LCD_LED, LCD_CHANNEL); - - SetCPDuty(1024); // channel 0, duty cycle 100% - ledcWrite(RED_CHANNEL, 255); - ledcWrite(GREEN_CHANNEL, 0); - ledcWrite(BLUE_CHANNEL, 255); - ledcWrite(LCD_CHANNEL, 0); - - // Setup PIN interrupt on rising edge - // the timer interrupt will be reset in the ISR. - attachInterrupt(PIN_CP_OUT, onCPpulse, RISING); - - // Uart 1 is used for Modbus @ 9600 8N1 - RTUutils::prepareHardwareSerial(Serial1); - Serial1.begin(MODBUS_BAUDRATE, SERIAL_8N1, PIN_RS485_RX, PIN_RS485_TX); - - - //Check type of calibration value used to characterize ADC - _LOG_A("Checking eFuse Vref settings: "); - if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { - _LOG_A("OK\n"); - } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { - _LOG_A("Two Point\n"); - } else { - _LOG_A("not programmed!!!\n"); - } - - // We might need some sort of authentication in the future. - // SmartEVSE v3 have programmed ECDSA-256 keys stored in nvs - // Unused for now. - if (preferences.begin("KeyStorage", true) ) { // true = readonly -//prevent compiler warning -#if DBG != 0 - uint16_t hwversion = preferences.getUShort("hwversion"); // 0x0101 (01 = SmartEVSE, 01 = hwver 01) -#endif - serialnr = preferences.getUInt("serialnr"); - String ec_private = preferences.getString("ec_private"); - String ec_public = preferences.getString("ec_public"); - preferences.end(); - - _LOG_A("hwversion %04x serialnr:%u \n",hwversion, serialnr); - //_LOG_A(ec_public); - } else { - _LOG_A("No KeyStorage found in nvs!\n"); - if (!serialnr) serialnr = MacId() & 0xffff; // when serialnr is not programmed (anymore), we use the Mac address - } - // overwrite APhostname if serialnr is programmed - APhostname = "SmartEVSE-" + String( serialnr & 0xffff, 10); // SmartEVSE access point Name = SmartEVSE-xxxxx - WiFi.setHostname(APhostname.c_str()); - - // Read all settings from non volatile memory; MQTTprefix will be overwritten if stored in NVS - read_settings(); // initialize with default data when starting for the first time - validate_settings(); - ReadRFIDlist(); // Read all stored RFID's from storage - _LOG_A("APpassword: %s\n",APpassword.c_str()); - - // Create Task EVSEStates, that handles changes in the CP signal - xTaskCreate( - EVSEStates, // Function that should be called - "EVSEStates", // Name of the task (for debugging) - 4096, // Stack size (bytes) // printf needs atleast 1kb - NULL, // Parameter to pass - 5, // Task priority - high - NULL // Task handle - ); - - // Create Task BlinkLed (10ms) - xTaskCreate( - BlinkLed, // Function that should be called - "BlinkLed", // Name of the task (for debugging) - 1024, // Stack size (bytes) // printf needs atleast 1kb - NULL, // Parameter to pass - 1, // Task priority - low - NULL // Task handle - ); - - // Create Task 100ms Timer - xTaskCreate( - Timer100ms, // Function that should be called - "Timer100ms", // Name of the task (for debugging) - 4608, // Stack size (bytes) - NULL, // Parameter to pass - 3, // Task priority - medium - NULL // Task handle - ); - - // Create Task Second Timer (1000ms) - xTaskCreate( - Timer1S, // Function that should be called - "Timer1S", // Name of the task (for debugging) - 4096, // Stack size (bytes) - NULL, // Parameter to pass - 3, // Task priority - medium - NULL // Task handle - ); - - // Setup WiFi, webserver and firmware OTA - // Please be aware that after doing a OTA update, its possible that the active partition is set to OTA1. - // Uploading a new firmware through USB will however update OTA0, and you will not notice any changes... - WiFiSetup(); - - // Set eModbus LogLevel to 1, to suppress possible E5 errors - MBUlogLvl = LOG_LEVEL_CRITICAL; - ConfigureModbusMode(255); - - BacklightTimer = BACKLIGHT; - GLCD_init(); - - CP_ON; // CP signal ACTIVE - - -} - -void loop() { - //this loop is for non-time critical stuff that needs to run approx 1 / second - if (WiFi.isConnected()) - mg_mgr_poll(&mgr, 1000); - else - vTaskDelay(1000 / portTICK_PERIOD_MS); - - getLocalTime(&timeinfo, 1000U); - if (!LocalTimeSet && WIFImode == 1) { - _LOG_A("Time not synced with NTP yet.\n"); - } - - if (shouldReboot) { - delay(1000); - ESP.restart(); - } - -#ifndef DEBUG_DISABLED - // Remote debug over WiFi - Debug.handle(); -#endif - - // TODO move this to a once a minute loop? - if (DelayedStartTime.epoch2 && LocalTimeSet) { - // Compare the times - time_t now = time(nullptr); //get current local time - DelayedStartTime.diff = DelayedStartTime.epoch2 - (mktime(localtime(&now)) - EPOCH2_OFFSET); - if (DelayedStartTime.diff > 0) { - if (Access_bit != 0 && (DelayedStopTime.epoch2 == 0 || DelayedStopTime.epoch2 > DelayedStartTime.epoch2)) - setAccess(0); //switch to OFF, we are Delayed Charging - } - else { - //starttime is in the past so we are NOT Delayed Charging, or we are Delayed Charging but the starttime has passed! - if (DelayedRepeat == 1) - DelayedStartTime.epoch2 += 24 * 3600; //add 24 hours so we now have a new starttime - else - DelayedStartTime.epoch2 = DELAYEDSTARTTIME; - setAccess(1); - } - } - //only update StopTime.diff if starttime has already passed - if (DelayedStopTime.epoch2 && LocalTimeSet) { - // Compare the times - time_t now = time(nullptr); //get current local time - DelayedStopTime.diff = DelayedStopTime.epoch2 - (mktime(localtime(&now)) - EPOCH2_OFFSET); - if (DelayedStopTime.diff <= 0) { - //DelayedStopTime has passed - if (DelayedRepeat == 1) //we are on a daily repetition schedule - DelayedStopTime.epoch2 += 24 * 3600; //add 24 hours so we now have a new starttime - else - DelayedStopTime.epoch2 = DELAYEDSTOPTIME; - setAccess(0); //switch to OFF - } - } -} diff --git a/SmartEVSE-3/SmartEVSE-3/src/font.cpp b/SmartEVSE-3/SmartEVSE-3/src/font.cpp deleted file mode 100644 index 5ec6dec..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/font.cpp +++ /dev/null @@ -1,266 +0,0 @@ -#include "glcd.h" - -#ifdef GLCD_FULL_CHARSET -const unsigned char font[0x100][5] = { -#else -const unsigned char font[0x7F][5] = { -#endif - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x00 - {0x00, 0x12, 0x1F, 0x10, 0x00}, // 0x01 � - {0x00, 0x19, 0x1D, 0x17, 0x12}, // 0x02 � - {0x00, 0x11, 0x15, 0x1F, 0x0A}, // 0x03 � - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x04 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x05 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x06 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x07 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x08 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x09 - {0x08, 0x1C, 0x1C, 0x1C, 0x08}, // 0x0A Energy blob - {0x63, 0x55, 0x49, 0x41, 0x63}, // 0x0B Sum - {0x00, 0x02, 0x05, 0x02, 0x00}, // 0x0C � - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x0D - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x0E - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x0F - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x10 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x11 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x12 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x13 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x14 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x15 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x16 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x17 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x18 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x19 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x1A - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x1B - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x1C - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x1D - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x1E - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x1F - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x20 Space - {0x00, 0x00, 0x5F, 0x00, 0x00}, // 0x21 ! - {0x00, 0x07, 0x00, 0x07, 0x00}, // 0x22 " - {0x14, 0x7F, 0x14, 0x7F, 0x14}, // 0x23 # - {0x24, 0x2A, 0x7F, 0x2A, 0x12}, // 0x24 $ - {0x23, 0x13, 0x08, 0x64, 0x62}, // 0x25 % - {0x36, 0x49, 0x56, 0x20, 0x50}, // 0x26 & - {0x00, 0x08, 0x07, 0x03, 0x00}, // 0x27 ' - {0x00, 0x1C, 0x22, 0x41, 0x00}, // 0x28 ( - {0x00, 0x41, 0x22, 0x1C, 0x00}, // 0x29 ) - {0x2A, 0x1C, 0x7F, 0x1C, 0x2A}, // 0x2A * - {0x08, 0x08, 0x3E, 0x08, 0x08}, // 0x2B + - {0x00, 0x80, 0x70, 0x30, 0x00}, // 0x2C , - {0x08, 0x08, 0x08, 0x08, 0x08}, // 0x2D - - {0x00, 0x00, 0x60, 0x60, 0x00}, // 0x2E . - {0x20, 0x10, 0x08, 0x04, 0x02}, // 0x2F / - {0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0x30 0 - {0x00, 0x42, 0x7F, 0x40, 0x00}, // 0x31 1 - {0x72, 0x49, 0x49, 0x49, 0x46}, // 0x32 2 - {0x21, 0x41, 0x49, 0x4D, 0x33}, // 0x33 3 - {0x18, 0x14, 0x12, 0x7F, 0x10}, // 0x34 4 - {0x27, 0x45, 0x45, 0x45, 0x39}, // 0x35 5 - {0x3C, 0x4A, 0x49, 0x49, 0x31}, // 0x36 6 - {0x41, 0x21, 0x11, 0x09, 0x07}, // 0x37 7 - {0x36, 0x49, 0x49, 0x49, 0x36}, // 0x38 8 - {0x46, 0x49, 0x49, 0x29, 0x1E}, // 0x39 9 - {0x00, 0x00, 0x14, 0x00, 0x00}, // 0x3A : - {0x00, 0x40, 0x34, 0x00, 0x00}, // 0x3B ; - {0x00, 0x08, 0x14, 0x22, 0x41}, // 0x3C < - {0x14, 0x14, 0x14, 0x14, 0x14}, // 0x3D = - {0x00, 0x41, 0x22, 0x14, 0x08}, // 0x3E > - {0x02, 0x01, 0x59, 0x09, 0x06}, // 0x3F ? - {0x3E, 0x41, 0x5D, 0x59, 0x4E}, // 0x40 @ - {0x7C, 0x12, 0x11, 0x12, 0x7C}, // 0x41 A - {0x7F, 0x49, 0x49, 0x49, 0x36}, // 0x42 B - {0x3E, 0x41, 0x41, 0x41, 0x22}, // 0x43 C - {0x7F, 0x41, 0x41, 0x41, 0x3E}, // 0x44 D - {0x7F, 0x49, 0x49, 0x49, 0x41}, // 0x45 E - {0x7F, 0x09, 0x09, 0x09, 0x01}, // 0x46 F - {0x3E, 0x41, 0x41, 0x51, 0x73}, // 0x47 G - {0x7F, 0x08, 0x08, 0x08, 0x7F}, // 0x48 H - {0x00, 0x41, 0x7F, 0x41, 0x00}, // 0x49 I - {0x20, 0x40, 0x41, 0x3F, 0x01}, // 0x4A J - {0x7F, 0x08, 0x14, 0x22, 0x41}, // 0x4B K - {0x7F, 0x40, 0x40, 0x40, 0x40}, // 0x4C L - {0x7F, 0x02, 0x1C, 0x02, 0x7F}, // 0x4D M - {0x7F, 0x04, 0x08, 0x10, 0x7F}, // 0x4E N - {0x3E, 0x41, 0x41, 0x41, 0x3E}, // 0x4F O - {0x7F, 0x09, 0x09, 0x09, 0x06}, // 0x50 P - {0x3E, 0x41, 0x51, 0x21, 0x5E}, // 0x51 Q - {0x7F, 0x09, 0x19, 0x29, 0x46}, // 0x52 R - {0x26, 0x49, 0x49, 0x49, 0x32}, // 0x53 S - {0x03, 0x01, 0x7F, 0x01, 0x03}, // 0x54 T - {0x3F, 0x40, 0x40, 0x40, 0x3F}, // 0x55 U - {0x1F, 0x20, 0x40, 0x20, 0x1F}, // 0x56 V - {0x3F, 0x40, 0x38, 0x40, 0x3F}, // 0x57 W - {0x63, 0x14, 0x08, 0x14, 0x63}, // 0x58 X - {0x03, 0x04, 0x78, 0x04, 0x03}, // 0x59 Y - {0x61, 0x59, 0x49, 0x4D, 0x43}, // 0x5A Z - {0x00, 0x7F, 0x41, 0x41, 0x41}, // 0x5B [ - {0x02, 0x04, 0x08, 0x10, 0x20}, // 0x5C Backslash - {0x00, 0x41, 0x41, 0x41, 0x7F}, // 0x5D ] - {0x04, 0x02, 0x01, 0x02, 0x04}, // 0x5E ^ - {0x40, 0x40, 0x40, 0x40, 0x40}, // 0x5F _ - {0x00, 0x03, 0x07, 0x08, 0x00}, // 0x60 ` - {0x20, 0x54, 0x54, 0x78, 0x40}, // 0x61 a - {0x7F, 0x28, 0x44, 0x44, 0x38}, // 0x62 b - {0x38, 0x44, 0x44, 0x44, 0x28}, // 0x63 c - {0x38, 0x44, 0x44, 0x28, 0x7F}, // 0x64 d - {0x38, 0x54, 0x54, 0x54, 0x18}, // 0x65 e - {0x00, 0x08, 0x7E, 0x09, 0x02}, // 0x66 f - {0x18, 0xA4, 0xA4, 0x9C, 0x78}, // 0x67 g - {0x7F, 0x08, 0x04, 0x04, 0x78}, // 0x68 h - {0x00, 0x44, 0x7D, 0x40, 0x00}, // 0x69 i - {0x20, 0x40, 0x40, 0x3D, 0x00}, // 0x6A j - {0x7F, 0x10, 0x28, 0x44, 0x00}, // 0x6B k - {0x00, 0x41, 0x7F, 0x40, 0x00}, // 0x6C l - {0x7C, 0x04, 0x78, 0x04, 0x78}, // 0x6D m - {0x7C, 0x08, 0x04, 0x04, 0x78}, // 0x6E n - {0x38, 0x44, 0x44, 0x44, 0x38}, // 0x6F o - {0xFC, 0x18, 0x24, 0x24, 0x18}, // 0x70 p - {0x18, 0x24, 0x24, 0x18, 0xFC}, // 0x71 q - {0x7C, 0x08, 0x04, 0x04, 0x08}, // 0x72 r - {0x48, 0x54, 0x54, 0x54, 0x24}, // 0x73 s - {0x04, 0x04, 0x3F, 0x44, 0x24}, // 0x74 t - {0x3C, 0x40, 0x40, 0x20, 0x7C}, // 0x75 u - {0x1C, 0x20, 0x40, 0x20, 0x1C}, // 0x76 v - {0x3C, 0x40, 0x30, 0x40, 0x3C}, // 0x77 w - {0x44, 0x28, 0x10, 0x28, 0x44}, // 0x78 x - {0x4C, 0x90, 0x90, 0x90, 0x7C}, // 0x79 y - {0x44, 0x64, 0x54, 0x4C, 0x44}, // 0x7A z - {0x00, 0x08, 0x36, 0x41, 0x00}, // 0x7B { - {0x00, 0x00, 0x77, 0x00, 0x00}, // 0x7C | - {0x00, 0x41, 0x36, 0x08, 0x00}, // 0x7D } - {0x02, 0x01, 0x02, 0x04, 0x02}, // 0x7E ~ -#ifdef GLCD_FULL_CHARSET - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x7F Delete - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x80 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x81 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x82 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x83 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x84 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x85 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x86 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x87 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x88 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x89 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x8A - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x8B - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x8C - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x8D - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x8E - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x8F - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x90 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x91 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x92 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x93 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x94 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x95 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x96 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x97 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x98 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x99 - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x9A - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x9B - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x9C - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x9D - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x9E - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0x9F - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0xA0 - {0x00, 0x00, 0xFA, 0x00, 0x00}, // 0xA1 � - {0x3C, 0x24, 0xFF, 0x24, 0x24}, // 0xA2 � - {0x48, 0x7E, 0x49, 0x43, 0x66}, // 0xA3 � - {0x1C, 0x36, 0x55, 0x41, 0x22}, // 0xA4 � - {0x2B, 0x2F, 0xFC, 0x2F, 0x2B}, // 0xA5 � - {0x08, 0x55, 0x56, 0x55, 0x20}, // 0xA6 � - {0x00, 0x66, 0x89, 0x95, 0x6A}, // 0xA7 � - {0x00, 0x59, 0x7A, 0x69, 0x00}, // 0xA8 � - {0x38, 0x7C, 0x6C, 0x44, 0x38}, // 0xA9 � - {0x26, 0x29, 0x29, 0x2F, 0x28}, // 0xAA � - {0x08, 0x14, 0x2A, 0x14, 0x22}, // 0xAB � - {0x08, 0x08, 0x08, 0x08, 0x38}, // 0xAC � - {0x00, 0x00, 0x00, 0x00, 0x00}, // 0xAD � - {0x38, 0x7C, 0x5C, 0x64, 0x38}, // 0xAE � - {0x00, 0x01, 0x01, 0x01, 0x00}, // 0xAF � - {0x00, 0x02, 0x05, 0x02, 0x00}, // 0xB0 � - {0x44, 0x44, 0x5F, 0x44, 0x44}, // 0xB1 � - {0x00, 0x19, 0x1D, 0x17, 0x12}, // 0xB2 � - {0x00, 0x11, 0x15, 0x1F, 0x0A}, // 0xB3 � - {0x44, 0x65, 0x56, 0x4D, 0x44}, // 0xB4 � - {0x40, 0x7E, 0x20, 0x1E, 0x20}, // 0xB5 � - {0x06, 0x09, 0x7F, 0x01, 0x7F}, // 0xB6 � - {0x00, 0x00, 0x08, 0x00, 0x00}, // 0xB7 � - {0x00, 0x69, 0x7A, 0x59, 0x00}, // 0xB8 � - {0x00, 0x12, 0x1F, 0x10, 0x00}, // 0xB9 � - {0x26, 0x29, 0x29, 0x29, 0x26}, // 0xBA � - {0x22, 0x14, 0x2A, 0x14, 0x08}, // 0xBB � - {0x3E, 0x41, 0x7F, 0x49, 0x41}, // 0xBC � - {0x38, 0x44, 0x38, 0x54, 0x18}, // 0xBD � - {0x04, 0x09, 0x70, 0x09, 0x04}, // 0xBE � - {0x30, 0x48, 0x4D, 0x40, 0x20}, // 0xBF � - {0x70, 0x28, 0x25, 0x2A, 0x70}, // 0xC0 � - {0x70, 0x2A, 0x25, 0x28, 0x70}, // 0xC1 � - {0x70, 0x2A, 0x25, 0x2A, 0x70}, // 0xC2 � - {0x70, 0x2A, 0x25, 0x2A, 0x71}, // 0xC3 � - {0x70, 0x29, 0x24, 0x29, 0x70}, // 0xC4 � - {0x70, 0x28, 0x25, 0x28, 0x70}, // 0xC5 � - {0x7C, 0x12, 0x7F, 0x49, 0x41}, // 0xC6 � - {0x1E, 0xA1, 0xA1, 0x61, 0x12}, // 0xC7 � - {0x7C, 0x54, 0x55, 0x46, 0x00}, // 0xC8 � - {0x7C, 0x56, 0x55, 0x44, 0x00}, // 0xC9 � - {0x7C, 0x56, 0x55, 0x46, 0x00}, // 0xCA � - {0x7C, 0x55, 0x54, 0x45, 0x00}, // 0xCB � - {0x00, 0x44, 0x7D, 0x46, 0x00}, // 0xCC � - {0x00, 0x46, 0x7D, 0x44, 0x00}, // 0xCD � - {0x00, 0x46, 0x7D, 0x46, 0x00}, // 0xCE � - {0x00, 0x45, 0x7C, 0x45, 0x00}, // 0xCF � - {0x7F, 0x49, 0x41, 0x41, 0x3E}, // 0xD0 � - {0x7C, 0x0E, 0x19, 0x32, 0x7D}, // 0xD1 � - {0x38, 0x44, 0x45, 0x46, 0x38}, // 0xD2 � - {0x38, 0x46, 0x45, 0x44, 0x38}, // 0xD3 � - {0x38, 0x46, 0x45, 0x46, 0x38}, // 0xD4 � - {0x38, 0x46, 0x45, 0x46, 0x39}, // 0xD5 � - {0x38, 0x45, 0x44, 0x45, 0x38}, // 0xD6 � - {0x00, 0x14, 0x08, 0x14, 0x00}, // 0xD7 � - {0x3C, 0x32, 0x2A, 0x26, 0x1E}, // 0xD8 � - {0x3C, 0x40, 0x41, 0x42, 0x3C}, // 0xD9 � - {0x3C, 0x42, 0x41, 0x40, 0x3C}, // 0xDA � - {0x3C, 0x42, 0x41, 0x42, 0x3C}, // 0xDB � - {0x3C, 0x41, 0x40, 0x41, 0x3C}, // 0xDC � - {0x04, 0x0A, 0x71, 0x08, 0x04}, // 0xDD � - {0x7E, 0x24, 0x24, 0x24, 0x18}, // 0xDE � - {0x7C, 0x2A, 0x2A, 0x3E, 0x14}, // 0xDF � - {0x20, 0x54, 0x55, 0x7A, 0x40}, // 0xE0 � - {0x20, 0x56, 0x55, 0x78, 0x40}, // 0xE1 � - {0x20, 0x56, 0x55, 0x7A, 0x40}, // 0xE2 � - {0x20, 0x56, 0x55, 0x7A, 0x41}, // 0xE3 � - {0x20, 0x55, 0x54, 0x79, 0x40}, // 0xE4 � - {0x20, 0x54, 0x55, 0x78, 0x40}, // 0xE5 � - {0x20, 0x54, 0x38, 0x54, 0x18}, // 0xE6 � - {0x18, 0x3C, 0xA4, 0xE4, 0x24}, // 0xE7 � - {0x38, 0x54, 0x55, 0x56, 0x18}, // 0xE8 � - {0x38, 0x56, 0x55, 0x54, 0x18}, // 0xE9 � - {0x38, 0x56, 0x55, 0x56, 0x18}, // 0xEA � - {0x38, 0x55, 0x54, 0x55, 0x18}, // 0xEB � - {0x00, 0x44, 0x7D, 0x42, 0x00}, // 0xEC � - {0x00, 0x46, 0x7D, 0x40, 0x00}, // 0xED � - {0x00, 0x46, 0x7D, 0x42, 0x00}, // 0xEE � - {0x00, 0x45, 0x7C, 0x41, 0x00}, // 0xEF � - {0x38, 0x45, 0x45, 0x47, 0x38}, // 0xF0 � - {0x7C, 0x0A, 0x05, 0x06, 0x79}, // 0xF1 � - {0x38, 0x44, 0x45, 0x46, 0x38}, // 0xF2 � - {0x38, 0x46, 0x45, 0x44, 0x38}, // 0xF3 � - {0x38, 0x46, 0x45, 0x46, 0x38}, // 0xF4 � - {0x38, 0x46, 0x45, 0x46, 0x39}, // 0xF5 � - {0x38, 0x45, 0x44, 0x45, 0x38}, // 0xF6 � - {0x08, 0x08, 0x6B, 0x6B, 0x08}, // 0xF7 � - {0x3C, 0x32, 0x2A, 0x26, 0x1E}, // 0xF8 � - {0x3C, 0x40, 0x41, 0x22, 0x7C}, // 0xF9 � - {0x3C, 0x42, 0x41, 0x20, 0x7C}, // 0xFA � - {0x3C, 0x42, 0x41, 0x22, 0x7C}, // 0xFB � - {0x3C, 0x41, 0x40, 0x21, 0x7C}, // 0xFC � - {0x4C, 0x92, 0x91, 0x90, 0x7C}, // 0xFD � - {0xFF, 0x22, 0x22, 0x22, 0x1C}, // 0xFE � - {0x4C, 0x91, 0x90, 0x91, 0x7C}, // 0xFF � -#endif -}; diff --git a/SmartEVSE-3/SmartEVSE-3/src/font2.cpp b/SmartEVSE-3/SmartEVSE-3/src/font2.cpp deleted file mode 100644 index 837ee08..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/font2.cpp +++ /dev/null @@ -1,270 +0,0 @@ -#include "glcd.h" - -#ifdef GLCD_HIRES_FONT - -#ifdef GLCD_FULL_CHARSET -const unsigned char font2[0x100][23] = { -#else -const unsigned char font2[0x7F][23] = { -#endif - {0x0}, // 0x00 - {0x0}, // 0x01 - {0x0}, // 0x02 - {0x0}, // 0x03 - {0x0}, // 0x04 - {0x0}, // 0x05 - {0x0}, // 0x06 - {0x0}, // 0x07 - {0x0}, // 0x08 - {0x0}, // 0x09 - {0x0}, // 0x0A - {0x0}, // 0x0B - {0x0}, // 0x0C - {0x0}, // 0x0D - {0x0}, // 0x0E - {0x0}, // 0x0F - {0x0}, // 0x10 - {0x0}, // 0x11 - {0x0}, // 0x12 - {0x0}, // 0x13 - {0x0}, // 0x14 - {0x0}, // 0x15 - {0x0}, // 0x16 - {0x0}, // 0x17 - {0x0}, // 0x18 - {0x0}, // 0x19 - {0x0}, // 0x1A - {0x0}, // 0x1B - {0x0}, // 0x1C - {0x0}, // 0x1D - {0x0}, // 0x1E - {0x0}, // 0x1F - {0x3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x20 Space - {0x4, 0x7C, 0x00, 0xFF, 0x33, 0xFF, 0x33, 0x7C, 0x00}, // 0x21 ! - {0x6, 0x3C, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x3C, 0x00}, // 0x22 " - {0xB, 0x00, 0x02, 0x10, 0x1E, 0x90, 0x1F, 0xF0, 0x03, 0x7E, 0x02, 0x1E, 0x1E, 0x90, 0x1F, 0xF0, 0x03, 0x7E, 0x02, 0x1E, 0x00, 0x10, 0x00}, // 0x23 # - {0x8, 0x78, 0x04, 0xFC, 0x0C, 0xCC, 0x0C, 0xFF, 0x3F, 0xFF, 0x3F, 0xCC, 0x0C, 0xCC, 0x0F, 0x88, 0x07}, // 0x24 $ - {0xB, 0x00, 0x30, 0x38, 0x38, 0x38, 0x1C, 0x38, 0x0E, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x38, 0x70, 0x38, 0x38, 0x38, 0x1C, 0x00}, // 0x25 % - {0x9, 0x00, 0x1F, 0xB8, 0x3F, 0xFC, 0x31, 0xC6, 0x21, 0xE2, 0x37, 0x3E, 0x1E, 0x1C, 0x1C, 0x00, 0x36, 0x00, 0x22}, // 0x26 & - {0x3, 0x27, 0x00, 0x3F, 0x00, 0x1F, 0x00}, // 0x27 ' - {0x6, 0xF0, 0x03, 0xFC, 0x0F, 0xFE, 0x1F, 0x07, 0x38, 0x01, 0x20, 0x01, 0x20}, // 0x28 ( - {0x6, 0x01, 0x20, 0x01, 0x20, 0x07, 0x38, 0xFE, 0x1F, 0xFC, 0x0F, 0xF0, 0x03}, // 0x29 ) - {0x8, 0x98, 0x0C, 0xB8, 0x0E, 0xE0, 0x03, 0xF8, 0x0F, 0xF8, 0x0F, 0xE0, 0x03, 0xB8, 0x0E, 0x98, 0x0C}, // 0x2A * - {0x8, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xF0, 0x0F, 0xF0, 0x0F, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01}, // 0x2B + - {0x3, 0x00, 0xB8, 0x00, 0xF8, 0x00, 0x78}, // 0x2C , - {0x8, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01}, // 0x2D - - {0x3, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38}, // 0x2E . - {0xB, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00}, // 0x2F / - {0xB, 0xF8, 0x07, 0xFE, 0x1F, 0x06, 0x1E, 0x03, 0x33, 0x83, 0x31, 0xC3, 0x30, 0x63, 0x30, 0x33, 0x30, 0x1E, 0x18, 0xFE, 0x1F, 0xF8, 0x07}, // 0x30 0 - {0xB, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0x0C, 0x30, 0x0E, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00}, // 0x31 1 - {0xB, 0x1C, 0x30, 0x1E, 0x38, 0x07, 0x3C, 0x03, 0x3E, 0x03, 0x37, 0x83, 0x33, 0xC3, 0x31, 0xE3, 0x30, 0x77, 0x30, 0x3E, 0x30, 0x1C, 0x30}, // 0x32 2 - {0xB, 0x0C, 0x0C, 0x0E, 0x1C, 0x07, 0x38, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x39, 0x7E, 0x1F, 0x3C, 0x0E}, // 0x33 3 - {0xB, 0xC0, 0x03, 0xE0, 0x03, 0x70, 0x03, 0x38, 0x03, 0x1C, 0x03, 0x0E, 0x03, 0x07, 0x03, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x03, 0x00, 0x03}, // 0x34 4 - {0xB, 0x3F, 0x0C, 0x7F, 0x1C, 0x63, 0x38, 0x63, 0x30, 0x63, 0x30, 0x63, 0x30, 0x63, 0x30, 0x63, 0x30, 0xE3, 0x38, 0xC3, 0x1F, 0x83, 0x0F}, // 0x35 5 - {0xB, 0xC0, 0x0F, 0xF0, 0x1F, 0xF8, 0x39, 0xDC, 0x30, 0xCE, 0x30, 0xC7, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x39, 0x80, 0x1F, 0x00, 0x0F}, // 0x36 6 - {0xB, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x30, 0x03, 0x3C, 0x03, 0x0F, 0xC3, 0x03, 0xF3, 0x00, 0x3F, 0x00, 0x0F, 0x00, 0x03, 0x00}, // 0x37 7 - {0xB, 0x00, 0x0F, 0xBC, 0x1F, 0xFE, 0x39, 0xE7, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x30, 0xFE, 0x39, 0xBC, 0x1F, 0x00, 0x0F}, // 0x38 8 - {0xB, 0x3C, 0x00, 0x7E, 0x00, 0xE7, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x38, 0xC3, 0x1C, 0xC3, 0x0E, 0xE7, 0x07, 0xFE, 0x03, 0xFC, 0x00}, // 0x39 9 - {0x3, 0x70, 0x1C, 0x70, 0x1C, 0x70, 0x1C}, // 0x3A : - {0x3, 0x70, 0x9C, 0x70, 0xFC, 0x70, 0x7C}, // 0x3B ; - {0x8, 0xC0, 0x00, 0xE0, 0x01, 0xF0, 0x03, 0x38, 0x07, 0x1C, 0x0E, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30}, // 0x3C < - {0x9, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06}, // 0x3D = - {0x8, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0x1C, 0x0E, 0x38, 0x07, 0xF0, 0x03, 0xE0, 0x01, 0xC0, 0x00}, // 0x3E > - {0xA, 0x1C, 0x00, 0x1E, 0x00, 0x07, 0x00, 0x03, 0x00, 0x83, 0x37, 0xC3, 0x37, 0xE3, 0x00, 0x77, 0x00, 0x3E, 0x00, 0x1C, 0x00}, // 0x3F ? - {0xB, 0xF8, 0x0F, 0xFE, 0x1F, 0x07, 0x18, 0xF3, 0x33, 0xFB, 0x37, 0x1B, 0x36, 0xFB, 0x37, 0xFB, 0x37, 0x07, 0x36, 0xFE, 0x03, 0xF8, 0x01}, // 0x40 @ - {0xA, 0x00, 0x38, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x06, 0x1F, 0x06, 0x1F, 0x06, 0xFC, 0x06, 0xE0, 0x07, 0x00, 0x3F, 0x00, 0x38}, // 0x41 A - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x30, 0xFE, 0x39, 0xBC, 0x1F, 0x00, 0x0F}, // 0x42 B - {0xA, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0x0C, 0x0C}, // 0x43 C - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0xFC, 0x0F, 0xF0, 0x03}, // 0x44 D - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0x03, 0x30, 0x03, 0x30}, // 0x45 E - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0x03, 0x00, 0x03, 0x00}, // 0x46 F - {0xA, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC7, 0x3F, 0xC6, 0x3F}, // 0x47 G - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xFF, 0x3F, 0xFF, 0x3F}, // 0x48 H - {0x6, 0x03, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30}, // 0x49 I - {0xA, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0xFF, 0x1F, 0xFF, 0x07}, // 0x4A J - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x00, 0xE0, 0x01, 0xF0, 0x03, 0x38, 0x07, 0x1C, 0x0E, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30}, // 0x4B K - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30}, // 0x4C L - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0x1E, 0x00, 0x78, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0x78, 0x00, 0x1E, 0x00, 0xFF, 0x3F, 0xFF, 0x3F}, // 0x4D M - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0x0E, 0x00, 0x38, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0x00, 0x07, 0x00, 0x1C, 0xFF, 0x3F, 0xFF, 0x3F}, // 0x4E N - {0xA, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0xFC, 0x0F, 0xF0, 0x03}, // 0x4F O - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0x83, 0x01, 0x83, 0x01, 0x83, 0x01, 0x83, 0x01, 0x83, 0x01, 0xC7, 0x01, 0xFE, 0x00, 0x7C, 0x00}, // 0x50 P - {0xA, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0x03, 0x36, 0x07, 0x3E, 0x0E, 0x1C, 0xFC, 0x3F, 0xF0, 0x33}, // 0x51 Q - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0x83, 0x01, 0x83, 0x01, 0x83, 0x03, 0x83, 0x07, 0x83, 0x0F, 0xC7, 0x1D, 0xFE, 0x38, 0x7C, 0x30}, // 0x52 R - {0xA, 0x3C, 0x0C, 0x7E, 0x1C, 0xE7, 0x38, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC7, 0x39, 0x8E, 0x1F, 0x0C, 0x0F}, // 0x53 S - {0x8, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00}, // 0x54 T - {0xA, 0xFF, 0x07, 0xFF, 0x1F, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0xFF, 0x1F, 0xFF, 0x07}, // 0x55 U - {0xA, 0x07, 0x00, 0x3F, 0x00, 0xF8, 0x01, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0x3E, 0xC0, 0x0F, 0xF8, 0x01, 0x3F, 0x00, 0x07, 0x00}, // 0x56 V - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x1C, 0x00, 0x06, 0x80, 0x03, 0x80, 0x03, 0x00, 0x06, 0x00, 0x1C, 0xFF, 0x3F, 0xFF, 0x3F}, // 0x57 W - {0xA, 0x03, 0x30, 0x0F, 0x3C, 0x1C, 0x0E, 0x30, 0x03, 0xE0, 0x01, 0xE0, 0x01, 0x30, 0x03, 0x1C, 0x0E, 0x0F, 0x3C, 0x03, 0x30}, // 0x58 X - {0xA, 0x03, 0x00, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x00, 0xC0, 0x3F, 0xC0, 0x3F, 0xF0, 0x00, 0x3C, 0x00, 0x0F, 0x00, 0x03, 0x00}, // 0x59 Y - {0xA, 0x03, 0x30, 0x03, 0x3C, 0x03, 0x3E, 0x03, 0x33, 0xC3, 0x31, 0xE3, 0x30, 0x33, 0x30, 0x1F, 0x30, 0x0F, 0x30, 0x03, 0x30}, // 0x5A Z - {0x6, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30}, // 0x5B [ - {0xB, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x18}, // 0x5C Backslash - {0x6, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F}, // 0x5D ] - {0xB, 0x60, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0x60, 0x00}, // 0x5E ^ - {0xB, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0}, // 0x5F _ - {0x3, 0x3E, 0x00, 0x7E, 0x00, 0x4E, 0x00}, // 0x60 ` - {0xA, 0x00, 0x1C, 0x40, 0x3E, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0xE0, 0x3F, 0xC0, 0x3F}, // 0x61 a - {0xA, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x38, 0xC0, 0x1F, 0x80, 0x0F}, // 0x62 b - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xC0, 0x18, 0x80, 0x08}, // 0x63 c - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x30, 0xC0, 0x30, 0xFF, 0x3F, 0xFF, 0x3F}, // 0x64 d - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x3B, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0xC0, 0x13, 0x80, 0x01}, // 0x65 e - {0x8, 0xC0, 0x00, 0xC0, 0x00, 0xFC, 0x3F, 0xFE, 0x3F, 0xC7, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0x03, 0x00}, // 0x66 f - {0xA, 0x80, 0x03, 0xC0, 0xC7, 0xE0, 0xCE, 0x60, 0xCC, 0x60, 0xCC, 0x60, 0xCC, 0x60, 0xCC, 0x60, 0xE6, 0xE0, 0x7F, 0xE0, 0x3F}, // 0x67 g - {0x9, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xC0, 0x3F, 0x80, 0x3F}, // 0x68 h - {0x6, 0x00, 0x30, 0x60, 0x30, 0xEC, 0x3F, 0xEC, 0x3F, 0x00, 0x30, 0x00, 0x30}, // 0x69 i - {0x6, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xC0, 0x60, 0xC0, 0xEC, 0xFF, 0xEC, 0x7F}, // 0x6A j - {0x8, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x03, 0x80, 0x07, 0xC0, 0x0F, 0xE0, 0x1C, 0x60, 0x38, 0x00, 0x30}, // 0x6B k - {0x6, 0x00, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30}, // 0x6C l - {0xA, 0xE0, 0x3F, 0xC0, 0x3F, 0xE0, 0x00, 0xE0, 0x00, 0xC0, 0x3F, 0xC0, 0x3F, 0xE0, 0x00, 0xE0, 0x00, 0xC0, 0x3F, 0x80, 0x3F}, // 0x6D m - {0x9, 0xE0, 0x3F, 0xE0, 0x3F, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xC0, 0x3F, 0x80, 0x3F}, // 0x6E n - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x38, 0xC0, 0x1F, 0x80, 0x0F}, // 0x6F o - {0xA, 0xE0, 0xFF, 0xE0, 0xFF, 0x60, 0x0C, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0xE0, 0x1C, 0xC0, 0x0F, 0x80, 0x07}, // 0x70 p - {0xA, 0x80, 0x07, 0xC0, 0x0F, 0xE0, 0x1C, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x0C, 0xE0, 0xFF, 0xE0, 0xFF}, // 0x71 q - {0x9, 0xE0, 0x3F, 0xE0, 0x3F, 0xC0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xC0, 0x00}, // 0x72 r - {0x8, 0xC0, 0x11, 0xE0, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x3F, 0x40, 0x1E}, // 0x73 s - {0x8, 0x60, 0x00, 0x60, 0x00, 0xFE, 0x1F, 0xFE, 0x3F, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x00, 0x30}, // 0x74 t - {0xA, 0xE0, 0x0F, 0xE0, 0x1F, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0xE0, 0x3F, 0xE0, 0x3F}, // 0x75 u - {0xA, 0x60, 0x00, 0xE0, 0x01, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x38, 0x00, 0x38, 0x00, 0x1E, 0x80, 0x07, 0xE0, 0x01, 0x60, 0x00}, // 0x76 v - {0xA, 0xE0, 0x07, 0xE0, 0x1F, 0x00, 0x38, 0x00, 0x1C, 0xE0, 0x0F, 0xE0, 0x0F, 0x00, 0x1C, 0x00, 0x38, 0xE0, 0x1F, 0xE0, 0x07}, // 0x77 w - {0x9, 0x60, 0x30, 0xE0, 0x38, 0xC0, 0x1D, 0x80, 0x0F, 0x00, 0x07, 0x80, 0x0F, 0xC0, 0x1D, 0xE0, 0x38, 0x60, 0x30}, // 0x78 x - {0x8, 0x60, 0x00, 0xE0, 0x81, 0x80, 0xE7, 0x00, 0x7E, 0x00, 0x1E, 0x80, 0x07, 0xE0, 0x01, 0x60, 0x00}, // 0x79 y - {0x9, 0x60, 0x30, 0x60, 0x38, 0x60, 0x3C, 0x60, 0x36, 0x60, 0x33, 0xE0, 0x31, 0xE0, 0x30, 0x60, 0x30, 0x20, 0x30}, // 0x7A z - {0x8, 0x80, 0x00, 0xC0, 0x01, 0xFC, 0x1F, 0x7E, 0x3F, 0x07, 0x70, 0x03, 0x60, 0x03, 0x60, 0x03, 0x60}, // 0x7B { - {0x2, 0xBF, 0x3F, 0xBF, 0x3F}, // 0x7C | - {0x8, 0x03, 0x60, 0x03, 0x60, 0x03, 0x60, 0x07, 0x70, 0x7E, 0x3F, 0xFC, 0x1F, 0xC0, 0x01, 0x80, 0x00}, // 0x7D } - {0xA, 0x10, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x10, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x04, 0x00}, // 0x7E ~ -#ifdef GLCD_FULL_CHARSET - {0xA, 0x00, 0x0F, 0x80, 0x0F, 0xC0, 0x0C, 0x60, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x60, 0x0C, 0xC0, 0x0C, 0x80, 0x0F, 0x00, 0x0F}, // 0x7F Delete - {0x0}, // 0x80 - {0x0}, // 0x81 - {0x0}, // 0x82 - {0x0}, // 0x83 - {0x0}, // 0x84 - {0x0}, // 0x85 - {0x0}, // 0x86 - {0x0}, // 0x87 - {0x0}, // 0x88 - {0x0}, // 0x89 - {0x0}, // 0x8A - {0x0}, // 0x8B - {0x0}, // 0x8C - {0x0}, // 0x8D - {0x0}, // 0x8E - {0x0}, // 0x8F - {0x0}, // 0x90 - {0x0}, // 0x91 - {0x0}, // 0x92 - {0x0}, // 0x93 - {0x0}, // 0x94 - {0x0}, // 0x95 - {0x0}, // 0x96 - {0x0}, // 0x97 - {0x0}, // 0x98 - {0x0}, // 0x99 - {0x0}, // 0x9A - {0x0}, // 0x9B - {0x0}, // 0x9C - {0x0}, // 0x9D - {0x0}, // 0x9E - {0x0}, // 0x9F - {0x0}, // 0xA0 - {0x4, 0x80, 0x0F, 0xF3, 0x3F, 0xF3, 0x3F, 0x80, 0x0F}, // 0xA1 � - {0x8, 0xE0, 0x03, 0xF0, 0x07, 0x38, 0x0E, 0xFE, 0x3F, 0xFE, 0x3F, 0x18, 0x0C, 0x38, 0x0E, 0x30, 0x06}, // 0xA2 � - {0x9, 0x00, 0x18, 0x80, 0x1C, 0xF8, 0x1F, 0xFC, 0x0B, 0x8C, 0x18, 0x8C, 0x18, 0x1C, 0x18, 0x18, 0x18, 0x00, 0x08}, // 0xA3 � - {0x9, 0x20, 0x01, 0xF0, 0x03, 0xFC, 0x0F, 0x2E, 0x1D, 0x27, 0x39, 0x23, 0x31, 0x23, 0x30, 0x07, 0x38, 0x0E, 0x1C}, // 0xA4 � - {0xA, 0x03, 0x00, 0x0F, 0x0A, 0x3C, 0x0A, 0xF0, 0x0A, 0xC0, 0x3F, 0xC0, 0x3F, 0xF0, 0x0A, 0x3C, 0x0A, 0x0F, 0x0A, 0x03, 0x00}, // 0xA5 � - {0xA, 0x60, 0x0C, 0xF0, 0x1C, 0xF9, 0x39, 0x9B, 0x31, 0x9E, 0x31, 0x9E, 0x31, 0x9B, 0x31, 0xB9, 0x3B, 0x30, 0x1F, 0x20, 0x0E}, // 0xA6 � - {0x8, 0xDC, 0x08, 0xFE, 0x19, 0x22, 0x11, 0x22, 0x11, 0x22, 0x11, 0x22, 0x11, 0xE6, 0x1F, 0xC4, 0x0E}, // 0xA7 � - {0x8, 0xC0, 0x11, 0xE2, 0x33, 0x66, 0x33, 0x6C, 0x33, 0x6C, 0x33, 0x66, 0x33, 0x62, 0x3F, 0x40, 0x1E}, // 0xA8 � - {0xB, 0xF0, 0x07, 0xF8, 0x0F, 0x1C, 0x1C, 0xCC, 0x19, 0xEC, 0x1B, 0x2C, 0x1A, 0x6C, 0x1B, 0x4C, 0x19, 0x1C, 0x1C, 0xF8, 0x0F, 0xF0, 0x07}, // 0xA9 � - {0xA, 0x70, 0x00, 0xFA, 0x06, 0xDB, 0x06, 0xDB, 0x06, 0xDB, 0x06, 0xDB, 0x06, 0xDB, 0x06, 0xDB, 0x06, 0xFF, 0x06, 0xFE, 0x00}, // 0xAA � - {0x9, 0x80, 0x00, 0xC0, 0x01, 0x60, 0x03, 0x20, 0x02, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x01, 0x60, 0x03, 0x20, 0x02}, // 0xAB � - {0xA, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0xF8, 0x03, 0xF8, 0x03}, // 0xAC � - {0x6, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00}, // 0xAD � - {0xB, 0xF0, 0x07, 0xF8, 0x0F, 0x1C, 0x1C, 0xEC, 0x1B, 0xEC, 0x1B, 0xAC, 0x18, 0xEC, 0x1B, 0x4C, 0x1B, 0x1C, 0x1C, 0xF8, 0x0F, 0xF0, 0x07}, // 0xAE � - {0x4, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00}, // 0xAF � - {0x6, 0x1E, 0x00, 0x3F, 0x00, 0x33, 0x00, 0x33, 0x00, 0x3F, 0x00, 0x1E, 0x00}, // 0xB0 � - {0x6, 0xC0, 0x18, 0xC0, 0x18, 0xF0, 0x1B, 0xF0, 0x1B, 0xC0, 0x18, 0xC0, 0x18}, // 0xB1 � - {0x5, 0x19, 0x00, 0x1D, 0x00, 0x15, 0x00, 0x17, 0x00, 0x12, 0x00}, // 0xB2 � - {0x5, 0x11, 0x00, 0x15, 0x00, 0x15, 0x00, 0x1F, 0x00, 0x0A, 0x00}, // 0xB3 � - {0xA, 0x18, 0x30, 0x18, 0x38, 0x19, 0x3C, 0x1B, 0x36, 0x1E, 0x33, 0x9E, 0x31, 0xDB, 0x30, 0x79, 0x30, 0x38, 0x30, 0x18, 0x30}, // 0xB4 � - {0x9, 0xF0, 0xFF, 0xF0, 0xFF, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0xF0, 0x0F, 0xF0, 0x0F}, // 0xB5 � - {0xA, 0x38, 0x00, 0x7C, 0x00, 0xC6, 0x00, 0x82, 0x00, 0xFE, 0x3F, 0xFE, 0x3F, 0x02, 0x00, 0xFE, 0x3F, 0xFE, 0x3F, 0x02, 0x00}, // 0xB6 � - {0x3, 0x80, 0x01, 0xC0, 0x03, 0x80, 0x01}, // 0xB7 � - {0x9, 0x60, 0x30, 0x62, 0x38, 0x66, 0x3C, 0x6C, 0x36, 0x6C, 0x33, 0xE6, 0x31, 0xE2, 0x30, 0x60, 0x30, 0x20, 0x30}, // 0xB8 � - {0x3, 0x02, 0x00, 0x1F, 0x00, 0x1F, 0x00}, // 0xB9 � - {0xA, 0x3C, 0x00, 0x7E, 0x06, 0xE7, 0x06, 0xC3, 0x06, 0xC3, 0x06, 0xC3, 0x06, 0xC3, 0x06, 0xE7, 0x06, 0x7E, 0x06, 0x3C, 0x00}, // 0xBA � - {0x9, 0x20, 0x02, 0x60, 0x03, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x20, 0x02, 0x60, 0x03, 0xC0, 0x01, 0x80, 0x00}, // 0xBB � - {0xA, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30}, // 0xBC � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x60, 0x30, 0xC0, 0x1F, 0xE0, 0x3B, 0x60, 0x33, 0x60, 0x33, 0xC0, 0x13, 0x80, 0x01}, // 0xBD � - {0xA, 0x08, 0x00, 0x18, 0x00, 0x33, 0x00, 0x63, 0x00, 0xC0, 0x3F, 0xC0, 0x3F, 0x63, 0x00, 0x33, 0x00, 0x18, 0x00, 0x08, 0x00}, // 0xBE � - {0xA, 0x00, 0x0E, 0x00, 0x1F, 0x80, 0x3B, 0xC0, 0x31, 0xFB, 0x30, 0x7B, 0x30, 0x00, 0x30, 0x00, 0x38, 0x00, 0x1E, 0x00, 0x0E}, // 0xBF � - {0xA, 0x00, 0x38, 0x00, 0x3E, 0x80, 0x0F, 0xE1, 0x0D, 0x7B, 0x0C, 0x7E, 0x0C, 0xE4, 0x0D, 0x80, 0x0F, 0x00, 0x3E, 0x00, 0x38}, // 0xC0 � - {0xA, 0x00, 0x38, 0x00, 0x3E, 0x80, 0x0F, 0xE4, 0x0D, 0x7E, 0x0C, 0x7B, 0x0C, 0xE1, 0x0D, 0x80, 0x0F, 0x00, 0x3E, 0x00, 0x38}, // 0xC1 � - {0xA, 0x00, 0x38, 0x00, 0x3E, 0x84, 0x0F, 0xE6, 0x0D, 0x7B, 0x0C, 0x7B, 0x0C, 0xE6, 0x0D, 0x84, 0x0F, 0x00, 0x3E, 0x00, 0x38}, // 0xC2 � - {0xA, 0x00, 0x38, 0x00, 0x3E, 0x82, 0x0F, 0xE3, 0x0D, 0x79, 0x0C, 0x7B, 0x0C, 0xE2, 0x0D, 0x83, 0x0F, 0x01, 0x3E, 0x00, 0x38}, // 0xC3 � - {0xA, 0x00, 0x38, 0x00, 0x3E, 0x83, 0x0F, 0xE3, 0x0D, 0x78, 0x0C, 0x78, 0x0C, 0xE3, 0x0D, 0x83, 0x0F, 0x00, 0x3E, 0x00, 0x38}, // 0xC4 � - {0xA, 0x00, 0x38, 0x00, 0x3E, 0x80, 0x0F, 0xE2, 0x0D, 0x75, 0x0C, 0x75, 0x0C, 0xE2, 0x0D, 0x80, 0x0F, 0x00, 0x3E, 0x00, 0x38}, // 0xC5 � - {0xA, 0x00, 0x3C, 0x80, 0x3F, 0xF0, 0x07, 0x7C, 0x06, 0x1F, 0x06, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0xC3, 0x30, 0x03, 0x30}, // 0xC6 � - {0xA, 0xF0, 0x01, 0xFC, 0x07, 0x0E, 0xCE, 0x07, 0xDC, 0x03, 0xF8, 0x03, 0xF8, 0x03, 0x18, 0x07, 0x1C, 0x1E, 0x0E, 0x1C, 0x06}, // 0xC7 � - {0xA, 0xF8, 0x3F, 0xF8, 0x3F, 0x99, 0x31, 0x9B, 0x31, 0x9E, 0x31, 0x9C, 0x31, 0x98, 0x31, 0x98, 0x31, 0x18, 0x30, 0x18, 0x30}, // 0xC8 � - {0xA, 0xF8, 0x3F, 0xF8, 0x3F, 0x98, 0x31, 0x98, 0x31, 0x9C, 0x31, 0x9E, 0x31, 0x9B, 0x31, 0x99, 0x31, 0x18, 0x30, 0x18, 0x30}, // 0xC9 � - {0xA, 0xF8, 0x3F, 0xF8, 0x3F, 0x9C, 0x31, 0x9E, 0x31, 0x9B, 0x31, 0x9B, 0x31, 0x9E, 0x31, 0x9C, 0x31, 0x18, 0x30, 0x18, 0x30}, // 0xCA � - {0xA, 0xF8, 0x3F, 0xF8, 0x3F, 0x9B, 0x31, 0x9B, 0x31, 0x98, 0x31, 0x98, 0x31, 0x9B, 0x31, 0x9B, 0x31, 0x18, 0x30, 0x18, 0x30}, // 0xCB � - {0x6, 0x19, 0x30, 0x1B, 0x30, 0xFE, 0x3F, 0xFC, 0x3F, 0x18, 0x30, 0x18, 0x30}, // 0xCC � - {0x6, 0x18, 0x30, 0x18, 0x30, 0xFC, 0x3F, 0xFE, 0x3F, 0x1B, 0x30, 0x19, 0x30}, // 0xCD � - {0x6, 0x1C, 0x30, 0x1E, 0x30, 0xFB, 0x3F, 0xFB, 0x3F, 0x1E, 0x30, 0x1C, 0x30}, // 0xCE � - {0x6, 0x1B, 0x30, 0x1B, 0x30, 0xF8, 0x3F, 0xF8, 0x3F, 0x1B, 0x30, 0x1B, 0x30}, // 0xCF � - {0xA, 0xC0, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0xFC, 0x0F, 0xF0, 0x03}, // 0xD0 � - {0xA, 0xF8, 0x3F, 0xF8, 0x3F, 0x72, 0x00, 0xE3, 0x00, 0xC1, 0x01, 0x83, 0x03, 0x02, 0x07, 0x03, 0x0E, 0xF9, 0x3F, 0xF8, 0x3F}, // 0xD1 � - {0xA, 0xE0, 0x0F, 0xF0, 0x1F, 0x39, 0x38, 0x1B, 0x30, 0x1E, 0x30, 0x1C, 0x30, 0x18, 0x30, 0x38, 0x38, 0xF0, 0x1F, 0xE0, 0x0F}, // 0xD2 � - {0xA, 0xE0, 0x0F, 0xF0, 0x1F, 0x38, 0x38, 0x18, 0x30, 0x1C, 0x30, 0x1E, 0x30, 0x1B, 0x30, 0x39, 0x38, 0xF0, 0x1F, 0xE0, 0x0F}, // 0xD3 � - {0xA, 0xE0, 0x0F, 0xF0, 0x1F, 0x3C, 0x38, 0x1E, 0x30, 0x1B, 0x30, 0x1B, 0x30, 0x1E, 0x30, 0x3C, 0x38, 0xF0, 0x1F, 0xE0, 0x0F}, // 0xD4 � - {0xA, 0xE0, 0x0F, 0xF0, 0x1F, 0x3A, 0x38, 0x1B, 0x30, 0x19, 0x30, 0x1B, 0x30, 0x1A, 0x30, 0x3B, 0x38, 0xF1, 0x1F, 0xE0, 0x0F}, // 0xD5 � - {0xA, 0xE0, 0x0F, 0xF0, 0x1F, 0x3B, 0x38, 0x1B, 0x30, 0x18, 0x30, 0x18, 0x30, 0x1B, 0x30, 0x3B, 0x38, 0xF0, 0x1F, 0xE0, 0x0F}, // 0xD6 � - {0x8, 0x10, 0x04, 0x30, 0x06, 0x60, 0x03, 0xC0, 0x01, 0xC0, 0x01, 0x60, 0x03, 0x30, 0x06, 0x10, 0x04}, // 0xD7 � - {0xA, 0xF0, 0x2F, 0xF8, 0x3F, 0x1C, 0x18, 0x0C, 0x3E, 0x8C, 0x37, 0xEC, 0x31, 0x7C, 0x30, 0x18, 0x38, 0xFC, 0x1F, 0xF4, 0x0F}, // 0xD8 � - {0xA, 0xF8, 0x07, 0xF8, 0x1F, 0x01, 0x38, 0x03, 0x30, 0x06, 0x30, 0x04, 0x30, 0x00, 0x30, 0x00, 0x38, 0xF8, 0x1F, 0xF8, 0x07}, // 0xD9 � - {0xA, 0xF8, 0x07, 0xF8, 0x1F, 0x00, 0x38, 0x00, 0x30, 0x04, 0x30, 0x06, 0x30, 0x03, 0x30, 0x01, 0x38, 0xF8, 0x1F, 0xF8, 0x07}, // 0xDA � - {0xA, 0xF8, 0x07, 0xF8, 0x1F, 0x04, 0x38, 0x06, 0x30, 0x03, 0x30, 0x03, 0x30, 0x06, 0x30, 0x04, 0x38, 0xF8, 0x1F, 0xF8, 0x07}, // 0xDB � - {0xA, 0xE0, 0x0F, 0xE0, 0x1F, 0x0C, 0x38, 0x0C, 0x30, 0x00, 0x30, 0x00, 0x30, 0x0C, 0x30, 0x0C, 0x18, 0xE0, 0x3F, 0xE0, 0x3F}, // 0xDC � - {0xA, 0x08, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC4, 0x3F, 0xC6, 0x3F, 0x63, 0x00, 0x31, 0x00, 0x18, 0x00, 0x08, 0x00}, // 0xDD � - {0x8, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x1B, 0x36, 0x18, 0x06, 0x18, 0x06, 0xF8, 0x07, 0xF0, 0x03}, // 0xDE � - {0x8, 0xC0, 0xFF, 0xE0, 0xFF, 0x30, 0x21, 0x10, 0x21, 0x10, 0x21, 0x30, 0x33, 0xE0, 0x3F, 0xC0, 0x1E}, // 0xDF � - {0xA, 0x00, 0x1C, 0x40, 0x3E, 0x60, 0x33, 0x62, 0x33, 0x66, 0x33, 0x6C, 0x33, 0x68, 0x33, 0x60, 0x33, 0xE0, 0x3F, 0xC0, 0x3F}, // 0xE0 � - {0xA, 0x00, 0x1C, 0x40, 0x3E, 0x60, 0x33, 0x68, 0x33, 0x6C, 0x33, 0x66, 0x33, 0x62, 0x33, 0x60, 0x33, 0xE0, 0x3F, 0xC0, 0x3F}, // 0xE1 � - {0xA, 0x00, 0x1C, 0x40, 0x3E, 0x68, 0x33, 0x6C, 0x33, 0x66, 0x33, 0x66, 0x33, 0x6C, 0x33, 0x68, 0x33, 0xE0, 0x3F, 0xC0, 0x3F}, // 0xE2 � - {0xA, 0x00, 0x1C, 0x40, 0x3E, 0x68, 0x33, 0x6C, 0x33, 0x64, 0x33, 0x6C, 0x33, 0x68, 0x33, 0x6C, 0x33, 0xE4, 0x3F, 0xC0, 0x3F}, // 0xE3 � - {0xA, 0x00, 0x1C, 0x40, 0x3E, 0x6C, 0x33, 0x6C, 0x33, 0x60, 0x33, 0x60, 0x33, 0x6C, 0x33, 0x6C, 0x33, 0xE0, 0x3F, 0xC0, 0x3F}, // 0xE4 � - {0xA, 0x00, 0x1C, 0x40, 0x3E, 0x60, 0x33, 0x64, 0x33, 0x6A, 0x33, 0x6A, 0x33, 0x64, 0x33, 0x60, 0x33, 0xE0, 0x3F, 0xC0, 0x3F}, // 0xE5 � - {0xA, 0x80, 0x1C, 0xC0, 0x3E, 0x40, 0x22, 0x40, 0x22, 0xC0, 0x1F, 0x80, 0x3F, 0x40, 0x22, 0x40, 0x22, 0xC0, 0x33, 0x80, 0x11}, // 0xE6 � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0xB8, 0x60, 0xB0, 0x60, 0xF0, 0x60, 0xF0, 0x60, 0x30, 0xE0, 0x38, 0xC0, 0x18, 0x80, 0x08}, // 0xE7 � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x33, 0x62, 0x33, 0x66, 0x33, 0x6C, 0x33, 0x68, 0x33, 0x60, 0x33, 0xC0, 0x13, 0x80, 0x03}, // 0xE8 � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x3B, 0x60, 0x33, 0x68, 0x33, 0x6C, 0x33, 0x66, 0x33, 0x62, 0x33, 0xC0, 0x13, 0x80, 0x03}, // 0xE9 � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE8, 0x33, 0x6C, 0x33, 0x66, 0x33, 0x66, 0x33, 0x6C, 0x33, 0x68, 0x33, 0xC0, 0x13, 0x80, 0x03}, // 0xEA � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xEC, 0x33, 0x6C, 0x33, 0x60, 0x33, 0x60, 0x33, 0x6C, 0x33, 0x6C, 0x33, 0xC0, 0x13, 0x80, 0x03}, // 0xEB � - {0x6, 0x00, 0x30, 0x62, 0x30, 0xE6, 0x3F, 0xEC, 0x3F, 0x08, 0x30, 0x00, 0x30}, // 0xEC � - {0x6, 0x00, 0x30, 0x68, 0x30, 0xEC, 0x3F, 0xE6, 0x3F, 0x02, 0x30, 0x00, 0x30}, // 0xED � - {0x6, 0x08, 0x30, 0x6C, 0x30, 0xE6, 0x3F, 0xE6, 0x3F, 0x0C, 0x30, 0x08, 0x30}, // 0xEE � - {0x6, 0x0C, 0x30, 0x6C, 0x30, 0xE0, 0x3F, 0xEC, 0x3F, 0x0C, 0x30, 0x00, 0x30}, // 0xEF � - {0x6, 0x78, 0x1C, 0xFC, 0x3E, 0xCC, 0x33, 0x8C, 0x33, 0x0C, 0x3F, 0x18, 0x1E}, // 0xF0 � - {0x9, 0xE0, 0x3F, 0xE8, 0x3F, 0x6C, 0x00, 0x64, 0x00, 0x6C, 0x00, 0x68, 0x00, 0xEC, 0x00, 0xC4, 0x3F, 0x80, 0x3F}, // 0xF1 � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x62, 0x30, 0x66, 0x30, 0x6C, 0x30, 0x68, 0x30, 0xE0, 0x38, 0xC0, 0x1F, 0x80, 0x0F}, // 0xF2 � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x68, 0x30, 0x6C, 0x30, 0x66, 0x30, 0x62, 0x30, 0xE0, 0x38, 0xC0, 0x1F, 0x80, 0x0F}, // 0xF3 � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xE8, 0x38, 0x6C, 0x30, 0x66, 0x30, 0x66, 0x30, 0x6C, 0x30, 0xE8, 0x38, 0xC0, 0x1F, 0x80, 0x0F}, // 0xF4 � - {0xA, 0x80, 0x0F, 0xC8, 0x1F, 0xEC, 0x38, 0x64, 0x30, 0x6C, 0x30, 0x68, 0x30, 0x6C, 0x30, 0xE4, 0x38, 0xC0, 0x1F, 0x80, 0x0F}, // 0xF5 � - {0xA, 0x80, 0x0F, 0xC0, 0x1F, 0xEC, 0x38, 0x6C, 0x30, 0x60, 0x30, 0x60, 0x30, 0x6C, 0x30, 0xEC, 0x38, 0xC0, 0x1F, 0x80, 0x0F}, // 0xF6 � - {0x8, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xB0, 0x0D, 0xB0, 0x0D, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01}, // 0xF7 � - {0xA, 0x80, 0x2F, 0xC0, 0x3F, 0xE0, 0x18, 0x60, 0x3C, 0x60, 0x36, 0x60, 0x33, 0xE0, 0x31, 0xC0, 0x38, 0xE0, 0x1F, 0xA0, 0x0F}, // 0xF8 � - {0xA, 0xE0, 0x0F, 0xE0, 0x1F, 0x00, 0x38, 0x02, 0x30, 0x06, 0x30, 0x0C, 0x30, 0x08, 0x30, 0x00, 0x18, 0xE0, 0x3F, 0xE0, 0x3F}, // 0xF9 � - {0xA, 0xE0, 0x0F, 0xE0, 0x1F, 0x00, 0x38, 0x08, 0x30, 0x0C, 0x30, 0x06, 0x30, 0x02, 0x30, 0x00, 0x18, 0xE0, 0x3F, 0xE0, 0x3F}, // 0xFA � - {0xA, 0xE0, 0x0F, 0xE0, 0x1F, 0x08, 0x38, 0x0C, 0x30, 0x06, 0x30, 0x06, 0x30, 0x0C, 0x30, 0x08, 0x18, 0xE0, 0x3F, 0xE0, 0x3F}, // 0xFB � - {0xA, 0xE0, 0x0F, 0xE0, 0x1F, 0x0C, 0x38, 0x0C, 0x30, 0x00, 0x30, 0x00, 0x30, 0x0C, 0x30, 0x0C, 0x18, 0xE0, 0x3F, 0xE0, 0x3F}, // 0xFC � - {0x8, 0x60, 0x00, 0xE0, 0x81, 0x80, 0xE7, 0x10, 0x7E, 0x18, 0x1E, 0x8C, 0x07, 0xE4, 0x01, 0x60, 0x00}, // 0xFD � - {0x8, 0x0C, 0x30, 0xFC, 0x3F, 0xFC, 0x3F, 0x6C, 0x36, 0x60, 0x06, 0x60, 0x06, 0xE0, 0x07, 0xC0, 0x03}, // 0xFE � - {0x8, 0x60, 0x00, 0xEC, 0x81, 0x8C, 0xE7, 0x00, 0x7E, 0x00, 0x1E, 0x8C, 0x07, 0xEC, 0x01, 0x60, 0x00}, // 0xFF � -#endif -}; - -#endif diff --git a/SmartEVSE-3/SmartEVSE-3/src/glcd.cpp b/SmartEVSE-3/SmartEVSE-3/src/glcd.cpp deleted file mode 100644 index 1d83240..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/glcd.cpp +++ /dev/null @@ -1,1215 +0,0 @@ -/* -; Project: Smart EVSE -; -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include "evse.h" -#include "glcd.h" -#include "utils.h" - -#include "font.cpp" -#include "font2.cpp" - - -const unsigned char LCD_Flow [] = { -0x00, 0x00, 0x98, 0xCC, 0x66, 0x22, 0x22, 0x22, 0xF2, 0xAA, 0x26, 0x2A, 0xF2, 0x22, 0x22, 0x22, -0x66, 0xCC, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0x60, 0x30, 0x60, 0xC0, -0x90, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x42, 0x04, 0xE0, 0x10, 0x08, -0x0B, 0x08, 0x10, 0xE0, 0x04, 0x42, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x4F, -0x49, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x40, 0x61, 0x31, 0x18, 0x08, 0x08, 0x08, 0x08, 0xFF, 0x08, 0x8D, 0x4A, 0xFF, 0x08, 0x08, 0x08, -0x08, 0x18, 0x31, 0x61, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x03, 0x06, 0x0C, 0x19, 0x32, 0x64, 0xC8, 0x10, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x02, -0x1A, 0x02, 0x01, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x05, 0x88, 0x50, 0xFF, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0xFF, 0x00, 0xF8, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x00, 0x00, 0x00, 0xF0, 0x10, -0x10, 0x10, 0x10, 0x10, 0xF0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x80, 0x60, 0x10, 0x08, 0x04, 0x02, 0x82, 0x81, 0x81, 0x81, 0x01, 0x01, 0x01, 0x01, -0x01, 0x01, 0x01, 0x02, 0x02, 0x04, 0x84, 0x8C, 0x88, 0x88, 0x10, 0x10, 0x20, 0x40, 0x80, 0x00, -0x00, 0x00, 0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x07, 0x05, 0x04, 0x04, 0x07, 0x0C, 0x18, 0x30, -0x68, 0x48, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, -0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, -0x08, 0x08, 0x00, 0x7F, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x7F, 0x40, -0x40, 0x40, 0x42, 0x40, 0x7F, 0x40, 0x40, 0x7F, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, -0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, -0x00, 0x06, 0x19, 0x10, 0x10, 0x1C, 0x02, 0x19, 0x24, 0x42, 0x42, 0x24, 0x19, 0x02, 0x1C, 0x10, -0x10, 0x10, 0x10, 0x1C, 0x02, 0x19, 0x24, 0x42, 0x42, 0x24, 0x19, 0x02, 0x1C, 0x10, 0x10, 0x1F -}; - -uint8_t LCDpos = 0; -bool LCDToggle = false; // Toggle display between two values -unsigned char LCDText = 0; // Cycle through text messages -unsigned int GLCDx, GLCDy; -uint8_t GLCDbuf[512]; // GLCD buffer (half of the display) -tm DelayedStartTimeTM; -time_t DelayedStartTime_Old; - -void st7565_command(unsigned char data) { - _A0_0; - SPI.transfer(data); -} - -void st7565_data(unsigned char data) { - _A0_1; - SPI.transfer(data); -} - -void goto_row(unsigned char y) { - unsigned char pattern; - pattern = 0xB0 | (y & 0xBF); // put row address on data port set command - st7565_command(pattern); -} -//-------------------- - -void goto_col(unsigned char x) { - unsigned char pattern; - pattern = ((0xF0 & x) >> 4) | 0x10; - st7565_command(pattern); // set high byte column command - pattern = ((0x0F & x)) | 0x00; - st7565_command(pattern); // set low byte column command; -} -//-------------------- - -void goto_xy(unsigned char x, unsigned char y) { - goto_col(x); - goto_row(y); -} - -void glcd_clrln(unsigned char ln, unsigned char data) { - unsigned char i; - goto_xy(0, ln); - for (i = 0; i < 128; i++) { - st7565_data(data); // put data on data port - } -} - -/* -void glcd_clrln_buffer(unsigned char ln) { - unsigned char i; - if (ln > 7) return; - for (i = 0; i < 128; i++) { - GLCDbuf[i+ (ln * 128)] = 0; - } -} -*/ -void glcd_clear(void) { - unsigned char i; - for (i = 0; i < 8; i++) { - glcd_clrln(i, 0); - } -} - -void GLCD_buffer_clr(void) { - unsigned char x = 0; - do { - GLCDbuf[x++] = 0; // clear first 256 bytes of GLCD buffer - } while (x != 0); -} - -void GLCD_sendbuf(unsigned char RowAdr, unsigned char Rows) { - unsigned char i, y = 0; - unsigned int x = 0; - - do { - goto_xy(0, RowAdr + y); - for (i = 0; i < 128; i++) st7565_data(GLCDbuf[x++]); // put data on data port - } while (++y < Rows); -} - -void GLCD_font_condense(unsigned char c, unsigned char *start, unsigned char *end, unsigned char space) { - if(c >= '0' && c <= '9') return; - if(c == ' ' && space) return; - if(font[c][0] == 0) { - if(font[c][1] == 0) *start = 2; - else *start = 1; - } - if(font[c][4] == 0) *end = 4; -} - -unsigned char GLCD_text_length(const char *str) { - unsigned char i = 0, length = 0; - unsigned char s, e; - - while (str[i]) { - s = 0; - e = 5; - GLCD_font_condense(str[i], &s, &e, 0); - length += (e - s) + 1; - i++; - } - - return length - 1; -} - -#ifdef GLCD_HIRES_FONT -unsigned char GLCD_text_length2(const char *str) { - unsigned char i = 0, length = 0; - - while (str[i]) { - length += font2[(int) str[i]][0] + 2; - i++; - } - - return length - 2; -} -#endif - -/** - * Write character to buffer - * - * @param c - * @param 00000000 Options - * |+++ Shift font down - * +--- Merge with buffer content -*/ -void GLCD_write_buf(unsigned int c, unsigned char Options) { - unsigned int f, x; - unsigned char i = 0, m = 5, shift = 0; - bool merge = false; - - x = 128 * GLCDy; - x += GLCDx; - - GLCD_font_condense(c, &i, &m, 1); // remove whitespace from font - GLCDx += (m - i) + 1; - - if (Options) { - shift = Options & 0b00000111; - if (Options & GLCD_MERGE) merge = true; - } - - do { - f = font[c][i]; - if(shift) f <<= shift; - if(merge) f |= GLCDbuf[x]; - GLCDbuf[x] = f; - x++; - } while (++i < m); -} - -// Write one double height character to the GLCD buffer -// special characters '.' and ' ' will use reduced width in the buffer -void GLCD_write_buf2(unsigned int c) { -#ifdef GLCD_HIRES_FONT - unsigned char i = 1; - - while ((i < (font2[c][0] * 2)) && (GLCDx < 128)) { - GLCDbuf[GLCDx] = font2[c][i]; - GLCDbuf[GLCDx + 128] = font2[c][i + 1]; - i += 2; - GLCDx ++; - } -#else - unsigned char i = 0, m = 5, ch, z1; - - GLCD_font_condense(c, &i, &m, 0); - - while ((i < m) && (GLCDx < 127)) { - z1 = 0; - ch = font[c][i]; - if (ch & 0x01u) z1 |= 0x3u; - if (ch & 0x02u) z1 |= 0xcu; - if (ch & 0x04u) z1 |= 0x30u; - if (ch & 0x08u) z1 |= 0xc0u; - GLCDbuf[GLCDx] = z1; - GLCDbuf[GLCDx + 1] = z1; - z1 = 0; - ch = ch >> 4; - if (ch & 0x01u) z1 |= 0x3u; - if (ch & 0x02u) z1 |= 0xcu; - if (ch & 0x04u) z1 |= 0x30u; - if (ch & 0x08u) z1 |= 0xc0u; - GLCDbuf[GLCDx + 128] = z1; - GLCDbuf[GLCDx + 129] = z1; - i++; - GLCDx += 2; - }; -#endif - GLCDx += 2; -} - -/** - * Write a string to LCD buffer - * - * @param str - * @param x - * @param y - * @param Options - */ -void GLCD_write_buf_str(unsigned char x, unsigned char y, const char* str, unsigned char Options) { - unsigned char i = 0; - - switch(Options) { - case GLCD_ALIGN_LEFT: - GLCDx = x; - break; - case GLCD_ALIGN_CENTER: - GLCDx = x - (GLCD_text_length(str) / 2); - break; - case GLCD_ALIGN_RIGHT: - GLCDx = x - GLCD_text_length(str); - break; - } - - GLCDy = y; - while (str[i]) { - GLCD_write_buf(str[i++], 0); - } -} - -void GLCD_write_buf_str2(const char *str, unsigned char Options) { - unsigned char i = 0; - -#ifdef GLCD_HIRES_FONT - if (Options == GLCD_ALIGN_CENTER) GLCDx = 64 - GLCD_text_length2(str) / 2; -#else - if (Options == GLCD_ALIGN_CENTER) GLCDx = 64 - GLCD_text_length(str); -#endif - else GLCDx = 2; - - while (str[i]) { - GLCD_write_buf2(str[i++]); - } -} - -void GLCD_print_buf(unsigned char y, const char *str) { - GLCD_buffer_clr(); // Clear buffer - GLCD_write_buf_str(0, y, str, GLCD_ALIGN_LEFT); - GLCD_sendbuf(y, 1); // copy buffer to LCD -} - -// uses buffer -void GLCD_print_buf2_left(const char *data) { - GLCD_buffer_clr(); // Clear buffer - GLCD_write_buf_str2(data, GLCD_ALIGN_LEFT); - GLCD_sendbuf(2, 2); // copy buffer to LCD -} - -void GLCD_print_buf2(unsigned char y, const char* str) { - GLCD_buffer_clr(); // Clear buffer - GLCD_write_buf_str2(str, GLCD_ALIGN_CENTER); - GLCD_sendbuf(y, 2); // copy buffer to LCD -} - -// Write Menu to buffer, then send to GLCD -void GLCD_print_menu(unsigned char y, const char* str) { - GLCD_buffer_clr(); // Clear buffer - GLCD_write_buf_str2(str, GLCD_ALIGN_CENTER); - - if ((SubMenu && y == 4) || (!SubMenu && y == 2)) { // navigation arrows - GLCDx = 0; - GLCD_write_buf2('<'); - GLCDx = 10 * 12; // last character of line - GLCD_write_buf2('>'); - } - - GLCD_sendbuf(y, 2); -} - - -/** - * Increase or decrease int value - * - * @param unsigned int Buttons - * @param unsigned int Value - * @param unsigned int Min - * @param unsigned int Max - * @return unsigned int Value - */ -unsigned int MenuNavInt(unsigned char Buttons, unsigned int Value, unsigned int Min, unsigned int Max) { - if (Buttons == 0x3) { - if (Value >= Max) Value = Min; - else Value++; - } else if (Buttons == 0x6) { - if (Value <= Min) Value = Max; - else Value--; - } - - return Value; -} - -/** - * Get to next or previous value of an char array - * - * @param unsigned char Buttons - * @param unsigned char Value - * @param unsigned char Count - * @param unsigned char array Values - * @return unsigned char Value - */ -unsigned char MenuNavCharArray(unsigned char Buttons, unsigned char Value, unsigned char Values[], unsigned char Count) { - unsigned int i; - - for (i = 0; i < Count; i++) { - if (Value == Values[i]) break; - } - i = MenuNavInt(Buttons, i, 0, Count - 1u); - - return Values[i]; -} - -// uses buffer -void GLCDHelp(void) // Display/Scroll helptext on LCD -{ - unsigned int x; - - x = strlen(MenuStr[LCDNav].Desc); - GLCD_print_buf2_left(MenuStr[LCDNav].Desc + LCDpos); - - if (LCDpos++ == 0) ScrollTimer = millis() - 4000; - else if (LCDpos > (x - 10)) { - ScrollTimer = millis() - 3000; - LCDpos = 0; - } else ScrollTimer = millis() - 4700; -} - - -// called once a second -void GLCD(void) { - unsigned char x; - unsigned int seconds, minutes; - static unsigned char energy_mains = 20; // X position - static unsigned char energy_ev = 74; // X position - char Str[26]; - - LCDTimer++; - - if (LCDNav) { - GLCD_buffer_clr(); - // top line - if (LCDNav == MENU_RFIDREADER && SubMenu) { - if (RFIDstatus == 2) GLCD_print_buf(0, (const char*) "Card Stored"); - else if (RFIDstatus == 3) GLCD_print_buf(0, (const char*) "Card Deleted"); - else if (RFIDstatus == 4) GLCD_print_buf(0, (const char*) "Card already stored!"); - else if (RFIDstatus == 5) GLCD_print_buf(0, (const char*) "Card not in storage!"); - else if (RFIDstatus == 6) GLCD_print_buf(0, (const char*) "Card storage full!"); - else glcd_clrln(0, 0x00); // Clear line - LCDTimer = 0; // reset timer, so it will not exit the menu when learning/deleting cards - } else { - // When connected to Wifi, display IP and time in top row - uint8_t WIFImode = getItemValue(MENU_WIFI); - if (WIFImode == 1 ) { // Wifi Enabled - - if (WiFi.status() == WL_CONNECTED) { - sprintf(Str, "%s",WiFi.localIP().toString().c_str()); - GLCD_write_buf_str(0,0, Str, GLCD_ALIGN_LEFT); - if (LocalTimeSet) sprintf(Str, "%02u:%02u",timeinfo.tm_hour, timeinfo.tm_min); - else sprintf(Str, "--:--"); - GLCD_write_buf_str(127,0, Str, GLCD_ALIGN_RIGHT); - } else GLCD_write_buf_str(0,0, "Not connected to WiFi", GLCD_ALIGN_LEFT); - - // When Wifi Setup is selected, show password and SSID of the Access Point - } else if (WIFImode == 2) { - if (SubMenu && WiFi.getMode() != WIFI_AP_STA) { // Do not show if AP_STA mode is started - sprintf(Str, "O button starts portal"); - GLCD_write_buf_str(0,0, Str, GLCD_ALIGN_LEFT); - } else { - // Show Access Point name - sprintf(Str, "AP:%u", serialnr); - GLCD_write_buf_str(0,0, Str, GLCD_ALIGN_LEFT); - // and password - sprintf(Str, "PW:%s", APpassword.c_str()); - GLCD_write_buf_str(127,0, Str, GLCD_ALIGN_RIGHT); - } - } - } - // update LCD - GLCD_sendbuf(0, 1); - - if (LCDTimer > 120) { - LCDNav = 0; // Exit Setup menu after 120 seconds. - read_settings(); // don't save, but restore settings - } else return; // disable LCD status messages when navigating LCD Menu - } // if LCDNav - - if (LCDTimer == 1) { - LCDText = 0; - } else if (LCDTimer > 4) { - LCDTimer = 1; - LCDToggle = 1u - LCDToggle; - LCDText++; - } - - if (ErrorFlags) { // We switch backlight on, as we exit after displaying the error - BacklightTimer = BACKLIGHT; // Backlight timer is set to 120 seconds - - if (ErrorFlags & (CT_NOCOMM | EV_NOCOMM)) { // No serial communication for 10 seconds - - if (ErrorFlags & EV_NOCOMM) { - GLCD_print_buf2(0, (const char *) "CAN'T READ"); - GLCD_print_buf2(2, (const char *) "EV METER"); - } else { - GLCD_print_buf2(0, (const char *) "ERROR NO"); - GLCD_print_buf2(2, (const char *) "SERIAL COM"); - } - GLCD_print_buf2(4, (const char *) "CHECK CFG"); - GLCD_print_buf2(6, (const char *) "OR WIRING"); - return; - } else if (ErrorFlags & TEMP_HIGH) { // Temperature reached 65C - GLCD_print_buf2(0, (const char *) "HIGH TEMP"); - GLCD_print_buf2(2, (const char *) "ERROR"); - GLCD_print_buf2(4, (const char *) "CHARGING"); - GLCD_print_buf2(6, (const char *) "STOPPED"); - return; - } else if (ErrorFlags & RCM_TRIPPED) { // Residual Current Sensor tripped - if (!LCDToggle) { - GLCD_print_buf2(0, (const char *) "RESIDUAL"); - GLCD_print_buf2(2, (const char *) "FAULT"); - GLCD_print_buf2(4, (const char *) "CURRENT"); - GLCD_print_buf2(6, (const char *) "DETECTED"); - } else { - GLCD_print_buf2(0, (const char *) "PRESS"); - GLCD_print_buf2(2, (const char *) "BUTTON"); - GLCD_print_buf2(4, (const char *) "TO"); - GLCD_print_buf2(6, (const char *) "RESET"); - } - return; - } else if (ErrorFlags & Test_IO) { // Only used when testing the module - GLCD_print_buf2(2, (const char *) "IO Test"); - sprintf(Str, "FAILED! %u", TestState); - GLCD_print_buf2(4, Str); - return; - } else if (ErrorFlags & BL_FLASH) { // Bootloader update error - GLCD_print_buf2(2, (const char *) "BOOTLOADER"); - GLCD_print_buf2(4, (const char *) "UPDATE ERR"); - return; - } - } // end of ERROR() // more specific error handling in the code below - - if (TestState == 80) { // Only used when testing the module - GLCD_print_buf2(2, (const char *) "IO Test"); - GLCD_print_buf2(4, (const char *) "Passed"); - return; - } - - // MODE NORMAL - if (Mode == MODE_NORMAL || !Access_bit) { - - glcd_clrln(0, 0x00); - glcd_clrln(1, 0x04); // horizontal line - glcd_clrln(6, 0x10); // horizontal line - glcd_clrln(7, 0x00); - - if (ErrorFlags & LESS_6A) { - GLCD_print_buf2(2, (const char *) "WAITING"); - GLCD_print_buf2(4, (const char *) "FOR POWER"); - } else if (State == STATE_MODEM_REQUEST || State == STATE_MODEM_WAIT || State == STATE_MODEM_DONE) { // Modem states - - BacklightTimer = BACKLIGHT; - - GLCD_print_buf2(2, (const char *) "MODEM"); - GLCD_print_buf2(4, (const char *) "COMM"); - } else if (State == STATE_MODEM_DENIED) { // Modem denied state - - BacklightTimer = BACKLIGHT; - - GLCD_print_buf2(2, (const char *) "MODEM"); - GLCD_print_buf2(4, (const char *) "DENIED"); - } else if (State == STATE_C) { // STATE C - - BacklightTimer = BACKLIGHT; - - GLCD_print_buf2(2, (const char *) "CHARGING"); - sprintf(Str, "%u.%uA",Balanced[0] / 10, Balanced[0] % 10); - GLCD_print_buf2(4, Str); - } else { // STATE A and STATE B - if (Access_bit) { - GLCD_print_buf2(2, (const char *) "READY TO"); - sprintf(Str, "CHARGE %u", ChargeDelay); - if (ChargeDelay) { - // BacklightTimer = BACKLIGHT; - } else Str[6] = '\0'; - GLCD_print_buf2(4, Str); - } else { - if (getItemValue(MENU_RFIDREADER)) { - if (RFIDstatus == 7) { - GLCD_print_buf2(2, (const char *) "INVALID"); - GLCD_print_buf2(4, (const char *) "RFID CARD"); - } else { - GLCD_print_buf2(2, (const char *) "PRESENT"); - GLCD_print_buf2(4, (const char *) "RFID CARD"); - } - } else { - if (DelayedStartTime.epoch2) { - GLCD_print_buf2(2, (const char *) "STARTING @"); -#define _24H 24*60*60 -#define _WEEK 7*_24H - String StrFormat; - if (DelayedStartTime.diff <= _24H) - //if it starts in the next 24 hours, just print hours : minutes - StrFormat = "%R"; - else { - //if it starts in the next week, print day of week, day of month, hours: minutes - if (DelayedStartTime.diff <= _WEEK) - StrFormat = "%a %e %R"; - else - //if it starts later, print day of week, day of month, month, year perhaps scrolling hours/minutes? - StrFormat = "%a %e %b"; - //StrFormat = "%a %e %b '%C %R"; - } - if (DelayedStartTime.epoch2 && LocalTimeSet && DelayedStartTime.epoch2 != DelayedStartTime_Old) { - time_t epoch = DelayedStartTime.epoch2 + EPOCH2_OFFSET; - DelayedStartTimeTM = *localtime(&epoch); - } - if (!strftime(Str, 26, StrFormat.c_str(), &DelayedStartTimeTM)) - sprintf(Str, "later..."); - GLCD_print_buf2(4, Str); - //print current time - if (LocalTimeSet) { - GLCD_buffer_clr(); - if (strftime(Str, 26, "%a %e %b '%y %R", &timeinfo)) - GLCD_write_buf_str(0,0, Str, GLCD_ALIGN_LEFT); - GLCD_sendbuf(7, 1); - } - } else { - GLCD_print_buf2(2, (const char *) "ACCESS"); - GLCD_print_buf2(4, (const char *) "DENIED"); - } - } - } - } - } // MODE SMART or SOLAR - else if ((Mode == MODE_SMART) || (Mode == MODE_SOLAR)) { - - memcpy (GLCDbuf, LCD_Flow, 512); // copy Flow Menu to LCD buffer - - if (Mode == MODE_SMART) { // remove the Sun from the LCD buffer - for (x=0; x<13; x++) { - GLCDbuf[x+74u] = 0; - GLCDbuf[x+74u+128u] = 0; - } - } - if (SolarStopTimer) { - seconds = SolarStopTimer; // display remaining time before charging is stopped - minutes = seconds / 60; - seconds = seconds % 60; - sprintf(Str, "%02u:%02u", minutes, seconds); - GLCD_write_buf_str(100, 0, Str, GLCD_ALIGN_LEFT); // print to buffer - } else { - for (x = 0; x < 8; x++) GLCDbuf[x + 92u] = 0; // remove the clock from the LCD buffer - } - - - if (Isum < 0) { - energy_mains -= 3; // animate the flow of Mains energy on LCD. - if (energy_mains < 20) energy_mains = 44; // Only in Mode: Smart or Solar - } else { - energy_mains += 3; - if (energy_mains > 44) energy_mains = 20; - } - - GLCDx = energy_mains; - GLCDy = 3; - - if (abs(Isum) >3 ) GLCD_write_buf(0x0A, 0); // Show energy flow 'blob' between Grid and House - // If current flow is < 0.3A don't show the blob - - if (EVMeter) { // If we have a EV kWh meter configured, Show total charged energy in kWh on LCD. - sprintfl(Str, "%2u.%1ukWh", EnergyCharged, 3, 1); // Will reset to 0.0kWh when charging cable reconnected, and state change from STATE B->C - GLCD_write_buf_str(89, 1, Str,GLCD_ALIGN_LEFT); // print to buffer - } - - // Write number of used phases into the car - /* if (Node[0].Phases) { - GLCDx = 110; - GLCDy = 2; - GLCD_write_buf(Node[0].Phases, 2 | GLCD_MERGE); - } -*/ - if (State == STATE_C) { - BacklightTimer = BACKLIGHT; - - energy_ev += 3; // animate energy flow to EV - if (energy_ev > 89) energy_ev = 74; - - GLCDx = energy_ev; - GLCDy = 3; - GLCD_write_buf(0x0A, 0); // Show energy flow 'blob' between House and Car - - if (LCDToggle && EVMeter) { - if (PowerMeasured < 9950) { - sprintfl(Str, "%1u.%1ukW", PowerMeasured, 3, 1); - } else { - sprintfl(Str, "%ukW", PowerMeasured, 3, 0); - } - } else { - sprintfl(Str, "%uA", Balanced[0], 1, 0); - } - GLCD_write_buf_str(85, 2, Str, GLCD_ALIGN_CENTER); - } else if (State == STATE_A) { - // Remove line between House and Car - for (x = 73; x < 96; x++) GLCDbuf[3u * 128u + x] = 0; - } - - if (LCDToggle && Mode == MODE_SOLAR) { // Show Sum of currents when solar charging. - GLCDx = 41; - GLCDy = 1; - GLCD_write_buf(0x0B, 0); // Sum symbol - - sprintfl(Str, "%dA", Isum, 1, 0); - GLCD_write_buf_str(46, 2, Str, GLCD_ALIGN_RIGHT); // print to buffer - } else { // Displayed only in Smart and Solar modes - for (x = 0; x < 3; x++) { // Display L1, L2 and L3 currents on LCD - sprintfl(Str, "%dA", Irms[x], 1, 0); - GLCD_write_buf_str(46, x, Str, GLCD_ALIGN_RIGHT); // print to buffer - } - } - GLCD_sendbuf(0, 4); // Copy LCD buffer to GLCD - - glcd_clrln(4, 0); // Clear line 4 - if (ErrorFlags & LESS_6A) { - if (!LCDToggle) { - GLCD_print_buf2(5, (const char *) "WAITING"); - } else GLCD_print_buf2(5, (const char *) "FOR POWER"); - } else if (ErrorFlags & NO_SUN) { - if (!LCDToggle) { - GLCD_print_buf2(5, (const char *) "WAITING"); - } else GLCD_print_buf2(5, (const char *) "FOR SOLAR"); - } else if (State == STATE_MODEM_REQUEST || State == STATE_MODEM_WAIT || State == STATE_MODEM_DONE) { // Modem states - GLCD_print_buf2(5, (const char *) "MODEM"); - } else if (State != STATE_C) { - switch (Switching_To_Single_Phase) { - case FALSE: - sprintf(Str, "READY %u", ChargeDelay); - if (!ChargeDelay) Str[5] = '\0'; - break; - case GOING_TO_SWITCH: - sprintf(Str, "3F -> 1F %u", ChargeDelay); - if (!ChargeDelay) Str[7] = '\0'; - break; - case AFTER_SWITCH: // never getting here, just preventing compiler warning - break; - } - GLCD_print_buf2(5, Str); - } else if (State == STATE_C) { - switch (LCDText) { - default: - LCDText = 0; - if (Mode != MODE_NORMAL) { - if (Mode == MODE_SOLAR) sprintf(Str, "SOLAR"); - else sprintf(Str, "SMART"); - if (Nr_Of_Phases_Charging != 0) { - sprintf(Str+5," %uF", Nr_Of_Phases_Charging); - } - GLCD_print_buf2(5, Str); - break; - } else LCDText++; - // fall through - case 1: - GLCD_print_buf2(5, (const char *) "CHARGING"); - break; - case 2: - if (EVMeter) { - sprintfl(Str, "%u.%01u kW", PowerMeasured, 3, 1); - GLCD_print_buf2(5, Str); - break; - } else LCDText++; - // fall through - case 3: - if (EVMeter) { - sprintfl(Str, "%u.%02u kWh", EnergyCharged, 3, 2); - GLCD_print_buf2(5, Str); - break; - } else LCDText++; - // fall through - case 4: - sprintf(Str, "%u.%u A", Balanced[0] / 10, Balanced[0] % 10); - GLCD_print_buf2(5, Str); - break; - } - } - glcd_clrln(7, 0x00); - } // End Mode SMART or SOLAR - -} - - -/** - * Counts nr of menu options currently available - * - * @param unsigned char count - * @return unsigned char postion - */ -unsigned char GetPosInMenu (unsigned char count) { - unsigned char i; - - for (i = 0; i < count; i++) { - if (MenuItems[i] == LCDNav) return i + 1u; - } - return 0; -} - - -/** - * Get active option of an menu item - * - * @param uint8_t nav - * @return uint8_t[] MenuItemOption - */ -const char * getMenuItemOption(uint8_t nav) { - static char Str[12]; // must be declared static, since it's referenced outside of function scope - - // Text - const static char StrFixed[] = "Fixed"; - const static char StrSocket[] = "Socket"; - const static char StrSmart[] = "Smart"; - const static char StrNormal[] = "Normal"; - const static char StrSolar[] = "Solar"; - const static char StrSolenoid[] = "Solenoid"; - const static char StrMotor[] = "Motor"; - const static char StrDisabled[] = "Disabled"; - const static char StrLoadBl[9][9] = {"Disabled", "Master", "Node 1", "Node 2", "Node 3", "Node 4", "Node 5", "Node 6", "Node 7"}; - const static char StrSwitch[5][10] = {"Disabled", "Access B", "Access S", "Sma-Sol B", "Sma-Sol S"}; - const static char StrGrid[2][10] = {"4Wire", "3Wire"}; - const static char StrEnabled[] = "Enabled"; - const static char StrExitMenu[] = "MENU"; - const static char StrRFIDReader[6][10] = {"Disabled", "EnableAll", "EnableOne", "Learn", "Delete", "DeleteAll"}; - const static char StrWiFi[3][10] = {"Disabled", "Enabled", "SetupWifi"}; - - unsigned int value = getItemValue(nav); - - switch (nav) { - case MENU_MAX_TEMP: - sprintf(Str, "%2u C", value); - return Str; - case MENU_C2: - return StrEnableC2[value]; - case MENU_CONFIG: - if (value) return StrFixed; - else return StrSocket; - case MENU_MODE: - if (Mode == MODE_SMART) return StrSmart; - else if (Mode == MODE_SOLAR) return StrSolar; - else return StrNormal; - case MENU_START: - sprintf(Str, "-%2u A", value); - return Str; - case MENU_STOP: - if (value) { - sprintf(Str, "%2u min", value); - return Str; - } else return StrDisabled; - case MENU_LOADBL: - return StrLoadBl[LoadBl]; - case MENU_MAINS: - case MENU_SUMMAINS: - case MENU_MIN: - case MENU_MAX: - case MENU_CIRCUIT: - case MENU_IMPORT: - sprintf(Str, "%2u A", value); - return Str; - case MENU_LOCK: - if (value == 1) return StrSolenoid; - else if (value == 2) return StrMotor; - else return StrDisabled; - case MENU_SWITCH: - return StrSwitch[value]; - case MENU_RCMON: - if (value) return StrEnabled; - else return StrDisabled; - case MENU_MAINSMETER: - case MENU_EVMETER: - return (const char*)EMConfig[value].Desc; - case MENU_GRID: - return StrGrid[value]; - case MENU_MAINSMETERADDRESS: - case MENU_EVMETERADDRESS: - case MENU_EMCUSTOM_UREGISTER: - case MENU_EMCUSTOM_IREGISTER: - case MENU_EMCUSTOM_PREGISTER: - case MENU_EMCUSTOM_EREGISTER: - if(value < 0x1000) sprintf(Str, "%u (%02X)", value, value); // This just fits on the LCD. - else sprintf(Str, "%u %X", value, value); - return Str; - case MENU_EMCUSTOM_ENDIANESS: - switch(value) { - case 0: return "LBF & LWF"; - case 1: return "LBF & HWF"; - case 2: return "HBF & LWF"; - case 3: return "HBF & HWF"; - default: return ""; - } - case MENU_EMCUSTOM_DATATYPE: - switch (value) { - case MB_DATATYPE_INT16: return "INT16"; - case MB_DATATYPE_INT32: return "INT32"; - case MB_DATATYPE_FLOAT32: return "FLOAT32"; - } - // fall through - case MENU_EMCUSTOM_FUNCTION: - switch (value) { - case 3: return "3:Hold. Reg"; - case 4: return "4:Input Reg"; - default: return ""; - } - case MENU_EMCUSTOM_UDIVISOR: - case MENU_EMCUSTOM_IDIVISOR: - case MENU_EMCUSTOM_PDIVISOR: - case MENU_EMCUSTOM_EDIVISOR: - sprintf(Str, "%lu", pow_10[value]); - return Str; - case MENU_RFIDREADER: - return StrRFIDReader[value]; - case MENU_WIFI: - return StrWiFi[value]; - case MENU_EXIT: - return StrExitMenu; - default: - return ""; - } -} - - -/** - * Create an array of available menu items - * Depends on configuration settings like CONFIG/MODE/LoadBL - * - * @return uint8_t MenuItemCount - */ -uint8_t getMenuItems (void) { - uint8_t m = 0; - - uint8_t MainsMeter = getItemValue(MENU_MAINSMETER); - MenuItems[m++] = MENU_MODE; // EVSE mode (0:Normal / 1:Smart / 2: Solar) - MenuItems[m++] = MENU_CONFIG; // Configuration (0:Socket / 1:Fixed Cable) - if (!getItemValue(MENU_CONFIG)) { // ? Fixed Cable? - MenuItems[m++] = MENU_LOCK; // - Cable lock (0:Disable / 1:Solenoid / 2:Motor) - } - MenuItems[m++] = MENU_LOADBL; // Load Balance Setting (0:Disable / 1:Master / 2-8:Node) - if (Mode) { // ? Smart or Solar mode? - if (LoadBl < 2) { // - ? Load Balancing Disabled/Master? - MenuItems[m++] = MENU_MAINSMETER; // - - Type of Mains electric meter (0: Disabled / Constants EM_*) - if (MainsMeter == EM_SENSORBOX) { // - - ? Sensorbox? - if (GridActive == 1) MenuItems[m++] = MENU_GRID; -// if (CalActive == 1) MenuItems[m++] = MENU_CAL; // - - - Sensorbox CT measurement calibration - } else if (MainsMeter && MainsMeter != EM_API) { // - - ? Other? - MenuItems[m++] = MENU_MAINSMETERADDRESS; // - - - Address of Mains electric meter (9 - 247) - } - } - MenuItems[m++] = MENU_EVMETER; // - Type of EV electric meter (0: Disabled / Constants EM_*) - if (EVMeter && EVMeter != EM_API) { // - ? EV meter configured? - MenuItems[m++] = MENU_EVMETERADDRESS; // - - Address of EV electric meter (9 - 247) - } - if (LoadBl < 2) { // - ? Load Balancing Disabled/Master? - if (MainsMeter == EM_CUSTOM || EVMeter == EM_CUSTOM) { // ? Custom electric meter used? - MenuItems[m++] = MENU_EMCUSTOM_ENDIANESS; // - - Byte order of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_DATATYPE; // - - Data type of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_FUNCTION; // - - Modbus Function of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_UREGISTER; // - - Starting register for voltage of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_UDIVISOR; // - - Divisor for voltage of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_IREGISTER; // - - Starting register for current of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_IDIVISOR; // - - Divisor for current of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_PREGISTER; // - - Starting register for power of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_PDIVISOR; // - - Divisor for power of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_EREGISTER; // - - Starting register for energy of custom electric meter - MenuItems[m++] = MENU_EMCUSTOM_EDIVISOR; // - - Divisor for energy of custom electric meter - } - if (MainsMeter) { // Mainsmeter is configured and Load Balancing Disabled/Master? - MenuItems[m++] = MENU_MAINS; // - Max Mains Amps (hard limit, limited by the MAINS connection) (A) (Mode:Smart/Solar) - MenuItems[m++] = MENU_MIN; // - Minimal current the EV is happy with (A) (Mode:Smart/Solar or LoadBl:Master) - } - } - } - MenuItems[m++] = MENU_MAX; // Max Charge current (A) - if (LoadBl == 1 || (LoadBl == 0 && Mode != MODE_NORMAL && EVMeter)) { // ? Load balancing Master? - // Also, when not in Normal Mode and that EV meter is present, MaxCircuit will limit - // the total current (subpanel configuration) - MenuItems[m++] = MENU_CIRCUIT; // - Max current of the EVSE circuit (A) - } - if (Mode == MODE_SOLAR && LoadBl < 2) { // ? Solar mode and Load Balancing Disabled/Master? - MenuItems[m++] = MENU_START; // - Start Surplus Current (A) - MenuItems[m++] = MENU_STOP; // - Stop time (min) - MenuItems[m++] = MENU_IMPORT; // - Import Current from Grid (A) - MenuItems[m++] = MENU_C2; - } - MenuItems[m++] = MENU_SWITCH; // External Switch on SW (0:Disable / 1:Access / 2:Smart-Solar) - MenuItems[m++] = MENU_RCMON; // Residual Current Monitor on RCM (0:Disable / 1:Enable) - MenuItems[m++] = MENU_RFIDREADER; // RFID Reader connected to SW (0:Disable / 1:Enable / 2:Learn / 3:Delete / 4:Delate All) - MenuItems[m++] = MENU_WIFI; // Wifi Disabled / Enabled / Portal - MenuItems[m++] = MENU_MAX_TEMP; - if (MainsMeter && LoadBl < 2) - MenuItems[m++] = MENU_SUMMAINS; - MenuItems[m++] = MENU_EXIT; - - return m; -} - - - -/** - * Called when one of the SmartEVSE buttons is pressed - * - * @param Buttons: < o > - * Value: 1 2 4 - * Bit: 0:Pressed / 1:Released - */ -void GLCDMenu(uint8_t Buttons) { - static unsigned long ButtonTimer = 0; - static uint8_t ButtonRelease = 0; // keeps track of LCD Menu Navigation - static uint16_t CT1, value, ButtonRepeat = 0; - char Str[24]; - - unsigned char MenuItemsCount = getMenuItems(); - - // Main Menu Navigation - BacklightTimer = BACKLIGHT; // delay before LCD backlight turns off. - - // Disable buttons when access switch is configured and access is denied - if ((getItemValue(MENU_SWITCH) == 1 || getItemValue(MENU_SWITCH) == 2) && Access_bit == 0 && LCDNav == 0) - return; - - if (getItemValue(MENU_RCMON) == 1 && (ErrorFlags & RCM_TRIPPED) && RCMFAULT == LOW) { // RCM was tripped, but RCM level is back to normal - ErrorFlags &= ~RCM_TRIPPED; // Clear RCM error bit, by pressing any button - } - - if ((LCDNav == 0) && (Buttons == 0x5) && (ButtonRelease == 0)) { // Button 2 pressed ? - LCDNav = MENU_ENTER; // about to enter menu - ButtonTimer = millis(); - } else if (LCDNav == MENU_ENTER && ((ButtonTimer + 2000) < millis() )) { // - LCDNav = MenuItems[0]; // Main Menu entered - ButtonRelease = 1; - } else if ((LCDNav == MENU_ENTER) && (Buttons == 0x7)) { // Button 2 released before entering menu? - LCDNav = 0; - ButtonRelease = 0; - GLCD(); - // stop charging if < button is pressed longer then 2 seconds - } else if ((LCDNav == 0) && (Buttons == 0x6) && (ButtonRelease == 0)) { // Button 1 pressed ? - LCDNav = MENU_OFF; // about to cancel charging - ButtonTimer = millis(); - } else if (LCDNav == MENU_OFF && ((ButtonTimer + 2000) < millis() )) { - LCDNav = 0; // Charging canceled - setAccess(false); - ButtonRelease = 1; - } else if ((LCDNav == MENU_OFF) && (Buttons == 0x7)) { // Button 1 released before entering menu? - LCDNav = 0; - ButtonRelease = 0; - GLCD(); - // start charging if > button is pressed longer then 2 seconds - } else if ((Access_bit == 0) && (LCDNav == 0) && (Buttons == 0x3) && (ButtonRelease == 0)) { // Button 3 pressed ? - LCDNav = MENU_ON; // about to start charging - ButtonTimer = millis(); - } else if (LCDNav == MENU_ON && ((ButtonTimer + 2000) < millis() )) { - LCDNav = 0; // Charging canceled - setAccess(true); - ButtonRelease = 1; - } else if ((LCDNav == MENU_ON) && (Buttons == 0x7)) { // Button 1 released before entering menu? - LCDNav = 0; - ButtonRelease = 0; - GLCD(); - //////////////// - } else if (Buttons == 0x2 && (ButtonRelease < 2)) { // Buttons < and > pressed ? - if ((LCDNav == MENU_CAL) && SubMenu ) { // While in CT CAL submenu ? - ICal = ICAL; // reset Calibration value - SubMenu = 0; // Exit Submenu - } else if (LCDNav == 0) GLCD_init(); // When not in Menu, re-initialize LCD - ButtonRelease = 1; - } else if ((LCDNav > 1) && LCDNav != MENU_OFF && LCDNav != MENU_ON && (Buttons == 0x2 || Buttons == 0x3 || Buttons == 0x6)) { // Buttons < or > or both pressed - if (ButtonRelease == 0) { // We are navigating between sub menu options - if (SubMenu) { - value = getItemValue(LCDNav); - switch (LCDNav) { - case MENU_CAL: - CT1 = MenuNavInt(Buttons, CT1, 60, 999); // range 6.0 - 99.9A - break; - case MENU_MAINSMETER: - do { - value = MenuNavInt(Buttons, value, MenuStr[LCDNav].Min, MenuStr[LCDNav].Max); - } while (value >= EM_UNUSED_SLOT1 && value <= EM_UNUSED_SLOT4); - setItemValue(LCDNav, value); - break; - case MENU_EVMETER: // do not display the Sensorbox or unused slots here - do { - value = MenuNavInt(Buttons, value, MenuStr[LCDNav].Min, MenuStr[LCDNav].Max); - } while (value == EM_SENSORBOX || (value >= EM_UNUSED_SLOT1 && value <= EM_UNUSED_SLOT4)); - setItemValue(LCDNav, value); - break; - case MENU_WIFI: - value = MenuNavInt(Buttons, value, MenuStr[LCDNav].Min, MenuStr[LCDNav].Max); - setItemValue(LCDNav, value); - if (value !=2 ) - handleWIFImode(); //postpone handling WIFImode == 2 to moving to upper line - break; - default: - value = MenuNavInt(Buttons, value, MenuStr[LCDNav].Min, MenuStr[LCDNav].Max); - setItemValue(LCDNav, value); - break; - } - } else { - LCDNav = MenuNavCharArray(Buttons, LCDNav, MenuItems, MenuItemsCount); - } - ButtonRelease = 1; - // Repeat button after 0.5 second - } else if (ButtonRelease == 2 && ButtonRepeat == 0) { - ButtonRepeat = 500; - ButtonTimer = millis() + ButtonRepeat; - } else if (ButtonRepeat && millis() > ButtonTimer) { - ButtonRelease = 0; - if (ButtonRepeat > 1) { - ButtonRepeat -= (ButtonRepeat / 8); - ButtonTimer = millis() + ButtonRepeat; - } - } - } else if (LCDNav > 1 && LCDNav != MENU_OFF && LCDNav != MENU_ON && Buttons == 0x5 && ButtonRelease == 0) { // Button 2 pressed? - ButtonRelease = 1; - if (SubMenu) { // We are currently in Submenu - SubMenu = 0; // Exit Submenu now - if (LCDNav == MENU_CAL && Iuncal) { // Exit CT1 calibration? check if Iuncal is not zero - ICal = ((unsigned long)CT1 * 10 + 5) * ICAL / Iuncal; // Calculate new Calibration value - Irms[0] = CT1; // Set the Irms value, so the LCD update is instant - } - uint8_t WIFImode = getItemValue(MENU_WIFI); - if (LCDNav == MENU_WIFI && WIFImode == 2) - handleWIFImode(); - } else { // We are currently not in Submenu. - SubMenu = 1; // Enter Submenu now - if (LCDNav == MENU_CAL) { // CT1 calibration start - CT1 = (unsigned int) abs(Irms[0]); // make working copy of CT1 value - } else if (LCDNav == MENU_EXIT) { // Exit Main Menu - LCDNav = 0; - SubMenu = 0; - ErrorFlags = NO_ERROR; // Clear All Errors when exiting the Main Menu - TestState = 0; // Clear TestState - ChargeDelay = 0; // Clear ChargeDelay - setSolarStopTimer(0); // Disable Solar Timer - GLCD(); - write_settings(); // Write to eeprom - ButtonRelease = 2; // Skip updating of the LCD - } - } - - } else if (Buttons == 0x7) { // Buttons released - ButtonRelease = 0; - ButtonRepeat = 0; - delay(10); // debounce keys (blocking) - } - - // - // here we update the LCD - // - String HoldStr = ""; - switch (LCDNav) { - case 1: - HoldStr = "for Menu"; - break; - case MENU_OFF: - HoldStr = "to Stop"; - break; - case MENU_ON: - HoldStr = "to Start"; - break; - case MENU_CAL: - if (ButtonRelease == 1) { - GLCD_print_menu(2, MenuStr[LCDNav].LCD); // add navigation arrows on both sides - if (SubMenu) { - sprintf(Str, "%u.%uA", CT1 / 10, CT1 % 10); - } else { - sprintf(Str, "%u.%uA",((unsigned int) abs(Irms[0]) / 10), ((unsigned int) abs(Irms[0]) % 10) ); - } - GLCD_print_menu(4, Str); - } - break; - default: - //so we are anything else then 1, MENU_OFF, MENU_ON - if (ButtonRelease == 1) { - GLCD_print_menu(4, getMenuItemOption(LCDNav)); // print Menu - if (LCDNav != 0) { - GLCD_print_menu(2, MenuStr[LCDNav].LCD); // add navigation arrows on both sides - // Bottom row of the GLCD - GLCD_buffer_clr(); - sprintf(Str, "%i%cC", getItemValue(STATUS_TEMP), 0x0C); // ° Degree symbol - GLCD_write_buf_str(0, 0, Str, GLCD_ALIGN_LEFT); // show the internal temperature - sprintf(Str, "%.19s",(const char *) VERSION); - GLCD_write_buf_str(127, 0, Str, GLCD_ALIGN_RIGHT);// show software version in bottom right corner. - GLCD_sendbuf(7, 1); - } - ButtonRelease = 2; // Set value to 2, so that LCD will be updated only once - } - } - - if (HoldStr != "") { - glcd_clrln(0, 0x00); - glcd_clrln(1, 0x04); // horizontal line - GLCD_print_buf2(2, (const char *) "Hold 2 sec"); - GLCD_print_buf2(4, HoldStr.c_str()); - glcd_clrln(6, 0x10); // horizontal line - glcd_clrln(7, 0x00); - ButtonRelease = 2; // Set value to 2, so that LCD will be updated only once - } - - ScrollTimer = millis(); // reset timer for HelpMenu text - LCDpos = 0; // reset position of scrolling text - OldButtonState = Buttons; - LCDTimer = 0; -} - - -void GLCD_init(void) { - delay(200); // transients on the line could have garbled the LCD, wait 200ms then re-init. - _A0_0; // A0=0 - _RSTB_0; // Reset GLCD module - delayMicroseconds(4); - _RSTB_1; // Reset line high - delayMicroseconds(4); - - st7565_command(0xA2); // (11) set bias at duty cycle 1.65 (0xA2=1.9 0xA3=1.6) - st7565_command(0xA0); // (8) SEG direction (0xA0 or 0xA1) - st7565_command(0xC8); // (15) comm direction normal =0xC0 comm reverse= 0xC8 - - st7565_command(0x20 | 0x04); // (17) set Regulation Ratio (0-7) - - st7565_command(0xF8); // (19) send Booster command - st7565_command(0x01); // set Booster value 00=4x 01=5x - - st7565_command(0x81); // (18) send Electronic Volume command 0x81 - st7565_command(0x24); // set Electronic volume (0x00-0x3f) - - st7565_command(0xA6); // (9) Inverse display (0xA7=inverse 0xA6=normal) - st7565_command(0xA4); // (10) ALL pixel on (A4=normal, A5=all ON) - - st7565_command(0x28 | 0x07); // (16) ALL Power Control ON - - glcd_clear(); // clear internal GLCD buffer - goto_row(0x00); // (3) Set page address - goto_col(0x00); // (4) Set column addr LSB - - st7565_command(0xAF); // (1) ON command - -} - diff --git a/SmartEVSE-3/SmartEVSE-3/src/modbus.cpp b/SmartEVSE-3/SmartEVSE-3/src/modbus.cpp deleted file mode 100644 index 895e489..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/modbus.cpp +++ /dev/null @@ -1,690 +0,0 @@ -/* -; Project: Smart EVSE -; -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. -*/ - -#include -#include -#include -#include "ModbusServerRTU.h" -#include "ModbusClientRTU.h" -#include "driver/uart.h" - -#include "evse.h" -#include "modbus.h" -#include "utils.h" - -extern struct ModBus MB; - -// ########################## Modbus helper functions ########################## - -/** - * Send single value over modbus - * - * @param uint8_t address - * @param uint8_t function - * @param uint16_t register - * @param uint16_t data - */ -void ModbusSend8(uint8_t address, uint8_t function, uint16_t reg, uint16_t data) { - // 0x12345678 is a token to keep track of modbus requests/responses. - // token: first byte address, second byte function, third and fourth reg - uint32_t token; - token = reg; - token += address << 24; - token += function << 16; - Error err = MBclient.addRequest(token, address, function, reg, data); - if (err!=SUCCESS) { - ModbusError e(err); - _LOG_A("Error creating request: 0x%02x - %s\n", (int)e, (const char *)e); - } - else { - _LOG_V("Sent packet"); - } - _LOG_V_NO_FUNC(" address: 0x%02x, function: 0x%02x, reg: 0x%04x, token:0x%08x, data: 0x%04x.\n", address, function, reg, token, data); -} - -/** - * Combine Bytes received over modbus - * - * @param pointer to var - * @param pointer to buf - * @param uint8_t pos - * @param uint8_t endianness:\n - * 0: low byte first, low word first (little endian)\n - * 1: low byte first, high word first\n - * 2: high byte first, low word first\n - * 3: high byte first, high word first (big endian) - * @param MBDataType dataType: used to determine how many bytes should be combined - */ -void combineBytes(void *var, uint8_t *buf, uint8_t pos, uint8_t endianness, MBDataType dataType) { - char *pBytes; - pBytes = (char *)var; - - // ESP32 is little endian - switch(endianness) { - case ENDIANESS_LBF_LWF: // low byte first, low word first (little endian) - *pBytes++ = (uint8_t)buf[pos + 0]; - *pBytes++ = (uint8_t)buf[pos + 1]; - if (dataType != MB_DATATYPE_INT16) { - *pBytes++ = (uint8_t)buf[pos + 2]; - *pBytes = (uint8_t)buf[pos + 3]; - } - break; - case ENDIANESS_LBF_HWF: // low byte first, high word first - if (dataType != MB_DATATYPE_INT16) { - *pBytes++ = (uint8_t)buf[pos + 2]; - *pBytes++ = (uint8_t)buf[pos + 3]; - } - *pBytes++ = (uint8_t)buf[pos + 0]; - *pBytes = (uint8_t)buf[pos + 1]; - break; - case ENDIANESS_HBF_LWF: // high byte first, low word first - *pBytes++ = (uint8_t)buf[pos + 1]; - *pBytes++ = (uint8_t)buf[pos + 0]; - if (dataType != MB_DATATYPE_INT16) { - *pBytes++ = (uint8_t)buf[pos + 3]; - *pBytes = (uint8_t)buf[pos + 2]; - } - break; - case ENDIANESS_HBF_HWF: // high byte first, high word first (big endian) - if (dataType != MB_DATATYPE_INT16) { - *pBytes++ = (uint8_t)buf[pos + 3]; - *pBytes++ = (uint8_t)buf[pos + 2]; - } - *pBytes++ = (uint8_t)buf[pos + 1]; - *pBytes = (uint8_t)buf[pos + 0]; - break; - default: - break; - } -} - - - -// ########################### Modbus main functions ########################### - - - -/** - * Request read holding (FC=3) or read input register (FC=04) to a device over modbus - * - * @param uint8_t address - * @param uint8_t function - * @param uint16_t register - * @param uint16_t quantity - */ -void ModbusReadInputRequest(uint8_t address, uint8_t function, uint16_t reg, uint16_t quantity) { - MB.RequestAddress = address; - MB.RequestFunction = function; - MB.RequestRegister = reg; - ModbusSend8(address, function, reg, quantity); -} - - -/** - * Response read holding (FC=3) or read input register (FC=04) to a device over modbus - * - * @param uint8_t address - * @param uint8_t function - * @param uint16_t pointer to values - * @param uint8_t count of values - */ -void ModbusReadInputResponse(uint8_t address, uint8_t function, uint16_t *values, uint8_t count) { - _LOG_A("ModbusReadInputResponse, to do!\n"); - //ModbusSend(address, function, count * 2u, values, count); -} - -/** - * Request write single register (FC=06) to a device over modbus - * - * @param uint8_t address - * @param uint16_t register - * @param uint16_t value - */ -void ModbusWriteSingleRequest(uint8_t address, uint16_t reg, uint16_t value) { - MB.RequestAddress = address; - MB.RequestFunction = 0x06; - MB.RequestRegister = reg; - ModbusSend8(address, 0x06, reg, value); -} - -/** - * Request write multiple register (FC=16) to a device over modbus - * - * @param uint8_t address - * @param uint16_t register - * @param uint8_t pointer to data - * @param uint8_t count of data - */ -void ModbusWriteMultipleRequest(uint8_t address, uint16_t reg, uint16_t *values, uint8_t count) { - - MB.RequestAddress = address; - MB.RequestFunction = 0x10; - MB.RequestRegister = reg; - // 0x12345678 is a token to keep track of modbus requests/responses. - // token: first byte address, second byte function, third and fourth reg - uint32_t token; - token = reg; - token += address << 24; - token += 0x10 << 16; - Error err = MBclient.addRequest(token, address, 0x10, reg, (uint16_t) count, count * 2u, values); - if (err!=SUCCESS) { - ModbusError e(err); - _LOG_A("Error creating request: 0x%02x - %s\n", (int)e, (const char *)e); - } - _LOG_V("Sent packet address: 0x%02x, function: 0x10, reg: 0x%04x, token: 0x%08x count: %u, values:", address, reg, token, count); - for (uint16_t i = 0; i < count; i++) { - _LOG_V_NO_FUNC(" %04x", values[i]); - } - _LOG_V_NO_FUNC("\n"); -} - -/** - * Response an exception - * - * @param uint8_t address - * @param uint8_t function - * @param uint8_t exeption - */ -void ModbusException(uint8_t address, uint8_t function, uint8_t exception) { - //uint16_t temp[1]; - _LOG_A("ModbusException, to do!\n"); - //ModbusSend(address, function, exception, temp, 0); -} - -/** - * Decode received modbus packet - * - * @param uint8_t pointer to buffer - * @param uint8_t length of buffer - */ -void ModbusDecode(uint8_t * buf, uint8_t len) { - // Clear old values - MB.Address = 0; - MB.Function = 0; - MB.Register = 0; - MB.RegisterCount = 0; - MB.Value = 0; - MB.DataLength = 0; - MB.Type = MODBUS_INVALID; - MB.Exception = 0; - - _LOG_V("Received packet (%i bytes)", len); - for (uint8_t x=0; x= 6) { - // Modbus device address - MB.Address = buf[0]; - // Modbus function - MB.Function = buf[1]; - - _LOG_V(" valid Modbus packet: Address 0x%02x Function 0x%02x", MB.Address, MB.Function); - switch (MB.Function) { - case 0x03: // (Read holding register) - case 0x04: // (Read input register) - if (len == 6) { - // request packet - MB.Type = MODBUS_REQUEST; - // Modbus register - MB.Register = (uint16_t)(buf[2] <<8) | buf[3]; - // Modbus register count - MB.RegisterCount = (uint16_t)(buf[4] <<8) | buf[5]; - } else { - // Modbus datacount - MB.DataLength = buf[2]; - if (MB.DataLength == len - 3) { - // packet length OK - // response packet - MB.Type = MODBUS_RESPONSE; - } else { - _LOG_W("Invalid modbus FC=04 packet\n"); - } - } - break; - case 0x06: - // (Write single register) - if (len == 6) { - // request and response packet are the same - MB.Type = MODBUS_OK; - // Modbus register - MB.Register = (uint16_t)(buf[2] <<8) | buf[3]; - // Modbus register count - MB.RegisterCount = 1; - // value - MB.Value = (uint16_t)(buf[4] <<8) | buf[5]; - } else { - _LOG_W("Invalid modbus FC=06 packet\n"); - } - break; - case 0x10: - // (Write multiple register)) - // Modbus register - MB.Register = (uint16_t)(buf[2] <<8) | buf[3]; - // Modbus register count - MB.RegisterCount = (uint16_t)(buf[4] <<8) | buf[5]; - if (len == 6) { - // response packet - MB.Type = MODBUS_RESPONSE; - } else { - // Modbus datacount - MB.DataLength = buf[6]; - if (MB.DataLength == len - 7) { - // packet length OK - // request packet - MB.Type = MODBUS_REQUEST; - } else { - _LOG_W("Invalid modbus FC=16 packet\n"); - } - } - break; - default: - break; - } - - // MB.Data - if (MB.Type && MB.DataLength) { - // Set pointer to Data - MB.Data = buf; - // Modbus data is always at the end ahead the checksum - MB.Data = MB.Data + (len - MB.DataLength); - } - - // Request - Response check - switch (MB.Type) { - case MODBUS_REQUEST: - MB.RequestAddress = MB.Address; - MB.RequestFunction = MB.Function; - MB.RequestRegister = MB.Register; - break; - case MODBUS_RESPONSE: - // If address and function identical with last send or received request, it is a valid response - if (MB.Address == MB.RequestAddress && MB.Function == MB.RequestFunction) { - if (MB.Function == 0x03 || MB.Function == 0x04) - MB.Register = MB.RequestRegister; - } - MB.RequestAddress = 0; - MB.RequestFunction = 0; - MB.RequestRegister = 0; - break; - case MODBUS_OK: - // If address and function identical with last send or received request, it is a valid response - if (MB.Address == MB.RequestAddress && MB.Function == MB.RequestFunction && MB.Address != BROADCAST_ADR) { - MB.Type = MODBUS_RESPONSE; - MB.RequestAddress = 0; - MB.RequestFunction = 0; - MB.RequestRegister = 0; - } else { - MB.Type = MODBUS_REQUEST; - MB.RequestAddress = MB.Address; - MB.RequestFunction = MB.Function; - MB.RequestRegister = MB.Register; - } - default: - break; - } - } - if(MB.Type) { - _LOG_V_NO_FUNC(" Register 0x%04x", MB.Register); - } - switch (MB.Type) { - case MODBUS_REQUEST: - _LOG_V_NO_FUNC(" Request\n"); - break; - case MODBUS_RESPONSE: - _LOG_V_NO_FUNC(" Response\n"); - break; - } -} - - - -// ########################### EVSE modbus functions ########################### - - -/** - * Send measurement request over modbus - * - * @param uint8_t Meter - * @param uint8_t Address - * @param uint16_t Register - * @param uint8_t Count - */ -void requestMeasurement(uint8_t Meter, uint8_t Address, uint16_t Register, uint8_t Count) { - ModbusReadInputRequest(Address, EMConfig[Meter].Function, Register, (EMConfig[Meter].DataType == MB_DATATYPE_INT16 ? Count : (Count * 2u))); -} - -/** - * Decode measurement value - * - * @param pointer to buf - * @param uint8_t Count - * @param uint8_t Endianness - * @param MBDataType dataType - * @param signed char Divisor - * @return signed int Measurement - */ -signed int receiveMeasurement(uint8_t *buf, uint8_t Count, uint8_t Endianness, MBDataType dataType, signed char Divisor) { - float dCombined; - signed int lCombined; - - if (dataType == MB_DATATYPE_FLOAT32) { - combineBytes(&dCombined, buf, Count * (dataType == MB_DATATYPE_INT16 ? 2u : 4u), Endianness, dataType); - if (Divisor >= 0) { - lCombined = (signed int)(dCombined / (signed int)pow_10[(unsigned)Divisor]); - } else { - lCombined = (signed int)(dCombined * (signed int)pow_10[(unsigned)-Divisor]); - } - } else { - combineBytes(&lCombined, buf, Count * (dataType == MB_DATATYPE_INT16 ? 2u : 4u), Endianness, dataType); - if (dataType == MB_DATATYPE_INT16) { - lCombined = (signed int)((int16_t)lCombined); /* sign extend 16bit into 32bit */ - } - if (Divisor >= 0) { - lCombined = lCombined / (signed int)pow_10[(unsigned)Divisor]; - } else { - lCombined = lCombined * (signed int)pow_10[(unsigned)-Divisor]; - } - } - - return lCombined; -} - -/** - * Send current measurement request over modbus - * - * @param uint8_t Meter - * @param uint8_t Address - */ -void requestCurrentMeasurement(uint8_t Meter, uint8_t Address) { - switch(Meter) { - case EM_API: - break; - case EM_SENSORBOX: - ModbusReadInputRequest(Address, 4, 0, 20); - break; - case EM_EASTRON1P: - case EM_EASTRON3P: - case EM_EASTRON3P_INV: - // Phase 1-3 current: Register 0x06 - 0x0B (unsigned) - // Phase 1-3 power: Register 0x0C - 0x11 (signed) - ModbusReadInputRequest(Address, 4, 0x06, 12); - break; - case EM_ABB: - // Phase 1-3 current: Register 0x5B0C - 0x5B11 (unsigned) - // Phase 1-3 power: Register 0x5B16 - 0x5B1B (signed) - ModbusReadInputRequest(Address, 3, 0x5B0C, 16); - break; - case EM_SOLAREDGE: - // Read 3 Current values + scaling factor - ModbusReadInputRequest(Address, EMConfig[Meter].Function, EMConfig[Meter].IRegister, 4); - break; - case EM_FINDER_7M: - // Phase 1-3 current: Register 2516 - 2521 (unsigned) - // Phase 1-3 power: Register 2530 - 2535 (signed) - ModbusReadInputRequest(Address, 4, 2516, 20); - break; - default: - // Read 3 Current values - requestMeasurement(Meter, Address, EMConfig[Meter].IRegister, 3); - break; - } -} - -/** - * Read current measurement from modbus - * - * @param pointer to buf - * @param uint8_t Meter - * @param pointer to Current (mA) - * @return uint8_t error - */ -uint8_t receiveCurrentMeasurement(uint8_t *buf, uint8_t Meter, signed int *var) { - uint8_t x, offset; - - // No CAL option in Menu - CalActive = 0; - - switch(Meter) { - case EM_API: - break; - case EM_SENSORBOX: - // return immediately if the data contains no new P1 or CT measurement - if (buf[3] == 0) return 0; // error!! - // determine if there is P1 data present, otherwise use CT data - if (buf[3] & 0x80) offset = 4; // P1 data present - else offset = 7; // Use CTs - // offset 16 is Smart meter P1 current - for (x = 0; x < 3; x++) { - // SmartEVSE works with Amps * 10 - var[x] = receiveMeasurement(buf, offset + x, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, EMConfig[Meter].IDivisor - 3u); - // When using CT's , adjust the measurements with calibration value - if (offset == 7) { - if (x == 0) Iuncal = abs((var[x] / 10)); // Store uncalibrated CT1 measurement (10mA) - var[x] = var[x] * (signed int)ICal / ICAL; - // When MaxMains is set to >100A, it's assumed 200A:50ma CT's are used. - if (getItemValue(MENU_MAINS) > 100) var[x] = var[x] * 2; // Multiply measured currents with 2 - // very small negative currents are shown as zero. - if ((var[x] > -1) && (var[x] < 1)) var[x] = 0; - CalActive = 1; // Enable CAL option in Menu - } - } - // Set Sensorbox 2 to 3/4 Wire configuration (and phase Rotation) (v2.16) - if (buf[1] >= 0x10 && offset == 7) { - GridActive = 1; // Enable the GRID menu option - if ((buf[1] & 0x3) != (Grid << 1) && (LoadBl < 2)) ModbusWriteSingleRequest(0x0A, 0x800, Grid << 1); - } else GridActive = 0; - break; - case EM_SOLAREDGE: - { - // Need to handle the extra scaling factor - int scalingFactor = -(int)receiveMeasurement( - buf, - 3, - EMConfig[Meter].Endianness, - EMConfig[Meter].DataType, - 0 - ); - // Now decode the three Current values using that scaling factor - for (x = 0; x < 3; x++) { - var[x] = receiveMeasurement( - buf, - x, - EMConfig[Meter].Endianness, - EMConfig[Meter].DataType, - scalingFactor - 3 - ); - } - break; - } - default: - for (x = 0; x < 3; x++) { - var[x] = receiveMeasurement( - buf, - x, - EMConfig[Meter].Endianness, - EMConfig[Meter].DataType, - EMConfig[Meter].IDivisor - 3 - ); - } - break; - } - - // Get sign from power measurement on some electric meters - switch(Meter) { - case EM_EASTRON1P: // for some reason the EASTRON1P also needs to loop through the 3 var[x] - // if you only loop through x=0, the minus sign of the current is incorrect - // when exporting current - case EM_EASTRON3P: - for (x = 0; x < 3; x++) { - if (receiveMeasurement(buf, x + 3u, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, EMConfig[Meter].PDivisor) < 0) var[x] = -var[x]; - } - break; - case EM_EASTRON3P_INV: - for (x = 0; x < 3; x++) { - if (receiveMeasurement(buf, x + 3u, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, EMConfig[Meter].PDivisor) > 0) var[x] = -var[x]; - } - break; - case EM_ABB: - for (x = 0; x < 3; x++) { - if (receiveMeasurement(buf, x + 5u, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, EMConfig[Meter].PDivisor) < 0) var[x] = -var[x]; - } - break; - case EM_FINDER_7M: - for (x = 0; x < 3; x++) { - if (receiveMeasurement(buf, x + 7u, EMConfig[Meter].Endianness, EMConfig[Meter].DataType, EMConfig[Meter].PDivisor) < 0) var[x] = -var[x]; - } - break; - } - - // all OK - return 1; -} - -/** - * Map a Modbus register to an item ID (MENU_xxx or STATUS_xxx) - * - * @return uint8_t ItemID - */ -uint8_t mapModbusRegister2ItemID() { - uint16_t RegisterStart, ItemStart, Count; - - // Register 0x00*: Status - if (MB.Register < (MODBUS_EVSE_STATUS_START + MODBUS_EVSE_STATUS_COUNT)) { - RegisterStart = MODBUS_EVSE_STATUS_START; - ItemStart = STATUS_STATE; - Count = MODBUS_EVSE_STATUS_COUNT; - - // Register 0x01*: Node specific configuration - } else if (MB.Register >= MODBUS_EVSE_CONFIG_START && MB.Register < (MODBUS_EVSE_CONFIG_START + MODBUS_EVSE_CONFIG_COUNT)) { - RegisterStart = MODBUS_EVSE_CONFIG_START; - ItemStart = MENU_CONFIG; - Count = MODBUS_EVSE_CONFIG_COUNT; - - // Register 0x02*: System configuration (same on all SmartEVSE in a LoadBalancing setup) - } else if (MB.Register >= MODBUS_SYS_CONFIG_START && MB.Register < (MODBUS_SYS_CONFIG_START + MODBUS_SYS_CONFIG_COUNT)) { - RegisterStart = MODBUS_SYS_CONFIG_START; - ItemStart = MENU_MODE; - Count = MODBUS_SYS_CONFIG_COUNT; - - } else { - return 0; - } - - if (MB.RegisterCount <= (RegisterStart + Count) - MB.Register) { - return (MB.Register - RegisterStart + ItemStart); - } else { - return 0; - } -} - -/** - * Read item values and send modbus response - */ -/* -void ReadItemValueResponse(void) { - uint8_t ItemID; - uint8_t i; - uint16_t values[MODBUS_MAX_REGISTER_READ]; - - ItemID = mapModbusRegister2ItemID(); - if (ItemID) { - for (i = 0; i < MB.RegisterCount; i++) { - values[i] = getItemValue(ItemID + i); - } - ModbusReadInputResponse(MB.Address, MB.Function, values, MB.RegisterCount); - } else { - ModbusException(MB.Address, MB.Function, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS); - } -} -*/ - -/** - * Write item values and send modbus response - */ -/* -void WriteItemValueResponse(void) { - uint8_t ItemID; - uint8_t OK = 0; - - ItemID = mapModbusRegister2ItemID(); - if (ItemID) { - OK = setItemValue(ItemID, MB.Value); - } - - if (OK && ItemID < STATUS_STATE) write_settings(); - - if (MB.Address != BROADCAST_ADR || LoadBl == 0) { - if (!ItemID) { - ModbusException(MB.Address, MB.Function, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS); - } else if (!OK) { - ModbusException(MB.Address, MB.Function, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE); - } else { - ModbusWriteSingleResponse(MB.Address, MB.Register, MB.Value); - } - } -} -*/ - -/** - * Write multiple item values and send modbus response - */ -/* -void WriteMultipleItemValueResponse(void) { - uint8_t ItemID; - uint16_t i, OK = 0, value; - - ItemID = mapModbusRegister2ItemID(); - if (ItemID) { - for (i = 0; i < MB.RegisterCount; i++) { - value = (MB.Data[i * 2] <<8) | MB.Data[(i * 2) + 1]; - OK += setItemValue(ItemID + i, value); - } - } - - if (OK && ItemID < STATUS_STATE) write_settings(); - - if (MB.Address != BROADCAST_ADR || LoadBl == 0) { - if (!ItemID) { - ModbusException(MB.Address, MB.Function, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS); - } else if (!OK) { - ModbusException(MB.Address, MB.Function, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE); - } else { - ModbusWriteMultipleResponse(MB.Address, MB.Register, OK); - } - } -} -*/ diff --git a/SmartEVSE-3/SmartEVSE-3/src/mongoose.c b/SmartEVSE-3/SmartEVSE-3/src/mongoose.c deleted file mode 100644 index 62fde56..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/mongoose.c +++ /dev/null @@ -1,15692 +0,0 @@ -// Copyright (c) 2004-2013 Sergey Lyubka -// Copyright (c) 2013-2024 Cesanta Software Limited -// All rights reserved -// -// This software is dual-licensed: you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 2 as -// published by the Free Software Foundation. For the terms of this -// license, see http://www.gnu.org/licenses/ -// -// You are free to use this software under the terms of the GNU General -// Public License, but WITHOUT ANY WARRANTY; without even the implied -// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// Alternatively, you can license this software under a commercial -// license, as set out in https://www.mongoose.ws/licensing/ -// -// SPDX-License-Identifier: GPL-2.0-only or commercial - -#include "mongoose.h" - -#ifdef MG_ENABLE_LINES -#line 1 "src/base64.c" -#endif - - -static int mg_base64_encode_single(int c) { - if (c < 26) { - return c + 'A'; - } else if (c < 52) { - return c - 26 + 'a'; - } else if (c < 62) { - return c - 52 + '0'; - } else { - return c == 62 ? '+' : '/'; - } -} - -static int mg_base64_decode_single(int c) { - if (c >= 'A' && c <= 'Z') { - return c - 'A'; - } else if (c >= 'a' && c <= 'z') { - return c + 26 - 'a'; - } else if (c >= '0' && c <= '9') { - return c + 52 - '0'; - } else if (c == '+') { - return 62; - } else if (c == '/') { - return 63; - } else if (c == '=') { - return 64; - } else { - return -1; - } -} - -size_t mg_base64_update(unsigned char ch, char *to, size_t n) { - unsigned long rem = (n & 3) % 3; - if (rem == 0) { - to[n] = (char) mg_base64_encode_single(ch >> 2); - to[++n] = (char) ((ch & 3) << 4); - } else if (rem == 1) { - to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4)); - to[++n] = (char) ((ch & 15) << 2); - } else { - to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6)); - to[++n] = (char) mg_base64_encode_single(ch & 63); - n++; - } - return n; -} - -size_t mg_base64_final(char *to, size_t n) { - size_t saved = n; - // printf("---[%.*s]\n", n, to); - if (n & 3) n = mg_base64_update(0, to, n); - if ((saved & 3) == 2) n--; - // printf(" %d[%.*s]\n", n, n, to); - while (n & 3) to[n++] = '='; - to[n] = '\0'; - return n; -} - -size_t mg_base64_encode(const unsigned char *p, size_t n, char *to, size_t dl) { - size_t i, len = 0; - if (dl > 0) to[0] = '\0'; - if (dl < ((n / 3) + (n % 3 ? 1 : 0)) * 4 + 1) return 0; - for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len); - len = mg_base64_final(to, len); - return len; -} - -size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t dl) { - const char *end = src == NULL ? NULL : src + n; // Cannot add to NULL - size_t len = 0; - if (dl < n / 4 * 3 + 1) goto fail; - while (src != NULL && src + 3 < end) { - int a = mg_base64_decode_single(src[0]), - b = mg_base64_decode_single(src[1]), - c = mg_base64_decode_single(src[2]), - d = mg_base64_decode_single(src[3]); - if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) { - goto fail; - } - dst[len++] = (char) ((a << 2) | (b >> 4)); - if (src[2] != '=') { - dst[len++] = (char) ((b << 4) | (c >> 2)); - if (src[3] != '=') dst[len++] = (char) ((c << 6) | d); - } - src += 4; - } - dst[len] = '\0'; - return len; -fail: - if (dl > 0) dst[0] = '\0'; - return 0; -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_ch32v307.c" -#endif - - - -#if MG_DEVICE == MG_DEVICE_CH32V307 -// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html - -#define FLASH_BASE 0x40022000 -#define FLASH_ACTLR (FLASH_BASE + 0) -#define FLASH_KEYR (FLASH_BASE + 4) -#define FLASH_OBKEYR (FLASH_BASE + 8) -#define FLASH_STATR (FLASH_BASE + 12) -#define FLASH_CTLR (FLASH_BASE + 16) -#define FLASH_ADDR (FLASH_BASE + 20) -#define FLASH_OBR (FLASH_BASE + 28) -#define FLASH_WPR (FLASH_BASE + 32) - -void *mg_flash_start(void) { - return (void *) 0x08000000; -} -size_t mg_flash_size(void) { - return 480 * 1024; // First 320k is 0-wait -} -size_t mg_flash_sector_size(void) { - return 4096; -} -size_t mg_flash_write_align(void) { - return 4; -} -int mg_flash_bank(void) { - return 0; -} -void mg_device_reset(void) { - *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() -} -static void flash_unlock(void) { - static bool unlocked; - if (unlocked == false) { - MG_REG(FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_KEYR) = 0xcdef89ab; - unlocked = true; - } -} -static void flash_wait(void) { - while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; -} - -bool mg_flash_erase(void *addr) { - //MG_INFO(("%p", addr)); - flash_unlock(); - flash_wait(); - MG_REG(FLASH_ADDR) = (uint32_t) addr; - MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT; - flash_wait(); - return true; -} - -static bool is_page_boundary(const void *addr) { - uint32_t val = (uint32_t) addr; - return (val & (mg_flash_sector_size() - 1)) == 0; -} - -bool mg_flash_write(void *addr, const void *buf, size_t len) { - //MG_INFO(("%p %p %lu", addr, buf, len)); - //mg_hexdump(buf, len); - flash_unlock(); - const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2]; - uint16_t *dst = (uint16_t *) addr; - MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG - //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR))); - while (src < end) { - if (is_page_boundary(dst)) mg_flash_erase(dst); - *dst++ = *src++; - flash_wait(); - } - MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG - return true; -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_dummy.c" -#endif - - -#if MG_DEVICE == MG_DEVICE_NONE -void *mg_flash_start(void) { - return NULL; -} -size_t mg_flash_size(void) { - return 0; -} -size_t mg_flash_sector_size(void) { - return 0; -} -size_t mg_flash_write_align(void) { - return 0; -} -int mg_flash_bank(void) { - return 0; -} -bool mg_flash_erase(void *location) { - (void) location; - return false; -} -bool mg_flash_swap_bank(void) { - return true; -} -bool mg_flash_write(void *addr, const void *buf, size_t len) { - (void) addr, (void) buf, (void) len; - return false; -} -void mg_device_reset(void) { -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_flash.c" -#endif - - -#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \ - MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 -// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1) -// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an -// object, we pad it at the end for alignment. -// -// Objects in the flash sector are stored sequentially: -// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ...... -// -// In order to get to the next object, read its size, then align up. - -// Traverse the list of saved objects -size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) { - size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p; - uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2; - if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) { - if (size) *size = (size_t) p32[0]; - if (key) *key = p32[1]; - aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align); - if (left < aligned_size) aligned_size = 0; // Out of bounds, fail - } - return aligned_size; -} - -// Return the last sector of Bank 2 -static char *flash_last_sector(void) { - size_t ss = mg_flash_sector_size(), size = mg_flash_size(); - char *base = (char *) mg_flash_start(), *last = base + size - ss; - if (mg_flash_bank() == 2) last -= size / 2; - return last; -} - -// Find a saved object with a given key -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { - char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL; - size_t ss = mg_flash_sector_size(), ofs = 0, n, sz; - bool ok = false; - if (s == NULL) s = flash_last_sector(); - if (s < base || s >= base + mg_flash_size()) { - MG_ERROR(("%p is outsize of flash", sector)); - } else if (((s - base) % ss) != 0) { - MG_ERROR(("%p is not a sector boundary", sector)); - } else { - uint32_t k, scanned = 0; - while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) { - // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key)); - // mg_hexdump(s + ofs, n); - if (k == key && sz == len) { - res = s + ofs + sizeof(uint32_t) * 2; - memcpy(buf, res, len); // Copy object - ok = true; // Keep scanning for the newer versions of it - } - ofs += n, scanned++; - } - MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res)); - } - return ok; -} - -// For all saved objects in the sector, delete old versions of objects -static void mg_flash_sector_cleanup(char *sector) { - // Buffer all saved objects into an IO buffer (backed by RAM) - // erase sector, and re-save them. - struct mg_iobuf io = {0, 0, 0, 2048}; - size_t ss = mg_flash_sector_size(); - size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2; - uint32_t key; - // Traverse all objects - MG_DEBUG(("Cleaning up sector %p", sector)); - while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) { - // Delete an old copy of this object in the cache - for (size_t o = 0; o < io.len; o += size2 + hs) { - uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t)); - size2 = *(uint32_t *) (io.buf + o); - if (k == key) { - mg_iobuf_del(&io, o, size2 + hs); - break; - } - } - // And add the new copy - mg_iobuf_add(&io, io.len, sector + ofs, size + hs); - ofs += n; - } - // All objects are cached in RAM now - if (mg_flash_erase(sector)) { // Erase sector. If successful, - for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached objects - size = *(uint32_t *) (io.buf + ofs); - key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t)); - mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash - } - } - mg_iobuf_free(&io); -} - -// Save an object with a given key - append to the end of an object list -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { - char *base = (char *) mg_flash_start(), *s = (char *) sector; - size_t ss = mg_flash_sector_size(), ofs = 0, n; - bool ok = false; - if (s == NULL) s = flash_last_sector(); - if (s < base || s >= base + mg_flash_size()) { - MG_ERROR(("%p is outsize of flash", sector)); - } else if (((s - base) % ss) != 0) { - MG_ERROR(("%p is not a sector boundary", sector)); - } else { - char ab[mg_flash_write_align()]; // Aligned write block - uint32_t hdr[2] = {(uint32_t) len, key}; - size_t needed = sizeof(hdr) + len; - size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab)); - while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; - - // If there is not enough space left, cleanup sector and re-eval ofs - if (ofs + needed_aligned >= ss) { - mg_flash_sector_cleanup(s); - ofs = 0; - while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; - } - - if (ofs + needed_aligned <= ss) { - // Enough space to save this object - if (sizeof(ab) < sizeof(hdr)) { - // Flash write granularity is 32 bit or less, write with no buffering - ok = mg_flash_write(s + ofs, hdr, sizeof(hdr)); - if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len); - } else { - // Flash granularity is sizeof(hdr) or more. We need to save in - // 3 chunks: initial block, bulk, rest. This is because we have - // two memory chunks to write: hdr and buf, on aligned boundaries. - n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write - if (n > len) n = len; // is - memset(ab, 0xff, sizeof(ab)); // initialized to all-one - memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size) - memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf - MG_INFO(("saving initial block of %lu", sizeof(ab))); - ok = mg_flash_write(s + ofs, ab, sizeof(ab)); - if (ok && len > n) { - size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab)); - if (n2 > 0) { - MG_INFO(("saving bulk, %lu", n2)); - ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2); - } - if (ok && len > n) { - size_t n3 = len - n - n2; - if (n3 > sizeof(ab)) n3 = sizeof(ab); - memset(ab, 0xff, sizeof(ab)); - memcpy(ab, (char *) buf + n + n2, n3); - MG_INFO(("saving rest, %lu", n3)); - ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab)); - } - } - } - MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned, - s + ofs, key, ok)); - MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); - } else { - MG_ERROR(("Sector is full")); - } - } - return ok; -} -#else -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { - (void) sector, (void) key, (void) buf, (void) len; - return false; -} -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { - (void) sector, (void) key, (void) buf, (void) len; - return false; -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_imxrt.c" -#endif - - - -#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 - -struct mg_flexspi_lut_seq { - uint8_t seqNum; - uint8_t seqId; - uint16_t reserved; -}; - -struct mg_flexspi_mem_config { - uint32_t tag; - uint32_t version; - uint32_t reserved0; - uint8_t readSampleClkSrc; - uint8_t csHoldTime; - uint8_t csSetupTime; - uint8_t columnAddressWidth; - uint8_t deviceModeCfgEnable; - uint8_t deviceModeType; - uint16_t waitTimeCfgCommands; - struct mg_flexspi_lut_seq deviceModeSeq; - uint32_t deviceModeArg; - uint8_t configCmdEnable; - uint8_t configModeType[3]; - struct mg_flexspi_lut_seq configCmdSeqs[3]; - uint32_t reserved1; - uint32_t configCmdArgs[3]; - uint32_t reserved2; - uint32_t controllerMiscOption; - uint8_t deviceType; - uint8_t sflashPadType; - uint8_t serialClkFreq; - uint8_t lutCustomSeqEnable; - uint32_t reserved3[2]; - uint32_t sflashA1Size; - uint32_t sflashA2Size; - uint32_t sflashB1Size; - uint32_t sflashB2Size; - uint32_t csPadSettingOverride; - uint32_t sclkPadSettingOverride; - uint32_t dataPadSettingOverride; - uint32_t dqsPadSettingOverride; - uint32_t timeoutInMs; - uint32_t commandInterval; - uint16_t dataValidTime[2]; - uint16_t busyOffset; - uint16_t busyBitPolarity; - uint32_t lookupTable[64]; - struct mg_flexspi_lut_seq lutCustomSeq[12]; - uint32_t reserved4[4]; -}; - -struct mg_flexspi_nor_config { - struct mg_flexspi_mem_config memConfig; - uint32_t pageSize; - uint32_t sectorSize; - uint8_t ipcmdSerialClkFreq; - uint8_t isUniformBlockSize; - uint8_t reserved0[2]; - uint8_t serialNorType; - uint8_t needExitNoCmdMode; - uint8_t halfClkForNonReadCmd; - uint8_t needRestoreNoCmdMode; - uint32_t blockSize; - uint32_t reserve2[11]; -}; - -/* FLEXSPI memory config block related defintions */ -#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian -#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 - -#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ - (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \ - MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) - -#define MG_CMD_SDR 0x01 -#define MG_CMD_DDR 0x21 -#define MG_DUMMY_SDR 0x0C -#define MG_DUMMY_DDR 0x2C -#define MG_RADDR_SDR 0x02 -#define MG_RADDR_DDR 0x22 -#define MG_READ_SDR 0x09 -#define MG_READ_DDR 0x29 -#define MG_WRITE_SDR 0x08 -#define MG_WRITE_DDR 0x28 -#define MG_STOP 0 - -#define MG_FLEXSPI_1PAD 0 -#define MG_FLEXSPI_2PAD 1 -#define MG_FLEXSPI_4PAD 2 -#define MG_FLEXSPI_8PAD 3 - -#define MG_FLEXSPI_QSPI_LUT \ - { \ - [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, MG_FLEXSPI_4PAD, \ - 0x18), \ - [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, MG_FLEXSPI_4PAD, \ - 0x04), \ - [4 * 1 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \ - [4 * 3 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 9 + 1] = \ - MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - [4 * 11 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - } - -#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU) -#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U) -#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U) -#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U) -#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U) -#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U) - -#define FLEXSPI_NOR_INSTANCE 0 - -#if MG_DEVICE == MG_DEVICE_RT1020 -struct mg_flexspi_nor_driver_interface { - uint32_t version; - int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, - const uint32_t *src); - uint32_t reserved; - int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, - uint32_t lengthInBytes); - uint32_t reserved2; - int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, - uint32_t seqNumber); - int (*xfer)(uint32_t instance, char *xfer); - void (*clear_cache)(uint32_t instance); -}; -#elif MG_DEVICE == MG_DEVICE_RT1060 -struct mg_flexspi_nor_driver_interface { - uint32_t version; - int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, - const uint32_t *src); - int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, - uint32_t lengthInBytes); - int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *dst, uint32_t addr, - uint32_t lengthInBytes); - void (*clear_cache)(uint32_t instance); - int (*xfer)(uint32_t instance, char *xfer); - int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, - uint32_t seqNumber); - int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *option); -}; -#endif - -#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \ - (*(uint32_t*)0x0020001c + 16))) - -static bool s_flash_irq_disabled; - -MG_IRAM void *mg_flash_start(void) { - return (void *) 0x60000000; -} -MG_IRAM size_t mg_flash_size(void) { - return 8 * 1024 * 1024; -} -MG_IRAM size_t mg_flash_sector_size(void) { - return 4 * 1024; // 4k -} -MG_IRAM size_t mg_flash_write_align(void) { - return 256; -} -MG_IRAM int mg_flash_bank(void) { - return 0; -} - -MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; -} - -// Note: the get_config function below works both for RT1020 and 1060 -#if MG_DEVICE == MG_DEVICE_RT1020 -MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { - struct mg_flexspi_nor_config default_config = { - .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG, - .version = MG_FLEXSPI_CFG_BLK_VERSION, - .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad - .csHoldTime = 3, - .csSetupTime = 3, - .controllerMiscOption = MG_BIT(4), - .deviceType = 1, // serial NOR - .sflashPadType = 4, - .serialClkFreq = 7, // 133MHz - .sflashA1Size = 8 * 1024 * 1024, - .lookupTable = MG_FLEXSPI_QSPI_LUT}, - .pageSize = 256, - .sectorSize = 4 * 1024, - .ipcmdSerialClkFreq = 1, - .blockSize = 64 * 1024, - .isUniformBlockSize = false}; - - *config = default_config; - return 0; -} -#else -MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { - uint32_t options[] = {0xc0000000, 0x00}; - - MG_ARM_DISABLE_IRQ(); - uint32_t status = - flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options); - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); - } - if (status) { - MG_ERROR(("Failed to extract flash configuration: status %u", status)); - } - return status; -} -#endif - -MG_IRAM bool mg_flash_erase(void *addr) { - struct mg_flexspi_nor_config config; - if (flexspi_nor_get_config(&config) != 0) { - return false; - } - if (flash_page_start(addr) == false) { - MG_ERROR(("%p is not on a sector boundary", addr)); - return false; - } - - void *dst = (void *)((char *) addr - (char *) mg_flash_start()); - - // Note: Interrupts must be disabled before any call to the ROM API on RT1020 - // and 1060 - MG_ARM_DISABLE_IRQ(); - bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst, - mg_flash_sector_size()) == 0); - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); // Reenable them after the call - } - MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); - return ok; -} - -MG_IRAM bool mg_flash_swap_bank() { - return true; -} - -static inline void spin(volatile uint32_t count) { - while (count--) (void) 0; -} - -static inline void flash_wait(void) { - while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) & MG_BIT(1)) == 0) - spin(1); -} - -MG_IRAM static void *flash_code_location(void) { - return (void *) ((char *) mg_flash_start() + 0x2000); -} - -MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { - struct mg_flexspi_nor_config config; - if (flexspi_nor_get_config(&config) != 0) { - return false; - } - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; - } - - if ((char *) addr < (char *) mg_flash_start()) { - MG_ERROR(("Invalid flash write address: %p", addr)); - return false; - } - - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - - // Note: If we overwrite the flash irq section of the image, we must also - // make sure interrupts are disabled and are not reenabled until we write - // this sector with another irq table. - if ((char *) addr == (char *) flash_code_location()) { - s_flash_irq_disabled = true; - MG_ARM_DISABLE_IRQ(); - } - - while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) { - break; - } - uint32_t status; - uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start(); - if ((char *) buf >= (char *) mg_flash_start()) { - // If we copy from FLASH to FLASH, then we first need to copy the source - // to RAM - size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t); - uint32_t tmp[tmp_buf_size]; - - for (size_t i = 0; i < tmp_buf_size; i++) { - flash_wait(); - tmp[i] = src[i]; - } - MG_ARM_DISABLE_IRQ(); - status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, - (uint32_t) dst_ofs, tmp); - } else { - MG_ARM_DISABLE_IRQ(); - status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, - (uint32_t) dst_ofs, src); - } - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); - } - src = (uint32_t *) ((char *) src + mg_flash_write_align()); - dst = (uint32_t *) ((char *) dst + mg_flash_write_align()); - if (status != 0) { - ok = false; - } - } - MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); - return ok; -} - -MG_IRAM void mg_device_reset(void) { - MG_DEBUG(("Resetting device...")); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; -} - -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_stm32h5.c" -#endif - - - -#if MG_DEVICE == MG_DEVICE_STM32H5 - -#define FLASH_BASE 0x40022000 // Base address of the flash controller -#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 -#define FLASH_OPTKEYR (FLASH_BASE + 0xc) -#define FLASH_OPTCR (FLASH_BASE + 0x1c) -#define FLASH_NSSR (FLASH_BASE + 0x20) -#define FLASH_NSCR (FLASH_BASE + 0x28) -#define FLASH_NSCCR (FLASH_BASE + 0x30) -#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) -#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) - -void *mg_flash_start(void) { - return (void *) 0x08000000; -} -size_t mg_flash_size(void) { - return 2 * 1024 * 1024; // 2Mb -} -size_t mg_flash_sector_size(void) { - return 8 * 1024; // 8k -} -size_t mg_flash_write_align(void) { - return 16; // 128 bit -} -int mg_flash_bank(void) { - return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; -} - -static void flash_unlock(void) { - static bool unlocked = false; - if (unlocked == false) { - MG_REG(FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_KEYR) = 0Xcdef89ab; - MG_REG(FLASH_OPTKEYR) = 0x08192a3b; - MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f; - unlocked = true; - } -} - -static int flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; -} - -static bool flash_is_err(void) { - return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 -} - -static void flash_wait(void) { - while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && - (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { - (void) 0; - } -} - -static void flash_clear_err(void) { - flash_wait(); // Wait until ready - MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors -} - -static bool flash_bank_is_swapped(void) { - return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 -} - -bool mg_flash_erase(void *location) { - bool ok = false; - if (flash_page_start(location) == false) { - MG_ERROR(("%p is not on a sector boundary")); - } else { - uintptr_t diff = (char *) location - (char *) mg_flash_start(); - uint32_t sector = diff / mg_flash_sector_size(); - uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value - flash_unlock(); - flash_clear_err(); - MG_REG(FLASH_NSCR) = 0; - if ((sector < 128 && flash_bank_is_swapped()) || - (sector > 127 && !flash_bank_is_swapped())) { - MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL - } - if (sector > 127) sector -= 128; - MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num - MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing - flash_wait(); - ok = !flash_is_err(); - MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, - ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); - // mg_hexdump(location, 32); - MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR - } - return ok; -} - -bool mg_flash_swap_bank(void) { - uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); - flash_unlock(); - flash_clear_err(); - // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); - MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); - // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); - MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART - while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; - return true; -} - -bool mg_flash_write(void *addr, const void *buf, size_t len) { - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; - } - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - flash_unlock(); - flash_clear_err(); - MG_ARM_DISABLE_IRQ(); - // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); - MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag - while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; - *(volatile uint32_t *) dst++ = *src++; - flash_wait(); - if (flash_is_err()) ok = false; - } - MG_ARM_ENABLE_IRQ(); - MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, - flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), - MG_REG(FLASH_NSSR))); - MG_REG(FLASH_NSCR) = 0; // Clear flags - return ok; -} - -void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_stm32h7.c" -#endif - - - -#if MG_DEVICE == MG_DEVICE_STM32H7 - -#define FLASH_BASE1 0x52002000 // Base address for bank1 -#define FLASH_BASE2 0x52002100 // Base address for bank2 -#define FLASH_KEYR 0x04 // See RM0433 4.9.2 -#define FLASH_OPTKEYR 0x08 -#define FLASH_OPTCR 0x18 -#define FLASH_SR 0x10 -#define FLASH_CR 0x0c -#define FLASH_CCR 0x14 -#define FLASH_OPTSR_CUR 0x1c -#define FLASH_OPTSR_PRG 0x20 -#define FLASH_SIZE_REG 0x1ff1e880 - -MG_IRAM void *mg_flash_start(void) { - return (void *) 0x08000000; -} -MG_IRAM size_t mg_flash_size(void) { - return MG_REG(FLASH_SIZE_REG) * 1024; -} -MG_IRAM size_t mg_flash_sector_size(void) { - return 128 * 1024; // 128k -} -MG_IRAM size_t mg_flash_write_align(void) { - return 32; // 256 bit -} -MG_IRAM int mg_flash_bank(void) { - if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual bank support - return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; -} - -MG_IRAM static void flash_unlock(void) { - static bool unlocked = false; - if (unlocked == false) { - MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; - if (mg_flash_bank() > 0) { - MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; - } - MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" - MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once - unlocked = true; - } -} - -MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; -} - -MG_IRAM static bool flash_is_err(uint32_t bank) { - return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 -} - -MG_IRAM static void flash_wait(uint32_t bank) { - while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; -} - -MG_IRAM static void flash_clear_err(uint32_t bank) { - flash_wait(bank); // Wait until ready - MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors -} - -MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) { - return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 -} - -// Figure out flash bank based on the address -MG_IRAM static uint32_t flash_bank(void *addr) { - size_t ofs = (char *) addr - (char *) mg_flash_start(); - if (mg_flash_bank() == 0) return FLASH_BASE1; - return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; -} - -MG_IRAM bool mg_flash_erase(void *addr) { - bool ok = false; - if (flash_page_start(addr) == false) { - MG_ERROR(("%p is not on a sector boundary", addr)); - } else { - uintptr_t diff = (char *) addr - (char *) mg_flash_start(); - uint32_t sector = diff / mg_flash_sector_size(); - uint32_t bank = flash_bank(addr); - uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value - - flash_unlock(); - if (sector > 7) sector -= 8; - - flash_clear_err(bank); - MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism - MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase - MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit - MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing - ok = !flash_is_err(bank); - MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, - ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), - MG_REG(bank + FLASH_SR))); - MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR - } - return ok; -} - -MG_IRAM bool mg_flash_swap_bank() { - if (mg_flash_bank() == 0) return true; - uint32_t bank = FLASH_BASE1; - uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); - flash_unlock(); - flash_clear_err(bank); - // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); - MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); - // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); - MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART - while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; - return true; -} - -MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; - } - uint32_t bank = flash_bank(addr); - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - flash_unlock(); - flash_clear_err(bank); - MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag - MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism - MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len)); - MG_ARM_DISABLE_IRQ(); - while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; - *(volatile uint32_t *) dst++ = *src++; - flash_wait(bank); - if (flash_is_err(bank)) ok = false; - } - MG_ARM_ENABLE_IRQ(); - MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, - ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), - MG_REG(bank + FLASH_SR))); - MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag - return ok; -} - -MG_IRAM void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/dns.c" -#endif - - - - - - - - -struct dns_data { - struct dns_data *next; - struct mg_connection *c; - uint64_t expire; - uint16_t txnid; -}; - -static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, - struct mg_dns *, bool); - -static void mg_dns_free(struct dns_data **head, struct dns_data *d) { - LIST_DELETE(struct dns_data, head, d); - free(d); -} - -void mg_resolve_cancel(struct mg_connection *c) { - struct dns_data *tmp, *d; - struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; - for (d = *head; d != NULL; d = tmp) { - tmp = d->next; - if (d->c == c) mg_dns_free(head, d); - } -} - -static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, - char *to, size_t tolen, size_t j, - int depth) { - size_t i = 0; - if (tolen > 0 && depth == 0) to[0] = '\0'; - if (depth > 5) return 0; - // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1])); - while (ofs + i + 1 < len) { - size_t n = s[ofs + i]; - if (n == 0) { - i++; - break; - } - if (n & 0xc0) { - size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len - // MG_INFO(("PTR %lx", (unsigned long) ptr)); - if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && - mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0) - return 0; - i += 2; - break; - } - if (ofs + i + n + 1 >= len) return 0; - if (j > 0) { - if (j < tolen) to[j] = '.'; - j++; - } - if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); - j += n; - i += n + 1; - if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk - // MG_INFO(("--> [%s]", to)); - } - if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term - return i; -} - -static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, - char *dst, size_t dstlen) { - return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0); -} - -size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, - bool is_question, struct mg_dns_rr *rr) { - const uint8_t *s = buf + ofs, *e = &buf[len]; - - memset(rr, 0, sizeof(*rr)); - if (len < sizeof(struct mg_dns_header)) return 0; // Too small - if (len > 512) return 0; // Too large, we don't expect that - if (s >= e) return 0; // Overflow - - if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) - return 0; - s += rr->nlen + 4; - if (s > e) return 0; - rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]); - rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); - if (is_question) return (size_t) (rr->nlen + 4); - - s += 6; - if (s > e) return 0; - rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); - if (s + rr->alen > e) return 0; - return (size_t) (rr->nlen + rr->alen + 10); -} - -bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { - const struct mg_dns_header *h = (struct mg_dns_header *) buf; - struct mg_dns_rr rr; - size_t i, n, num_answers, ofs = sizeof(*h); - memset(dm, 0, sizeof(*dm)); - - if (len < sizeof(*h)) return 0; // Too small, headers dont fit - if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity - num_answers = mg_ntohs(h->num_answers); - if (num_answers > 10) { - MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers)); - num_answers = 10; // Sanity cap - } - dm->txnid = mg_ntohs(h->txnid); - - for (i = 0; i < mg_ntohs(h->num_questions); i++) { - if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; - // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass)); - ofs += n; - } - for (i = 0; i < num_answers; i++) { - if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; - // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass, - // dm->name)); - mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); - ofs += n; - - if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { - dm->addr.is_ip6 = false; - memcpy(&dm->addr.ip, &buf[ofs - 4], 4); - dm->resolved = true; - break; // Return success - } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) { - dm->addr.is_ip6 = true; - memcpy(&dm->addr.ip, &buf[ofs - 16], 16); - dm->resolved = true; - break; // Return success - } - } - return true; -} - -static void dns_cb(struct mg_connection *c, int ev, void *ev_data) { - struct dns_data *d, *tmp; - struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; - if (ev == MG_EV_POLL) { - uint64_t now = *(uint64_t *) ev_data; - for (d = *head; d != NULL; d = tmp) { - tmp = d->next; - // MG_DEBUG ("%lu %lu dns poll", d->expire, now)); - if (now > d->expire) mg_error(d->c, "DNS timeout"); - } - } else if (ev == MG_EV_READ) { - struct mg_dns_message dm; - int resolved = 0; - if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) { - MG_ERROR(("Unexpected DNS response:")); - mg_hexdump(c->recv.buf, c->recv.len); - } else { - // MG_VERBOSE(("%s %d", dm.name, dm.resolved)); - for (d = *head; d != NULL; d = tmp) { - tmp = d->next; - // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid)); - if (dm.txnid != d->txnid) continue; - if (d->c->is_resolving) { - if (dm.resolved) { - dm.addr.port = d->c->rem.port; // Save port - d->c->rem = dm.addr; // Copy resolved address - MG_DEBUG( - ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem)); - mg_connect_resolved(d->c); -#if MG_ENABLE_IPV6 - } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' && - c->mgr->use_dns6 == false) { - struct mg_str x = mg_str(dm.name); - mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); -#endif - } else { - mg_error(d->c, "%s DNS lookup failed", dm.name); - } - } else { - MG_ERROR(("%lu already resolved", d->c->id)); - } - mg_dns_free(head, d); - resolved = 1; - } - } - if (!resolved) MG_ERROR(("stray DNS reply")); - c->recv.len = 0; - } else if (ev == MG_EV_CLOSE) { - for (d = *head; d != NULL; d = tmp) { - tmp = d->next; - mg_error(d->c, "DNS error"); - mg_dns_free(head, d); - } - } -} - -static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name, - uint16_t txnid, bool ipv6) { - struct { - struct mg_dns_header header; - uint8_t data[256]; - } pkt; - size_t i, n; - memset(&pkt, 0, sizeof(pkt)); - pkt.header.txnid = mg_htons(txnid); - pkt.header.flags = mg_htons(0x100); - pkt.header.num_questions = mg_htons(1); - for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { - if (name->ptr[i] == '.' || i >= name->len) { - pkt.data[n] = (uint8_t) (i - n); - memcpy(&pkt.data[n + 1], name->ptr + n, i - n); - n = i + 1; - } - if (i >= name->len) break; - } - memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query - n += 5; - if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query - // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query - // n += 6; - return mg_send(c, &pkt, sizeof(pkt.header) + n); -} - -static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, - struct mg_dns *dnsc, bool ipv6) { - struct dns_data *d = NULL; - if (dnsc->url == NULL) { - mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); - } else if (dnsc->c == NULL) { - dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); - if (dnsc->c != NULL) { - dnsc->c->pfn = dns_cb; - // dnsc->c->is_hexdumping = 1; - } - } - if (dnsc->c == NULL) { - mg_error(c, "resolver"); - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { - mg_error(c, "resolve OOM"); - } else { - struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; - d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1; - d->next = (struct dns_data *) c->mgr->active_dns_requests; - c->mgr->active_dns_requests = d; - d->expire = mg_millis() + (uint64_t) ms; - d->c = c; - c->is_resolving = 1; - MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, - name->ptr, dnsc->url, d->txnid)); - if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) { - mg_error(dnsc->c, "DNS send"); - } - } -} - -void mg_resolve(struct mg_connection *c, const char *url) { - struct mg_str host = mg_url_host(url); - c->rem.port = mg_htons(mg_url_port(url)); - if (mg_aton(host, &c->rem)) { - // host is an IP address, do not fire name resolution - mg_connect_resolved(c); - } else { - // host is not an IP, send DNS resolution request - struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4; - mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6); - } -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/event.c" -#endif - - - - - - -void mg_call(struct mg_connection *c, int ev, void *ev_data) { -#if MG_ENABLE_PROFILE - const char *names[] = { - "EV_ERROR", "EV_OPEN", "EV_POLL", "EV_RESOLVE", - "EV_CONNECT", "EV_ACCEPT", "EV_TLS_HS", "EV_READ", - "EV_WRITE", "EV_CLOSE", "EV_HTTP_MSG", "EV_HTTP_CHUNK", - "EV_WS_OPEN", "EV_WS_MSG", "EV_WS_CTL", "EV_MQTT_CMD", - "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"}; - if (ev != MG_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) { - MG_PROF_ADD(c, names[ev]); - } -#endif - // Fire protocol handler first, user handler second. See #2559 - if (c->pfn != NULL) c->pfn(c, ev, ev_data); - if (c->fn != NULL) c->fn(c, ev, ev_data); -} - -void mg_error(struct mg_connection *c, const char *fmt, ...) { - char buf[64]; - va_list ap; - va_start(ap, fmt); - mg_vsnprintf(buf, sizeof(buf), fmt, &ap); - va_end(ap); - MG_ERROR(("%lu %ld %s", c->id, c->fd, buf)); - c->is_closing = 1; // Set is_closing before sending MG_EV_CALL - mg_call(c, MG_EV_ERROR, buf); // Let user handler override it -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/fmt.c" -#endif - - - - -static bool is_digit(int c) { - return c >= '0' && c <= '9'; -} - -static int addexp(char *buf, int e, int sign) { - int n = 0; - buf[n++] = 'e'; - buf[n++] = (char) sign; - if (e > 400) return 0; - if (e < 10) buf[n++] = '0'; - if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100); - if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10); - buf[n++] = (char) (e + '0'); - return n; -} - -static int xisinf(double x) { - union { - double f; - uint64_t u; - } ieee754 = {x}; - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 && - ((unsigned) ieee754.u == 0); -} - -static int xisnan(double x) { - union { - double f; - uint64_t u; - } ieee754 = {x}; - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) + - ((unsigned) ieee754.u != 0) > - 0x7ff00000; -} - -static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, bool tz) { - char buf[40]; - int i, s = 0, n = 0, e = 0; - double t, mul, saved; - if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0"); - if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf"); - if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan"); - if (d < 0.0) d = -d, buf[s++] = '-'; - - // Round - saved = d; - mul = 1.0; - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; - while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0; - for (i = 0, t = mul * 5; i < width; i++) t /= 10.0; - d += t; - // Calculate exponent, and 'mul' for scientific representation - mul = 1.0; - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++; - while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--; - // printf(" --> %g %d %g %g\n", saved, e, t, mul); - - if (e >= width && width > 1) { - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); - // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf); - n += addexp(buf + s + n, e, '+'); - return mg_snprintf(dst, dstlen, "%.*s", n, buf); - } else if (e <= -width && width > 1) { - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); - // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf); - n += addexp(buf + s + n, -e, '-'); - return mg_snprintf(dst, dstlen, "%.*s", n, buf); - } else { - for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) { - int ch = (int) (d / t); - if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0'); - d -= ch * t; - t /= 10.0; - } - // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf); - if (n == 0) buf[s++] = '0'; - while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0; - if (s + n < (int) sizeof(buf)) buf[n + s++] = '.'; - // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf); - for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) { - int ch = (int) (d / t); - buf[s + n++] = (char) (ch + '0'); - d -= ch * t; - t /= 10.0; - } - } - while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeroes - if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot - n += s; - if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1; - buf[n] = '\0'; - return mg_snprintf(dst, dstlen, "%s", buf); -} - -static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) { - const char *letters = "0123456789abcdef"; - uint64_t v = (uint64_t) val; - size_t s = 0, n, i; - if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val); - // This loop prints a number in reverse order. I guess this is because we - // write numbers from right to left: least significant digit comes last. - // Maybe because we use Arabic numbers, and Arabs write RTL? - if (is_hex) { - for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15]; - } else { - for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10]; - } - // Reverse a string - for (i = 0; i < n / 2; i++) { - char t = buf[s + i]; - buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t; - } - if (val == 0) buf[n++] = '0'; // Handle special case - return n + s; -} - -static size_t scpy(void (*out)(char, void *), void *ptr, char *buf, - size_t len) { - size_t i = 0; - while (i < len && buf[i] != '\0') out(buf[i++], ptr); - return i; -} - -size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) { - size_t len = 0; - va_list ap; - va_start(ap, fmt); - len = mg_vxprintf(out, ptr, fmt, &ap); - va_end(ap); - return len; -} - -size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt, - va_list *ap) { - size_t i = 0, n = 0; - while (fmt[i] != '\0') { - if (fmt[i] == '%') { - size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */; - char pad = ' ', minus = 0, c = fmt[++i]; - if (c == '#') x++, c = fmt[++i]; - if (c == '-') minus++, c = fmt[++i]; - if (c == '0') pad = '0', c = fmt[++i]; - while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i]; - if (c == '.') { - c = fmt[++i]; - if (c == '*') { - pr = (size_t) va_arg(*ap, int); - c = fmt[++i]; - } else { - pr = 0; - while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i]; - } - } - while (c == 'h') c = fmt[++i]; // Treat h and hh as int - if (c == 'l') { - is_long++, c = fmt[++i]; - if (c == 'l') is_long++, c = fmt[++i]; - } - if (c == 'p') x = 1, is_long = 1; - if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' || - c == 'g' || c == 'f') { - bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p'); - char tmp[40]; - size_t xl = x ? 2 : 0; - if (c == 'g' || c == 'f') { - double v = va_arg(*ap, double); - if (pr == ~0U) pr = 6; - k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g'); - } else if (is_long == 2) { - int64_t v = va_arg(*ap, int64_t); - k = mg_lld(tmp, v, s, h); - } else if (is_long == 1) { - long v = va_arg(*ap, long); - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h); - } else { - int v = va_arg(*ap, int); - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h); - } - for (j = 0; j < xl && w > 0; j++) w--; - for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++) - n += scpy(out, param, &pad, 1); - n += scpy(out, param, (char *) "0x", xl); - for (j = 0; pad == '0' && k < w && j + k < w; j++) - n += scpy(out, param, &pad, 1); - n += scpy(out, param, tmp, k); - for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++) - n += scpy(out, param, &pad, 1); - } else if (c == 'm' || c == 'M') { - mg_pm_t f = va_arg(*ap, mg_pm_t); - if (c == 'm') out('"', param); - n += f(out, param, ap); - if (c == 'm') n += 2, out('"', param); - } else if (c == 'c') { - int ch = va_arg(*ap, int); - out((char) ch, param); - n++; - } else if (c == 's') { - char *p = va_arg(*ap, char *); - if (pr == ~0U) pr = p == NULL ? 0 : strlen(p); - for (j = 0; !minus && pr < w && j + pr < w; j++) - n += scpy(out, param, &pad, 1); - n += scpy(out, param, p, pr); - for (j = 0; minus && pr < w && j + pr < w; j++) - n += scpy(out, param, &pad, 1); - } else if (c == '%') { - out('%', param); - n++; - } else { - out('%', param); - out(c, param); - n += 2; - } - i++; - } else { - out(fmt[i], param), n++, i++; - } - } - return n; -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/fs.c" -#endif - - - - -struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { - struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd)); - if (fd != NULL) { - fd->fd = fs->op(path, flags); - fd->fs = fs; - if (fd->fd == NULL) { - free(fd); - fd = NULL; - } - } - return fd; -} - -void mg_fs_close(struct mg_fd *fd) { - if (fd != NULL) { - fd->fs->cl(fd->fd); - free(fd); - } -} - -struct mg_str mg_file_read(struct mg_fs *fs, const char *path) { - struct mg_str result = {NULL, 0}; - void *fp; - fs->st(path, &result.len, NULL); - if ((fp = fs->op(path, MG_FS_READ)) != NULL) { - result.ptr = (char *) calloc(1, result.len + 1); - if (result.ptr != NULL && - fs->rd(fp, (void *) result.ptr, result.len) != result.len) { - free((void *) result.ptr); - result.ptr = NULL; - } - fs->cl(fp); - } - if (result.ptr == NULL) result.len = 0; - return result; -} - -bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, - size_t len) { - bool result = false; - struct mg_fd *fd; - char tmp[MG_PATH_MAX]; - mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand()); - if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) { - result = fs->wr(fd->fd, buf, len) == len; - mg_fs_close(fd); - if (result) { - fs->rm(path); - fs->mv(tmp, path); - } else { - fs->rm(tmp); - } - } - return result; -} - -bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) { - va_list ap; - char *data; - bool result = false; - va_start(ap, fmt); - data = mg_vmprintf(fmt, &ap); - va_end(ap); - result = mg_file_write(fs, path, data, strlen(data)); - free(data); - return result; -} - -// This helper function allows to scan a filesystem in a sequential way, -// without using callback function: -// char buf[100] = ""; -// while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) { -// ... -static void mg_fs_ls_fn(const char *filename, void *param) { - struct mg_str *s = (struct mg_str *) param; - if (s->ptr[0] == '\0') { - mg_snprintf((char *) s->ptr, s->len, "%s", filename); - } else if (strcmp(s->ptr, filename) == 0) { - ((char *) s->ptr)[0] = '\0'; // Fetch next file - } -} - -bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len) { - struct mg_str s = {buf, len}; - fs->ls(path, mg_fs_ls_fn, &s); - return buf[0] != '\0'; -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/fs_fat.c" -#endif - - - -#if MG_ENABLE_FATFS -#include - -static int mg_days_from_epoch(int y, int m, int d) { - y -= m <= 2; - int era = y / 400; - int yoe = y - era * 400; - int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; - int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; - return era * 146097 + doe - 719468; -} - -static time_t mg_timegm(const struct tm *t) { - int year = t->tm_year + 1900; - int month = t->tm_mon; // 0-11 - if (month > 11) { - year += month / 12; - month %= 12; - } else if (month < 0) { - int years_diff = (11 - month) / 12; - year -= years_diff; - month += 12 * years_diff; - } - int x = mg_days_from_epoch(year, month + 1, t->tm_mday); - return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec; -} - -static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) { - struct tm tm; - memset(&tm, 0, sizeof(struct tm)); - tm.tm_sec = (ftime << 1) & 0x3e; - tm.tm_min = ((ftime >> 5) & 0x3f); - tm.tm_hour = ((ftime >> 11) & 0x1f); - tm.tm_mday = (fdate & 0x1f); - tm.tm_mon = ((fdate >> 5) & 0x0f) - 1; - tm.tm_year = ((fdate >> 9) & 0x7f) + 80; - return mg_timegm(&tm); -} - -static int ff_stat(const char *path, size_t *size, time_t *mtime) { - FILINFO fi; - if (path[0] == '\0') { - if (size) *size = 0; - if (mtime) *mtime = 0; - return MG_FS_DIR; - } else if (f_stat(path, &fi) == 0) { - if (size) *size = (size_t) fi.fsize; - if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime); - return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0); - } else { - return 0; - } -} - -static void ff_list(const char *dir, void (*fn)(const char *, void *), - void *userdata) { - DIR d; - FILINFO fi; - if (f_opendir(&d, dir) == FR_OK) { - while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') { - if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue; - fn(fi.fname, userdata); - } - f_closedir(&d); - } -} - -static void *ff_open(const char *path, int flags) { - FIL f; - unsigned char mode = FA_READ; - if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND; - if (f_open(&f, path, mode) == 0) { - FIL *fp; - if ((fp = calloc(1, sizeof(*fp))) != NULL) { - memcpy(fp, &f, sizeof(*fp)); - return fp; - } - } - return NULL; -} - -static void ff_close(void *fp) { - if (fp != NULL) { - f_close((FIL *) fp); - free(fp); - } -} - -static size_t ff_read(void *fp, void *buf, size_t len) { - UINT n = 0, misalign = ((size_t) buf) & 3; - if (misalign) { - char aligned[4]; - f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n); - memcpy(buf, aligned, n); - } else { - f_read((FIL *) fp, buf, len, &n); - } - return n; -} - -static size_t ff_write(void *fp, const void *buf, size_t len) { - UINT n = 0; - return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0; -} - -static size_t ff_seek(void *fp, size_t offset) { - f_lseek((FIL *) fp, offset); - return offset; -} - -static bool ff_rename(const char *from, const char *to) { - return f_rename(from, to) == FR_OK; -} - -static bool ff_remove(const char *path) { - return f_unlink(path) == FR_OK; -} - -static bool ff_mkdir(const char *path) { - return f_mkdir(path) == FR_OK; -} - -struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read, - ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir}; -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/fs_packed.c" -#endif - - - - -struct packed_file { - const char *data; - size_t size; - size_t pos; -}; - -#if MG_ENABLE_PACKED_FS -#else -const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { - *size = 0, *mtime = 0; - (void) path; - return NULL; -} -const char *mg_unlist(size_t no) { - (void) no; - return NULL; -} -#endif - -struct mg_str mg_unpacked(const char *path) { - size_t len = 0; - const char *buf = mg_unpack(path, &len, NULL); - return mg_str_n(buf, len); -} - -static int is_dir_prefix(const char *prefix, size_t n, const char *path) { - // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n])); - return n < strlen(path) && strncmp(prefix, path, n) == 0 && - (n == 0 || path[n] == '/' || path[n - 1] == '/'); -} - -static int packed_stat(const char *path, size_t *size, time_t *mtime) { - const char *p; - size_t i, n = strlen(path); - if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file - // Scan all files. If `path` is a dir prefix for any of them, it's a dir - for (i = 0; (p = mg_unlist(i)) != NULL; i++) { - if (is_dir_prefix(path, n, p)) return MG_FS_DIR; - } - return 0; -} - -static void packed_list(const char *dir, void (*fn)(const char *, void *), - void *userdata) { - char buf[MG_PATH_MAX], tmp[sizeof(buf)]; - const char *path, *begin, *end; - size_t i, n = strlen(dir); - tmp[0] = '\0'; // Previously listed entry - for (i = 0; (path = mg_unlist(i)) != NULL; i++) { - if (!is_dir_prefix(dir, n, path)) continue; - begin = &path[n + 1]; - end = strchr(begin, '/'); - if (end == NULL) end = begin + strlen(begin); - mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin); - buf[sizeof(buf) - 1] = '\0'; - // If this entry has been already listed, skip - // NOTE: we're assuming that file list is sorted alphabetically - if (strcmp(buf, tmp) == 0) continue; - fn(buf, userdata); // Not yet listed, call user function - strcpy(tmp, buf); // And save this entry as listed - } -} - -static void *packed_open(const char *path, int flags) { - size_t size = 0; - const char *data = mg_unpack(path, &size, NULL); - struct packed_file *fp = NULL; - if (data == NULL) return NULL; - if (flags & MG_FS_WRITE) return NULL; - if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) { - fp->size = size; - fp->data = data; - } - return (void *) fp; -} - -static void packed_close(void *fp) { - if (fp != NULL) free(fp); -} - -static size_t packed_read(void *fd, void *buf, size_t len) { - struct packed_file *fp = (struct packed_file *) fd; - if (fp->pos + len > fp->size) len = fp->size - fp->pos; - memcpy(buf, &fp->data[fp->pos], len); - fp->pos += len; - return len; -} - -static size_t packed_write(void *fd, const void *buf, size_t len) { - (void) fd, (void) buf, (void) len; - return 0; -} - -static size_t packed_seek(void *fd, size_t offset) { - struct packed_file *fp = (struct packed_file *) fd; - fp->pos = offset; - if (fp->pos > fp->size) fp->pos = fp->size; - return fp->pos; -} - -static bool packed_rename(const char *from, const char *to) { - (void) from, (void) to; - return false; -} - -static bool packed_remove(const char *path) { - (void) path; - return false; -} - -static bool packed_mkdir(const char *path) { - (void) path; - return false; -} - -struct mg_fs mg_fs_packed = { - packed_stat, packed_list, packed_open, packed_close, packed_read, - packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir}; - -#ifdef MG_ENABLE_LINES -#line 1 "src/fs_posix.c" -#endif - - -#if MG_ENABLE_POSIX_FS - -#ifndef MG_STAT_STRUCT -#define MG_STAT_STRUCT stat -#endif - -#ifndef MG_STAT_FUNC -#define MG_STAT_FUNC stat -#endif - -static int p_stat(const char *path, size_t *size, time_t *mtime) { -#if !defined(S_ISDIR) - MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); - return 0; -#else -#if MG_ARCH == MG_ARCH_WIN32 - struct _stati64 st; - wchar_t tmp[MG_PATH_MAX]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); - if (_wstati64(tmp, &st) != 0) return 0; - // If path is a symlink, windows reports 0 in st.st_size. - // Get a real file size by opening it and jumping to the end - if (st.st_size == 0 && (st.st_mode & _S_IFREG)) { - FILE *fp = _wfopen(tmp, L"rb"); - if (fp != NULL) { - fseek(fp, 0, SEEK_END); - if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on win10+ - fclose(fp); - } - } -#else - struct MG_STAT_STRUCT st; - if (MG_STAT_FUNC(path, &st) != 0) return 0; -#endif - if (size) *size = (size_t) st.st_size; - if (mtime) *mtime = st.st_mtime; - return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); -#endif -} - -#if MG_ARCH == MG_ARCH_WIN32 -struct dirent { - char d_name[MAX_PATH]; -}; - -typedef struct win32_dir { - HANDLE handle; - WIN32_FIND_DATAW info; - struct dirent result; -} DIR; - -#if 0 -int gettimeofday(struct timeval *tv, void *tz) { - FILETIME ft; - unsigned __int64 tmpres = 0; - - if (tv != NULL) { - GetSystemTimeAsFileTime(&ft); - tmpres |= ft.dwHighDateTime; - tmpres <<= 32; - tmpres |= ft.dwLowDateTime; - tmpres /= 10; // convert into microseconds - tmpres -= (int64_t) 11644473600000000; - tv->tv_sec = (long) (tmpres / 1000000UL); - tv->tv_usec = (long) (tmpres % 1000000UL); - } - (void) tz; - return 0; -} -#endif - -static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { - int ret; - char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; - strncpy(buf, path, sizeof(buf)); - buf[sizeof(buf) - 1] = '\0'; - // Trim trailing slashes. Leave backslash for paths like "X:\" - p = buf + strlen(buf) - 1; - while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; - memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); - ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); - // Convert back to Unicode. If doubly-converted string does not match the - // original, something is fishy, reject. - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), - NULL, NULL); - if (strcmp(buf, buf2) != 0) { - wbuf[0] = L'\0'; - ret = 0; - } - return ret; -} - -DIR *opendir(const char *name) { - DIR *d = NULL; - wchar_t wpath[MAX_PATH]; - DWORD attrs; - - if (name == NULL) { - SetLastError(ERROR_BAD_ARGUMENTS); - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - } else { - to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); - attrs = GetFileAttributesW(wpath); - if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { - (void) wcscat(wpath, L"\\*"); - d->handle = FindFirstFileW(wpath, &d->info); - d->result.d_name[0] = '\0'; - } else { - free(d); - d = NULL; - } - } - return d; -} - -int closedir(DIR *d) { - int result = 0; - if (d != NULL) { - if (d->handle != INVALID_HANDLE_VALUE) - result = FindClose(d->handle) ? 0 : -1; - free(d); - } else { - result = -1; - SetLastError(ERROR_BAD_ARGUMENTS); - } - return result; -} - -struct dirent *readdir(DIR *d) { - struct dirent *result = NULL; - if (d != NULL) { - memset(&d->result, 0, sizeof(d->result)); - if (d->handle != INVALID_HANDLE_VALUE) { - result = &d->result; - WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, - sizeof(result->d_name), NULL, NULL); - if (!FindNextFileW(d->handle, &d->info)) { - FindClose(d->handle); - d->handle = INVALID_HANDLE_VALUE; - } - } else { - SetLastError(ERROR_FILE_NOT_FOUND); - } - } else { - SetLastError(ERROR_BAD_ARGUMENTS); - } - return result; -} -#endif - -static void p_list(const char *dir, void (*fn)(const char *, void *), - void *userdata) { -#if MG_ENABLE_DIRLIST - struct dirent *dp; - DIR *dirp; - if ((dirp = (opendir(dir))) == NULL) return; - while ((dp = readdir(dirp)) != NULL) { - if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; - fn(dp->d_name, userdata); - } - closedir(dirp); -#else - (void) dir, (void) fn, (void) userdata; -#endif -} - -static void *p_open(const char *path, int flags) { -#if MG_ARCH == MG_ARCH_WIN32 - const char *mode = flags == MG_FS_READ ? "rb" : "a+b"; - wchar_t b1[MG_PATH_MAX], b2[10]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); - MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); - return (void *) _wfopen(b1, b2); -#else - const char *mode = flags == MG_FS_READ ? "rbe" : "a+be"; // e for CLOEXEC - return (void *) fopen(path, mode); -#endif -} - -static void p_close(void *fp) { - fclose((FILE *) fp); -} - -static size_t p_read(void *fp, void *buf, size_t len) { - return fread(buf, 1, len, (FILE *) fp); -} - -static size_t p_write(void *fp, const void *buf, size_t len) { - return fwrite(buf, 1, len, (FILE *) fp); -} - -static size_t p_seek(void *fp, size_t offset) { -#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \ - (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \ - (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) - if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0; -#else - if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0; -#endif - return (size_t) ftell((FILE *) fp); -} - -static bool p_rename(const char *from, const char *to) { - return rename(from, to) == 0; -} - -static bool p_remove(const char *path) { - return remove(path) == 0; -} - -static bool p_mkdir(const char *path) { - return mkdir(path, 0775) == 0; -} - -#else - -static int p_stat(const char *path, size_t *size, time_t *mtime) { - (void) path, (void) size, (void) mtime; - return 0; -} -static void p_list(const char *path, void (*fn)(const char *, void *), - void *userdata) { - (void) path, (void) fn, (void) userdata; -} -static void *p_open(const char *path, int flags) { - (void) path, (void) flags; - return NULL; -} -static void p_close(void *fp) { - (void) fp; -} -static size_t p_read(void *fd, void *buf, size_t len) { - (void) fd, (void) buf, (void) len; - return 0; -} -static size_t p_write(void *fd, const void *buf, size_t len) { - (void) fd, (void) buf, (void) len; - return 0; -} -static size_t p_seek(void *fd, size_t offset) { - (void) fd, (void) offset; - return (size_t) ~0; -} -static bool p_rename(const char *from, const char *to) { - (void) from, (void) to; - return false; -} -static bool p_remove(const char *path) { - (void) path; - return false; -} -static bool p_mkdir(const char *path) { - (void) path; - return false; -} -#endif - -struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, - p_write, p_seek, p_rename, p_remove, p_mkdir}; - -#ifdef MG_ENABLE_LINES -#line 1 "src/http.c" -#endif - - - - - - - - - - - - - -bool mg_to_size_t(struct mg_str str, size_t *val); -bool mg_to_size_t(struct mg_str str, size_t *val) { - size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits = 0; - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++; - if (i < str.len && str.ptr[i] == '-') return false; - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') { - size_t digit = (size_t) (str.ptr[i] - '0'); - if (result > max2) return false; // Overflow - result *= 10; - if (result > max - digit) return false; // Overflow - result += digit; - i++, ndigits++; - } - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++; - if (ndigits == 0) return false; // #2322: Content-Length = 1 * DIGIT - if (i != str.len) return false; // Ditto - *val = (size_t) result; - return true; -} - -// Chunk deletion marker is the MSB in the "processed" counter -#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1)) - -// Multipart POST example: -// --xyz -// Content-Disposition: form-data; name="val" -// -// abcdef -// --xyz -// Content-Disposition: form-data; name="foo"; filename="a.txt" -// Content-Type: text/plain -// -// hello world -// -// --xyz-- -size_t mg_http_next_multipart(struct mg_str body, size_t ofs, - struct mg_http_part *part) { - struct mg_str cd = mg_str_n("Content-Disposition", 19); - const char *s = body.ptr; - size_t b = ofs, h1, h2, b1, b2, max = body.len; - - // Init part params - if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); - - // Skip boundary - while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; - if (b <= ofs || b + 2 >= max) return 0; - // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); - - // Skip headers - h1 = h2 = b + 2; - for (;;) { - while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; - if (h2 == h1) break; - if (h2 + 2 >= max) return 0; - // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1])); - if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && - mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) { - struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); - part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); - part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); - } - h1 = h2 = h2 + 2; - } - b1 = b2 = h2 + 2; - while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' && - memcmp(&s[b2 + 2], s, b - ofs) == 0)) - b2++; - - if (b2 + 2 >= max) return 0; - if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); - // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1])); - return b2 + 2; -} - -void mg_http_bauth(struct mg_connection *c, const char *user, - const char *pass) { - struct mg_str u = mg_str(user), p = mg_str(pass); - size_t need = c->send.len + 36 + (u.len + p.len) * 2; - if (c->send.size < need) mg_iobuf_resize(&c->send, need); - if (c->send.size >= need) { - size_t i, n = 0; - char *buf = (char *) &c->send.buf[c->send.len]; - memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send! - for (i = 0; i < u.len; i++) { - n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n); - } - if (p.len > 0) { - n = mg_base64_update(':', buf + 21, n); - for (i = 0; i < p.len; i++) { - n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n); - } - } - n = mg_base64_final(buf + 21, n); - c->send.len += 21 + (size_t) n + 2; - memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2); - } else { - MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need)); - } -} - -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) { - struct mg_str entry, k, v, result = mg_str_n(NULL, 0); - while (mg_span(buf, &entry, &buf, '&')) { - if (mg_span(entry, &k, &v, '=') && name.len == k.len && - mg_ncasecmp(name.ptr, k.ptr, k.len) == 0) { - result = v; - break; - } - } - return result; -} - -int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, - size_t dst_len) { - int len; - if (dst != NULL && dst_len > 0) { - dst[0] = '\0'; // If destination buffer is valid, always nul-terminate it - } - if (dst == NULL || dst_len == 0) { - len = -2; // Bad destination - } else if (buf->ptr == NULL || name == NULL || buf->len == 0) { - len = -1; // Bad source - } else { - struct mg_str v = mg_http_var(*buf, mg_str(name)); - if (v.ptr == NULL) { - len = -4; // Name does not exist - } else { - len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1); - if (len < 0) len = -3; // Failed to decode - } - } - return len; -} - -static bool isx(int c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'F'); -} - -int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, - int is_form_url_encoded) { - size_t i, j; - for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) { - if (src[i] == '%') { - // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len - if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) { - mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]); - i += 2; - } else { - return -1; - } - } else if (is_form_url_encoded && src[i] == '+') { - dst[j] = ' '; - } else { - dst[j] = src[i]; - } - } - if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination - return i >= src_len && j < dst_len ? (int) j : -1; -} - -static bool isok(uint8_t c) { - return c == '\n' || c == '\r' || c >= ' '; -} - -int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { - size_t i; - for (i = 0; i < buf_len; i++) { - if (!isok(buf[i])) return -1; - if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') || - (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n')) - return (int) i + 1; - } - return 0; -} -struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) { - size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]); - for (i = 0; i < max && h->headers[i].name.len > 0; i++) { - struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value; - if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v; - } - return NULL; -} - -// Is it a valid utf-8 continuation byte -static bool vcb(uint8_t c) { - return (c & 0xc0) == 0x80; -} - -// Get character length (valid utf-8). Used to parse method, URI, headers -static size_t clen(const char *s, const char *end) { - const unsigned char *u = (unsigned char *) s, c = *u; - long n = (long) (end - s); - if (c > ' ' && c < '~') return 1; // Usual ascii printed char - if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; // 2-byte UTF8 - if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2])) return 3; - if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) && vcb(u[3])) - return 4; - return 0; -} - -// Skip until the newline. Return advanced `s`, or NULL on error -static const char *skiptorn(const char *s, const char *end, struct mg_str *v) { - v->ptr = s; - while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline - if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r - if (s < end && s[0] == '\r') s++; // Skip \r - if (s >= end || *s++ != '\n') return NULL; // Skip \n - return s; -} - -static bool mg_http_parse_headers(const char *s, const char *end, - struct mg_http_header *h, size_t max_hdrs) { - size_t i, n; - for (i = 0; i < max_hdrs; i++) { - struct mg_str k = {NULL, 0}, v = {NULL, 0}; - if (s >= end) return false; - if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break; - k.ptr = s; - while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s += n, k.len += n; - if (k.len == 0) return false; // Empty name - if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8 - if (*s++ != ':') return false; // Invalid, not followed by : - // if (clen(s, end) == 0) return false; // Invalid UTF-8 - while (s < end && s[0] == ' ') s++; // Skip spaces - if ((s = skiptorn(s, end, &v)) == NULL) return false; - while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces - // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.ptr, (int) v.len, v.ptr)); - h[i].name = k, h[i].value = v; // Success. Assign values - } - return true; -} - -int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { - int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len); - const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL - struct mg_str *cl; - size_t n; - - memset(hm, 0, sizeof(*hm)); - if (req_len <= 0) return req_len; - - hm->message.ptr = hm->head.ptr = s; - hm->body.ptr = end; - hm->head.len = (size_t) req_len; - hm->message.len = hm->body.len = (size_t) -1; // Set body length to infinite - - // Parse request line - hm->method.ptr = s; - while (s < end && (n = clen(s, end)) > 0) s += n, hm->method.len += n; - while (s < end && s[0] == ' ') s++; // Skip spaces - hm->uri.ptr = s; - while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n; - while (s < end && s[0] == ' ') s++; // Skip spaces - if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false; - - // If URI contains '?' character, setup query string - if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) { - hm->query.ptr = qs + 1; - hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1)); - hm->uri.len = (size_t) (qs - hm->uri.ptr); - } - - // Sanity check. Allow protocol/reason to be empty - // Do this check after hm->method.len and hm->uri.len are finalised - if (hm->method.len == 0 || hm->uri.len == 0) return -1; - - if (!mg_http_parse_headers(s, end, hm->headers, - sizeof(hm->headers) / sizeof(hm->headers[0]))) - return -1; // error when parsing - if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) { - if (mg_to_size_t(*cl, &hm->body.len) == false) return -1; - hm->message.len = (size_t) req_len + hm->body.len; - } - - // mg_http_parse() is used to parse both HTTP requests and HTTP - // responses. If HTTP response does not have Content-Length set, then - // body is read until socket is closed, i.e. body.len is infinite (~0). - // - // For HTTP requests though, according to - // http://tools.ietf.org/html/rfc7231#section-8.1.3, - // only POST and PUT methods have defined body semantics. - // Therefore, if Content-Length is not specified and methods are - // not one of PUT or POST, set body length to 0. - // - // So, if it is HTTP request, and Content-Length is not set, - // and method is not (PUT or POST) then reset body length to zero. - is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0; - if (hm->body.len == (size_t) ~0 && !is_response && - mg_vcasecmp(&hm->method, "PUT") != 0 && - mg_vcasecmp(&hm->method, "POST") != 0) { - hm->body.len = 0; - hm->message.len = (size_t) req_len; - } - - // The 204 (No content) responses also have 0 body length - if (hm->body.len == (size_t) ~0 && is_response && - mg_vcasecmp(&hm->uri, "204") == 0) { - hm->body.len = 0; - hm->message.len = (size_t) req_len; - } - if (hm->message.len < (size_t) req_len) return -1; // Overflow protection - - return req_len; -} - -static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt, - va_list *ap) { - size_t len = c->send.len; - mg_send(c, " \r\n", 10); - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); - if (c->send.len >= len + 10) { - mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10); - c->send.buf[len + 8] = '\r'; - if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset marker - } - mg_send(c, "\r\n", 2); -} - -void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - mg_http_vprintf_chunk(c, fmt, &ap); - va_end(ap); -} - -void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { - mg_printf(c, "%lx\r\n", (unsigned long) len); - mg_send(c, buf, len); - mg_send(c, "\r\n", 2); - if (len == 0) c->is_resp = 0; -} - -// clang-format off -static const char *mg_http_status_code_str(int status_code) { - switch (status_code) { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - case 208: return "Already Reported"; - case 226: return "IM Used"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - case 308: return "Permanent Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Payload Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 418: return "I'm a teapot"; - case 421: return "Misdirected Request"; - case 422: return "Unprocessable Entity"; - case 423: return "Locked"; - case 424: return "Failed Dependency"; - case 426: return "Upgrade Required"; - case 428: return "Precondition Required"; - case 429: return "Too Many Requests"; - case 431: return "Request Header Fields Too Large"; - case 444: return "Connection Closed Without Response"; - case 451: return "Unavailable For Legal Reasons"; - case 499: return "Client Closed Request"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - case 506: return "Variant Also Negotiates"; - case 507: return "Insufficient Storage"; - case 508: return "Loop Detected"; - case 510: return "Not Extended"; - case 511: return "Network Authentication Required"; - case 599: return "Network Connect Timeout Error"; - default: return ""; - } -} -// clang-format on - -void mg_http_reply(struct mg_connection *c, int code, const char *headers, - const char *fmt, ...) { - va_list ap; - size_t len; - mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code, - mg_http_status_code_str(code), headers == NULL ? "" : headers); - len = c->send.len; - va_start(ap, fmt); - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap); - va_end(ap); - if (c->send.len > 16) { - size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11, "%-10lu", - (unsigned long) (c->send.len - len)); - c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space - } - c->is_resp = 0; -} - -static void http_cb(struct mg_connection *, int, void *); -static void restore_http_cb(struct mg_connection *c) { - mg_fs_close((struct mg_fd *) c->pfn_data); - c->pfn_data = NULL; - c->pfn = http_cb; - c->is_resp = 0; -} - -char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime); -char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { - mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size); - return buf; -} - -static void static_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { - struct mg_fd *fd = (struct mg_fd *) c->pfn_data; - // Read to send IO buffer directly, avoid extra on-stack buffer - size_t n, max = MG_IO_SIZE, space; - size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) / - sizeof(size_t) * sizeof(size_t)]; - if (c->send.size < max) mg_iobuf_resize(&c->send, max); - if (c->send.len >= c->send.size) return; // Rate limit - if ((space = c->send.size - c->send.len) > *cl) space = *cl; - n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space); - c->send.len += n; - *cl -= n; - if (n == 0) restore_http_cb(c); - } else if (ev == MG_EV_CLOSE) { - restore_http_cb(c); - } - (void) ev_data; -} - -// Known mime types. Keep it outside guess_content_type() function, since -// some environments don't like it defined there. -// clang-format off -static struct mg_str s_known_types[] = { - MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"), - MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"), - MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"), - MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"), - MG_C_STR("gif"), MG_C_STR("image/gif"), - MG_C_STR("png"), MG_C_STR("image/png"), - MG_C_STR("jpg"), MG_C_STR("image/jpeg"), - MG_C_STR("jpeg"), MG_C_STR("image/jpeg"), - MG_C_STR("woff"), MG_C_STR("font/woff"), - MG_C_STR("ttf"), MG_C_STR("font/ttf"), - MG_C_STR("svg"), MG_C_STR("image/svg+xml"), - MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"), - MG_C_STR("avi"), MG_C_STR("video/x-msvideo"), - MG_C_STR("csv"), MG_C_STR("text/csv"), - MG_C_STR("doc"), MG_C_STR("application/msword"), - MG_C_STR("exe"), MG_C_STR("application/octet-stream"), - MG_C_STR("gz"), MG_C_STR("application/gzip"), - MG_C_STR("ico"), MG_C_STR("image/x-icon"), - MG_C_STR("json"), MG_C_STR("application/json"), - MG_C_STR("mov"), MG_C_STR("video/quicktime"), - MG_C_STR("mp3"), MG_C_STR("audio/mpeg"), - MG_C_STR("mp4"), MG_C_STR("video/mp4"), - MG_C_STR("mpeg"), MG_C_STR("video/mpeg"), - MG_C_STR("pdf"), MG_C_STR("application/pdf"), - MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"), - MG_C_STR("tgz"), MG_C_STR("application/tar-gz"), - MG_C_STR("wav"), MG_C_STR("audio/wav"), - MG_C_STR("webp"), MG_C_STR("image/webp"), - MG_C_STR("zip"), MG_C_STR("application/zip"), - MG_C_STR("3gp"), MG_C_STR("video/3gpp"), - {0, 0}, -}; -// clang-format on - -static struct mg_str guess_content_type(struct mg_str path, const char *extra) { - struct mg_str entry, k, v, s = mg_str(extra); - size_t i = 0; - - // Shrink path to its extension only - while (i < path.len && path.ptr[path.len - i - 1] != '.') i++; - path.ptr += path.len - i; - path.len = i; - - // Process user-provided mime type overrides, if any - while (mg_span(s, &entry, &s, ',')) { - if (mg_span(entry, &k, &v, '=') && mg_strcmp(path, k) == 0) return v; - } - - // Process built-in mime types - for (i = 0; s_known_types[i].ptr != NULL; i += 2) { - if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1]; - } - - return mg_str("text/plain; charset=utf-8"); -} - -static int getrange(struct mg_str *s, size_t *a, size_t *b) { - size_t i, numparsed = 0; - for (i = 0; i + 6 < s->len; i++) { - struct mg_str k, v = mg_str_n(s->ptr + i + 6, s->len - i - 6); - if (memcmp(&s->ptr[i], "bytes=", 6) != 0) continue; - if (mg_span(v, &k, &v, '-')) { - if (mg_to_size_t(k, a)) numparsed++; - if (v.len > 0 && mg_to_size_t(v, b)) numparsed++; - } else { - if (mg_to_size_t(v, a)) numparsed++; - } - break; - } - return (int) numparsed; -} - -void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, - const char *path, - const struct mg_http_serve_opts *opts) { - char etag[64], tmp[MG_PATH_MAX]; - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; - struct mg_fd *fd = NULL; - size_t size = 0; - time_t mtime = 0; - struct mg_str *inm = NULL; - struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); - bool gzip = false; - - if (path != NULL) { - // If a browser sends us "Accept-Encoding: gzip", try to open .gz first - struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding"); - if (ae != NULL && mg_strstr(*ae, mg_str("gzip")) != NULL) { - mg_snprintf(tmp, sizeof(tmp), "%s.gz", path); - fd = mg_fs_open(fs, tmp, MG_FS_READ); - if (fd != NULL) gzip = true, path = tmp; - } - // No luck opening .gz? Open what we've told to open - if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ); - } - - // Failed to open, and page404 is configured? Open it, then - if (fd == NULL && opts->page404 != NULL) { - fd = mg_fs_open(fs, opts->page404, MG_FS_READ); - mime = guess_content_type(mg_str(path), opts->mime_types); - path = opts->page404; - } - - if (fd == NULL || fs->st(path, &size, &mtime) == 0) { - mg_http_reply(c, 404, opts->extra_headers, "Not found\n"); - mg_fs_close(fd); - // NOTE: mg_http_etag() call should go first! - } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && - (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && - mg_vcasecmp(inm, etag) == 0) { - mg_fs_close(fd); - mg_http_reply(c, 304, opts->extra_headers, ""); - } else { - int n, status = 200; - char range[100]; - size_t r1 = 0, r2 = 0, cl = size; - - // Handle Range header - struct mg_str *rh = mg_http_get_header(hm, "Range"); - range[0] = '\0'; - if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) { - // If range is specified like "400-", set second limit to content len - if (n == 1) r2 = cl - 1; - if (r1 > r2 || r2 >= cl) { - status = 416; - cl = 0; - mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n", - (int64_t) size); - } else { - status = 206; - cl = r2 - r1 + 1; - mg_snprintf(range, sizeof(range), - "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1, - (uint64_t) (r1 + cl - 1), (uint64_t) size); - fs->sk(fd->fd, r1); - } - } - mg_printf(c, - "HTTP/1.1 %d %s\r\n" - "Content-Type: %.*s\r\n" - "Etag: %s\r\n" - "Content-Length: %llu\r\n" - "%s%s%s\r\n", - status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, - etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "", - range, opts->extra_headers ? opts->extra_headers : ""); - if (mg_vcasecmp(&hm->method, "HEAD") == 0) { - c->is_draining = 1; - c->is_resp = 0; - mg_fs_close(fd); - } else { - // Track to-be-sent content length at the end of c->data, aligned - size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) / - sizeof(size_t) * sizeof(size_t)]; - c->pfn = static_cb; - c->pfn_data = fd; - *clp = cl; - } - } -} - -struct printdirentrydata { - struct mg_connection *c; - struct mg_http_message *hm; - const struct mg_http_serve_opts *opts; - const char *dir; -}; - -#if MG_ENABLE_DIRLIST -static void printdirentry(const char *name, void *userdata) { - struct printdirentrydata *d = (struct printdirentrydata *) userdata; - struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; - size_t size = 0; - time_t t = 0; - char path[MG_PATH_MAX], sz[40], mod[40]; - int flags, n = 0; - - // MG_DEBUG(("[%s] [%s]", d->dir, name)); - if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) > - sizeof(path)) { - MG_ERROR(("%s truncated", name)); - } else if ((flags = fs->st(path, &size, &t)) == 0) { - MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno)); - } else { - const char *slash = flags & MG_FS_DIR ? "/" : ""; - if (flags & MG_FS_DIR) { - mg_snprintf(sz, sizeof(sz), "%s", "[DIR]"); - } else { - mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size); - } -#if defined(MG_HTTP_DIRLIST_TIME_FMT) - { - char time_str[40]; - struct tm *time_info = localtime(&t); - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info); - mg_snprintf(mod, sizeof(mod), "%s", time_str); - } -#else - mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t); -#endif - n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); - mg_printf(d->c, - " %s%s" - "%s%s\n", - n, path, slash, name, slash, (unsigned long) t, mod, - flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz); - } -} - -static void listdir(struct mg_connection *c, struct mg_http_message *hm, - const struct mg_http_serve_opts *opts, char *dir) { - const char *sort_js_code = - ""; - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; - struct printdirentrydata d = {c, hm, opts, dir}; - char tmp[10], buf[MG_PATH_MAX]; - size_t off, n; - int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf, sizeof(buf), 0); - struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri; - - mg_printf(c, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html; charset=utf-8\r\n" - "%s" - "Content-Length: \r\n\r\n", - opts->extra_headers == NULL ? "" : opts->extra_headers); - off = c->send.len; // Start of body - mg_printf(c, - "Index of %.*s%s%s" - "" - "

Index of %.*s

" - "" - "" - "" - "" - "\n", - (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int) uri.len, - uri.ptr); - mg_printf(c, "%s", - " " - "\n"); - - fs->ls(dir, printdirentry, &d); - mg_printf(c, - "" - "
Name" - "ModifiedSize

..[DIR]

Mongoose v.%s
\n", - MG_VERSION); - n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off)); - if (n > sizeof(tmp)) n = 0; - memcpy(c->send.buf + off - 12, tmp, n); // Set content length - c->is_resp = 0; // Mark response end -} -#endif - -// Resolve requested file into `path` and return its fs->st() result -static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, - struct mg_fs *fs, struct mg_str url, struct mg_str dir, - char *path, size_t path_size) { - int flags, tmp; - // Append URI to the root_dir, and sanitize it - size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr); - if (n + 2 >= path_size) { - mg_http_reply(c, 400, "", "Exceeded path size"); - return -1; - } - path[path_size - 1] = '\0'; - // Terminate root dir with slash - if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0'; - if (url.len < hm->uri.len) { - mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n, - path_size - n, 0); - } - path[path_size - 1] = '\0'; // Double-check - if (!mg_path_is_sane(path)) { - mg_http_reply(c, 400, "", "Invalid path"); - return -1; - } - n = strlen(path); - while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes - flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path, NULL, NULL); - MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.ptr, path, - flags)); - if (flags == 0) { - // Do nothing - let's caller decide - } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 && - hm->uri.ptr[hm->uri.len - 1] != '/') { - mg_printf(c, - "HTTP/1.1 301 Moved\r\n" - "Location: %.*s/\r\n" - "Content-Length: 0\r\n" - "\r\n", - (int) hm->uri.len, hm->uri.ptr); - c->is_resp = 0; - flags = -1; - } else if (flags & MG_FS_DIR) { - if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 && - (tmp = fs->st(path, NULL, NULL)) != 0) || - (mg_snprintf(path + n, path_size - n, "/index.shtml") > 0 && - (tmp = fs->st(path, NULL, NULL)) != 0))) { - flags = tmp; - } else if ((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX ".gz") > - 0 && - (tmp = fs->st(path, NULL, NULL)) != - 0)) { // check for gzipped index - flags = tmp; - path[n + 1 + strlen(MG_HTTP_INDEX)] = - '\0'; // Remove appended .gz in index file name - } else { - path[n] = '\0'; // Remove appended index file name - } - } - return flags; -} - -static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, - const struct mg_http_serve_opts *opts, char *path, - size_t path_size) { - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; - struct mg_str k, v, part, s = mg_str(opts->root_dir), u = {NULL, 0}, p = u; - while (mg_span(s, &part, &s, ',')) { - if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0); - if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v; - if (hm->uri.len < k.len) continue; - if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue; - u = k, p = v; - } - return uri_to_path2(c, hm, fs, u, p, path, path_size); -} - -void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, - const struct mg_http_serve_opts *opts) { - char path[MG_PATH_MAX]; - const char *sp = opts->ssi_pattern; - int flags = uri_to_path(c, hm, opts, path, sizeof(path)); - if (flags < 0) { - // Do nothing: the response has already been sent by uri_to_path() - } else if (flags & MG_FS_DIR) { -#if MG_ENABLE_DIRLIST - listdir(c, hm, opts, path); -#else - mg_http_reply(c, 403, "", "Forbidden\n"); -#endif - } else if (flags && sp != NULL && - mg_globmatch(sp, strlen(sp), path, strlen(path))) { - mg_http_serve_ssi(c, opts->root_dir, path); - } else { - mg_http_serve_file(c, hm, path, opts); - } -} - -static bool mg_is_url_safe(int c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~'; -} - -size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) { - size_t i, n = 0; - for (i = 0; i < sl; i++) { - int c = *(unsigned char *) &s[i]; - if (n + 4 >= len) return 0; - if (mg_is_url_safe(c)) { - buf[n++] = s[i]; - } else { - buf[n++] = '%'; - mg_hex(&s[i], 1, &buf[n]); - n += 2; - } - } - if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate the destination - if (len > 0) buf[len - 1] = '\0'; // Always. - return n; -} - -void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen, - char *pass, size_t passlen) { - struct mg_str *v = mg_http_get_header(hm, "Authorization"); - user[0] = pass[0] = '\0'; - if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) { - char buf[256]; - size_t n = mg_base64_decode(v->ptr + 6, v->len - 6, buf, sizeof(buf)); - const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0); - if (p != NULL) { - mg_snprintf(user, userlen, "%.*s", p - buf, buf); - mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1, p + 1); - } - } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) { - mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7); - } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { - struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); - if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr); - } else { - mg_http_get_var(&hm->query, "access_token", pass, passlen); - } -} - -static struct mg_str stripquotes(struct mg_str s) { - return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"' - ? mg_str_n(s.ptr + 1, s.len - 2) - : s; -} - -struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { - size_t i; - for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) { - if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) { - const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len]; - int q = p < x && *p == '"' ? 1 : 0; - while (p < x && - (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ',')) - p++; - // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len, - // v.ptr, (int) (p - b), b)); - return stripquotes(mg_str_n(b, (size_t) (p - b + q))); - } - } - return mg_str_n(NULL, 0); -} - -bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { - return mg_match(hm->uri, mg_str(glob), NULL); -} - -long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, - struct mg_fs *fs, const char *dir, size_t max_size) { - char buf[20] = "0", file[40], path[MG_PATH_MAX]; - long res = 0, offset; - mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); - mg_http_get_var(&hm->query, "file", file, sizeof(file)); - offset = strtol(buf, NULL, 0); - mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file); - if (hm->body.len == 0) { - mg_http_reply(c, 200, "", "%ld", res); // Nothing to write - } else if (file[0] == '\0') { - mg_http_reply(c, 400, "", "file required"); - res = -1; - } else if (mg_path_is_sane(file) == false) { - mg_http_reply(c, 400, "", "%s: invalid file", file); - res = -2; - } else if (offset < 0) { - mg_http_reply(c, 400, "", "offset required"); - res = -3; - } else if ((size_t) offset + hm->body.len > max_size) { - mg_http_reply(c, 400, "", "%s: over max size of %lu", path, - (unsigned long) max_size); - res = -4; - } else { - struct mg_fd *fd; - size_t current_size = 0; - MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset)); - if (offset == 0) fs->rm(path); // If offset if 0, truncate file - fs->st(path, ¤t_size, NULL); - if (offset > 0 && current_size != (size_t) offset) { - mg_http_reply(c, 400, "", "%s: offset mismatch", path); - res = -5; - } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { - mg_http_reply(c, 400, "", "open(%s): %d", path, errno); - res = -6; - } else { - res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len); - mg_fs_close(fd); - mg_http_reply(c, 200, "", "%ld", res); - } - } - return res; -} - -int mg_http_status(const struct mg_http_message *hm) { - return atoi(hm->uri.ptr); -} - -static bool is_hex_digit(int c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'F'); -} - -static int skip_chunk(const char *buf, int len, int *pl, int *dl) { - int i = 0, n = 0; - if (len < 3) return 0; - while (i < len && is_hex_digit(buf[i])) i++; - if (i == 0) return -1; // Error, no length specified - if (i > (int) sizeof(int) * 2) return -1; // Chunk length is too big - if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return -1; // Error - n = (int) mg_unhexn(buf, (size_t) i); // Decode chunk length - if (n < 0) return -1; // Error - if (n > len - i - 4) return 0; // Chunk not yet fully buffered - if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return -1; // Error - *pl = i + 2, *dl = n; - return i + 2 + n + 2; -} - -static void http_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ || ev == MG_EV_CLOSE) { - struct mg_http_message hm; - size_t ofs = 0; // Parsing offset - - while (c->is_resp == 0 && ofs < c->recv.len) { - const char *buf = (char *) c->recv.buf + ofs; - int n = mg_http_parse(buf, c->recv.len - ofs, &hm); - struct mg_str *te; // Transfer - encoding header - bool is_chunked = false; - if (n < 0) { - // We don't use mg_error() here, to avoid closing pipelined requests - // prematurely, see #2592 - MG_ERROR(("HTTP parse, %lu bytes", c->recv.len)); - c->is_draining = 1; - mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs); - c->recv.len = 0; - return; - } - if (n == 0) break; // Request is not buffered yet - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length - hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG - hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr); - } - if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) { - if (mg_vcasecmp(te, "chunked") == 0) { - is_chunked = true; - } else { - mg_error(c, "Invalid Transfer-Encoding"); // See #2460 - return; - } - } - - if (is_chunked) { - // For chunked data, strip off prefixes and suffixes from chunks - // and relocate them right after the headers, then report a message - char *s = (char *) c->recv.buf + ofs + n; - int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs - (size_t) n); - - // Find zero-length chunk (the end of the body) - while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o += cl; - if (cl == 0) break; // No zero-len chunk, buffer more data - if (cl < 0) { - mg_error(c, "Invalid chunk"); - break; - } - - // Zero chunk found. Second pass: strip + relocate - o = 0, hm.body.len = 0, hm.message.len = (size_t) n; - while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) { - memmove(s + hm.body.len, s + o + pl, (size_t) dl); - o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t) dl; - if (dl == 0) break; - } - ofs += (size_t) (n + o); - } else { // Normal, non-chunked data - size_t len = c->recv.len - ofs - (size_t) n; - if (hm.body.len > len) break; // Buffer more data - ofs += (size_t) n + hm.body.len; - } - - if (c->is_accepted) c->is_resp = 1; // Start generating response - mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp - } - if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete processed data - } - (void) ev_data; -} - -static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_HTTP_MSG) { - struct mg_http_message *hm = (struct mg_http_message *) ev_data; - if (mg_http_match_uri(hm, "/quit")) { - mg_http_reply(c, 200, "", "ok\n"); - c->is_draining = 1; - c->data[0] = 'X'; - } else if (mg_http_match_uri(hm, "/debug")) { - int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG); - mg_log_set(level); - mg_http_reply(c, 200, "", "Debug level set to %d\n", level); - } else { - mg_http_reply(c, 200, "", "hi\n"); - } - } else if (ev == MG_EV_CLOSE) { - if (c->data[0] == 'X') *(bool *) c->fn_data = true; - } -} - -void mg_hello(const char *url) { - struct mg_mgr mgr; - bool done = false; - mg_mgr_init(&mgr); - if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true; - while (done == false) mg_mgr_poll(&mgr, 100); - mg_mgr_free(&mgr); -} - -struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); - if (c != NULL) c->pfn = http_cb; - return c; -} - -struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); - if (c != NULL) c->pfn = http_cb; - return c; -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/iobuf.c" -#endif - - - - - -static size_t roundup(size_t size, size_t align) { - return align == 0 ? size : (size + align - 1) / align * align; -} - -int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { - int ok = 1; - new_size = roundup(new_size, io->align); - if (new_size == 0) { - mg_bzero(io->buf, io->size); - free(io->buf); - io->buf = NULL; - io->len = io->size = 0; - } else if (new_size != io->size) { - // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the - // porting to some obscure platforms like FreeRTOS - void *p = calloc(1, new_size); - if (p != NULL) { - size_t len = new_size < io->len ? new_size : io->len; - if (len > 0 && io->buf != NULL) memmove(p, io->buf, len); - mg_bzero(io->buf, io->size); - free(io->buf); - io->buf = (unsigned char *) p; - io->size = new_size; - } else { - ok = 0; - MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); - } - } - return ok; -} - -int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) { - io->buf = NULL; - io->align = align; - io->size = io->len = 0; - return mg_iobuf_resize(io, size); -} - -size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf, - size_t len) { - size_t new_size = roundup(io->len + len, io->align); - mg_iobuf_resize(io, new_size); // Attempt to resize - if (new_size != io->size) len = 0; // Resize failure, append nothing - if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs); - if (buf != NULL) memmove(io->buf + ofs, buf, len); - if (ofs > io->len) io->len += ofs - io->len; - io->len += len; - return len; -} - -size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) { - if (ofs > io->len) ofs = io->len; - if (ofs + len > io->len) len = io->len - ofs; - if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len); - if (io->buf) mg_bzero(io->buf + io->len - len, len); - io->len -= len; - return len; -} - -void mg_iobuf_free(struct mg_iobuf *io) { - mg_iobuf_resize(io, 0); -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/json.c" -#endif - - - - -static const char *escapeseq(int esc) { - return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\""; -} - -static char json_esc(int c, int esc) { - const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc); - for (p = esc1; *p != '\0'; p++) { - if (*p == c) return esc2[p - esc1]; - } - return 0; -} - -static int mg_pass_string(const char *s, int len) { - int i; - for (i = 0; i < len; i++) { - if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) { - i++; - } else if (s[i] == '\0') { - return MG_JSON_INVALID; - } else if (s[i] == '"') { - return i; - } - } - return MG_JSON_INVALID; -} - -static double mg_atod(const char *p, int len, int *numlen) { - double d = 0.0; - int i = 0, sign = 1; - - // Sign - if (i < len && *p == '-') { - sign = -1, i++; - } else if (i < len && *p == '+') { - i++; - } - - // Decimal - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { - d *= 10.0; - d += p[i] - '0'; - } - d *= sign; - - // Fractional - if (i < len && p[i] == '.') { - double frac = 0.0, base = 0.1; - i++; - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { - frac += base * (p[i] - '0'); - base /= 10.0; - } - d += frac * sign; - } - - // Exponential - if (i < len && (p[i] == 'e' || p[i] == 'E')) { - int j, exp = 0, minus = 0; - i++; - if (i < len && p[i] == '-') minus = 1, i++; - if (i < len && p[i] == '+') i++; - while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308) - exp = exp * 10 + (p[i++] - '0'); - if (minus) exp = -exp; - for (j = 0; j < exp; j++) d *= 10.0; - for (j = 0; j < -exp; j++) d /= 10.0; - } - - if (numlen != NULL) *numlen = i; - return d; -} - -// Iterate over object or array elements -size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, - struct mg_str *val) { - if (ofs >= obj.len) { - ofs = 0; // Out of boundaries, stop scanning - } else if (obj.len < 2 || (*obj.ptr != '{' && *obj.ptr != '[')) { - ofs = 0; // Not an array or object, stop - } else { - struct mg_str sub = mg_str_n(obj.ptr + ofs, obj.len - ofs); - if (ofs == 0) ofs++, sub.ptr++, sub.len--; - if (*obj.ptr == '[') { // Iterate over an array - int n = 0, o = mg_json_get(sub, "$", &n); - if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { - ofs = 0; // Error parsing key, stop scanning - } else { - if (key) *key = mg_str_n(NULL, 0); - if (val) *val = mg_str_n(sub.ptr + o, (size_t) n); - ofs = (size_t) (&sub.ptr[o + n] - obj.ptr); - } - } else { // Iterate over an object - int n = 0, o = mg_json_get(sub, "$", &n); - if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { - ofs = 0; // Error parsing key, stop scanning - } else { - if (key) *key = mg_str_n(sub.ptr + o, (size_t) n); - sub.ptr += o + n, sub.len -= (size_t) (o + n); - while (sub.len > 0 && *sub.ptr != ':') sub.len--, sub.ptr++; - if (sub.len > 0 && *sub.ptr == ':') sub.len--, sub.ptr++; - n = 0, o = mg_json_get(sub, "$", &n); - if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { - ofs = 0; // Error parsing value, stop scanning - } else { - if (val) *val = mg_str_n(sub.ptr + o, (size_t) n); - ofs = (size_t) (&sub.ptr[o + n] - obj.ptr); - } - } - } - // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.ptr)); - while (ofs && ofs < obj.len && - (obj.ptr[ofs] == ' ' || obj.ptr[ofs] == '\t' || - obj.ptr[ofs] == '\n' || obj.ptr[ofs] == '\r')) { - ofs++; - } - if (ofs && ofs < obj.len && obj.ptr[ofs] == ',') ofs++; - if (ofs > obj.len) ofs = 0; - } - return ofs; -} - -int mg_json_get(struct mg_str json, const char *path, int *toklen) { - const char *s = json.ptr; - int len = (int) json.len; - enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; - unsigned char nesting[MG_JSON_MAX_DEPTH]; - int i = 0; // Current offset in `s` - int j = 0; // Offset in `s` we're looking for (return value) - int depth = 0; // Current depth (nesting level) - int ed = 0; // Expected depth - int pos = 1; // Current position in `path` - int ci = -1, ei = -1; // Current and expected index in array - - if (toklen) *toklen = 0; - if (path[0] != '$') return MG_JSON_INVALID; - -#define MG_CHECKRET(x) \ - do { \ - if (depth == ed && path[pos] == '\0' && ci == ei) { \ - if (toklen) *toklen = i - j + 1; \ - return j; \ - } \ - } while (0) - -// In the ascii table, the distance between `[` and `]` is 2. -// Ditto for `{` and `}`. Hence +2 in the code below. -#define MG_EOO(x) \ - do { \ - if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \ - if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \ - depth--; \ - MG_CHECKRET(x); \ - } while (0) - - for (i = 0; i < len; i++) { - unsigned char c = ((unsigned char *) s)[i]; - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; - switch (expecting) { - case S_VALUE: - // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei); - if (depth == ed) j = i; - if (c == '{') { - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; - if (depth == ed && path[pos] == '.' && ci == ei) { - // If we start the object, reset array indices - ed++, pos++, ci = ei = -1; - } - nesting[depth++] = c; - expecting = S_KEY; - break; - } else if (c == '[') { - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; - if (depth == ed && path[pos] == '[' && ei == ci) { - ed++, pos++, ci = 0; - for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) { - ei *= 10; - ei += path[pos] - '0'; - } - if (path[pos] != 0) pos++; - } - nesting[depth++] = c; - break; - } else if (c == ']' && depth > 0) { // Empty array - MG_EOO(']'); - } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { - i += 3; - } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { - i += 3; - } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { - i += 4; - } else if (c == '-' || ((c >= '0' && c <= '9'))) { - int numlen = 0; - mg_atod(&s[i], len - i, &numlen); - i += numlen - 1; - } else if (c == '"') { - int n = mg_pass_string(&s[i + 1], len - i - 1); - if (n < 0) return n; - i += n + 1; - } else { - return MG_JSON_INVALID; - } - MG_CHECKRET('V'); - if (depth == ed && ei >= 0) ci++; - expecting = S_COMMA_OR_EOO; - break; - - case S_KEY: - if (c == '"') { - int n = mg_pass_string(&s[i + 1], len - i - 1); - if (n < 0) return n; - if (i + 1 + n >= len) return MG_JSON_NOT_FOUND; - if (depth < ed) return MG_JSON_NOT_FOUND; - if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND; - // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n, - // &s[i + 1], n, depth, ed, ci, ei); - // NOTE(cpq): in the check sequence below is important. - // strncmp() must go first: it fails fast if the remaining length - // of the path is smaller than `n`. - if (depth == ed && path[pos - 1] == '.' && - strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 && - (path[pos + n] == '\0' || path[pos + n] == '.' || - path[pos + n] == '[')) { - pos += n; - } - i += n + 1; - expecting = S_COLON; - } else if (c == '}') { // Empty object - MG_EOO('}'); - expecting = S_COMMA_OR_EOO; - if (depth == ed && ei >= 0) ci++; - } else { - return MG_JSON_INVALID; - } - break; - - case S_COLON: - if (c == ':') { - expecting = S_VALUE; - } else { - return MG_JSON_INVALID; - } - break; - - case S_COMMA_OR_EOO: - if (depth <= 0) { - return MG_JSON_INVALID; - } else if (c == ',') { - expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; - } else if (c == ']' || c == '}') { - if (depth == ed && c == '}' && path[pos - 1] == '.') - return MG_JSON_NOT_FOUND; - if (depth == ed && c == ']' && path[pos - 1] == ',') - return MG_JSON_NOT_FOUND; - MG_EOO('O'); - if (depth == ed && ei >= 0) ci++; - } else { - return MG_JSON_INVALID; - } - break; - } - } - return MG_JSON_NOT_FOUND; -} - -struct mg_str mg_json_get_tok(struct mg_str json, const char *path) { - int len = 0, ofs = mg_json_get(json, path, &len); - return mg_str_n(ofs < 0 ? NULL : json.ptr + ofs, - (size_t) (len < 0 ? 0 : len)); -} - -bool mg_json_get_num(struct mg_str json, const char *path, double *v) { - int n, toklen, found = 0; - if ((n = mg_json_get(json, path, &toklen)) >= 0 && - (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) { - if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL); - found = 1; - } - return found; -} - -bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { - int found = 0, off = mg_json_get(json, path, NULL); - if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) { - if (v != NULL) *v = json.ptr[off] == 't'; - found = 1; - } - return found; -} - -bool mg_json_unescape(struct mg_str s, char *to, size_t n) { - size_t i, j; - for (i = 0, j = 0; i < s.len && j < n; i++, j++) { - if (s.ptr[i] == '\\' && i + 5 < s.len && s.ptr[i + 1] == 'u') { - // \uXXXX escape. We could process a simple one-byte chars - // \u00xx from the ASCII range. More complex chars would require - // dragging in a UTF8 library, which is too much for us - if (s.ptr[i + 2] != '0' || s.ptr[i + 3] != '0') return false; // Give up - ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s.ptr + i + 4, 2); - - i += 5; - } else if (s.ptr[i] == '\\' && i + 1 < s.len) { - char c = json_esc(s.ptr[i + 1], 0); - if (c == 0) return false; - to[j] = c; - i++; - } else { - to[j] = s.ptr[i]; - } - } - if (j >= n) return false; - if (n > 0) to[j] = '\0'; - return true; -} - -char *mg_json_get_str(struct mg_str json, const char *path) { - char *result = NULL; - int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && len > 1 && json.ptr[off] == '"') { - if ((result = (char *) calloc(1, (size_t) len)) != NULL && - !mg_json_unescape(mg_str_n(json.ptr + off + 1, (size_t) (len - 2)), - result, (size_t) len)) { - free(result); - result = NULL; - } - } - return result; -} - -char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) { - char *result = NULL; - int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && json.ptr[off] == '"' && len > 1 && - (result = (char *) calloc(1, (size_t) len)) != NULL) { - size_t k = mg_base64_decode(json.ptr + off + 1, (size_t) (len - 2), result, - (size_t) len); - if (slen != NULL) *slen = (int) k; - } - return result; -} - -char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) { - char *result = NULL; - int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && json.ptr[off] == '"' && len > 1 && - (result = (char *) calloc(1, (size_t) len / 2)) != NULL) { - mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *) result); - result[len / 2 - 1] = '\0'; - if (slen != NULL) *slen = len / 2 - 1; - } - return result; -} - -long mg_json_get_long(struct mg_str json, const char *path, long dflt) { - double dv; - long result = dflt; - if (mg_json_get_num(json, path, &dv)) result = (long) dv; - return result; -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/log.c" -#endif - - - - - -int mg_log_level = MG_LL_INFO; -static mg_pfn_t s_log_func = mg_pfn_stdout; -static void *s_log_func_param = NULL; - -void mg_log_set_fn(mg_pfn_t fn, void *param) { - s_log_func = fn; - s_log_func_param = param; -} - -static void logc(unsigned char c) { - s_log_func((char) c, s_log_func_param); -} - -static void logs(const char *buf, size_t len) { - size_t i; - for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]); -} - -#if MG_ENABLE_CUSTOM_LOG -// Let user define their own mg_log_prefix() and mg_log() -#else -void mg_log_prefix(int level, const char *file, int line, const char *fname) { - const char *p = strrchr(file, '/'); - char buf[41]; - size_t n; - if (p == NULL) p = strrchr(file, '\\'); - n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level, - p == NULL ? file : p + 1, line, fname); - if (n > sizeof(buf) - 2) n = sizeof(buf) - 2; - while (n < sizeof(buf)) buf[n++] = ' '; - logs(buf, n - 1); -} - -void mg_log(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap); - va_end(ap); - logs("\r\n", 2); -} -#endif - -static unsigned char nibble(unsigned c) { - return (unsigned char) (c < 10 ? c + '0' : c + 'W'); -} - -#define ISPRINT(x) ((x) >= ' ' && (x) <= '~') -void mg_hexdump(const void *buf, size_t len) { - const unsigned char *p = (const unsigned char *) buf; - unsigned char ascii[16], alen = 0; - size_t i; - for (i = 0; i < len; i++) { - if ((i % 16) == 0) { - // Print buffered ascii chars - if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0; - // Print hex address, then \t - logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)), - logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3); - } - logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two nibbles, e.g. c5 - logc(' '); // Space after hex number - ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf - } - while (alen < 16) logs(" ", 3), ascii[alen++] = ' '; - logs(" ", 2), logs((char *) ascii, 16), logc('\n'); -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/md5.c" -#endif - - - -// This code implements the MD5 message-digest algorithm. -// The algorithm is due to Ron Rivest. This code was -// written by Colin Plumb in 1993, no copyright is claimed. -// This code is in the public domain; do with it what you wish. -// -// Equivalent code is available from RSA Data Security, Inc. -// This code has been tested against that, and is equivalent, -// except that you don't need to include two pages of legalese -// with every copy. -// -// To compute the message digest of a chunk of bytes, declare an -// MD5Context structure, pass it to MD5Init, call MD5Update as -// needed on buffers full of bytes, and then call MD5Final, which -// will fill a supplied 16-byte array with the digest. - -#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5 - -static void mg_byte_reverse(unsigned char *buf, unsigned longs) { - if (MG_BIG_ENDIAN) { - do { - uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | - ((unsigned) buf[1] << 8 | buf[0]); - *(uint32_t *) buf = t; - buf += 4; - } while (--longs); - } else { - (void) buf, (void) longs; // Little endian. Do nothing - } -} - -#define F1(x, y, z) (z ^ (x & (y ^ z))) -#define F2(x, y, z) F1(z, x, y) -#define F3(x, y, z) (x ^ y ^ z) -#define F4(x, y, z) (y ^ (x | ~z)) - -#define MD5STEP(f, w, x, y, z, data, s) \ - (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) - -/* - * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious - * initialization constants. - */ -void mg_md5_init(mg_md5_ctx *ctx) { - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - - ctx->bits[0] = 0; - ctx->bits[1] = 0; -} - -static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) { - uint32_t a, b, c, d; - - a = buf[0]; - b = buf[1]; - c = buf[2]; - d = buf[3]; - - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); - - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); - - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); - - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); - - buf[0] += a; - buf[1] += b; - buf[2] += c; - buf[3] += d; -} - -void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) { - uint32_t t; - - t = ctx->bits[0]; - if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++; - ctx->bits[1] += (uint32_t) len >> 29; - - t = (t >> 3) & 0x3f; - - if (t) { - unsigned char *p = (unsigned char *) ctx->in + t; - - t = 64 - t; - if (len < t) { - memcpy(p, buf, len); - return; - } - memcpy(p, buf, t); - mg_byte_reverse(ctx->in, 16); - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); - buf += t; - len -= t; - } - - while (len >= 64) { - memcpy(ctx->in, buf, 64); - mg_byte_reverse(ctx->in, 16); - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); - buf += 64; - len -= 64; - } - - memcpy(ctx->in, buf, len); -} - -void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) { - unsigned count; - unsigned char *p; - uint32_t *a; - - count = (ctx->bits[0] >> 3) & 0x3F; - - p = ctx->in + count; - *p++ = 0x80; - count = 64 - 1 - count; - if (count < 8) { - memset(p, 0, count); - mg_byte_reverse(ctx->in, 16); - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); - memset(ctx->in, 0, 56); - } else { - memset(p, 0, count - 8); - } - mg_byte_reverse(ctx->in, 14); - - a = (uint32_t *) ctx->in; - a[14] = ctx->bits[0]; - a[15] = ctx->bits[1]; - - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); - mg_byte_reverse((unsigned char *) ctx->buf, 4); - memcpy(digest, ctx->buf, 16); - memset((char *) ctx, 0, sizeof(*ctx)); -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/mqtt.c" -#endif - - - - - - - - -#define MQTT_CLEAN_SESSION 0x02 -#define MQTT_HAS_WILL 0x04 -#define MQTT_WILL_RETAIN 0x20 -#define MQTT_HAS_PASSWORD 0x40 -#define MQTT_HAS_USER_NAME 0x80 - -struct mg_mqtt_pmap { - uint8_t id; - uint8_t type; -}; - -static const struct mg_mqtt_pmap s_prop_map[] = { - {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT}, - {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA}, - {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT}, - {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT}, - {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT}, - {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA}, - {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT}, - {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT}, - {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT}, - {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT}, - {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR}, - {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT}, - {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}}; - -void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, - uint32_t len) { - uint8_t buf[1 + sizeof(len)], *vlen = &buf[1]; - buf[0] = (uint8_t) ((cmd << 4) | flags); - do { - *vlen = len % 0x80; - len /= 0x80; - if (len > 0) *vlen |= 0x80; - vlen++; - } while (len > 0 && vlen < &buf[sizeof(buf)]); - mg_send(c, buf, (size_t) (vlen - buf)); -} - -static void mg_send_u16(struct mg_connection *c, uint16_t value) { - mg_send(c, &value, sizeof(value)); -} - -static void mg_send_u32(struct mg_connection *c, uint32_t value) { - mg_send(c, &value, sizeof(value)); -} - -static uint8_t varint_size(size_t length) { - uint8_t bytes_needed = 0; - do { - bytes_needed++; - length /= 0x80; - } while (length > 0); - return bytes_needed; -} - -static size_t encode_varint(uint8_t *buf, size_t value) { - size_t len = 0; - - do { - uint8_t byte = (uint8_t) (value % 128); - value /= 128; - if (value > 0) byte |= 0x80; - buf[len++] = byte; - } while (value > 0); - - return len; -} - -static size_t decode_varint(const uint8_t *buf, size_t len, size_t *value) { - size_t multiplier = 1, offset; - *value = 0; - - for (offset = 0; offset < 4 && offset < len; offset++) { - uint8_t encoded_byte = buf[offset]; - *value += (encoded_byte & 0x7f) * multiplier; - multiplier *= 128; - - if ((encoded_byte & 0x80) == 0) return offset + 1; - } - - return 0; -} - -static int mqtt_prop_type_by_id(uint8_t prop_id) { - size_t i, num_properties = sizeof(s_prop_map) / sizeof(s_prop_map[0]); - for (i = 0; i < num_properties; ++i) { - if (s_prop_map[i].id == prop_id) return s_prop_map[i].type; - } - return -1; // Property ID not found -} - -// Returns the size of the properties section, without the -// size of the content's length -static size_t get_properties_length(struct mg_mqtt_prop *props, size_t count) { - size_t i, size = 0; - for (i = 0; i < count; i++) { - size++; // identifier - switch (mqtt_prop_type_by_id(props[i].id)) { - case MQTT_PROP_TYPE_STRING_PAIR: - size += (uint32_t) (props[i].val.len + props[i].key.len + - 2 * sizeof(uint16_t)); - break; - case MQTT_PROP_TYPE_STRING: - size += (uint32_t) (props[i].val.len + sizeof(uint16_t)); - break; - case MQTT_PROP_TYPE_BINARY_DATA: - size += (uint32_t) (props[i].val.len + sizeof(uint16_t)); - break; - case MQTT_PROP_TYPE_VARIABLE_INT: - size += varint_size((uint32_t) props[i].iv); - break; - case MQTT_PROP_TYPE_INT: - size += (uint32_t) sizeof(uint32_t); - break; - case MQTT_PROP_TYPE_SHORT: - size += (uint32_t) sizeof(uint16_t); - break; - case MQTT_PROP_TYPE_BYTE: - size += (uint32_t) sizeof(uint8_t); - break; - default: - return size; // cannot parse further down - } - } - - return size; -} - -// returns the entire size of the properties section, including the -// size of the variable length of the content -static size_t get_props_size(struct mg_mqtt_prop *props, size_t count) { - size_t size = get_properties_length(props, count); - size += varint_size(size); - return size; -} - -static void mg_send_mqtt_properties(struct mg_connection *c, - struct mg_mqtt_prop *props, size_t nprops) { - size_t total_size = get_properties_length(props, nprops); - uint8_t buf_v[4] = {0, 0, 0, 0}; - uint8_t buf[4] = {0, 0, 0, 0}; - size_t i, len = encode_varint(buf, total_size); - - mg_send(c, buf, (size_t) len); - for (i = 0; i < nprops; i++) { - mg_send(c, &props[i].id, sizeof(props[i].id)); - switch (mqtt_prop_type_by_id(props[i].id)) { - case MQTT_PROP_TYPE_STRING_PAIR: - mg_send_u16(c, mg_htons((uint16_t) props[i].key.len)); - mg_send(c, props[i].key.ptr, props[i].key.len); - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.ptr, props[i].val.len); - break; - case MQTT_PROP_TYPE_BYTE: - mg_send(c, &props[i].iv, sizeof(uint8_t)); - break; - case MQTT_PROP_TYPE_SHORT: - mg_send_u16(c, mg_htons((uint16_t) props[i].iv)); - break; - case MQTT_PROP_TYPE_INT: - mg_send_u32(c, mg_htonl((uint32_t) props[i].iv)); - break; - case MQTT_PROP_TYPE_STRING: - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.ptr, props[i].val.len); - break; - case MQTT_PROP_TYPE_BINARY_DATA: - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.ptr, props[i].val.len); - break; - case MQTT_PROP_TYPE_VARIABLE_INT: - len = encode_varint(buf_v, props[i].iv); - mg_send(c, buf_v, (size_t) len); - break; - } - } -} - -size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, - size_t ofs) { - uint8_t *i = (uint8_t *) msg->dgram.ptr + msg->props_start + ofs; - uint8_t *end = (uint8_t *) msg->dgram.ptr + msg->dgram.len; - size_t new_pos = ofs, len; - prop->id = i[0]; - - if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size) - return 0; - i++, new_pos++; - - switch (mqtt_prop_type_by_id(prop->id)) { - case MQTT_PROP_TYPE_STRING_PAIR: - prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->key.ptr = (char *) i + 2; - i += 2 + prop->key.len; - prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.ptr = (char *) i + 2; - new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len; - break; - case MQTT_PROP_TYPE_BYTE: - prop->iv = (uint8_t) i[0]; - new_pos++; - break; - case MQTT_PROP_TYPE_SHORT: - prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - new_pos += sizeof(uint16_t); - break; - case MQTT_PROP_TYPE_INT: - prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) | - ((uint32_t) i[2] << 8) | i[3]; - new_pos += sizeof(uint32_t); - break; - case MQTT_PROP_TYPE_STRING: - prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.ptr = (char *) i + 2; - new_pos += 2 + prop->val.len; - break; - case MQTT_PROP_TYPE_BINARY_DATA: - prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.ptr = (char *) i + 2; - new_pos += 2 + prop->val.len; - break; - case MQTT_PROP_TYPE_VARIABLE_INT: - len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv); - new_pos = (!len) ? 0 : new_pos + len; - break; - default: - new_pos = 0; - } - - return new_pos; -} - -void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) { - char rnd[10], client_id[21]; - struct mg_str cid = opts->client_id; - size_t total_len = 7 + 1 + 2 + 2; - uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0}; - - if (cid.len == 0) { - mg_random(rnd, sizeof(rnd)); - mg_hex(rnd, sizeof(rnd), client_id); - client_id[sizeof(client_id) - 1] = '\0'; - cid = mg_str(client_id); - } - - if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4 (3.1.1) - c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag - hdr[7] = (uint8_t) ((opts->qos & 3) << 3); // Connection flags - if (opts->user.len > 0) { - total_len += 2 + (uint32_t) opts->user.len; - hdr[7] |= MQTT_HAS_USER_NAME; - } - if (opts->pass.len > 0) { - total_len += 2 + (uint32_t) opts->pass.len; - hdr[7] |= MQTT_HAS_PASSWORD; - } - if (opts->topic.len > 0 && opts->message.len > 0) { - total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len; - hdr[7] |= MQTT_HAS_WILL; - } - if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION; - if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN; - total_len += (uint32_t) cid.len; - if (c->is_mqtt5) { - total_len += get_props_size(opts->props, opts->num_props); - if (hdr[7] & MQTT_HAS_WILL) - total_len += get_props_size(opts->will_props, opts->num_will_props); - } - - mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len); - mg_send(c, hdr, sizeof(hdr)); - // keepalive == 0 means "do not disconnect us!" - mg_send_u16(c, mg_htons((uint16_t) opts->keepalive)); - - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); - - mg_send_u16(c, mg_htons((uint16_t) cid.len)); - mg_send(c, cid.ptr, cid.len); - - if (hdr[7] & MQTT_HAS_WILL) { - if (c->is_mqtt5) - mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props); - - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.ptr, opts->topic.len); - mg_send_u16(c, mg_htons((uint16_t) opts->message.len)); - mg_send(c, opts->message.ptr, opts->message.len); - } - if (opts->user.len > 0) { - mg_send_u16(c, mg_htons((uint16_t) opts->user.len)); - mg_send(c, opts->user.ptr, opts->user.len); - } - if (opts->pass.len > 0) { - mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)); - mg_send(c, opts->pass.ptr, opts->pass.len); - } -} - -void mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { - uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0)); - size_t len = 2 + opts->topic.len + opts->message.len; - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len, - (char *) opts->topic.ptr, (int) opts->message.len, - (char *) opts->message.ptr)); - if (opts->qos > 0) len += 2; - if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props); - - mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len); - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.ptr, opts->topic.len); - if (opts->qos > 0) { - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; - mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); - } - - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); - - if (opts->message.len > 0) mg_send(c, opts->message.ptr, opts->message.len); -} - -void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { - uint8_t qos_ = opts->qos & 3; - size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0; - size_t len = 2 + opts->topic.len + 2 + 1 + plen; - - mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len); - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; - mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); - - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.ptr, opts->topic.len); - mg_send(c, &qos_, sizeof(qos_)); -} - -int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, - struct mg_mqtt_message *m) { - uint8_t lc = 0, *p, *end; - uint32_t n = 0, len_len = 0; - - memset(m, 0, sizeof(*m)); - m->dgram.ptr = (char *) buf; - if (len < 2) return MQTT_INCOMPLETE; - m->cmd = (uint8_t) (buf[0] >> 4); - m->qos = (buf[0] >> 1) & 3; - - n = len_len = 0; - p = (uint8_t *) buf + 1; - while ((size_t) (p - buf) < len) { - lc = *((uint8_t *) p++); - n += (uint32_t) ((lc & 0x7f) << 7 * len_len); - len_len++; - if (!(lc & 0x80)) break; - if (len_len >= 4) return MQTT_MALFORMED; - } - end = p + n; - if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE; - m->dgram.len = (size_t) (end - buf); - - switch (m->cmd) { - case MQTT_CMD_CONNACK: - if (end - p < 2) return MQTT_MALFORMED; - m->ack = p[1]; - break; - case MQTT_CMD_PUBACK: - case MQTT_CMD_PUBREC: - case MQTT_CMD_PUBREL: - case MQTT_CMD_PUBCOMP: - case MQTT_CMD_SUBSCRIBE: - case MQTT_CMD_SUBACK: - case MQTT_CMD_UNSUBSCRIBE: - case MQTT_CMD_UNSUBACK: - if (p + 2 > end) return MQTT_MALFORMED; - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); - p += 2; - break; - case MQTT_CMD_PUBLISH: { - if (p + 2 > end) return MQTT_MALFORMED; - m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); - m->topic.ptr = (char *) p + 2; - p += 2 + m->topic.len; - if (p > end) return MQTT_MALFORMED; - if (m->qos > 0) { - if (p + 2 > end) return MQTT_MALFORMED; - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); - p += 2; - } - if (p > end) return MQTT_MALFORMED; - if (version == 5 && p + 2 < end) { - len_len = - (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size); - if (!len_len) return MQTT_MALFORMED; - m->props_start = (size_t) (p + len_len - buf); - p += len_len + m->props_size; - } - if (p > end) return MQTT_MALFORMED; - m->data.ptr = (char *) p; - m->data.len = (size_t) (end - p); - break; - } - default: - break; - } - return MQTT_OK; -} - -static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ) { - for (;;) { - uint8_t version = c->is_mqtt5 ? 5 : 4; - struct mg_mqtt_message mm; - int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm); - if (rc == MQTT_MALFORMED) { - MG_ERROR(("%lu MQTT malformed message", c->id)); - c->is_closing = 1; - break; - } else if (rc == MQTT_OK) { - MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd, - (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr)); - switch (mm.cmd) { - case MQTT_CMD_CONNACK: - mg_call(c, MG_EV_MQTT_OPEN, &mm.ack); - if (mm.ack == 0) { - MG_DEBUG(("%lu Connected", c->id)); - } else { - MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack)); - c->is_closing = 1; - } - break; - case MQTT_CMD_PUBLISH: { - /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, - mm.topic.ptr, (int) mm.data.len, mm.data.ptr));*/ - if (mm.qos > 0) { - uint16_t id = mg_ntohs(mm.id); - uint32_t remaining_len = sizeof(id); - if (c->is_mqtt5) remaining_len += 2; // 3.4.2 - - mg_mqtt_send_header( - c, - (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK), - 0, remaining_len); - mg_send(c, &id, sizeof(id)); - - if (c->is_mqtt5) { - uint16_t zero = 0; - mg_send(c, &zero, sizeof(zero)); - } - } - mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff - break; - } - case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable header rc - uint16_t id = mg_ntohs(mm.id); - uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1 - mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len); - mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2 - break; - } - case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable header rc - uint16_t id = mg_ntohs(mm.id); - uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1 - mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len); - mg_send(c, &id, sizeof(id)); - break; - } - } - mg_call(c, MG_EV_MQTT_CMD, &mm); - mg_iobuf_del(&c->recv, 0, mm.dgram.len); - } else { - break; - } - } - } - (void) ev_data; -} - -void mg_mqtt_ping(struct mg_connection *nc) { - mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0); -} - -void mg_mqtt_pong(struct mg_connection *nc) { - mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0); -} - -void mg_mqtt_disconnect(struct mg_connection *c, - const struct mg_mqtt_opts *opts) { - size_t len = 0; - if (c->is_mqtt5) len = 1 + get_props_size(opts->props, opts->num_props); - mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len); - - if (c->is_mqtt5) { - uint8_t zero = 0; - mg_send(c, &zero, sizeof(zero)); // reason code - mg_send_mqtt_properties(c, opts->props, opts->num_props); - } -} - -struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url, - const struct mg_mqtt_opts *opts, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); - if (c != NULL) { - struct mg_mqtt_opts empty; - memset(&empty, 0, sizeof(empty)); - mg_mqtt_login(c, opts == NULL ? &empty : opts); - c->pfn = mqtt_cb; - } - return c; -} - -struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); - if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr; - return c; -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/net.c" -#endif - - - - - - - - - -size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) { - size_t old = c->send.len; - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); - return c->send.len - old; -} - -size_t mg_printf(struct mg_connection *c, const char *fmt, ...) { - size_t len = 0; - va_list ap; - va_start(ap, fmt); - len = mg_vprintf(c, fmt, &ap); - va_end(ap); - return len; -} - -static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { - uint32_t localhost = mg_htonl(0x7f000001); - if (mg_vcasecmp(&str, "localhost") != 0) return false; - memcpy(addr->ip, &localhost, sizeof(uint32_t)); - addr->is_ip6 = false; - return true; -} - -static bool mg_atone(struct mg_str str, struct mg_addr *addr) { - if (str.len > 0) return false; - memset(addr->ip, 0, sizeof(addr->ip)); - addr->is_ip6 = false; - return true; -} - -static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { - uint8_t data[4] = {0, 0, 0, 0}; - size_t i, num_dots = 0; - for (i = 0; i < str.len; i++) { - if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { - int octet = data[num_dots] * 10 + (str.ptr[i] - '0'); - if (octet > 255) return false; - data[num_dots] = (uint8_t) octet; - } else if (str.ptr[i] == '.') { - if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; - num_dots++; - } else { - return false; - } - } - if (num_dots != 3 || str.ptr[i - 1] == '.') return false; - memcpy(&addr->ip, data, sizeof(data)); - addr->is_ip6 = false; - return true; -} - -static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { - int i; - uint32_t ipv4; - if (str.len < 14) return false; - if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') return false; - for (i = 2; i < 6; i++) { - if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false; - } - // struct mg_str s = mg_str_n(&str.ptr[7], str.len - 7); - if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false; - memcpy(&ipv4, addr->ip, sizeof(ipv4)); - memset(addr->ip, 0, sizeof(addr->ip)); - addr->ip[10] = addr->ip[11] = 255; - memcpy(&addr->ip[12], &ipv4, 4); - addr->is_ip6 = true; - return true; -} - -static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { - size_t i, j = 0, n = 0, dc = 42; - addr->scope_id = 0; - if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2; - if (mg_v4mapped(str, addr)) return true; - for (i = 0; i < str.len; i++) { - if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') || - (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') || - (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) { - unsigned long val; - if (i > j + 3) return false; - // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.ptr[j])); - val = mg_unhexn(&str.ptr[j], i - j + 1); - addr->ip[n] = (uint8_t) ((val >> 8) & 255); - addr->ip[n + 1] = (uint8_t) (val & 255); - } else if (str.ptr[i] == ':') { - j = i + 1; - if (i > 0 && str.ptr[i - 1] == ':') { - dc = n; // Double colon - if (i > 1 && str.ptr[i - 2] == ':') return false; - } else if (i > 0) { - n += 2; - } - if (n > 14) return false; - addr->ip[n] = addr->ip[n + 1] = 0; // For trailing :: - } else if (str.ptr[i] == '%') { // Scope ID - for (i = i + 1; i < str.len; i++) { - if (str.ptr[i] < '0' || str.ptr[i] > '9') return false; - addr->scope_id = (uint8_t) (addr->scope_id * 10); - addr->scope_id = (uint8_t) (addr->scope_id + (str.ptr[i] - '0')); - } - } else { - return false; - } - } - if (n < 14 && dc == 42) return false; - if (n < 14) { - memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2); - memset(&addr->ip[dc], 0, 14 - n); - } - - addr->is_ip6 = true; - return true; -} - -bool mg_aton(struct mg_str str, struct mg_addr *addr) { - // MG_INFO(("[%.*s]", (int) str.len, str.ptr)); - return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) || - mg_aton6(str, addr); -} - -struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) { - struct mg_connection *c = - (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize); - if (c != NULL) { - c->mgr = mgr; - c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE; - c->id = ++mgr->nextid; - MG_PROF_INIT(c); - } - return c; -} - -void mg_close_conn(struct mg_connection *c) { - mg_resolve_cancel(c); // Close any pending DNS query - LIST_DELETE(struct mg_connection, &c->mgr->conns, c); - if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL; - if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL; - // Order of operations is important. `MG_EV_CLOSE` event must be fired - // before we deallocate received data, see #1331 - mg_call(c, MG_EV_CLOSE, NULL); - MG_DEBUG(("%lu %ld closed", c->id, c->fd)); - MG_PROF_DUMP(c); - MG_PROF_FREE(c); - - mg_tls_free(c); - mg_iobuf_free(&c->recv); - mg_iobuf_free(&c->send); - mg_iobuf_free(&c->rtls); - mg_bzero((unsigned char *) c, sizeof(*c)); - free(c); -} - -struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = NULL; - if (url == NULL || url[0] == '\0') { - MG_ERROR(("null url")); - } else if ((c = mg_alloc_conn(mgr)) == NULL) { - MG_ERROR(("OOM")); - } else { - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); - c->is_udp = (strncmp(url, "udp:", 4) == 0); - c->fd = (void *) (size_t) MG_INVALID_SOCKET; - c->fn = fn; - c->is_client = true; - c->fn_data = fn_data; - MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); - mg_call(c, MG_EV_OPEN, (void *) url); - mg_resolve(c, url); - } - return c; -} - -struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = NULL; - if ((c = mg_alloc_conn(mgr)) == NULL) { - MG_ERROR(("OOM %s", url)); - } else if (!mg_open_listener(c, url)) { - MG_ERROR(("Failed: %s, errno %d", url, errno)); - MG_PROF_FREE(c); - free(c); - c = NULL; - } else { - c->is_listening = 1; - c->is_udp = strncmp(url, "udp:", 4) == 0; - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); - c->fn = fn; - c->fn_data = fn_data; - mg_call(c, MG_EV_OPEN, NULL); - if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must - MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); - } - return c; -} - -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_alloc_conn(mgr); - if (c != NULL) { - c->fd = (void *) (size_t) fd; - c->fn = fn; - c->fn_data = fn_data; - MG_EPOLL_ADD(c); - mg_call(c, MG_EV_OPEN, NULL); - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); - } - return c; -} - -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, - unsigned flags, void (*fn)(void *), void *arg) { - struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t)); - if (t != NULL) { - mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); - t->id = mgr->timerid++; - } - return t; -} - -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) { - if (c->rtls.len == 0) return MG_IO_WAIT; - if (len > c->rtls.len) len = c->rtls.len; - memcpy(buf, c->rtls.buf, len); - mg_iobuf_del(&c->rtls, 0, len); - return (long) len; -} - -void mg_mgr_free(struct mg_mgr *mgr) { - struct mg_connection *c; - struct mg_timer *tmp, *t = mgr->timers; - while (t != NULL) tmp = t->next, free(t), t = tmp; - mgr->timers = NULL; // Important. Next call to poll won't touch timers - for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1; - mg_mgr_poll(mgr, 0); -#if MG_ENABLE_FREERTOS_TCP - FreeRTOS_DeleteSocketSet(mgr->ss); -#endif - MG_DEBUG(("All connections closed")); -#if MG_ENABLE_EPOLL - if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1; -#endif - mg_tls_ctx_free(mgr); -} - -void mg_mgr_init(struct mg_mgr *mgr) { - memset(mgr, 0, sizeof(*mgr)); -#if MG_ENABLE_EPOLL - if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) - MG_ERROR(("epoll_create1 errno %d", errno)); -#else - mgr->epoll_fd = -1; -#endif -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK - // clang-format off - { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } - // clang-format on -#elif MG_ENABLE_FREERTOS_TCP - mgr->ss = FreeRTOS_CreateSocketSet(); -#elif defined(__unix) || defined(__unix__) || defined(__APPLE__) - // Ignore SIGPIPE signal, so if client cancels the request, it - // won't kill the whole process. - signal(SIGPIPE, SIG_IGN); -#endif - mgr->pipe = MG_INVALID_SOCKET; - mgr->dnstimeout = 3000; - mgr->dns4.url = "udp://8.8.8.8:53"; - mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; - mg_tls_ctx_init(mgr); -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/net_builtin.c" -#endif - - -#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP -#define MG_EPHEMERAL_PORT_BASE 32768 -#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) - -#ifndef MIP_TCP_KEEPALIVE_MS -#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms -#endif - -#define MIP_TCP_ACK_MS 150 // Timeout for ACKing -#define MIP_TCP_ARP_MS 100 // Timeout for ARP response -#define MIP_TCP_SYN_MS 15000 // Timeout for connection establishment -#define MIP_TCP_FIN_MS 1000 // Timeout for closing connection - -struct connstate { - uint32_t seq, ack; // TCP seq/ack counters - uint64_t timer; // TCP keep-alive / ACK timer - uint8_t mac[6]; // Peer MAC address - uint8_t ttype; // Timer type. 0: ack, 1: keep-alive -#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive -#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it soon -#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response -#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response -#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection - uint8_t tmiss; // Number of keep-alive misses - struct mg_iobuf raw; // For TLS only. Incoming raw data -}; - -#pragma pack(push, 1) - -struct lcp { - uint8_t addr, ctrl, proto[2], code, id, len[2]; -}; - -struct eth { - uint8_t dst[6]; // Destination MAC address - uint8_t src[6]; // Source MAC address - uint16_t type; // Ethernet type -}; - -struct ip { - uint8_t ver; // Version - uint8_t tos; // Unused - uint16_t len; // Length - uint16_t id; // Unused - uint16_t frag; // Fragmentation -#define IP_FRAG_OFFSET_MSK 0xFF1F -#define IP_MORE_FRAGS_MSK 0x20 - uint8_t ttl; // Time to live - uint8_t proto; // Upper level protocol - uint16_t csum; // Checksum - uint32_t src; // Source IP - uint32_t dst; // Destination IP -}; - -struct ip6 { - uint8_t ver; // Version - uint8_t opts[3]; // Options - uint16_t len; // Length - uint8_t proto; // Upper level protocol - uint8_t ttl; // Time to live - uint8_t src[16]; // Source IP - uint8_t dst[16]; // Destination IP -}; - -struct icmp { - uint8_t type; - uint8_t code; - uint16_t csum; -}; - -struct arp { - uint16_t fmt; // Format of hardware address - uint16_t pro; // Format of protocol address - uint8_t hlen; // Length of hardware address - uint8_t plen; // Length of protocol address - uint16_t op; // Operation - uint8_t sha[6]; // Sender hardware address - uint32_t spa; // Sender protocol address - uint8_t tha[6]; // Target hardware address - uint32_t tpa; // Target protocol address -}; - -struct tcp { - uint16_t sport; // Source port - uint16_t dport; // Destination port - uint32_t seq; // Sequence number - uint32_t ack; // Acknowledgement number - uint8_t off; // Data offset - uint8_t flags; // TCP flags -#define TH_FIN 0x01 -#define TH_SYN 0x02 -#define TH_RST 0x04 -#define TH_PUSH 0x08 -#define TH_ACK 0x10 -#define TH_URG 0x20 -#define TH_ECE 0x40 -#define TH_CWR 0x80 - uint16_t win; // Window - uint16_t csum; // Checksum - uint16_t urp; // Urgent pointer -}; - -struct udp { - uint16_t sport; // Source port - uint16_t dport; // Destination port - uint16_t len; // UDP length - uint16_t csum; // UDP checksum -}; - -struct dhcp { - uint8_t op, htype, hlen, hops; - uint32_t xid; - uint16_t secs, flags; - uint32_t ciaddr, yiaddr, siaddr, giaddr; - uint8_t hwaddr[208]; - uint32_t magic; - uint8_t options[32]; -}; - -#pragma pack(pop) - -struct pkt { - struct mg_str raw; // Raw packet data - struct mg_str pay; // Payload data - struct eth *eth; - struct llc *llc; - struct arp *arp; - struct ip *ip; - struct ip6 *ip6; - struct icmp *icmp; - struct tcp *tcp; - struct udp *udp; - struct dhcp *dhcp; -}; - -static void send_syn(struct mg_connection *c); - -static void mkpay(struct pkt *pkt, void *p) { - pkt->pay = - mg_str_n((char *) p, (size_t) (&pkt->raw.ptr[pkt->raw.len] - (char *) p)); -} - -static uint32_t csumup(uint32_t sum, const void *buf, size_t len) { - const uint8_t *p = (const uint8_t *) buf; - for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8); - return sum; -} - -static uint16_t csumfin(uint32_t sum) { - while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); - return mg_htons(~sum & 0xffff); -} - -static uint16_t ipcsum(const void *buf, size_t len) { - uint32_t sum = csumup(0, buf, len); - return csumfin(sum); -} - -static void settmout(struct mg_connection *c, uint8_t type) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - struct connstate *s = (struct connstate *) (c + 1); - unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS - : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS - : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS - : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS - : MIP_TCP_KEEPALIVE_MS; - s->timer = ifp->now + n; - s->ttype = type; - MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer)); -} - -static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) { - size_t n = ifp->driver->tx(ifp->tx.ptr, len, ifp); - if (n == len) ifp->nsent++; - return n; -} - -static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) { - struct eth *eth = (struct eth *) ifp->tx.ptr; - struct arp *arp = (struct arp *) (eth + 1); - memset(eth->dst, 255, sizeof(eth->dst)); - memcpy(eth->src, ifp->mac, sizeof(eth->src)); - eth->type = mg_htons(0x806); - memset(arp, 0, sizeof(*arp)); - arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6, - arp->plen = 4; - arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip; - memcpy(arp->sha, ifp->mac, sizeof(arp->sha)); - ether_output(ifp, PDIFF(eth, arp + 1)); -} - -static void onstatechange(struct mg_tcpip_if *ifp) { - if (ifp->state == MG_TCPIP_STATE_READY) { - MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip)); - MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw)); - MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac)); - arp_ask(ifp, ifp->gw); - } else if (ifp->state == MG_TCPIP_STATE_UP) { - MG_ERROR(("Link up")); - srand((unsigned int) mg_millis()); - } else if (ifp->state == MG_TCPIP_STATE_DOWN) { - MG_ERROR(("Link down")); - } -} - -static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst, - uint8_t proto, uint32_t ip_src, uint32_t ip_dst, - size_t plen) { - struct eth *eth = (struct eth *) ifp->tx.ptr; - struct ip *ip = (struct ip *) (eth + 1); - memcpy(eth->dst, mac_dst, sizeof(eth->dst)); - memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC - eth->type = mg_htons(0x800); - memset(ip, 0, sizeof(*ip)); - ip->ver = 0x45; // Version 4, header length 5 words - ip->frag = 0x40; // Don't fragment - ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen)); - ip->ttl = 64; - ip->proto = proto; - ip->src = ip_src; - ip->dst = ip_dst; - ip->csum = ipcsum(ip, sizeof(*ip)); - return ip; -} - -static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, - uint16_t sport, uint32_t ip_dst, uint16_t dport, - const void *buf, size_t len) { - struct ip *ip = - tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp)); - struct udp *udp = (struct udp *) (ip + 1); - // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len)); - udp->sport = sport; - udp->dport = dport; - udp->len = mg_htons((uint16_t) (sizeof(*udp) + len)); - udp->csum = 0; - uint32_t cs = csumup(0, udp, sizeof(*udp)); - cs = csumup(cs, buf, len); - cs = csumup(cs, &ip->src, sizeof(ip->src)); - cs = csumup(cs, &ip->dst, sizeof(ip->dst)); - cs += (uint32_t) (ip->proto + sizeof(*udp) + len); - udp->csum = csumfin(cs); - memmove(udp + 1, buf, len); - // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len)); - ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len); -} - -static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, - uint32_t ip_dst, uint8_t *opts, size_t optslen, - bool ciaddr) { - // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6 - struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; - dhcp.magic = mg_htonl(0x63825363); - memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac)); - memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid)); - memcpy(&dhcp.options, opts, optslen); - if (ciaddr) dhcp.ciaddr = ip_src; - tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp, - sizeof(dhcp)); -} - -static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255}; - -// RFC-2131 #4.3.6, #4.4.1 -static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req, - uint32_t ip_srv) { - uint8_t opts[] = { - 53, 1, 3, // Type: DHCP request - 55, 2, 1, 3, // GW and mask - 12, 3, 'm', 'i', 'p', // Host name: "mip" - 54, 4, 0, 0, 0, 0, // DHCP server ID - 50, 4, 0, 0, 0, 0, // Requested IP - 255 // End of options - }; - memcpy(opts + 14, &ip_srv, sizeof(ip_srv)); - memcpy(opts + 20, &ip_req, sizeof(ip_req)); - tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false); - MG_DEBUG(("DHCP req sent")); -} - -// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast) -static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t *mac_dst, - uint32_t ip_src, uint32_t ip_dst) { - uint8_t opts[] = { - 53, 1, 3, // Type: DHCP request - 255 // End of options - }; - tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true); - MG_DEBUG(("DHCP req sent")); -} - -static void tx_dhcp_discover(struct mg_tcpip_if *ifp) { - uint8_t opts[] = { - 53, 1, 1, // Type: DHCP discover - 55, 2, 1, 3, // Parameters: ip, mask - 255 // End of options - }; - tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false); - MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac)); -} - -static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt, - bool lsn) { - struct mg_connection *c = NULL; - for (c = mgr->conns; c != NULL; c = c->next) { - if (c->is_arplooking && pkt->arp && - memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0) - break; - if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break; - if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && - lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport)) - break; - } - return c; -} - -static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { - // ARP request. Make a response, then send - // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4, - // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa)); - struct eth *eth = (struct eth *) ifp->tx.ptr; - struct arp *arp = (struct arp *) (eth + 1); - memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst)); - memcpy(eth->src, ifp->mac, sizeof(eth->src)); - eth->type = mg_htons(0x806); - *arp = *pkt->arp; - arp->op = mg_htons(2); - memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha)); - memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha)); - arp->tpa = pkt->arp->spa; - arp->spa = ifp->ip; - MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac, - &ifp->mac)); - ether_output(ifp, PDIFF(eth, arp + 1)); - } else if (pkt->arp->op == mg_htons(2)) { - if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; - if (pkt->arp->spa == ifp->gw) { - // Got response for the GW ARP request. Set ifp->gwmac - memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac)); - } else { - struct mg_connection *c = getpeer(ifp->mgr, pkt, false); - if (c != NULL && c->is_arplooking) { - struct connstate *s = (struct connstate *) (c + 1); - memcpy(s->mac, pkt->arp->sha, sizeof(s->mac)); - MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip, - mg_print_mac, s->mac)); - c->is_arplooking = 0; - send_syn(c); - settmout(c, MIP_TTYPE_SYN); - } - } - } -} - -static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - // MG_DEBUG(("ICMP %d", (int) len)); - if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { - size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp); - size_t space = ifp->tx.len - hlen, plen = pkt->pay.len; - if (plen > space) plen = space; - struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src, - sizeof(struct icmp) + plen); - struct icmp *icmp = (struct icmp *) (ip + 1); - memset(icmp, 0, sizeof(*icmp)); // Set csum to 0 - memcpy(icmp + 1, pkt->pay.ptr, plen); // Copy RX payload to TX - icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); - ether_output(ifp, hlen + plen); - } -} - -static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) { - uint32_t ip = 0, gw = 0, mask = 0, lease = 0; - uint8_t msgtype = 0, state = ifp->state; - // perform size check first, then access fields - uint8_t *p = pkt->dhcp->options, - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len]; - if (end < (uint8_t *) (pkt->dhcp + 1)) return; - if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return; - while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9 - if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { // Mask - memcpy(&mask, p + 2, sizeof(mask)); - } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) { // GW - memcpy(&gw, p + 2, sizeof(gw)); - ip = pkt->dhcp->yiaddr; - } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease - memcpy(&lease, p + 2, sizeof(lease)); - lease = mg_ntohl(lease); - } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type - msgtype = p[2]; - } - p += p[1] + 2; - } - // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4) - if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP - ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; - } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw && - lease) { // DHCPOFFER - // select IP, (4.4.1) (fallback to IP source addr on foul play) - tx_dhcp_request_sel(ifp, ip, pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src); - ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state - } else if (msgtype == 5) { // DHCPACK - if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP - ifp->lease_expire = ifp->now + lease * 1000; - MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); - // assume DHCP server = router until ARP resolves - memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); - ifp->ip = ip, ifp->gw = gw, ifp->mask = mask; - ifp->state = MG_TCPIP_STATE_READY; // BOUND state - uint64_t rand; - mg_random(&rand, sizeof(rand)); - srand((unsigned int) (rand + mg_millis())); - } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) { // renew - ifp->lease_expire = ifp->now + lease * 1000; - MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); - } // TODO(): accept provided T1/T2 and store server IP for renewal (4.4) - } - if (ifp->state != state) onstatechange(ifp); -} - -// Simple DHCP server that assigns a next IP address: ifp->ip + 1 -static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) { - uint8_t op = 0, *p = pkt->dhcp->options, - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len]; - if (end < (uint8_t *) (pkt->dhcp + 1)) return; - // struct dhcp *req = pkt->dhcp; - struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; - res.yiaddr = ifp->ip; - ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1 - while (p + 1 < end && p[0] != 255) { // Parse options - if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type - op = p[2]; - } - p += p[1] + 2; - } - if (op == 1 || op == 3) { // DHCP Discover or DHCP Request - uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or DHCP ACK - uint8_t opts[] = { - 53, 1, msg, // Message type - 1, 4, 0, 0, 0, 0, // Subnet mask - 54, 4, 0, 0, 0, 0, // Server ID - 12, 3, 'm', 'i', 'p', // Host name: "mip" - 51, 4, 255, 255, 255, 255, // Lease time - 255 // End of options - }; - memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6); - memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask)); - memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip)); - memcpy(&res.options, opts, sizeof(opts)); - res.magic = pkt->dhcp->magic; - res.xid = pkt->dhcp->xid; - if (ifp->enable_get_gateway) { - ifp->gw = res.yiaddr; - memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); - } - tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67), - op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res)); - } -} - -static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - struct mg_connection *c = getpeer(ifp->mgr, pkt, true); - if (c == NULL) { - // No UDP listener on this port. Should send ICMP, but keep silent. - } else { - c->rem.port = pkt->udp->sport; - memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); - struct connstate *s = (struct connstate *) (c + 1); - memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); - if (c->recv.len >= MG_MAX_RECV_SIZE) { - mg_error(c, "max_recv_buf_size reached"); - } else if (c->recv.size - c->recv.len < pkt->pay.len && - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { - mg_error(c, "oom"); - } else { - memcpy(&c->recv.buf[c->recv.len], pkt->pay.ptr, pkt->pay.len); - c->recv.len += pkt->pay.len; - mg_call(c, MG_EV_READ, &pkt->pay.len); - } - } -} - -static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip, - uint8_t flags, uint16_t sport, uint16_t dport, - uint32_t seq, uint32_t ack, const void *buf, size_t len) { - struct ip *ip = - tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len); - struct tcp *tcp = (struct tcp *) (ip + 1); - memset(tcp, 0, sizeof(*tcp)); - if (buf != NULL && len) memmove(tcp + 1, buf, len); - tcp->sport = sport; - tcp->dport = dport; - tcp->seq = seq; - tcp->ack = ack; - tcp->flags = flags; - tcp->win = mg_htons(8192); - tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4); - uint32_t cs = 0; - uint16_t n = (uint16_t) (sizeof(*tcp) + len); - uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)}; - cs = csumup(cs, tcp, n); - cs = csumup(cs, &ip->src, sizeof(ip->src)); - cs = csumup(cs, &ip->dst, sizeof(ip->dst)); - cs = csumup(cs, pseudo, sizeof(pseudo)); - tcp->csum = csumfin(cs); - MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src, - mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst, - mg_ntohs(tcp->dport), tcp->flags, (int) len)); - // mg_hexdump(ifp->tx.ptr, PDIFF(ifp->tx.ptr, tcp + 1) + len); - return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len); -} - -static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt, - uint8_t flags, uint32_t seq, const void *buf, - size_t len) { - uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0; - return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport, - pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), - buf, len); -} - -static struct mg_connection *accept_conn(struct mg_connection *lsn, - struct pkt *pkt) { - struct mg_connection *c = mg_alloc_conn(lsn->mgr); - if (c == NULL) { - MG_ERROR(("OOM")); - return NULL; - } - struct connstate *s = (struct connstate *) (c + 1); - s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); - memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); - settmout(c, MIP_TTYPE_KEEPALIVE); - memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); - c->rem.port = pkt->tcp->sport; - MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem)); - LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c); - c->is_accepted = 1; - c->is_hexdumping = lsn->is_hexdumping; - c->pfn = lsn->pfn; - c->loc = lsn->loc; - c->pfn_data = lsn->pfn_data; - c->fn = lsn->fn; - c->fn_data = lsn->fn_data; - mg_call(c, MG_EV_OPEN, NULL); - mg_call(c, MG_EV_ACCEPT, NULL); - return c; -} - -static size_t trim_len(struct mg_connection *c, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8; - size_t max_headers_len = - eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len); - size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len; - - // If the frame exceeds the available buffer, trim the length - if (len + max_headers_len > ifp->tx.len) { - len = ifp->tx.len - max_headers_len; - } - // Ensure the MTU isn't lower than the minimum allowed value - if (ifp->mtu < min_mtu) { - MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu)); - ifp->mtu = (uint16_t) min_mtu; - } - // If the total packet size exceeds the MTU, trim the length - if (len + max_headers_len - eth_h_len > ifp->mtu) { - len = ifp->mtu - max_headers_len + eth_h_len; - if (c->is_udp) { - MG_ERROR(("UDP datagram exceeds MTU. Truncating it.")); - } - } - - return len; -} - -long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - struct connstate *s = (struct connstate *) (c + 1); - uint32_t dst_ip = *(uint32_t *) c->rem.ip; - len = trim_len(c, len); - if (c->is_udp) { - tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf, len); - } else { - size_t sent = - tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq), mg_htonl(s->ack), buf, len); - if (sent == 0) { - return MG_IO_WAIT; - } else if (sent == (size_t) -1) { - return MG_IO_ERR; - } else { - s->seq += (uint32_t) len; - if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE); - } - } - return (long) len; -} - -static void read_conn(struct mg_connection *c, struct pkt *pkt) { - struct connstate *s = (struct connstate *) (c + 1); - struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; - uint32_t seq = mg_ntohl(pkt->tcp->seq); - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - if (pkt->tcp->flags & TH_FIN) { - // If we initiated the closure, we reply with ACK upon receiving FIN - // If we didn't initiate it, we reply with FIN as part of the normal TCP - // closure process - uint8_t flags = TH_ACK; - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1); - if (c->is_draining && s->ttype == MIP_TTYPE_FIN) { - if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ? - s->seq++; // Yes. Increment our SEQ - } else { // Otherwise, - s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK - } - } else { - flags |= TH_FIN; - c->is_draining = 1; - settmout(c, MIP_TTYPE_FIN); - } - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags, - c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); - } else if (pkt->pay.len == 0) { - // TODO(cpq): handle this peer's ACK - } else if (seq != s->ack) { - uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); - if (s->ack == ack) { - MG_VERBOSE(("ignoring duplicate pkt")); - } else { - MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack)); - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, - c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", - 0); - } - } else if (io->size - io->len < pkt->pay.len && - !mg_iobuf_resize(io, io->len + pkt->pay.len)) { - mg_error(c, "oom"); - } else { - // Copy TCP payload into the IO buffer. If the connection is plain text, - // we copy to c->recv. If the connection is TLS, this data is encrypted, - // therefore we copy that encrypted data to the c->rtls iobuffer instead, - // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will - // call back mg_io_recv() which grabs raw data from c->rtls - memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len); - io->len += pkt->pay.len; - - MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack)); - // Advance ACK counter - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); -#if 0 - // Send ACK immediately - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - MG_DEBUG((" imm ACK", c->id, mg_htonl(pkt->tcp->seq), s->ack)); - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, c->loc.port, - c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); -#else - // if not already running, setup a timer to send an ACK later - if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK); -#endif - - if (c->is_tls && c->is_tls_hs) { - mg_tls_handshake(c); - } else if (c->is_tls) { - // TLS connection. Make room for decrypted data in c->recv - io = &c->recv; - if (io->size - io->len < pkt->pay.len && - !mg_iobuf_resize(io, io->len + pkt->pay.len)) { - mg_error(c, "oom"); - } else { - // Decrypt data directly into c->recv - long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len); - if (n == MG_IO_ERR) { - mg_error(c, "TLS recv error"); - } else if (n > 0) { - // Decrypted successfully - trigger MG_EV_READ - io->len += (size_t) n; - mg_call(c, MG_EV_READ, &n); - } - } - } else { - // Plain text connection, data is already in c->recv, trigger - // MG_EV_READ - mg_call(c, MG_EV_READ, &pkt->pay.len); - } - } -} - -static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - struct mg_connection *c = getpeer(ifp->mgr, pkt, false); - struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1); -#if 0 - MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len)); -#endif - if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) { - s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1; - tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0); - c->is_connecting = 0; // Client connected - settmout(c, MIP_TTYPE_KEEPALIVE); - mg_call(c, MG_EV_CONNECT, NULL); // Let user know - } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) { - // mg_hexdump(pkt->raw.ptr, pkt->raw.len); - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (c != NULL && pkt->tcp->flags & TH_RST) { - mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 - } else if (c != NULL) { -#if 0 - MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len, - mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport), - mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport))); - mg_hexdump(pkt->pay.ptr, pkt->pay.len); -#endif - s->tmiss = 0; // Reset missed keep-alive counter - if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer - settmout(c, - MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending - read_conn(c, pkt); // Override timer with ACK timeout if needed - } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (pkt->tcp->flags & TH_RST) { - if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 - // ignore RST if not connected - } else if (pkt->tcp->flags & TH_SYN) { - // Use peer's source port as ISN, in order to recognise the handshake - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport)); - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); - } else if (pkt->tcp->flags & TH_FIN) { - tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) { - accept_conn(c, pkt); - } else if (!c->is_accepted) { // no peer - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); - } else { - // MG_VERBOSE(("dropped silently..")); - } -} - -static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) { - if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag & IP_FRAG_OFFSET_MSK) { - if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1); - if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1); - struct mg_connection *c = getpeer(ifp->mgr, pkt, false); - if (c) mg_error(c, "Received fragmented packet"); - } else if (pkt->ip->proto == 1) { - pkt->icmp = (struct icmp *) (pkt->ip + 1); - if (pkt->pay.len < sizeof(*pkt->icmp)) return; - mkpay(pkt, pkt->icmp + 1); - rx_icmp(ifp, pkt); - } else if (pkt->ip->proto == 17) { - pkt->udp = (struct udp *) (pkt->ip + 1); - if (pkt->pay.len < sizeof(*pkt->udp)) return; - mkpay(pkt, pkt->udp + 1); - MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, - mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst, - mg_ntohs(pkt->udp->dport), (int) pkt->pay.len)); - if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) { - pkt->dhcp = (struct dhcp *) (pkt->udp + 1); - mkpay(pkt, pkt->dhcp + 1); - rx_dhcp_client(ifp, pkt); - } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) { - pkt->dhcp = (struct dhcp *) (pkt->udp + 1); - mkpay(pkt, pkt->dhcp + 1); - rx_dhcp_server(ifp, pkt); - } else { - rx_udp(ifp, pkt); - } - } else if (pkt->ip->proto == 6) { - pkt->tcp = (struct tcp *) (pkt->ip + 1); - if (pkt->pay.len < sizeof(*pkt->tcp)) return; - mkpay(pkt, pkt->tcp + 1); - uint16_t iplen = mg_ntohs(pkt->ip->len); - uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U)); - if (iplen >= off) pkt->pay.len = (size_t) (iplen - off); - MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, - mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst, - mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len)); - rx_tcp(ifp, pkt); - } -} - -static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) { - // MG_DEBUG(("IP %d", (int) len)); - if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) { - pkt->icmp = (struct icmp *) (pkt->ip6 + 1); - if (pkt->pay.len < sizeof(*pkt->icmp)) return; - mkpay(pkt, pkt->icmp + 1); - rx_icmp(ifp, pkt); - } else if (pkt->ip6->proto == 17) { - pkt->udp = (struct udp *) (pkt->ip6 + 1); - if (pkt->pay.len < sizeof(*pkt->udp)) return; - // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport), - // mg_htons(udp->dport))); - mkpay(pkt, pkt->udp + 1); - } -} - -static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { - struct pkt pkt; - memset(&pkt, 0, sizeof(pkt)); - pkt.raw.ptr = (char *) buf; - pkt.raw.len = len; - pkt.eth = (struct eth *) buf; - // mg_hexdump(buf, len > 16 ? 16: len); - if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt? - if (ifp->enable_mac_check && - memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 && - memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) - return; - if (ifp->enable_crc32_check && len > 4) { - len -= 4; // TODO(scaprile): check on bigendian - uint32_t crc = mg_crc32(0, (const char *) buf, len); - if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return; - } - if (pkt.eth->type == mg_htons(0x806)) { - pkt.arp = (struct arp *) (pkt.eth + 1); - if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated - rx_arp(ifp, &pkt); - } else if (pkt.eth->type == mg_htons(0x86dd)) { - pkt.ip6 = (struct ip6 *) (pkt.eth + 1); - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // Truncated - if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP - mkpay(&pkt, pkt.ip6 + 1); - rx_ip6(ifp, &pkt); - } else if (pkt.eth->type == mg_htons(0x800)) { - pkt.ip = (struct ip *) (pkt.eth + 1); - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated - // Truncate frame to what IP header tells us - if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) { - pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth); - } - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated - if ((pkt.ip->ver >> 4) != 4) return; // Not IP - mkpay(&pkt, pkt.ip + 1); - rx_ip(ifp, &pkt); - } else { - MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type))); - if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ? 32 : len); - } -} - -static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t uptime_ms) { - if (ifp == NULL || ifp->driver == NULL) return; - bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, uptime_ms); - ifp->now = uptime_ms; - - // Handle physical interface up/down status - if (expired_1000ms && ifp->driver->up) { - bool up = ifp->driver->up(ifp); - bool current = ifp->state != MG_TCPIP_STATE_DOWN; - if (up != current) { - ifp->state = up == false ? MG_TCPIP_STATE_DOWN - : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP - : MG_TCPIP_STATE_READY; - if (!up && ifp->enable_dhcp_client) ifp->ip = 0; - onstatechange(ifp); - } - } - if (ifp->state == MG_TCPIP_STATE_DOWN) return; - - // DHCP RFC-2131 (4.4) - if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) { - tx_dhcp_discover(ifp); // INIT (4.4.1) - } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && - ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING - if (ifp->now >= ifp->lease_expire) { - ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP - onstatechange(ifp); - } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire && - ((ifp->now / 1000) % 60) == 0) { - // hack: 30 min before deadline, try to rebind (4.3.6) every min - tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff); - } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5) - } - - // Read data from the network - if (ifp->driver->rx != NULL) { // Polling driver. We must call it - size_t len = - ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp); - if (len > 0) { - ifp->nrecv++; - mg_tcpip_rx(ifp, ifp->recv_queue.buf, len); - } - } else { // Interrupt-based driver. Fills recv queue itself - char *buf; - size_t len = mg_queue_next(&ifp->recv_queue, &buf); - if (len > 0) { - mg_tcpip_rx(ifp, buf, len); - mg_queue_del(&ifp->recv_queue, len); - } - } - - // Process timeouts - for (struct mg_connection *c = ifp->mgr->conns; c != NULL; c = c->next) { - if (c->is_udp || c->is_listening || c->is_resolving) continue; - struct connstate *s = (struct connstate *) (c + 1); - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - if (uptime_ms > s->timer) { - if (s->ttype == MIP_TTYPE_ACK) { - MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack)); - tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq), mg_htonl(s->ack), "", 0); - } else if (s->ttype == MIP_TTYPE_ARP) { - mg_error(c, "ARP timeout"); - } else if (s->ttype == MIP_TTYPE_SYN) { - mg_error(c, "Connection timeout"); - } else if (s->ttype == MIP_TTYPE_FIN) { - c->is_closing = 1; - continue; - } else { - if (s->tmiss++ > 2) { - mg_error(c, "keepalive"); - } else { - MG_VERBOSE(("%lu keepalive", c->id)); - tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq - 1), mg_htonl(s->ack), "", 0); - } - } - - settmout(c, MIP_TTYPE_KEEPALIVE); - } - } -} - -// This function executes in interrupt context, thus it should copy data -// somewhere fast. Note that newlib's malloc is not thread safe, thus use -// our lock-free queue with preallocated buffer to copy data and return asap -void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) { - char *p; - if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) { - memcpy(p, buf, len); - mg_queue_add(&ifp->recv_queue, len); - ifp->nrecv++; - } else { - ifp->ndrop++; - } -} - -void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { - // If MAC address is not set, make a random one - if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 && - ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) { - ifp->mac[0] = 0x02; // Locally administered, unicast - mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1); - MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac)); - } - - if (ifp->driver->init && !ifp->driver->init(ifp)) { - MG_ERROR(("driver init failed")); - } else { - size_t framesize = 1540; - ifp->tx.ptr = (char *) calloc(1, framesize), ifp->tx.len = framesize; - if (ifp->recv_queue.size == 0) - ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192; - ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size); - ifp->timer_1000ms = mg_millis(); - mgr->priv = ifp; - ifp->mgr = mgr; - ifp->mtu = MG_TCPIP_MTU_DEFAULT; - mgr->extraconnsize = sizeof(struct connstate); - if (ifp->ip == 0) ifp->enable_dhcp_client = true; - memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast - mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535 - ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from - // MG_EPHEMERAL_PORT_BASE to 65535 - if (ifp->tx.ptr == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); - } -} - -void mg_tcpip_free(struct mg_tcpip_if *ifp) { - free(ifp->recv_queue.buf); - free((char *) ifp->tx.ptr); -} - -static void send_syn(struct mg_connection *c) { - struct connstate *s = (struct connstate *) (c + 1); - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port)); - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL, - 0); -} - -void mg_connect_resolved(struct mg_connection *c) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - c->is_resolving = 0; - if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; - memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t)); - c->loc.port = mg_htons(ifp->eport++); - MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, - &c->rem)); - mg_call(c, MG_EV_RESOLVE, NULL); - if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) { - struct connstate *s = (struct connstate *) (c + 1); - memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast - } else if (((rem_ip & ifp->mask) == (ifp->ip & ifp->mask))) { - // If we're in the same LAN, fire an ARP lookup. - MG_DEBUG(("%lu ARP lookup...", c->id)); - arp_ask(ifp, rem_ip); - settmout(c, MIP_TTYPE_ARP); - c->is_arplooking = 1; - c->is_connecting = 1; - } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) { - struct connstate *s = (struct connstate *) (c + 1); // 224 to 239, E0 to EF - uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group - memcpy(s->mac, mcastp, 3); - memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb - s->mac[3] &= 0x7F; - } else { - struct connstate *s = (struct connstate *) (c + 1); - memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac)); - if (c->is_udp) { - mg_call(c, MG_EV_CONNECT, NULL); - } else { - send_syn(c); - settmout(c, MIP_TTYPE_SYN); - c->is_connecting = 1; - } - } -} - -bool mg_open_listener(struct mg_connection *c, const char *url) { - c->loc.port = mg_htons(mg_url_port(url)); - return true; -} - -static void write_conn(struct mg_connection *c) { - long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len) - : mg_io_send(c, c->send.buf, c->send.len); - if (len == MG_IO_ERR) { - mg_error(c, "tx err"); - } else if (len > 0) { - mg_iobuf_del(&c->send, 0, (size_t) len); - mg_call(c, MG_EV_WRITE, &len); - } -} - -static void init_closure(struct mg_connection *c) { - struct connstate *s = (struct connstate *) (c + 1); - if (c->is_udp == false && c->is_listening == false && - c->is_connecting == false) { // For TCP conns, - struct mg_tcpip_if *ifp = - (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); - settmout(c, MIP_TTYPE_FIN); - } -} - -static void close_conn(struct mg_connection *c) { - struct connstate *s = (struct connstate *) (c + 1); - mg_iobuf_free(&s->raw); // For TLS connections, release raw data - mg_close_conn(c); -} - -static bool can_write(struct mg_connection *c) { - return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 && - c->is_tls_hs == 0 && c->is_arplooking == 0; -} - -void mg_mgr_poll(struct mg_mgr *mgr, int ms) { - struct mg_connection *c, *tmp; - uint64_t now = mg_millis(); - mg_tcpip_poll((struct mg_tcpip_if *) mgr->priv, now); - mg_timer_poll(&mgr->timers, now); - for (c = mgr->conns; c != NULL; c = tmp) { - tmp = c->next; - struct connstate *s = (struct connstate *) (c + 1); - mg_call(c, MG_EV_POLL, &now); - MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't', - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h', - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); - if (can_write(c)) write_conn(c); - if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) - init_closure(c); - if (c->is_closing) close_conn(c); - } - (void) ms; -} - -bool mg_send(struct mg_connection *c, const void *buf, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - bool res = false; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) { - mg_error(c, "net down"); - } else if (c->is_udp) { - struct connstate *s = (struct connstate *) (c + 1); - len = trim_len(c, len); // Trimming length if necessary - tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len); - res = true; - } else { - res = mg_iobuf_add(&c->send, c->send.len, buf, len); - } - return res; -} -#endif // MG_ENABLE_TCPIP - -#ifdef MG_ENABLE_LINES -#line 1 "src/ota_dummy.c" -#endif - - - -#if MG_OTA == MG_OTA_NONE -bool mg_ota_begin(size_t new_firmware_size) { - (void) new_firmware_size; - return true; -} -bool mg_ota_write(const void *buf, size_t len) { - (void) buf, (void) len; - return true; -} -bool mg_ota_end(void) { - return true; -} -bool mg_ota_commit(void) { - return true; -} -bool mg_ota_rollback(void) { - return true; -} -int mg_ota_status(int fw) { - (void) fw; - return 0; -} -uint32_t mg_ota_crc32(int fw) { - (void) fw; - return 0; -} -uint32_t mg_ota_timestamp(int fw) { - (void) fw; - return 0; -} -size_t mg_ota_size(int fw) { - (void) fw; - return 0; -} -MG_IRAM void mg_ota_boot(void) { -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/ota_flash.c" -#endif - - - - - -// This OTA implementation uses the internal flash API outlined in device.h -// It splits flash into 2 equal partitions, and stores OTA status in the -// last sector of the partition. - -#if MG_OTA == MG_OTA_FLASH - -#define MG_OTADATA_KEY 0xb07afed0 - -static char *s_addr; // Current address to write to -static size_t s_size; // Firmware size to flash. In-progress indicator -static uint32_t s_crc32; // Firmware checksum - -struct mg_otadata { - uint32_t crc32, size, timestamp, status; -}; - -bool mg_ota_begin(size_t new_firmware_size) { - bool ok = false; - if (s_size) { - MG_ERROR(("OTA already in progress. Call mg_ota_end()")); - } else { - size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); - s_crc32 = 0; - s_addr = (char *) mg_flash_start() + half; - MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max)); - if (new_firmware_size < max) { - ok = true; - s_size = new_firmware_size; - MG_INFO(("Starting OTA, firmware size %lu", s_size)); - } else { - MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max)); - } - } - return ok; -} - -bool mg_ota_write(const void *buf, size_t len) { - bool ok = false; - if (s_size == 0) { - MG_ERROR(("OTA is not started, call mg_ota_begin()")); - } else { - size_t align = mg_flash_write_align(); - size_t len_aligned_down = MG_ROUND_DOWN(len, align); - if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down); - if (len_aligned_down < len) { - size_t left = len - len_aligned_down; - char tmp[align]; - memset(tmp, 0xff, sizeof(tmp)); - memcpy(tmp, (char *) buf + len_aligned_down, left); - ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp)); - } - s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC - MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); - s_addr += len; - } - return ok; -} - -MG_IRAM static uint32_t mg_fwkey(int fw) { - uint32_t key = MG_OTADATA_KEY + fw; - int bank = mg_flash_bank(); - if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--; - if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++; - return key; -} - -bool mg_ota_end(void) { - char *base = (char *) mg_flash_start() + mg_flash_size() / 2; - bool ok = false; - if (s_size) { - size_t size = s_addr - base; - uint32_t crc32 = mg_crc32(0, base, s_size); - if (size == s_size && crc32 == s_crc32) { - uint32_t now = (uint32_t) (mg_now() / 1000); - struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; - uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS); - ok = mg_flash_save(NULL, key, &od, sizeof(od)); - } - MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, - size, ok ? "ok" : "fail")); - s_size = 0; - if (ok) ok = mg_flash_swap_bank(); - } - MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); - return ok; -} - -MG_IRAM static struct mg_otadata mg_otadata(int fw) { - uint32_t key = mg_fwkey(fw); - struct mg_otadata od = {}; - MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev")); - mg_flash_load(NULL, key, &od, sizeof(od)); - // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); - // mg_hexdump(&od, sizeof(od)); - return od; -} - -int mg_ota_status(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.status; -} -uint32_t mg_ota_crc32(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.crc32; -} -uint32_t mg_ota_timestamp(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.timestamp; -} -size_t mg_ota_size(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.size; -} - -MG_IRAM bool mg_ota_commit(void) { - bool ok = true; - struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); - if (od.status != MG_OTA_COMMITTED) { - od.status = MG_OTA_COMMITTED; - MG_INFO(("Committing current firmware, OD size %lu", sizeof(od))); - ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od)); - } - return ok; -} - -bool mg_ota_rollback(void) { - MG_DEBUG(("Rolling firmware back")); - if (mg_flash_bank() == 0) { - // No dual bank support. Mark previous firmware as FIRST_BOOT - struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); - prev.status = MG_OTA_FIRST_BOOT; - return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev, - sizeof(prev)); - } else { - return mg_flash_swap_bank(); - } -} - -MG_IRAM void mg_ota_boot(void) { - MG_INFO(("Booting. Flash bank: %d", mg_flash_bank())); - struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT); - struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); - - if (curr.status == MG_OTA_FIRST_BOOT) { - if (prev.status == MG_OTA_UNAVAILABLE) { - MG_INFO(("Setting previous firmware state to committed")); - prev.status = MG_OTA_COMMITTED; - mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, sizeof(prev)); - } - curr.status = MG_OTA_UNCOMMITTED; - MG_INFO(("First boot, setting status to UNCOMMITTED")); - mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, sizeof(curr)); - } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) { - // Swap paritions. Pray power does not disappear - size_t fs = mg_flash_size(), ss = mg_flash_sector_size(); - char *partition1 = mg_flash_start(); - char *partition2 = mg_flash_start() + fs / 2; - size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition - - if (curr.status != MG_OTA_UNAVAILABLE && - prev.status != MG_OTA_UNAVAILABLE) { - // We know exact sizes of both firmwares. - // Shrink swap size to the MAX(firmware1, firmware2) - size_t sz = curr.size > prev.size ? curr.size : prev.size; - if (sz > 0 && sz < max) max = sz; - } - - // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED - prev.status = MG_OTA_UNCOMMITTED; - mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev, - sizeof(prev)); - mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr, - sizeof(curr)); - - MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss)); - MG_INFO(("Do NOT power off...")); - mg_log_level = MG_LL_NONE; - - // We use the last sector of partition2 for OTA data/config storage - // Therefore we can use last sector of partition1 for swapping - char *tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1 - (void) tmpsector; - for (ofs = 0; ofs < max; ofs += ss) { - // mg_flash_erase(tmpsector); - mg_flash_write(tmpsector, partition1 + ofs, ss); - // mg_flash_erase(partition1 + ofs); - mg_flash_write(partition1 + ofs, partition2 + ofs, ss); - // mg_flash_erase(partition2 + ofs); - mg_flash_write(partition2 + ofs, tmpsector, ss); - } - mg_device_reset(); - } -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/printf.c" -#endif - - - - -size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt, va_list *ap) { - size_t len = mg_snprintf(NULL, 0, fmt, ap); - char *buf; - if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) { - len = 0; // Nah. Not enough space - } else { - len = mg_vsnprintf((char *) buf, len + 1, fmt, ap); - mg_queue_add(q, len); - } - return len; -} - -size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) { - va_list ap; - size_t len; - va_start(ap, fmt); - len = mg_queue_vprintf(q, fmt, &ap); - va_end(ap); - return len; -} - -static void mg_pfn_iobuf_private(char ch, void *param, bool expand) { - struct mg_iobuf *io = (struct mg_iobuf *) param; - if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2); - if (io->len + 2 <= io->size) { - io->buf[io->len++] = (uint8_t) ch; - io->buf[io->len] = 0; - } else if (io->len < io->size) { - io->buf[io->len++] = 0; // Guarantee to 0-terminate - } -} - -static void mg_putchar_iobuf_static(char ch, void *param) { - mg_pfn_iobuf_private(ch, param, false); -} - -void mg_pfn_iobuf(char ch, void *param) { - mg_pfn_iobuf_private(ch, param, true); -} - -size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) { - struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0}; - size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap); - if (n < len) buf[n] = '\0'; - return n; -} - -size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) { - va_list ap; - size_t n; - va_start(ap, fmt); - n = mg_vsnprintf(buf, len, fmt, &ap); - va_end(ap); - return n; -} - -char *mg_vmprintf(const char *fmt, va_list *ap) { - struct mg_iobuf io = {0, 0, 0, 256}; - mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap); - return (char *) io.buf; -} - -char *mg_mprintf(const char *fmt, ...) { - char *s; - va_list ap; - va_start(ap, fmt); - s = mg_vmprintf(fmt, &ap); - va_end(ap); - return s; -} - -void mg_pfn_stdout(char c, void *param) { - putchar(c); - (void) param; -} - -static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t *p) { - return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); -} - -static size_t print_ip6(void (*out)(char, void *), void *arg, uint16_t *p) { - return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]", mg_ntohs(p[0]), - mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]), - mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]), - mg_ntohs(p[7])); -} - -size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap) { - uint8_t *p = va_arg(*ap, uint8_t *); - return print_ip4(out, arg, p); -} - -size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap) { - uint16_t *p = va_arg(*ap, uint16_t *); - return print_ip6(out, arg, p); -} - -size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap) { - struct mg_addr *addr = va_arg(*ap, struct mg_addr *); - if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *) addr->ip); - return print_ip4(out, arg, (uint8_t *) &addr->ip); -} - -size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap) { - struct mg_addr *a = va_arg(*ap, struct mg_addr *); - return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a, mg_ntohs(a->port)); -} - -size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap) { - uint8_t *p = va_arg(*ap, uint8_t *); - return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2], - p[3], p[4], p[5]); -} - -static char mg_esc(int c, bool esc) { - const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\""; - for (p = esc ? esc1 : esc2; *p != '\0'; p++) { - if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2]; - } - return 0; -} - -static char mg_escape(int c) { - return mg_esc(c, true); -} - -static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf, - size_t len) { - size_t i = 0, extra = 0; - for (i = 0; i < len && buf[i] != '\0'; i++) { - char c = mg_escape(buf[i]); - if (c) { - out('\\', ptr), out(c, ptr), extra++; - } else { - out(buf[i], ptr); - } - } - return i + extra; -} - -static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t *buf, - size_t len) { - size_t i, j, n = 0; - const char *t = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - for (i = 0; i < len; i += 3) { - uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0, - c3 = i + 2 < len ? buf[i + 2] : 0; - char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='}; - if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)]; - if (i + 2 < len) tmp[3] = t[c3 & 63]; - for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j], arg); - n += j; - } - return n; -} - -size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap) { - size_t bl = (size_t) va_arg(*ap, int); - uint8_t *p = va_arg(*ap, uint8_t *); - const char *hex = "0123456789abcdef"; - size_t j; - for (j = 0; j < bl; j++) { - out(hex[(p[j] >> 4) & 0x0F], arg); - out(hex[p[j] & 0x0F], arg); - } - return 2 * bl; -} -size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap) { - size_t len = (size_t) va_arg(*ap, int); - uint8_t *buf = va_arg(*ap, uint8_t *); - return bcpy(out, arg, buf, len); -} - -size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap) { - size_t len = (size_t) va_arg(*ap, int); - char *p = va_arg(*ap, char *); - if (len == 0) len = p == NULL ? 0 : strlen(p); - return qcpy(out, arg, p, len); -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/queue.c" -#endif - - - -#if (defined(__GNUC__) && (__GNUC__ > 4) || \ - (defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 1)) || \ - defined(__clang__) -#define MG_MEMORY_BARRIER() __sync_synchronize() -#elif defined(_MSC_VER) && _MSC_VER >= 1700 -#define MG_MEMORY_BARRIER() MemoryBarrier() -#elif !defined(MG_MEMORY_BARRIER) -#define MG_MEMORY_BARRIER() -#endif - -// Every message in a queue is prepended by a 32-bit message length (ML). -// If ML is 0, then it is the end, and reader must wrap to the beginning. -// -// Queue when q->tail <= q->head: -// |----- free -----| ML | message1 | ML | message2 | ----- free ------| -// ^ ^ ^ ^ -// buf tail head len -// -// Queue when q->tail > q->head: -// | ML | message2 |----- free ------| ML | message1 | 0 |---- free ----| -// ^ ^ ^ ^ -// buf head tail len - -void mg_queue_init(struct mg_queue *q, char *buf, size_t size) { - q->size = size; - q->buf = buf; - q->head = q->tail = 0; -} - -static size_t mg_queue_read_len(struct mg_queue *q) { - uint32_t n = 0; - MG_MEMORY_BARRIER(); - memcpy(&n, q->buf + q->tail, sizeof(n)); - assert(q->tail + n + sizeof(n) <= q->size); - return n; -} - -static void mg_queue_write_len(struct mg_queue *q, size_t len) { - uint32_t n = (uint32_t) len; - memcpy(q->buf + q->head, &n, sizeof(n)); - MG_MEMORY_BARRIER(); -} - -size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) { - size_t space = 0, hs = sizeof(uint32_t) * 2; // *2 is for the 0 marker - if (q->head >= q->tail && q->head + len + hs <= q->size) { - space = q->size - q->head - hs; // There is enough space - } else if (q->head >= q->tail && q->tail > hs) { - mg_queue_write_len(q, 0); // Not enough space ahead - q->head = 0; // Wrap head to the beginning - } - if (q->head + hs + len < q->tail) space = q->tail - q->head - hs; - if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t); - return space; -} - -size_t mg_queue_next(struct mg_queue *q, char **buf) { - size_t len = 0; - if (q->tail != q->head) { - len = mg_queue_read_len(q); - if (len == 0) { // Zero (head wrapped) ? - q->tail = 0; // Reset tail to the start - if (q->head > q->tail) len = mg_queue_read_len(q); // Read again - } - } - if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t); - assert(q->tail + len <= q->size); - return len; -} - -void mg_queue_add(struct mg_queue *q, size_t len) { - assert(len > 0); - mg_queue_write_len(q, len); - assert(q->head + sizeof(uint32_t) * 2 + len <= q->size); - q->head += len + sizeof(uint32_t); -} - -void mg_queue_del(struct mg_queue *q, size_t len) { - q->tail += len + sizeof(uint32_t); - assert(q->tail + sizeof(uint32_t) <= q->size); -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/rpc.c" -#endif - - - -void mg_rpc_add(struct mg_rpc **head, struct mg_str method, - void (*fn)(struct mg_rpc_req *), void *fn_data) { - struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc)); - if (rpc != NULL) { - rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data = fn_data; - rpc->next = *head, *head = rpc; - } -} - -void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) { - struct mg_rpc *r; - while ((r = *head) != NULL) { - if (r->fn == fn || fn == NULL) { - *head = r->next; - free((void *) r->method.ptr); - free(r); - } else { - head = &(*head)->next; - } - } -} - -static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) { - struct mg_rpc *h = r->head == NULL ? NULL : *r->head; - while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next; - if (h != NULL) { - r->rpc = h; - h->fn(r); - } else { - mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.ptr); - } -} - -void mg_rpc_process(struct mg_rpc_req *r) { - int len, off = mg_json_get(r->frame, "$.method", &len); - if (off > 0 && r->frame.ptr[off] == '"') { - struct mg_str method = mg_str_n(&r->frame.ptr[off + 1], (size_t) len - 2); - mg_rpc_call(r, method); - } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 || - (off = mg_json_get(r->frame, "$.error", &len)) > 0) { - mg_rpc_call(r, mg_str("")); // JSON response! call "" method handler - } else { - mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len, - r->frame.ptr); // Invalid - } -} - -void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) { - int len, off = mg_json_get(r->frame, "$.id", &len); - if (off > 0) { - mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc, 0, "id", len, - &r->frame.ptr[off], mg_print_esc, 0, "result"); - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap); - mg_xprintf(r->pfn, r->pfn_data, "}"); - } -} - -void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - mg_rpc_vok(r, fmt, &ap); - va_end(ap); -} - -void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) { - int len, off = mg_json_get(r->frame, "$.id", &len); - mg_xprintf(r->pfn, r->pfn_data, "{"); - if (off > 0) { - mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0, "id", len, - &r->frame.ptr[off]); - } - mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc, 0, "error", - mg_print_esc, 0, "code", code, mg_print_esc, 0, "message"); - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap); - mg_xprintf(r->pfn, r->pfn_data, "}}"); -} - -void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - mg_rpc_verr(r, code, fmt, &ap); - va_end(ap); -} - -static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) { - struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **); - size_t len = 0; - for (h = *head; h != NULL; h = h->next) { - if (h->method.len == 0) continue; // Ignore response handler - len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",", - mg_print_esc, (int) h->method.len, h->method.ptr); - } - return len; -} - -void mg_rpc_list(struct mg_rpc_req *r) { - mg_rpc_ok(r, "[%M]", print_methods, r->head); -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/sha1.c" -#endif -/* Copyright(c) By Steve Reid */ -/* 100% Public Domain */ - - - -union char64long16 { - unsigned char c[64]; - uint32_t l[16]; -}; - -#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) - -static uint32_t blk0(union char64long16 *block, int i) { - if (MG_BIG_ENDIAN) { - } else { - block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | - (rol(block->l[i], 8) & 0x00FF00FF); - } - return block->l[i]; -} - -/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */ -#undef blk -#undef R0 -#undef R1 -#undef R2 -#undef R3 -#undef R4 - -#define blk(i) \ - (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \ - block->l[(i + 2) & 15] ^ block->l[i & 15], \ - 1)) -#define R0(v, w, x, y, z, i) \ - z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \ - w = rol(w, 30); -#define R1(v, w, x, y, z, i) \ - z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ - w = rol(w, 30); -#define R2(v, w, x, y, z, i) \ - z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ - w = rol(w, 30); -#define R3(v, w, x, y, z, i) \ - z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ - w = rol(w, 30); -#define R4(v, w, x, y, z, i) \ - z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ - w = rol(w, 30); - -static void mg_sha1_transform(uint32_t state[5], - const unsigned char *buffer) { - uint32_t a, b, c, d, e; - union char64long16 block[1]; - - memcpy(block, buffer, 64); - a = state[0]; - b = state[1]; - c = state[2]; - d = state[3]; - e = state[4]; - R0(a, b, c, d, e, 0); - R0(e, a, b, c, d, 1); - R0(d, e, a, b, c, 2); - R0(c, d, e, a, b, 3); - R0(b, c, d, e, a, 4); - R0(a, b, c, d, e, 5); - R0(e, a, b, c, d, 6); - R0(d, e, a, b, c, 7); - R0(c, d, e, a, b, 8); - R0(b, c, d, e, a, 9); - R0(a, b, c, d, e, 10); - R0(e, a, b, c, d, 11); - R0(d, e, a, b, c, 12); - R0(c, d, e, a, b, 13); - R0(b, c, d, e, a, 14); - R0(a, b, c, d, e, 15); - R1(e, a, b, c, d, 16); - R1(d, e, a, b, c, 17); - R1(c, d, e, a, b, 18); - R1(b, c, d, e, a, 19); - R2(a, b, c, d, e, 20); - R2(e, a, b, c, d, 21); - R2(d, e, a, b, c, 22); - R2(c, d, e, a, b, 23); - R2(b, c, d, e, a, 24); - R2(a, b, c, d, e, 25); - R2(e, a, b, c, d, 26); - R2(d, e, a, b, c, 27); - R2(c, d, e, a, b, 28); - R2(b, c, d, e, a, 29); - R2(a, b, c, d, e, 30); - R2(e, a, b, c, d, 31); - R2(d, e, a, b, c, 32); - R2(c, d, e, a, b, 33); - R2(b, c, d, e, a, 34); - R2(a, b, c, d, e, 35); - R2(e, a, b, c, d, 36); - R2(d, e, a, b, c, 37); - R2(c, d, e, a, b, 38); - R2(b, c, d, e, a, 39); - R3(a, b, c, d, e, 40); - R3(e, a, b, c, d, 41); - R3(d, e, a, b, c, 42); - R3(c, d, e, a, b, 43); - R3(b, c, d, e, a, 44); - R3(a, b, c, d, e, 45); - R3(e, a, b, c, d, 46); - R3(d, e, a, b, c, 47); - R3(c, d, e, a, b, 48); - R3(b, c, d, e, a, 49); - R3(a, b, c, d, e, 50); - R3(e, a, b, c, d, 51); - R3(d, e, a, b, c, 52); - R3(c, d, e, a, b, 53); - R3(b, c, d, e, a, 54); - R3(a, b, c, d, e, 55); - R3(e, a, b, c, d, 56); - R3(d, e, a, b, c, 57); - R3(c, d, e, a, b, 58); - R3(b, c, d, e, a, 59); - R4(a, b, c, d, e, 60); - R4(e, a, b, c, d, 61); - R4(d, e, a, b, c, 62); - R4(c, d, e, a, b, 63); - R4(b, c, d, e, a, 64); - R4(a, b, c, d, e, 65); - R4(e, a, b, c, d, 66); - R4(d, e, a, b, c, 67); - R4(c, d, e, a, b, 68); - R4(b, c, d, e, a, 69); - R4(a, b, c, d, e, 70); - R4(e, a, b, c, d, 71); - R4(d, e, a, b, c, 72); - R4(c, d, e, a, b, 73); - R4(b, c, d, e, a, 74); - R4(a, b, c, d, e, 75); - R4(e, a, b, c, d, 76); - R4(d, e, a, b, c, 77); - R4(c, d, e, a, b, 78); - R4(b, c, d, e, a, 79); - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - state[4] += e; - /* Erase working structures. The order of operations is important, - * used to ensure that compiler doesn't optimize those out. */ - memset(block, 0, sizeof(block)); - a = b = c = d = e = 0; - (void) a; - (void) b; - (void) c; - (void) d; - (void) e; -} - -void mg_sha1_init(mg_sha1_ctx *context) { - context->state[0] = 0x67452301; - context->state[1] = 0xEFCDAB89; - context->state[2] = 0x98BADCFE; - context->state[3] = 0x10325476; - context->state[4] = 0xC3D2E1F0; - context->count[0] = context->count[1] = 0; -} - -void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data, - size_t len) { - size_t i, j; - - j = context->count[0]; - if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++; - context->count[1] += (uint32_t) (len >> 29); - j = (j >> 3) & 63; - if ((j + len) > 63) { - memcpy(&context->buffer[j], data, (i = 64 - j)); - mg_sha1_transform(context->state, context->buffer); - for (; i + 63 < len; i += 64) { - mg_sha1_transform(context->state, &data[i]); - } - j = 0; - } else - i = 0; - memcpy(&context->buffer[j], &data[i], len - i); -} - -void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) { - unsigned i; - unsigned char finalcount[8], c; - - for (i = 0; i < 8; i++) { - finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> - ((3 - (i & 3)) * 8)) & - 255); - } - c = 0200; - mg_sha1_update(context, &c, 1); - while ((context->count[0] & 504) != 448) { - c = 0000; - mg_sha1_update(context, &c, 1); - } - mg_sha1_update(context, finalcount, 8); - for (i = 0; i < 20; i++) { - digest[i] = - (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); - } - memset(context, '\0', sizeof(*context)); - memset(&finalcount, '\0', sizeof(finalcount)); -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/sha256.c" -#endif - - -#define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) -#define ch(x, y, z) (((x) & (y)) ^ (~(x) & (z))) -#define maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) -#define ep0(x) (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22)) -#define ep1(x) (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25)) -#define sig0(x) (ror(x, 7) ^ ror(x, 18) ^ ((x) >> 3)) -#define sig1(x) (ror(x, 17) ^ ror(x, 19) ^ ((x) >> 10)) - -static const uint32_t mg_sha256_k[64] = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, - 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, - 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, - 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, - 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, - 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; - -void mg_sha256_init(mg_sha256_ctx *ctx) { - ctx->len = 0; - ctx->bits = 0; - ctx->state[0] = 0x6a09e667; - ctx->state[1] = 0xbb67ae85; - ctx->state[2] = 0x3c6ef372; - ctx->state[3] = 0xa54ff53a; - ctx->state[4] = 0x510e527f; - ctx->state[5] = 0x9b05688c; - ctx->state[6] = 0x1f83d9ab; - ctx->state[7] = 0x5be0cd19; -} - -static void mg_sha256_chunk(mg_sha256_ctx *ctx) { - int i, j; - uint32_t a, b, c, d, e, f, g, h; - uint32_t m[64]; - for (i = 0, j = 0; i < 16; ++i, j += 4) - m[i] = (uint32_t) ((ctx->buffer[j] << 24) | (ctx->buffer[j + 1] << 16) | - (ctx->buffer[j + 2] << 8) | (ctx->buffer[j + 3])); - for (; i < 64; ++i) - m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16]; - - a = ctx->state[0]; - b = ctx->state[1]; - c = ctx->state[2]; - d = ctx->state[3]; - e = ctx->state[4]; - f = ctx->state[5]; - g = ctx->state[6]; - h = ctx->state[7]; - - for (i = 0; i < 64; ++i) { - uint32_t t1 = h + ep1(e) + ch(e, f, g) + mg_sha256_k[i] + m[i]; - uint32_t t2 = ep0(a) + maj(a, b, c); - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - - ctx->state[0] += a; - ctx->state[1] += b; - ctx->state[2] += c; - ctx->state[3] += d; - ctx->state[4] += e; - ctx->state[5] += f; - ctx->state[6] += g; - ctx->state[7] += h; -} - -void mg_sha256_update(mg_sha256_ctx *ctx, const unsigned char *data, - size_t len) { - size_t i; - for (i = 0; i < len; i++) { - ctx->buffer[ctx->len] = data[i]; - if ((++ctx->len) == 64) { - mg_sha256_chunk(ctx); - ctx->bits += 512; - ctx->len = 0; - } - } -} - -// TODO: make final reusable (remove side effects) -void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *ctx) { - uint32_t i = ctx->len; - if (i < 56) { - ctx->buffer[i++] = 0x80; - while (i < 56) { - ctx->buffer[i++] = 0x00; - } - } else { - ctx->buffer[i++] = 0x80; - while (i < 64) { - ctx->buffer[i++] = 0x00; - } - mg_sha256_chunk(ctx); - memset(ctx->buffer, 0, 56); - } - - ctx->bits += ctx->len * 8; - ctx->buffer[63] = (uint8_t) ((ctx->bits) & 0xff); - ctx->buffer[62] = (uint8_t) ((ctx->bits >> 8) & 0xff); - ctx->buffer[61] = (uint8_t) ((ctx->bits >> 16) & 0xff); - ctx->buffer[60] = (uint8_t) ((ctx->bits >> 24) & 0xff); - ctx->buffer[59] = (uint8_t) ((ctx->bits >> 32) & 0xff); - ctx->buffer[58] = (uint8_t) ((ctx->bits >> 40) & 0xff); - ctx->buffer[57] = (uint8_t) ((ctx->bits >> 48) & 0xff); - ctx->buffer[56] = (uint8_t) ((ctx->bits >> 56) & 0xff); - mg_sha256_chunk(ctx); - - for (i = 0; i < 4; ++i) { - digest[i] = (ctx->state[0] >> (24 - i * 8)) & 0xff; - digest[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0xff; - digest[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0xff; - digest[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0xff; - digest[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0xff; - digest[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0xff; - digest[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0xff; - digest[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0xff; - } -} - -void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, - size_t datasz) { - mg_sha256_ctx ctx; - uint8_t k[64] = {0}; - uint8_t o_pad[64], i_pad[64]; - unsigned int i; - memset(i_pad, 0x36, sizeof(i_pad)); - memset(o_pad, 0x5c, sizeof(o_pad)); - if (keysz < 64) { - memmove(k, key, keysz); - } else { - mg_sha256_init(&ctx); - mg_sha256_update(&ctx, key, keysz); - mg_sha256_final(k, &ctx); - } - for (i = 0; i < sizeof(k); i++) { - i_pad[i] ^= k[i]; - o_pad[i] ^= k[i]; - } - mg_sha256_init(&ctx); - mg_sha256_update(&ctx, i_pad, sizeof(i_pad)); - mg_sha256_update(&ctx, data, datasz); - mg_sha256_final(dst, &ctx); - mg_sha256_init(&ctx); - mg_sha256_update(&ctx, o_pad, sizeof(o_pad)); - mg_sha256_update(&ctx, dst, 32); - mg_sha256_final(dst, &ctx); -} - - -#ifdef MG_ENABLE_LINES -#line 1 "src/sntp.c" -#endif - - - - - - -#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds -#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1 - -static int64_t gettimestamp(const uint32_t *data) { - uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]); - if (sec) sec -= SNTP_TIME_OFFSET; - return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0); -} - -int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { - int64_t res = -1; - int mode = len > 0 ? buf[0] & 7 : 0; - int version = len > 0 ? (buf[0] >> 3) & 7 : 0; - if (len < 48) { - MG_ERROR(("%s", "corrupt packet")); - } else if (mode != 4 && mode != 5) { - MG_ERROR(("%s", "not a server reply")); - } else if (buf[1] == 0) { - MG_ERROR(("%s", "server sent a kiss of death")); - } else if (version == 4 || version == 3) { - // int64_t ref = gettimestamp((uint32_t *) &buf[16]); - int64_t t0 = gettimestamp((uint32_t *) &buf[24]); - int64_t t1 = gettimestamp((uint32_t *) &buf[32]); - int64_t t2 = gettimestamp((uint32_t *) &buf[40]); - int64_t t3 = (int64_t) mg_millis(); - int64_t delta = (t3 - t0) - (t2 - t1); - MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta)); - res = t2 + delta / 2; - } else { - MG_ERROR(("unexpected version: %d", version)); - } - return res; -} - -static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ) { - int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len); - if (milliseconds > 0) { - MG_DEBUG(("%lu got time: %lld ms from epoch", c->id, milliseconds)); - mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds); - MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000), - (unsigned) (milliseconds % 1000))); - } - mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer - } else if (ev == MG_EV_CONNECT) { - mg_sntp_request(c); - } else if (ev == MG_EV_CLOSE) { - } - (void) ev_data; -} - -void mg_sntp_request(struct mg_connection *c) { - if (c->is_resolving) { - MG_ERROR(("%lu wait until resolved", c->id)); - } else { - int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98 - uint8_t buf[48] = {0}; - uint32_t *t = (uint32_t *) &buf[40]; - double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC; - buf[0] = (0 << 6) | (4 << 3) | 3; - t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET); - t[1] = mg_htonl((uint32_t) frac); - mg_send(c, buf, sizeof(buf)); - } -} - -struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fnd) { - struct mg_connection *c = NULL; - if (url == NULL) url = "udp://time.google.com:123"; - if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb; - return c; -} - -#ifdef MG_ENABLE_LINES -#line 1 "src/sock.c" -#endif - - - - - - - - - - - -#if MG_ENABLE_SOCKET - -#ifndef closesocket -#define closesocket(x) close(x) -#endif - -#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd) -#define S2PTR(s_) ((void *) (size_t) (s_)) - -#ifndef MSG_NONBLOCKING -#define MSG_NONBLOCKING 0 -#endif - -#ifndef AF_INET6 -#define AF_INET6 10 -#endif - -#ifndef MG_SOCK_ERR -#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0) -#endif - -#ifndef MG_SOCK_INTR -#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET && MG_SOCK_ERR(-1) == EINTR) -#endif - -#ifndef MG_SOCK_PENDING -#define MG_SOCK_PENDING(errcode) \ - (((errcode) < 0) && (errno == EINPROGRESS || errno == EWOULDBLOCK)) -#endif - -#ifndef MG_SOCK_RESET -#define MG_SOCK_RESET(errcode) \ - (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET)) -#endif - -union usa { - struct sockaddr sa; - struct sockaddr_in sin; -#if MG_ENABLE_IPV6 - struct sockaddr_in6 sin6; -#endif -}; - -static socklen_t tousa(struct mg_addr *a, union usa *usa) { - socklen_t len = sizeof(usa->sin); - memset(usa, 0, sizeof(*usa)); - usa->sin.sin_family = AF_INET; - usa->sin.sin_port = a->port; - memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t)); -#if MG_ENABLE_IPV6 - if (a->is_ip6) { - usa->sin.sin_family = AF_INET6; - usa->sin6.sin6_port = a->port; - usa->sin6.sin6_scope_id = a->scope_id; - memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip)); - len = sizeof(usa->sin6); - } -#endif - return len; -} - -static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) { - a->is_ip6 = is_ip6; - a->port = usa->sin.sin_port; - memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t)); -#if MG_ENABLE_IPV6 - if (is_ip6) { - memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip)); - a->port = usa->sin6.sin6_port; - a->scope_id = (uint8_t) usa->sin6.sin6_scope_id; - } -#endif -} - -static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) { - union usa usa; - socklen_t n = sizeof(usa); - if (getsockname(fd, &usa.sa, &n) == 0) { - tomgaddr(&usa, addr, n != sizeof(usa.sin)); - } -} - -static void iolog(struct mg_connection *c, char *buf, long n, bool r) { - if (n == MG_IO_WAIT) { - // Do nothing - } else if (n <= 0) { - c->is_closing = 1; // Termination. Don't call mg_error(): #1529 - } else if (n > 0) { - if (c->is_hexdumping) { - MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port, &c->loc, - r ? "<-" : "->", mg_print_ip_port, &c->rem, n)); - mg_hexdump(buf, (size_t) n); - } - if (r) { - c->recv.len += (size_t) n; - mg_call(c, MG_EV_READ, &n); - } else { - mg_iobuf_del(&c->send, 0, (size_t) n); - // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0); - if (c->send.len == 0) { - MG_EPOLL_MOD(c, 0); - } - mg_call(c, MG_EV_WRITE, &n); - } - } -} - -long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { - long n; - if (c->is_udp) { - union usa usa; - socklen_t slen = tousa(&c->rem, &usa); - n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen); - if (n > 0) setlocaddr(FD(c), &c->loc); - } else { - n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING); - } - MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n))); - if (MG_SOCK_PENDING(n)) return MG_IO_WAIT; - if (MG_SOCK_RESET(n)) return MG_IO_RESET; - if (n <= 0) return MG_IO_ERR; - return n; -} - -bool mg_send(struct mg_connection *c, const void *buf, size_t len) { - if (c->is_udp) { - long n = mg_io_send(c, buf, len); - MG_DEBUG(("%lu %ld %d:%d %ld err %d", c->id, c->fd, (int) c->send.len, - (int) c->recv.len, n, MG_SOCK_ERR(n))); - iolog(c, (char *) buf, n, false); - return n > 0; - } else { - return mg_iobuf_add(&c->send, c->send.len, buf, len); - } -} - -static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) { -#if defined(MG_CUSTOM_NONBLOCK) - MG_CUSTOM_NONBLOCK(fd); -#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK - unsigned long on = 1; - ioctlsocket(fd, FIONBIO, &on); -#elif MG_ENABLE_RL - unsigned long on = 1; - ioctlsocket(fd, FIONBIO, &on); -#elif MG_ENABLE_FREERTOS_TCP - const BaseType_t off = 0; - if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0; - if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0; -#elif MG_ENABLE_LWIP - lwip_fcntl(fd, F_SETFL, O_NONBLOCK); -#elif MG_ARCH == MG_ARCH_AZURERTOS - fcntl(fd, F_SETFL, O_NONBLOCK); -#elif MG_ARCH == MG_ARCH_TIRTOS - int val = 0; - setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val)); - // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT - int sz = sizeof(val); - getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz); - val /= 2; // set send low-water mark at half send buffer size - setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val)); -#else - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // Non-blocking mode - fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec -#endif -} - -bool mg_open_listener(struct mg_connection *c, const char *url) { - MG_SOCKET_TYPE fd = MG_INVALID_SOCKET; - bool success = false; - c->loc.port = mg_htons(mg_url_port(url)); - if (!mg_aton(mg_url_host(url), &c->loc)) { - MG_ERROR(("invalid listening URL: %s", url)); - } else { - union usa usa; - socklen_t slen = tousa(&c->loc, &usa); - int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET; - int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM; - int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; - (void) on; - - if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) { - MG_ERROR(("socket: %d", MG_SOCK_ERR(-1))); -#if defined(SO_EXCLUSIVEADDRUSE) - } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, - (char *) &on, sizeof(on))) != 0) { - // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" - MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on, MG_SOCK_ERR(rc))); -#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE) - } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, - sizeof(on))) != 0) { - // 1. SO_REUSEADDR semantics on UNIX and Windows is different. On - // Windows, SO_REUSEADDR allows to bind a socket to a port without error - // even if the port is already open by another program. This is not the - // behavior SO_REUSEADDR was designed for, and leads to hard-to-track - // failure scenarios. - // - // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by defining - // SO_REUSE = 1 in lwipopts.h, otherwise the code below will compile but - // won't work! (setsockopt will return EINVAL) - MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc))); -#endif -#if MG_IPV6_V6ONLY - // Bind only to the V6 address, not V4 address on this port - } else if (c->loc.is_ip6 && - (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on, - sizeof(on))) != 0) { - // See #2089. Allow to bind v4 and v6 sockets on the same port - MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc))); -#endif - } else if ((rc = bind(fd, &usa.sa, slen)) != 0) { - MG_ERROR(("bind: %d", MG_SOCK_ERR(rc))); - } else if ((type == SOCK_STREAM && - (rc = listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE)) != 0)) { - // NOTE(lsm): FreeRTOS uses backlog value as a connection limit - // In case port was set to 0, get the real port number - MG_ERROR(("listen: %d", MG_SOCK_ERR(rc))); - } else { - setlocaddr(fd, &c->loc); - mg_set_non_blocking_mode(fd); - c->fd = S2PTR(fd); - MG_EPOLL_ADD(c); - success = true; - } - } - if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd); - return success; -} - -static long recv_raw(struct mg_connection *c, void *buf, size_t len) { - long n = 0; - if (c->is_udp) { - union usa usa; - socklen_t slen = tousa(&c->rem, &usa); - n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); - if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin)); - } else { - n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); - } - MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n))); - if (MG_SOCK_PENDING(n)) return MG_IO_WAIT; - if (MG_SOCK_RESET(n)) return MG_IO_RESET; - if (n <= 0) return MG_IO_ERR; - return n; -} - -static bool ioalloc(struct mg_connection *c, struct mg_iobuf *io) { - bool res = false; - if (io->len >= MG_MAX_RECV_SIZE) { - mg_error(c, "MG_MAX_RECV_SIZE"); - } else if (io->size <= io->len && - !mg_iobuf_resize(io, io->size + MG_IO_SIZE)) { - mg_error(c, "OOM"); - } else { - res = true; - } - return res; -} - -// NOTE(lsm): do only one iteration of reads, cause some systems -// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data -static void read_conn(struct mg_connection *c) { - if (ioalloc(c, &c->recv)) { - char *buf = (char *) &c->recv.buf[c->recv.len]; - size_t len = c->recv.size - c->recv.len; - long n = -1; - if (c->is_tls) { - if (!ioalloc(c, &c->rtls)) return; - n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len], - c->rtls.size - c->rtls.len); - if (n == MG_IO_ERR && c->rtls.len == 0) { - // Close only if we have fully drained both raw (rtls) and TLS buffers - c->is_closing = 1; - } else { - if (n > 0) c->rtls.len += (size_t) n; - if (c->is_tls_hs) mg_tls_handshake(c); - if (c->is_tls_hs) return; - n = mg_tls_recv(c, buf, len); - } - } else { - n = recv_raw(c, buf, len); - } - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd, - (long) c->send.len, (long) c->send.size, (long) c->recv.len, - (long) c->recv.size, n, MG_SOCK_ERR(n))); - iolog(c, buf, n, true); - } -} - -static void write_conn(struct mg_connection *c) { - char *buf = (char *) c->send.buf; - size_t len = c->send.len; - long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, len); - MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd, - (long) c->send.len, (long) c->send.size, (long) c->recv.len, - (long) c->recv.size, n, MG_SOCK_ERR(n))); - iolog(c, buf, n, false); -} - -static void close_conn(struct mg_connection *c) { - if (FD(c) != MG_INVALID_SOCKET) { -#if MG_ENABLE_EPOLL - epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL); -#endif - closesocket(FD(c)); -#if MG_ENABLE_FREERTOS_TCP - FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL); -#endif - } - mg_close_conn(c); -} - -static void connect_conn(struct mg_connection *c) { - union usa usa; - socklen_t n = sizeof(usa); - // Use getpeername() to test whether we have connected - if (getpeername(FD(c), &usa.sa, &n) == 0) { - c->is_connecting = 0; - setlocaddr(FD(c), &c->loc); - mg_call(c, MG_EV_CONNECT, NULL); - MG_EPOLL_MOD(c, 0); - if (c->is_tls_hs) mg_tls_handshake(c); - } else { - mg_error(c, "socket error"); - } -} - -static void setsockopts(struct mg_connection *c) { -#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \ - MG_ARCH == MG_ARCH_TIRTOS - (void) c; -#else - int on = 1; -#if !defined(SOL_TCP) -#define SOL_TCP IPPROTO_TCP -#endif - if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0) - (void) 0; - if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) != - 0) - (void) 0; -#endif -} - -void mg_connect_resolved(struct mg_connection *c) { - int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; - int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has resolved IP - c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket - c->is_resolving = 0; // Clear resolving flag - if (FD(c) == MG_INVALID_SOCKET) { - mg_error(c, "socket(): %d", MG_SOCK_ERR(-1)); - } else if (c->is_udp) { - MG_EPOLL_ADD(c); -#if MG_ARCH == MG_ARCH_TIRTOS - union usa usa; // TI-RTOS NDK requires binding to receive on UDP sockets - socklen_t slen = tousa(&c->loc, &usa); - if ((rc = bind(c->fd, &usa.sa, slen)) != 0) - MG_ERROR(("bind: %d", MG_SOCK_ERR(rc))); -#endif - setlocaddr(FD(c), &c->loc); - mg_call(c, MG_EV_RESOLVE, NULL); - mg_call(c, MG_EV_CONNECT, NULL); - } else { - union usa usa; - socklen_t slen = tousa(&c->rem, &usa); - mg_set_non_blocking_mode(FD(c)); - setsockopts(c); - MG_EPOLL_ADD(c); - mg_call(c, MG_EV_RESOLVE, NULL); - rc = connect(FD(c), &usa.sa, slen); // Attempt to connect - if (rc == 0) { // Success - setlocaddr(FD(c), &c->loc); - mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user - } else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP handshake - MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port, &c->rem)); - c->is_connecting = 1; - } else { - mg_error(c, "connect: %d", MG_SOCK_ERR(rc)); - } - } -} - -static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa, - socklen_t *len) { - MG_SOCKET_TYPE fd = MG_INVALID_SOCKET; - do { - memset(usa, 0, sizeof(*usa)); - fd = accept(sock, &usa->sa, len); - } while (MG_SOCK_INTR(fd)); - return fd; -} - -static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) { - struct mg_connection *c = NULL; - union usa usa; - socklen_t sa_len = sizeof(usa); - MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len); - if (fd == MG_INVALID_SOCKET) { -#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS) - // AzureRTOS, in non-block socket mode can mark listening socket readable - // even it is not. See comment for 'select' func implementation in - // nx_bsd.c That's not an error, just should try later - if (errno != EAGAIN) -#endif - MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERR(-1))); -#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \ - (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL - } else if ((long) fd >= FD_SETSIZE) { - MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE)); - closesocket(fd); -#endif - } else if ((c = mg_alloc_conn(mgr)) == NULL) { - MG_ERROR(("%lu OOM", lsn->id)); - closesocket(fd); - } else { - tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin)); - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); - c->fd = S2PTR(fd); - MG_EPOLL_ADD(c); - mg_set_non_blocking_mode(FD(c)); - setsockopts(c); - c->is_accepted = 1; - c->is_hexdumping = lsn->is_hexdumping; - c->loc = lsn->loc; - c->pfn = lsn->pfn; - c->pfn_data = lsn->pfn_data; - c->fn = lsn->fn; - c->fn_data = lsn->fn_data; - MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd, mg_print_ip_port, - &c->rem, mg_print_ip_port, &c->loc)); - mg_call(c, MG_EV_OPEN, NULL); - mg_call(c, MG_EV_ACCEPT, NULL); - } -} - -static bool can_read(const struct mg_connection *c) { - return c->is_full == false; -} - -static bool can_write(const struct mg_connection *c) { - return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0); -} - -static bool skip_iotest(const struct mg_connection *c) { - return (c->is_closing || c->is_resolving || FD(c) == MG_INVALID_SOCKET) || - (can_read(c) == false && can_write(c) == false); -} - -static void mg_iotest(struct mg_mgr *mgr, int ms) { -#if MG_ENABLE_FREERTOS_TCP - struct mg_connection *c; - for (c = mgr->conns; c != NULL; c = c->next) { - c->is_readable = c->is_writable = 0; - if (skip_iotest(c)) continue; - if (can_read(c)) - FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT); - if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE); - if (c->is_closing) ms = 1; - } - FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms)); - for (c = mgr->conns; c != NULL; c = c->next) { - EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss); - c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0; - c->is_writable = bits & eSELECT_WRITE ? 1U : 0; - if (c->fd != MG_INVALID_SOCKET) - FreeRTOS_FD_CLR(c->fd, mgr->ss, - eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE); - } -#elif MG_ENABLE_EPOLL - size_t max = 1; - for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { - c->is_readable = c->is_writable = 0; - if (c->rtls.len > 0) ms = 1, c->is_readable = 1; - if (can_write(c)) MG_EPOLL_MOD(c, 1); - if (c->is_closing) ms = 1; - max++; - } - struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0])); - int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms); - for (int i = 0; i < n; i++) { - struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr; - if (evs[i].events & EPOLLERR) { - mg_error(c, "socket error"); - } else if (c->is_readable == 0) { - bool rd = evs[i].events & (EPOLLIN | EPOLLHUP); - bool wr = evs[i].events & EPOLLOUT; - c->is_readable = can_read(c) && rd ? 1U : 0; - c->is_writable = can_write(c) && wr ? 1U : 0; - if (c->rtls.len > 0) c->is_readable = 1; - } - } - (void) skip_iotest; -#elif MG_ENABLE_POLL - nfds_t n = 0; - for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++; - struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0])); - memset(fds, 0, n * sizeof(fds[0])); - n = 0; - for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { - c->is_readable = c->is_writable = 0; - if (skip_iotest(c)) { - // Socket not valid, ignore - } else if (c->rtls.len > 0) { - ms = 1; // Don't wait if TLS is ready - } else { - fds[n].fd = FD(c); - if (can_read(c)) fds[n].events |= POLLIN; - if (can_write(c)) fds[n].events |= POLLOUT; - if (c->is_closing) ms = 1; - n++; - } - } - - // MG_INFO(("poll n=%d ms=%d", (int) n, ms)); - if (poll(fds, n, ms) < 0) { -#if MG_ARCH == MG_ARCH_WIN32 - if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets -#endif - memset(fds, 0, n * sizeof(fds[0])); - } - n = 0; - for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { - if (skip_iotest(c)) { - // Socket not valid, ignore - } else if (c->rtls.len > 0) { - c->is_readable = 1; - } else { - if (fds[n].revents & POLLERR) { - mg_error(c, "socket error"); - } else { - c->is_readable = - (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0); - c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0); - if (c->rtls.len > 0) c->is_readable = 1; - } - n++; - } - } -#else - struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0}, *tvp; - struct mg_connection *c; - fd_set rset, wset, eset; - MG_SOCKET_TYPE maxfd = 0; - int rc; - - FD_ZERO(&rset); - FD_ZERO(&wset); - FD_ZERO(&eset); - tvp = ms < 0 ? NULL : &tv; - for (c = mgr->conns; c != NULL; c = c->next) { - c->is_readable = c->is_writable = 0; - if (skip_iotest(c)) continue; - FD_SET(FD(c), &eset); - if (can_read(c)) FD_SET(FD(c), &rset); - if (can_write(c)) FD_SET(FD(c), &wset); - if (c->rtls.len > 0) tvp = &tv_zero; - if (FD(c) > maxfd) maxfd = FD(c); - if (c->is_closing) ms = 1; - } - - if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) < 0) { -#if MG_ARCH == MG_ARCH_WIN32 - if (maxfd == 0) Sleep(ms); // On Windows, select fails if no sockets -#else - MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc))); -#endif - FD_ZERO(&rset); - FD_ZERO(&wset); - FD_ZERO(&eset); - } - - for (c = mgr->conns; c != NULL; c = c->next) { - if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) { - mg_error(c, "socket error"); - } else { - c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset); - c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset); - if (c->rtls.len > 0) c->is_readable = 1; - } - } -#endif -} - -static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2]) { - socklen_t n = sizeof(usa[0].sin); - bool success = false; - - sp[0] = sp[1] = MG_INVALID_SOCKET; - (void) memset(&usa[0], 0, sizeof(usa[0])); - usa[0].sin.sin_family = AF_INET; - *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); // 127.0.0.1 - usa[1] = usa[0]; - - if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET && - (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET && - bind(sp[0], &usa[0].sa, n) == 0 && // - bind(sp[1], &usa[1].sa, n) == 0 && // - getsockname(sp[0], &usa[0].sa, &n) == 0 && // - getsockname(sp[1], &usa[1].sa, &n) == 0 && // - connect(sp[0], &usa[1].sa, n) == 0 && // - connect(sp[1], &usa[0].sa, n) == 0) { // - success = true; - } - if (!success) { - if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]); - if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]); - sp[0] = sp[1] = MG_INVALID_SOCKET; - } - return success; -} - -// mg_wakeup() event handler -static void wufn(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ) { - unsigned long *id = (unsigned long *) c->recv.buf; - // MG_INFO(("Got data")); - // mg_hexdump(c->recv.buf, c->recv.len); - if (c->recv.len >= sizeof(*id)) { - struct mg_connection *t; - for (t = c->mgr->conns; t != NULL; t = t->next) { - if (t->id == *id) { - struct mg_str data = mg_str_n((char *) c->recv.buf + sizeof(*id), - c->recv.len - sizeof(*id)); - mg_call(t, MG_EV_WAKEUP, &data); - } - } - } - c->recv.len = 0; // Consume received data - } else if (ev == MG_EV_CLOSE) { - closesocket(c->mgr->pipe); // When we're closing, close the other - c->mgr->pipe = MG_INVALID_SOCKET; // side of the socketpair, too - } - (void) ev_data; -} - -bool mg_wakeup_init(struct mg_mgr *mgr) { - bool ok = false; - if (mgr->pipe == MG_INVALID_SOCKET) { - union usa usa[2]; - MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET}; - struct mg_connection *c = NULL; - if (!mg_socketpair(sp, usa)) { - MG_ERROR(("Cannot create socket pair")); - } else if ((c = mg_wrapfd(mgr, (int) sp[1], wufn, NULL)) == NULL) { - closesocket(sp[0]); - closesocket(sp[1]); - sp[0] = sp[1] = MG_INVALID_SOCKET; - } else { - tomgaddr(&usa[0], &c->rem, false); - MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0])); - mgr->pipe = sp[0]; - ok = true; - } - } - return ok; -} - -bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf, - size_t len) { - if (mgr->pipe != MG_INVALID_SOCKET && conn_id > 0) { - char *extended_buf = (char *) alloca(len + sizeof(conn_id)); - memcpy(extended_buf, &conn_id, sizeof(conn_id)); - memcpy(extended_buf + sizeof(conn_id), buf, len); - send(mgr->pipe, extended_buf, len + sizeof(conn_id), MSG_NONBLOCKING); - return true; - } - return false; -} - -void mg_mgr_poll(struct mg_mgr *mgr, int ms) { - struct mg_connection *c, *tmp; - uint64_t now; - - mg_iotest(mgr, ms); - now = mg_millis(); - mg_timer_poll(&mgr->timers, now); - - for (c = mgr->conns; c != NULL; c = tmp) { - bool is_resp = c->is_resp; - tmp = c->next; - mg_call(c, MG_EV_POLL, &now); - if (is_resp && !c->is_resp) { - long n = 0; - mg_call(c, MG_EV_READ, &n); - } - MG_VERBOSE(("%lu %c%c %c%c%c%c%c %lu %lu", c->id, - c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-', - c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c', - c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r', - c->is_closing ? 'C' : 'c', mg_tls_pending(c), c->rtls.len)); - if (c->is_resolving || c->is_closing) { - // Do nothing - } else if (c->is_listening && c->is_udp == 0) { - if (c->is_readable) accept_conn(mgr, c); - } else if (c->is_connecting) { - if (c->is_readable || c->is_writable) connect_conn(c); - //} else if (c->is_tls_hs) { - // if ((c->is_readable || c->is_writable)) mg_tls_handshake(c); - } else { - if (c->is_readable) read_conn(c); - if (c->is_writable) write_conn(c); - } - - if (c->is_draining && c->send.len == 0) c->is_closing = 1; - if (c->is_closing) close_conn(c); - } -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/ssi.c" -#endif - - - - -#ifndef MG_MAX_SSI_DEPTH -#define MG_MAX_SSI_DEPTH 5 -#endif - -#ifndef MG_SSI_BUFSIZ -#define MG_SSI_BUFSIZ 1024 -#endif - -#if MG_ENABLE_SSI -static char *mg_ssi(const char *path, const char *root, int depth) { - struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE}; - FILE *fp = fopen(path, "rb"); - if (fp != NULL) { - char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)]; - int ch, intag = 0; - size_t len = 0; - buf[0] = arg[0] = '\0'; - while ((ch = fgetc(fp)) != EOF) { - if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { - buf[len++] = (char) (ch & 0xff); - buf[len] = '\0'; - if (sscanf(buf, " %#x %#x", s_txdesc[s_txno][1], tsr)); - if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |= MG_BIT(31); - } - - GMAC_REGS->GMAC_RSR = rsr; - GMAC_REGS->GMAC_TSR = tsr; -} - -struct mg_tcpip_driver mg_tcpip_driver_same54 = { - mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL, - mg_tcpip_driver_same54_up}; -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/drivers/stm32f.c" -#endif - - -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \ - MG_ENABLE_DRIVER_STM32F -struct stm32f_eth { - volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR, - MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR, - MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR, - MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, - RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR, - RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR, - RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR, - PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, RESERVED9[564], - DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER, - DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR, - DMACHRBAR; -}; -#undef ETH -#define ETH ((struct stm32f_eth *) (uintptr_t) 0x40028000) - -#define ETH_PKT_SIZE 1540 // Max frame size -#define ETH_DESC_CNT 4 // Descriptors count -#define ETH_DS 4 // Descriptor size (words) - -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers -static uint8_t s_txno; // Current TX descriptor -static uint8_t s_rxno; // Current RX descriptor - -static struct mg_tcpip_if *s_ifp; // MIP interface -enum { - MG_PHYREG_BCR = 0, - MG_PHYREG_BSR = 1, - MG_PHYREG_ID1 = 2, - MG_PHYREG_ID2 = 3, - MG_PHYREG_CSCR = 31 -}; - -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) { - ETH->MACMIIAR &= (7 << 2); - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6); - ETH->MACMIIAR |= MG_BIT(0); - while (ETH->MACMIIAR & MG_BIT(0)) (void) 0; - return ETH->MACMIIDR; -} - -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { - ETH->MACMIIDR = val; - ETH->MACMIIAR &= (7 << 2); - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1); - ETH->MACMIIAR |= MG_BIT(0); - while (ETH->MACMIIAR & MG_BIT(0)) (void) 0; -} - -static uint32_t get_hclk(void) { - struct rcc { - volatile uint32_t CR, PLLCFGR, CFGR; - } *rcc = (struct rcc *) 0x40023800; - uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */; - - if (rcc->CFGR & (1 << 2)) { - clk = hse; - } else if (rcc->CFGR & (1 << 3)) { - uint32_t vco, m, n, p; - m = (rcc->PLLCFGR & (0x3f << 0)) >> 0; - n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6; - p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2; - clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi; - vco = (uint32_t) ((uint64_t) clk * n / m); - clk = vco / p; - } else { - clk = hsi; - } - uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4; - if (hpre < 8) return clk; - - uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) - return ((uint32_t) clk) >> ahbptab[hpre - 8]; -} - -// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3, -// it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived -// from the HSI (internal RC), and it can go above specs, the datasheets -// specify a range of frequencies and activate one of a series of dividers to -// keep the MDC clock safely below 2.5MHz. We guess a divider setting based on -// HCLK with a +5% drift. If the user uses a different clock from our -// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx -// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift) -static int guess_mdc_cr(void) { - uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values - uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers - uint32_t hclk = get_hclk(); // Guess system HCLK - int result = -1; // Invalid CR value - if (hclk < 25000000) { - MG_ERROR(("HCLK too low")); - } else { - for (int i = 0; i < 6; i++) { - if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { - result = crs[i]; - break; - } - } - if (result < 0) MG_ERROR(("HCLK too high")); - } - MG_DEBUG(("HCLK: %u, CR: %d", hclk, result)); - return result; -} - -static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) { - struct mg_tcpip_driver_stm32f_data *d = - (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data; - uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; - s_ifp = ifp; - - // Init RX descriptors - for (int i = 0; i < ETH_DESC_CNT; i++) { - s_rxdesc[i][0] = MG_BIT(31); // Own - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd address chained - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data buffer - s_rxdesc[i][3] = - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain - } - - // Init TX descriptors - for (int i = 0; i < ETH_DESC_CNT; i++) { - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer - s_txdesc[i][3] = - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain - } - - ETH->DMABMR |= MG_BIT(0); // Software reset - while ((ETH->DMABMR & MG_BIT(0)) != 0) (void) 0; // Wait until done - - // Set MDC clock divider. If user told us the value, use it. Otherwise, guess - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; - ETH->MACMIIAR = ((uint32_t) cr & 7) << 2; - - // NOTE(cpq): we do not use extended descriptor bit 7, and do not use - // hardware checksum. Therefore, descriptor size is 4, not 8 - // ETH->DMABMR = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) | - // MG_BIT(25); - ETH->MACIMR = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT - ETH->MACFCR = MG_BIT(7); // Disable zero quarta pause - // ETH->MACFFR = MG_BIT(31); // Receive all - eth_write_phy(phy_addr, MG_PHYREG_BCR, MG_BIT(15)); // Reset PHY - eth_write_phy(phy_addr, MG_PHYREG_BCR, MG_BIT(12)); // Set autonegotiation - ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors - ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors - ETH->DMAIER = MG_BIT(6) | MG_BIT(16); // RIE, NISE - ETH->MACCR = - MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE, Duplex, Fast - ETH->DMAOMR = - MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST, TSF, RSF - - MG_DEBUG(("PHY ID: %#04hx %#04hx", eth_read_phy(phy_addr, MG_PHYREG_ID1), - eth_read_phy(phy_addr, MG_PHYREG_ID2))); - - // MAC address filtering - ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; - ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) | - ((uint32_t) ifp->mac[2] << 16) | - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; - return true; -} - -static size_t mg_tcpip_driver_stm32f_tx(const void *buf, size_t len, - struct mg_tcpip_if *ifp) { - if (len > sizeof(s_txbuf[s_txno])) { - MG_ERROR(("Frame too big, %ld", (long) len)); - len = 0; // Frame is too big - } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) { - ifp->nerr++; - MG_ERROR(("No free descriptors")); - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR); - len = 0; // All descriptors are busy, fail - } else { - memcpy(s_txbuf[s_txno], buf, len); // Copy data - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len - s_txdesc[s_txno][0] = MG_BIT(20) | MG_BIT(28) | MG_BIT(29); // Chain,FS,LS - s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA take over - if (++s_txno >= ETH_DESC_CNT) s_txno = 0; - } - MG_DSB(); // ensure descriptors have been written - ETH->DMASR = MG_BIT(2) | MG_BIT(5); // Clear any prior TBUS/TUS - ETH->DMATPDR = 0; // and resume - return len; -} - -static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) { - struct mg_tcpip_driver_stm32f_data *d = - (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data; - uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; - uint32_t bsr = eth_read_phy(phy_addr, MG_PHYREG_BSR); - bool up = bsr & MG_BIT(2) ? 1 : 0; - if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up - uint32_t scsr = eth_read_phy(phy_addr, MG_PHYREG_CSCR); - // tmp = reg with flags set to the most likely situation: 100M full-duplex - // if(link is slow or half) set flags otherwise - // reg = tmp - uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11); // 100M, Full-duplex - if ((scsr & MG_BIT(3)) == 0) maccr &= ~MG_BIT(14); // 10M - if ((scsr & MG_BIT(4)) == 0) maccr &= ~MG_BIT(11); // Half-duplex - ETH->MACCR = maccr; // IRQ handler does not fiddle with this register - MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10, - maccr & MG_BIT(11) ? "full" : "half")); - } - return up; -} - -#ifdef __riscv -__attribute__((interrupt())) // For RISCV CH32V307, which share the same MAC -#endif -void ETH_IRQHandler(void); -void ETH_IRQHandler(void) { - if (ETH->DMASR & MG_BIT(6)) { // Frame received, loop - ETH->DMASR = MG_BIT(16) | MG_BIT(6); // Clear flag - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not forever - if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done - if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == - (MG_BIT(8) | MG_BIT(9))) && - !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored frames - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1)); - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0], - // ETH->DMASR); - mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); - } - s_rxdesc[s_rxno][0] = MG_BIT(31); - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; - } - } - // Cleanup flags - ETH->DMASR = MG_BIT(16) // NIS, normal interrupt summary - | MG_BIT(7); // Clear possible RBUS while processing - ETH->DMARPDR = 0; // and resume RX -} - -struct mg_tcpip_driver mg_tcpip_driver_stm32f = { - mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL, - mg_tcpip_driver_stm32f_up}; -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/drivers/stm32h.c" -#endif - - -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \ - MG_ENABLE_DRIVER_STM32H -struct stm32h_eth { - volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R, - RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR, MACIVIR, - RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7], MACISR, - MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2], MACLCSR, - MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR, RESERVED10, - MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR, MACMDIODR, - RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR, MACA1HR, - MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248], MMCCR, - MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR, MMCTMCGPR, - RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR, - RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR, - MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R, - RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R, - RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R, - MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR, MACSSIR, - MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25, MACTSSR, - RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR, RESERVED28, - MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR, - RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR, MACPPSIR, - MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R, MACSPI2R, MACLMIR, - RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55], MTLTQOMR, - MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR, MTLRQMPOCR, MTLRQDR, - RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR, RESERVED37[60], DMACCR, - DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR, - DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER, - DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42, DMACCARDR, - RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR, RESERVED45[2], - DMACMFCR; -}; -#undef ETH -#define ETH \ - ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL + 0x8000UL)) - -#define ETH_PKT_SIZE 1540 // Max frame size -#define ETH_DESC_CNT 4 // Descriptors count -#define ETH_DS 4 // Descriptor size (words) - -static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors -static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers -static struct mg_tcpip_if *s_ifp; // MIP interface -enum { - MG_PHY_ADDR = 0, - MG_PHYREG_BCR = 0, - MG_PHYREG_BSR = 1, - MG_PHYREG_CSCR = 31 -}; // PHY constants - -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) { - ETH->MACMDIOAR &= (0xF << 8); - ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 3 << 2; - ETH->MACMDIOAR |= MG_BIT(0); - while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0; - return ETH->MACMDIODR; -} - -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { - ETH->MACMDIODR = val; - ETH->MACMDIOAR &= (0xF << 8); - ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 1 << 2; - ETH->MACMDIOAR |= MG_BIT(0); - while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0; -} - -static uint32_t get_hclk(void) { - struct rcc { - volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1, D1CFGR, - D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR, PLL1FRACR, - PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR, D2CCIP1R, - D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5, BDCR, CSR, - RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR, APB1LRSTR, - APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR, RESERVED11[9], - RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR, APB1HENR, - APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR, - AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR, APB4LPENR, - RESERVED13[4]; - } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400)); - uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /* 8MHz */, - csi = 4000000 /* 4MHz */; - unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3; - - if (sel == 1) { - clk = csi; - } else if (sel == 2) { - clk = hse; - } else if (sel == 3) { - uint32_t vco, m, n, p; - unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0; - m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4); - n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 + - ((rcc->PLLCFGR & MG_BIT(0)) ? 1 : 0); // round-up in fractional mode - p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1; - if (src == 1) { - clk = csi; - } else if (src == 2) { - clk = hse; - } else { - clk = hsi; - clk >>= ((rcc->CR & 3) >> 3); - } - vco = (uint32_t) ((uint64_t) clk * n / m); - clk = vco / p; - } else { - clk = hsi; - clk >>= ((rcc->CR & 3) >> 3); - } - const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) - uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8; - if (d1cpre >= 8) clk >>= cptab[d1cpre - 8]; - MG_DEBUG(("D1 CLK: %u", clk)); - uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0; - if (hpre < 8) return clk; - return ((uint32_t) clk) >> cptab[hpre - 8]; -} - -// Guess CR from AHB1 clock. MDC clock is generated from the ETH peripheral -// clock (AHB1); as per 802.3, it must not exceed 2. As the AHB clock can -// be derived from HSI or CSI (internal RC) clocks, and those can go above -// specs, the datasheets specify a range of frequencies and activate one of a -// series of dividers to keep the MDC clock safely below 2.5MHz. We guess a -// divider setting based on HCLK with some drift. If the user uses a different -// clock from our defaults, needs to set the macros on top. Valid for -// STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock has a 7.5 % -// worst case drift @ max temp) -static int guess_mdc_cr(void) { - const uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMDIOAR::CR values - const uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers - uint32_t hclk = get_hclk(); // Guess system HCLK - int result = -1; // Invalid CR value - for (int i = 0; i < 6; i++) { - if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { - result = crs[i]; - break; - } - } - if (result < 0) MG_ERROR(("HCLK too high")); - MG_DEBUG(("HCLK: %u, CR: %d", hclk, result)); - return result; -} - -static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) { - struct mg_tcpip_driver_stm32h_data *d = - (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data; - s_ifp = ifp; - - // Init RX descriptors - for (int i = 0; i < ETH_DESC_CNT; i++) { - s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data buffer - s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V - } - - // Init TX descriptors - for (int i = 0; i < ETH_DESC_CNT; i++) { - s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer - } - - ETH->DMAMR |= MG_BIT(0); // Software reset - while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0; // Wait until done - - // Set MDC clock divider. If user told us the value, use it. Otherwise, guess - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; - ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8; - - // NOTE(scaprile): We do not use timing facilities so the DMA engine does not - // re-write buffer address - ETH->DMAMR = 0 << 16; // use interrupt mode 0 (58.8.1) (reset value) - ETH->DMASBMR |= MG_BIT(12); // AAL NOTE(scaprile): is this actually needed - ETH->MACIER = 0; // Do not enable additional irq sources (reset value) - ETH->MACTFCR = MG_BIT(7); // Disable zero-quanta pause - // ETH->MACPFR = MG_BIT(31); // Receive all - eth_write_phy(MG_PHY_ADDR, MG_PHYREG_BCR, MG_BIT(15)); // Reset PHY - eth_write_phy(MG_PHY_ADDR, MG_PHYREG_BCR, - MG_BIT(12)); // Set autonegotiation - ETH->DMACRDLAR = - (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors start address - ETH->DMACRDRLR = ETH_DESC_CNT - 1; // ring length - ETH->DMACRDTPR = - (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - - 1]; // last valid descriptor address - ETH->DMACTDLAR = - (uint32_t) (uintptr_t) s_txdesc; // TX descriptors start address - ETH->DMACTDRLR = ETH_DESC_CNT - 1; // ring length - ETH->DMACTDTPR = - (uint32_t) (uintptr_t) s_txdesc; // first available descriptor address - ETH->DMACCR = 0; // DSL = 0 (contiguous descriptor table) (reset value) - ETH->DMACIER = MG_BIT(6) | MG_BIT(15); // RIE, NIE - ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) | - MG_BIT(15); // RE, TE, Duplex, Fast, Reserved - ETH->MTLTQOMR |= MG_BIT(1); // TSF - ETH->MTLRQOMR |= MG_BIT(5); // RSF - ETH->DMACTCR |= MG_BIT(0); // ST - ETH->DMACRCR |= MG_BIT(0); // SR - - // MAC address filtering - ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; - ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) | - ((uint32_t) ifp->mac[2] << 16) | - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; - return true; -} - -static uint32_t s_txno; -static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t len, - struct mg_tcpip_if *ifp) { - if (len > sizeof(s_txbuf[s_txno])) { - MG_ERROR(("Frame too big, %ld", (long) len)); - len = 0; // Frame is too big - } else if ((s_txdesc[s_txno][3] & MG_BIT(31))) { - ifp->nerr++; - MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno, - s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR)); - for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X", s_txdesc[i][3])); - len = 0; // All descriptors are busy, fail - } else { - memcpy(s_txbuf[s_txno], buf, len); // Copy data - s_txdesc[s_txno][2] = (uint32_t) len; // Set data len - s_txdesc[s_txno][3] = MG_BIT(28) | MG_BIT(29); // FD, LD - s_txdesc[s_txno][3] |= MG_BIT(31); // Set OWN bit - let DMA take over - if (++s_txno >= ETH_DESC_CNT) s_txno = 0; - } - ETH->DMACSR |= MG_BIT(2) | MG_BIT(1); // Clear any prior TBU, TPS - ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno]; // and resume - return len; - (void) ifp; -} - -static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) { - uint32_t bsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BSR); - bool up = bsr & MG_BIT(2) ? 1 : 0; - if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up - uint32_t scsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_CSCR); - // tmp = reg with flags set to the most likely situation: 100M full-duplex - // if(link is slow or half) set flags otherwise - // reg = tmp - uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13); // 100M, Full-duplex - if ((scsr & MG_BIT(3)) == 0) maccr &= ~MG_BIT(14); // 10M - if ((scsr & MG_BIT(4)) == 0) maccr &= ~MG_BIT(13); // Half-duplex - ETH->MACCR = maccr; // IRQ handler does not fiddle with this register - MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10, - maccr & MG_BIT(13) ? "full" : "half")); - } - return up; -} - -void ETH_IRQHandler(void); -static uint32_t s_rxno; -void ETH_IRQHandler(void) { - if (ETH->DMACSR & MG_BIT(6)) { // Frame received, loop - ETH->DMACSR = MG_BIT(15) | MG_BIT(6); // Clear flag - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not forever - if (s_rxdesc[s_rxno][3] & MG_BIT(31)) break; // exit when done - if (((s_rxdesc[s_rxno][3] & (MG_BIT(28) | MG_BIT(29))) == - (MG_BIT(28) | MG_BIT(29))) && - !(s_rxdesc[s_rxno][3] & MG_BIT(15))) { // skip partial/errored frames - uint32_t len = s_rxdesc[s_rxno][3] & (MG_BIT(15) - 1); - // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len, s_rxdesc[s_rxno][3], - // ETH->DMACSR)); - mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); - } - s_rxdesc[s_rxno][3] = - MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; - } - } - ETH->DMACSR = - MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing - ETH->DMACRDTPR = - (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and resume RX -} - -struct mg_tcpip_driver mg_tcpip_driver_stm32h = { - mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL, - mg_tcpip_driver_stm32h_up}; -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/drivers/tm4c.c" -#endif - - -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C -struct tm4c_emac { - volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL, - EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, EMACSTATUS, - EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H, - EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H, - EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL, - EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4, - EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4], - EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], EMACRXCNTCRCERR, - EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239], - EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, EMACSUBSECINC, - EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD, - EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL, - RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294], - EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR, - EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT, - RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA, - RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS, - EMACEPHYIM, EMACEPHYIMSC; -}; -#undef EMAC -#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000) - -#define ETH_PKT_SIZE 1540 // Max frame size -#define ETH_DESC_CNT 4 // Descriptors count -#define ETH_DS 4 // Descriptor size (words) - -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers -static struct mg_tcpip_if *s_ifp; // MIP interface -enum { - EPHY_ADDR = 0, - EPHYBMCR = 0, - EPHYBMSR = 1, - EPHYSTS = 16 -}; // PHY constants - -static inline void tm4cspin(volatile uint32_t count) { - while (count--) (void) 0; -} - -static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) { - EMAC->EMACMIIADDR &= (0xf << 2); - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6); - EMAC->EMACMIIADDR |= MG_BIT(0); - while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1); - return EMAC->EMACMIIDATA; -} - -static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { - EMAC->EMACMIIDATA = val; - EMAC->EMACMIIADDR &= (0xf << 2); - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1); - EMAC->EMACMIIADDR |= MG_BIT(0); - while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1); -} - -static uint32_t get_sysclk(void) { - struct sysctl { - volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0, - PLLFREQ1; - } *sysctl = (struct sysctl *) 0x400FE000; - uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 25MHz */; - if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL - uint32_t fin, vco, mdiv, n, q, psysdiv; - uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24; - if (pllsrc == 0) { - clk = piosc; - } else if (pllsrc == 3) { - clk = mosc; - } else { - MG_ERROR(("Unsupported clock source")); - } - q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8; - n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0; - fin = clk / ((q + 1) * (n + 1)); - mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >> - 0; // mint + (mfrac / 1024); MFRAC not supported - psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0; - vco = (uint32_t) ((uint64_t) fin * mdiv); - return vco / (psysdiv + 1); - } - uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20; - if (oscsrc == 0) { - clk = piosc; - } else if (oscsrc == 3) { - clk = mosc; - } else { - MG_ERROR(("Unsupported clock source")); - } - uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16; - return clk / (osysdiv + 1); -} - -// Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as per -// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock can be -// derived from the PIOSC (internal RC), and it can go above specs, the -// datasheets specify a range of frequencies and activate one of a series of -// dividers to keep the MDC clock safely below 2.5MHz. We guess a divider -// setting based on SYSCLK with a +5% drift. If the user uses a different clock -// from our defaults, needs to set the macros on top Valid for TM4C129x (20.7) -// (4.5% worst case drift) -// The PHY receives the main oscillator (MOSC) (20.3.1) -static int guess_mdc_cr(void) { - uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values - uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers - uint32_t sysclk = get_sysclk(); // Guess system SYSCLK - int result = -1; // Invalid CR value - if (sysclk < 25000000) { - MG_ERROR(("SYSCLK too low")); - } else { - for (int i = 0; i < 4; i++) { - if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { - result = crs[i]; - break; - } - } - if (result < 0) MG_ERROR(("SYSCLK too high")); - } - MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result)); - return result; -} - -static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) { - struct mg_tcpip_driver_tm4c_data *d = - (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data; - s_ifp = ifp; - - // Init RX descriptors - for (int i = 0; i < ETH_DESC_CNT; i++) { - s_rxdesc[i][0] = MG_BIT(31); // Own - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd address chained - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data buffer - s_rxdesc[i][3] = - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain - // MG_DEBUG(("%d %p", i, s_rxdesc[i])); - } - - // Init TX descriptors - for (int i = 0; i < ETH_DESC_CNT; i++) { - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer - s_txdesc[i][3] = - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain - } - - EMAC->EMACDMABUSMOD |= MG_BIT(0); // Software reset - while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1); // Wait until done - - // Set MDC clock divider. If user told us the value, use it. Otherwise, guess - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; - EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2; - - // NOTE(cpq): we do not use extended descriptor bit 7, and do not use - // hardware checksum. Therefore, descriptor size is 4, not 8 - // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) | MG_BIT(25); - EMAC->EMACIM = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT - EMAC->EMACFLOWCTL = MG_BIT(7); // Disable zero-quanta pause - // EMAC->EMACFRAMEFLTR = MG_BIT(31); // Receive all - // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode - emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15)); // Reset internal PHY (EPHY) - emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12)); // Set autonegotiation - EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors - EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX descriptors - EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16); // RIE, NIE - EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE, Duplex, Fast - EMAC->EMACDMAOPMODE = - MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST, TSF, RSF - EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; - EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) | - ((uint32_t) ifp->mac[2] << 16) | - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; - // NOTE(scaprile) There are 3 additional slots for filtering, disabled by - // default. This also applies to the STM32 driver (at least for F7) - return true; -} - -static uint32_t s_txno; -static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len, - struct mg_tcpip_if *ifp) { - if (len > sizeof(s_txbuf[s_txno])) { - MG_ERROR(("Frame too big, %ld", (long) len)); - len = 0; // fail - } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) { - ifp->nerr++; - MG_ERROR(("No descriptors available")); - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) - // EMAC->EMACDMARIS); - len = 0; // fail - } else { - memcpy(s_txbuf[s_txno], buf, len); // Copy data - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len - s_txdesc[s_txno][0] = - MG_BIT(20) | MG_BIT(28) | MG_BIT(29) | MG_BIT(30); // Chain,FS,LS,IC - s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA take over - if (++s_txno >= ETH_DESC_CNT) s_txno = 0; - } - EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5); // Clear any prior TU/UNF - EMAC->EMACTXPOLLD = 0; // and resume - return len; - (void) ifp; -} - -static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) { - uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR); - bool up = (bmsr & MG_BIT(2)) ? 1 : 0; - if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up - uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS); - // tmp = reg with flags set to the most likely situation: 100M full-duplex - // if(link is slow or half) set flags otherwise - // reg = tmp - uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11); // 100M, Full-duplex - if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14); // 10M - if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11); // Half-duplex - EMAC->EMACCFG = emaccfg; // IRQ handler does not fiddle with this register - MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 : 10, - emaccfg & MG_BIT(11) ? "full" : "half")); - } - return up; -} - -void EMAC0_IRQHandler(void); -static uint32_t s_rxno; -void EMAC0_IRQHandler(void) { - if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop - EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not forever - if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done - if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == (MG_BIT(8) | MG_BIT(9))) && - !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored frames - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1)); - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0], - // EMAC->EMACDMARIS); - mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); - } - s_rxdesc[s_rxno][0] = MG_BIT(31); - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; - } - } - EMAC->EMACDMARIS = MG_BIT(7); // Clear possible RU while processing - EMAC->EMACRXPOLLD = 0; // and resume RX -} - -struct mg_tcpip_driver mg_tcpip_driver_tm4c = {mg_tcpip_driver_tm4c_init, - mg_tcpip_driver_tm4c_tx, NULL, - mg_tcpip_driver_tm4c_up}; -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/drivers/w5500.c" -#endif - - -#if MG_ENABLE_TCPIP - -enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 }; - -static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, bool wr, - void *buf, size_t len) { - uint8_t *p = (uint8_t *) buf; - uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255), - (uint8_t) ((block << 3) | (wr ? 4 : 0))}; - s->begin(s->spi); - for (size_t i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]); - for (size_t i = 0; i < len; i++) { - uint8_t r = s->txn(s->spi, p[i]); - if (!wr) p[i] = r; - } - s->end(s->spi); -} - -// clang-format off -static void w5500_wn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); } -static void w5500_w1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); } -static void w5500_w2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); } -static void w5500_rn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); } -static uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; } -static uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); } -// clang-format on - -static size_t w5500_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) { - struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; - uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read recv len - while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until it is stable - // printf("RSR: %d\n", (int) n); - if (n > 0) { - uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer - n = w5500_r2(s, W5500_RX0, ptr); // Read frame length - if (n <= len + 2 && n > 1) { - r = (uint16_t) (n - 2); - w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r); - } - w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance read pointer - w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV - // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r); - } - return r; -} - -static size_t w5500_tx(const void *buf, size_t buflen, struct mg_tcpip_if *ifp) { - struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; - uint16_t n = 0, len = (uint16_t) buflen; - while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space - uint16_t ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer - w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data - w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance write pointer - w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND - for (int i = 0; i < 40; i++) { - uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR - if (ir == 0) continue; - // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr); - w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it! - if (ir & 8) len = 0; // Timeout. Report error - if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout - } - return len; -} - -static bool w5500_init(struct mg_tcpip_if *ifp) { - struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; - s->end(s->spi); - w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80 - w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset - w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set - // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC - w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size - w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size - w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW - w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN - return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW -} - -static bool w5500_up(struct mg_tcpip_if *ifp) { - struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data; - uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e); - return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up) -} - -struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx, w5500_up}; -#endif diff --git a/SmartEVSE-3/SmartEVSE-3/src/utils.cpp b/SmartEVSE-3/SmartEVSE-3/src/utils.cpp deleted file mode 100644 index 7e306d9..0000000 --- a/SmartEVSE-3/SmartEVSE-3/src/utils.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* -; Project: Smart EVSE -; -; -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in -; all copies or substantial portions of the Software. -; -; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -; THE SOFTWARE. - */ - -#include -#include -#include - -#include "evse.h" -#include "utils.h" - -unsigned long pow_10[10] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; - - -// read Mac, and reverse to ID -uint32_t MacId() { - - uint32_t id = 0; - - for (int i=0; i<17; i=i+8) { - id |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; - } - return id >> 2; // low two bits are unused. -} - - -unsigned char crc8(unsigned char *buf, unsigned char len) { - unsigned char crc = 0, i, mix, inbyte; - - while (len--) { - inbyte = *buf++; - for (i = 8; i; i--) { - mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - if (mix) crc ^= 0x8C; - inbyte >>= 1; - } - } - return crc; -} - -/** - * Calculates 16-bit CRC of given data - * used for Frame Check Sequence on data frame - * - * @param unsigned char pointer to buffer - * @param unsigned char length of buffer - * @return unsigned int CRC - */ -/* -unsigned int crc16(unsigned char *buf, unsigned char len) { - unsigned int pos, crc = 0xffff; - unsigned char i; - - // Poly used is x^16+x^15+x^2+x - for (pos = 0; pos < len; pos++) { - crc ^= (unsigned int)buf[pos]; // XOR byte into least sig. byte of crc - - for (i = 8; i != 0; i--) { // Loop over each bit - if ((crc & 0x0001) != 0) { // If the LSB is set - crc >>= 1; // Shift right and XOR 0xA001 - crc ^= 0xA001; - } else // Else LSB is not set - crc >>= 1; // Just shift right - } - } - - return crc; -} -*/ - - -/** - * Insert rounded value into string in printf style - * - * @param pointer to string - * @param string Format - * @param signed long Value to round and insert - * @param unsinged char Divisor where to set decimal point - * @param unsigned char Decimal place count - */ -void sprintfl(char *str, const char *Format, signed long Value, unsigned char Divisor, unsigned char Decimal) { - signed long val; - - val = Value / (signed long) pow_10[Divisor - Decimal - 1]; - // Round value - if(val < 0) val -= 5; - else val += 5; - val /= 10; - // Split value - if(Decimal > 0) sprintf(str, Format, (signed int) (val / (signed long) pow_10[Decimal]), (unsigned int) (abs(val) % pow_10[Decimal])); - else sprintf(str, Format, (signed int) val); -} - -/* triwave8: triangle (sawtooth) wave generator. Useful for - turning a one-byte ever-increasing value into a - one-byte value that oscillates up and down. - - input output - 0..127 0..254 (positive slope) - 128..255 254..0 (negative slope) - */ -unsigned char triwave8(unsigned char in) { - if (in & 0x80) { - in = 255u - in; - } - unsigned char out = in << 1; - return out; -} - -unsigned char scale8(unsigned char i, unsigned char scale) { - return (((unsigned int) i) * (1 + (unsigned int) (scale))) >> 8; -} - -/* easing functions; see http://easings.net - - ease8InOutQuad: 8-bit quadratic ease-in / ease-out function - */ -unsigned char ease8InOutQuad(unsigned char i) { - unsigned char j = i; - if (j & 0x80) { - j = 255u - j; - } - unsigned char jj = scale8(j, j); - unsigned char jj2 = jj << 1; - if (i & 0x80) { - jj2 = 255u - jj2; - } - return jj2; -} diff --git a/SmartEVSE-3/SmartEVSE-3/test/README b/SmartEVSE-3/SmartEVSE-3/test/README deleted file mode 100644 index b94d089..0000000 --- a/SmartEVSE-3/SmartEVSE-3/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PlatformIO Unit Testing and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/SmartEVSE-3/SmartEVSE-3/test/feed_bat.sh b/SmartEVSE-3/SmartEVSE-3/test/feed_bat.sh deleted file mode 100755 index bd462e7..0000000 --- a/SmartEVSE-3/SmartEVSE-3/test/feed_bat.sh +++ /dev/null @@ -1,19 +0,0 @@ -FEED="HomeBatteryCurrent" - -if [ $# -eq 0 ]; then - echo "Usage: $0 4-digit-topic-nr-of-your-smartevse." - exit 1 -fi - -echo "Enter your current in deci-Ampères:" -OLD_CURRENT=20 -while true; do - read -t 8 CURRENT - if [ $CURRENT"x" == "x" ]; then - CURRENT=$OLD_CURRENT - else - OLD_CURRENT=$CURRENT - fi - echo $FEED-CURRENT=$CURRENT. - mosquitto_pub -h 127.0.0.1 -t "SmartEVSE-$1/Set/$FEED" -m $CURRENT -done diff --git a/SmartEVSE-3/SmartEVSE-3/test/feed_ev.sh b/SmartEVSE-3/SmartEVSE-3/test/feed_ev.sh deleted file mode 100755 index 292c61a..0000000 --- a/SmartEVSE-3/SmartEVSE-3/test/feed_ev.sh +++ /dev/null @@ -1,22 +0,0 @@ -FEED="EVMeter" - -if [ $# -eq 0 ]; then - echo "Usage: $0 4-digit-topic-nr-of-your-smartevse." - exit 1 -fi - -echo "Enter your current in deci-Ampères:" -OLD_CURRENT=20 -#energy in Wh: -ENERGY=0 -while true; do - read -t 5 CURRENT - if [ $CURRENT"x" == "x" ]; then - CURRENT=$OLD_CURRENT - else - OLD_CURRENT=$CURRENT - fi - echo $FEED-CURRENT=$CURRENT. - mosquitto_pub -h 127.0.0.1 -t "SmartEVSE-$1/Set/$FEED" -m $CURRENT:$CURRENT:$CURRENT:11000:$ENERGY - ENERGY=$((ENERGY + 100)) -done diff --git a/SmartEVSE-3/SmartEVSE-3/test/feed_mains.sh b/SmartEVSE-3/SmartEVSE-3/test/feed_mains.sh deleted file mode 100755 index 2465f84..0000000 --- a/SmartEVSE-3/SmartEVSE-3/test/feed_mains.sh +++ /dev/null @@ -1,20 +0,0 @@ -FEED="MainsMeter" - -if [ $# -eq 0 ]; then - echo "Usage: $0 4-digit-topic-nr-of-your-smartevse." - exit 1 -fi - -echo "Enter your current in deci-Ampères:" -OLD_CURRENT=20 -while true; do - read -t 1 CURRENT - if [ $CURRENT"x" == "x" ]; then - CURRENT=$OLD_CURRENT - else - OLD_CURRENT=$CURRENT - fi - echo $FEED-CURRENT=$CURRENT. - mosquitto_pub -h 127.0.0.1 -t "SmartEVSE-$1/Set/$FEED" -m $CURRENT:$CURRENT:$CURRENT - sleep 1 -done diff --git a/SmartEVSE-3/SmartEVSE-3/test/tests.sh b/SmartEVSE-3/SmartEVSE-3/test/tests.sh deleted file mode 100755 index 94567a3..0000000 --- a/SmartEVSE-3/SmartEVSE-3/test/tests.sh +++ /dev/null @@ -1,743 +0,0 @@ -#!/bin/bash - -# This script is meant to automate testing of SmartEVSEv3 firmware -# Needs jq binary - -#serialnrs (4 digits): - -if [ "$#" -ne 3 ]; then - echo "Usage: $0 ; serialnr is 4 digits." - echo - echo "Make sure you compiled your test version with -DAUTOMATED_TESTING=1 !!" - echo - echo "Known BUGS: Only run one test at a time; running multiple tests leads to false " - echo - echo "WARNING: ONLY USE THIS SCRIPT ON SMARTEVSEs ON A TEST BENCH" - echo "NEVER USE THIS SCRIPT ON A LIVE SMARTEVSE; IT _WILL_ BLOW YOUR FUSES AND YOUR BREAKERS!!!" - exit 1 -fi - -COUNT=`pgrep tests | wc -l` -if [ $COUNT -ne 2 ]; then - echo "Tests are already running in the background, exiting" - killall tests.sh - exit 1 -fi - -if [ $3 -eq 0 ]; then #all tests selected - SEL=$((0xFFFF)) -else - SEL=$3 -fi - -DBG=1 #1 means debug mode on, 0 means debug mode off - -# please give values in deci-Ampère: -MASTER_SOCKET_HARDWIRED=320 -SLAVE_SOCKET_HARDWIRED=130 -MASTER_MAC_ID=$1 -SLAVE_MAC_ID=$2 - -MASTER="smartevse-"$1".local" -SLAVE="smartevse-"$2".local" - -control_c() -# run if user hits control-c -{ - echo -en "\n*** Ouch! Exiting ***\n" - #kill all running subprocesses - pkill -P $$ - exit $? -} - -# trap keyboard interrupt (control-c) -trap control_c SIGINT - - -#curl suppress data -CURLPOST="curl -s -o /dev/null -X POST -d''" - -# Colors for echo -# Reset -NC='\033[0m' # Text Reset - -# Regular Colors -Black='\033[0;30m' # Black -Red='\033[0;31m' # Red -Green='\033[0;32m' # Green -Yellow='\033[0;33m' # Yellow -Blue='\033[0;34m' # Blue -Purple='\033[0;35m' # Purple -Cyan='\033[0;36m' # Cyan -White='\033[0;37m' # White - -ABORT=0 - -init_devices () { -for device in $SLAVE $MASTER; do - #go to Normal Mode for init - $CURLPOST $device/reboot - sleep 1 - $CURLPOST "$device/automated_testing?loadbl=0&mainsmeter=1" - $CURLPOST $device/settings?mode=1 -done -read -p "Make sure all EVSE's are set to NOT CHARGING, then press " dummy -sleep 5 -} - -init_currents () { -#first load all settings before the test -for device in $SLAVE $MASTER; do - $CURLPOST "$device/automated_testing?config=1¤t_max=60¤t_max_circuit=70¤t_main=80" - $CURLPOST "$device/settings?current_max_sum_mains=600&enable_C2=0" - #$CURLPOST $device/settings?enable_C2=0 #TODO cycle through all tests with different settings !!! -done -} - -#takes 3 arguments, actual value, test value and margin -print_results() { - if [ $DBG -eq 1 ]; then - printf "CHARGECUR=$1, TARGET=$2." - fi - if [ $1 -ge $(( $2 - $3 )) ] && [ $1 -le $(( $2 + $3 )) ]; then - printf "$Green Passed $NC LBL=$loadbl_master, Mode=$MODE: $device chargecurrent is limited to $TESTSTRING.\n" - else - printf "$Red Failed $NC LBL=$loadbl_master, Mode=$MODE: $device chargecurrent is $1 dA and should be limited to $2 dA (with a margin of $3 dA) because of $TESTSTRING.\n" - fi -} - -#takes 4 arguments, actual value, test value, margin, string_to_test -print_results2() { - if [ $DBG -eq 1 ]; then - printf "$4=$1, TARGET=$2." - fi - if [ $1 -ge $(( $2 - $3 )) ] && [ $1 -le $(( $2 + $3 )) ]; then - printf "$Green Passed $NC LBL=$loadbl_master, Mode=$MODE: $device $4 is as expected when testing $TESTSTRING.\n" - else - printf "$Red Failed $NC LBL=$loadbl_master, Mode=$MODE: $device $4=$1 should be $4=$2 when testing $TESTSTRING.\n" - fi -} - -check_all_charge_currents () { - for device in $MASTER $SLAVE; do - check_charging - print_results "$CHARGECUR" "$TESTVALUE10" "0" - done -} - -set_loadbalancing () { - #make sure we switch lbl in the right order so we dont get modbus errors - if [ $loadbl_master -eq 0 ]; then - loadbl_slave=0 - $CURLPOST $SLAVE/automated_testing?loadbl=$loadbl_slave - $CURLPOST $MASTER/automated_testing?loadbl=$loadbl_master - fi - if [ $loadbl_master -eq 1 ]; then - $CURLPOST $MASTER/automated_testing?loadbl=$loadbl_master - sleep 1 - loadbl_slave=2 - $CURLPOST $SLAVE/automated_testing?loadbl=$loadbl_slave - fi -} - -MODESTR=("Off" "Normal" "Solar" "Smart") - -set_mode () { - $CURLPOST $MASTER/settings?mode=$mode_master - if [ $loadbl_slave -eq 0 ]; then - $CURLPOST $SLAVE/settings?mode=$mode_master - fi - MODE=${MODESTR[$mode_master]} - printf "Testing LBL=$loadbl_master, mode=$MODE on $TESTSTRING.\r" - TESTVALUE10=$((TESTVALUE * 10)) -} - -overload_mains () { -# echo $TESTVALUE10 >feed_mains_$device - #now overload the mains by 1A - echo $(( TESTVALUE10 + 10 )) >feed_mains_$device - #settle switching modes AND stabilizing charging speeds - printf "Watch the charge current of device $device going down in 1-2A steps!\r" - sleep 10 - #now stabilize the mains to MaxMains - echo $(( TESTVALUE10 )) >feed_mains_$device -} - -set_mainsmeter_to_api () { - if [ ! -e "feed_mains_$device" ]; then - mkfifo feed_mains_$device - fi - if [ $device == $MASTER ]; then - MAC_ID=$MASTER_MAC_ID - else - MAC_ID=$SLAVE_MAC_ID - fi - ./feed_mains.sh $MAC_ID /dev/null & - echo $TESTVALUE10 >feed_mains_$device - $CURLPOST $device/automated_testing?mainsmeter=9 -} - -run_test_loadbl0 () { - init_devices - init_currents - for device in $MASTER $SLAVE; do - set_mainsmeter_to_api - done - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - - loadbl_master=0 - set_loadbalancing - for mode_master in 3 2; do - set_mode - for device in $MASTER $SLAVE; do - $CURLPOST $device$CONFIG_COMMAND - overload_mains - check_charging - #we start charging at maxcurrent and then step down for approx. 1A per 670ms - print_results "$CHARGECUR" "${TARGET[$mode_master]}" "$MARGIN" - done - done - #set MainsMeter to Sensorbox - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?mainsmeter=1 - done - #kill all running subprocesses - pkill -P $$ -} - -run_test_loadbl1 () { - init_devices - init_currents - device=$MASTER - set_mainsmeter_to_api - loadbl_master=1 - set_loadbalancing - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - #if we are in loadbl 0 we don't test the slave device - for mode_master in 3 2; do - set_mode - $CURLPOST $device$CONFIG_COMMAND - overload_mains - TOTCUR=0 - for device in $SLAVE $MASTER; do - check_charging - TOTCUR=$((TOTCUR + CHARGECUR)) - done - #we started charging at maxcurrent and then stepped down for approx. 1A per 670ms - print_results "$TOTCUR" "${TARGET[$mode_master]}" "$MARGIN" - done - #set MainsMeter to Sensorbox - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?mainsmeter=1 - done - #kill all running subprocesses - pkill -P $$ -} - -check_charging () { - #make sure we are actually charging - CURL=$(curl -s -X GET $device/settings) - STATE_ID=$(echo $CURL | jq ".evse.state_id") - print_results2 "$STATE_ID" "2" "0" "STATE_ID" - CHARGECUR=$(echo $CURL | jq ".settings.charge_current") -} - -#TEST1: MODESWITCH TEST: test if mode changes on master reflects on slave and vice versa -NR=$((2**0)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="Modeswitch" - printf "Starting $TESTSTRING test #$NR:\n" - init_devices - $CURLPOST $MASTER/automated_testing?loadbl=1 - $CURLPOST $SLAVE/automated_testing?loadbl=2 - for mode_master in 1 2 3; do - $CURLPOST $MASTER/settings?mode=$mode_master - sleep 5 - mode_slave=$(curl -s -X GET $SLAVE/settings | jq ".mode_id") - if [ "x"$mode_slave == "x"$mode_master ]; then - printf "$Green Passed $NC Master switching to mode $mode_master, slave follows.\n" - else - printf "$Red Failed $NC Master switching to mode $mode_master, slave is at $mode_slave.\n" - ABORT=1 - fi - done - - if [ $ABORT -ne 0 ]; then - exit $ABORT - fi - - for mode_slave in 1 2 3; do - $CURLPOST $SLAVE/settings?mode=$mode_slave - sleep 5 - mode_master=$(curl -s -X GET $MASTER/settings | jq ".mode_id") - if [ "x"$mode_slave == "x"$mode_master ]; then - printf "$Green Passed $NC Slave switching to $mode_slave, master follows.\n" - else - printf "$Red Failed $NC Slave switching to $mode_slave, master is at $mode_master.\n" - ABORT=1 - fi - done - - #if we cannot rely on correct switching of modes between master and slave, we will have to abort testing - if [ $ABORT -ne 0 ]; then - printf "Since one of the previous tests failed we abort testing." - exit $ABORT - fi -fi - -#TEST2: SOCKET HARDWIRING TEST: test if Socket resistors in test bench limit our current // Configuration (0:Socket / 1:Fixed Cable) -# needs setting [MASTER|SLAVE]_SOCKET_HARDWIRED to the correct values of your test bench -NR=$((2**1)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="Socket Hardwiring" - printf "Starting $TESTSTRING test #$NR:\n" - printf "Make sure your Sensorbox is on MAX power delivery to the grid, or Solar tests will fail!\n" - init_devices - init_currents - #first load all settings before the test - for device in $SLAVE $MASTER; do - $CURLPOST $device/automated_testing?config=0 - done - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - - for loadbl_master in 0 1; do - set_loadbalancing - #if we are in loadbl 0 we test the slave device in loadbl 0 also - for mode_master in 1 2 3; do - set_mode - #settle switching modes AND stabilizing charging speeds - sleep 10 - for device in $SLAVE $MASTER; do - check_charging - if [ $device == $MASTER ]; then - print_results "$CHARGECUR" "$MASTER_SOCKET_HARDWIRED" "0" - else - print_results "$CHARGECUR" "$SLAVE_SOCKET_HARDWIRED" "0" - fi - done - done - done - - #for all other tests we don't want socket resistors to limit our currents, so switch to Fixed Cable - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?config=1 - done -fi - -#TEST4: MAXCURRENT TEST: test if MaxCurrent is obeyed -NR=$((2**2)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="MaxCurrent" - printf "Starting $TESTSTRING test #$NR:\n" - printf "Make sure your Sensorbox is on MAX power delivery to the grid, or Solar tests will fail!\n" - init_devices - init_currents - - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - - for loadbl_master in 0 1; do - set_loadbalancing - #if we are in loadbl 0 we test the slave device in loadbl 0 also - TESTVALUE=12 - for mode_master in 1 3 2; do - set_mode - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?current_max=$TESTVALUE - done - #settle switching modes AND stabilizing charging speeds - sleep 10 - check_all_charge_currents - #increase testvalue to test if the device responds to that - TESTVALUE=$(( TESTVALUE + 1 )) - done - done -fi - -#TEST8: MAXCIRCUIT TEST: test if MaxCircuit is obeyed -NR=$((2**3)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="MaxCirCuit" - printf "Starting $TESTSTRING test #$NR:\n" - printf "Make sure your Sensorbox is on MAX power delivery to the grid, or Solar tests will fail!\n" - init_devices - init_currents - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - - for loadbl_master in 0 1; do - set_loadbalancing - #if we are in loadbl 0 we test the slave device in loadbl 0 also - TESTVALUE=20 - for mode_master in 1 3 2; do - set_mode - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?current_max_circuit=$TESTVALUE - done - #settle switching modes AND stabilizing charging speeds - sleep 13 - - if [ $loadbl_master -eq 0 ]; then - check_all_charge_currents - else - TOTCUR=0 - for device in $SLAVE $MASTER; do - check_charging - TOTCUR=$((TOTCUR + CHARGECUR)) - done - print_results "$TOTCUR" "$TESTVALUE10" "0" - fi - #increase testvalue to test if the device responds to that - TESTVALUE=$(( TESTVALUE + 1 )) - done - done -fi - -#TEST16: MAXMAINS TEST: test if MaxMains is obeyed when using EM_API for loadbl=0 -NR=$((2**4)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="MaxMains via EM_API for loadbl=0" - printf "Starting $TESTSTRING test #$NR:\n" - #the margin for which we will accept the lowering/upping of the charge current, in dA - MARGIN=20 - TESTVALUE=25 - #Target values for Off, Normal, Solar, Smart mode RESPECTIVELY: - TARGET=(0 0 300 450) - CONFIG_COMMAND="/automated_testing?current_main=$TESTVALUE" - run_test_loadbl0 -fi - -#TEST32: MAXMAINS TEST: test if MaxMains is obeyed when using EM_API -NR=$((2**5)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="MaxMains via EM_API for loadbl=1" - printf "Starting $TESTSTRING test #$NR:\n" - #the margin for which we will accept the lowering/upping of the charge current, in dA - MARGIN=20 - TESTVALUE=25 - #Target values for Off, Normal, Solar, Smart mode RESPECTIVELY: - TARGET=(0 0 420 560) - CONFIG_COMMAND="/automated_testing?current_main=$TESTVALUE" - run_test_loadbl1 -fi - -#TEST64: MAXSUMMAINS TEST: test if MaxSumMains is obeyed when using EM_API for loadbl=0 -NR=$((2**6)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="MaxSumMains via EM_API for loadbl=0" - printf "Starting $TESTSTRING test #$NR:\n" - #the margin for which we will accept the lowering/upping of the charge current, in dA - MARGIN=20 - TESTVALUE=50 - #Target values for Off, Normal, Solar, Smart mode RESPECTIVELY: - TARGET=(0 0 310 455) - CONFIG_COMMAND="/settings?current_max_sum_mains=$((TESTVALUE * 3))" - run_test_loadbl0 -fi - -#TEST128: MAXSUMMAINS TEST: test if MaxSumMains is obeyed when using EM_API for loadbl=1 -NR=$((2**7)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="MaxSumMains via EM_API for loadbl=1" - printf "Starting $TESTSTRING test #$NR:\n" - #the margin for which we will accept the lowering/upping of the charge current, in dA - MARGIN=20 - TESTVALUE=50 - TARGET=(0 0 425 560) - CONFIG_COMMAND="/settings?current_max_sum_mains=$((TESTVALUE * 3))" - run_test_loadbl1 -fi - -#TEST256: STARTCURRENT TEST: test if StartCurrent is obeyed in Solar Mode for loadbl=0 -NR=$((2**8)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="StartCurrent, StopTimer and ImportCurrent via EM_API for loadbl=0" - printf "Starting $TESTSTRING test #$NR:\n" - #the margin for which we will accept the lowering/upping of the charge current, in dA - MARGIN=20 - #make mains_overload feed mains_current with 3A to the grid - TESTVALUE=-3 - #note that startcurrent shown as -4A on the display is stored as 4A ! - #CONFIG_COMMAND="/settings?solar_start_current=4" - init_devices - init_currents - for device in $MASTER $SLAVE; do - set_mainsmeter_to_api - $CURLPOST "$device/settings?solar_start_current=4&solar_max_import=15&solar_stop_time=1" - done - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - - loadbl_master=0 - set_loadbalancing - #SOLAR mode - mode_master=2 - set_mode - for device in $MASTER $SLAVE; do - echo 60 >feed_mains_$device - done - printf "Feeding total of 18A....chargecurrent should drop to 6A, then triggers stoptimer and when it expires, stops charging because over import limit of 15A\r" - TESTSTRING="SolarStopTimer should have been activated on overload on ImportCurrent" - sleep 50 - for device in $MASTER $SLAVE; do - TIMER=$(curl -s -X GET $device/settings | jq ".evse.solar_stop_timer") - print_results2 "$TIMER" "28" "15" "SOLAR_STOP_TIMER" - done - TESTSTRING="Charging should stop after expiring SolarStopTimer" - printf "$TESTSTRING\r" - sleep 40 - for device in $MASTER $SLAVE; do - STATE_ID=$(curl -s -X GET $device/settings | jq ".evse.state_id") - print_results2 "$STATE_ID" "10" "0" "STATE_ID" - echo -20 >feed_mains_$device - done - TESTSTRING="Feeding total of -6A....should trigger ready-timer 60s" - printf "$TESTSTRING\r" - sleep 65 - read -p "To start charging, set EVSE's to NO CHARGING and then to CHARGING again, then press " dummy - TESTSTRING="Feeding total of 18A should drop the charging current" - printf "$TESTSTRING\r" - for device in $MASTER $SLAVE; do - check_charging - #dropping the charge current by a few amps - echo 60 >feed_mains_$device - done - sleep 3 - TESTSTRING="Feeding total of 15A should stabilize the charging current" - printf "$TESTSTRING\r" - for device in $MASTER $SLAVE; do - check_charging - print_results "$CHARGECUR" "475" "30" - echo 50 >feed_mains_$device - done - sleep 10 - for device in $MASTER $SLAVE; do - check_charging - print_results "$CHARGECUR" "450" "35" - done - #set MainsMeter to Sensorbox - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?mainsmeter=1 - done - #kill all running subprocesses - pkill -P $$ -fi - -#TEST512: STARTCURRENT TEST: test if StartCurrent is obeyed in Solar Mode for loadbl=1 -NR=$((2**9)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="StartCurrent, StopTimer and ImportCurrent via EM_API for loadbl=1" - printf "Starting $TESTSTRING test #$NR:\n" - #the margin for which we will accept the lowering/upping of the charge current, in dA - MARGIN=20 - #make mains_overload feed mains_current with 3A to the grid - TESTVALUE=-3 - #note that startcurrent shown as -4A on the display is stored as 4A ! - #CONFIG_COMMAND="/settings?solar_start_current=4" - init_devices - init_currents - for device in $MASTER; do - set_mainsmeter_to_api - $CURLPOST "$device/settings?solar_start_current=4&solar_max_import=15&solar_stop_time=1" - done - - loadbl_master=1 - set_loadbalancing - #SOLAR mode - sleep 2 - mode_master=2 - set_mode - sleep 2 - printf "\n" - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - TESTSTRING="Feeding total of 3A so we should wait for the sun" - printf "$TESTSTRING\r" - echo 10 >feed_mains_$MASTER - sleep 5 - for device in $MASTER $SLAVE; do - STATE_ID=$(curl -s -X GET $device/settings | jq ".evse.state_id") - print_results2 "$STATE_ID" "9" "0" "STATE_ID" - done - TESTSTRING="Feeding total of -6A....should trigger ready-timer 60s" - printf "$TESTSTRING\r" - echo -20 >feed_mains_$MASTER - sleep 70 - read -p "To start charging, set EVSE's to NO CHARGING and then to CHARGING again, then press " dummy - sleep 2 - for device in $MASTER $SLAVE; do - check_charging - done - TESTSTRING="Feeding total of 18A should drop the charging current" - printf "$TESTSTRING\r" - echo 60 >feed_mains_$MASTER - sleep 10 - for device in $MASTER $SLAVE; do - check_charging - print_results "$CHARGECUR" "225" "20" - done - TESTSTRING="Feeding total of 15A should stabilize the charging current" - printf "$TESTSTRING\r" - echo 50 >feed_mains_$MASTER - sleep 10 - for device in $MASTER $SLAVE; do - check_charging - print_results "$CHARGECUR" "210" "20" - done - printf "Feeding total of 18A....chargecurrent should drop to 6A, then triggers stoptimer and when it expires, stops charging because over import limit of 15A\r" - TESTSTRING="SolarStopTimer should have been activated on overload on ImportCurrent" - echo 60 >feed_mains_$MASTER - sleep 60 - for device in $MASTER $SLAVE; do - TIMER=$(curl -s -X GET $device/settings | jq ".evse.solar_stop_timer") - print_results2 "$TIMER" "15" "5" "SOLAR_STOP_TIMER" - done - TESTSTRING="Charging should stop after expiring SolarStopTimer" - printf "$TESTSTRING\r" - sleep 40 - for device in $MASTER $SLAVE; do - STATE_ID=$(curl -s -X GET $device/settings | jq ".evse.state_id") - print_results2 "$STATE_ID" "9" "0" "STATE_ID" - done - TESTSTRING="Feeding total of -6A....should trigger ready-timer 60s" - printf "$TESTSTRING\r" - echo -20 >feed_mains_$MASTER - sleep 63 - read -p "To start charging, set EVSE's to NO CHARGING and then to CHARGING again, then press " dummy - sleep 2 - for device in $MASTER $SLAVE; do - check_charging - done - #set MainsMeter to Sensorbox - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?mainsmeter=1 - done - #kill all running subprocesses - pkill -P $$ -fi - -#TEST1024: shortened version of test512 -NR=$((2**10)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="StartCurrent, StopTimer and ImportCurrent via EM_API for loadbl=1" - printf "Starting $TESTSTRING test #$NR:\n" - #the margin for which we will accept the lowering/upping of the charge current, in dA - MARGIN=20 - TESTVALUE=-3 - TESTVALUE10=$((TESTVALUE*10)) - #note that startcurrent shown as -4A on the display is stored as 4A ! - #CONFIG_COMMAND="/settings?solar_start_current=4" - init_devices - init_currents - for device in $MASTER; do - set_mainsmeter_to_api - $CURLPOST "$device/settings?solar_start_current=4&solar_max_import=15&solar_stop_time=1" - done - #to speed up testing lower max_current - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?current_max=9 - done - sleep 1 - loadbl_master=1 - set_loadbalancing - #SOLAR mode - sleep 2 - mode_master=2 - set_mode - sleep 2 - printf "\n" - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - printf "Feeding total of 18A....chargecurrent should drop to 6A, then triggers stoptimer and when it expires, stops charging because over import limit of 15A\r" - TESTSTRING="SolarStopTimer should have been activated on overload on ImportCurrent" - echo 60 >feed_mains_$MASTER - sleep 40 - for device in $MASTER $SLAVE; do - TIMER=$(curl -s -X GET $device/settings | jq ".evse.solar_stop_timer") - print_results2 "$TIMER" "26" "5" "SOLAR_STOP_TIMER" - done - TESTSTRING="Charging should stop after expiring SolarStopTimer" - printf "$TESTSTRING\r" - sleep 40 - for device in $MASTER $SLAVE; do - STATE_ID=$(curl -s -X GET $device/settings | jq ".evse.state_id") - print_results2 "$STATE_ID" "9" "0" "STATE_ID" - done - TESTSTRING="Feeding total of -6A....should trigger ready-timer 60s" - printf "$TESTSTRING\r" - echo -20 >feed_mains_$MASTER - sleep 63 - read -p "To start charging, set EVSE's to NO CHARGING and then to CHARGING again, then press " dummy - sleep 2 - for device in $MASTER $SLAVE; do - check_charging - done - #set MainsMeter to Sensorbox - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?mainsmeter=1 - done - #kill all running subprocesses - pkill -P $$ -fi - -#TEST2048: modified version of test1024, only testing a master without any slaves -NR=$((2**11)) -if [ $((SEL & NR)) -ne 0 ]; then - TESTSTRING="StartCurrent, StopTimer and ImportCurrent via EM_API for loadbl=1 with only Master" - printf "Starting $TESTSTRING test #$NR:\n" - #the margin for which we will accept the lowering/upping of the charge current, in dA - MARGIN=20 - #make mains_overload feed mains_current with 3A per phase to the grid - TESTVALUE=-3 - TESTVALUE10=$((TESTVALUE*10)) - #note that startcurrent shown as -4A on the display is stored as 4A ! - #CONFIG_COMMAND="/settings?solar_start_current=4" - init_devices - init_currents - for device in $MASTER; do - set_mainsmeter_to_api - $CURLPOST "$device/settings?solar_start_current=4&solar_max_import=15&solar_stop_time=1" - done - #to speed up testing lower max_current - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?current_max=9 - done - loadbl_master=1 - set_loadbalancing - #put slave into multi=disabled - $CURLPOST $SLAVE/automated_testing?loadbl=0 - #SOLAR mode - sleep 2 - mode_master=2 - set_mode - sleep 2 - printf "\n" - read -p "Make sure all EVSE's are set to CHARGING, then press " dummy - printf "Feeding total of 18A....chargecurrent should drop to 6A, then triggers stoptimer and when it expires, stops charging because over import limit of 15A\r" - TESTSTRING="SolarStopTimer should have been activated on overload on ImportCurrent" - echo 60 >feed_mains_$MASTER - sleep 65 - for device in $MASTER; do - TIMER=$(curl -s -X GET $device/settings | jq ".evse.solar_stop_timer") - print_results2 "$TIMER" "8" "5" "SOLAR_STOP_TIMER" - done - TESTSTRING="Charging should stop after expiring SolarStopTimer" - printf "$TESTSTRING\r" - sleep 40 - for device in $MASTER; do - STATE_ID=$(curl -s -X GET $device/settings | jq ".evse.state_id") - print_results2 "$STATE_ID" "9" "0" "STATE_ID" - done - TESTSTRING="Feeding total of -6A....should trigger ready-timer 60s" - printf "$TESTSTRING\r" - echo -20 >feed_mains_$MASTER - sleep 63 - read -p "To start charging, set EVSE's to NO CHARGING and then to CHARGING again, then press " dummy - sleep 2 - for device in $MASTER; do - check_charging - done - #set MainsMeter to Sensorbox - for device in $MASTER $SLAVE; do - $CURLPOST $device/automated_testing?mainsmeter=1 - done - #kill all running subprocesses - pkill -P $$ -fi - -exit 0 diff --git a/SmartEVSE-3/coredumping.txt b/SmartEVSE-3/coredumping.txt deleted file mode 100755 index 15d1e83..0000000 --- a/SmartEVSE-3/coredumping.txt +++ /dev/null @@ -1,26 +0,0 @@ -How to analyze your coredump partition on SmartEVSE; advanced developers only! - -First you have to install esp32-arduino-lib-builder: --cd /usr/local/src/coredump --git clone https://github.com/espressif/esp32-arduino-lib-builder --cd esp32-arduino-lib-builder --vi configs/defconfig.esp32, add to bottom of file: CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH=y --./build.sh -t esp32 --cp -r tools/* ~/.platformio/packages/framework-arduinoespressif32/ - -Then, every time you want to analyze the coredump partition, you need to add esp-coredump to your path: -cd /usr/local/src/coredump/esp32-arduino-lib-builder/esp-idf; . export.sh; cd - - -Read coredump partition on the fly: -esp-coredump --chip esp32 -p /dev/ttyUSB0 -b 2000000 info_corefile release/firmware.elf - -Store coredump partition in a file: -esptool.py --chip esp32 --port "/dev/ttyUSB0" --baud 2000000 read_flash 0x3F0000 65536 coredump.bin - -Analyze the coredump file: -esp-coredump info_corefile --core coredump.bin --core-format raw release/firmware.elf - -Reminder to myself, the command line for flashing is currently: -esptool.py --chip esp32 --port "/dev/ttyUSB0" --baud 2000000 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 /usr/local/src/SmartEVSE-3.5/SmartEVSE-3/.pio/build/release/bootloader.bin 0x8000 /usr/local/src/SmartEVSE-3.5/SmartEVSE-3/.pio/build/release/partitions.bin 0xe000 /home/hans/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/release/firmware.bin - -REMEMBER this will only work if you rename the alternative partitions_custom.csv.coredump to partitions_custom.csv before compiling! diff --git a/SmartEVSE-3/docs/REST_API.md b/SmartEVSE-3/docs/REST_API.md deleted file mode 100644 index 7e1e204..0000000 --- a/SmartEVSE-3/docs/REST_API.md +++ /dev/null @@ -1,185 +0,0 @@ -# REST API - -The REST API can be accessed through any http tool, here as an example CURL will be used. - -# GET: /settings - -curl -X GET http://ipaddress/settings - -will give output like: -``` -{"version":"21:02:46 @Jan 3 2024","mode":"OFF","mode_id":0,"car_connected":false,"wifi":{"status":"WL_CONNECTED","ssid":"wifi_nomap_EXT","rssi":-82,"bssid":"28:87:BA:D6:B9:DE"},"evse":{"temp":16,"temp_max":60,"connected":false,"access":false,"mode":1,"loadbl":0,"pwm":1024,"solar_stop_timer":0,"state":"Ready to Charge","state_id":0,"error":"None","error_id":0,"rfid":"Not Installed"},"settings":{"charge_current":0,"override_current":0,"current_min":6,"current_max":16,"current_main":25,"current_max_circuit":16,"current_max_sum_mains":600,"solar_max_import":9,"solar_start_current":29,"solar_stop_time":10,"enable_C2":"Always On","modem":"Not present","mains_meter":"InvEastrn","starttime":0,"stoptime":0,"repeat":0},"mqtt":{"host":"10.0.0.28","port":1883,"topic_prefix":"SmartEVSE-51446","username":"homeassistant","password_set":true,"status":"Connected"},"home_battery":{"current":0,"last_update":0},"ev_meter":{"description":"Eastron3P","address":11,"import_active_power":0,"total_kwh":5670.1,"charged_kwh":0,"currents":{"TOTAL":1,"L1":0,"L2":0,"L3":1},"import_active_energy":5670.1,"export_active_energy":0},"mains_meter":{"import_active_energy":8614.8,"export_active_energy":5289.3},"phase_currents":{"TOTAL":75,"L1":57,"L2":6,"L3":12,"last_data_update":1704535684,"charging_L1":false,"charging_L2":false,"charging_L3":false,"original_data":{"TOTAL":75,"L1":57,"L2":6,"L3":12}},"backlight":{"timer":0,"status":"OFF"}} -``` - -This output is often used to add to your bug report, so the developers can see your configuration. - -NOTE: -In the http world, GET parameters are passed like this: -curl -X GET http://ipaddress/endpoint?param1=value1¶m2=value2 -and POST parameters are passed like this: -curl -X POST http://ipaddress/endpoint -d 'param1=value1' -d 'param2=value2' -d '' - -Now in the ESP world, we all have picked up the habit of using the GET way of passing parameters also for POST commands. SmartEVSE development not excluded.... -From version v3.6.0 on, instead of using the Arduino Core webserver libraries, we are now using the Mongoose webserver, which is broadly used. This webserver however sticks to the "normal" http standards. - -This means that if you POST a request to SmartEVSE > 3.6.0, the webserver will be waiting for the -d data until it times out (or you ctrl-C your curl command). -d '' -You can prevent this by adding -''' --d '' -''' - -to your curl POST command. -d '' - -# POST: /settings -* backlight - -  Turns backlight on (1) or off (0) for the duration of the backlighttimer. - -``` - curl -X POST http://ipaddress/settings?backlight=1 -d '' -``` - -* mode - -  Only following values are permitted: -
  0: OFF -
  1: NORMAL -
  2: SOLAR -
  3: SMART - -* stop_timer - -  Set the stop timer to be used when there isn't sufficient solar power. Value must be >=0 and <= 60. -
  Using 0 will disable the stop timer. - -* disable_override_current - -  If this parameter is passed the override current will be reset (value doesn't matter) - -* override_current - -  Works only when using NORMAL or SMART mode -
  Desired current multiplied by 10 -
  If set to 0, override_current is disabled - -
  Examples: -
  If the desired current is 8.3A the value to be sent is 83 -``` - curl -X POST http://ipaddress/settings?override_current=83 -d '' -``` - -* enable_C2 - -  Enables switching between 1 phase mode and 3 phase mode by controlling a 2nd contactor (C2 port) -
   -
  Note 1: The 2nd contactor will only be turned ON when state chages to C (Charging) -
  Note 2: This is just changing the config setting, the contactor will not be controlled immediately but only when there is a -
  state change. -
   -
  If car is charging and you want to change from 1F to 3F or vice versa: -``` - - Change mode to OFF - - Enable or disable C2 contactor - - Change to desired value: 0 "Not present", 1 "Always Off", 2 "Solar Off", 3 "Always On", 4 "Auto" - - Examples: - - If the desired C2 mode is "Solar Off", the string to be sent is 2 -``` -* starttime - -  Enables delayed charging; always has to be combined with sending the mode in which you want to start charging. -
   -
  Note 1: The time string has to be in the format "2023-04-14T23:31". -
  Note 2: The time must be in the future, in local time. -
  Note 3: Only valid when combined with Normal or Smart mode. Solar mode will itself decide when to start... -
   -
  Examples: -
  If you want the car to start charging at 23:31 on April 14th 2023, in Smart mode, the strings to be sent are: - -``` - curl -X POST 'http://ipaddress/settings?starttime="2023-04-14T23:31"&mode=3' -d '' -``` - -* solar_start_current - -  The Start Current at which the car starts charging when in Solar Mode. -
   -
  Examples: -
  If you want the car to start charging when the sum of all 3 phases of the MainsMeter is exporting 6A or more to the grid, -
  the value to be sent is 6 - -* current_min - -  The Minimum Charging Current in Ampères, per phase. -
  Usually you should leave this setting at its default value (6A) since this is standarized. -
  Note: This setting is useful for EV's that don't obey standards, like the Renault Zoe, whose MinCurrents not only differ -
  from the standard, but also change when charging at 1 phase and charging at 2 phases. -
  The values even differ per build year. -
  Examples: -
  If you want the car to start charging at minimally 6A, the value to be sent is 6 - -* solar_max_import - -  The maximum current (sum of all phases) of the MainsMeter that can be imported before the solar timer is fired off, -
  after expiration the car will stop charging. - -
  Examples: -
  If you want the car to stop charging when the sum of all 3 phases of the MainsMeter is importing 0A or more to the grid, -
  the value to be sent is 0 - -* current_max_sum_mains - -  The Maximum allowed Mains Current summed over all phases: 10-600A -
  This is used for the EU Capacity rate limiting, currently only in Belgium. -
  Usually you should leave this setting at its default value (600A) -
  since your electricity provider probably does not supports this. - -# POST: /currents - -* battery_current - -  Actual home battery current multiplied by 10 -
  A positive number means the home battery is charging -
  A negative number means the home battery is discharging - -* L1, L2, L3 - -  Note: Only works when MainsMeter == API -
  L1, L2 and L3 must be send all together otherwise the data won't be registered. -
  Ampere must be multiplied by 10 -``` - curl -X POST "http://ipaddress/currents?L1=100&L2=50&L3=30" -d '' -``` -  P.S.: If you want to send your currents through HomeAsistant, look at the scripts in the (integration)[integration] directory. - -# POST: /modem - -* pwm - -  The duty cycle (PWM) multiplied by 10 -
  Examples: -
  If the desired dutycycle is 5% the value to be sent is 50 -
  Note: EXPERIMENTAL FEATURE ONLY FOR EXPERTS -
  DO NOT USE THIS IF YOU ARE NOT AN EVSE EXPERT. DANGEROUS! - - -# POST: /ev_meter - -* L1, L2, L3 - -  Note: Only works when EVMeter == API -
  L1, L2 and L3 must be send all together otherwise the data won't be registered. -
  Ampere must be multiplied by 10 -``` - curl -X POST "http://ipaddress/ev_meter?L1=100&L2=50&L3=30" -d '' -``` - -* import_active_energy, export_active_energy and import_active_power - -  Note: Only works when EvMeter == API -
  import_active_energy, export_active_energy and import_active_power must be send all together otherwise -
  the data won't be registered. -
  Data should be in Wh (kWh * 1000), for import_active_power data should be in w(att) - -# POST: /reboot - -  Note: no parameters, reboots your device. diff --git a/SmartEVSE-3/docs/SmartEVSEv3_build.pdf b/SmartEVSE-3/docs/SmartEVSEv3_build.pdf deleted file mode 100644 index c7e1ae4..0000000 Binary files a/SmartEVSE-3/docs/SmartEVSEv3_build.pdf and /dev/null differ diff --git a/SmartEVSE-3/docs/configuration.md b/SmartEVSE-3/docs/configuration.md deleted file mode 100644 index bac92c3..0000000 --- a/SmartEVSE-3/docs/configuration.md +++ /dev/null @@ -1,256 +0,0 @@ - -# How to configure -* First configure all settings that are shown to you (see below); don't configure your MAINSMET -* Now you are ready to test/operate your SmartEVSE in its simplest mode, called Normal Mode. -* If your EV charges at MAX current, and everything works as expected, and you don't have a MAINSMET, you are done! -* If you have a MAINSMET, configure it now; browse through the settings again, since now other options have opened up -* If you are feeding your SmartEVSE with MAINS or EV data through the REST API or the MQTT API, make sure you have set up these feeds; as soon as you select "API" for the Meters, the data is expected within 11 seconds! You can use the test scripts in the test directory to feed your MQTT server with test data. -* If you configured MULTIple SmartEVSE's, follow instructions below -* Put your SmartEVSE in Solar Mode, and some specific settings for Solar Mode will open up -* Now your SmartEVSE is ready for use! - -# All menu options on the LCD screen: -``` -MODE: - Per default you are in Normal EVSE mode; you can also choose Smart Mode or Solar Mode, - but you will have to configure a MAINSMETer to actually use these modes. - The EV will charge with the current set at MAX - The EV will charge with a dynamic charge current, depending on MAINSMET - data, and MAINS, MAX, MIN settings - The EV will charge on solar power - -CONFIG Configure EVSE with Type 2 Socket or fixed cable: - Your SmartEVSE is connected to a socket, so it will need to sense the - cable used for its maximum capacity - Your SmartEVSE is connected to a fixed cable, so MAX will determine your - maximum charge current - -LOCK (only appears when CONFIG is set to ) - Enable or disable the locking actuator (config = socket) - No lock is used - Dostar, DUOSIDA DSIEC-ELB or Ratio lock - Signal wire reversed, DUOSIDA DSIEC-EL or Phoenix Contact - -PWR SHARE ; formerly known as LOADBALANCING. - 2 to 8 EVSE’s can be connected via modbus, and their load will be balanced - Single SmartEVSE - Set the first SmartEVSE to Master. Make sure there is only one Master. - And the other SmartEVSE's to Node 1-7. - -MAINSMET Set type of MAINS meter (only appears in Smart or Solar mode): - No MAINS meter connected; only Normal mode possible - the Sensorbox will send measurement data to the SmartEVSE - The MAINS meter data will be fed through the REST API or the MQTT API. - / / <...> / a Modbus kWh meter is used - - Note that Eastron1P is for single phase Eastron meters, Eastron3P for Eastron three phase - meters, and InvEastron is for Eastron three phase meter that is fed from below (inverted). - If MAINSMET is not and not , these settings appear: - MAINSADR Set the Modbus address for the kWh meter - GRID (only appears when Sensorbox with CT’s is used) - 3 or 4 wire - CAL Calibrate CT1. CT2 and CT3 will use the same cal value. - 6.0-99.9A A minimum of 6A is required in order to change this value. - Hold both ▼and ▲ buttons to reset to default settings. - -EV METER Set type of EV kWh meter (measures power and charged energy) - No EV meter connected. - The EV meter data will be fed through the REST API or the MQTT API. - / / <...> / a Modbus kWh meter is used - - Note that Eastron1P is for single phase Eastron meters, Eastron3P for Eastron three phase - meters, and InvEastron is for Eastron three phase meter that is fed from below (inverted). - If EV METER is not and not , this setting appears: - EV ADR Set the Modbus address for the EV Meter - -MAINS (only appears when a MAINSMET is configured): - Set Max Mains current: 10-200A (per phase) - -MIN (only appears when a MAINSMET is configured): - Set MIN charge current for the EV: 6-16A (per phase) - -MAX Set MAX charge current for the EV: 10-80A (per phase) - If CONFIG is set to , configure MAX lower or equal to the maximum current - that your fixed cable can carry. - -CIRCUIT (only appears when PWR SHARE set to , or when PWR SHARE set to - and Mode is Smart or Solar and EV METER not set to ): - Set the max current the EVSE circuit can handle (load balancing): 10-200A - (see also subpanel wiring) - -SWITCH Set the function of an external switch connected to pin SW - A push button on io pin SW can be used to STOP charging - A momentary push Button is used to enable/disable access to the charging station - A toggle switch is used to enable/disable access to the charging station - A momentary push Button is used to switch between Smart and Solar modes - A toggle switch is used to switch between Smart and Solar modes - -RCMON RCM14-03 Residual Current Monitor is plugged into connector P1 - The RCD option is not used - When a fault current is detected, the contactor will be opened - -RFID use a RFID card reader to enable/disable access to the EVSE - A maximum of 20 RFID cards can be stored. - / / / / - -WIFI Enable wifi connection to your LAN - No wifi connection - The SmartEVSE presents itself as a Wifi Acces Point "smartevse-xxxx"; - connect with your phone to that access point, goto http://192.168.4.1/ - and configure your Wifi password - Connect to your LAN via Wifi. - -MAX TEMP Maximum allowed temperature for your SmartEVSE; 40-75C, default 65. - You can increase this if your SmartEVSE is in direct sunlight. - -SUMMAINS (only appears when a MAINSMET is configured): - Maximum allowed Mains Current summed over all phases: 10-600A - This is used for the EU Capacity rate limiting, currently only in Belgium - -The following options are only shown when Mode set to and -PWR SHARE set to or : -START set the current on which the EV should start Solar charging: - -0 -48A (sum of all phases) -STOP Stop charging when there is not enough solar power available: - Disabled - 60 minutes (Disabled = never stop charging) -IMPORT Allow additional grid power when solar charging: 0-20A (summed over all phases) - NOTE: A setting of IMPORT lower thant START + MIN makes NO SENSE and will - result in a non-charging SmartEVSE when in Solar mode. - You even need to set IMPORT at least a few Amps higher then START + MIN to get - a desired charging behaviour if you are charging at 1 phase. - You even need to set IMPORT at least a few Amps higher then START + 3 * MIN to get - a desired charging behaviour if you are charging at 3 phases. - NOTE2: Note that START and IMPORT are summed over all phases, and MIN is per phase! -CONTACT2 One can add a second contactor (C2) that switches off 2 of the 3 phases of a - 3 phase Mains installation; this can be useful if one wants to charge of off - Solar; EV's have a minimal charge current of 6A, so switching off 2 phases - allows you to charge with a current of 6-18A, while 3 phases have a - minimum current of 3x6A=18A. - This way you can still charge solar-only on smaller solar installations. -
- IMPORTANT NOTE: You WILL have to wire your C2 contactor according to the schematics - in [Hardware installation](docs/installation.md). If you invent your own wiring - your installation will be UNSAFE! - - No second contactor C2 is present (default) - C2 is always off, so you are single phase charging - C2 is always on, so you are three phase charging (if your Mains are three phase and your EV - supports it) - C2 is always on except in Solar Mode where it is always off - SmartEVSE starts charging at 3phase, but when in Solar mode and not enough - current available for 3 phases, switches off C2 so it will continue on 1 phase - - -``` -# REST API - -For the specification of the REST API, see [REST API](REST_API.md) - -# MQTT API -Your SmartEVSE can now export the most important data to your MQTT-server. Just fill in the configuration data on the webserver and the data will automatically be announced to your MQTT server. - -You can easily show all the MQTT topics published: -``` -mosquitto_sub -v -h ip-of-mosquitto-server -u username -P password -t '#' -``` - -You can feed the SmartEVSE data by publishing to a topic: -``` -mosquitto_pub -h ip-of-mosquitto-server -u username -P password -t 'SmartEVSE-xxxxx/Set/CurrentOverride' -m 150 -``` -...where xxxxx your SmartEVSE's serial number, will set your Override Current to 15.0A. - -Valid topics you can publish to are: -``` -/Set/Mode -/Set/CurrentOverride -/Set/CurrentMaxSumMains -/Set/CPPWMOverride -/Set/MainsMeter -/Set/EVMeter -/Set/HomeBatteryCurrent -/Set/RequiredEVCCID -``` -Your MainsMeter can be fed with: -``` -mosquitto_pub -h ip-of-mosquitto-server -u username -P password -t 'SmartEVSE-xxxxx/Set/MainsMeter' -m L1:L2:L3 -``` -...where L1 - L3 are the currents in deci-Ampères. So 100 means 10.0A. - -You can find test scripts in the test directory that feed EV and MainsMeter data to your MQTT server. - -# Multiple SmartEVSE controllers on one mains supply (Power Share) -Up to eight SmartEVSE modules can share one mains supply. - - Hardware connections - - Connect the A, B and GND connections from the Master to the Node(s). - - So A connects to A, B goes to B etc. - - If you are using Smart/Solar mode, you should connect the A, B , +12V and GND wires from the sensorbox to the same screw terminals of the SmartEVSE! Make sure that the +12V wire from the sensorbox is connected to only -one– SmartEVSE. - - - Software configuration - - Set one SmartEVSE PWR SHARE setting to MASTER, the others to NODE 1-7. Make sure there is only one Master, and the Node numbers are unique. - - On the Master configure the following: - - MODE Set this to Smart if a Sensorbox (or configured kWh meter) is used to measure the current draw on the mains supply. - It will then dynamically vary the charge current for all connected EV’s. If you are using a dedicated mains supply for the EV’s you can leave this set to Normal. - - MAINS Set to the maximum current of the MAINS connection (per phase). - If the sensorbox or other MainsMeter device measures a higher current then this value on one of the phases, it will immediately reduce the current to the EVSE’s - - CIRCUIT Set this to the maximum current of the EVSE circuit (per phase). - This will be split between the connected and charging EV’s. - - MAX Set the maximum charging current for the EV connected to -this- SmartEVSE (per phase). - - MIN Set to the lowest allowable charging current for all connected EV’s. - - On the Nodes configure the following: - - MAX Set the maximum charging current for the EV connected to -this- SmartEVSE (per phase). - -# Home Battery Integration -In a normal EVSE setup a sensorbox is used to read the P1 information to deduce if there is sufficient solar energy available. This however can give unwanted results when also using a home battery as this will result in one battery charging the other one.
- -For this purpose the settings endpoint allows you to pass through the battery current information: -* A positive current means the battery is charging -* A negative current means the battery is discharging - -The EVSE will use the battery current to neutralize the impact of a home battery on the P1 information.
-**Regular updates from the consumer are required to keep this working as values cannot be older than 11 seconds.** - -### Example -* Home battery is charging at 2300W -> 10A -* P1 has an export value of 230W -> -1A -* EVSE will neutralize the battery and P1 will be "exporting" -11A - -The sender has several options when sending the home battery current: -* Send the current AS-IS -> EVSE current will be maximized -* Only send when battery is discharging -> AS-IS operation but EVSE will not discharge the home battery -* Reserve an amount of current for the home battery (e.g. 10A) -> Prioritize the home battery up to a specific limit - -# Integration with Home Assistant -There are three options to integrate SmartEVSE with Home Assistant: -* through the HA-integration - the easy way
- - If you want to integrate your SmartEVSE with Home Asisstant, please have a look at [the SmartEVSE `custom_component` for Home Assistant](https://github.com/dingo35/ha-SmartEVSEv3). This `custom_component` uses the API to share data from the SmartEVSE to Home Assistant, and enables you to set SmartEVSE settings from Home Assistant. You will need firmware version 1.5.2 or higher to use this integration. - -* by manually configuring your configuration.yaml
- - Its a lot of work, but you can have everything exactly your way. See examples in the integrations directory of our github repository. - -* by MQTT
- - If you don't like the integration, e.g. because it only updates its data every 60 seconds, you might like to interface through MQTT; updates are done as soon as values change.... you can even mix it up by using both the integration AND the MQTT interface at the same time! - -# EU Capacity Rate Limiting -An EU directive gives electricity providers the possibility to charge end consumers by a "capacity rate", so consumers will be stimulated to flatten their usage curve. -Currently the only known country that has this active is Belgium. -For more details see https://github.com/serkri/SmartEVSE-3/issues/215 - -* In the Menu screen an item "SumMains" is now available, default set at 600A -* This setting will only be of use in Smart or Solar mode -* Apart from all other limits (Mains, MaxCirCuit), the charge current will be limited so that the sum of all phases of the Mains currents will not be exceeding the SumMains setting -* If you don't understand this setting, or don't live in Belgium, leave this setting at its default value - -# Building the firmware -You can get the latest release off of https://github.com/dingo35/SmartEVSE-3.5/releases, but if you want to build it yourself: -* Install platformio-core https://docs.platformio.org/en/latest/core/installation/methods/index.html -* Clone this github project, cd to the smartevse directory where platformio.ini is located -* Compile firmware.bin: platformio run -For versions older than v3.6.0: -* Compile spiffs.bin: platformio run -t buildfs - -If you are not using the webserver /update endpoint: -* Upload via USB configured in platformio.ini: platformio run --target upload diff --git a/SmartEVSE-3/docs/installation.md b/SmartEVSE-3/docs/installation.md deleted file mode 100644 index 9244039..0000000 --- a/SmartEVSE-3/docs/installation.md +++ /dev/null @@ -1,67 +0,0 @@ -# Hardware installation - -We refer to [this wiring diagram](SmartEVSEv3_build.pdf) for wiring the SmartEVSE. - - -# Inverted wiring of kWh meter -If you are using a 3 phase Eastron kWh meter, you can feed it from below (like in most Dutch power panels). Now the polarity of currents is reversed, so in the MainsMeter or EVMeter configuration you should choose kWh meter type "Inverted Eastron". - -# Subpanel or "garage" configuration -If you have other current-users on a Subpanel, use this wiring and the added configuration: - - mains - | - [main breaker 25A] - | - [kWh meter "Mains"] - | - ----------------------------------- - | | | - [group breaker 16A] [subpanel breaker 16A] - | - [kWh meter "EV"] - | - ---------------- - | | - [washer breaker 16A] [smartevse breaker 16A] - - In this example you configure Mains to 25A, MaxCircuit to 16A; the charger will limit itself so that neither the 25A mains nor the 16A from the subpanel will be - exceeded... - Note that for this functionality you will need to be in Smart or Solar mode; it is no longer necessary to enable Load Balancing for this function. - -# Second Contactor C2 -One can add a second contactor (C2) that switches off 2 of the 3 phases of a three-phase Mains installation; this can be useful if one wants to charge of off - Solar; EV's have a minimal charge current of 6A, so switching off 2 phases allows you to charge with a current of 6-18A, while 3 phases have a minimum current - of 3x6A=18A. This way you can still charge solar-only on smaller solar installations. - - one should wire C2 according to this schema: - - N L1 L2 L3 - | | | | - -------------------- - | 4-p contactor C1 | - -------------------- - | | | | - | | ------------------ - | | |2-p contactor C2| - | | ------------------ - | | | | - -------------------- - | EV-cable | - -------------------- - - This way the (dangerous) situation is avoided that some Phases are switched ON, and Neutral is switched OFF. - Note that it is important that you actually DO NOT switch the L1 pin of the CCS plug with the C2 contactor; some cars (e.g. Tesla Model 3) will go into error; - they expect the charging phase to be on the L1 pin when single-phase charging... - Note also that in case the phases cannot be detected automatically (especially when no EVmeter is connected), and SmartEVSE _knows_ it is charging at a single - phase (e.g. because Contact2 is at "Always Off"), it assumes that L1 is the phase we are charging on!! - - By default C2 is switched OFF ("Not present"); if you want to keep on charging on 3 phases after installing C2, you should change the setting Contact2 in the - Setup Menu. - -# Multiple SmartEVSE controllers on one mains supply (Power Share) -Up to eight SmartEVSE modules can share one mains supply. -Hardware connections -* Connect the A, B and GND connections from the Master to the Node(s). -* So A connects to A, B goes to B etc. -* If you are using Smart/Solar mode, you should connect the A, B , +12V and GND wires from the sensorbox to the same screw terminals of the SmartEVSE! Make sure that the +12V wire from the sensorbox is connected to only -one– SmartEVSE. - diff --git a/SmartEVSE-3/docs/operation.md b/SmartEVSE-3/docs/operation.md deleted file mode 100644 index 84cdb89..0000000 --- a/SmartEVSE-3/docs/operation.md +++ /dev/null @@ -1,80 +0,0 @@ -# Improved starting/stopping through the LCD screen -* When pressing o button longer then 2 seconds you will enter the Menu screen -* When pressing < button shorter then 2 seconds, you will toggle between Smart/Solar mode -* When pressing < button longer then 2 seconds the access will be denied, i.e. the mode will be set to "Off" and charging will stop -* When pressing > button longer then 2 seconds the access will be granted, i.e. the previously set mode will be activated and charging will start - -# Webserver -After configuration of your Wifi parameters, your SmartEVSE will present itself on your LAN via a webserver. This webserver can be accessed through: -* http://ip-address/ -* http://smartevse-xxxx.local/ where xxxx is the serial number of your SmartEVSE. It can be found on a sticker on the bottom of your SmartEVSE. It might be necessary that mDNS is configured on your LAN. -* http://smartevse-xxxx.lan/ where xxxx is the serial number of your SmartEVSE. It can be found on a sticker on the bottom of your SmartEVSE. It might be necessary that mDNS is configured on your LAN. -* OTA update of your firmware: - - surf to http://your-smartevse/update or press the UPDATE button on the webserver - - select the firmware.bin from this archive, OR if you want the debug version (via telnet over your wifi), - rename firmware.debug.bin to firmware.bin and select that. YOU CANNOT FLASH A FILE WITH ANOTHER NAME! - - if you get FAIL, check your wifi connection and try again; - - after OK, wait 10-30 seconds and your new firmware including the webserver should be online! -* Added wifi-debugging: if you flashed the debug version, telnet http://your-smartevse/ will bring you to a debugger that shows you whats going on! -* OTA upload of rfid lists: - - via the "update" button or the /update endpoint you can upload a file called rfid.txt; - - file layout: every line is supposed to contain one RFID (=NFC) TAG UID of size bytes in hex format: -''' -112233445566 -0A3B123FFFA0 -''' - - before upload all existing RFID's are deleted in the SmartEVSE you are uploading to - - if you have PWR SHARE enabled (master/slave configuration), you must upload to every single SmartEVSE; this enables you to maintain different lists for different SmartEVSEs. - -# Mode switching when PWR SHARE is activated -* If you switch mode on the Master, the Slaves will follow that mode switch -* If you switch mode on one Slave, and your Master does not have a Smart/Solar toggle switch configured, the Master and all the other slaves will follow -* If you have a Smart/Solar toggle switch you have to guard yourself that Master and Slaves are all in the same mode. We recommend replacing that toggle switch by a pushbutton switch. - -# Error Messages -If an error occurs, the SmartEVSE will stop charging, and display one of the following messages: -* ERROR NO SERIAL COM CHECK WIRING
No signal from the Sensorbox or other SmartEVSE (when load balancing is used) has been received for 11 seconds. Please check the wiring to the Sensorbox or other SmartEVSE. -* ERROR NO CURRENT
There is not enough current available to start charging, or charging was interrupted because there was not enough current available to keep charging. The SmartEVSE will try again in 60 seconds. -* ERROR HIGH TEMP
The temperature inside the module has reached 65º Celsius. Charging is stopped. -Once the temperature has dropped below 55ºC charging is started again. -* RESIDUAL FAULT CURRENT DETECTED
An optional DC Residual Current Monitor has detected a fault current, the Contactor is switched off. -The error condition can be reset by pressing any button on the SmartEVSE. - -# Changes with regards to the original firmware -* Endpoint to send L1/2/3 data, this removed the need for a SensorBox - * Note: Set MainsMeter to the new 'API' option in the config menu when sending L1/2/3 -* Endpoint to send EvMeter L1/2/3 data (and energy/power) - * Note: Set EvMeter to the new 'API' option in the config menu when sending L1/2/3 -* Callable API endpoints for easy integration (see [REST_API](REST_API.md) and [Home Assistant Integration](configuration.md#integration-with-home-assistant)) - * Change charging mode - * Override charge current - * Pass in current measurements (p1, battery, ...) - this eliminates having to use additional hardware - * Switch between single- and three phase power (requires extra 2P relais on the c2 connecor, see [Second Contactor](installation.md#second-contactor-c2)) - -# Simple Timer - -There is a simple timer implemented on the webserver, for Delayed Charging. -* Upon refreshing your webpage, the StartTime field (next to the Mode buttons) will be filled with the current system time. -* If you press any of the Mode buttons, your charging session will start immediately; -* If you choose to enter a StartTime that is in the future, a StopTime field will open up; - If you leave this to the default value it is considered to be empty; now if you press Normal, Solar or Smart mode - - the StartTime will be registered, - - the mode will switch to OFF, - - a charging session will be started at StartTime, at either Normal or Smart mode; - - the SmartEVSE will stay on indefinitely. -* If you enter a StopTime, a checkbox named "Daily" will open up; if you check this, the startime/stoptime combination will be used on a daily basis, - starting on the date you entered at the StartTime. -* To clear StartTime, StopTime and Repeat, refresh your webpage and choose either Normal, Solar or Smart mode. -* Know bugs: - - if your NTP time is not synchronized yet (e.g. after a reboot), results will be unpredictable. WAIT until time is settled. - - if your StopTime is AFTER your StartTime+24Hours, untested territories are entered. Please enter values that make sense. - -# EU Capacity Rate Limiting -An EU directive gives electricity providers the possibility to charge end consumers by a "capacity rate", so consumers will be stimulated to flatten their usage curve. -Currently the only known country that has this active is Belgium. -For more details see https://github.com/serkri/SmartEVSE-3/issues/215 - -* In the Menu screen an item "SumMains" is now available, default set at 600A -* This setting will only be of use in Smart or Solar mode -* Apart from all other limits (Mains, MaxCirCuit), the charge current will be limited so that the sum of all phases of the Mains currents will not be exceeding the SumMains setting -* If you don't understand this setting, or don't live in Belgium, leave this setting at its default value diff --git a/SmartEVSE-3/integrations/README.md b/SmartEVSE-3/integrations/README.md deleted file mode 100644 index 346a85a..0000000 --- a/SmartEVSE-3/integrations/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Easy integration with Home Assistant -If you want to integrate your SmartEVSE with Home Asisstant, please have a look at the [SmartEVSE `custom_component` for Home Assistant](https://github.com/dingo35/ha-SmartEVSEv3). This `custom_component` uses the SmartEVSE API to share data from the SmartEVSE to Home Assistant, and enables you to set SmartEVSE settings from Home Assistant. You will need firmware version 1.5.2 or higher to use this integration. - -The YAML file in the home-assistant folder is deprecated. - -# Use ESPHome to provide `current` information to SmartEVSE -Your SmartEVSE has an API endpoint to send L1/L2/L3 data, which means that you don't need to connect a SensorBox to retrieve the required information for load-balancing or solar-charging. -If you are using a P1 reader (often also called DSMR-reader) with your electricity meter, you might want to send the `current` information (L1/L2/L3) directly from your ESPHome device to your SmartEVSE. - -See the [esphome folder](/integrations/esphome/) for an example ESPHome configuration. diff --git a/SmartEVSE-3/integrations/home-assistant/HASS-Node-RED.json b/SmartEVSE-3/integrations/home-assistant/HASS-Node-RED.json deleted file mode 100644 index 804c994..0000000 --- a/SmartEVSE-3/integrations/home-assistant/HASS-Node-RED.json +++ /dev/null @@ -1,442 +0,0 @@ -[ - { - "id": "fa7895a2ccea5b3f", - "type": "tab", - "label": "SerKri - EVSE", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "17800a49b47fa026", - "type": "inject", - "z": "fa7895a2ccea5b3f", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "5", - "crontab": "", - "once": false, - "onceDelay": "5", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 330, - "y": 200, - "wires": [ - [ - "ff1480dbbb7f18c7" - ] - ] - }, - { - "id": "ff1480dbbb7f18c7", - "type": "link call", - "z": "fa7895a2ccea5b3f", - "name": "", - "links": [ - "5862ce2c075744ed" - ], - "timeout": "30", - "x": 610, - "y": 200, - "wires": [ - [ - "784e625f0910483a" - ] - ] - }, - { - "id": "a69e33209285ace0", - "type": "function", - "z": "fa7895a2ccea5b3f", - "name": "EVSE - Home Battery Charge Calculation", - "func": "let battery_charge = msg.battery_charge.state;\nlet battery_charge_last_update = new Date(msg.battery_charge.last_updated);\nlet battery_level = msg.battery_level;\nlet voltage = msg.voltage < 100 ? 230 : msg.voltage;\nlet battery_reserve = msg.battery_reserve * 10;\n\nlet current = new Date();\nlet interval = new Date(current.getTime() - (24 * 3600000));\n\nlet battery_ampere = Math.round(battery_charge / voltage * 10);\nmsg.battery_ampere_original=battery_ampere;\n\nif(battery_charge_last_update.getTime() >= interval.getTime() && battery_level < 100) {\n if(battery_ampere <= battery_reserve) {\n battery_ampere = Math.round( (battery_ampere - battery_reserve) * 0.2);\n } else if (battery_ampere > battery_reserve) {\n battery_ampere -= battery_reserve;\n }\n} else {\n battery_ampere = 0;\n}\n\nmsg.battery_charge = battery_charge;\nmsg.last_update = battery_charge_last_update;\n\nmsg.battery_ampere=battery_ampere;\n\nreturn msg;\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 480, - "y": 320, - "wires": [ - [ - "1533b6b07d285c0f" - ] - ] - }, - { - "id": "d6cf6a7d5b14cbf7", - "type": "link in", - "z": "fa7895a2ccea5b3f", - "name": "EVSE - Home Battery Charge Calculation", - "links": [], - "x": 255, - "y": 320, - "wires": [ - [ - "a69e33209285ace0" - ] - ] - }, - { - "id": "c3b957aecca6b782", - "type": "comment", - "z": "fa7895a2ccea5b3f", - "name": "CALCULATION", - "info": "", - "x": 320, - "y": 270, - "wires": [] - }, - { - "id": "1533b6b07d285c0f", - "type": "link out", - "z": "fa7895a2ccea5b3f", - "name": "", - "mode": "return", - "links": [], - "x": 725, - "y": 320, - "wires": [] - }, - { - "id": "784e625f0910483a", - "type": "link call", - "z": "fa7895a2ccea5b3f", - "name": "", - "links": [ - "d6cf6a7d5b14cbf7" - ], - "timeout": "30", - "x": 990, - "y": 200, - "wires": [ - [ - "950b5b90f76bc7d4" - ] - ] - }, - { - "id": "950b5b90f76bc7d4", - "type": "link call", - "z": "fa7895a2ccea5b3f", - "name": "", - "links": [ - "09aa17af8f2e1275" - ], - "timeout": "30", - "x": 1350, - "y": 200, - "wires": [ - [] - ] - }, - { - "id": "c2cb7755cbd71b6a", - "type": "comment", - "z": "fa7895a2ccea5b3f", - "name": "SCHEDULE", - "info": "", - "x": 310, - "y": 140, - "wires": [] - }, - { - "id": "d615e1479edfb12a", - "type": "api-current-state", - "z": "fa7895a2ccea5b3f", - "name": "Battery Charge/Discharge", - "server": "ce8f7f94.ea5ce", - "version": 3, - "outputs": 1, - "halt_if": "", - "halt_if_type": "str", - "halt_if_compare": "is", - "entity_id": "sensor.storage_charge_discharge_power_bt21a0492804", - "state_type": "str", - "blockInputOverrides": false, - "outputProperties": [ - { - "property": "battery_charge", - "propertyType": "msg", - "value": "", - "valueType": "entity" - } - ], - "for": "0", - "forType": "num", - "forUnits": "minutes", - "override_topic": false, - "state_location": "payload", - "override_payload": "msg", - "entity_location": "data", - "override_data": "msg", - "x": 440, - "y": 690, - "wires": [ - [ - "e54b4429805d53aa" - ] - ] - }, - { - "id": "e54b4429805d53aa", - "type": "api-current-state", - "z": "fa7895a2ccea5b3f", - "name": "Battery Level", - "server": "ce8f7f94.ea5ce", - "version": 3, - "outputs": 1, - "halt_if": "", - "halt_if_type": "str", - "halt_if_compare": "is", - "entity_id": "sensor.solar_battery_level", - "state_type": "str", - "blockInputOverrides": false, - "outputProperties": [ - { - "property": "battery_level", - "propertyType": "msg", - "value": "", - "valueType": "entityState" - } - ], - "for": "0", - "forType": "num", - "forUnits": "minutes", - "override_topic": false, - "state_location": "payload", - "override_payload": "msg", - "entity_location": "data", - "override_data": "msg", - "x": 680, - "y": 690, - "wires": [ - [ - "29cae9661c1b2f0b" - ] - ] - }, - { - "id": "5862ce2c075744ed", - "type": "link in", - "z": "fa7895a2ccea5b3f", - "name": "EVSE - Fetch Home Battery Data", - "links": [], - "x": 255, - "y": 690, - "wires": [ - [ - "d615e1479edfb12a" - ] - ] - }, - { - "id": "fa6dced73a152860", - "type": "link out", - "z": "fa7895a2ccea5b3f", - "name": "", - "mode": "return", - "links": [], - "x": 1235, - "y": 690, - "wires": [] - }, - { - "id": "29cae9661c1b2f0b", - "type": "api-current-state", - "z": "fa7895a2ccea5b3f", - "name": "Voltage", - "server": "ce8f7f94.ea5ce", - "version": 3, - "outputs": 1, - "halt_if": "", - "halt_if_type": "str", - "halt_if_compare": "is", - "entity_id": "sensor.energy_voltage", - "state_type": "str", - "blockInputOverrides": false, - "outputProperties": [ - { - "property": "voltage", - "propertyType": "msg", - "value": "", - "valueType": "entityState" - } - ], - "for": "0", - "forType": "num", - "forUnits": "minutes", - "override_topic": false, - "state_location": "payload", - "override_payload": "msg", - "entity_location": "data", - "override_data": "msg", - "x": 880, - "y": 690, - "wires": [ - [ - "fdd19243ac817d7a" - ] - ] - }, - { - "id": "fdd19243ac817d7a", - "type": "api-current-state", - "z": "fa7895a2ccea5b3f", - "name": "Battery Reserve", - "server": "ce8f7f94.ea5ce", - "version": 3, - "outputs": 1, - "halt_if": "", - "halt_if_type": "str", - "halt_if_compare": "is", - "entity_id": "input_number.home_battery_reserve", - "state_type": "str", - "blockInputOverrides": false, - "outputProperties": [ - { - "property": "battery_reserve", - "propertyType": "msg", - "value": "", - "valueType": "entityState" - } - ], - "for": "0", - "forType": "num", - "forUnits": "minutes", - "override_topic": false, - "state_location": "payload", - "override_payload": "msg", - "entity_location": "data", - "override_data": "msg", - "x": 1080, - "y": 690, - "wires": [ - [ - "fa6dced73a152860" - ] - ] - }, - { - "id": "09aa17af8f2e1275", - "type": "link in", - "z": "fa7895a2ccea5b3f", - "name": "EVSE - Set Home Battery Current", - "links": [], - "x": 255, - "y": 530, - "wires": [ - [ - "572d40e8e7d6b40d" - ] - ] - }, - { - "id": "1f23fc18269f9a1d", - "type": "link out", - "z": "fa7895a2ccea5b3f", - "name": "", - "mode": "return", - "links": [], - "x": 565, - "y": 530, - "wires": [] - }, - { - "id": "572d40e8e7d6b40d", - "type": "http request", - "z": "fa7895a2ccea5b3f", - "name": "", - "method": "POST", - "ret": "obj", - "paytoqs": "ignore", - "url": "http://10.0.0.1/currents?battery_current={{battery_ampere}}", - "tls": "", - "persist": false, - "proxy": "", - "authType": "", - "senderr": true, - "credentials": { - "user": "", - "password": "" - }, - "x": 420, - "y": 530, - "wires": [ - [ - "1f23fc18269f9a1d" - ] - ] - }, - { - "id": "99331fbb5748fc53", - "type": "inject", - "z": "fa7895a2ccea5b3f", - "name": "Set Battery Current", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 210, - "y": 470, - "wires": [ - [ - "572d40e8e7d6b40d" - ] - ] - }, - { - "id": "1278c4ccd68616de", - "type": "comment", - "z": "fa7895a2ccea5b3f", - "name": "CALL EVSE", - "info": "", - "x": 310, - "y": 420, - "wires": [] - }, - { - "id": "1944f244e90c43ef", - "type": "comment", - "z": "fa7895a2ccea5b3f", - "name": "FETCH DATA FOR CALCULATION", - "info": "", - "x": 380, - "y": 640, - "wires": [] - }, - { - "id": "ce8f7f94.ea5ce", - "type": "server", - "name": "Home Assistant", - "version": 2, - "addon": true, - "rejectUnauthorizedCerts": true, - "ha_boolean": "y|yes|true|on|home|open", - "connectionDelay": true, - "cacheJson": true, - "heartbeat": false, - "heartbeatInterval": 30 - } -] \ No newline at end of file diff --git a/SmartEVSE-3/integrations/home-assistant/configuration.yaml b/SmartEVSE-3/integrations/home-assistant/configuration.yaml deleted file mode 100644 index 994ada4..0000000 --- a/SmartEVSE-3/integrations/home-assistant/configuration.yaml +++ /dev/null @@ -1,148 +0,0 @@ -#lines to integrate SmartEVSE with HASS through native rest-API; no NodeRed or MQTT needed. -#adapt lines 6,145,146 to the url or ip-address of your SmartEVSE -#NOTE: this .yaml is deprecated; it is easier to use the HACS integration, just search for smartevse or look at https://github.com/dingo35/ha-SmartEVSEv3 - -sensor: - - platform: rest - resource: http://SmartEVSE-xxxxx.lan/settings - name: smartevse - scan_interval: 300 - timeout: 20 - json_attributes: - - mode -# - mode_id - - car_connected - - evse - - settings - - ev_meter - - mains_meter - - phase_currents - value_template: "OK" - -template: - sensor: - - name: "smartevse_mode" - state: >- - {{ state_attr('sensor.smartevse', 'mode') }} - - name: "smartevse_car_connected" - state: >- - {{ state_attr('sensor.smartevse', 'car_connected') }} - - name: "smartevse_temp" - state: >- - {{ state_attr('sensor.smartevse', 'evse')['temp'] }} - device_class: temperature - unit_of_measurement: "°C" -# - name: "smartevse_connected" -# state: >- -# {{ state_attr('sensor.smartevse', 'evse')['connected'] }} - - name: "smartevse_access" - state: >- - {{ state_attr('sensor.smartevse', 'evse')['access'] }} -# - name: "smartevse_mode2" -# state: >- -# {{ state_attr('sensor.smartevse', 'evse')['mode'] }} -# - name: "smartevse_charge_timer" -# state: >- -# {{ state_attr('sensor.smartevse', 'evse')['charge_timer'] }} - - name: "smartevse_solar_stop_timer" - state: >- - {{ state_attr('sensor.smartevse', 'evse')['solar_stop_timer'] }} - - name: "smartevse_state" - state: >- - {{ state_attr('sensor.smartevse', 'evse')['state'] }} -# - name: "smartevse_state_id" -# state: >- -# {{ state_attr('sensor.smartevse', 'evse')['state_id'] }} -# - name: "smartevse_error" -# state: >- -# {{ state_attr('sensor.smartevse', 'evse')['error'] }} - - name: "smartevse_error_id" - state: >- - {{ state_attr('sensor.smartevse', 'evse')['error_id'] }} - - name: "smartevse_charge_current" - state: >- - {{ state_attr('sensor.smartevse', 'settings')['charge_current'] / 10 }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_override_current" - state: >- - {{ state_attr('sensor.smartevse', 'settings')['override_current'] }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_current_min" - state: >- - {{ state_attr('sensor.smartevse', 'settings')['current_min'] }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_current_max" - state: >- - {{ state_attr('sensor.smartevse', 'settings')['current_max'] }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_current_main" - state: >- - {{ state_attr('sensor.smartevse', 'settings')['current_main'] }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_solar_max_import" - state: >- - {{ state_attr('sensor.smartevse', 'settings')['solar_max_import'] }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_solar_start_current" - state: >- - {{ state_attr('sensor.smartevse', 'settings')['solar_start_current'] }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_solar_stop_time" - state: >- - {{ state_attr('sensor.smartevse', 'settings')['solar_stop_time'] }} - - name: "smartevse_ev_import_active_energy" - state: >- - {{ state_attr('sensor.smartevse', 'ev_meter')['import_active_energy'] }} - unit_of_measurement: "kWh" - device_class: energy - state_class: total - - name: "smartevse_mains_import_active_energy" - state: >- - {{ state_attr('sensor.smartevse', 'mains_meter')['import_active_energy'] }} - unit_of_measurement: "kWh" - device_class: energy - state_class: total - - name: "smartevse_mains_export_active_energy" - state: >- - {{ state_attr('sensor.smartevse', 'mains_meter')['export_active_energy'] }} - unit_of_measurement: "kWh" - device_class: energy - state_class: total - - name: "smartevse_total" - state: >- - {{ state_attr('sensor.smartevse', 'phase_currents')['TOTAL'] / 10 }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_l1" - state: >- - {{ state_attr('sensor.smartevse', 'phase_currents')['L1'] / 10 }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_l2" - state: >- - {{ state_attr('sensor.smartevse', 'phase_currents')['L2'] / 10 }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_l3" - state: >- - {{ state_attr('sensor.smartevse', 'phase_currents')['L3'] / 10 }} - unit_of_measurement: "A" - device_class: current - - name: "smartevse_last_data_update" - state: >- - {{ state_attr('sensor.smartevse', 'phase_currents')['last_data_update'] | timestamp_custom ('%Y/%m/%d %H:%M:%S') }} - -switch: - - platform: command_line - switches: - smartevse_mode_switch: - command_on: "curl -s -X POST http://SmartEVSE-xxxxx.lan/settings?mode=3 -H 'accept: application/json' -H 'Content-Type: application/json' -d '{}'" - command_off: "curl -s -X POST http://SmartEVSE-xxxxx.lan/settings?mode=0 -H 'accept: application/json' -H 'Content-Type: application/json' -d '{}'" - value_template: '{{value_json.mode_id == "3"}}' diff --git a/SmartEVSE-3/integrations/home-assistant/esphome/ET-SM01 smart meter module.yaml b/SmartEVSE-3/integrations/home-assistant/esphome/ET-SM01 smart meter module.yaml deleted file mode 100644 index 9f60106..0000000 --- a/SmartEVSE-3/integrations/home-assistant/esphome/ET-SM01 smart meter module.yaml +++ /dev/null @@ -1,164 +0,0 @@ -substitutions: - device_name: et-sm01 - human_devicename: DSMR - device_description: "espthings.io ET-SM01 DIY P1 module to read your smart meter" - pcb_version: "220818-01" - url: "http://espthings.io/sm01" - -esphome: - name: ${device_name} - comment: "${device_description} ${pcb_version}" - platform: ESP8266 - esp8266_restore_from_flash: true - board: d1_mini - name_add_mac_suffix: false - project: - name: "espthings.io-ET-SM01" - version: "${pcb_version}" - -wifi: - networks: - - ssid: !secret esphome_wifi_ssid - password: !secret esphome_wifi_password - -logger: - baud_rate: 0 - -api: - encryption: - key: !secret esphome_api_key - -ota: - password: !secret esphome_ota_password - -web_server: - port: 80 - -uart: - baud_rate: 115200 - rx_pin: D7 - -http_request: - useragent: esphome/device - timeout: 1s - -dsmr: - id: dsmr_instance - -sensor: - - platform: dsmr - energy_delivered_tariff1: - name: "$human_devicename - Energy Consumed Tariff 1" - state_class: total_increasing - energy_delivered_tariff2: - name: "$human_devicename - Energy Consumed Tariff 2" - state_class: total_increasing - energy_returned_tariff1: - name: "$human_devicename - Energy Produced Tariff 1" - state_class: total_increasing - energy_returned_tariff2: - name: "$human_devicename - Energy Produced Tariff 2" - state_class: total_increasing - power_delivered: - name: "$human_devicename - Power Consumed" - accuracy_decimals: 3 - power_returned: - name: "$human_devicename - Power Produced" - accuracy_decimals: 3 - electricity_failures: - name: "$human_devicename - Electricity Failures" - icon: mdi:alert - electricity_long_failures: - name: "$human_devicename - Long Electricity Failures" - icon: mdi:alert - voltage_l1: - name: "$human_devicename - Voltage Phase 1" - voltage_l2: - name: "$human_devicename - Voltage Phase 2" - voltage_l3: - name: "$human_devicename - Voltage Phase 3" - current_l1: - name: "$human_devicename - Current Phase 1" - id: currentl1 - current_l2: - name: "$human_devicename - Current Phase 2" - id: currentl2 - current_l3: - name: "$human_devicename - Current Phase 3" - id: currentl3 - power_delivered_l1: - name: "$human_devicename - Power Consumed Phase 1" - accuracy_decimals: 3 - power_delivered_l2: - name: "$human_devicename - Power Consumed Phase 2" - accuracy_decimals: 3 - power_delivered_l3: - name: "$human_devicename - Power Consumed Phase 3" - accuracy_decimals: 3 - power_returned_l1: - name: "$human_devicename - Power Produced Phase 1" - accuracy_decimals: 3 - id: returnedl1 - power_returned_l2: - name: "$human_devicename - Power Produced Phase 2" - accuracy_decimals: 3 - id: returnedl2 - power_returned_l3: - name: "$human_devicename - Power Produced Phase 3" - accuracy_decimals: 3 - id: returnedl3 - gas_delivered: - name: "$human_devicename - Gas Consumed" - state_class: total_increasing - gas_delivered_be: - name: "$human_devicename - Gas Consumed Belgium" - state_class: total_increasing - - platform: uptime - name: "$human_devicename - Uptime" - - platform: wifi_signal - name: "$human_devicename - Wi-Fi Signal" - update_interval: 60s - -text_sensor: - - platform: dsmr - identification: - name: "$human_devicename - Identification" - p1_version: - name: "$human_devicename - Version" - p1_version_be: - name: "$human_devicename - Version Belgium" - electricity_tariff: - name: "$human_devicename - Tarief" - - platform: wifi_info - ip_address: - name: "$human_devicename - IP Address" - ssid: - name: "$human_devicename - Wi-Fi SSID" - bssid: - name: "$human_devicename - Wi-Fi BSSID" - - platform: version - name: "$human_devicename - ESPHome Version" - hide_timestamp: true - -switch: - - platform: restart - name: "$human_devicename - Restart" - -interval: - - interval: 5sec - then: - - http_request.post: - url: !lambda |- - std::string url; - - float l1 = id(returnedl1).state > 0 ? - id(currentl1).state : id(currentl1).state; - float l2 = id(returnedl2).state > 0 ? - id(currentl2).state : id(currentl2).state; - float l3 = id(returnedl3).state > 0 ? - id(currentl3).state : id(currentl3).state; - - url.append("http://192.168.207.121/currents?L1="); - url.append(to_string(l1*10)); - url.append("&L2="); - url.append(to_string(l2*10)); - url.append("&L3="); - url.append(to_string(l3*10)); - return url.c_str(); diff --git a/SmartEVSE-3/integrations/home-assistant/mqtt-configuration.yaml b/SmartEVSE-3/integrations/home-assistant/mqtt-configuration.yaml deleted file mode 100644 index bbc3633..0000000 --- a/SmartEVSE-3/integrations/home-assistant/mqtt-configuration.yaml +++ /dev/null @@ -1,44 +0,0 @@ -###### Content below should go in automations.yaml -###### DO NOT COPY THESE INTO CONFIGURATION.YAML - create the automations in the UI then paste the code below in YAML mode - -alias: .EVSE set MQTT MainsMeter from HA -description: "" -trigger: - - platform: state - entity_id: - - sensor.meter_active_power_l1 - for: - hours: 0 - minutes: 0 - seconds: 1 -action: - - service: mqtt.publish - data_template: - topic: SmartEVSE-XXXXX/Set/MainsMeter - payload: |- - {{ - ((states('sensor.meter_active_power_l1')|float / states('sensor.on_grid_l1_voltage')|float ) * (-1) * 10 ) | round(0) - }}:{{ - ((states('sensor.meter_active_power_l2')|float / states('sensor.on_grid_l2_voltage')|float ) * (-1) * 10 ) | round(0) - }}:{{ - ((states('sensor.meter_active_power_l3')|float / states('sensor.on_grid_l3_voltage')|float ) * (-1) * 10 ) | round(0) - }} - -## -------------------------------------------------------------- - -alias: .EVSE set MQTT HomeBatteryCurrent from HA -trigger: - - platform: state - entity_id: - - sensor.battery_current - for: - hours: 0 - minutes: 0 - seconds: 1 -action: - - service: mqtt.publish - data_template: - topic: SmartEVSE-XXXXX/Set/HomeBatteryCurrent - payload: "{{ (states('sensor.battery_current') | float * 10 * (-1)) | int }}" - - diff --git a/SmartEVSE-3/integrations/home-assistant/p1_dsmr_to_SmartEVSE_API/automations.yaml b/SmartEVSE-3/integrations/home-assistant/p1_dsmr_to_SmartEVSE_API/automations.yaml deleted file mode 100644 index 7d0394d..0000000 --- a/SmartEVSE-3/integrations/home-assistant/p1_dsmr_to_SmartEVSE_API/automations.yaml +++ /dev/null @@ -1,11 +0,0 @@ -- id: 'xxxxxxxxxxxxxx' - alias: P1toSmartevse - description: '' - trigger: - - platform: time_pattern - seconds: /1 - condition: [] - action: - - service: shell_command.dsmrtosmartevse - data: {} - mode: single diff --git a/SmartEVSE-3/integrations/home-assistant/p1_dsmr_to_SmartEVSE_API/configuration.yaml b/SmartEVSE-3/integrations/home-assistant/p1_dsmr_to_SmartEVSE_API/configuration.yaml deleted file mode 100644 index a7b1937..0000000 --- a/SmartEVSE-3/integrations/home-assistant/p1_dsmr_to_SmartEVSE_API/configuration.yaml +++ /dev/null @@ -1,38 +0,0 @@ -#Data naar SMARTEVSe -shell_command: - dsmrtosmartevse: 'curl -X POST "http://192.168.x.x/currents?L1={{ states("sensor.nettocurrent_l1") | float*10 }}&L2={{ states("sensor.nettocurrent_l2") | float*10 }}&L3={{ states("sensor.nettocurrent_l3") | float*10 }}" -H "accept: application/json" -H "Content-Type: application/json" -d {}' - -template: - - sensor: - - name: "nettocurrent_l1" - unit_of_measurement: A - device_class: current - state: > - {% set solarl1 = states('sensor.electricity_meter_power_production_phase_l1') | float(0) %} - {% set currentl1 = states('sensor.electricity_meter_current_phase_l1') | float(0) %} - {{ currentl1 * -1 if solarl1 > 0 else currentl1 }} - availability: > - {{ states('sensor.electricity_meter_power_production_phase_l1') | is_number and - states('sensor.electricity_meter_current_phase_l1') | is_number }} - - sensor: - - name: "nettocurrent_l2" - unit_of_measurement: A - device_class: current - state: > - {% set solarl2 = states('sensor.electricity_meter_power_production_phase_l2') | float(0) %} - {% set currentl2 = states('sensor.electricity_meter_current_phase_l2') | float(0) %} - {{ currentl2 * -1 if solarl2 > 0 else currentl2 }} - availability: > - {{ states('sensor.electricity_meter_power_production_phase_l2') | is_number and - states('sensor.electricity_meter_current_phase_l2') | is_number }} - - sensor: - - name: "nettocurrent_l3" - unit_of_measurement: A - device_class: current - state: > - {% set solarl3 = states('sensor.electricity_meter_power_production_phase_l3') | float(0) %} - {% set currentl3 = states('sensor.electricity_meter_current_phase_l3') | float(0) %} - {{ currentl3 * -1 if solarl3 > 0 else currentl3 }} - availability: > - {{ states('sensor.electricity_meter_power_production_phase_l3') | is_number and - states('sensor.electricity_meter_current_phase_l3') | is_number }} diff --git a/SmartEVSE-3/pictures/SmartEVSEv3.png b/SmartEVSE-3/pictures/SmartEVSEv3.png deleted file mode 100644 index b4ee28a..0000000 Binary files a/SmartEVSE-3/pictures/SmartEVSEv3.png and /dev/null differ diff --git a/SmartEVSE-3/schematic/SmartEVSEv3.pdf b/SmartEVSE-3/schematic/SmartEVSEv3.pdf deleted file mode 100644 index 5476dfc..0000000 Binary files a/SmartEVSE-3/schematic/SmartEVSEv3.pdf and /dev/null differ diff --git a/SmartEVSE-3/sign_firmware/check_rsa_verify.sh b/SmartEVSE-3/sign_firmware/check_rsa_verify.sh deleted file mode 100755 index 0820539..0000000 --- a/SmartEVSE-3/sign_firmware/check_rsa_verify.sh +++ /dev/null @@ -1,9 +0,0 @@ -if [ "x$1" == "x" ]; then - echo "Usage: $0 path_to_signed_binary_firmware_file" - exit 1 -fi - -dd if=$1 of=extracted_firmware.sign bs=1 count=512 -dd if=$1 of=extracted_firmware.bin bs=1 skip=512 -openssl dgst -verify rsa_key.pub -keyform PEM -sha256 -signature extracted_firmware.sign extracted_firmware.bin -rm extracted_firmware.sign extracted_firmware.bin diff --git a/SmartEVSE-3/sign_firmware/rsa_key.pub b/SmartEVSE-3/sign_firmware/rsa_key.pub deleted file mode 100644 index dad4fb1..0000000 --- a/SmartEVSE-3/sign_firmware/rsa_key.pub +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtjEWhkfKPAUrtX1GueYq -JmDp4qSHBG6ndwikAHvteKgWQABDpwaemZdxh7xVCuEdjEkaecinNOZ0LpSCF3QO -qflnXkvpYVxjdTpKBxo7vP5QEa3I6keJfwpoMzGuT8XOK7id6FHJhtYEXcaufALi -mR/NXT11ikHLtluATymPdoSscMiwry0qX03yIek91lDypBNl5uvD2jxn9smlijfq -9j0lwtpLBWJPU8vsU0uzuj7Qq5pWZFKsjiNWfbvNJXuLsupOazf5sh0yeQzL1CBL -RUsBlYVoChTmSOyvi6kO5vW/6GLOafJF0FTdOQ+Gf3/IB6M1ErSxlqxQhHq0pb7Y -INl7+aFCmlRjyLlMjb8xdtuedlZKv8mLd37AyPAihrq9gV74xq6c7w2y+h9213p8 -jgcmo/HvOlGaXEIOVCUu102teOckXjTni2yhEtFISCaWuaIdb5P9e0uBIy1e+Bi6 -/7A3aut5MQP07DO99BFETXyFF6EixhTF8fpwVZ5vXeIDvKKEDUGuzAziUEGIZpic -UQ2fmTzIaTBbNlCMeTQFIpZCosM947aGKNBp672wdf996SRwg9E2VWzW2Z1UuwWV -BPVQkHb1Hsy7C9fg5JcLKB9zEfyUH0Tm9Iur1vsuA5++JNl2+T55192wqyF0R9sb -YtSTUJNSiSwqWt1m0FLOJD0CAwEAAQ== ------END PUBLIC KEY-----