From 07f7c02d79bf3ca6eff85615674e2f36bef9af33 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Fri, 4 Aug 2023 23:27:14 +0200 Subject: [PATCH] YaSolR --- .clang-format | 20 + .github/FUNDING.yml | 13 + .github/dependabot.yml | 20 + .github/workflows/build-oss.yml | 158 + .github/workflows/cpplint.yml | 49 + .github/workflows/dependabot.yml | 20 + .github/workflows/release-notes-config.json | 50 + .gitignore | 13 + .vscode/settings.json | 10 + LICENSE | 674 ++++ README.md | 61 + data/config.html | 377 +++ data/logo-icon.png | Bin 0 -> 2926 bytes data/logo.png | Bin 0 -> 18517 bytes include/YaSolR.h | 43 + include/YaSolRClass.h | 43 + include/YaSolRDeclarations.h | 82 + include/YaSolRMacros.h | 424 +++ include/YaSolRWebsite.h | 314 ++ lib/ESPDASH/LICENSE | 674 ++++ lib/ESPDASH/README.md | 85 + lib/ESPDASH/keywords.txt | 13 + lib/ESPDASH/library.json | 30 + lib/ESPDASH/library.properties | 9 + lib/ESPDASH/src/Card.cpp | 140 + lib/ESPDASH/src/Card.h | 70 + lib/ESPDASH/src/Chart.cpp | 144 + lib/ESPDASH/src/Chart.h | 82 + lib/ESPDASH/src/ESPDash.cpp | 515 +++ lib/ESPDASH/src/ESPDash.h | 135 + lib/ESPDASH/src/Statistic.cpp | 22 + lib/ESPDASH/src/Statistic.h | 30 + lib/ESPDASH/src/dash_webpage.cpp | 3008 +++++++++++++++++ lib/ESPDASH/src/dash_webpage.h | 8 + lib/ESPDASH/src/vector.h | 243 ++ lib/MycilaAppInfo/LICENSE | 10 + lib/MycilaAppInfo/MycilaAppInfo.cpp | 81 + lib/MycilaAppInfo/MycilaAppInfo.h | 34 + lib/MycilaAppInfo/library.properties | 5 + lib/MycilaButton/LICENSE | 10 + lib/MycilaButton/MycilaButton.cpp | 88 + lib/MycilaButton/MycilaButton.h | 47 + lib/MycilaButton/library.properties | 5 + lib/MycilaBuzzer/LICENSE | 10 + lib/MycilaBuzzer/MycilaBuzzer.cpp | 58 + lib/MycilaBuzzer/MycilaBuzzer.h | 29 + lib/MycilaBuzzer/library.properties | 5 + lib/MycilaDimmer/LICENSE | 10 + lib/MycilaDimmer/MycilaDimmer.cpp | 73 + lib/MycilaDimmer/MycilaDimmer.h | 58 + lib/MycilaDimmer/library.properties | 5 + lib/MycilaGrid/LICENSE | 10 + lib/MycilaGrid/MycilaGrid.cpp | 68 + lib/MycilaGrid/MycilaGrid.h | 35 + lib/MycilaGrid/library.properties | 5 + lib/MycilaHTTPd/LICENSE | 10 + lib/MycilaHTTPd/MycilaHTTPd.cpp | 137 + lib/MycilaHTTPd/MycilaHTTPd.h | 47 + lib/MycilaHTTPd/library.properties | 5 + lib/MycilaLights/LICENSE | 10 + lib/MycilaLights/MycilaLights.cpp | 78 + lib/MycilaLights/MycilaLights.h | 62 + lib/MycilaLights/library.properties | 5 + lib/MycilaRelayManager/LICENSE | 10 + lib/MycilaRelayManager/MycilaRelayManager.cpp | 78 + lib/MycilaRelayManager/MycilaRelayManager.h | 34 + lib/MycilaRelayManager/library.properties | 5 + lib/MycilaRouter/LICENSE | 10 + lib/MycilaRouter/MycilaRouter.cpp | 102 + lib/MycilaRouter/MycilaRouter.h | 32 + lib/MycilaRouter/library.properties | 5 + lib/MycilaRouterOutput/LICENSE | 10 + lib/MycilaRouterOutput/MycilaRouterOutput.cpp | 232 ++ lib/MycilaRouterOutput/MycilaRouterOutput.h | 85 + lib/MycilaRouterOutput/library.properties | 5 + lib/MycilaSystem/LICENSE | 10 + lib/MycilaSystem/MycilaSystem.cpp | 103 + lib/MycilaSystem/MycilaSystem.h | 40 + lib/MycilaSystem/library.properties | 5 + lib/MycilaTaskManager/LICENSE | 10 + lib/MycilaTaskManager/MycilaTaskManager.h | 200 ++ lib/MycilaTaskManager/library.properties | 5 + lib/MycilaTemperatureSensor/LICENSE | 10 + .../MycilaTemperatureSensor.cpp | 80 + .../MycilaTemperatureSensor.h | 53 + .../library.properties | 5 + lib/MycilaTime/LICENSE | 10 + lib/MycilaTime/MycilaTime.h | 74 + lib/MycilaTime/library.properties | 5 + lib/MycilaZeroCrossDetection/LICENSE | 10 + .../MycilaZeroCrossDetection.cpp | 59 + .../MycilaZeroCrossDetection.h | 29 + .../library.properties | 5 + partitions.csv | 6 + pio/data.py | 10 + pio/factory.py | 87 + pio/version.py | 40 + platformio.ini | 906 +++++ platformio_override.ini.template | 22 + src/YaSolRClass.cpp | 23 + src/YaSolR_API_MQTT.cpp | 87 + src/YaSolR_API_REST.cpp | 214 ++ src/YaSolR_Config.cpp | 245 ++ src/YaSolR_Display.cpp | 86 + src/YaSolR_EventHandlers.cpp | 172 + src/YaSolR_HADiscovery.cpp | 174 + src/YaSolR_MQTT.cpp | 131 + src/YaSolR_Tasks.cpp | 342 ++ src/YaSolR_Website_Init.cpp | 528 +++ src/YaSolR_Website_Update.cpp | 339 ++ src/main.cpp | 208 ++ test/config.txt | 10 + test/init.sh | 15 + test/test-all.sh | 23 + test/test-api-app.sh | 22 + test/test-api-config.sh | 99 + test/test-api-grid.sh | 45 + test/test-api-mqtt.sh | 24 + test/test-api-network.sh | 24 + test/test-api-ntp.sh | 28 + test/test-api-relays.sh | 52 + test/test-api-router.sh | 77 + test/test-api-system.sh | 71 + test/test-api.sh | 19 + test/utils/api.sh | 70 + 125 files changed, 14099 insertions(+) create mode 100644 .clang-format create mode 100644 .github/FUNDING.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build-oss.yml create mode 100644 .github/workflows/cpplint.yml create mode 100644 .github/workflows/dependabot.yml create mode 100644 .github/workflows/release-notes-config.json create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 data/config.html create mode 100644 data/logo-icon.png create mode 100644 data/logo.png create mode 100644 include/YaSolR.h create mode 100644 include/YaSolRClass.h create mode 100644 include/YaSolRDeclarations.h create mode 100644 include/YaSolRMacros.h create mode 100644 include/YaSolRWebsite.h create mode 100644 lib/ESPDASH/LICENSE create mode 100644 lib/ESPDASH/README.md create mode 100644 lib/ESPDASH/keywords.txt create mode 100644 lib/ESPDASH/library.json create mode 100644 lib/ESPDASH/library.properties create mode 100644 lib/ESPDASH/src/Card.cpp create mode 100644 lib/ESPDASH/src/Card.h create mode 100644 lib/ESPDASH/src/Chart.cpp create mode 100644 lib/ESPDASH/src/Chart.h create mode 100644 lib/ESPDASH/src/ESPDash.cpp create mode 100644 lib/ESPDASH/src/ESPDash.h create mode 100644 lib/ESPDASH/src/Statistic.cpp create mode 100644 lib/ESPDASH/src/Statistic.h create mode 100644 lib/ESPDASH/src/dash_webpage.cpp create mode 100644 lib/ESPDASH/src/dash_webpage.h create mode 100644 lib/ESPDASH/src/vector.h create mode 100644 lib/MycilaAppInfo/LICENSE create mode 100644 lib/MycilaAppInfo/MycilaAppInfo.cpp create mode 100644 lib/MycilaAppInfo/MycilaAppInfo.h create mode 100644 lib/MycilaAppInfo/library.properties create mode 100644 lib/MycilaButton/LICENSE create mode 100644 lib/MycilaButton/MycilaButton.cpp create mode 100644 lib/MycilaButton/MycilaButton.h create mode 100644 lib/MycilaButton/library.properties create mode 100644 lib/MycilaBuzzer/LICENSE create mode 100644 lib/MycilaBuzzer/MycilaBuzzer.cpp create mode 100644 lib/MycilaBuzzer/MycilaBuzzer.h create mode 100644 lib/MycilaBuzzer/library.properties create mode 100644 lib/MycilaDimmer/LICENSE create mode 100644 lib/MycilaDimmer/MycilaDimmer.cpp create mode 100644 lib/MycilaDimmer/MycilaDimmer.h create mode 100644 lib/MycilaDimmer/library.properties create mode 100644 lib/MycilaGrid/LICENSE create mode 100644 lib/MycilaGrid/MycilaGrid.cpp create mode 100644 lib/MycilaGrid/MycilaGrid.h create mode 100644 lib/MycilaGrid/library.properties create mode 100644 lib/MycilaHTTPd/LICENSE create mode 100644 lib/MycilaHTTPd/MycilaHTTPd.cpp create mode 100644 lib/MycilaHTTPd/MycilaHTTPd.h create mode 100644 lib/MycilaHTTPd/library.properties create mode 100644 lib/MycilaLights/LICENSE create mode 100644 lib/MycilaLights/MycilaLights.cpp create mode 100644 lib/MycilaLights/MycilaLights.h create mode 100644 lib/MycilaLights/library.properties create mode 100644 lib/MycilaRelayManager/LICENSE create mode 100644 lib/MycilaRelayManager/MycilaRelayManager.cpp create mode 100644 lib/MycilaRelayManager/MycilaRelayManager.h create mode 100644 lib/MycilaRelayManager/library.properties create mode 100644 lib/MycilaRouter/LICENSE create mode 100644 lib/MycilaRouter/MycilaRouter.cpp create mode 100644 lib/MycilaRouter/MycilaRouter.h create mode 100644 lib/MycilaRouter/library.properties create mode 100644 lib/MycilaRouterOutput/LICENSE create mode 100644 lib/MycilaRouterOutput/MycilaRouterOutput.cpp create mode 100644 lib/MycilaRouterOutput/MycilaRouterOutput.h create mode 100644 lib/MycilaRouterOutput/library.properties create mode 100644 lib/MycilaSystem/LICENSE create mode 100644 lib/MycilaSystem/MycilaSystem.cpp create mode 100644 lib/MycilaSystem/MycilaSystem.h create mode 100644 lib/MycilaSystem/library.properties create mode 100644 lib/MycilaTaskManager/LICENSE create mode 100644 lib/MycilaTaskManager/MycilaTaskManager.h create mode 100644 lib/MycilaTaskManager/library.properties create mode 100644 lib/MycilaTemperatureSensor/LICENSE create mode 100644 lib/MycilaTemperatureSensor/MycilaTemperatureSensor.cpp create mode 100644 lib/MycilaTemperatureSensor/MycilaTemperatureSensor.h create mode 100644 lib/MycilaTemperatureSensor/library.properties create mode 100644 lib/MycilaTime/LICENSE create mode 100644 lib/MycilaTime/MycilaTime.h create mode 100644 lib/MycilaTime/library.properties create mode 100644 lib/MycilaZeroCrossDetection/LICENSE create mode 100644 lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.cpp create mode 100644 lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.h create mode 100644 lib/MycilaZeroCrossDetection/library.properties create mode 100644 partitions.csv create mode 100644 pio/data.py create mode 100644 pio/factory.py create mode 100644 pio/version.py create mode 100644 platformio.ini create mode 100644 platformio_override.ini.template create mode 100644 src/YaSolRClass.cpp create mode 100644 src/YaSolR_API_MQTT.cpp create mode 100644 src/YaSolR_API_REST.cpp create mode 100644 src/YaSolR_Config.cpp create mode 100644 src/YaSolR_Display.cpp create mode 100644 src/YaSolR_EventHandlers.cpp create mode 100644 src/YaSolR_HADiscovery.cpp create mode 100644 src/YaSolR_MQTT.cpp create mode 100644 src/YaSolR_Tasks.cpp create mode 100644 src/YaSolR_Website_Init.cpp create mode 100644 src/YaSolR_Website_Update.cpp create mode 100644 src/main.cpp create mode 100644 test/config.txt create mode 100755 test/init.sh create mode 100755 test/test-all.sh create mode 100755 test/test-api-app.sh create mode 100755 test/test-api-config.sh create mode 100755 test/test-api-grid.sh create mode 100755 test/test-api-mqtt.sh create mode 100755 test/test-api-network.sh create mode 100755 test/test-api-ntp.sh create mode 100755 test/test-api-relays.sh create mode 100755 test/test-api-router.sh create mode 100755 test/test-api-system.sh create mode 100755 test/test-api.sh create mode 100755 test/utils/api.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a44b066 --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +BinPackArguments: false +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Inline +ColumnLimit: 0 +ContinuationIndentWidth: 2 +FixNamespaceComments: false +IndentAccessModifiers: true +IndentCaseLabels: true +IndentWidth: 2 +NamespaceIndentation: All +PointerAlignment: Left +ReferenceAlignment: Left +TabWidth: 2 +UseTab: Never \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e411f9b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: mathieucarbou +# patreon: # Replace with a single Patreon username +# open_collective: # Replace with a single Open Collective username +# ko_fi: # Replace with a single Ko-fi username +# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +# liberapay: # Replace with a single Liberapay username +# issuehunt: # Replace with a single IssueHunt username +# otechie: # Replace with a single Otechie username +# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: https://paypal.me/mathieucarboufr diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bd654c2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gitsubmodule" + directory: "/" + schedule: + interval: "daily" + reviewers: + - "mathieucarbou" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + reviewers: + - "mathieucarbou" diff --git a/.github/workflows/build-oss.yml b/.github/workflows/build-oss.yml new file mode 100644 index 0000000..589c44b --- /dev/null +++ b/.github/workflows/build-oss.yml @@ -0,0 +1,158 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Build Open-Source Firmware + +on: + workflow_dispatch: + schedule: + - cron: "0 5 * * 1" + push: + tags: ["v*"] + branches: ["*"] + paths-ignore: ["docs/**", "**/*.md", "site/**", "**/portal/**", "tools/**", "test/**"] + pull_request: + paths-ignore: ["docs/**", "**/*.md", "site/**", "**/portal/**", "tools/**", "test/**"] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + pio_envs: + if: ${{ github.repository != 'mathieucarbou/YaSolR' }} + name: Get PlatformIO Envs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cache + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-pio_envs + path: ~/.cache/pip + + - name: Pyhton + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + + - name: Get Envs + id: envs + run: | + rm -f platformio_override.ini + echo "pio_name=$(pio project config --json-output | jq -cr '.[0][1][0][1]')" >> $GITHUB_OUTPUT + echo "pio_default_envs=$(pio project config --json-output | jq -cr '[ .[][0] | select(startswith("env:oss-")) | .[4:] ]')" >> $GITHUB_OUTPUT + + outputs: + pio_name: ${{ steps.envs.outputs.pio_name }} + pio_default_envs: ${{ steps.envs.outputs.pio_default_envs }} + + YaSolR: + name: YaSolR + runs-on: ubuntu-latest + needs: [pio_envs] + strategy: + matrix: + environment: ${{ fromJSON(needs.pio_envs.outputs.pio_default_envs) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Get Tags + run: git fetch --force --tags origin + + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-pio + path: | + ~/.cache/pip + ~/.platformio + + - name: Cache YaSolR + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-YaSolR + path: .pio/libdeps + + - name: Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Build + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + pio run -e ${{ matrix.environment }} + ref="${{ github.ref_name }}" + ref="${ref//\//_}" + mv .pio/build/${{ matrix.environment }}/firmware.bin ./${{ needs.pio_envs.outputs.pio_name }}-$ref-${{ matrix.environment }}.bin + mv .pio/build/${{ matrix.environment }}/firmware.factory.bin ./${{ needs.pio_envs.outputs.pio_name }}-$ref-${{ matrix.environment }}.factory.bin + + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.environment }} + if-no-files-found: error + path: | + *.bin + + release: + name: Release + if: ${{ github.repository_owner == 'mathieucarbou' && github.event_name != 'pull_request' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) }} + runs-on: ubuntu-latest + needs: [YaSolR] + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: Move + run: | + ls -R artifacts + find artifacts -name '*.bin' -exec mv {} artifacts/ \; + + - name: Pre-Release + if: ${{ github.ref == 'refs/heads/main' }} + uses: mathieucarbou/marvinpinto-action-automatic-releases@latest + with: + repo_token: "${{ secrets.ACCESS_TOKEN }}" + automatic_release_tag: latest + prerelease: true + title: Pre-release Builds + files: | + artifacts/*.bin + + - name: Changelog + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + id: github_release + uses: mikepenz/release-changelog-builder-action@v4 + with: + failOnError: true + commitMode: true + configuration: ".github/workflows/release-notes-config.json" + env: + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} + + - name: Release + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + uses: softprops/action-gh-release@v1 + with: + body: ${{steps.github_release.outputs.changelog}} + draft: false + files: artifacts/*.bin + env: + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/cpplint.yml b/.github/workflows/cpplint.yml new file mode 100644 index 0000000..8d1659a --- /dev/null +++ b/.github/workflows/cpplint.yml @@ -0,0 +1,49 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Cpplint + +on: + workflow_dispatch: + schedule: + - cron: "0 5 * * 1" + push: + tags: ["v*"] + branches: ["*"] + paths: ["**/*.h", "**/*.cpp"] + pull_request: + paths: ["**/*.h", "**/*.cpp"] + +jobs: + cpplint: + name: cpplint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cache + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-cpplint + path: ~/.cache/pip + + - name: Pyhton + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: cpplint + run: | + python -m pip install --upgrade pip + pip install --upgrade cpplint + cpplint \ + --repository=. \ + --recursive \ + --filter=-whitespace/line_length,-whitespace/braces,-whitespace/comments,-runtime/indentation_namespace,-whitespace/indent,-readability/braces,-whitespace/newline,-readability/todo,-build/c++11 \ + --exclude=lib/ElegantOTAPro \ + --exclude=lib/ESPDASH \ + --exclude=lib/ESPDASHPro \ + --exclude=lib/WebSerialLite \ + lib \ + include \ + src diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 0000000..ab015ac --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,20 @@ +name: PlatformIO Dependabot + +on: + workflow_dispatch: + schedule: + # Runs every day at 01:00 + - cron: "0 1 * * *" + +jobs: + dependabot: + runs-on: ubuntu-latest + name: PlatformIO Dependabot + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: run PlatformIO Dependabot + uses: peterus/platformio_dependabot@v1 + with: + assignee: mathieucarbou + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-notes-config.json b/.github/workflows/release-notes-config.json new file mode 100644 index 0000000..5ccab1c --- /dev/null +++ b/.github/workflows/release-notes-config.json @@ -0,0 +1,50 @@ +{ + "categories": [ + { + "title": "## ⚡ Breaking Changes", + "labels": [ + "breaking change" + ] + }, + { + "title": "## 🚀 Features", + "labels": [ + "feature" + ] + }, + { + "title": "## 🐛 Fixes", + "labels": [ + "fix" + ] + }, + { + "title": "## 📚 Documentation", + "labels": [ + "doc" + ] + }, + { + "title": "## 🛠 Under the hood", + "labels": [] + } + ], + "template": "${{CHANGELOG}}", + "pr_template": "- [${{TITLE}}](https://github.com/mathieucarbou/${{REPO}}/commit/${{MERGE_SHA}})", + "empty_template": "- no changes", + "label_extractor": [ + { + "pattern": "(.): (.+)", + "target": "$1", + "on_property": "title" + }, + { + "pattern": "(.) (.+)", + "target": "$1", + "on_property": "title" + } + ], + "tag_resolver": { + "method": "semver" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2966dd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +.lh +/.pio +/.vscode/* +!/.vscode/settings.json +/logs +/platformio_override.ini +/platformio_override.ini +/platformio-device-monitor*.log +/site/_site +/site/.jekyll-cache +/site/.sass-cache +/data/*.gz diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a3c9c1f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: LLVM, UseTab: Never, IndentWidth: 2, TabWidth: 2, AllowShortIfStatementsOnASingleLine: false, IndentCaseLabels: true, ColumnLimit: 0, AccessModifierOffset: -2, NamespaceIndentation: All, FixNamespaceComments: false, IndentAccessModifiers: true, PointerAlignment: Left, ReferenceAlignment: Left, ContinuationIndentWidth: 2, AllowShortLambdasOnASingleLine: Inline, BinPackArguments: false, AllowAllArgumentsOnNextLine: false, AllowAllParametersOfDeclarationOnNextLine: false}", + "files.exclude": { + "**/.lh": true + }, + "cSpell.words": [ + "YASOLR" + ], + "scm.defaultViewMode": "tree" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ed942a --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +[![YaS☀️lR (Yet another Solar Router)](https://yasolr.carbou.me/assets/img/logo.png)](https://yasolr.carbou.me/) + +[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](http://perso.crans.org/besson/LICENSE.html) +[![Download](https://img.shields.io/badge/Download-bin-green.svg)](https://yasolr.carbou.me/download) +[![Doc](https://img.shields.io/badge/Doc-html-green.svg)](https://yasolr.carbou.me/manual) + +[![YaS☀️lR OSS](https://img.shields.io/badge/YaSolR%20OSS-sources-green.svg)](https://github.com/mathieucarbou/YaSolR-OSS/) [![YaS☀️lR OSS Build](https://github.com/mathieucarbou/YaSolR-OSS/actions/workflows/build-oss.yml/badge.svg?branch=main)](https://github.com/mathieucarbou/YaSolR-OSS/actions/workflows/build-oss.yml) + +# Documentation + +Please look at the website and manual for more information about how to flash. + +👉 Website: [https://yasolr.carbou.me/](https://yasolr.carbou.me/) + +# Developer guide + +## Project structure + +- `.github`: CI/CD workflows +- `data`: Build components added to the firmware +- `include`: Firmware include code +- `lib`: Firmware libraries +- `pio`: pio scripts +- `src`: Firmware source code +- `test`: Firmware tests +- `tools`: Some random tools +- `partitions.csv`: ESP32 custom partition table +- `platformio_override.ini.template` (to copy to `platformio_override.ini`): PlatformIO configuration override +- `platformio.ini`: PlatformIO configuration + +## Building the firmware + +1. Configure `platformio_override.ini` to your needs (you can copy the template file `platformio_override.ini.template` to `platformio_override.ini` for that) + +2. Build the file system and the firmware: + +```bash +./build.sh +``` + +## Flashing + +First time, flash the entire firmware which includes the partition table and all partitions: + +```bash +esptool.py --port /dev/ttyUSB0 \ + --chip esp32 \ + --before default_reset \ + --after hard_reset \ + write_flash \ + --flash_mode dout \ + --flash_freq 40m \ + --flash_size detect \ + 0x0 YaSolR-VERSION-MODEL-CHIP.factory.bin +``` + +Next time, just upload the partition you modify + +```bash +./upload.sh +``` diff --git a/data/config.html b/data/config.html new file mode 100644 index 0000000..9969a21 --- /dev/null +++ b/data/config.html @@ -0,0 +1,377 @@ + + + + + YaSolR Config Debug + + + +

Configuration Debug

+

+ This page can be used to troubleshoot configuration issues and update the + configuration. Use with care! +

+ + + + + + + + + + + + + + +
+
+ +
+
+
+ +
+
+
+ + +
+
+
+ +
+
+ + + + + + + +
NameValueDescription
+ + + + + + + \ No newline at end of file diff --git a/data/logo-icon.png b/data/logo-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c0462a5214edc4b0186e34e22301a743dbbb48ab GIT binary patch literal 2926 zcmZ`*c|6qVAOE^HgB&U3n2_6yt1&aiNR7L3470L4wtA_)M1 zw38zS$G5utsrQ{Y|9xWdFD~DR9^zf#L_)WN)VsEEX`}``-bgLh^tJ-xK0LfRG9xsK)mIw9xLa zzI{>{Ao>M&hERjdcefq(_p$Fo+4nL=!Cu27^(N!68T-#{LVPzp~a0Ww97YC^RZ63KC@w zp+_Epnj#PgsEHZW%*>ddVa$xCu}ExV8dK|Yk^k1gkeNY|VGLFnody=vB?Z!tvaB^V z1wvoP=RR3sA>Wi}%rD#GZxAYoKusYg(0`+m*4PIP>6D{_ zw$PSls7;0c0l)S4nc)3zgstE|32u>LWd0}FR51M_uoc^qFX$8L9>yk9yfI-^GL0$d z$`lSqLH{-KPv${7g&yh2AO(@pe4$Os7U-YZn>s$V%K%&bDW9#L;?X7n>Wyi20I_mHniDUaMMs(cs2KP8sIeI$S zEx0`~9`wmIpyk<7ORD96%ulM;!>ZvmOMO6iJLkCWoj8y&E>8RJhTSjrkgT|^MpT2! zpPdZEv;q|q!p?Sad=lEyE?F^SdMA^jjvaej6Sf}leDqSw@&vPe?J~2kEl3=6+f^-e zU~Fus$}KmlEFx}lw8cCyW-W^*2T1ix82uR(ELv0(e#%_DvOq2?rt-PEjKq-hg5*){ z`rD`X79;}Yi3f_fFD8ax(Ur&W*DGGhg!n!Hc}|qf54q+BzIda{wlQ12*KpoHe7jwX z@73Ae@Pw$VU4_q)kdB{X+s3KiWcP_f< z7^sH`{3v%xrnillps<|c2Vr7Wt3%(dYond!21`i{&zUr z;=eO}0J<$V>8#O%?QwX0V{uMOK7F%7Gm;cH2# zS$oq}4M9B5iTmvxPExl1X+i1|F`=y++BaT>7;t{3SAXJ;MNR2jKaIk|wFvgQ&o#AV z(QUEzk+X?$!=|ze!=i^Q;%659Y$oR?tEzDqldmq_o$kdLHYEKXTw)ZXgg&i#@2O-y zWs*C7p`G-Hl60MzT<3|!R@nTdMdF8`PaU!qt*oVfTf;&h>yo_E1YXAn9!Q}|`5=L1 zF&mj;>SZ+0(BAwcG*!Ja_5w%4_mnk)B)Zg9yr0Xn4+0flH}Rk7>_6GRufM#!$LmAv z2mG~B=1LJqJepDL{s5sMqoOb;opw0)B%#yY)+47P7%o%UNge6$j1}V!e_XG0NK$p& z7NRpTR&=bQ5Snm8Uj z=Sw?wHL2?)lnu|U&ZlMkuwJVbSL#F^Q|>bIKkt{DA8Reken$)HG(+6qhgWqSCtR)a z9Oh(TpDB;zs$F(d=sQ%20rE|g`o`*ho)W=|NhZ2bBrfNu@`z zDqeKweQyBO8jkNoVft|r&rcZeX7@_uXo{i>oz)rMH-mFxmma6L54@C5AU1|@0fM!b zjxmJuWT*dPaZ)DX0R!&XpCrPw7;A5obg9zqhi;ErtSsQiU#V;l zZzRHi!92N9ZoH>&U6^LSEV$)0|j!7QO%b$vg zd;e#8Y(zB{+Bn`UPM!#f+g_JB^@;0wg#MGbxeqzzW-wET*s%0n!O|{CSNscMbg6voBF>?2D*b|CyAk|!S>pE-=EWt2n7+~i zPiefz4mTbtTGn+^P36~Td8D-bqm5T5rO&k+aNw*~#?zBUcrvV(d ziu%Wjx`-f|=?e!KMguwzQ>aX%=CMIMdSW4T1GeX#_9+B$w&dLj{pnH&jf$)LFuAZ4 z9s#MB>c1%!)vH9ilkyw&{xuWOnfTvE%#0kod+*nUNJ5^c9zclf<=wq*SbQN&UP+jk ztkIziU+A=dR=a?0Nq%%bTkYc8{kk_iYN|B~u0Q>I)lH(qO)R^?eUvdhi|>TiYiI!dq|1NI?%bmCJFJ{E^u*?__$Su5|O_U62G(~Yx2lKyyzPgZ_yxVPCr z^-hIKM#ig^?Dc@Rx@xP}49a|~YXYZ3O&YQhjUTDAfoC_)zd{<`SKniQ`n<0Ml2#s;6CfNu5JImh`@v_o}*dDyws<3d56TR}jw-YBgQDWz-+M)5;ri0&gb>->+f1 zaBF*zXj-+b8{;FRM@UP8r6lKU9gC|>S&?X5shBgmYkV{_^S~K$rZcM_K7SkZ>~Kf4 zF{cZvSUZxqQPJ3G>!lJ~`)MXSK_gRXqs7uVDo{~m4T@32!eoyc!vuenPIj)C+5-VU F{|6cq%IN?A literal 0 HcmV?d00001 diff --git a/data/logo.png b/data/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f48bbc68052ec42bada05785a0383cad503f3e GIT binary patch literal 18517 zcmZ^}19T=$w+0%U6FU=5Y`w9QiEVpg+qP|+6DMzM+qRR5ojc$EpL@=K&pNkyRoCvl z_p_f}Shc!qb-28&I3gS_90&*qqNId~;@`IOZ{>l3`g`AanQ{EvK$r>22!epr$HKoG zK>mFuHj+@30Ri!#00Hp}0s(pbtMWSm0dZyo0XfqL0Rg0efMD5WwJY%aHMp6mOPb2a zfKdOHVL%{2zkz`Nl|cVqAfUJ)|3v#Mf$)Li{kJR@Y5@ZAUwYZU?LS50Z~e#b-z|6^ z=>J5?1N(39&OGq{mjB@f*_w>{hYi+FLgQ~g;L-l6pdcAp*dQQaNfyfLPUzoNB?lL4`twUvz{z>SyWUmk$J@;_<@65@ZkI9c+NsLRL` z3)?!F5VO-W(le6q!4VS^^Eeor0u)8W{tN#1iI>FO$;l4Dz~JiYO7F@-Z|h*jz{JJH z#lXnSz|2hd*MrW{-Nwnljn2lA^xr}L-#8*Bjz$g^c1{+yHpKtLH88YwcH$)=`G@Gg z%fItek;5GW=sAYBQT|VU{|2c4Ux5Eq{%?SsgN4c8A^8^r(|-~Cr|v)e|H%^|Z{cQQ zr7mJ&ZDQm2PgX3ff7ALuE&m%SY-?rfpk!xYWWx8C=wFckQ2lT3fAMJi4-XR~*MIW- zhvmPJJPiNL+kedCzZc2Bw0~C#9~=+Ef3G+`xQr%ZF%S>|5J?e1WjD}EZ)h(S(Z{ci zTTKrMm>}67LIDCW)R@GLWDKB?Y$sSf%$D@gK7PG0Wbeg*D{%dq|NXW+E zRmI5BhD27ux^lXb7sO6B2k<=`^l+iisIZAr2N^WT$kdekfD@jL+AvWt!RD z7F{{t5G?j-*?1*I&0jI-DULT)0DHrjImL>jt^7gA5N36F6Lbuw6y~;(4=u_KA&Ji4 zX2=n0qLeU_XpUKviyE;Ex@p$ZR$3L9!D{f($P{I(t6bW+pG)VG&z3YwCLHiYdrN6# zQa=TTojcs&G`}HvCqDZFlEF2MjNoQZT z2rXj0hP&7UmpzO_!-It&Xh>lo^4%;`|C)g{W^HIb-3lz4IpDIi62}8i`KY)B7M?9B z5;L7?&o$c;h6`ib0P2WSsk@)Y;S@yCJ1F-D3PWSf=V^EhiyB4+JD#ONbwa#Ak%;$D zsf(72_z2pc_<%j3TvQQXyQp1-a@Su9cgr2AeyjW?Aw&k2&cgPldaogDBu%;ZjX&y8 zWabzAnEgs+JCE~tv~^JY*WtLihsKfS(j32;ns){p>!rVa z+Br9Pz20*<2T+V`3PqI#5&xEeHrqpeV~SFv2IVH}`B(fXvV zO;a`vno=z;MfRCwzxpb89HeIwN-JO_&$|pXgTa-uYCBFUS>$39)<{;(*;u^UyF1TA zi5j6RMp%$#F;nD6o_fZ&2dZ67j&96Y3Y?f!%?-|^`9mtoi6QHC*B?D&xUO^v8yW;z zW-n|#g)Am;7V#Uit6J8I>$};p9(*n+S-g#+`VZiROe23cAZc!f> z?yF;vJ$rt;HR$U778!geX$VA|qMM3f4{bn`*P?~Hf#NGmXZTTqqT#UAv7D}$s$G7U z@Y(DyJ|~3iZBll+)DPj2Umebgg?pa>)X5ObeAP_--HFL*(Z_v`48tJpW&Q^ita7={ z9@QLj$f2`dDk7PG6H`GA-PF{DHAC8Pl|PJ)e);fYGP^6gpCUGEV#|<5iQFY;Hx2HJ zjcNGL>%={3zL-J#n{oA?>i11|y+1-&Y1D85+K$c5S4-2DUn2EfaV5{SYLi6KdJj3V z#$<~c&GqKGlM$es2x^iP{%vO)5x;ZJe`i!MH|RfCVT*7;TK}}wkxopLQWEV*qyaC2 z%XdeRC}=vjy7DY({e-Qs*}YL-XhCVyL>g#BT#AFDqK`A4?%H6!B`Igj@8F6GIk-i8 zT)?xH>p&;iQ?K))OTCxsX0xa$Nou3PueT9(G;moD>(JCAG$8}biKajrh5qml&kx}* z4d%SozSA8FuiSEo70QlX%L`t?wfIg9Z=@~c$5gs@+d{j3I+ZLOZ7T5tOoE{ANtiSv zP04_FZ$KR9?lgyvBqtjf$6;ui@a(njA_mLI%Gl9ML=QzYi$Wc_=<;|~z!hw=b~Fmr z2W#>e4j)5NKi25>FLHW9nm{K;a5V>#nPv}Ty?I72>@OwR% z0sG8R5dAP_V9e!b50!Ov0N5RvQ_UMPeyRycwiIr-5;IGkW=#~09J)tCF#bi#3VH&d z8G@#_6cm-?q5?XkVCJbgr5~IO6GW6s|u%1LU#r6wX1+PYL_m& zGgRy)E%y;D(GwXi12sb3~3KLW{uaY$ry zYHHIP$8JD0%sfkgqU?A-V8v0LNw?+l;9-0^we1%>(+K!=DH zhF4t1dB&kCkS0OP=^iRxuyb_dPA^WDXs$cg*$S-_?_vpPESV$ z;Q^o3S{zahm18E*t5JMoY;6)sCRme=RSWoTrm&|gn+QVB81}kHek7!yE1b9nC@(n8q@68);utPgkqF!0H2-nL#kD7Twc9Cw#mCkW|Q^f`- zJb497(i>!>=q9Y#v|@o#kyf|w2;ap5nI_B`MfXCVD1JodrAOpu`UP~+LE$Eh9yHUG zro~3(*19gJC^MVJum&|?M-J{TbgewO_li|>u1h3X-4jS5#bhNcQyr#kjMsYB?QoAt zL0;6N+(jZUWMd*Iwlb~JW>?QU1fg=VEyx;s7j&}fS)itW-AIM~TJ-CcWOuhHo1gV0 z*L1)Qu^%kc;PWryD4TIy6C0Ukl+rX)JPwNq@=1jAn2esn=->uAnNf~gHW<2s3V;aj zYp`=}Rv9(kdn`ONWzHxxo0yqNadG*0xUMU1RqhoIbSHv=c#kiSJGJM}_EJY>14{X1 z?B!z*P><6g&fS4(poo|=7I;A0TM0BLon!0UIlTPon@GO;7<05C!>k(O5zo0_b=oiu z3Mnx;PBmBGDr;VBV!54;nPkWd5tP|*H?hro+8a?t%7mR4AJ&|$3yX9|Yz${9C{15{JlKhA7bg(pB5VLwXSigbrh7Y?LJSXmM!mPCH5lqvWDDp(Wxy} ze7&1@(ros9eO*2upUg^K*4~-kEG5%P&(69JKBAA|(`aVBf-7N;PeTCg-4n@hiK2qe zt&o4_qhokNh1WyknIW3?9!h-08XdM}6IQwl*5{Nw!uJ1epa z(v#jA(ky_nld?f75p!$?ZW8Q#+8b=VU(pBjOZS3UIMqMt^yKb}Aq(N?TaM0FCym{0 zTbcLUYgdATR|Tdmuf8`y1i4&lrg4>cM|p9DU^mD^-~zsHMxT_IPPZ;oZfCVDSc;lA zFP|loP^1ZtP>Mv-njj(6`D9DWIpxsE2*I1Bz{z06@!+6Mfw8xfED_WoMi1}+hhhwp zi(hc)0I06vzfvbRDNRcG-Zry$bz+m+h)!3x2-I|gDTIGmvsx@p zMk$p<{q*_+xXU~2(5^Wrq}3ZxSJ+Jaear9RfcYlDVDveTlj;;{$}&?AMbU^hy_)@t zvN6Q_mze5e`A=2E%lk#^;bels>#237XH)jb+tciDCd+{*B8%8^xbdp;Wx}D?;ak1z zQ$bLL1^vv_K@88PmLs5=pGPx(vriURi=NpFyQGr7p!wJ^NI9%&IC-Oz`5d2?tr8L1 zn?H25l1avH4Nni@)vRp2(BRP$M$s-XXf7ZSQ7BtU=GqqfOHS*RSh}p4HYjOYH!X0t zePr-8D&iEMu2a^CUDB0)l8O|psgBklKw)~NVe`t2s>aUrJ5#z`f`WG4PO{s5gzH4H zV^zQSeFDY`)FNHzB4t6ph9@>}c2ClNghLY=2Aq}Q#ZB1fkj6Bfg8;>G)qb6I?`0De zRZi3SfLmJzY*2OW8c*g^E_J5KX^+LSi=oR|U6IQ|E^nE;T9-TXW@G?EIgvE+x~q&d z63x)~`VYa|MI*q>qVK{V#w;0LR*uCg(F}Hxf%LvwYa@foEmVF&AC9;EKD7oqG@>!k zhoRsD;9)1w7waYWPd1Yz36DxXX*Ir3ODuYnVaM|ik@3DxsRO`rg)1{3u8-@M&VsCD z_+Uabli44zNAtbGU0)-W2gYLDRN#a3nstEu&G$bbn#Ei?I09cR`Hn>>GI4tDhYnKa zKNeuR1sbK>s6j1ZBvG82*L}6qX5R{IN@$c}j2jh=bcd`2Ob80F=~BW2ODDd6 zE@&|Q4KTfyt=|rk48wDh&@UZgB%Up|eJk<#&6-6ls5dxBe*8Q6VBO&^yLCQN=}-`S zViYq=cth4Nl1p4ESGC7}m1DV&h_Zp20R$^zpZ#kh3*%+vM$9X|@*1tS?LR!&)gkxV z-Y2C&9z79MrdJzadE$_|3PUw$5oQu}wCITxm^s6xC1J1pt#cHNMGR|NbO<>ORb(hX z%8rW-(v95>?Zky!3;Ps|8YB$Vbjir&*DlH>ksfDe!>+fSF_2`CBu#fB6G&rT9h=rF znc}Sm`*UefPs0n=SXAvEqR@~{U#2$USqfieC)x-j8T60P!Nr29t{MQ4 zLt(f^Ww=~*g;^pdQPJ0kJn*NFYKZEEyV+G0V+S55`46B7*Mdc1)s4yD94jfug1eur zYHI{oSQRV<%);9+qzMp(6>V{O8*UO0$MY*9qCG{04>4VLAZu4E`8KIkGqG(jMaq~^ z77-;ulwFoP7G;^{Am|T+v*_+0|3J_0a}UEj{YZAH`q9_0n+yGJlqA}}s%W-2xB0kC z^)WFhO84_y>;O0omPX%(?5UlQ95^So0FzVerA82bw-!mp5{d_O#Wxoe zq6_R~`!+@rTj($Yh&#uS#q7iw)v}))b7Y_doUDjp_%ot=i5h2&3-*pX*hdW2cxGU< z0u89Fd>O0yHUkX$#_N$W0xyqQ%AUxw?97Q${=pii!?GREx4bfa5m?3(5*KJ0RR(_y z$G)}9~zx&jcx{czTLCysNKguY*QsVFfCxUJ!UsA=>Rxc zQ`(Xw)QbDg*i1l8B)Lv4z62P%qbU_d)1&L-3s+^|m{bC%riOQiR;cRWaLJ;hujv5r zKnBTZ7~Gwxyg7tVU9Dc^;K4^|vO+S$29%loFx0x7nh^0Zs{v6GNq0hu*f>I)Bb6wXgnh)-^_v}B?yJyD~@>J(%^jY#@6^m zmVF|YowO5Qvk_N(s$*nYik=8eso?-8MJ=#5i?!2@7a_} zNHVIsk5Yn|lawhjA$wSTV6q379OUDX4Yir|R?5)jI?8QL~Eqe6}F|P)8yq+{>Mxgd9WZCvkA*d^HO43);Szbc_=O9H%AJTUy!5<1f3}b zjvqG0^Xj*0%d4ttkov2;DW+Cf6g(DT%nf@)mv3*IX>Z8Xk#qeM*u-&8LE-dSv;lV0 zTlnY2z=DMXUHduZA;qtU3z~5bo;SGtNYy4)4#z&!Lq`i)tP}RJSH|!j234bOURv|+i$)V+m zAs3U`S*Y})A0W_-JVhN9{_elBtZ0pB#w2m0TZvi7^f3Lp%Hx9vEEvPmWYMU7*u=hP zU5-BYQu%)$%r84w4K_vYfyOam1hSBg8DFRx`65j(8{Z8w%NcFo*ln4;@wfP_6aua4 zxMxH=(F}_ALCNiP20*{h&?}zx^&!?i#N(f>zE^FQQa8;L6a=gBZJ(_9x6LrwVlw)| zrFEP5ehJiyA8fQ&4Hykl8>@_WxWFT`ku4{+sar(<8r0$|$V+6a#00bT!xyx^^r7E= znBXTt$aS@_J$W6KIx6Sj?bBWBG}j*4v5!215uetKTHX*t?e1mFDlV9XUtI7%a+Ku+ z1Q59=z7_toqRKGVD0i%F+g81yYvPN-IQ#n*gc;CUCfiMU4ZI{Wn>g9tT+yg6gyE-> zi#n)MS@7RPP)#6LA0?CUa}l#ri~vcf8{7G*czLk0QAOY^`*@Vka^_-ScV$nVs*HH@F;L_K#6Xt! zH%C|Ja`j5uYwQj(I2$SR{AmWtu!ZzCZJwA2Ju=}w#cMX8^YX@PHzg;IP2v{%yt8?z z*U89N87CTAqzTpSkG0PMhO0yichar!mF><9RHr(Wp-pe5+4(36@C^CVWgcmXl91Si)A$9V(3>g z+%)C>q!~`Tw#9N44~8RA-x>}{f_e=)2@*wb^;ZK+&;C|m<%Y-{BIcVh_omJIB0T;T zc0&lIt#D;$GEqb4jQ_lw=5f1Dq|+A5-(Ap|zTrE!e+*2D2UNs98+zfre1k6H4MXj@ zM?uka3nRv$XpSqPR7tmelxja+p8byljGY4&Q%%oe|fSmr zn5s*8hP)W(# zLr@@u+-!RN7jUXx>xqs#jEeatzTpPmc@O4)t0$Ds2;1}2)mUvHdQzFVJ zI+#XiWn{ng+udg74dqjWM+e z&w;x#0uk+O)XA<)I`{*IS&D;dw%`5lAvX{|c8+>di0_?VCcr|AiCl;KY|clAE|rNp zWd%P2pNJ;2L&SUI5s;IprmZHY_Uk083lJXHx7&QLR|02BSGl<5INJ-xpwF_ z(>ldi5t@z4m0D3E_U{BNVTLan$-sK3pQvE7i9^$m@j`UGr8gLK2_DtR*7cRq>_sC& zn;t{AG!fm{&0luSg|~ZrM-nXI;4M8BXF&l=!td@>l7Ku>1jel2{ISfPFR6`HsR~k6 zgcY>7%!}>;79V>mT*aB>S+&bX6cx2w&@?#nfR68L#(d5zpQmidN0)zKEb)#}zr>JJ zma)X6uhV;e+Tt$6AF0q z`p_au*HXx8-x7{_eGLa|xHiJhe=e~vtpsA?{%E~W-ySd~Jl9K;u{)jK$NM1NCB8d- z=j!lQZ*WIZq4AFB>oIq+WaU- zU;LQZeeok43_ezJdXr|V(aKJoAdQ)8yYE2Kp0D{rx(3o5R=fDdWKNI3JE)Pf-StNo zzHn!`nrThpL6$?1cZtyy47zEMiNr zwk<(J=xXHT%O>?%dkU!L!aR0%qd!mCwHgj)D`n)8;&R;cvN%E{ z3;BgpD%(0g)ZU)6%_>{*Q~K_wEWS@C#a^UiwVkLc{<3!+WQt17&!P7&NZ zoih^4DPYdnPDsVZlBQznMNCZ<3ko00O=q4-l-`Po;j7lH{B1ifzsld?-NfHF zHfp&T8^gnTFIPK_YVYhMj7R)5ey6A3i{_z=hu2sjl1q#x%JPmRV%?i4}ovFoyA-@7w%rVVpn5? ziE4M`zwGERf6_Fy7Z5oAhT167ra+h~C-VthZ5iFDVe-#_UcVV?*)8mhu{Ko?3YuC} zj!+y$gP6*UDx{a%NtXVfO$rNVPIk z8Q`uAzksjk{O*e`PMA?m^YC6S0ro9Wk+Q@yfr1Rj01YM4M*G!S`gN|GTU$~4MdPW( z6m6(?CqcId<|-rQeM9wZ|xkw1_i4nUglt1%uDUazKhyN;s&!$?OYDu427)8+96 zb9o-(yax;K%dWp{#thJuXxmkkYVvY~%u=H0%|nV%{HzivMoElKS~p7y=4nn6)xf2l zLJT}Lk1OF3D-*OxwQ~?j&d$d5lDaaA!pQQ+HCkAX#Wj+XcI@@n)~7;aPY#N^75P@f zMeGzYD2Mp!@Pbua`XCjSSffI{+ZhmjaH8KO5QaiJE+$z~HH*yy#YEtqzWK#e{rlc~~7 zyX%W7toPv!nomlPqSsgL@@2dJs!CjIWe}W~_kNns@SA?!ZsTPx47L4d-f&=k6w z+?<+XEo=ex+`u>a>jL3u{-+Y2(t)21v+MK+xh%~7fcYW+jCwG|j~wn}T}0P=qTObL z-d_d40@=#|ppk`wId^N5+Xe8GQIL11=KkuJ#LBb3^R-C})u_DDFKZN@XSP!^{_x$0 zTSgKk$0qEIq!q)np45X2;a-wRIZ?5Fr%GHby6ZPEGnXnqp~(10LTA2~79r1Lz4Oi4 zab$z7{nACG=*5G@+%~E4+rF&?VFaViMY}ZXGt-?wDUR^L90KNn3$jZmjKCc5Z&2<# z>)_VGC=f;P>t}Mx##NX246!}o$i@Trm7vp6?lD711he$R^I$2=vQ$;{2c)(Z$p9EF ze|c`{qW);wu>Zv?;6pDH7tYD2n_2{NF#EC^d zA(&Le5I(4j)&jzA&)PuVRtX$U#%}#UK3G1LWQ7cDgHxLd7qNtIe}u!^GwZ0eql_*_ zKtI%AP^gU)m9y-y+aA!~#<#s%FYvw$OJ!xQUv}(Hr|AEL3}-Nj|KwyJv_A!rs%^Sn z=65?JxX@@gS`DbO`J@eb57irNc)?BEs&Z!t7e@)Zu98GjoB&A_5Mwt%h#TyFgSDr$Byh_^KDeLobZvX!%> z=ePu1^wsshotO4Fy{T(Np9c$a9D56qp2K7f7EaFO%g3aEzh?$4RUT^l*!nZPXRr8S zGexv|Z}yGT#aAUJq)l>oUidz!Jhte-SGC-qlDQ7dqRMEC2xtWMUQxU-!mj2%1mfGh z@%P?%b4SGJ7Y&1fTls-${np!*YRa&bVk0sr@BIE@$;DngB0Z(B<9rcx%g7^{dbX%#LuoQqUpCe`g?3I>Mpc}IJiavv1q65M-rV1GyUlu)b_KuuX4Nd5h zy@LtP02=eiic=r6A_$Wxmqb*YvW#__ivhtFtPx@9jz@JgK!+lAQjAZC3oOQ*6*VW< zR>hQecJMv~MhUmwNdUUg)VY`0UZHbkzq)izQQk(uD3J`~?(aC&J+n^d?9e-06pV3d zw5lVh1hFS34jOXOp!2q~x`XNB__QnHZT0$X0y(J=AON-!7w8JtPD>9_mX>bSC36pF zSJQoC9fPKmy7l9e75FjBP)-(F$*g}knam4OVw6DO&M>=@3<%cL8V5yxVpjc(&s7j0 zRiKbYfiP9W@!I13sqWl!pes-qzS`ly%H9tJT_)PoG17FPhtq<1g`_exfu04&9bke-c zd9gi89xI41yGXrCUUs@2;w~KU;?vQw1pJCL&u>Qx4O`9634E|P=n<5x@^DY_>@zXc z3WW5TaJBnqb~euo^OD_#30G;ad7SoVUtx ze}_y&Yd_I5bmMiQnFUf*^ zHlp~ASCx$K$UT3$o<4ARU><^n1f7#+?OKC01=cm z#b9tAFg&)*^MGB{Iioz*QnQPv|EYGR|G9x(YB4GZmx|7K6PHs08|OrH{FOIF11MhE zo!4=@vo8GmV?o5m#~!_*Lz;$Xl}zjGg^(u6PZZ8cv3TVnN#+4Q$Non5Cycs}Vo zR8*+=4LRP|hVClRd9?kG8L}AMk?bRik%LB4^uY+`CsTphk))L@I7V<-t7k+j${>+o z1q{Yulr-?r$zX&_;bBZ8gNw_X8I!NnKbv7bC6VM-uop`uk4xiArHD}dMxR%XRTH?7wYf10 ze++i zET2zK)EXG-^)_s;nuW1+2sD>Res_<6RGU@`sO^NO6~7%2W{ zqe6ruIv`fE^1NNudW z`*bb>ZhZ5a!3f-OYKj~X3L0gkU>8DA(?+7a>?C>PU^e%M-iPpm`9v`)h>`)Mb1*US z|ACOK?a5U;Y9U_GrnoSuH#%ND0-riB>DImhuUk>m4?@bjN3%dV2ODA-lQ4hvvv)-CW`r zA!g!9=WmAU~!l?D_U_5 z|B$M|1NFxp2%jJEbY+C8+=4yUcdpoY)$AVMm#)e>NJ72p4aEEcfL+ z#23SCL%qp#S9j`>)}5neZ#k}dG`odX(7{ZsbX+32b_on7CBS(JTr zg%xb^k@1@C>F_$2mCl!t_naQ`!&cY9WzptvKLaqRu`jP93QfRL!uR_fUNPDHa8`Ic zV6A0aw<~C`=VsE`6l`30ihgZ-Y-OD$%e~3$LPei>0_=H?-*@|fNXGEt1ui?9h?aE^ ztpZ()hmk>pG9zXW0FxGh@eKhF6WxkOtC|*La>F{XgV4=l!W{)H4hm$6P1P;y%1I3+ z;O;ke4#Q$0q`eB$-7vBl_$?7p&C8Tp66!?2l4q%W4iX5 zH%ejR_!xE?2MQ5sdOfrwA$Q3EiS08f)?T+nH0+nW)msc1_Er*MBFF3J;~$95C0ww3 z?%V>+`(rIAXC^Z}!s;H+qQ@B8)~^`g`B&lR~ds?UYD%HB<5v|?iP zq&}ULJt!cOu<6T9h543O{4Adn1~=D;c^5+<36Ed(t z#RQ48rDm(SzBW>UvxrPVu0l^1z4c6$pKPk$wA4wP^G(^aC{8m)^fCNi5g~0;fNnmG zVm{APqC0ki7fza2@Nza93b;J4G(&7va3G!C4|aZL~B$+3x6 zo#7R|Z+jaAaW3Z4LHQk4dCj8Puo1e>?Fmk^;KRa37%M?4I=g({trPVHMM9%(7Mi_k zNkIz;w~n)kdLxULXLqT2%FIN2kok2X`tf?PYM~GQP!YJaYSlP0UhWiYc0sV|ShmfR zdMq60^0AzVC93XZ8CW^eTd!}+>%~7YFu*FSmzPjc82PpUK`bhY=Cl-(53+22I$_fK zE_dmUNUF7PGt>FySkU|oxeI)?>CXFcvDIxK?OIi4yJu0(AY(`HGlw$qjG4XVH)0}P z`MH}oX=ADHog7JWo+tT@Qy<)QoF~pU3V#?-f1-2hgqXkg>p8aMU}@5zxuu@*MsF}w zJI)&2`V}6Y^)KXh42KH=1Ivf07wUpIcj>ai+fHqB+l6%929@}x*MT#fmzwjURN3VD zm|J~fxhmtqm%9b3`{;xFF|V5|{z3bU@`AC>z1>tAq5M&iIu6m5Z#`2aAR`Y_9Dve* z){pikjQYCbS#g4u(cieQiJ0L=TYT6K!Jjq~NnD&2X; zK^!JGjBAi4*3NtVdRmEvv;jej+c-?SvXD#3>D2H+ zaAS5hxyb`|{7I{=wH@2zyt<1US?8LYkvxVwP~O8=wGuyt^p)_=7`-_>276bV!AD;LfD6tsI^sH^Wa-qQt5rqcKa~-yo@=aj=@ho}Zm-#NvoE76D>xl zua2Ro-nlNi6()I(io;aR~NbzouC@(wNr)1%lG4bcOd7p z>ADhsQgs_n{?>n*km153(+Y8PPm^-Bu;1i-YOVV9qnXG>{ED80Skm@2%V-+K1tgQS z^LShvNB3QFfIK`Ls`>=lb#;fI{*Z#fdVG<<4$Pk9=$3OqqQ|{2A$>O!?jk{IxvtL| zwc}^@W&(x7w6HGl+O!lI7a*{~fG}FE$^5nJD)qjO%Hey*Zk*KmS3J|PdN;9Mt_7Eh;>#U)oz98;NxLBPzG};XH=c3w|wa) zn;tF1>$g#Fxc412miKB|1Ru!viwfxi5x&Bs0(A{KV?Q$)uFZ)WAA76PXMdg=e z*Lr~$a6j$*r7SK)3EatIg+fHrtyyfX{u>^%#OBT+?9g}!MKHQtW=76|l1f_L8o}8r z9>8F(EbW>fZd+HO_ONzi=epo)t{e}+ztZ|!Qn zHxg$6NxN8qL8=0v;bsOS+X2yK0x4fBQqoOq9-*>pJ%36MIN9WOZm^6;K^pbzt%{vg$=7+8MVynrc%`-Fc@r|}8BviiQr-ioE z$>y!~;%fKv_Ux0`r_kw=*t{n(e-oVrIxm$p>e;CuA%mnb>jm9c9}U7@0|OW0lF%Et?bsA6MR9)q}s&2FP9C3@^e1Lm-MyAfF*HVcmh@RFZ(i$7o@ZQ8A$eAjnNPR0z~!Oyuy zJWPT3DF-SgUTdDfoOrd2$-VvVl6gMvT^jTtK2;V>)7*+xyMFEwRhk&QN{+EtfuXf| zw|wDrSpCu)eiaz}UE?EEin&_n_vFVlo98CHX%U{T$_$zqy= zTvuABeHsbm;E}y~iKpF^7yD)9<@Obs*dzY;P=kH3E52X+07D1ezZ*=Mqxp(6zpYc6 za;RLr^z610Wk&dPVPIJeM@|Dpe~x#1Q6C+7Gcpid`UO+&IKVSu&(u|iikIbFh24MH zfPq1lBV<6|YDsPL~9JvT~Z(j)HkdjV_iWsWlZj4;=IoAb!f$4{Z$ zn7z>fC-ReCM^%O9Gjwfn zLn8sTpoD8$T7}klZ}m5IynO)9*Ys%5ici*y7#<|YCM)<@E}6Ow_ zALAwJ~2gYn~2-oWcnU0!w3;ch!r&Dgc+Heup-d>AywWQa&h6%?{ zpl0tD!r`0=?Hsr-al7g_m4}1wkFx3dXm_;W!BSOUOR*#y5Qe!Tl}k~9t8qqVO<_ej zCDM%hZhE}r_ZiF-|x`$9oTk!K)g zp0zG}$$%)nOER1(=1zPWsRt(?&^6R|!5@g&p~(3Hp|$Qx_!U3t@d>@yuFd=PW=aR86glidWSsF@1-0!f#4aVL-+*SiIb}03q1eIP$G|i`7-->k3cjR{h^^3IiRtWe zI~z*VW%Bg<5N71cILZ6ZES&&m3GVDs@?o_5U3AI{9W;zS)lWO^%>@b(iP`OaYJ>A^ z2Hy85X*QSGTDaD5=&EE`Y$fl2*e)n+3^0sB#c$bi>V2|V<@AAT&&@IOgSGV*oV&{j zcNGt&g6a^VMy3Ph0$t2@!qCm0=IuZH`v<3J!hMwUn(1eyxG9UlDwu_8$_wYK1yOOzZXI*NcnX>cFor*C`d zj+2AMmHUr-Z8E{~;8L9yQa!UAz@v=m%ikA>X>I%X`s($Xn?<#%iWG|iH=@PkfxYYV zE>8|4@@WkX(vb8t)mM)Z@3plF%g0adgUkp`)g*TE7QcHrZ)A&H@pHlfGXSzFA}fZ$ zRQ(Ze?buv-H1<4S1_nyiM;jiOwKNi2LqW)>c$cJ{L%9v~6X9*%=r@D&rOPkk1;ll! zAO^9G?5CW~;^IkfR7VK70K1w_vA0m8!xcQAciKBWw=PP0?T#2-YwOrS1&E7OPCN_? ze7ilobNIE_D$6JD_!qt#Eqa8n*MrxO8VzV!C3trLzjc!lo@3=&Py`;J$-vr;2ZF^t z@dX+Q8sIRwLS#*UTCBzQWRYcj zK95&)MgsPaeEsfc=(}l8w;!>V;&0(@I+<5F6KCZjxiAjY=kXfSsegucU7A8W3nP*( z_cE{Iw|GRHB*e5WgFhRbj9&g^;_dQInz@v3YiInrjIzc=lLDw7rwu3>5)jSjt-vB5 zbVH?A)ud$`2)`@fuY6x#7xilP@6{P6%@+skx$%-q+U5|ZEL&)3Wa zYb`cAKq*(m;YA+%Q+7mG(8Y$y|5TIj$6{vOSKH44|?bk|HD`z;nyenXV->+&Kr zu(XjXs zYX7{>^7$aI#3>w&#=et3w*Q!bYrnCY?hsn5+G9BbAC2BQw!SRa=PBkv;)zrEn8p+4 zgH!hAci{Zkl}zYHAj28KW9c5+nR9mS=(?a|p-#t7E>feQ#GUEbscH5suJKQ7Llh{o ztvV-!+YZk`SooQ6cX#96hOO(>XC2kzhc$cjS2D<~qSK>epFflU^yCnUsf?TA8%P*R zLe~vErSln*+4L=~&q9FVvAy#}jIcAIk6`d=S#lx9+SQrIgNdIehnwP--gD@6C!?2u zZyzG%p4`YX8T@5Sk%U*v{YW|WZcq-0ar{}9=A!p}s`c-!OtmyF@+T0WR2csVtY6GE z>i4jDIUoAl3hpOFEL%TY$5j(x(M4nA;c)N3cz;xs93d0EK=2IbtW3eJM5_9(%`=d) z7gozSGKEN#=CY@BI5)q+!=0V_cO-WttlcS}h&?(hs#Z1^W{%lwERJ8$%SfBJl{>fC zR&vMVZs{F+KIpHRs4P_p8`kl@9J3saX50HDa+-2oo|YYyI*YAM>wt!aVdplg`1}hy zdnEqR$XpiTyKj86{TfV3pG&9i-0d0&D!aY3LVo+5!yJXmHT|Q^ts;n z|0iDtp!sWkmspWoe8_>1I_(ROr`;GDW@%SZE9|2*=%gXHj0oY(O3%IH#Y!+dAE7!> z1Oh2hzDD+Pyb~kzxjJ>nozwF65MNA~T6m7qLZE18s#JW5B=_4Ir!Wzol($ekeLoAd z4;UpV{zAhq#_%cL1Lxtt7=iD>^5KqC6(WRchsH-~`>yRoKQeR8TAsiq4CGY= zba*b=^uvG1{gv`2#IU=5IHnW{S1ev3T(x*3^p*h3{BDE~{n(=d5;oNY4~-injoK1887` z9G4}HuP-OIB`v}v+|Zl^S6YVgmuTh&iwAj zg=yWj4c5e%NNIcVarb&(+kZ{xcb-n`-XTgDW02uMch#a? z;mSqd{&2$mAikj36OJ~R=3xK;1e-}jK~xWPVpok()*>nY^l#jc!8f<#p)alD375+| zgmVD}G%N;D2%|K>)Q!zT^Ag$%&(c$m@k@6?MOu#oj!vPz#x?I{Tz%_R=1=z>uU!sz z?d6mPdwp|CN@~B`yCADCwQXdGaL4xtJey7q*PFRG!FRm`v@xf1r%sfMtX)jSUgS=i zHrb$^xTbUIlFZ%|k5rauzJ+mAOv3mjtAT6z&QCKJ(>sijza54zT*Xmhhf3K#r^Yxj zD5hwiv$niOXuEl}{qW{>^}9B$t2^p)JG1Cy0eX*Jrr+#6rSLtMh1jvL_u@}pPL0Lh z$nwn0h&zyse-RH2;y70BUUIfpF>g8OQZeL_#es*V#m*m-M!<}O!aK7`r^@2_bo#wohwrkY+HU(M+ccl|Z^Jm*eN?HOBhz&-Qu zX>IVjxoLeUuE+h(yWwxXV9UJfsz{6QB6d^$0T#D0y2*I%YWw47YqhF+Rok_Hd;O*p z<;s&|??1!K*m?FKAA$FuQe8WaDO`xHKYpASwfCfY>Rhex$mV&+PH$P^I8a=oF2z^D zO&Fzf51w+apl^Ut!eeFX6Ht>cD1my$li`6Dx$3&^hVKSZY|O?2#Qk_l)_&O_@x%jpCDF z{k@*w`|MYy2QEv3G-spQdFqpj$>EiJg9~urorv-^tVAXmpM9Lt@<6`oK6n=B?TF<# zQoPMaFT{={d_AfUSw|Y`Hx8R4s7#{gP?+f&f}Woy$T$y}Mks+rrAEPGWua7_vZ)UI z9nYxeAyc70ZYU!bKaLwu$p2Bf4fs+Ncf{lG@GqIZC~oB=t{&BJ9gR`OQy;{6X+UX- zHdK$H@j9LjdGtGKBdW)5IPl3?3;6jHw}I%vGx)#v&qt}f&zyk4%G6D1p0qYGpn-xH z$7B8BBErX{jqZIA{L9vH42biC;oYU=%d1ODiqP}wFa6*7!_(jE9jkFZ{e5e${`3u& zo<<&;u`us6E((-o*TzeQeD~yd&e0+7+#$D{Ak%snf@K8 zN%@WN^@0e*F=*MI(J3gt1mrw)Zh{B01kjP5363}T-}~1`fB*aScMAMYf!`_cI|Y8H cKqCtLKUhARX_g79O8@`>07*qoM6N<$f_AJC;s5{u literal 0 HcmV?d00001 diff --git a/include/YaSolR.h b/include/YaSolR.h new file mode 100644 index 0000000..0e60a48 --- /dev/null +++ b/include/YaSolR.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef APP_VERSION_TRIAL +#include +#endif + +#ifdef APP_VERSION_PRO +#include +#endif diff --git a/include/YaSolRClass.h b/include/YaSolRClass.h new file mode 100644 index 0000000..643e6a3 --- /dev/null +++ b/include/YaSolRClass.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +#include + +namespace YaSolR { + class YaSolRClass { + public: + void begin() { + _initConfig(); + _initHTTPd(); + _initEventHandlers(); + _initWebsite(); + _initREST(); + _initMQTT(); + } + + Mycila::EasyDisplayType getDisplayType() const; + uint32_t getDisplayRotation() const; + Mycila::DimmerType getDimmerType(const char* name) const; + + void updateDisplay(); + void updateWebsite(); + void publishHADiscovery(); + void publishMQTT(); + + private: + void _initConfig(); + void _initHTTPd(); + void _initEventHandlers(); + void _initWebsite(); + void _initREST(); + void _initMQTT(); + }; + + extern YaSolRClass YaSolR; +} // namespace YaSolR diff --git a/include/YaSolRDeclarations.h b/include/YaSolRDeclarations.h new file mode 100644 index 0000000..93f8535 --- /dev/null +++ b/include/YaSolRDeclarations.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include + +#include + +#ifdef APP_VERSION_PRO +#include +#else +#include +#endif + +extern AsyncWebServer webServer; +extern ESPDash dashboard; + +extern Mycila::Relay relay1; +extern Mycila::Relay relay2; +extern Mycila::Relay output1BypassRelay; +extern Mycila::Relay output2BypassRelay; + +extern Mycila::Dimmer output1Dimmer; +extern Mycila::Dimmer output2Dimmer; + +extern Mycila::RouterOutput output1; +extern Mycila::RouterOutput output2; + +extern Mycila::TemperatureSensor output1TemperatureSensor; +extern Mycila::TemperatureSensor output2TemperatureSensor; +extern Mycila::TemperatureSensor systemTemperatureSensor; + +extern Mycila::Task autoRelayTask; +extern Mycila::Task buttonTask; +extern Mycila::Task configureButtonTask; +extern Mycila::Task configureBuzzerTask; +extern Mycila::Task configureDisplayTask; +extern Mycila::Task configureGridTask; +extern Mycila::Task configureJSYTask; +extern Mycila::Task configureLightsTask; +extern Mycila::Task configureMQTTTask; +extern Mycila::Task configureNetworkTask; +extern Mycila::Task configureNTPTask; +extern Mycila::Task configureOutput1BypassRelayTask; +extern Mycila::Task configureOutput1DimmerTask; +extern Mycila::Task configureOutput1TemperatureSensorTask; +extern Mycila::Task configureOutput2BypassRelayTask; +extern Mycila::Task configureOutput2DimmerTask; +extern Mycila::Task configureOutput2TemperatureSensorTask; +extern Mycila::Task configureRelay1Task; +extern Mycila::Task configureRelay2Task; +extern Mycila::Task configureSystemTemperatureSensorTask; +extern Mycila::Task configureTaskMonitorTask; +extern Mycila::Task configureZCDTask; +extern Mycila::Task displayTask; +extern Mycila::Task espConnectTask; +extern Mycila::Task haDiscoTask; +extern Mycila::Task lightsTask; +extern Mycila::Task mqttTask; +extern Mycila::Task otaPrepareTask; +extern Mycila::Task output1AutoBypassTask; +extern Mycila::Task output1DimmerLimitTask; +extern Mycila::Task output1TemperatureTask; +extern Mycila::Task output2AutoBypassTask; +extern Mycila::Task output2DimmerLimitTask; +extern Mycila::Task output2TemperatureTask; +extern Mycila::Task publisherTask; +extern Mycila::Task resetTask; +extern Mycila::Task restartTask; +extern Mycila::Task stackMonitorTask; +extern Mycila::Task startNetworkServicesTask; +extern Mycila::Task stopNetworkServicesTask; +extern Mycila::Task systemTemperatureTask; +extern Mycila::Task websiteTask; + +#ifdef APP_VERSION_TRIAL +extern Mycila::Task trialTask; +#endif diff --git a/include/YaSolRMacros.h b/include/YaSolRMacros.h new file mode 100644 index 0000000..67eab20 --- /dev/null +++ b/include/YaSolRMacros.h @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#define YASOLR_TRUE "true" +#define YASOLR_FALSE "false" +#define YASOLR_ON "on" +#define YASOLR_OFF "off" +#define YASOLR_BOOL(a) ((a) ? YASOLR_TRUE : YASOLR_FALSE) +#define YASOLR_STATE(a) ((a) ? YASOLR_ON : YASOLR_OFF) + +#define HIDDEN_PWD "********" +#define NAME_SYSTEM "system" +#define NAME_OUTPUT1 "output1" +#define NAME_OUTPUT2 "output2" +#define NAME_RELAY1 "relay1" +#define NAME_RELAY2 "relay2" + +// configuration keys + +#define KEY_HOSTNAME "hostname" +#define KEY_ADMIN_PASSWORD "admin_pwd" +#define KEY_DEBUG_ENABLE "debug_enable" + +#define KEY_AP_MODE_ENABLE "ap_mode_enable" + +#define KEY_WIFI_SSID "wifi_ssid" +#define KEY_WIFI_PASSWORD "wifi_pwd" +#define KEY_WIFI_CONNECTION_TIMEOUT "wifi_timeout" + +#define KEY_CAPTURE_PORTAL_TIMEOUT "portal_timeout" + +#define KEY_NTP_SERVER "ntp_server" +#define KEY_NTP_TIMEZONE "ntp_timezone" + +#define KEY_MQTT_ENABLE "mqtt_enable" +#define KEY_MQTT_SERVER "mqtt_server" +#define KEY_MQTT_PORT "mqtt_port" +#define KEY_MQTT_SECURED "mqtt_secure" +#define KEY_MQTT_USERNAME "mqtt_user" +#define KEY_MQTT_PASSWORD "mqtt_pwd" +#define KEY_MQTT_TOPIC "mqtt_topic" +#define KEY_MQTT_PUBLISH_INTERVAL "mqtt_pub_itvl" + +#define KEY_HA_DISCOVERY_ENABLE "ha_disco_enable" +#define KEY_HA_DISCOVERY_TOPIC "ha_disco_topic" + +#define KEY_BUZZER_ENABLE "buzzer_enable" +#define KEY_BUZZER_PIN "buzzer_pin" + +#define KEY_BUTTON_ENABLE "button_enable" +#define KEY_BUTTON_PIN "button_pin" +#define KEY_BUTTON_ACTION "button_action" + +#define KEY_LIGHTS_ENABLE "lights_enable" +#define KEY_LIGHTS_GREEN_PIN "lights_g_pin" +#define KEY_LIGHTS_YELLOW_PIN "lights_y_pin" +#define KEY_LIGHTS_RED_PIN "lights_r_pin" + +#define KEY_DISPLAY_ENABLE "dp_enable" +#define KEY_DISPLAY_TYPE "dp_type" +#define KEY_DISPLAY_CLOCK_PIN "dp_clock_pin" +#define KEY_DISPLAY_DATA_PIN "dp_data_pin" +#define KEY_DISPLAY_ROTATION "dp_rotation" +#define KEY_DISPLAY_POWER_SAVE_DELAY "dp_powsave" + +#define KEY_SYSTEM_TEMP_ENABLE "sys_tmp_enable" +#define KEY_SYSTEM_TEMP_PIN "sys_tmp_pin" + +#define KEY_RELAY1_ENABLE "relay1_enable" +#define KEY_RELAY1_PIN "relay1_pin" +#define KEY_RELAY1_TYPE "relay1_type" +#define KEY_RELAY1_POWER "relay1_power" + +#define KEY_RELAY2_ENABLE "relay2_enable" +#define KEY_RELAY2_PIN "relay2_pin" +#define KEY_RELAY2_TYPE "relay2_type" +#define KEY_RELAY2_POWER "relay2_power" + +#define KEY_OUTPUT1_DIMMER_ENABLE "out1_dim_enable" +#define KEY_OUTPUT1_DIMMER_PIN "out1_dim_pin" +#define KEY_OUTPUT1_DIMMER_TYPE "out1_dim_type" +#define KEY_OUTPUT1_DIMMER_AUTO "out1_dim_auto" +#define KEY_OUTPUT1_DIMMER_LEVEL_LIMIT "out1_dim_limit" + +#define KEY_OUTPUT1_RELAY_ENABLE "out1_bp_enable" +#define KEY_OUTPUT1_RELAY_PIN "out1_bp_pin" +#define KEY_OUTPUT1_RELAY_TYPE "out1_bp_type" + +#define KEY_OUTPUT1_TEMP_ENABLE "out1_tmp_enable" +#define KEY_OUTPUT1_TEMP_PIN "out1_tmp_pin" + +#define KEY_OUTPUT1_AUTO_BYPASS_ENABLE "out1_aut_enable" +#define KEY_OUTPUT1_AUTO_START_TEMPERATURE "out1_tmp_low" +#define KEY_OUTPUT1_AUTO_STOP_TEMPERATURE "out1_tmp_high" +#define KEY_OUTPUT1_AUTO_START_TIME "out1_start_time" +#define KEY_OUTPUT1_AUTO_STOP_TIME "out1_end_time" +#define KEY_OUTPUT1_AUTO_WEEK_DAYS "out1_wdays" + +#define KEY_OUTPUT2_DIMMER_ENABLE "out2_dim_enable" +#define KEY_OUTPUT2_DIMMER_PIN "out2_dim_pin" +#define KEY_OUTPUT2_DIMMER_TYPE "out2_dim_type" +#define KEY_OUTPUT2_DIMMER_AUTO "out2_dim_auto" +#define KEY_OUTPUT2_DIMMER_LEVEL_LIMIT "out2_dim_limit" + +#define KEY_OUTPUT2_RELAY_ENABLE "out2_bp_enable" +#define KEY_OUTPUT2_RELAY_PIN "out2_bp_pin" +#define KEY_OUTPUT2_RELAY_TYPE "out2_bp_type" + +#define KEY_OUTPUT2_TEMP_ENABLE "out2_tmp_enable" +#define KEY_OUTPUT2_TEMP_PIN "out2_tmp_pin" + +#define KEY_OUTPUT2_AUTO_BYPASS_ENABLE "out2_aut_enable" +#define KEY_OUTPUT2_AUTO_START_TEMPERATURE "out2_tmp_low" +#define KEY_OUTPUT2_AUTO_STOP_TEMPERATURE "out2_tmp_high" +#define KEY_OUTPUT2_AUTO_START_TIME "out2_start_time" +#define KEY_OUTPUT2_AUTO_STOP_TIME "out2_end_time" +#define KEY_OUTPUT2_AUTO_WEEK_DAYS "out2_wdays" + +#define KEY_ZCD_ENABLE "zcd_enable" +#define KEY_ZCD_PIN "zcd_pin" +#define KEY_GRID_FREQ "grid_freq" + +#define KEY_JSY_ENABLE "jsy_enable" +#define KEY_JSY_RX_PIN "jsy_rx_pin" +#define KEY_JSY_TX_PIN "jsy_tx_pin" + +#define KEY_GRID_POWER_MQTT_TOPIC "grid_pow_mqtt" + +// default settinga + +#ifndef YASOLR_IO_ASYNC_STACK_SIZE +#define YASOLR_IO_ASYNC_STACK_SIZE 256 * 19 +#endif + +#ifndef YASOLR_TEMPERATURE_READ_INTERVAL +#define YASOLR_TEMPERATURE_READ_INTERVAL 10 +#endif + +#ifndef YASOLR_TEMPERATURE_READ_INTERVAL +#define YASOLR_TEMPERATURE_READ_INTERVAL 10 +#endif + +#ifndef YASOLR_SERIAL_BAUDRATE +#define YASOLR_SERIAL_BAUDRATE 115200 +#endif + +#ifndef YASOLR_ADMIN_USERNAME +#define YASOLR_ADMIN_USERNAME "admin" +#endif + +#ifndef YASOLR_RELAY_PAUSE_DURATION +#define YASOLR_RELAY_PAUSE_DURATION 60 +#endif + +#ifndef YASOLR_DEBUG_ENABLE +#define YASOLR_DEBUG_ENABLE false +#endif + +// network + +#ifndef YASOLR_AP_MODE_ENABLE +#define YASOLR_AP_MODE_ENABLE false +#endif + +// ntp + +#ifndef YASOLR_NTP_SERVER +#define YASOLR_NTP_SERVER "pool.ntp.org" +#endif + +#ifndef YASOLR_NTP_TIMEZONE +#define YASOLR_NTP_TIMEZONE "Europe/Paris" +#endif + +// ota + +#ifndef YASOLR_MQTT_ENABLE +#define YASOLR_MQTT_ENABLE false +#endif + +// mqtt + +#ifndef YASOLR_MQTT_PORT +#define YASOLR_MQTT_PORT 1883 +#endif + +#ifndef YASOLR_MQTT_USER +#define YASOLR_MQTT_USER "homeassistant" +#endif + +#ifndef YASOLR_MQTT_SECURED +#define YASOLR_MQTT_SECURED false +#endif + +#ifndef YASOLR_MQTT_PUBLISH_INTERVAL +#define YASOLR_MQTT_PUBLISH_INTERVAL 5 +#endif + +// ha + +#ifndef YASOLR_HA_DISCOVERY_ENABLE +#define YASOLR_HA_DISCOVERY_ENABLE false +#endif + +// buzzer + +#ifndef YASOLR_BUZZER_ENABLE +#define YASOLR_BUZZER_ENABLE false +#endif + +#ifndef YASOLR_BUZZER_PIN +#define YASOLR_BUZZER_PIN 19 +#endif + +// button + +#ifndef YASOLR_BUTTON_ENABLE +#define YASOLR_BUTTON_ENABLE false +#endif + +#ifndef YASOLR_BUTTON_PIN +#define YASOLR_BUTTON_PIN 23 +#endif + +#ifndef YASOLR_BUTTON_ACTION +#define YASOLR_BUTTON_ACTION "restart" +#endif + +// lights + +#ifndef YASOLR_LIGHTS_ENABLE +#define YASOLR_LIGHTS_ENABLE false +#endif + +#ifndef YASOLR_LIGHTS_GREEN_PIN +#define YASOLR_LIGHTS_GREEN_PIN 0 +#endif + +#ifndef YASOLR_LIGHTS_YELLOW_PIN +#define YASOLR_LIGHTS_YELLOW_PIN 2 +#endif + +#ifndef YASOLR_LIGHTS_RED_PIN +#define YASOLR_LIGHTS_RED_PIN 15 +#endif + +#ifndef YASOLR_DISPLAY_ENABLE +#define YASOLR_DISPLAY_ENABLE false +#endif + +// display + +#ifndef YASOLR_DISPLAY_TYPE +#define YASOLR_DISPLAY_TYPE "SH1106" +#endif + +#ifndef YASOLR_DISPLAY_CLOCK_PIN +#define YASOLR_DISPLAY_CLOCK_PIN 22 +#endif + +#ifndef YASOLR_DISPLAY_DATA_PIN +#define YASOLR_DISPLAY_DATA_PIN 21 +#endif + +#ifndef YASOLR_DISPLAY_ROTATION +#define YASOLR_DISPLAY_ROTATION 0 +#endif + +#ifndef YASOLR_DISPLAY_POWER_SAVE_DELAY +#define YASOLR_DISPLAY_POWER_SAVE_DELAY 0 +#endif + +// system temperature + +#ifndef YASOLR_SYSTEM_TEMP_ENABLE +#define YASOLR_SYSTEM_TEMP_ENABLE false +#endif + +#ifndef YASOLR_SYSTEM_TEMP_PIN +#define YASOLR_SYSTEM_TEMP_PIN 4 +#endif + +// relay 1 + +#ifndef YASOLR_RELAY_ENABLE +#define YASOLR_RELAY_ENABLE false +#endif + +#ifndef YASOLR_RELAY1_PIN +#define YASOLR_RELAY1_PIN 13 +#endif + +#ifndef YASOLR_RELAY_TYPE +#define YASOLR_RELAY_TYPE "NO" +#endif + +#ifndef YASOLR_RELAY_POWER +#define YASOLR_RELAY_POWER 0 +#endif + +// relay 2 + +#ifndef YASOLR_RELAY2_PIN +#define YASOLR_RELAY2_PIN 12 +#endif + +// output 1 + +#ifndef YASOLR_OUTPUT_DIMMER_ENABLE +#define YASOLR_OUTPUT_DIMMER_ENABLE false +#endif + +#ifndef YASOLR_OUTPUT1_DIMMER_PIN +#define YASOLR_OUTPUT1_DIMMER_PIN 25 +#endif + +#ifndef YASOLR_OUTPUT_DIMMER_TYPE +#define YASOLR_OUTPUT_DIMMER_TYPE "TRIAC" +#endif + +#ifndef YASOLR_OUTPUT_DIMMER_AUTO +#define YASOLR_OUTPUT_DIMMER_AUTO false +#endif + +#ifndef YASOLR_OUTPUT_DIMMER_LEVEL_LIMIT +#define YASOLR_OUTPUT_DIMMER_LEVEL_LIMIT 100 +#endif + +#ifndef YASOLR_OUTPUT_RELAY_ENABLE +#define YASOLR_OUTPUT_RELAY_ENABLE false +#endif + +#ifndef YASOLR_OUTPUT1_RELAY_PIN +#define YASOLR_OUTPUT1_RELAY_PIN 32 +#endif + +#ifndef YASOLR_OUTPUT_RELAY_TYPE +#define YASOLR_OUTPUT_RELAY_TYPE "NO" +#endif + +#ifndef YASOLR_OUTPUT_TEMP_ENABLE +#define YASOLR_OUTPUT_TEMP_ENABLE false +#endif + +#ifndef YASOLR_OUTPUT1_TEMP_PIN +#define YASOLR_OUTPUT1_TEMP_PIN 18 +#endif + +#ifndef YASOLR_OUTPUT_AUTO_BYPASS_ENABLE +#define YASOLR_OUTPUT_AUTO_BYPASS_ENABLE false +#endif + +#ifndef YASOLR_OUTPUT_AUTO_START_TEMPERATURE +#define YASOLR_OUTPUT_AUTO_START_TEMPERATURE 50 +#endif + +#ifndef YASOLR_OUTPUT_AUTO_STOP_TEMPERATURE +#define YASOLR_OUTPUT_AUTO_STOP_TEMPERATURE 60 +#endif + +#ifndef YASOLR_OUTPUT_AUTO_START_TIME +#define YASOLR_OUTPUT_AUTO_START_TIME "22:00" +#endif + +#ifndef YASOLR_OUTPUT_AUTO_STOP_TIME +#define YASOLR_OUTPUT_AUTO_STOP_TIME "06:00" +#endif + +#ifndef YASOLR_OUTPUT_AUTO_WEEK_DAYS +#define YASOLR_OUTPUT_AUTO_WEEK_DAYS "sun,mon,tue,wed,thu,fri,sat" +#endif + +// output 2 + +#ifndef YASOLR_OUTPUT2_DIMMER_PIN +#define YASOLR_OUTPUT2_DIMMER_PIN 26 +#endif + +#ifndef YASOLR_OUTPUT2_RELAY_PIN +#define YASOLR_OUTPUT2_RELAY_PIN 33 +#endif + +#ifndef YASOLR_OUTPUT2_TEMP_PIN +#define YASOLR_OUTPUT2_TEMP_PIN 5 +#endif + +// electricity + +#ifndef YASOLR_ZCD_ENABLE +#define YASOLR_ZCD_ENABLE false +#endif + +#ifndef YASOLR_ZCD_PIN +#define YASOLR_ZCD_PIN 35 +#endif + +#ifndef YASOLR_GRID_FREQUENCY +#define YASOLR_GRID_FREQUENCY 50 +#endif + +#ifndef YASOLR_GRID_POWER_MQTT_TOPIC +#define YASOLR_GRID_POWER_MQTT_TOPIC "" +#endif + +#ifndef YASOLR_JSY_ENABLE +#define YASOLR_JSY_ENABLE false +#endif + +#ifndef YASOLR_JSY_RX_PIN +#define YASOLR_JSY_RX_PIN 17 +#endif + +#ifndef YASOLR_JSY_TX_PIN +#define YASOLR_JSY_TX_PIN 16 +#endif + +// lang + +#ifndef YASOLR_LANG +#define YASOLR_LANG "en" +#endif diff --git a/include/YaSolRWebsite.h b/include/YaSolRWebsite.h new file mode 100644 index 0000000..3912d5d --- /dev/null +++ b/include/YaSolRWebsite.h @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +#include + +#ifdef APP_VERSION_PRO +#define PUSH_BUTTON_CARD_CB () +#else +#define ENERGY_CARD GENERIC_CARD +#define PUSH_BUTTON_CARD BUTTON_CARD +#define PUSH_BUTTON_CARD_CB (int32_t value) +#endif + +namespace YaSolR { + class WebsiteClass { + public: + void init(); + void update() { _update(false); } + + private: + Statistic _firmName = Statistic(&dashboard, "Application"); + Statistic _firmModel = Statistic(&dashboard, "Application Model"); + Statistic _firmVersionStat = Statistic(&dashboard, "Application Version"); + Statistic _firmManufacturerStat = Statistic(&dashboard, "Application Manufacturer"); + + Statistic _firmFilenameStat = Statistic(&dashboard, "Firmware Filename"); + Statistic _firmTimeStat = Statistic(&dashboard, "Firmware Timestamp"); + Statistic _firmHashStat = Statistic(&dashboard, "Firmware Hash"); + + Statistic _deviceIdStat = Statistic(&dashboard, "Device Id"); + Statistic _cpuModelStat = Statistic(&dashboard, "Device CPU Model"); + Statistic _cpuCoresStat = Statistic(&dashboard, "Device CPU Cores"); + Statistic _bootCountStat = Statistic(&dashboard, "Device Boot Count"); + Statistic _heapMemoryTotalStat = Statistic(&dashboard, "Device Heap Memory Total"); + Statistic _heapMemoryUsageStat = Statistic(&dashboard, "Device Heap Memory Usage"); + Statistic _heapMemoryUsedStat = Statistic(&dashboard, "Device Heap Memory Used"); + + Statistic _netModeStat = Statistic(&dashboard, "Network Mode Preferred"); + Statistic _apIPStat = Statistic(&dashboard, "Access Point IP Address"); + Statistic _apMACStat = Statistic(&dashboard, "Access Point MAC Address"); + Statistic _ethIPStat = Statistic(&dashboard, "Ethernet IP Address"); + Statistic _ethMACStat = Statistic(&dashboard, "Ethernet MAC Address"); + Statistic _wifiIPStat = Statistic(&dashboard, "WiFi IP Address"); + Statistic _wifiMACStat = Statistic(&dashboard, "WiFi MAC Address"); + Statistic _wifiSSIDStat = Statistic(&dashboard, "WiFi SSID"); + Statistic _wifiRSSIStat = Statistic(&dashboard, "WiFi RSSI"); + Statistic _wifiSignalStat = Statistic(&dashboard, "WiFi Signal"); + + Statistic _gridVoltStat = Statistic(&dashboard, "Grid Voltage"); + Statistic _gridFreqStat = Statistic(&dashboard, "Grid Frequency"); + + Statistic _output1SwitchCountStat = Statistic(&dashboard, "Output 1 Switch Count"); + Statistic _output2SwitchCountStat = Statistic(&dashboard, "Output 2 Switch Count"); + Statistic _relay1SwitchCountStat = Statistic(&dashboard, "Relay 1 Switch Count"); + Statistic _relay2SwitchCountStat = Statistic(&dashboard, "Relay 2 Switch Count"); + + Statistic _systemTempStat = Statistic(&dashboard, "System Temperature"); + + Statistic _timeStat = Statistic(&dashboard, "Time"); + Statistic _uptimeStat = Statistic(&dashboard, "Uptime"); +#ifdef APP_VERSION_TRIAL + Statistic _trialRemainingStat = Statistic(&dashboard, "Remaining Trial Time"); +#endif + + // home + + Card _gridPower = Card(&dashboard, ENERGY_CARD, "Grid Power", "W"); + Card _routerPower = Card(&dashboard, ENERGY_CARD, "Routed Power", "W"); + Card _routerPowerFactor = Card(&dashboard, ENERGY_CARD, "Router Power Factor", "%"); + Card _routerTHDi = Card(&dashboard, ENERGY_CARD, "Router THDi", "%"); + + Card _output1State = Card(&dashboard, STATUS_CARD, "Output 1 Status", DASH_STATUS_IDLE); + Card _output1TempState = Card(&dashboard, TEMPERATURE_CARD, "Output 1 Temperature", "°C"); + Card _output1DimmerSlider = Card(&dashboard, SLIDER_CARD, "Output 1 Dimmer", "%", 0, 100, 1); + Card _output1BypassSwitch = Card(&dashboard, BUTTON_CARD, "Output 1 Bypass Relay"); + + Card _output2State = Card(&dashboard, STATUS_CARD, "Output 2 Status", DASH_STATUS_IDLE); + Card _output2TempState = Card(&dashboard, TEMPERATURE_CARD, "Output 2 Temperature", "°C"); + Card _output2DimmerSlider = Card(&dashboard, SLIDER_CARD, "Output 2 Dimmer", "%", 0, 100, 1); + Card _output2BypassSwitch = Card(&dashboard, BUTTON_CARD, "Output 2 Bypass Relay"); + + Card _relay1Switch = Card(&dashboard, BUTTON_CARD, "Relay 1"); + Card _relay2Switch = Card(&dashboard, BUTTON_CARD, "Relay 2"); + + Card _routerEnergy = Card(&dashboard, ENERGY_CARD, "Total Routed Energy", "kWh"); + +#ifdef APP_VERSION_PRO + // health + Card _stateBuzzer = Card(&dashboard, STATUS_CARD, "Buzzer", DASH_STATUS_IDLE); + Card _stateDisplay = Card(&dashboard, STATUS_CARD, "Display", DASH_STATUS_IDLE); + Card _stateGridPowerMQTT = Card(&dashboard, STATUS_CARD, "Grid Power from MQTT", DASH_STATUS_IDLE); + Card _stateJSY = Card(&dashboard, STATUS_CARD, "JSY", DASH_STATUS_IDLE); + Card _stateMQTT = Card(&dashboard, STATUS_CARD, "MQTT", DASH_STATUS_IDLE); + Card _stateNTP = Card(&dashboard, STATUS_CARD, "NTP", DASH_STATUS_IDLE); + Card _stateOutput1AutoBypass = Card(&dashboard, STATUS_CARD, "Output 1 Auto Bypass", DASH_STATUS_IDLE); + Card _stateOutput1AutoDimmer = Card(&dashboard, STATUS_CARD, "Output 1 Auto Dimmer", DASH_STATUS_IDLE); + Card _stateOutput1Bypass = Card(&dashboard, STATUS_CARD, "Output 1 Bypass Relay", DASH_STATUS_IDLE); + Card _stateOutput1Dimmer = Card(&dashboard, STATUS_CARD, "Output 1 Dimmer", DASH_STATUS_IDLE); + Card _stateOutput1Temp = Card(&dashboard, STATUS_CARD, "Output 1 Temperature", DASH_STATUS_IDLE); + Card _stateOutput2AutoBypass = Card(&dashboard, STATUS_CARD, "Output 2 Auto Bypass", DASH_STATUS_IDLE); + Card _stateOutput2AutoDimmer = Card(&dashboard, STATUS_CARD, "Output 2 Auto Dimmer", DASH_STATUS_IDLE); + Card _stateOutput2Bypass = Card(&dashboard, STATUS_CARD, "Output 2 Bypass Relay", DASH_STATUS_IDLE); + Card _stateOutput2Dimmer = Card(&dashboard, STATUS_CARD, "Output 2 Dimmer", DASH_STATUS_IDLE); + Card _stateOutput2Temp = Card(&dashboard, STATUS_CARD, "Output 2 Temperature", DASH_STATUS_IDLE); + Card _stateLEDs = Card(&dashboard, STATUS_CARD, "Physical LEDs", DASH_STATUS_IDLE); + Card _statePushButton = Card(&dashboard, STATUS_CARD, "Push Button", DASH_STATUS_IDLE); + Card _stateRelay1 = Card(&dashboard, STATUS_CARD, "Relay 1", DASH_STATUS_IDLE); + Card _stateRelay2 = Card(&dashboard, STATUS_CARD, "Relay 2", DASH_STATUS_IDLE); + Card _stateSystemTemp = Card(&dashboard, STATUS_CARD, "System Temperature", DASH_STATUS_IDLE); + Card _stateZCD = Card(&dashboard, STATUS_CARD, "ZCD", DASH_STATUS_IDLE); + + // pinout live + Card _pinLiveBuzzer = Card(&dashboard, STATUS_CARD, "Buzzer Pin", DASH_STATUS_IDLE); + Card _pinLiveDisplayClock = Card(&dashboard, STATUS_CARD, "Display Clock Pin", DASH_STATUS_IDLE); + Card _pinLiveDisplayData = Card(&dashboard, STATUS_CARD, "Display Data Pin", DASH_STATUS_IDLE); + Card _pinLiveJsyRX = Card(&dashboard, STATUS_CARD, "JSY RX Pin", DASH_STATUS_IDLE); + Card _pinLiveJsyTX = Card(&dashboard, STATUS_CARD, "JSY TX Pin", DASH_STATUS_IDLE); + Card _pinLiveLEDGreen = Card(&dashboard, STATUS_CARD, "LED Green Pin", DASH_STATUS_IDLE); + Card _pinLiveLEDYellow = Card(&dashboard, STATUS_CARD, "LED Yellow Pin", DASH_STATUS_IDLE); + Card _pinLiveLEDRed = Card(&dashboard, STATUS_CARD, "LED Red Pin", DASH_STATUS_IDLE); + Card _pinLiveOutput1Bypass = Card(&dashboard, STATUS_CARD, "Output 1 Bypass Relay Pin", DASH_STATUS_IDLE); + Card _pinLiveOutput1Dimmer = Card(&dashboard, STATUS_CARD, "Output 1 Dimmer Pin", DASH_STATUS_IDLE); + Card _pinLiveOutput1Temp = Card(&dashboard, STATUS_CARD, "Output 1 Temperature Pin", DASH_STATUS_IDLE); + Card _pinLiveOutput2Bypass = Card(&dashboard, STATUS_CARD, "Output 2 Bypass Relay Pin", DASH_STATUS_IDLE); + Card _pinLiveOutput2Dimmer = Card(&dashboard, STATUS_CARD, "Output 2 Dimmer Pin", DASH_STATUS_IDLE); + Card _pinLiveOutput2Temp = Card(&dashboard, STATUS_CARD, "Output 2 Temperature Pin", DASH_STATUS_IDLE); + Card _pinLivePushButton = Card(&dashboard, STATUS_CARD, "Push Button Pin", DASH_STATUS_IDLE); + Card _pinLiveRelay1 = Card(&dashboard, STATUS_CARD, "Relay 1 Pin", DASH_STATUS_IDLE); + Card _pinLiveRelay2 = Card(&dashboard, STATUS_CARD, "Relay 2 Pin", DASH_STATUS_IDLE); + Card _pinLiveSystemTemp = Card(&dashboard, STATUS_CARD, "System Temperature Pin", DASH_STATUS_IDLE); + Card _pinLiveZCD = Card(&dashboard, STATUS_CARD, "ZCD Sync Pin", DASH_STATUS_IDLE); + + // pinout config + Card _pinConfigBuzzer = Card(&dashboard, STATUS_CARD, "Buzzer Pin", DASH_STATUS_IDLE); + Card _pinConfigDisplayClock = Card(&dashboard, STATUS_CARD, "Display Clock Pin", DASH_STATUS_IDLE); + Card _pinConfigDisplayData = Card(&dashboard, STATUS_CARD, "Display Data Pin", DASH_STATUS_IDLE); + Card _pinConfigJsyRX = Card(&dashboard, STATUS_CARD, "JSY RX Pin", DASH_STATUS_IDLE); + Card _pinConfigJsyTX = Card(&dashboard, STATUS_CARD, "JSY TX Pin", DASH_STATUS_IDLE); + Card _pinConfigLEDGreen = Card(&dashboard, STATUS_CARD, "LED Green Pin", DASH_STATUS_IDLE); + Card _pinConfigLEDYellow = Card(&dashboard, STATUS_CARD, "LED Yellow Pin", DASH_STATUS_IDLE); + Card _pinConfigLEDRed = Card(&dashboard, STATUS_CARD, "LED Red Pin", DASH_STATUS_IDLE); + Card _pinConfigOutput1Bypass = Card(&dashboard, STATUS_CARD, "Output 1 Bypass Relay Pin", DASH_STATUS_IDLE); + Card _pinConfigOutput1Dimmer = Card(&dashboard, STATUS_CARD, "Output 1 Dimmer Pin", DASH_STATUS_IDLE); + Card _pinConfigOutput1Temp = Card(&dashboard, STATUS_CARD, "Output 1 Temperature Pin", DASH_STATUS_IDLE); + Card _pinConfigOutput2Bypass = Card(&dashboard, STATUS_CARD, "Output 2 Bypass Relay Pin", DASH_STATUS_IDLE); + Card _pinConfigOutput2Dimmer = Card(&dashboard, STATUS_CARD, "Output 2 Dimmer Pin", DASH_STATUS_IDLE); + Card _pinConfigOutput2Temp = Card(&dashboard, STATUS_CARD, "Output 2 Temperature Pin", DASH_STATUS_IDLE); + Card _pinConfigPushButton = Card(&dashboard, STATUS_CARD, "Push Button Pin", DASH_STATUS_IDLE); + Card _pinConfigRelay1 = Card(&dashboard, STATUS_CARD, "Relay 1 Pin", DASH_STATUS_IDLE); + Card _pinConfigRelay2 = Card(&dashboard, STATUS_CARD, "Relay 2 Pin", DASH_STATUS_IDLE); + Card _pinConfigSystemTemp = Card(&dashboard, STATUS_CARD, "System Temperature Pin", DASH_STATUS_IDLE); + Card _pinConfigZCD = Card(&dashboard, STATUS_CARD, "ZCD Sync Pin", DASH_STATUS_IDLE); + + // management tab + Card _otaLink = Card(&dashboard, LINK_CARD, "OTA Firmware Update"); + Card _consoleLink = Card(&dashboard, LINK_CARD, "Web Console"); + Card _configBackup = Card(&dashboard, LINK_CARD, "Configuration Backup"); + Card _configRestore = Card(&dashboard, FILE_UPLOAD_CARD, "Configuration Restore", ".txt"); + Card _debugMode = Card(&dashboard, BUTTON_CARD, "Debug Logging"); + Card _restart = Card(&dashboard, PUSH_BUTTON_CARD, "Restart"); + Card _reset = Card(&dashboard, PUSH_BUTTON_CARD, "Factory Reset"); + + // display configuration + Card _display = Card(&dashboard, BUTTON_CARD, "Display"); + Card _displayClockPin = Card(&dashboard, TEXT_INPUT_CARD, "Display Clock Pin"); + Card _displayDataPin = Card(&dashboard, TEXT_INPUT_CARD, "Display Data Pin"); + Card _displayType = Card(&dashboard, DROPDOWN_CARD, "Display Type"); + Card _displayRotation = Card(&dashboard, DROPDOWN_CARD, "Display Rotation"); + Card _displayPowerSaveDelay = Card(&dashboard, TEXT_INPUT_CARD, "Display Power Save Delay"); + + // Electricity meter configuration + Card _jsy = Card(&dashboard, BUTTON_CARD, "JSY"); + Card _jsyRXPin = Card(&dashboard, TEXT_INPUT_CARD, "JSY RX Pin (Serial TX)"); + Card _jsyTXPin = Card(&dashboard, TEXT_INPUT_CARD, "JSY TX Pin (Serial RX)"); + Card _jsyReset = Card(&dashboard, PUSH_BUTTON_CARD, "JSY Reset Energy"); + Card _zcd = Card(&dashboard, BUTTON_CARD, "ZCD"); + Card _zcdPin = Card(&dashboard, TEXT_INPUT_CARD, "ZCD Sync Pin"); + Card _gridFreq = Card(&dashboard, DROPDOWN_CARD, "Grid Frequency"); + Card _gridPowerMQTT = Card(&dashboard, TEXT_INPUT_CARD, "Grid Power MQTT Topic"); + + // mqtt configuration + Card _mqtt = Card(&dashboard, BUTTON_CARD, "MQTT"); + Card _mqttSecured = Card(&dashboard, BUTTON_CARD, "MQTT Secured"); + Card _mqttServer = Card(&dashboard, TEXT_INPUT_CARD, "MQTT Server"); + Card _mqttPort = Card(&dashboard, TEXT_INPUT_CARD, "MQTT Port"); + Card _mqttUser = Card(&dashboard, TEXT_INPUT_CARD, "MQTT User"); + Card _mqttPwd = Card(&dashboard, PASSWORD_CARD, "MQTT Password"); + Card _mqttTopic = Card(&dashboard, TEXT_INPUT_CARD, "MQTT Topic"); + Card _mqttPublishInterval = Card(&dashboard, TEXT_INPUT_CARD, "MQTT Publish Interval"); + Card _haDiscovery = Card(&dashboard, BUTTON_CARD, "Home Assistant Discovery"); + Card _haDiscoveryTopic = Card(&dashboard, TEXT_INPUT_CARD, "Home Assistant Discovery Topic"); + + // network configuration + Card _hostname = Card(&dashboard, TEXT_INPUT_CARD, "Hostname"); + Card _wifiSSID = Card(&dashboard, TEXT_INPUT_CARD, "WiFi SSID"); + Card _wifiPwd = Card(&dashboard, PASSWORD_CARD, "WiFi Password"); + Card _apMode = Card(&dashboard, BUTTON_CARD, "AP Mode"); + Card _adminPwd = Card(&dashboard, PASSWORD_CARD, "Admin Password"); + Card _ntpServer = Card(&dashboard, TEXT_INPUT_CARD, "NTP Server"); + Card _ntpTimezone = Card(&dashboard, ASYNC_DROPDOWN_CARD, "Timezone"); + Card _ntpSync = Card(&dashboard, TIME_SYNC_CARD, "Sync time with browser"); + + // output 1 configuration + Card _output1Dimmer = Card(&dashboard, BUTTON_CARD, "Output 1 Dimmer"); + Card _output1DimmerPin = Card(&dashboard, TEXT_INPUT_CARD, "Output 1 Dimmer Pin"); + Card _output1DimmerType = Card(&dashboard, DROPDOWN_CARD, "Output 1 Dimmer Type"); + Card _output1AutoDimmer = Card(&dashboard, BUTTON_CARD, "Output 1 Auto Dimmer"); + + Card _output1Bypass = Card(&dashboard, BUTTON_CARD, "Output 1 Bypass Relay"); + Card _output1BypassPin = Card(&dashboard, TEXT_INPUT_CARD, "Output 1 Bypass Relay Pin"); + Card _output1BypassType = Card(&dashboard, DROPDOWN_CARD, "Output 1 Bypass Relay Type"); + Card _output1AutoBypass = Card(&dashboard, BUTTON_CARD, "Output 1 Auto Bypass"); + + Card _output1Temp = Card(&dashboard, BUTTON_CARD, "Output 1 Temperature Sensor"); + Card _output1TempPin = Card(&dashboard, TEXT_INPUT_CARD, "Output 1 Temperature Sensor Pin"); + Card _output1DimmerPowerLimit = Card(&dashboard, SLIDER_CARD, "Output 1 Dimmer Level Limit", "%", 0, 100, 1); + Card _output1AutoStartWDays = Card(&dashboard, WEEK_SELECTOR_CARD, "Output 1 Auto Start Days"); + + Card _output1AutoStartTime = Card(&dashboard, TEXT_INPUT_CARD, "Output 1 Auto Start Time"); + Card _output1AutoStoptTime = Card(&dashboard, TEXT_INPUT_CARD, "Output 1 Auto Stop Time"); + Card _output1AutoStartTemp = Card(&dashboard, TEXT_INPUT_CARD, "Output 1 Auto Start Temperature", "°C"); + Card _output1AutoStoptTemp = Card(&dashboard, TEXT_INPUT_CARD, "Output 1 Auto Stop Temperature", "°C"); + + // output 2 configuration + Card _output2Dimmer = Card(&dashboard, BUTTON_CARD, "Output 2 Dimmer"); + Card _output2DimmerPin = Card(&dashboard, TEXT_INPUT_CARD, "Output 2 Dimmer Pin"); + Card _output2DimmerType = Card(&dashboard, DROPDOWN_CARD, "Output 2 Dimmer Type"); + Card _output2AutoDimmer = Card(&dashboard, BUTTON_CARD, "Output 2 Auto Dimmer"); + + Card _output2Bypass = Card(&dashboard, BUTTON_CARD, "Output 2 Bypass Relay"); + Card _output2BypassPin = Card(&dashboard, TEXT_INPUT_CARD, "Output 2 Bypass Relay Pin"); + Card _output2BypassType = Card(&dashboard, DROPDOWN_CARD, "Output 2 Bypass Relay Type"); + Card _output2AutoBypass = Card(&dashboard, BUTTON_CARD, "Output 2 Auto Bypass"); + + Card _output2Temp = Card(&dashboard, BUTTON_CARD, "Output 2 Temperature Sensor"); + Card _output2TempPin = Card(&dashboard, TEXT_INPUT_CARD, "Output 2 Temperature Sensor Pin"); + Card _output2DimmerPowerLimit = Card(&dashboard, SLIDER_CARD, "Output 2 Dimmer Power Limit", "%", 0, 100, 1); + Card _output2AutoStartWDays = Card(&dashboard, WEEK_SELECTOR_CARD, "Output 2 Auto Start Days"); + + Card _output2AutoStartTime = Card(&dashboard, TEXT_INPUT_CARD, "Output 2 Auto Start Time"); + Card _output2AutoStoptTime = Card(&dashboard, TEXT_INPUT_CARD, "Output 2 Auto Stop Time"); + Card _output2AutoStartTemp = Card(&dashboard, TEXT_INPUT_CARD, "Output 2 Auto Start Temperature"); + Card _output2AutoStoptTemp = Card(&dashboard, TEXT_INPUT_CARD, "Output 2 Auto Stop Temperature"); + + // relay 1 configuration + Card _relay1 = Card(&dashboard, BUTTON_CARD, "Relay 1"); + Card _relay1Pin = Card(&dashboard, TEXT_INPUT_CARD, "Relay 1 Pin"); + Card _relay1Type = Card(&dashboard, DROPDOWN_CARD, "Relay 1 Type"); + Card _relay1Power = Card(&dashboard, SLIDER_CARD, "Relay 1 Threshold", "W", 0, 3000, 50); + + // relay 2 configuration + Card _relay2 = Card(&dashboard, BUTTON_CARD, "Relay 2"); + Card _relay2Pin = Card(&dashboard, TEXT_INPUT_CARD, "Relay 2 Pin"); + Card _relay2Type = Card(&dashboard, DROPDOWN_CARD, "Relay 2 Type"); + Card _relay2Power = Card(&dashboard, SLIDER_CARD, "Relay 2 Threshold", "W", 0, 3000, 50); + + // system configuration + Card _systemTemp = Card(&dashboard, BUTTON_CARD, "System Temperature Sensor"); + Card _systemTempPin = Card(&dashboard, TEXT_INPUT_CARD, "System Temperature Sensor Pin"); + Card _buzzer = Card(&dashboard, BUTTON_CARD, "Buzzer"); + Card _buzzerPin = Card(&dashboard, TEXT_INPUT_CARD, "Buzzer Pin"); + Card _led = Card(&dashboard, BUTTON_CARD, "LED"); + Card _ledGreenPin = Card(&dashboard, TEXT_INPUT_CARD, "LED Green Pin"); + Card _ledYellowPin = Card(&dashboard, TEXT_INPUT_CARD, "LED Yellow Pin"); + Card _ledRedPin = Card(&dashboard, TEXT_INPUT_CARD, "LED Red Pin"); + Card _pushButton = Card(&dashboard, BUTTON_CARD, "Push Button"); + Card _pushButtonPin = Card(&dashboard, TEXT_INPUT_CARD, "Push Button Pin"); + Card _pushButtonAction = Card(&dashboard, DROPDOWN_CARD, "Push Button Action"); + + // https://en.wikipedia.org/wiki/List_of_Unicode_characters#Miscellaneous_Symbols + // https://en.wikipedia.org/wiki/List_of_Unicode_characters#Dingbats + // tabs + Tab _stateTab = Tab(&dashboard, "\u2764 Health"); + Tab _pinLiveTab = Tab(&dashboard, "\u26A1 Pinout Live"); + Tab _pinConfigTab = Tab(&dashboard, "\u21C6 Pinout Configured"); + Tab _managementTab = Tab(&dashboard, "\u2757 Management"); + // config tabs + Tab _displayTab = Tab(&dashboard, "\u2699 Display"); + Tab _electricityTab = Tab(&dashboard, "\u2699 Electricity"); + Tab _mqttTab = Tab(&dashboard, "\u2699 MQTT"); + Tab _networkTab = Tab(&dashboard, "\u2699 Network"); + Tab _output1Tab = Tab(&dashboard, "\u2699 Output 1"); + Tab _output2Tab = Tab(&dashboard, "\u2699 Output 2"); + Tab _relay1Tab = Tab(&dashboard, "\u2699 Relay 1"); + Tab _relay2Tab = Tab(&dashboard, "\u2699 Relay 2"); + Tab _systemTab = Tab(&dashboard, "\u2699 System"); +#endif + + private: + void _update(bool skipWebSocketPush); + + void _boolConfig(Card* card, const char* key); + void _daysConfig(Card* card, const char* key); + void _numConfig(Card* card, const char* key); + void _passwordConfig(Card* card, const char* key); + void _sliderConfig(Card* card, const char* key); + void _textConfig(Card* card, const char* key); + + void _outputDimmerSlider(Card* card, Mycila::RouterOutput* output); + void _outputRelaySwitch(Card* card, Mycila::RouterOutput* output); + void _relaySwitch(Card* card, Mycila::Relay* relay); + + void _pinout(Card* card, int32_t pin, std::map* pinout); + void _status(Card* card, const char* key, bool enabled, bool state = true, const char* err = ""); + void _temperature(Card* card, Mycila::TemperatureSensor* sensor); + }; + + extern WebsiteClass Website; +} // namespace YaSolR diff --git a/lib/ESPDASH/LICENSE b/lib/ESPDASH/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/lib/ESPDASH/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/lib/ESPDASH/README.md b/lib/ESPDASH/README.md new file mode 100644 index 0000000..170d062 --- /dev/null +++ b/lib/ESPDASH/README.md @@ -0,0 +1,85 @@ +

+
+

+

+
+
+ +

+ +  + +  + +

+ +
+
+

DASH V4 is the 4th-gen blazing fast library to create a functional & real-time dashboard for ESP8266 & ESP32 microcontrollers. This library includes charts, display cards, interactive buttons and many more components to create a perfect dashboard which is accessible locally via your IoT device's IP. DASH does not require any kind of internet connection, everything is stored locally.

+ +
+ +

Features

+ +- 🔥 Automatic webpage generation. +- ⏱️ Realtime updates to all connected clients. +- 🎷 No need to learn HTML/CSS/JS. ESP-DASH provides you with a C++ interface. +- 🛫 Ready to use components for your data. +- 🏀 It's dynamic! Add or remove components anytime from the webpage. +- 📈 Inbuilt charts support. + +
+
+ + +

Documentation

+

Learn more about Installation & Usage: Click Here

+ +
+
+ +

Open-Source Preview

+ + +
+
+ +

Want More? Upgrade to Pro

+ +ESP-DASH Pro comes with the following extended functionality: +- 11 Exclusive Widgets ( Line, Area, Pie, Text Input, Joystick and more! ) +- Tabs Support ~ Add multiple pages to your dashboard. +- Custom Title +- Custom Branding +- Custom Widget Size +- Enable/Disable Animations +- Atlast, It is a fantastic way to support the developer for the time went into the making & maintaining the library. + +
Available here: + +- [Official Website](https://espdash.pro) + +
+ + + ESP-DASH Pro + + +
+
+ +

Contributions

+

Every contribution to this repository is highly appreciated! If you spot any bug or problem, open a issue or pull request so that it can be rectified for everyone.

+ +**For feature requests:** Please open a issue and I'll add the feature in a future release once I get some time in my hands. + +
+
+ + +

License

+ +ESP-DASH is licensed under General Public License v3 ( GPLv3 ). If you are intending to use ESP-DASH in a commercial project, please consider buying the [ESP-DASH Pro](https://espdash.pro) which comes with a less restrictive SCL-1.0 license ( SOFTT Commercial License 1.0 ). + +
+
diff --git a/lib/ESPDASH/keywords.txt b/lib/ESPDASH/keywords.txt new file mode 100644 index 0000000..1e31794 --- /dev/null +++ b/lib/ESPDASH/keywords.txt @@ -0,0 +1,13 @@ +ESPDash KEYWORD1 +Card KEYWORD1 +Chart KEYWORD1 +Statistic KEYWORD1 +setAuthentication KEYWORD2 +sendUpdates KEYWORD2 +refreshLayout KEYWORD2 +add KEYWORD2 +remove KEYWORD2 +update KEYWORD2 +attachCallback KEYWORD2 +updateX KEYWORD2 +updateY KEYWORD2 \ No newline at end of file diff --git a/lib/ESPDASH/library.json b/lib/ESPDASH/library.json new file mode 100644 index 0000000..021652b --- /dev/null +++ b/lib/ESPDASH/library.json @@ -0,0 +1,30 @@ +{ + "name": "ESP-DASH", + "keywords": "EspDash, esp8266, esp32, ui, dashboard, user, ux, interface, gui, iot, arduino, Wifi, server", + "description": "A blazing fast library to create realtime dashboards for ESP8266's and ESP32's.", + "repository": + { + "type": "git", + "url": "https://github.com/ayushsharma82/ESP-DASH.git" + }, + "authors": + [ + { + "name": "Ayush Sharma", + "email": "asrocks5@gmail.com", + "maintainer": true + } + ], + "version": "4.0.4", + "frameworks": "arduino", + "platforms": ["espressif32", "espressif8266"], + "dependencies": + [ + { + "name": "ESP Async WebServer" + }, + { + "name": "ArduinoJson" + } + ] +} diff --git a/lib/ESPDASH/library.properties b/lib/ESPDASH/library.properties new file mode 100644 index 0000000..f571f9a --- /dev/null +++ b/lib/ESPDASH/library.properties @@ -0,0 +1,9 @@ +name=ESP-DASH +version=4.0.4 +author=Ayush Sharma +category=Communication +maintainer=Ayush Sharma +sentence=A blazing fast library to create realtime dashboards for ESP8266's and ESP32's. +paragraph=ESP-DASH lets you create functional and beautiful dashboards for your ESP8266 / ESP32 without the need of an internet connection. +url=https://github.com/ayushsharma82/ESP-DASH +architectures=esp8266,esp32 diff --git a/lib/ESPDASH/src/Card.cpp b/lib/ESPDASH/src/Card.cpp new file mode 100644 index 0000000..01d9003 --- /dev/null +++ b/lib/ESPDASH/src/Card.cpp @@ -0,0 +1,140 @@ +#include "Card.h" + +/* + Constructor +*/ +Card::Card(ESPDash *dashboard, const int type, const char* name, const char* symbol, const int min, const int max, const int step){ + _dashboard = dashboard; + _id = dashboard->nextId(); + _type = type; + _name = name; + _symbol = symbol; + _value_min = min; + _value_max = max; + _value_step = step; + _dashboard->add(this); +} + +/* + Attach Function Callback +*/ +void Card::attachCallback(std::function cb){ + _callback = cb; +} + + +/* + Value update methods +*/ +void Card::update(int value, const char* symbol){ + /* Clear String if it was used before */ + if(_value_type == Card::STRING){ + _value_s = ""; + } + /* Store new value */ + _value_type = Card::INTEGER; + if(strcmp(_symbol.c_str(), symbol) != 0 || _value_i != value) + _changed = true; + _value_i = value; + _symbol = symbol; +} + +void Card::update(int value){ + /* Clear String if it was used before */ + if(_value_type == Card::STRING){ + _value_s = ""; + } + /* Store new value */ + _value_type = Card::INTEGER; + if(_value_i != value) + _changed = true; + _value_i = value; +} + +void Card::update(float value, const char* symbol){ + /* Clear String if it was used before */ + if(_value_type == Card::STRING){ + _value_s = ""; + } + /* Store new value */ + _value_type = Card::FLOAT; + if(strcmp(_symbol.c_str(), symbol) != 0 || _value_f != value) + _changed = true; + _value_f = value; + _symbol = symbol; +} + +void Card::update(float value){ + /* Clear String if it was used before */ + if(_value_type == Card::STRING){ + _value_s = ""; + } + /* Store new value */ + _value_type = Card::FLOAT; + if(_value_f != value) + _changed = true; + _value_f = value; +} + +void Card::update(const String &value, const char* symbol){ + update(value.c_str(), symbol); +} + +void Card::update(const String &value){ + update(value.c_str()); +} + +void Card::update(const char* value, const char* symbol){ + if(_value_type == Card::STRING){ + if(strcmp(_value_s.c_str(), value) != 0) + _changed = true; + } + if (strcmp(_symbol.c_str(), symbol) != 0) { + _changed = true; + } + _value_type = Card::STRING; + _symbol = symbol; + _value_s = value; +} + +void Card::update(const char* value){ + if(_value_type == Card::STRING){ + if(strcmp(_value_s.c_str(), value) != 0) + _changed = true; + } + + _value_type = Card::STRING; + _value_s = value; +} + +void Card::update(bool value, const char* symbol){ + /* Clear String if it was used before */ + if(_value_type == Card::STRING){ + _value_s = ""; + } + /* Store new value */ + _value_type = Card::INTEGER; + if(strcmp(_symbol.c_str(), symbol) != 0 || _value_i != value) + _changed = true; + _value_i = value; + _symbol = symbol; +} + +void Card::update(bool value){ + /* Clear String if it was used before */ + if(_value_type == Card::STRING){ + _value_s = ""; + } + /* Store new value */ + _value_type = Card::INTEGER; + if(_value_i != value) + _changed = true; + _value_i = value; +} + +/* + Destructor +*/ +Card::~Card(){ + _dashboard->remove(this); +} diff --git a/lib/ESPDASH/src/Card.h b/lib/ESPDASH/src/Card.h new file mode 100644 index 0000000..7c0747e --- /dev/null +++ b/lib/ESPDASH/src/Card.h @@ -0,0 +1,70 @@ +#ifndef __CARD_H +#define __CARD_H + + +#include +#include "Arduino.h" + +#include "ESPDash.h" +#include "ArduinoJson.h" + +struct CardNames { + int value; + const char* type; +}; + +// functions defaults to zero (number card) +enum { + GENERIC_CARD, + TEMPERATURE_CARD, + HUMIDITY_CARD, + STATUS_CARD, + SLIDER_CARD, + BUTTON_CARD, + PROGRESS_CARD +}; + +// Forward Declaration +class ESPDash; + +// Card Class +class Card { + private: + ESPDash *_dashboard; + + uint32_t _id; + const char* _name; + int _type; + bool _changed; + enum { INTEGER, FLOAT, STRING } _value_type; + union alignas(4) { + float _value_f; + int _value_i; + }; + String _value_s; + int _value_min; + int _value_max; + int _value_step; + String _symbol; + std::function _callback = nullptr; + + public: + Card(ESPDash *dashboard, const int type, const char* name, const char* symbol = "", const int min = 0, const int max = 0, const int step = 1); + void attachCallback(std::function cb); + void update(int value); + void update(int value, const char* symbol); + void update(bool value); + void update(bool value, const char* symbol); + void update(float value); + void update(float value, const char* symbol); + void update(const char* value); + void update(const char* value, const char* symbol); + void update(const String &value); + void update(const String &value, const char* symbol); + ~Card(); + + friend class ESPDash; +}; + + +#endif diff --git a/lib/ESPDASH/src/Chart.cpp b/lib/ESPDASH/src/Chart.cpp new file mode 100644 index 0000000..4088c25 --- /dev/null +++ b/lib/ESPDASH/src/Chart.cpp @@ -0,0 +1,144 @@ +#include "Chart.h" + +/* + Constructor +*/ +Chart::Chart(ESPDash *dashboard, const int type, const char* name){ + _dashboard = dashboard; + _id = dashboard->nextId(); + _type = type; + _name = name; + _dashboard->add(this); +} + +#if DASH_USE_LEGACY_CHART_STORAGE == 1 + void Chart::emptyXAxisVectors() { + if(!_x_axis_i.Empty()) + _x_axis_i.Clear(); + if(!_x_axis_f.Empty()) + _x_axis_f.Clear(); + if(!_x_axis_s.Empty()) + _x_axis_s.Clear(); + } + + void Chart::emptyYAxisVectors() { + if(!_y_axis_i.Empty()) + _y_axis_i.Clear(); + if(!_y_axis_f.Empty()) + _y_axis_f.Clear(); + } +#else + void Chart::clearXAxisPointers() { + _x_axis_i_ptr = nullptr; + _x_axis_f_ptr = nullptr; + _x_axis_char_ptr = nullptr; + _x_axis_s_ptr = nullptr; + _x_axis_ptr_size = 0; + } + + void Chart::clearYAxisPointers() { + _y_axis_i_ptr = nullptr; + _y_axis_f_ptr = nullptr; + _y_axis_ptr_size = 0; + } +#endif + +/* + Value update methods +*/ +void Chart::updateX(int arr_x[], size_t x_size){ + _x_axis_type = GraphAxisType::INTEGER; + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + emptyXAxisVectors(); + for(int i=0; i < x_size; i++){ + _x_axis_i.PushBack(arr_x[i]); + } + #else + clearXAxisPointers(); + _x_axis_i_ptr = arr_x; + _x_axis_ptr_size = x_size; + #endif + _changed = true; +} + +void Chart::updateX(float arr_x[], size_t x_size){ + _x_axis_type = GraphAxisType::FLOAT; + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + emptyXAxisVectors(); + for(int i=0; i < x_size; i++){ + _x_axis_f.PushBack(arr_x[i]); + } + #else + clearXAxisPointers(); + _x_axis_f_ptr = arr_x; + _x_axis_ptr_size = x_size; + #endif + _changed = true; +} + +void Chart::updateX(String arr_x[], size_t x_size){ + _x_axis_type = GraphAxisType::STRING; + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + emptyXAxisVectors(); + for(int i=0; i < x_size; i++){ + _x_axis_s.PushBack(arr_x[i].c_str()); + } + #else + clearXAxisPointers(); + _x_axis_s_ptr = arr_x; + _x_axis_ptr_size = x_size; + #endif + _changed = true; +} + +void Chart::updateX(const char* arr_x[], size_t x_size){ + _x_axis_type = GraphAxisType::CHAR; + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + emptyXAxisVectors(); + for(int i=0; i < x_size; i++){ + _x_axis_s.PushBack(String(arr_x[i])); + } + #else + clearXAxisPointers(); + _x_axis_char_ptr = arr_x; + _x_axis_ptr_size = x_size; + #endif + _changed = true; +} + +void Chart::updateY(int arr_y[], size_t y_size){ + _y_axis_type = GraphAxisType::INTEGER; + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + emptyYAxisVectors(); + for(int i=0; i < y_size; i++){ + _y_axis_i.PushBack(arr_y[i]); + } + #else + clearYAxisPointers(); + _y_axis_i_ptr = arr_y; + _y_axis_ptr_size = y_size; + #endif + _changed = true; +} + +void Chart::updateY(float arr_y[], size_t y_size){ + _y_axis_type = GraphAxisType::FLOAT; + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + emptyYAxisVectors(); + for(int i=0; i < y_size; i++){ + _y_axis_f.PushBack(arr_y[i]); + } + #else + clearYAxisPointers(); + _y_axis_f_ptr = arr_y; + _y_axis_ptr_size = y_size; + #endif + _changed = true; +} + +/* + Destructor +*/ +Chart::~Chart(){ + _dashboard->remove(this); +} \ No newline at end of file diff --git a/lib/ESPDASH/src/Chart.h b/lib/ESPDASH/src/Chart.h new file mode 100644 index 0000000..796b1a3 --- /dev/null +++ b/lib/ESPDASH/src/Chart.h @@ -0,0 +1,82 @@ +#ifndef __CHART_H +#define __CHART_H + +#include +#include "Arduino.h" +#include "vector.h" + +#include "ESPDash.h" +#include "ArduinoJson.h" + +#ifndef DASH_USE_LEGACY_CHART_STORAGE + #define DASH_USE_LEGACY_CHART_STORAGE 0 +#endif + +// Default to Line Chart +enum { + BAR_CHART, +}; + +struct ChartNames { + int value; + const char* type; +}; + +enum GraphAxisType { INTEGER, FLOAT, CHAR, STRING }; + +// Forward Declaration +class ESPDash; + +// Chart Class +class Chart { + private: + ESPDash *_dashboard; + + uint32_t _id; + const char *_name; + int _type; + bool _changed; + GraphAxisType _x_axis_type; + GraphAxisType _y_axis_type; + + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + /* X-Axis */ + Vector _x_axis_i; + Vector _x_axis_f; + Vector _x_axis_s; + /* Y-Axis */ + Vector _y_axis_i; + Vector _y_axis_f; + + void emptyXAxisVectors(); + void emptyYAxisVectors(); + #else + /* X-Axis */ + int *_x_axis_i_ptr = nullptr; + float *_x_axis_f_ptr = nullptr; + const char **_x_axis_char_ptr = nullptr; + String *_x_axis_s_ptr = nullptr; + unsigned int _x_axis_ptr_size = 0; + /* Y-Axis */ + int *_y_axis_i_ptr = nullptr; + float *_y_axis_f_ptr = nullptr; + unsigned int _y_axis_ptr_size = 0; + + void clearXAxisPointers(); + void clearYAxisPointers(); + #endif + + public: + Chart(ESPDash *dashboard, const int type, const char* name); + void updateX(int arr_x[], size_t x_size); + void updateX(float arr_x[], size_t x_size); + void updateX(String arr_x[], size_t x_size); + void updateX(const char* arr_x[], size_t x_size); + void updateY(int arr_y[], size_t y_size); + void updateY(float arr_y[], size_t y_size); + ~Chart(); + + friend class ESPDash; +}; + +#endif \ No newline at end of file diff --git a/lib/ESPDASH/src/ESPDash.cpp b/lib/ESPDASH/src/ESPDash.cpp new file mode 100644 index 0000000..f7b52b4 --- /dev/null +++ b/lib/ESPDASH/src/ESPDash.cpp @@ -0,0 +1,515 @@ +#include "ESPDash.h" + +// Integral type to string pairs events +// ID, type +struct CardNames cardTags[] = { + {GENERIC_CARD, "generic"}, + {TEMPERATURE_CARD, "temperature"}, + {HUMIDITY_CARD, "humidity"}, + {STATUS_CARD, "status"}, + {SLIDER_CARD, "slider"}, + {BUTTON_CARD, "button"}, + {PROGRESS_CARD, "progress"}, +}; + +// Integral type to string pairs events +// ID, type +struct ChartNames chartTags[] = { + {BAR_CHART, "bar"}, +}; + + +/* + Constructors +*/ +ESPDash::ESPDash(AsyncWebServer* server) : ESPDash(server, "/", true) {} + +ESPDash::ESPDash(AsyncWebServer* server, bool enable_default_stats) : ESPDash(server, "/", enable_default_stats) {} + +ESPDash::ESPDash(AsyncWebServer* server, const char* uri, bool enable_default_stats) { + _server = server; + default_stats_enabled = enable_default_stats; + + // Initialize AsyncWebSocket + _ws = new AsyncWebSocket("/dashws"); + + // Attach AsyncWebServer Routes + _server->on(uri, HTTP_GET, [this](AsyncWebServerRequest *request){ + if(basic_auth){ + if(!request->authenticate(username, password)) + return request->requestAuthentication(); + } + // respond with the compressed frontend + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", DASH_HTML, sizeof(DASH_HTML)); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", "public, max-age=900"); + request->send(response); + }); + + // Websocket Callback Handler + _ws->onEvent([&](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len){ + // Request Buffer +#if ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument json; +#else + StaticJsonDocument<200> json; +#endif + + if (type == WS_EVT_DATA) { + AwsFrameInfo * info = (AwsFrameInfo * ) arg; + if (info -> final && info -> index == 0 && info -> len == len) { + if (info -> opcode == WS_TEXT) { + data[len] = 0; + deserializeJson(json, reinterpret_cast(data)); + // client side commands parsing + if (json["command"] == "get:layout") { + _asyncAccessInProgress = true; + generateLayoutJSON(client, false); + _asyncAccessInProgress = false; + } else if (json["command"] == "ping") { + return _ws->text(client->id(), "{\"command\":\"pong\"}"); + } else if (json["command"] == "button:clicked") { + // execute and reference card data struct to funtion + uint32_t id = json["id"].as(); + for(int i=0; i < cards.Size(); i++){ + Card *p = cards[i]; + if(id == p->_id){ + if(p->_callback != nullptr){ + _asyncAccessInProgress = true; + p->_callback(json["value"].as()); + _asyncAccessInProgress = false; + } + } + } + } else if (json["command"] == "slider:changed") { + // execute and reference card data struct to funtion + uint32_t id = json["id"].as(); + for(int i=0; i < cards.Size(); i++){ + Card *p = cards[i]; + if(id == p->_id){ + if(p->_callback != nullptr){ + _asyncAccessInProgress = true; + p->_callback(json["value"].as()); + _asyncAccessInProgress = false; + } + } + } + } + } + } + } + }); + + // Attach Websocket Instance to AsyncWebServer + _server->addHandler(_ws); +} + +void ESPDash::setAuthentication(const char *user, const char *pass) { + basic_auth = strlen(user) > 0 && strlen(pass) > 0; + if(basic_auth) { + strncpy(username, user, sizeof(username)); + strncpy(password, pass, sizeof(password)); + _ws->setAuthentication(user, pass); + } +} + +void ESPDash::setAuthentication(const String &user, const String &pass) { + setAuthentication(user.c_str(), pass.c_str()); +} + +// Add Card +void ESPDash::add(Card *card) { + cards.PushBack(card); +} + +// Remove Card +void ESPDash::remove(Card *card) { + for(int i=0; i < cards.Size(); i++){ + Card *p = cards[i]; + if(p->_id == card->_id){ + cards.Erase(i); + return; + } + } +} + + +// Add Chart +void ESPDash::add(Chart *chart) { + charts.PushBack(chart); +} + +// Remove Card +void ESPDash::remove(Chart *chart) { + for(int i=0; i < charts.Size(); i++){ + Chart *p = charts[i]; + if(p->_id == chart->_id){ + charts.Erase(i); + return; + } + } +} + +// Add Statistic +void ESPDash::add(Statistic *statistic) { + statistics.PushBack(statistic); +} + +// Remove Statistic +void ESPDash::remove(Statistic *statistic) { + for(int i=0; i < statistics.Size(); i++){ + Statistic *p = statistics[i]; + if(p->_id == statistic->_id){ + statistics.Erase(i); + return; + } + } +} + +// generates the layout JSON string to the frontend +void ESPDash::generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only, Card* onlyCard) { +#if ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument doc(DASH_JSON_DOCUMENT_ALLOCATION); +#else + JsonDocument doc; +#endif + + // preparing layout + if (!changes_only) { + doc["command"] = "update:layout:begin"; + } else { + doc["command"] = "update:components"; + } + if (!changes_only) { + send(client, doc); + } + + // Generate JSON for all Cards + doc["command"] = changes_only ? "update:components" : "update:layout:next"; + for (int i = 0; i < cards.Size(); i++) { + Card* c = cards[i]; + if (changes_only) { + if (!c->_changed && (onlyCard == nullptr || onlyCard->_id != c->_id)) { + continue; + } + } + + // Generate JSON +#if ARDUINOJSON_VERSION_MAJOR == 6 + JsonObject obj = doc["cards"].createNestedObject(); +#else + JsonObject obj = doc["cards"].add(); +#endif + generateComponentJSON(obj, c, changes_only); + + if (overflowed(doc)) { + doc["cards"].as().remove(doc["cards"].as().size() - 1); + send(client, doc); + doc["command"] = changes_only ? "update:components" : "update:layout:next"; + i--; + continue; + } + + // Clear change flags + if (changes_only) { + c->_changed = false; + } + } + + if (doc["cards"].as().size() > 0) + send(client, doc); + + // Generate JSON for all Charts + doc["command"] = changes_only ? "update:components" : "update:layout:next"; + for (int i = 0; i < charts.Size(); i++) { + Chart* c = charts[i]; + if (changes_only) { + if (!c->_changed) { + continue; + } + } + + // Generate JSON +#if ARDUINOJSON_VERSION_MAJOR == 7 + JsonObject obj = doc["charts"].add(); +#else + JsonObject obj = doc["charts"].createNestedObject(); +#endif + generateComponentJSON(obj, c, changes_only); + + if (overflowed(doc)) { + doc["charts"].as().remove(doc["charts"].as().size() - 1); + send(client, doc); + doc["command"] = changes_only ? "update:components" : "update:layout:next"; + i--; + continue; + } + + // Clear change flags + if (changes_only) { + c->_changed = false; + } + } + + if (doc["charts"].as().size() > 0) + send(client, doc); + + // Generate JSON for all Statistics + doc["command"] = changes_only ? "update:components" : "update:layout:next"; + int idx = 0; + + // Check if default statistics are needed + if (default_stats_enabled) { + if (!changes_only) { + // Hardware + doc["stats"][idx]["i"] = -1; + doc["stats"][idx]["k"] = "Hardware"; + doc["stats"][idx]["v"] = DASH_HARDWARE; + idx++; + + // SDK Version + doc["stats"][idx]["i"] = -2; + doc["stats"][idx]["k"] = "SDK Version"; +#if defined(ESP8266) + doc["stats"][idx]["v"] = ESP.getCoreVersion(); +#elif defined(ESP32) + doc["stats"][idx]["v"] = String(esp_get_idf_version()); +#endif + idx++; + + // MAC Address + doc["stats"][idx]["i"] = -3; + doc["stats"][idx]["k"] = "MAC Address"; + doc["stats"][idx]["v"] = WiFi.macAddress(); + idx++; + } + + // Free Heap + doc["stats"][idx]["i"] = -4; + doc["stats"][idx]["k"] = "Free Heap (SRAM)"; + doc["stats"][idx]["v"] = ESP.getFreeHeap(); + idx++; + + // WiFi Mode + doc["stats"][idx]["i"] = -5; + doc["stats"][idx]["k"] = "WiFi Mode"; + doc["stats"][idx]["v"] = WiFi.getMode(); + idx++; + + // WiFi Signal + doc["stats"][idx]["i"] = -6; + doc["stats"][idx]["k"] = "WiFi Signal"; + doc["stats"][idx]["v"] = WiFi.RSSI(); + idx++; + } + + // Loop through user defined stats + for (int i = 0; i < statistics.Size(); i++) { + Statistic* s = statistics[i]; + if (changes_only) { + if (!s->_changed) { + continue; + } + } + + doc["stats"][idx]["i"] = s->_id; + doc["stats"][idx]["k"] = s->_key; + if (changes_only || strlen(s->_value) > 0) + doc["stats"][idx]["v"] = s->_value; + doc["stats"][idx]["v"] = s->_value; + idx++; + + if (overflowed(doc)) { + doc["stats"].as().remove(idx - 1); + send(client, doc); + doc["command"] = changes_only ? "update:components" : "update:layout:next"; + i--; + idx = 0; + continue; + } + + // Clear change flags + if (changes_only) { + s->_changed = false; + } + } + + if (idx > 0) + send(client, doc); +} + +void ESPDash::send(AsyncWebSocketClient* client, JsonDocument& doc) { + const size_t len = measureJson(doc); + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); + serializeJson(doc, buffer->get(), len); + if (client != nullptr) { + client->text(buffer); + } else { + _ws->textAll(buffer); + } + doc.clear(); +} + +bool ESPDash::overflowed(JsonDocument& doc) { + return doc.overflowed() || measureJson(doc.as()) > DASH_JSON_SIZE; +} + +/* + Generate Card JSON +*/ +void ESPDash::generateComponentJSON(JsonObject& doc, Card* card, bool change_only){ + doc["id"] = card->_id; + if (!change_only){ + doc["n"] = card->_name; + doc["t"] = cardTags[card->_type].type; + doc["min"] = card->_value_min; + doc["max"] = card->_value_max; + if (card->_value_step != 1) + doc["step"] = card->_value_step; + } + if(change_only || !card->_symbol.isEmpty()) + doc["s"] = card->_symbol; + + switch (card->_value_type) { + case Card::INTEGER: + doc["v"] = card->_value_i; + break; + case Card::FLOAT: + doc["v"] = String(card->_value_f, 2); + break; + case Card::STRING: + if(change_only || !card->_value_s.isEmpty()) { + doc["v"] = card->_value_s; + } + break; + default: + // blank value + break; + } +} + + +/* + Generate Chart JSON +*/ +void ESPDash::generateComponentJSON(JsonObject& doc, Chart* chart, bool change_only){ + doc["id"] = chart->_id; + if(!change_only){ + doc["n"] = chart->_name; + doc["t"] = chartTags[chart->_type].type; + } + + JsonArray xAxis = doc["x"].to(); + switch (chart->_x_axis_type) { + case GraphAxisType::INTEGER: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_x_axis_i.Size(); i++) + xAxis.add(chart->_x_axis_i[i]); + #else + if (chart->_x_axis_i_ptr != nullptr) { + for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) + xAxis.add(chart->_x_axis_i_ptr[i]); + } + #endif + break; + case GraphAxisType::FLOAT: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_x_axis_f.Size(); i++) + xAxis.add(chart->_x_axis_f[i]); + #else + if (chart->_x_axis_f_ptr != nullptr) { + for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) + xAxis.add(chart->_x_axis_f_ptr[i]); + } + #endif + break; + case GraphAxisType::CHAR: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_x_axis_s.Size(); i++) + xAxis.add(chart->_x_axis_s[i].c_str()); + #else + if (chart->_x_axis_char_ptr != nullptr) { + for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) + xAxis.add(chart->_x_axis_char_ptr[i]); + } + #endif + break; + case GraphAxisType::STRING: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_x_axis_s.Size(); i++) + xAxis.add(chart->_x_axis_s[i].c_str()); + #else + if (chart->_x_axis_s_ptr != nullptr) { + for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) + xAxis.add(chart->_x_axis_s_ptr[i]); + } + #endif + break; + default: + // blank value + break; + } + + JsonArray yAxis = doc["y"].to(); + switch (chart->_y_axis_type) { + case GraphAxisType::INTEGER: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_y_axis_i.Size(); i++) + yAxis.add(chart->_y_axis_i[i]); + #else + if (chart->_y_axis_i_ptr != nullptr) { + for(unsigned int i=0; i < chart->_y_axis_ptr_size; i++) + yAxis.add(chart->_y_axis_i_ptr[i]); + } + #endif + break; + case GraphAxisType::FLOAT: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_y_axis_f.Size(); i++) + yAxis.add(chart->_y_axis_f[i]); + #else + if (chart->_y_axis_f_ptr != nullptr) { + for(unsigned int i=0; i < chart->_y_axis_ptr_size; i++) + yAxis.add(chart->_y_axis_f_ptr[i]); + } + #endif + break; + default: + // blank value + break; + } +} + +/* Send Card Updates to all clients */ +void ESPDash::sendUpdates(bool force) { + _ws->cleanupClients(DASH_MAX_WS_CLIENTS); + if (!hasClient()) { + return; + } + generateLayoutJSON(nullptr, !force); +} + +void ESPDash::refreshCard(Card *card) { + _ws->cleanupClients(DASH_MAX_WS_CLIENTS); + if (!hasClient()) { + return; + } + generateLayoutJSON(nullptr, true, card); +} + +uint32_t ESPDash::nextId() { + return _idCounter++; +} + +bool ESPDash::hasClient() { + return _ws->count() > 0; +} + +/* + Destructor +*/ +ESPDash::~ESPDash(){ + _server->removeHandler(_ws); + delete _ws; +} diff --git a/lib/ESPDASH/src/ESPDash.h b/lib/ESPDASH/src/ESPDash.h new file mode 100644 index 0000000..ab54d14 --- /dev/null +++ b/lib/ESPDASH/src/ESPDash.h @@ -0,0 +1,135 @@ +/* +____ ____ ___ ___ ____ ____ _ _ +|___ [__ |__] | \ |__| [__ |__| +|___ ___] | |__/ | | ___] | | + +ESP-DASH V3 +--------------------- +Author: Ayush Sharma +First Commit: Nov 5, 2017 +Github URL: https://github.com/ayushsharma82/ESP-DASH + +*/ + +#ifndef ESPDash_h +#define ESPDash_h + +#include +#include +#include +#include + +#include "Arduino.h" +#include "stdlib_noniso.h" +#include "dash_webpage.h" +#include "vector.h" + +#if defined(ESP8266) + #define DASH_HARDWARE "ESP8266" + #include "ESP8266WiFi.h" + #include "ESPAsyncTCP.h" +#elif defined(ESP32) + #define DASH_HARDWARE "ESP32" + #include "WiFi.h" + #include "AsyncTCP.h" +#endif + +#define DASH_STATUS_IDLE "i" +#define DASH_STATUS_SUCCESS "s" +#define DASH_STATUS_WARNING "w" +#define DASH_STATUS_DANGER "d" + +#include "ESPAsyncWebServer.h" +#include "ArduinoJson.h" +#include "Card.h" +#include "Chart.h" +#include "Statistic.h" + +#ifndef DASH_JSON_SIZE +#define DASH_JSON_SIZE 2048 +#endif + +#if ARDUINOJSON_VERSION_MAJOR == 6 && !defined(DASH_JSON_DOCUMENT_ALLOCATION) +#define DASH_JSON_DOCUMENT_ALLOCATION DASH_JSON_SIZE * 3 +#endif + +#ifndef DASH_USE_LEGACY_CHART_STORAGE + #define DASH_USE_LEGACY_CHART_STORAGE 0 +#endif + +#ifndef DASH_MAX_WS_CLIENTS + #define DASH_MAX_WS_CLIENTS DEFAULT_MAX_WS_CLIENTS +#endif + +// Forward Declaration +class Card; +class Chart; +class Statistic; + +// ESPDASH Class +class ESPDash{ + private: + AsyncWebServer* _server = nullptr; + AsyncWebSocket* _ws = nullptr; + + Vector cards; + Vector charts; + Vector statistics; + bool default_stats_enabled = false; + bool basic_auth = false; + char username[64]; + char password[64]; + uint32_t _idCounter = 0; + + volatile bool _asyncAccessInProgress = false; + + // Generate layout json + void generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only = false, Card* onlyCard = nullptr); + void send(AsyncWebSocketClient* client, JsonDocument& doc); + bool overflowed(JsonDocument& doc); + + // Generate Component JSON + void generateComponentJSON(JsonObject& obj, Card* card, bool change_only = false); + void generateComponentJSON(JsonObject& obj, Chart* chart, bool change_only = false); + + public: + ESPDash(AsyncWebServer* server, const char* uri, bool enable_default_stats = true); + ESPDash(AsyncWebServer* server, bool enable_default_stats); + ESPDash(AsyncWebServer* server); + + // Set Authentication + void setAuthentication(const char* user, const char* pass); + void setAuthentication(const String &user, const String &pass); + + // Add Card + void add(Card *card); + // Remove Card + void remove(Card *card); + + // Add Chart + void add(Chart *card); + // Remove Chart + void remove(Chart *card); + + // Add Statistic + void add(Statistic *statistic); + // Remove Statistic + void remove(Statistic *statistic); + + // Notify client side to update values + void sendUpdates(bool force = false); + void refreshLayout() { sendUpdates(true); } + void refreshCard(Card *card); + + uint32_t nextId(); + + bool hasClient(); + + // can be used to check if the async_http task might currently access the cards data, + // in which case you should not modify them + bool isAsyncAccessInProgress() { return _asyncAccessInProgress; } + + ~ESPDash(); +}; + +#endif diff --git a/lib/ESPDASH/src/Statistic.cpp b/lib/ESPDASH/src/Statistic.cpp new file mode 100644 index 0000000..b32bddc --- /dev/null +++ b/lib/ESPDASH/src/Statistic.cpp @@ -0,0 +1,22 @@ +#include "Statistic.h" + +Statistic::Statistic(ESPDash *dashboard, const char *key, const char *value) { + _dashboard = dashboard; + _id = dashboard->nextId(); + // Safe copy + _key = key; + strncpy(_value, value, sizeof(_value)); + _dashboard->add(this); +} + +void Statistic::set(const char *value) { + // Safe copy + _changed = strcmp(_value, value) != 0; + if(_changed) + strncpy(_value, value, sizeof(_value)); + +} + +Statistic::~Statistic() { + _dashboard->remove(this); +} \ No newline at end of file diff --git a/lib/ESPDASH/src/Statistic.h b/lib/ESPDASH/src/Statistic.h new file mode 100644 index 0000000..bf796dc --- /dev/null +++ b/lib/ESPDASH/src/Statistic.h @@ -0,0 +1,30 @@ +#ifndef __STAT_H +#define __STAT_H + +#include +#include "Arduino.h" +#include "vector.h" + +#include "ESPDash.h" +#include "ArduinoJson.h" + +// Forward Declaration +class ESPDash; + +class Statistic { + private: + ESPDash *_dashboard; + uint32_t _id; + const char *_key; + char _value[64]; + bool _changed = false; + + public: + Statistic(ESPDash *dashboard, const char *key, const char *value = ""); + void set(const char *value); + ~Statistic(); + + friend class ESPDash; +}; + +#endif \ No newline at end of file diff --git a/lib/ESPDASH/src/dash_webpage.cpp b/lib/ESPDASH/src/dash_webpage.cpp new file mode 100644 index 0000000..0096c7e --- /dev/null +++ b/lib/ESPDASH/src/dash_webpage.cpp @@ -0,0 +1,3008 @@ +#include "dash_webpage.h" + +const uint8_t DASH_HTML[90101] PROGMEM = { +31,139,8,0,0,0,0,0,2,3,204,57,105,115,219,184,146,223,231,87,200,92,175,30,81,129,104,121,102,178, +7,101,196,149,115,238,73,42,154,119,69,165,242,163,200,150,132,49,5,112,0,208,182,34,235,191,111,55,120,128, +118,146,122,95,246,154,67,106,162,27,125,95,148,47,78,94,189,125,249,219,223,223,189,30,109,221,174,124,246,213, +5,125,141,202,76,109,68,4,42,122,246,213,104,116,177,133,172,32,0,193,29,184,108,148,111,51,99,193,137,232, +207,191,189,153,252,71,52,58,107,145,165,84,215,35,3,165,136,100,174,85,52,218,26,88,139,232,108,157,221,208, +115,130,31,72,59,100,164,178,29,136,232,70,194,109,165,141,139,70,72,229,64,33,227,91,89,184,173,40,0,47, +194,196,63,240,145,84,210,201,172,156,216,60,43,65,156,39,211,192,204,73,87,194,179,191,103,115,93,190,31,189, +157,207,47,206,252,73,139,181,185,145,149,27,185,125,133,194,118,186,168,75,64,187,206,206,50,139,70,216,51,169, +10,184,75,190,253,143,167,249,122,149,159,39,191,219,175,110,50,51,42,140,120,187,250,29,114,151,20,176,150,10, +222,25,93,129,113,123,94,127,30,33,193,206,232,222,166,71,111,192,189,189,85,221,189,87,208,168,161,77,67,247, +90,125,158,110,190,223,173,116,217,208,84,61,175,202,104,167,201,128,100,155,217,1,53,223,125,134,164,106,145,63, +216,215,170,222,129,201,86,37,120,126,239,149,136,37,119,28,152,120,230,208,159,35,121,89,24,127,114,128,158,50, +61,153,114,140,195,90,110,234,254,249,214,72,215,193,55,89,89,67,10,71,150,202,133,91,10,224,175,165,231,138, +60,15,107,109,98,18,4,196,220,221,223,199,78,28,142,140,85,38,193,152,149,49,73,30,143,223,43,36,7,238, +22,176,100,51,185,142,95,43,22,238,233,53,122,38,118,140,237,190,124,199,128,171,13,42,127,228,239,123,209,53, +217,177,49,116,115,134,218,91,55,90,25,177,174,85,238,164,86,49,59,52,103,78,20,58,71,67,149,75,114,3, +153,131,215,37,208,83,28,81,234,70,44,49,80,254,44,173,35,181,220,120,236,18,91,87,148,153,118,8,199,109, +14,85,72,172,179,34,98,172,81,104,70,86,52,114,44,217,209,139,250,163,6,179,159,67,137,81,210,230,57,218, +244,39,146,182,160,66,121,196,106,249,39,198,84,108,217,76,193,237,232,151,218,101,164,253,219,149,5,115,3,38, +182,232,225,32,67,147,12,203,80,83,157,248,212,22,34,202,183,178,44,200,128,136,5,66,67,132,58,201,138,2, +138,95,117,1,150,153,196,101,155,95,179,157,191,243,243,15,191,254,20,141,199,134,108,23,226,145,70,136,80,177, +97,71,150,232,70,139,184,179,138,31,122,97,148,21,182,94,57,3,128,224,145,205,58,191,143,32,182,157,235,53, +102,66,23,57,155,72,229,96,99,164,219,143,199,177,14,79,98,128,97,220,162,74,107,48,6,76,165,75,153,123, +218,254,232,157,63,18,143,105,232,86,110,180,181,218,200,141,84,100,80,109,97,130,209,46,64,81,251,176,209,165, +78,6,143,34,146,42,47,235,2,162,244,147,155,153,210,106,191,211,245,167,119,244,78,186,40,125,116,104,209,163, +147,230,118,196,245,177,247,130,34,47,96,156,108,2,85,151,43,4,139,147,233,172,115,14,80,212,215,224,242,45, +146,81,223,228,154,29,143,179,149,137,7,238,172,29,102,114,224,251,55,231,147,63,228,68,91,119,76,98,153,8, +135,31,161,84,2,15,29,75,118,232,206,99,22,48,47,84,220,35,124,79,233,138,36,86,117,89,14,8,29,16, +11,153,160,220,215,25,42,92,235,1,242,202,12,248,83,94,98,242,73,244,101,71,16,5,210,143,94,255,160,204, +137,144,151,78,8,151,34,36,176,123,200,241,120,192,64,123,149,162,251,251,207,50,45,193,141,62,66,112,213,86, +13,121,35,10,155,209,71,248,98,249,103,88,197,252,35,120,207,11,199,145,183,104,159,130,186,119,193,178,222,65, +215,176,183,120,154,148,160,54,110,139,183,166,129,126,223,180,86,224,202,71,95,118,149,96,197,70,247,152,62,66, +139,233,18,83,224,24,174,15,136,14,61,209,249,18,11,242,18,3,15,73,238,238,18,139,41,15,49,227,132,136, +125,219,100,169,199,4,54,55,143,180,88,124,77,44,130,46,116,128,87,129,249,94,236,146,66,26,183,71,67,110, +180,44,70,83,214,85,44,33,91,199,219,16,140,80,220,139,37,55,226,151,204,109,147,93,118,215,177,105,221,194, +109,11,48,223,34,41,84,153,152,206,178,11,51,203,158,136,115,166,23,217,82,180,87,16,188,183,248,209,249,69, +31,91,160,197,223,219,71,7,193,210,219,222,82,110,185,38,107,67,247,49,228,243,6,167,209,208,164,138,13,127, +224,237,107,188,220,56,136,220,215,234,251,236,155,175,195,228,64,11,65,12,177,103,223,124,221,219,163,208,30,117, +1,51,245,228,9,115,11,181,20,147,243,206,2,215,42,60,57,15,210,64,134,116,112,212,24,31,149,176,100,128, +249,128,85,16,157,98,7,142,29,213,179,28,206,62,23,120,253,210,36,186,76,178,170,2,85,188,164,174,28,59, +22,8,222,54,110,33,18,169,44,24,247,2,80,28,144,59,238,239,31,149,246,187,166,178,171,204,128,114,52,44, +18,3,59,125,3,13,87,57,160,252,121,80,12,95,168,170,33,249,203,127,74,254,235,60,142,182,206,85,233,217, +217,237,237,109,114,251,77,162,205,230,236,235,233,116,122,102,111,54,17,31,114,251,240,101,110,191,193,157,87,252, +129,244,191,199,45,57,93,141,70,209,0,181,146,15,112,67,148,134,79,43,144,198,232,235,27,20,71,131,15,20, +152,184,33,224,49,46,33,178,245,214,231,8,6,133,221,197,3,132,32,247,95,118,215,158,59,103,228,170,118,128, +225,75,37,237,133,195,19,204,6,24,143,101,98,31,28,115,24,112,254,161,109,122,109,38,253,211,69,20,147,253, +234,202,239,141,87,87,108,144,131,170,25,35,148,200,95,84,81,177,212,207,72,235,246,37,68,72,224,129,36,183, +150,34,32,232,110,67,112,117,229,55,70,34,241,128,144,136,106,240,128,31,227,49,125,146,85,151,1,65,46,82, +156,64,54,72,244,97,255,149,196,58,186,36,229,210,39,50,16,61,15,68,244,96,178,125,178,54,122,71,101,141, +41,220,238,63,129,188,104,71,144,67,102,79,28,151,201,237,86,151,62,135,104,4,141,199,120,175,200,92,38,134, +245,244,166,245,114,103,143,107,125,20,69,169,11,84,243,118,177,94,213,171,85,9,54,5,113,114,206,243,76,229, +80,250,77,90,225,243,17,75,63,116,226,199,101,113,227,39,211,203,218,58,189,243,15,17,27,46,80,210,13,80, +40,11,200,99,140,91,63,10,231,131,81,184,247,227,122,142,142,31,76,105,25,251,110,119,50,7,230,182,70,223, +142,104,227,124,109,12,230,64,244,166,163,162,5,28,138,145,174,157,149,5,140,114,189,171,180,66,105,221,171,152, +252,152,17,93,208,107,14,65,196,90,145,88,18,148,156,158,38,90,93,225,34,133,198,85,181,221,62,236,12,102, +72,151,173,29,152,171,186,66,175,195,167,180,239,204,35,158,24,77,103,244,254,83,202,34,44,253,82,208,133,86, +69,95,141,135,207,197,65,60,11,51,145,184,147,241,171,44,191,182,11,183,156,13,103,137,22,115,243,41,155,99, +8,78,55,150,251,5,201,32,239,246,117,70,210,94,199,248,137,166,183,199,172,46,221,59,3,20,64,40,218,49, +129,11,244,177,145,83,1,205,156,159,36,125,126,240,240,71,69,159,175,140,192,90,222,73,75,205,217,234,242,6, +208,58,10,250,223,36,218,50,11,125,223,160,11,254,38,113,241,33,196,20,239,37,110,11,42,174,52,27,56,234, +71,63,138,62,4,103,55,194,95,72,65,25,49,7,231,89,127,15,98,26,56,87,122,224,92,76,181,66,251,29, +116,246,61,92,84,208,78,199,89,152,111,21,44,190,135,37,98,159,60,225,123,106,101,252,103,116,32,250,152,29, +233,218,190,93,48,121,127,87,76,185,23,248,147,236,153,33,88,233,42,102,113,216,33,28,82,184,139,15,189,64, +135,187,68,232,125,31,128,226,246,66,210,11,115,12,12,189,128,48,54,111,132,57,196,232,129,15,65,218,241,22, +123,3,196,189,252,70,198,236,163,234,197,35,216,137,247,94,230,200,44,47,33,67,23,163,73,15,7,99,216,36, +214,38,219,80,69,159,52,29,130,90,70,147,217,120,203,1,18,172,252,36,110,211,157,205,58,135,201,102,181,153, +181,223,98,49,57,95,242,192,110,60,14,112,82,53,27,11,149,190,124,88,61,93,246,253,40,89,151,82,167,240, +32,170,223,185,16,83,73,29,225,59,39,14,38,157,242,60,197,60,171,210,239,220,96,67,82,13,65,98,112,3, +135,24,129,156,113,164,71,160,10,68,210,181,189,145,116,196,143,248,20,48,209,75,32,107,73,65,25,187,97,242, +101,238,225,114,74,183,52,129,116,143,2,39,251,151,107,60,160,232,33,23,18,221,100,107,76,69,251,64,130,66, +145,64,92,138,248,28,159,40,206,36,86,163,216,129,41,191,153,86,203,134,204,245,12,104,159,31,168,247,125,219, +233,189,154,231,248,175,23,248,152,124,112,225,15,57,92,65,185,225,25,47,121,206,183,124,205,14,228,240,66,116, +25,205,107,161,59,176,18,69,27,250,93,183,11,206,170,201,100,198,118,11,185,168,150,36,101,41,170,150,100,69, +45,224,202,135,241,151,172,226,119,29,228,175,85,162,166,155,125,237,237,197,58,38,69,42,198,159,11,136,247,77, +159,152,11,67,59,65,252,156,205,230,151,232,177,57,38,209,30,237,76,227,185,200,227,231,124,207,248,60,201,209, +119,252,42,177,68,199,87,168,134,152,35,19,90,12,118,227,241,93,123,238,23,254,108,101,227,106,178,91,60,95, +178,174,123,92,119,105,198,111,59,40,100,218,13,234,113,144,14,69,162,239,247,201,46,206,248,150,113,227,89,238, +201,88,82,96,43,112,112,75,99,29,175,39,147,163,119,73,49,30,215,3,203,86,139,154,202,226,185,144,139,130, +128,185,104,46,191,18,207,233,123,70,47,49,207,47,227,192,168,152,76,136,25,75,175,124,106,189,98,151,39,198, +67,115,236,14,215,45,116,73,218,165,183,29,5,222,73,239,200,89,136,122,230,1,58,141,111,125,46,162,67,136, +26,253,118,77,207,136,33,25,248,88,162,107,76,243,208,170,62,140,9,41,188,156,53,74,120,149,81,124,137,222, +48,45,49,26,121,19,123,235,194,190,191,10,57,182,211,15,151,188,195,145,43,250,176,226,112,122,106,115,93,65, +122,126,244,113,214,125,182,53,57,165,7,90,24,212,66,47,121,38,28,126,209,120,203,134,191,35,148,20,103,195, +252,87,134,173,83,45,202,37,54,215,217,35,138,140,89,68,32,30,8,159,225,7,183,45,37,113,23,217,17,74, +11,163,79,248,54,68,199,112,110,232,92,49,255,5,158,159,89,182,111,160,189,11,32,184,224,245,112,201,251,244, +39,2,172,255,147,110,111,77,15,199,65,61,187,88,182,189,9,211,123,192,111,208,134,188,66,135,174,189,166,150, +119,155,75,170,121,88,56,82,195,135,189,54,205,142,126,105,152,217,241,216,38,59,191,150,115,117,127,143,195,53, +14,139,69,137,37,191,203,42,250,189,4,83,178,196,235,241,149,65,251,46,77,211,206,146,36,41,89,234,32,46, +25,151,195,165,9,75,30,219,88,54,236,232,65,249,247,238,97,62,120,61,224,241,236,161,183,72,136,97,176,51, +49,14,131,137,18,224,164,160,9,61,164,20,1,233,121,113,160,137,131,74,13,181,104,91,42,73,111,223,228,167, +75,33,240,61,24,37,87,253,118,193,105,41,225,129,136,252,80,198,83,54,60,91,184,179,111,206,239,167,203,123, +113,126,113,225,254,245,155,193,123,243,239,238,113,127,245,179,145,245,14,198,101,132,198,113,55,74,115,239,13,17, +194,233,213,71,229,27,128,126,40,183,24,215,54,136,181,227,74,187,43,248,163,206,74,12,252,10,93,95,164,244, +171,88,200,129,197,114,152,4,237,147,180,40,77,65,238,209,15,70,58,29,12,243,132,158,145,214,193,157,75,219, +190,29,187,164,61,193,188,47,47,75,114,67,71,130,30,102,188,223,67,27,85,188,139,210,140,219,107,89,93,53, +42,226,58,98,180,118,169,75,92,102,54,128,140,60,23,58,59,206,204,120,140,85,230,31,154,238,191,165,37,17, +11,62,247,65,132,75,32,151,38,222,21,247,247,216,71,226,53,47,56,166,98,29,18,23,199,74,219,72,46,107, +12,107,90,116,53,233,121,96,202,55,204,22,235,37,239,0,81,49,12,252,73,158,4,61,199,227,60,241,0,162, +135,112,92,97,187,31,143,125,6,173,25,90,120,100,222,79,97,87,218,210,250,138,80,254,104,97,66,154,144,152, +151,170,209,130,145,63,58,87,248,93,194,37,219,125,97,232,66,107,207,90,60,55,113,79,50,203,7,117,16,224, +164,140,215,140,175,251,162,123,199,154,110,246,121,106,106,40,51,151,72,229,140,198,254,226,98,74,188,30,203,56, +181,152,94,43,4,50,149,111,181,65,32,111,94,226,74,104,232,104,181,62,238,169,5,28,243,50,179,118,244,87, +119,56,109,243,13,247,47,44,118,183,149,150,38,39,125,39,29,74,212,238,120,170,233,237,166,55,82,9,162,120, +252,38,131,73,246,185,99,177,8,179,70,53,197,138,238,141,135,47,69,42,241,127,40,123,187,70,204,204,158,52, +165,173,18,91,249,151,29,82,233,120,60,165,25,142,78,111,69,88,64,31,157,220,161,175,41,25,218,195,65,70, +80,88,3,41,82,241,207,209,16,227,240,35,181,111,253,148,199,125,27,152,133,137,246,237,178,123,173,226,153,216, +155,216,112,201,229,226,155,37,167,114,239,12,60,228,232,72,39,126,142,163,85,237,28,189,190,242,108,60,206,40, +130,124,131,30,140,188,227,35,14,226,31,235,18,238,70,244,49,161,87,228,77,86,77,190,197,105,166,220,196,194, +78,174,116,89,140,156,65,15,74,181,161,63,82,194,232,118,178,70,65,163,149,54,5,152,201,215,29,128,68,202, +54,191,169,141,78,15,244,99,237,101,180,218,76,86,101,13,210,110,253,119,75,250,240,140,154,192,4,223,77,28, +68,41,93,32,176,61,93,252,203,191,173,232,223,229,104,171,111,192,164,136,221,152,108,63,121,58,109,15,60,213, +144,91,116,28,225,237,157,157,228,168,6,152,81,117,135,198,84,251,201,215,201,83,175,212,116,121,252,7,59,242, +93,140,13,146,29,222,226,151,67,192,187,134,38,90,227,66,174,40,102,22,179,72,11,13,141,179,100,126,29,113, +250,25,153,113,139,88,228,81,225,229,69,142,109,217,95,174,168,17,224,48,204,199,255,129,105,112,107,226,140,27, +94,242,210,199,229,242,134,162,228,225,188,17,145,94,155,152,158,89,43,177,189,251,13,206,40,76,186,248,127,42, +42,229,255,199,168,148,77,84,208,109,15,18,19,93,44,227,146,29,208,49,177,116,232,207,54,48,120,174,233,60, +11,103,231,71,94,208,17,46,0,239,98,215,134,147,78,184,69,36,167,110,51,168,174,31,218,159,208,125,133,209, +58,89,106,103,83,191,94,118,187,165,61,10,215,255,213,138,126,101,153,17,169,215,140,254,162,152,26,196,243,67, +134,252,110,32,205,2,113,41,114,108,38,26,123,52,45,108,152,89,253,95,3,101,83,254,30,31,245,140,34,137, +227,5,99,30,79,185,17,121,210,159,51,30,121,230,129,224,156,103,72,224,15,9,219,40,26,208,223,112,139,232, +246,20,61,180,104,222,202,44,87,203,182,197,174,244,8,195,2,170,240,221,214,171,107,234,220,105,67,173,204,214, +21,208,206,242,123,219,121,29,255,193,240,23,134,127,116,124,96,245,148,183,22,159,31,135,221,234,205,195,110,245, +176,1,189,140,35,250,97,155,113,32,176,202,220,22,97,53,128,55,49,240,136,22,164,136,71,74,43,232,142,10, +124,254,101,58,154,110,255,139,91,235,80,143,20,135,193,175,162,219,222,236,177,141,25,32,23,246,122,239,189,23, +103,152,204,176,159,7,242,193,164,39,239,126,146,13,11,14,185,222,183,81,44,23,73,35,201,191,133,148,62,82, +250,77,113,230,8,85,71,144,130,167,42,5,169,172,230,89,12,116,97,146,107,169,193,93,223,117,164,57,143,230, +154,185,43,140,136,110,38,178,180,147,141,173,208,210,126,41,33,223,245,164,226,141,151,235,19,236,139,28,129,210, +248,175,35,249,34,14,36,40,209,183,172,151,229,106,189,13,154,58,249,22,135,13,57,226,43,181,173,155,27,46, +12,185,111,56,31,224,109,139,15,143,222,243,152,154,110,21,146,11,74,199,181,206,166,3,3,62,251,55,20,30, +113,153,40,210,42,35,101,187,139,87,39,243,26,151,115,34,118,218,246,154,247,228,103,67,231,111,142,117,62,217, +99,203,13,177,189,70,104,194,219,102,129,170,191,49,43,76,187,238,139,111,108,189,170,111,224,1,216,241,133,29, +48,186,5,187,48,74,196,190,73,197,193,201,119,176,41,118,240,89,166,41,190,244,146,24,75,146,189,75,147,56, +1,16,16,144,0,21,10,208,58,150,43,140,146,83,150,159,252,89,150,103,155,178,42,167,124,143,126,1,45,254, +28,139,95,140,89,28,206,59,191,211,162,219,252,43,58,190,144,84,184,213,17,212,105,114,252,202,245,1,238,69, +100,68,127,192,230,175,55,30,79,170,15,204,2,43,55,110,60,226,122,236,32,74,130,18,70,130,4,50,87,201, +240,233,77,61,126,103,242,243,140,235,108,104,2,30,165,115,198,147,204,166,232,54,74,169,17,73,226,123,10,194, +166,16,82,252,32,28,243,185,18,255,180,79,213,79,205,213,228,132,55,95,184,129,198,186,46,139,98,89,33,70, +184,49,133,85,161,111,61,162,230,150,154,233,161,246,116,252,185,61,148,162,113,136,157,112,227,38,252,99,114,59, +93,0,1,66,192,196,253,43,187,190,187,183,221,189,14,93,161,154,186,2,108,93,214,110,175,190,119,238,143,200, +253,126,78,40,209,127,244,51,219,178,221,150,11,92,108,216,4,127,108,8,176,47,11,102,87,132,40,123,54,159, +28,98,207,253,211,14,225,16,187,151,63,62,234,177,197,121,135,215,119,190,121,191,249,110,64,25,231,116,70,47, +145,79,202,206,210,193,102,192,153,41,34,67,103,254,111,226,142,60,62,100,13,222,245,26,82,200,101,200,47,158, +19,168,68,106,14,10,145,218,59,210,85,19,121,185,242,106,34,23,249,14,233,28,251,60,21,48,15,5,172,126, +73,64,26,46,69,164,220,12,157,84,249,185,63,188,183,62,3,225,101,163,131,79,77,128,142,226,192,146,0,93, +40,150,179,143,101,7,238,168,143,185,158,46,187,80,242,177,147,61,248,137,223,255,59,67,201,39,131,202,254,153, +96,34,184,72,35,144,130,199,86,114,37,20,79,148,52,60,230,113,23,5,4,240,52,78,223,149,10,100,194,231, +177,101,25,87,105,204,176,67,18,246,99,212,207,207,193,104,18,55,36,230,81,108,83,90,130,105,164,159,109,4, +104,158,252,210,74,150,165,60,73,32,230,106,174,198,65,141,139,8,132,39,50,71,252,213,197,73,180,96,241,247, +164,226,177,195,102,145,200,44,205,36,187,117,174,151,55,2,228,81,198,144,112,173,178,107,150,27,120,9,226,41, +49,250,55,69,76,53,141,152,234,255,30,49,63,31,220,105,2,29,12,10,71,89,160,106,217,188,249,233,123,239, +230,119,119,81,97,224,212,145,223,64,253,128,215,131,127,238,20,152,143,244,7,78,253,249,47,105,255,241,238,10, +200,109,114,239,97,238,153,53,135,22,97,10,125,73,174,139,2,187,144,207,66,145,135,78,135,237,51,34,140,200, +82,241,40,209,32,51,158,69,153,141,121,170,51,103,177,209,75,60,137,241,31,217,136,144,9,143,179,12,164,120, +83,70,159,107,174,147,196,140,168,76,210,20,140,107,57,183,204,205,0,147,25,230,92,11,9,82,191,41,229,17, +90,165,138,198,19,184,241,52,252,204,11,147,7,225,96,96,122,182,194,255,168,129,199,119,189,73,81,117,94,103, +168,45,234,234,212,180,181,165,175,236,13,230,141,248,102,217,91,158,79,185,97,199,31,246,172,161,34,223,16,94, +226,89,59,162,83,119,4,97,90,33,12,234,225,33,221,101,9,48,195,48,156,242,131,4,193,104,151,112,57,134, +146,204,134,45,72,154,54,76,84,140,115,9,206,228,221,39,136,15,240,214,67,110,42,19,154,130,195,175,126,193, +4,139,242,232,138,17,254,184,139,109,224,4,206,73,222,152,106,244,13,109,124,168,92,4,187,61,201,236,161,6, +177,182,7,216,182,62,96,82,128,93,238,111,153,128,6,77,150,238,155,19,102,14,183,53,180,174,148,153,121,243, +43,112,27,90,153,61,50,191,125,99,91,108,233,172,253,214,121,170,47,111,128,177,84,91,3,8,145,243,27,84, +74,179,83,110,204,106,57,59,168,86,207,255,196,178,85,232,60,168,164,209,7,98,19,90,156,248,204,224,238,20, +162,72,161,64,41,174,79,191,255,238,189,95,5,59,150,51,33,10,121,182,188,25,234,63,77,4,109,239,120,232, +114,61,128,191,177,252,240,35,134,245,223,158,42,254,253,161,55,2,38,248,155,6,31,187,5,252,13,148,75,24, +132,127,215,12,217,65,248,249,129,46,248,27,233,14,187,18,1,157,181,21,187,3,127,163,21,226,39,45,14,0, +208,249,63,254,18,0,23,101,63,88,255,237,27,76,40,158,161,23,150,71,32,159,124,71,164,116,27,211,120,31, +240,77,18,209,76,35,65,170,9,86,234,83,155,158,8,202,187,120,217,98,79,218,168,255,249,224,171,64,254,251, +146,165,159,204,160,94,145,81,255,183,223,247,35,219,145,72,232,149,191,125,18,253,97,245,63,236,31,103,206,134, +190,208,215,239,254,27,182,140,152,198,106,238,103,124,239,70,231,55,30,120,251,122,87,85,216,237,243,114,177,216, +218,227,149,217,152,253,99,112,28,171,179,50,166,183,72,56,216,119,184,45,104,233,183,91,255,14,186,214,97,47, +233,103,217,252,136,183,81,141,11,238,192,245,71,183,176,96,222,88,103,182,95,246,186,76,204,88,181,239,153,35, +184,57,177,197,15,231,83,243,187,53,167,85,191,26,204,230,63,187,225,246,44,211,55,152,162,206,123,80,31,246, +109,12,127,107,48,90,62,59,44,19,59,175,2,252,227,18,191,124,58,167,181,149,150,153,88,127,3,230,180,108, +83,213,213,124,60,21,158,45,41,198,108,222,47,127,195,94,238,70,35,171,217,2,123,91,24,108,41,154,177,222, +219,233,94,115,113,125,141,207,194,136,251,94,235,195,150,202,148,198,144,199,31,219,28,147,77,102,123,17,179,203, +172,127,90,254,214,86,242,115,229,175,245,153,83,140,213,110,142,189,253,70,162,96,137,125,96,176,162,209,223,89, +107,87,225,238,84,191,171,50,62,167,19,116,39,47,192,85,101,140,15,19,183,131,211,206,55,227,215,63,251,94, +24,156,101,71,124,255,249,14,203,12,59,75,253,94,209,145,43,127,25,94,223,59,175,246,214,167,125,183,249,31, +214,153,171,248,245,164,10,124,46,83,69,90,179,100,83,234,125,174,94,20,163,127,170,189,9,113,150,248,218,243, +196,204,250,62,209,219,171,195,230,145,73,177,252,139,207,137,175,102,168,202,250,239,240,250,235,96,159,163,208,217, +199,64,142,154,215,78,238,53,62,135,127,222,207,22,103,201,254,175,255,95,53,23,33,190,143,249,138,23,68,121, +167,207,253,21,142,127,26,225,40,91,77,124,14,169,179,255,82,170,23,43,124,206,226,133,53,46,135,220,222,206, +222,198,137,133,191,113,114,111,246,185,200,158,116,30,171,22,255,54,46,122,253,139,66,59,115,16,231,205,115,169, +200,163,195,231,56,173,246,95,57,54,97,203,201,216,182,87,233,176,182,209,99,39,143,106,201,133,29,39,183,162, +216,149,3,223,251,100,39,31,205,159,118,226,204,159,15,214,14,244,243,19,135,251,62,135,184,18,44,206,220,245, +123,21,253,229,125,251,165,227,222,251,175,248,190,141,138,116,230,168,112,102,39,197,231,182,251,235,229,111,91,22, +248,189,165,248,189,190,254,178,215,127,215,2,135,60,255,184,86,229,176,228,88,205,89,195,110,235,207,222,221,213, +153,223,85,245,179,95,248,62,118,127,254,49,190,207,191,63,78,147,102,202,219,180,171,2,111,185,201,105,205,98, +157,136,58,107,180,20,223,187,60,207,188,100,219,81,187,147,231,128,41,31,170,148,177,169,202,97,170,19,225,159, +152,226,211,152,83,113,111,114,228,137,61,123,111,84,154,124,168,204,145,241,200,210,199,2,219,193,123,236,65,90, +52,93,162,189,173,143,197,74,211,242,78,226,41,126,53,88,255,136,202,190,139,223,187,148,81,43,86,240,134,184, +242,179,117,64,142,63,9,19,0,0,207,82,210,236,240,0,38,242,76,1,30,161,8,92,245,82,106,202,49,206, +36,55,192,144,232,126,210,88,142,67,227,59,139,253,251,187,240,205,97,241,93,193,232,170,192,120,92,249,59,119, +68,56,62,40,120,99,217,174,67,242,127,24,158,199,111,238,101,87,147,233,72,52,203,127,72,188,98,234,227,69, +234,220,59,222,240,141,20,255,254,194,68,195,102,151,110,217,25,171,124,147,31,241,163,138,177,23,51,203,29,255, +107,158,124,59,219,92,138,27,53,0,50,13,53,79,148,194,183,53,241,253,241,45,194,103,69,156,68,248,68,102, +211,12,109,246,140,225,123,58,221,72,209,217,207,28,69,254,244,236,167,167,177,198,83,136,173,38,206,207,210,82, +128,206,64,210,148,140,19,104,164,213,175,27,89,247,5,168,38,221,217,62,179,254,41,230,111,123,211,221,45,60, +191,167,158,183,181,183,188,74,236,117,99,52,127,179,228,178,24,167,78,0,136,187,109,51,226,119,149,99,55,169, +236,78,21,120,227,227,211,198,35,134,29,132,253,187,189,1,123,139,193,186,120,50,10,34,130,65,206,98,245,173, +205,102,112,122,206,61,195,180,244,65,222,239,37,4,85,146,91,204,33,196,207,248,119,127,246,30,119,119,222,194, +184,148,96,86,68,184,252,182,229,193,102,178,22,210,92,175,230,147,135,95,69,143,220,45,9,131,236,202,224,173, +148,223,49,69,197,161,218,109,152,69,140,201,51,251,201,158,197,189,4,171,192,193,60,219,48,12,243,47,145,9, +146,251,172,252,240,211,104,223,130,200,159,172,103,122,57,153,79,124,144,29,185,91,11,136,17,153,82,27,104,74, +10,186,100,35,64,232,40,13,185,222,43,201,66,241,189,183,28,135,75,141,115,215,16,150,94,141,45,101,49,235, +170,230,213,71,73,50,252,255,111,80,39,120,124,53,34,157,112,231,60,18,89,117,165,227,28,242,67,94,56,240, +110,11,59,56,56,231,10,87,61,254,105,93,19,105,126,252,163,191,64,146,122,120,103,23,152,77,15,0,21,56, +50,78,107,252,128,52,217,87,30,148,71,126,30,216,44,41,115,82,4,226,145,152,138,64,18,108,65,225,71,101, +207,184,130,35,40,162,90,104,10,159,61,110,126,188,197,251,192,227,224,160,20,90,248,144,42,192,9,96,138,66, +214,43,111,214,219,188,197,55,236,54,111,148,231,80,108,127,73,147,196,158,129,125,36,75,172,107,113,73,40,237, +156,214,84,125,76,171,116,61,130,189,23,175,10,120,77,5,80,35,80,92,81,220,54,159,188,126,148,75,149,144, +123,234,115,118,34,217,120,179,56,57,191,200,193,206,109,94,6,190,64,241,20,253,60,195,210,118,37,46,177,226, +169,121,97,102,120,36,181,179,205,0,232,193,5,36,247,6,59,137,211,213,98,123,175,55,89,85,219,182,134,169, +153,167,12,119,80,251,99,123,85,249,33,217,208,224,144,233,226,166,234,214,160,54,228,222,142,248,97,165,119,110, +123,59,37,190,166,94,80,107,50,8,7,168,45,36,201,118,158,92,152,46,97,47,130,199,227,78,239,171,242,1, +8,116,8,245,247,32,243,232,164,96,61,201,2,164,48,61,75,14,55,84,65,64,37,101,176,137,108,119,139,154, +92,143,225,220,210,231,64,222,181,184,79,2,176,121,126,205,72,172,129,188,173,87,76,2,116,186,30,6,16,84, +234,1,252,18,238,197,198,137,185,59,97,56,224,0,75,224,89,105,171,157,62,59,228,239,190,14,232,189,65,135, +201,226,119,180,13,119,72,44,214,34,61,129,113,86,28,202,192,2,34,72,103,231,145,182,208,134,210,225,2,87, +213,161,16,226,46,58,224,27,80,144,119,220,22,196,245,8,68,237,226,129,99,28,240,170,224,173,230,171,103,155, +120,101,219,207,146,167,211,5,227,196,175,40,110,192,7,19,165,205,131,88,187,239,163,112,101,88,181,212,113,191, +101,81,69,1,210,4,222,240,90,229,0,18,80,118,246,56,14,44,215,69,25,39,107,50,253,238,15,12,223,13, +203,186,90,64,101,4,77,120,93,26,199,32,172,251,211,198,160,74,33,63,103,45,25,196,213,78,245,167,46,148, +253,143,96,143,185,192,182,31,163,16,170,61,199,254,141,154,38,156,171,246,22,104,91,158,162,144,65,15,144,155, +71,91,85,149,253,124,118,117,240,45,252,76,242,155,44,64,69,220,103,103,81,125,229,148,247,159,175,26,38,226, +135,177,250,157,199,23,40,132,201,129,47,38,73,146,45,255,123,229,108,80,95,137,22,228,25,171,77,126,247,209, +159,103,156,134,207,232,226,252,143,28,155,80,91,224,209,169,71,64,163,135,140,20,194,224,121,95,118,92,178,145, +31,127,125,25,134,50,205,68,124,221,95,236,140,227,231,235,197,126,111,11,212,110,55,64,157,18,133,32,12,102, +229,245,61,27,161,236,23,217,30,110,230,79,159,15,253,72,14,143,126,101,58,157,95,212,164,54,164,239,245,25, +173,250,153,165,214,254,148,209,142,167,211,132,60,74,32,140,3,27,167,183,48,219,253,41,107,238,149,157,68,243, +183,132,143,116,147,244,214,226,218,84,74,54,34,104,123,143,48,161,184,66,112,9,73,115,184,214,242,172,158,199, +229,2,148,167,17,72,132,50,49,252,17,29,43,89,159,81,28,235,199,53,240,67,179,145,249,158,237,153,223,6, +75,130,210,100,27,189,1,28,25,103,121,150,64,94,120,169,10,104,128,174,252,56,38,176,146,198,147,151,5,4, +150,90,6,175,223,239,153,108,191,188,109,127,175,42,63,128,246,29,212,19,42,252,168,170,218,187,186,138,51,179, +170,96,66,221,248,181,251,17,103,112,248,109,158,249,62,175,174,145,218,17,99,190,126,10,17,236,47,3,179,187, +88,24,31,147,102,237,231,163,131,16,127,228,105,205,36,40,0,154,225,197,192,20,94,167,128,99,160,246,249,80, +230,206,215,188,221,90,76,242,246,183,137,81,8,4,145,0,38,249,107,248,87,190,98,112,142,178,220,111,139,182, +192,240,214,25,126,8,70,204,224,61,197,77,147,175,188,54,97,108,60,153,178,48,125,222,20,25,21,26,201,19, +70,177,125,77,71,11,70,240,45,0,213,11,47,57,227,171,91,166,246,58,57,39,161,236,55,51,84,112,185,217, +226,34,103,102,113,217,98,73,138,210,182,137,34,175,156,104,10,187,114,181,94,99,113,0,181,182,15,150,153,171, +16,227,148,98,58,66,142,31,42,195,194,58,181,1,41,135,204,69,220,22,172,145,138,242,37,233,119,233,161,40, +169,12,127,96,200,8,136,213,7,195,189,43,160,152,55,44,157,202,67,247,155,254,86,54,110,100,194,119,72,116, +241,16,166,1,115,107,25,195,226,252,201,76,122,83,31,79,233,161,189,35,74,70,162,23,63,238,8,9,208,236, +139,38,172,201,16,78,187,244,146,98,44,227,205,9,212,48,46,181,217,163,28,18,192,105,22,203,186,116,60,185, +126,164,63,83,26,57,241,250,213,233,180,240,81,51,187,62,26,166,48,164,236,143,215,152,106,230,126,133,234,153, +119,142,198,201,27,50,254,250,208,217,100,79,239,40,209,190,1,56,158,127,153,150,5,103,8,245,251,6,185,246, +144,43,172,207,225,247,113,196,172,48,164,67,148,27,173,216,28,183,212,41,152,218,207,245,67,218,215,103,41,43, +186,235,27,18,109,34,133,106,160,102,243,12,191,57,36,225,118,182,210,81,199,87,109,34,75,252,214,89,1,253, +47,252,88,68,187,244,211,104,140,212,234,214,75,108,72,39,135,191,119,23,135,215,15,158,202,76,130,181,142,119, +144,166,139,189,118,120,13,243,33,60,23,250,76,164,169,150,123,55,121,101,217,23,230,79,40,169,46,244,146,202, +185,242,1,199,233,204,125,248,37,252,86,36,153,171,254,44,204,133,57,180,20,193,204,138,58,193,216,43,181,228, +242,232,88,23,207,120,13,179,160,8,192,48,89,97,189,188,148,234,241,70,121,201,193,163,196,167,119,200,98,63, +168,215,208,232,127,138,60,25,238,116,140,41,148,85,0,154,250,51,164,93,88,113,123,242,197,189,240,239,2,230, +180,39,126,9,112,157,113,142,216,94,45,182,241,118,239,159,234,85,60,134,245,76,178,64,77,116,79,106,109,12, +188,129,91,11,110,125,248,17,25,50,2,180,2,156,123,219,38,156,113,98,222,10,177,230,72,129,196,144,251,213, +158,231,197,42,133,58,166,185,190,180,4,50,200,214,168,191,173,42,217,51,194,49,197,245,193,95,191,227,227,103, +137,0,217,19,105,130,31,51,212,110,135,212,234,46,110,251,242,39,124,46,249,37,210,127,139,68,102,250,231,125, +52,240,244,209,253,213,92,23,121,53,208,56,249,253,158,83,241,168,106,146,236,17,170,161,214,165,17,97,133,27, +70,232,145,75,4,110,4,6,41,14,51,251,148,209,71,240,126,227,171,182,145,192,221,31,141,11,71,190,29,24, +193,161,143,3,210,178,152,137,125,80,78,205,95,208,79,56,11,74,250,244,195,72,242,147,40,75,110,197,72,181, +20,173,249,112,81,27,25,120,20,119,137,95,132,172,240,227,222,132,46,14,183,10,118,145,189,77,184,59,83,22, +146,10,138,170,140,58,244,79,41,79,144,53,179,200,31,103,223,151,186,164,240,191,245,243,136,19,232,187,9,92, +252,167,94,36,48,36,101,1,19,187,11,133,62,48,213,214,69,205,67,62,163,235,19,53,139,8,118,107,145,9, +18,71,88,131,51,177,16,32,113,73,218,176,110,242,230,87,226,15,57,246,128,168,213,60,172,123,78,115,139,44, +14,105,199,212,34,30,223,243,85,4,23,81,23,206,239,245,201,193,196,95,248,254,118,192,195,7,130,85,88,114, +131,227,170,202,111,215,248,186,122,115,128,250,227,184,207,14,9,250,85,176,213,54,211,108,247,128,111,255,204,126, +238,159,23,163,141,209,75,225,215,231,54,124,84,0,4,220,75,130,170,34,74,99,136,222,188,221,170,249,199,52, +147,143,204,159,177,38,151,61,157,226,152,34,80,3,191,158,245,145,208,138,74,195,34,142,201,153,231,168,18,55, +212,177,209,51,232,30,64,116,78,17,25,227,95,255,216,115,43,43,214,189,129,67,240,34,135,107,105,242,39,162, +227,155,210,116,28,60,192,163,72,132,147,195,42,69,160,15,91,108,119,90,46,81,158,10,254,130,190,110,90,157, +21,57,174,108,107,45,239,181,216,92,246,211,31,16,45,175,116,37,24,81,61,229,250,190,215,145,98,202,120,54, +190,205,51,231,168,213,3,243,208,25,47,245,67,24,246,239,162,241,229,74,180,247,193,164,184,22,149,79,57,209, +232,77,197,187,196,77,190,190,172,106,77,141,255,70,226,144,112,246,47,144,87,214,31,238,84,107,0,182,172,18, +224,239,88,81,98,94,230,140,249,126,9,10,247,16,148,175,10,191,185,152,160,234,121,229,171,163,120,31,197,213, +138,77,1,131,230,21,86,23,213,8,214,136,117,131,61,107,175,28,151,43,14,170,127,190,67,138,250,145,73,227, +50,81,180,87,176,37,191,45,255,29,227,228,122,137,238,66,181,10,168,95,28,170,232,161,1,0,71,50,73,173, +158,206,125,85,2,32,170,141,1,254,176,94,197,51,93,241,31,241,152,149,125,218,228,5,91,169,182,92,18,222, +229,17,81,98,126,101,21,65,130,42,161,189,217,178,200,112,68,201,224,114,237,129,62,209,132,22,179,249,194,122, +253,198,91,117,32,192,143,142,238,211,142,158,130,136,228,159,64,227,123,188,236,22,233,175,44,239,60,201,9,21, +178,194,183,227,83,243,227,45,8,53,211,210,143,14,18,94,123,0,1,168,6,255,176,32,178,250,201,246,244,183, +195,154,63,167,125,96,56,73,116,240,203,216,80,117,107,25,211,188,135,151,8,7,200,169,84,37,51,223,252,196, +70,52,89,121,177,61,238,24,170,85,219,105,241,114,170,114,246,246,199,169,216,130,238,54,239,240,124,85,170,39, +218,64,237,105,17,200,1,112,92,224,31,74,176,16,249,16,204,223,132,168,145,167,30,89,14,7,243,77,153,133, +186,170,53,47,176,90,250,254,58,179,132,62,129,170,0,69,243,197,99,124,88,49,225,213,154,12,230,110,72,137, +120,189,31,188,238,38,197,219,54,207,133,60,234,199,221,234,108,124,188,27,93,243,65,110,89,205,175,120,136,137, +234,66,150,243,252,98,40,213,62,237,41,185,118,218,206,209,200,71,245,245,113,150,205,97,224,78,142,42,47,134, +85,195,154,48,250,0,6,31,129,190,147,226,120,137,220,76,147,88,19,19,106,229,6,225,107,187,128,221,217,139, +207,63,77,224,142,143,112,85,248,70,87,170,188,92,217,227,211,139,179,165,229,223,139,111,37,202,245,3,164,2, +221,148,152,31,82,247,212,253,185,56,175,4,95,89,112,102,18,179,56,207,230,76,85,175,159,143,51,122,213,148, +49,104,222,228,189,68,207,137,30,170,63,225,239,77,162,227,114,127,200,38,18,144,56,36,128,230,219,226,157,118, +224,208,229,244,46,172,119,213,131,189,1,0,108,203,109,225,151,74,119,58,19,250,70,248,172,41,0,12,220,27, +52,95,0,172,191,214,105,94,238,3,56,103,228,162,30,184,29,42,8,248,174,60,87,145,72,56,141,196,142,72, +242,230,113,137,73,212,40,25,238,170,123,129,176,17,53,17,33,223,19,121,254,80,33,64,16,50,101,148,175,12, +95,216,199,74,140,239,155,161,253,103,190,212,63,212,7,54,190,0,127,222,57,69,251,195,67,219,104,100,216,203, +226,74,115,85,139,73,41,62,238,50,66,113,106,136,8,138,117,191,5,201,144,108,70,193,107,126,107,233,13,193, +71,47,22,55,100,235,149,183,88,3,181,90,190,215,46,234,203,187,198,213,41,74,148,11,172,120,97,152,123,251, +252,197,146,254,241,144,43,75,8,245,253,206,27,199,163,240,209,250,241,137,239,248,151,237,58,49,91,164,205,183, +211,189,133,108,0,162,98,174,217,70,151,58,248,165,44,228,192,32,79,97,115,72,215,107,48,23,157,204,74,187, +39,155,91,50,143,14,165,67,219,90,79,83,163,62,142,223,133,160,217,93,85,212,77,25,134,207,229,170,109,65, +246,216,153,242,4,88,79,198,111,111,132,134,128,238,207,238,100,22,180,2,50,154,111,118,2,103,85,245,144,37, +60,215,64,156,112,120,184,108,72,111,207,5,53,5,68,123,249,165,58,22,191,236,185,218,223,234,241,217,236,211, +5,29,224,42,228,198,79,161,153,212,105,56,5,193,85,125,0,99,168,91,11,180,154,103,198,99,216,30,251,64, +42,76,183,0,39,22,92,177,138,133,186,230,135,160,115,85,149,115,133,79,126,126,180,90,148,153,167,84,132,1, +242,212,79,240,246,162,40,18,201,16,56,153,224,183,112,252,190,68,231,64,5,0,40,73,112,138,113,86,65,57, +233,183,52,128,211,17,57,145,140,70,73,215,116,32,94,110,40,249,77,248,101,172,182,149,197,26,64,251,213,164, +106,82,171,209,163,169,36,245,201,132,137,220,18,32,88,211,56,111,63,55,157,151,111,77,196,72,82,168,125,68, +182,34,230,154,16,46,192,69,252,169,187,178,6,106,21,200,106,167,23,92,124,231,117,184,188,237,147,99,60,85, +56,124,122,138,81,193,122,221,88,182,68,42,9,41,184,16,248,122,147,130,189,4,37,168,110,33,8,10,233,0, +11,177,201,161,9,128,230,124,100,224,237,26,38,171,90,189,213,29,13,52,23,33,136,0,144,10,246,70,34,69, +175,58,0,208,175,15,185,242,162,226,85,110,97,98,60,191,64,88,17,212,128,96,13,142,34,180,15,118,37,88, +133,243,168,250,183,78,193,231,85,136,108,64,53,163,12,180,16,137,39,86,238,197,225,229,215,39,212,12,17,49, +79,189,33,237,98,120,131,78,242,31,159,238,13,220,237,200,47,14,68,22,205,155,173,56,88,154,123,189,5,85, +13,168,167,144,233,151,230,155,192,3,162,118,113,155,109,248,0,0,215,17,212,33,60,191,64,51,238,83,200,120, +245,205,54,7,179,170,139,102,250,135,189,188,9,138,95,108,73,227,104,244,215,21,60,179,56,250,70,182,63,244, +135,233,8,66,168,150,96,255,48,198,210,33,177,246,254,16,55,166,117,239,131,142,252,208,17,152,55,240,91,55, +222,158,241,73,71,172,240,152,110,59,81,45,226,46,0,208,168,58,232,84,159,183,79,114,101,245,233,106,129,7, +76,8,224,245,70,61,169,214,174,100,70,63,224,129,67,106,27,229,9,116,11,52,191,107,121,141,92,232,190,144, +202,135,233,44,210,206,188,82,236,153,248,7,200,44,81,62,6,178,67,182,137,74,135,239,107,124,177,240,82,123, +134,246,51,42,91,208,214,34,103,243,218,131,149,108,89,108,2,180,159,104,25,187,86,5,71,125,208,215,201,93, +229,199,244,13,168,82,133,8,74,227,99,62,137,241,177,110,214,194,208,181,104,232,65,47,62,103,179,138,42,65, +173,52,229,243,96,5,150,100,105,245,155,126,17,149,74,231,71,149,189,90,12,77,201,146,29,100,140,173,220,67, +144,0,84,8,60,43,124,219,225,48,121,26,47,42,78,72,62,182,5,170,199,7,21,3,22,167,57,156,114,102, +25,162,77,174,251,44,1,57,143,97,198,78,20,64,216,171,1,184,127,10,154,249,99,80,34,194,75,19,0,92, +212,118,65,187,176,72,143,23,180,83,71,245,127,63,220,24,73,36,80,193,132,123,120,190,122,245,54,57,95,3, +134,52,12,107,80,4,64,157,87,156,126,171,124,91,3,146,93,94,199,219,21,193,49,240,207,221,242,215,101,215, +181,216,231,141,155,182,100,120,233,200,60,75,206,71,12,35,126,0,79,255,88,201,8,123,173,184,25,75,104,82, +163,127,85,193,254,21,61,65,149,132,158,158,220,241,25,244,245,251,27,150,3,249,196,231,215,173,129,250,33,22, +63,15,36,180,3,12,84,129,111,160,167,66,50,127,106,73,26,192,165,82,80,149,191,183,155,168,92,204,191,67, +150,99,81,95,121,67,33,98,191,49,168,245,228,84,207,14,5,166,201,253,66,70,17,20,125,246,26,140,43,119, +150,59,221,130,171,235,125,54,6,80,222,43,230,64,40,71,81,152,157,215,52,190,180,247,18,223,251,235,114,107, +191,169,155,98,23,140,80,237,7,218,248,48,165,194,115,34,214,42,142,191,247,58,187,63,66,77,177,12,10,76, +223,88,31,149,225,11,245,214,139,109,174,194,19,46,170,85,127,148,68,180,42,135,172,234,250,228,79,181,219,99, +15,25,153,230,195,239,132,203,172,130,17,26,108,75,119,71,106,95,164,255,35,191,20,156,208,200,62,46,121,144, +174,162,211,143,103,31,6,234,96,192,57,87,167,140,187,92,181,7,153,152,8,36,229,80,46,160,41,186,148,107, +0,204,49,66,73,41,204,235,251,231,79,161,92,32,93,113,84,7,206,86,80,184,248,154,231,176,16,94,26,1, +26,23,71,158,37,49,147,136,246,78,27,21,17,218,22,27,23,207,84,0,186,7,64,109,43,209,136,35,116,85, +190,4,120,21,25,250,159,5,218,194,115,151,206,82,149,134,148,22,93,127,83,116,208,110,230,34,128,152,42,48, +128,176,198,90,73,179,137,213,112,9,230,83,212,63,254,116,184,89,242,136,215,66,122,240,53,167,30,223,216,78, +64,97,20,54,44,248,68,192,194,248,81,154,141,232,78,182,96,251,151,225,121,173,42,2,143,249,40,61,165,253, +0,196,112,179,82,180,109,72,23,2,132,234,247,66,105,227,191,228,74,90,69,90,42,94,132,246,122,250,0,194, +70,100,173,227,235,4,181,91,107,47,124,198,127,206,111,90,118,118,37,35,23,204,74,154,180,206,56,202,54,138, +129,238,208,167,116,0,131,193,66,33,126,15,30,245,94,10,219,27,164,237,79,59,252,153,116,109,121,99,151,36, +19,138,27,143,197,139,138,226,238,208,66,53,64,10,133,200,117,118,11,55,249,59,108,41,127,248,62,207,207,42, +109,162,191,125,195,234,162,204,188,254,22,218,91,224,106,114,115,106,83,72,130,149,80,74,146,155,221,49,126,233, +109,154,83,241,35,144,147,59,240,113,239,34,99,179,1,158,45,174,218,115,100,52,14,144,122,189,253,110,111,248, +118,195,55,2,34,212,84,67,158,53,223,110,72,215,255,8,140,85,235,5,192,228,170,128,71,202,220,222,129,45, +191,157,100,63,100,103,71,122,251,140,179,187,31,162,203,100,239,70,21,69,205,104,85,191,67,77,23,170,64,248, +18,120,1,107,72,233,110,8,78,69,13,90,139,93,195,173,23,179,176,173,105,223,173,215,2,173,136,62,68,67, +5,14,112,5,156,179,48,78,171,169,202,153,111,214,71,146,158,0,243,110,158,17,177,134,210,187,128,182,80,75, +95,128,34,195,151,65,183,189,88,26,179,47,118,115,126,221,9,235,81,209,140,116,50,20,99,132,66,226,245,90, +222,181,3,10,128,115,182,152,250,35,7,29,196,70,156,160,35,108,20,181,43,127,171,169,112,164,156,134,53,240, +154,43,124,212,135,100,152,3,128,227,12,252,159,171,234,215,230,236,164,228,142,29,0,186,228,102,102,236,207,175, +182,238,234,228,5,126,28,6,60,27,152,53,168,20,118,114,218,54,113,200,4,170,250,231,49,185,195,45,142,159, +104,65,32,89,125,1,42,88,181,127,43,181,83,1,204,193,27,50,113,121,69,24,169,211,160,1,33,26,29,21, +10,197,27,78,79,66,77,113,240,96,170,24,14,138,229,182,152,89,251,156,88,59,31,133,231,20,12,223,169,179, +163,10,39,103,108,53,160,203,52,247,27,72,234,205,217,164,47,77,55,229,213,64,167,52,213,114,68,65,223,163, +204,225,217,113,157,40,128,121,120,125,167,224,33,209,141,72,124,37,188,185,32,100,86,248,114,117,31,106,53,60, +5,252,20,62,73,108,245,55,44,100,137,105,151,111,173,69,125,253,144,214,142,232,93,243,104,30,204,85,124,159, +160,3,102,254,125,40,53,28,61,33,150,221,139,147,159,254,132,17,23,27,55,91,30,170,187,245,8,48,89,213, +1,0,156,131,153,149,243,221,109,5,177,182,239,133,109,93,134,131,244,156,22,212,191,208,157,238,63,150,246,250, +34,148,234,164,6,162,112,82,62,24,69,85,140,129,215,197,179,100,205,145,52,195,72,42,190,10,240,214,234,161, +187,254,31,42,169,27,71,172,188,22,71,244,194,190,210,45,133,22,171,101,130,59,209,177,143,133,99,83,189,113, +219,105,140,10,85,130,251,150,94,42,5,244,93,185,234,123,125,221,107,170,174,209,224,213,30,210,70,73,151,164, +72,243,116,106,236,179,70,124,60,149,216,57,145,89,167,30,212,129,209,42,227,153,208,206,37,41,250,109,38,183, +156,125,34,253,32,47,45,212,92,5,149,130,161,213,93,119,153,126,141,120,202,148,121,142,20,138,239,99,156,145, +106,169,58,173,168,84,30,142,125,148,176,70,210,120,20,236,196,14,248,111,17,168,141,11,107,200,233,133,155,92, +105,167,45,182,159,8,129,74,43,80,14,51,81,180,48,83,118,132,17,208,223,186,131,145,78,152,31,163,216,20, +118,79,7,209,23,67,160,152,82,171,6,110,106,53,145,216,201,171,221,68,205,151,226,176,68,53,84,113,191,66, +149,128,65,109,244,52,70,198,89,225,30,205,230,38,211,175,200,5,48,118,167,181,253,250,71,254,124,241,128,207, +59,129,179,131,90,121,166,162,180,9,204,30,240,53,142,26,47,4,205,232,233,69,32,103,160,168,125,164,18,30, +78,45,160,144,243,155,129,10,133,129,239,164,4,56,50,127,163,157,14,82,37,76,191,199,48,119,233,55,122,12, +70,71,27,246,77,129,234,133,196,224,185,85,100,120,143,15,22,222,197,250,36,240,202,78,177,164,149,76,231,99, +10,229,87,164,59,13,4,65,253,107,249,9,38,176,167,234,14,77,110,184,120,229,41,181,103,212,167,253,100,133, +178,99,98,113,170,66,196,240,118,160,252,46,17,84,200,16,107,123,108,79,138,41,109,50,91,182,121,200,128,57, +235,198,74,155,167,33,42,117,9,70,142,246,235,218,27,180,151,176,53,254,165,170,111,173,87,145,238,86,246,69, +94,13,222,154,80,239,212,172,91,253,65,208,213,181,152,80,221,175,43,49,7,95,67,31,113,28,87,97,102,191, +190,235,33,215,172,97,142,166,10,5,104,76,63,205,245,93,200,75,111,25,4,191,116,125,172,32,20,85,205,45, +22,91,226,68,179,78,233,94,51,109,238,246,61,201,164,212,63,86,55,51,72,198,237,171,19,47,117,66,77,11, +180,142,200,36,88,145,68,233,141,11,33,170,190,255,216,231,11,58,169,145,14,41,50,6,200,184,161,237,254,168, +238,244,48,159,56,245,181,139,223,67,38,134,16,144,151,9,42,185,255,104,218,107,208,10,109,205,39,174,241,86, +251,139,68,235,243,164,252,217,183,228,58,192,40,204,232,61,54,121,35,222,46,29,123,194,129,73,199,233,176,74, +154,167,63,53,113,185,223,207,102,112,47,161,102,31,228,95,17,181,45,248,231,225,118,116,159,181,131,25,238,170, +143,6,119,234,30,62,93,152,151,249,240,158,9,28,64,66,228,249,122,161,26,100,214,74,60,16,114,85,67,163, +57,172,77,155,0,62,85,135,13,58,238,175,150,73,46,69,209,125,20,32,135,113,194,110,232,117,167,93,82,254, +98,137,238,211,246,182,77,207,104,214,80,151,162,73,205,31,236,153,242,138,251,6,151,35,169,134,151,254,30,207, +149,156,79,250,130,110,1,104,253,126,189,244,43,236,153,59,182,95,89,17,49,226,97,190,149,239,241,126,90,14, +36,2,12,220,22,73,223,66,219,31,231,85,204,17,202,198,84,62,212,216,220,220,36,176,10,213,132,125,229,38, +132,228,123,3,249,187,51,214,244,58,166,229,62,177,255,150,249,43,178,134,233,11,75,247,199,145,208,62,16,58, +133,66,184,10,138,128,83,247,105,246,144,18,197,228,178,61,85,247,232,133,207,189,227,9,52,103,137,151,184,50, +53,215,176,84,17,200,14,38,180,67,103,130,207,235,106,145,83,26,1,74,167,227,79,234,115,37,47,80,16,199, +212,126,173,186,232,85,191,185,50,179,141,47,13,31,250,103,254,73,52,135,55,185,247,47,120,9,107,144,181,26, +149,17,238,97,226,219,47,120,107,174,181,64,59,24,3,157,15,50,177,148,168,175,69,4,4,28,0,13,141,196, +119,151,74,164,174,238,99,69,220,231,115,241,155,174,62,160,115,167,163,228,141,118,53,171,89,69,143,15,178,242, +106,232,170,193,96,199,65,246,77,27,183,71,212,197,44,143,158,205,107,151,68,37,243,118,132,62,203,67,21,42, +161,182,137,135,218,166,140,15,76,174,3,254,87,56,190,195,7,52,113,77,142,211,51,95,31,25,126,32,252,17, +96,174,113,51,165,224,214,20,230,19,174,204,92,135,57,176,167,128,101,210,225,19,176,86,248,100,26,99,75,233, +21,47,97,42,83,14,74,34,254,54,71,117,214,223,204,204,210,199,245,252,233,230,163,80,84,5,80,146,56,90, +159,93,124,212,63,15,95,100,206,20,174,215,32,82,252,91,77,228,53,185,10,153,186,13,237,35,195,128,2,217, +98,166,109,45,158,192,167,209,112,11,122,80,202,242,109,58,92,172,2,237,46,21,78,7,37,49,108,189,2,156, +45,213,158,205,209,13,72,167,138,132,3,183,215,11,254,216,11,68,186,37,134,102,192,112,210,75,162,153,204,143, +42,165,61,79,70,96,230,126,91,90,193,131,178,54,18,175,197,178,186,148,135,164,73,31,67,234,196,177,216,190, +156,76,164,139,254,136,166,167,42,219,131,139,148,90,4,99,121,23,12,73,4,38,237,6,181,234,127,202,236,137, +135,118,155,213,221,153,117,93,160,189,6,66,68,238,16,5,122,36,117,211,160,133,78,205,120,147,20,47,5,1, +130,133,163,160,114,104,38,40,83,5,30,177,212,174,142,46,100,34,94,118,17,213,185,106,228,139,152,226,143,33, +106,94,254,82,60,62,36,215,134,47,224,63,181,97,121,158,99,205,73,94,109,57,176,230,121,196,5,186,251,209, +30,143,236,247,33,11,12,223,2,243,96,36,15,42,205,55,71,89,211,184,129,58,110,45,236,164,167,191,40,223, +121,170,98,193,12,163,35,181,182,151,116,251,181,226,216,155,90,134,136,73,89,253,51,12,185,73,104,226,16,185, +106,26,155,25,69,137,1,68,69,116,92,207,252,100,104,148,232,85,70,188,235,32,235,9,104,186,60,67,47,92, +165,46,177,251,246,168,214,194,229,102,68,117,148,136,34,226,138,135,64,137,245,234,0,29,41,49,159,100,159,155, +231,58,239,41,42,53,26,20,205,189,243,109,246,218,157,177,24,163,109,26,238,82,58,97,175,31,183,26,117,243, +119,48,197,46,241,235,224,195,48,91,18,244,72,58,220,6,4,182,44,1,4,108,208,4,252,67,41,186,196,11, +85,151,14,219,174,136,139,20,136,58,248,216,254,171,190,243,104,22,217,79,116,100,225,107,181,85,129,51,95,202, +21,196,236,150,248,89,93,154,125,227,160,81,108,243,197,3,26,174,14,202,108,173,47,11,230,1,27,201,208,126, +92,39,64,176,128,96,65,22,172,234,252,37,130,90,157,221,119,207,148,185,143,2,224,238,66,167,21,82,144,183, +100,249,108,60,93,180,129,15,238,4,49,2,166,178,52,87,191,17,16,167,14,184,99,71,88,159,29,7,193,97, +96,248,116,111,86,234,128,79,34,126,41,228,214,95,175,69,94,34,136,63,154,72,108,188,138,149,238,190,193,39, +26,158,199,103,244,188,224,152,237,189,186,163,103,78,230,125,99,208,139,132,127,93,148,30,110,2,148,113,118,21, +252,148,93,244,73,221,118,148,39,146,243,221,15,87,189,58,112,147,181,49,228,137,234,67,1,92,135,145,85,80, +6,188,135,79,87,84,15,102,154,124,30,96,13,15,253,144,160,171,161,205,61,135,107,94,185,146,196,231,152,174, +64,159,20,235,219,85,41,188,90,168,251,76,22,88,132,170,160,106,5,2,223,89,124,183,240,231,41,20,207,211, +206,94,245,219,231,13,209,67,83,209,3,26,53,70,22,79,169,201,1,34,199,164,121,8,219,87,12,246,220,248, +144,170,54,112,45,5,26,91,183,65,146,9,252,15,255,252,180,135,246,172,28,27,67,77,116,235,216,59,135,23, +189,39,193,221,122,222,245,70,243,105,106,234,246,54,15,155,246,249,224,39,23,24,174,112,216,27,243,117,61,59, +175,194,73,191,9,122,100,224,1,203,7,139,27,61,116,241,154,136,185,150,115,45,248,190,212,114,201,52,218,44, +240,157,194,87,46,227,198,239,235,34,201,155,124,228,151,16,70,50,240,250,121,145,45,245,245,193,222,38,59,245, +74,89,98,17,222,85,190,255,70,189,207,121,114,197,183,74,199,212,248,186,112,12,187,190,10,116,107,13,6,41, +187,84,190,196,199,225,63,15,164,240,220,139,139,14,150,7,6,188,14,33,244,22,48,74,140,114,240,47,178,127, +60,28,147,52,182,61,168,96,147,19,13,227,206,96,150,222,21,236,73,69,195,232,237,103,103,248,19,71,56,215, +246,234,10,29,218,162,12,199,156,25,133,123,94,162,135,25,196,241,44,247,111,120,118,162,129,17,44,72,239,87, +237,33,174,183,223,50,255,166,245,67,6,135,174,234,191,152,182,100,211,148,141,88,115,100,11,47,49,6,115,104, +252,144,101,180,91,99,167,246,151,4,254,55,80,243,66,90,156,59,173,174,235,191,183,46,240,191,231,122,203,102, +255,47,55,215,217,220,54,206,132,191,191,191,130,239,85,113,102,153,147,235,101,148,48,30,51,61,74,175,215,52, +50,100,66,230,201,170,4,40,74,190,252,248,219,93,16,132,192,72,215,123,26,32,148,109,128,23,80,6,207,115, +43,248,95,208,248,117,83,24,14,233,221,143,124,3,243,188,55,182,175,123,221,51,87,251,172,247,23,62,214,93, +141,183,60,214,229,231,185,43,181,249,76,247,214,155,57,66,122,83,25,232,89,128,52,92,55,191,16,183,206,252, +23,197,245,59,98,146,194,240,228,35,50,226,219,143,135,251,244,187,71,31,42,112,67,116,220,174,13,216,39,11, +72,143,69,2,71,135,191,234,77,240,155,45,200,248,237,111,131,37,35,57,166,27,72,14,69,72,142,59,53,143, +133,76,63,250,0,132,183,31,100,248,23,77,207,209,214,163,182,179,155,176,213,60,124,90,15,159,237,124,210,159, +131,32,95,114,208,88,1,170,72,91,153,82,229,9,33,117,248,17,63,143,20,244,136,127,70,143,248,103,113,190, +245,17,191,167,150,163,147,35,51,140,137,14,112,41,171,210,199,196,188,248,189,209,186,253,11,66,117,145,75,57, +253,15,4,235,126,51,88,62,183,21,164,80,192,28,38,48,128,62,172,224,18,74,88,194,26,78,225,21,220,129, +209,13,135,134,110,37,176,168,121,123,18,50,241,42,239,220,203,153,177,240,101,124,15,149,192,211,248,37,22,110, +142,212,31,78,26,229,157,7,102,210,235,88,106,154,245,48,126,77,179,138,159,129,173,205,44,236,241,15,3,173, +189,219,1,90,43,26,160,181,47,123,134,124,165,27,27,2,222,209,212,243,114,189,197,203,183,121,231,107,227,229, +82,199,107,118,51,211,241,82,123,19,95,53,38,18,137,193,139,188,243,198,76,44,117,252,138,39,42,29,151,84, +217,181,197,61,50,140,167,204,132,49,229,109,175,220,160,135,220,60,227,230,220,53,11,135,152,61,231,190,204,19, +150,53,193,48,54,65,151,209,113,144,69,199,77,20,12,253,217,14,3,97,208,202,54,180,11,3,89,14,130,35, +139,105,218,67,240,85,182,103,33,78,123,123,40,210,193,77,25,109,122,133,195,247,218,191,102,60,162,95,124,249, +199,155,195,209,15,127,60,29,67,223,207,125,59,45,144,165,6,177,12,57,92,169,11,37,2,26,11,159,53,102, +206,67,186,159,125,214,229,224,15,248,99,223,205,200,52,183,95,114,123,233,218,151,174,170,104,8,195,240,92,138, +50,73,33,168,241,30,12,247,216,15,34,3,244,56,10,74,252,155,69,123,150,180,130,151,138,107,213,136,99,194, +98,123,199,156,135,143,217,60,72,197,120,28,72,161,100,132,148,247,82,228,13,88,77,157,23,181,171,14,242,64, +101,34,157,149,27,167,110,208,56,181,57,133,42,231,144,53,135,74,107,209,79,1,120,84,150,35,3,127,132,14, +70,7,199,132,107,57,226,115,250,122,96,34,227,240,49,12,3,23,12,3,31,239,68,174,139,10,1,62,38,171, +196,47,65,174,11,15,22,78,45,153,127,127,240,205,245,93,225,184,88,94,145,118,80,146,217,248,119,136,143,126, +85,103,86,104,138,52,131,115,170,38,19,205,168,234,169,125,35,171,12,187,9,211,227,177,21,185,179,194,71,67, +213,39,73,109,203,64,234,82,202,169,137,155,111,46,199,51,139,42,56,96,90,203,156,196,103,54,195,26,97,118, +189,56,189,6,254,134,226,202,5,233,174,23,139,182,224,112,140,174,172,13,14,137,153,95,88,219,25,41,234,215, +138,86,191,70,145,117,72,78,83,43,17,205,159,104,220,255,44,118,185,43,38,219,121,91,72,134,61,253,5,126, +110,28,254,102,241,212,196,95,196,138,103,166,42,234,153,28,191,178,214,191,254,133,110,109,193,175,89,207,6,228, +26,7,204,187,180,156,198,103,126,80,172,30,148,92,178,221,164,106,34,86,17,238,227,131,54,109,106,84,88,109, +172,213,216,214,20,238,44,25,237,161,63,87,148,61,44,55,142,53,179,222,122,204,51,100,14,174,103,88,104,172, +88,120,231,211,107,124,225,69,141,22,234,201,133,162,243,135,248,41,92,207,204,20,57,21,57,8,83,156,155,34, +51,125,67,83,164,76,191,85,64,90,207,78,97,94,165,213,9,214,157,204,129,41,250,148,89,177,171,191,209,117, +105,138,146,138,18,150,33,40,26,177,172,70,188,98,130,93,68,111,143,226,111,103,18,61,112,71,255,81,47,4, +108,202,54,154,142,123,97,15,238,88,174,164,4,190,93,244,16,184,77,55,55,186,5,37,33,177,101,61,101,110, +82,190,15,37,88,112,203,121,203,11,80,24,194,107,154,196,183,32,51,235,33,207,226,251,80,130,5,181,224,44, +47,120,161,37,199,187,39,9,9,191,96,36,252,61,105,161,240,73,243,174,20,194,226,179,235,60,100,39,18,62, +249,105,36,252,130,145,240,44,162,9,133,95,48,20,62,49,55,38,37,53,14,9,33,193,43,210,73,247,164,213, +189,70,193,89,144,128,67,230,84,235,194,30,177,109,118,227,209,148,220,51,135,98,213,14,110,45,113,76,23,181, +17,163,45,8,238,181,212,173,221,152,250,81,219,20,251,136,90,234,213,251,247,236,221,132,226,200,249,41,249,83, +242,19,45,142,151,10,67,88,106,210,73,119,59,187,120,153,166,213,179,215,188,132,42,220,74,110,122,27,210,51, +125,69,98,86,108,250,31,144,241,200,16,47,149,134,80,234,24,53,240,93,210,24,170,244,137,210,213,218,116,90, +138,173,182,119,204,132,42,104,53,95,59,252,31,18,207,234,53,89,189,102,171,255,144,132,198,134,123,201,114,83, +219,41,105,219,146,228,146,63,51,201,53,105,187,78,153,229,33,65,42,118,102,121,240,239,122,144,152,157,204,169, +132,185,30,104,164,216,50,74,216,81,196,248,64,131,146,234,11,28,101,140,16,30,242,191,47,113,98,149,225,82, +222,60,92,40,83,220,193,185,68,167,56,242,190,228,117,137,251,203,227,15,180,236,94,129,162,9,63,84,215,241, +215,98,208,153,49,201,87,253,191,5,21,233,87,38,20,177,65,40,159,246,171,192,159,59,250,42,148,137,233,133, +252,8,138,16,25,142,167,155,180,168,69,60,158,33,23,225,43,61,67,47,37,113,236,62,212,114,210,250,136,46, +91,125,92,167,177,152,43,250,154,125,163,176,116,94,5,37,40,157,23,4,154,118,84,159,44,210,12,248,191,10, +193,19,170,118,8,5,117,98,4,117,42,244,50,70,62,251,195,36,13,89,210,184,181,153,76,211,170,205,227,26, +105,80,161,113,204,62,114,241,38,46,51,118,190,13,51,164,226,116,29,33,124,84,47,66,61,104,15,242,31,217, +59,14,229,198,113,221,175,232,245,38,241,17,236,218,153,235,189,247,94,53,25,223,198,179,46,123,114,174,223,253, +251,1,176,32,195,113,188,78,219,94,18,146,0,1,144,20,65,16,100,108,136,136,4,143,52,50,49,35,137,171, +59,36,17,52,5,69,91,31,232,87,155,35,189,4,71,251,180,191,80,112,180,55,250,250,21,14,142,166,213,197, +214,27,85,129,122,84,19,183,21,43,237,147,197,129,239,180,239,189,102,161,19,65,227,79,59,90,250,116,195,225, +244,248,236,66,165,11,93,171,189,125,233,110,253,50,153,209,102,16,207,223,177,234,167,110,122,66,97,11,187,197, +116,222,81,243,23,234,234,235,167,175,105,238,20,225,133,10,11,42,244,56,47,88,94,73,168,23,185,158,154,82, +184,21,14,193,41,24,14,245,33,192,47,26,24,227,125,4,193,72,188,15,65,173,36,88,90,172,115,245,38,166, +165,122,129,82,19,163,115,37,228,88,65,52,33,228,12,209,215,109,5,9,147,23,32,73,53,161,178,166,38,8, +229,100,150,214,14,41,88,228,161,76,232,42,104,71,161,0,149,179,148,190,224,198,122,70,130,102,96,168,122,19, +179,140,132,206,128,13,49,181,212,105,231,235,108,74,27,131,183,145,128,22,169,80,106,68,50,204,146,80,86,138, +9,214,242,176,115,158,210,23,64,248,9,2,205,4,36,232,77,224,65,171,102,54,12,185,242,53,201,225,49,122, +221,99,63,140,208,69,26,77,224,17,6,169,101,92,212,228,145,199,199,179,144,57,161,153,216,122,180,121,35,176, +173,10,117,105,172,36,76,171,105,91,154,133,245,227,135,33,229,73,128,173,73,0,61,9,224,184,139,224,244,36, +128,215,12,12,161,48,30,50,107,204,231,162,68,172,149,47,44,231,248,93,19,92,79,31,28,119,183,39,82,181, +39,150,16,85,241,133,4,6,242,157,255,117,40,207,40,176,48,56,99,249,95,93,141,197,255,224,90,61,161,202, +38,196,17,181,97,105,20,79,163,152,36,208,17,119,15,183,138,163,91,199,203,217,188,153,146,189,108,254,223,188, +186,156,79,48,123,31,151,255,164,23,90,10,220,123,107,50,70,74,210,200,70,22,20,220,33,168,146,92,133,161, +8,185,99,218,137,191,19,110,255,188,27,129,135,144,135,99,248,224,150,181,34,11,132,157,48,112,145,232,64,100, +177,112,59,65,139,133,25,31,208,36,116,15,27,50,206,248,218,26,3,110,163,45,195,136,131,203,93,91,246,253, +198,150,169,203,224,181,92,121,53,24,158,84,134,87,240,60,5,207,172,186,27,139,142,111,61,23,79,77,248,206, +243,169,197,169,27,207,21,187,164,39,79,209,43,159,164,147,136,92,23,106,233,45,118,108,93,179,32,167,145,132, +45,217,59,95,177,155,203,98,151,152,49,134,228,145,128,19,245,198,172,154,222,194,246,199,122,96,200,67,196,103, +140,239,243,221,43,245,203,90,235,229,153,150,251,125,36,68,143,107,54,105,226,117,218,111,103,13,219,148,55,157, +227,2,89,63,227,93,9,46,3,129,46,96,106,82,134,152,93,203,16,210,33,17,22,70,186,74,179,132,106,45, +41,160,72,203,5,178,225,34,129,64,104,53,47,65,44,18,90,221,146,102,129,74,122,249,121,245,22,12,253,245, +67,119,131,72,114,4,70,45,35,14,146,163,110,79,211,135,202,15,93,133,161,167,118,96,46,153,97,48,22,66, +244,54,249,73,3,73,139,177,131,236,102,155,68,55,191,37,11,170,161,227,87,178,124,42,138,218,95,173,241,140, +92,14,51,73,3,74,248,24,189,73,49,149,24,24,4,26,134,32,24,242,72,194,79,201,7,15,33,150,74,51, +216,10,40,69,18,43,245,4,182,154,190,101,25,173,150,171,169,19,139,72,52,85,84,40,72,106,156,47,49,39, +23,9,204,53,68,147,19,4,231,51,2,36,11,83,59,208,248,74,81,23,18,133,2,32,8,3,161,52,53,67, +52,162,45,169,138,30,72,6,165,159,203,179,226,199,254,14,154,153,6,162,224,246,61,239,253,27,192,11,104,24, +56,155,207,167,39,247,110,3,112,97,219,248,19,226,238,25,126,69,121,227,103,122,197,237,153,244,208,98,116,20, +174,222,253,43,231,217,91,5,103,203,205,142,65,91,192,174,69,253,251,37,45,170,132,55,84,214,117,199,162,238, +198,47,60,188,242,232,217,6,1,104,1,70,1,198,185,77,130,25,173,173,23,76,79,236,96,226,214,146,141,134, +220,228,99,240,29,80,105,29,222,15,108,229,143,155,211,184,198,99,232,194,38,30,231,93,210,188,67,121,72,175, +151,218,142,24,127,239,85,242,176,246,93,175,46,157,116,231,243,62,86,252,178,110,142,28,250,215,103,102,221,141, +109,36,78,218,51,221,14,242,167,191,62,211,35,242,33,242,85,38,221,3,125,174,212,91,92,18,164,108,113,14, +104,255,50,37,197,182,245,4,178,3,145,5,193,80,16,103,34,56,151,50,32,50,153,196,255,232,92,100,75,235, +125,46,158,54,6,164,204,198,15,255,116,93,212,236,145,69,70,221,76,49,224,131,181,41,71,238,2,117,68,239, +236,178,197,56,65,93,124,135,193,215,122,78,111,46,176,160,137,159,108,49,215,115,26,153,94,106,5,220,94,206, +126,185,185,92,236,93,4,226,195,17,6,137,167,139,147,213,248,176,56,145,124,91,169,11,251,109,148,181,198,131, +207,182,208,174,32,197,218,57,214,47,71,199,246,84,146,107,129,113,73,147,39,22,193,14,106,206,72,224,218,42, +27,103,83,108,99,225,149,80,130,5,7,161,226,203,148,98,93,106,189,163,131,126,36,79,206,15,222,91,142,218, +147,91,55,171,29,74,66,69,69,77,80,164,3,189,22,74,151,60,185,245,193,6,208,13,67,81,157,35,72,92, +68,90,131,234,66,1,41,161,14,3,232,34,65,241,250,175,33,34,13,75,55,163,216,2,27,134,3,126,250,101, +87,245,39,19,84,154,73,143,165,15,39,115,188,107,237,80,235,38,205,113,55,251,238,81,93,225,215,178,94,23, +151,88,175,218,19,60,236,253,233,77,198,88,159,104,37,36,7,51,103,138,43,13,167,227,71,130,212,11,54,102, +235,170,138,210,182,146,178,38,110,118,169,85,218,110,81,195,64,189,78,117,219,154,69,247,67,211,138,176,131,206, +236,165,21,167,156,86,156,114,88,113,174,83,71,246,169,199,234,94,170,71,50,57,145,121,45,176,165,17,201,85, +96,124,194,57,206,88,25,245,252,251,226,140,205,121,214,120,19,27,175,152,188,117,88,29,161,224,244,229,236,42, +103,124,78,85,50,62,164,38,153,199,112,34,151,221,25,31,76,189,240,100,30,77,123,12,124,138,208,82,249,173, +189,42,95,250,12,168,79,77,130,59,226,147,157,27,193,95,182,192,158,32,123,142,3,25,75,1,129,126,81,108, +202,109,22,196,232,53,103,193,244,204,207,80,191,191,157,94,218,17,136,218,201,2,108,55,211,75,51,26,35,205, +60,168,202,215,213,51,82,190,14,149,111,118,199,3,35,103,61,171,34,125,212,122,87,21,251,39,170,184,81,197, +124,88,19,203,53,104,34,164,39,170,120,134,42,118,187,170,120,69,69,164,154,251,169,124,143,146,163,181,188,247, +251,243,189,186,83,219,119,157,54,123,68,21,242,94,187,118,143,161,234,28,105,213,121,106,74,87,169,139,110,62, +249,223,95,255,170,191,135,244,212,95,190,224,207,83,253,176,250,235,87,230,79,218,174,115,201,81,28,8,255,191, +167,240,184,238,40,216,98,226,101,246,40,95,206,57,103,15,6,49,176,131,192,139,224,150,13,126,247,235,143,22, +22,96,152,205,127,44,144,186,91,82,171,27,181,176,62,148,230,97,86,71,66,217,45,53,78,155,116,44,43,53, +47,97,221,208,207,44,235,215,124,10,15,163,17,94,57,33,188,196,220,145,71,56,191,21,184,23,148,133,150,21, +30,28,28,187,52,123,187,233,157,31,159,248,145,23,165,12,50,30,69,67,56,113,24,72,166,117,151,184,49,233, +142,146,110,27,51,148,134,123,133,123,92,20,92,64,42,224,36,208,205,145,116,141,13,151,88,36,179,152,216,58, +223,159,158,154,244,84,72,122,137,42,59,199,168,196,214,5,110,176,119,186,237,94,203,98,207,40,54,233,41,54, +49,138,117,220,108,21,174,194,19,174,213,179,67,159,148,156,56,46,107,168,215,48,199,11,73,120,200,47,169,67, +95,55,54,34,218,135,137,222,204,216,166,74,167,5,247,140,232,7,182,145,220,96,27,55,142,236,147,15,215,244, +161,136,57,84,203,249,69,79,181,106,82,181,243,7,39,198,79,102,219,118,207,118,87,189,107,96,250,64,123,128, +193,123,156,85,83,114,185,56,62,68,219,94,206,219,186,201,13,41,55,3,202,11,230,31,155,93,112,17,46,211, +124,91,87,203,17,12,102,108,255,79,236,40,145,33,84,89,10,124,68,183,77,151,118,203,117,20,170,18,219,165, +91,179,250,112,163,243,101,154,47,221,45,103,211,117,151,27,52,192,161,112,110,208,232,92,152,50,85,82,98,135, +42,143,57,161,24,105,204,41,169,232,98,4,68,64,82,12,61,15,73,0,240,1,88,18,176,224,34,102,222,24, +0,132,79,115,170,136,181,185,161,237,191,107,192,4,40,71,111,139,213,160,0,151,243,88,139,29,78,96,163,113, +2,220,166,235,158,189,53,211,174,124,189,119,229,166,173,175,101,201,192,66,182,212,244,108,169,57,176,37,150,18, +186,153,230,170,193,85,251,77,167,91,42,239,43,93,83,109,65,181,245,27,173,106,16,153,17,208,52,122,131,125, +163,21,15,26,51,30,186,205,208,17,183,25,62,211,56,15,27,253,48,104,211,68,167,49,105,80,111,96,94,15, +220,41,122,156,59,241,9,225,173,77,187,157,93,184,123,83,56,169,138,79,211,70,68,246,25,52,193,232,86,191, +231,110,148,105,28,237,5,185,88,64,116,100,67,89,47,43,161,172,208,57,4,7,214,253,13,167,217,11,240,174, +218,16,118,123,104,223,34,178,4,27,81,71,144,165,1,156,175,99,86,213,253,76,192,197,46,219,80,194,3,102, +136,213,219,109,203,154,80,242,238,181,203,145,103,179,75,207,227,165,184,61,236,241,240,76,233,110,200,51,41,1, +90,103,222,51,3,78,50,36,25,240,64,96,137,193,130,139,136,121,105,40,225,89,44,115,211,243,44,57,237,89, +27,156,66,94,130,164,244,37,250,42,97,74,178,235,165,156,54,37,22,16,224,97,209,214,17,66,64,232,203,129, +105,49,81,226,134,32,50,62,101,116,203,245,25,221,30,214,10,221,90,214,96,108,90,71,146,206,67,169,29,169, +77,99,157,70,195,83,237,242,3,239,225,198,205,76,167,146,77,193,204,167,147,38,57,132,185,61,203,124,202,141, +120,194,25,181,14,110,0,170,107,232,53,237,51,135,123,25,51,61,162,235,115,42,160,157,222,61,100,117,196,207, +91,238,247,162,233,34,158,22,121,96,254,138,89,174,210,192,27,20,38,181,76,35,44,45,86,98,84,162,227,168, +85,53,202,223,150,197,85,41,20,149,188,90,14,75,120,214,91,174,254,28,229,107,116,245,234,110,233,125,201,72, +239,218,143,236,20,120,225,122,0,15,151,243,157,48,181,70,193,116,173,113,48,93,107,18,120,33,239,94,216,248, +18,181,174,253,205,44,182,28,1,92,210,198,127,130,35,6,203,138,167,241,229,219,54,187,108,179,3,147,189,70, +246,0,237,91,140,113,60,55,97,101,1,18,50,159,248,208,184,198,29,210,137,174,61,122,52,200,133,237,46,87, +75,198,100,102,1,64,38,117,169,138,242,120,91,164,6,223,198,209,206,220,215,63,250,200,37,50,128,178,106,129, +75,244,31,227,248,97,77,212,199,152,2,0,196,150,145,183,191,125,107,145,93,237,239,168,51,77,102,238,232,188, +207,254,237,197,130,106,211,66,22,9,1,142,91,12,211,2,223,31,153,71,174,50,144,202,192,152,38,144,168,36, +167,175,67,13,112,154,138,148,120,180,165,93,245,128,147,130,7,29,185,147,184,206,237,8,215,89,114,18,96,232, +165,29,232,146,140,130,166,208,47,132,173,70,208,202,204,132,71,127,93,19,140,178,1,120,48,33,234,196,111,61, +157,141,143,27,37,28,199,75,0,169,211,56,59,159,101,55,189,71,1,194,143,85,76,236,177,255,45,216,217,88, +117,235,137,61,38,246,152,217,99,205,14,52,146,13,7,108,176,247,102,187,218,158,112,172,230,217,91,16,178,87, +54,148,16,43,140,124,208,101,7,177,207,187,250,187,29,207,103,217,141,182,236,102,210,178,155,103,176,108,203,26, +248,29,77,77,232,42,121,61,119,117,189,90,239,187,186,70,87,249,81,208,80,66,93,133,227,14,198,208,1,254, +13,255,218,22,248,233,7,120,108,54,81,247,120,192,5,84,7,110,252,102,8,247,194,225,218,239,42,152,67,171, +61,108,79,153,84,132,109,5,40,173,0,111,233,159,185,1,46,50,127,185,212,11,78,13,27,75,124,73,88,171, +163,226,209,35,76,203,40,120,244,8,51,188,164,30,48,148,74,58,46,3,166,200,206,92,37,170,159,83,41,138, +186,98,4,155,46,57,167,174,145,151,160,135,241,94,160,234,54,167,251,146,236,73,29,60,95,109,172,105,51,17, +148,157,196,16,171,171,81,13,128,206,213,91,234,146,88,2,112,39,101,144,71,158,22,225,241,226,33,34,87,136, +60,117,146,70,110,91,161,39,119,221,170,122,231,190,142,86,121,106,108,15,150,53,41,151,75,189,214,193,38,229, +98,238,245,237,118,149,137,198,97,18,104,232,254,120,227,156,218,107,250,189,69,90,232,192,108,205,123,103,164,196, +198,39,181,53,6,243,199,74,213,186,113,48,62,103,123,12,95,237,131,228,160,177,43,230,57,234,152,60,116,205, +76,114,91,154,112,74,255,155,18,16,180,189,220,150,197,217,141,112,117,24,154,37,236,3,72,56,201,136,58,229, +203,19,100,193,58,129,181,100,173,48,6,48,141,237,54,175,5,43,99,137,18,35,92,55,89,8,86,18,91,87, +58,44,112,212,189,180,10,19,30,119,152,105,160,196,32,114,240,0,48,204,124,4,223,101,161,24,81,106,190,12, +69,225,18,199,78,40,227,216,233,246,166,20,193,245,109,8,50,81,134,145,130,16,126,86,10,202,14,165,232,136, +196,219,55,149,131,45,221,88,101,132,235,79,25,205,72,231,194,67,241,209,160,143,34,154,97,71,209,33,243,189, +62,51,67,233,230,248,185,116,32,66,127,160,199,136,208,171,139,105,1,40,235,179,239,76,59,76,156,244,92,154, +102,135,125,62,25,40,58,28,146,55,135,34,222,237,62,218,210,17,96,81,55,243,101,51,158,241,135,149,104,151, +243,204,195,235,124,245,18,170,28,15,204,188,72,46,127,188,196,231,28,248,29,97,96,21,175,21,120,133,223,225, +95,183,193,83,225,95,175,2,183,14,128,127,229,105,232,108,0,113,149,237,164,197,11,20,225,255,181,12,147,52, +139,74,129,93,82,250,187,8,124,149,21,21,222,183,82,101,97,64,146,187,199,99,142,239,53,196,84,51,223,170, +69,17,47,190,219,220,17,97,117,114,45,238,43,59,117,28,97,94,210,42,135,102,157,252,47,245,143,159,210,207, +30,98,156,239,78,111,29,189,178,184,181,248,40,9,202,234,228,142,90,252,71,251,236,78,206,145,197,95,217,227, +23,250,33,138,239,40,188,213,71,145,29,58,132,170,185,184,48,108,31,21,121,85,166,100,47,52,18,160,248,81, +100,130,44,40,90,32,36,41,23,85,34,22,223,124,241,243,226,235,52,20,185,18,68,113,202,205,94,23,126,167, +16,123,191,52,193,51,146,122,115,47,205,9,98,239,251,75,200,136,177,129,110,185,218,19,167,123,106,236,137,246, +152,246,164,20,119,107,161,170,15,58,196,236,167,101,32,197,174,191,146,107,138,113,168,240,63,105,79,194,159,54, +238,236,87,105,211,227,103,217,178,177,13,228,48,56,244,190,239,54,175,233,75,121,89,67,85,162,13,145,179,182, +114,180,33,223,253,205,72,22,146,3,251,191,15,36,102,70,51,154,83,23,155,101,96,153,42,223,125,88,129,251, +35,72,112,89,162,252,168,158,195,92,163,41,28,108,189,138,56,255,228,123,153,31,140,141,249,150,179,137,162,168, +34,87,101,46,128,150,214,192,16,72,99,122,88,234,241,122,114,84,45,34,138,7,143,138,211,211,249,47,117,65, +113,77,220,160,152,184,65,177,78,140,176,86,26,121,173,13,3,174,115,238,134,129,83,189,73,53,194,84,76,10, +66,229,245,117,179,192,138,156,231,187,188,57,141,86,184,7,156,179,159,80,85,20,8,130,28,0,149,122,7,202, +54,154,191,225,65,75,153,55,246,107,143,148,206,32,150,121,50,96,164,147,210,195,162,161,166,162,161,247,132,17, +210,176,38,64,142,8,35,97,212,140,205,164,245,217,99,9,177,209,76,250,178,112,3,6,205,196,243,120,197,80, +54,56,130,224,186,229,255,175,110,228,228,102,147,215,68,28,111,5,155,243,39,239,112,16,44,255,58,70,120,173, +218,251,247,91,95,49,225,52,223,219,113,147,162,50,215,9,233,68,149,44,63,67,158,136,153,14,12,190,76,68, +25,213,103,147,90,86,94,76,183,8,154,227,160,84,67,97,111,182,68,133,155,10,163,164,141,157,217,253,118,85, +106,246,173,247,239,255,51,209,174,148,166,112,0,83,61,241,187,24,24,158,99,20,113,118,50,97,21,30,137,111, +113,32,40,196,20,49,239,20,20,223,211,234,103,92,112,201,188,192,189,111,56,85,49,184,156,218,93,144,57,226, +153,180,19,127,218,194,175,245,1,6,86,51,169,95,58,152,32,144,28,202,90,169,4,86,226,17,86,229,175,92, +30,121,27,247,32,170,78,139,170,102,207,230,101,33,65,108,7,150,214,140,119,36,253,85,254,87,60,124,153,5, +220,42,248,209,148,18,220,25,130,163,26,150,202,192,192,215,208,109,144,70,67,147,137,12,102,224,220,181,153,20, +209,225,172,127,42,4,28,49,232,8,180,101,206,163,57,19,51,121,4,36,88,240,235,188,12,147,65,189,11,113, +95,135,33,145,74,30,48,197,202,78,107,50,96,243,154,221,82,132,72,49,44,7,117,16,252,13,21,176,199,232, +81,108,171,188,189,124,208,50,175,140,224,191,225,84,1,175,49,197,79,71,159,154,219,18,102,222,247,65,206,109, +190,88,220,150,139,133,81,6,2,85,54,93,147,55,137,90,207,88,30,83,177,212,121,192,134,98,16,4,12,45, +81,195,226,197,198,48,47,137,77,173,54,234,248,23,56,32,90,46,129,93,217,2,224,145,139,27,140,238,89,57, +38,81,237,172,75,142,89,212,152,221,58,236,164,56,245,74,8,106,99,168,171,27,153,61,173,88,1,113,175,207, +254,236,166,5,69,206,140,26,122,253,136,149,134,131,90,233,84,19,208,3,45,152,163,112,221,181,5,97,121,90, +177,115,60,47,109,166,31,108,28,30,170,220,62,60,132,109,194,50,203,161,239,236,74,54,198,90,241,247,63,117, +190,135,137,195,172,176,113,135,238,65,230,70,241,166,126,213,96,104,142,246,102,208,12,126,123,53,164,250,111,175, +36,163,199,204,195,48,21,36,67,2,156,126,233,4,0,96,111,44,177,207,61,73,32,147,15,36,120,45,23,198, +36,40,245,183,107,237,1,195,197,248,234,186,17,15,114,163,19,86,205,88,181,88,156,23,202,114,104,197,10,235, +253,176,6,3,86,106,230,50,23,7,213,152,2,43,73,8,140,148,92,156,177,134,69,225,122,4,240,75,38,115, +8,178,105,94,44,189,51,156,2,191,57,41,189,226,96,62,166,122,250,235,60,192,220,146,101,52,189,210,179,204, +46,138,107,199,12,23,141,129,255,214,188,66,155,183,54,230,21,202,188,53,25,129,20,65,107,146,173,148,240,163, +162,126,127,33,62,84,229,41,131,99,165,206,67,73,57,238,237,140,35,106,66,154,122,121,92,224,253,198,91,248, +140,54,108,205,250,38,180,6,154,134,229,124,25,33,111,11,128,155,240,99,42,90,70,38,11,51,102,245,122,213, +148,117,52,61,80,29,23,214,127,104,89,157,190,32,6,85,215,225,14,181,81,236,178,1,225,144,191,203,213,140, +81,17,50,50,166,44,23,65,226,140,88,53,250,172,181,219,83,123,209,135,210,139,241,97,101,239,20,44,241,24, +114,222,35,1,215,91,54,47,49,6,152,200,156,219,66,127,219,93,86,232,179,22,206,45,214,116,79,232,53,194, +156,248,107,254,155,97,177,82,157,118,169,106,140,120,11,121,16,244,178,68,23,121,140,172,169,52,214,5,15,11, +87,159,229,253,198,73,121,86,179,179,83,88,100,45,76,221,189,180,33,165,192,195,202,9,19,103,102,181,222,207, +223,22,242,40,250,240,146,238,229,169,191,79,63,23,249,94,176,79,43,158,235,5,58,250,240,254,243,203,47,47, +255,231,233,225,203,119,207,224,95,211,242,229,27,125,92,228,251,157,100,59,166,175,160,77,233,148,65,211,163,31, +69,190,239,167,157,46,61,145,154,229,188,156,37,49,125,219,124,171,249,76,88,45,158,131,87,76,24,53,4,234, +2,210,195,106,153,95,232,204,224,157,132,117,49,245,121,19,243,76,83,158,150,23,30,114,198,254,207,121,9,166, +60,145,152,31,4,43,127,199,108,124,61,49,204,147,81,146,65,147,142,82,108,250,163,126,150,196,196,119,194,241, +67,225,206,227,0,3,74,207,246,175,10,89,170,152,20,202,91,34,79,6,98,200,6,34,8,8,191,39,192,154, +120,37,5,249,117,86,31,121,130,208,166,199,59,130,180,242,192,99,139,24,82,179,65,51,36,172,75,96,142,53, +16,111,168,194,146,128,66,167,30,32,156,105,49,235,228,219,188,126,87,188,243,90,219,9,119,215,196,221,170,97, +138,167,9,16,165,76,49,169,61,30,74,50,116,20,127,82,180,51,217,117,129,157,127,40,135,57,228,31,11,228, +110,206,29,49,165,17,163,236,131,139,116,99,163,152,214,118,241,21,195,90,153,171,132,204,21,99,92,117,181,42, +165,42,57,248,226,168,196,98,71,127,5,139,80,232,20,151,10,142,29,79,125,5,184,163,228,161,116,51,192,247, +84,44,58,248,83,209,198,3,182,179,239,224,95,43,60,38,220,93,167,170,162,38,50,79,40,51,203,172,107,17, +95,146,142,132,52,230,3,34,253,28,98,143,5,193,210,72,150,243,113,217,54,170,140,46,67,30,93,82,1,189, +95,208,251,69,107,39,188,152,207,2,225,11,29,100,165,70,20,178,16,169,39,156,82,86,14,195,168,239,239,67, +172,149,65,190,71,232,149,250,75,28,89,73,127,112,189,149,206,106,247,111,129,240,214,18,99,133,45,19,199,204, +41,37,129,3,211,179,75,93,51,191,47,92,86,24,64,193,231,130,220,219,11,247,45,205,79,199,21,30,191,183, +23,236,1,129,19,100,204,238,18,204,166,64,13,161,37,182,146,208,10,91,70,104,129,109,25,214,132,206,177,87, +97,111,138,61,204,15,122,164,123,213,210,40,53,36,22,92,139,99,3,203,187,128,55,15,221,43,118,225,236,50, +29,30,217,41,8,185,146,17,38,178,232,50,248,24,229,174,230,111,220,18,171,24,132,221,116,107,115,155,226,231, +150,107,34,105,244,203,19,22,110,218,160,219,181,129,141,178,67,152,33,31,218,160,70,88,32,154,2,252,39,51, +7,233,120,177,192,38,161,251,194,158,147,67,235,186,148,38,177,15,142,200,19,66,124,237,90,16,160,82,219,223, +235,48,66,232,43,103,160,51,44,132,113,124,237,144,32,161,199,44,191,210,127,81,57,195,121,80,188,121,121,41, +62,158,21,63,212,119,95,67,222,159,73,3,10,57,78,34,37,13,165,131,129,234,151,71,125,50,76,70,16,178, +48,50,195,208,245,66,51,32,76,204,152,199,103,19,62,213,236,29,1,75,168,214,17,81,65,98,165,56,104,87, +140,18,132,114,112,84,170,71,165,196,170,81,73,35,200,213,197,192,67,207,10,243,185,157,161,75,179,42,79,169, +230,138,196,177,142,80,46,28,161,174,88,131,113,133,186,90,186,36,171,82,87,53,109,107,251,25,220,168,148,82, +158,158,150,53,84,174,87,224,99,35,222,224,77,32,32,218,140,117,208,74,181,37,139,125,159,59,158,123,122,121, +90,102,38,94,71,113,182,18,156,9,33,70,156,75,139,43,113,184,18,146,174,226,134,250,79,166,143,250,124,24, +245,81,245,155,18,252,20,101,40,59,180,25,90,164,181,200,99,94,233,56,219,133,25,128,211,60,91,17,147,144, +91,205,112,186,134,214,37,49,126,50,252,44,153,227,31,107,175,54,227,236,38,220,248,141,4,214,158,248,100,202, +167,174,226,251,184,201,142,226,173,62,141,186,214,150,171,116,175,28,58,195,206,82,58,37,236,166,69,81,128,159, +210,40,73,82,24,219,235,227,68,3,128,35,67,52,160,131,185,110,248,62,42,166,199,14,71,140,59,207,75,162, +173,56,233,111,7,104,160,176,249,66,174,205,140,219,67,180,33,87,134,5,102,88,144,24,81,206,80,189,38,55, +36,102,91,231,166,133,167,39,226,73,31,168,250,105,159,232,185,72,66,156,60,89,71,18,72,8,18,171,93,9, +21,93,133,126,18,30,179,200,40,160,160,232,78,66,219,32,199,16,195,164,147,70,91,253,209,86,212,223,76,49, +79,209,204,169,11,195,164,64,209,10,134,162,161,65,154,6,226,82,165,81,234,144,237,116,129,176,133,221,108,161, +183,123,64,224,88,205,106,161,28,13,138,184,218,161,107,137,31,245,179,21,5,117,210,248,42,6,174,7,205,149, +254,131,227,179,234,120,206,58,234,121,228,214,121,28,37,209,142,123,175,63,227,242,232,108,2,5,226,164,227,82, +222,169,88,241,227,132,57,215,252,241,173,87,103,199,199,197,173,215,64,197,230,197,191,115,189,63,147,249,85,156, +197,52,201,18,154,102,41,237,102,93,218,203,122,180,159,245,233,102,182,73,183,178,45,186,157,109,211,157,108,135, +62,204,146,152,62,202,146,132,62,206,146,148,62,201,146,46,125,154,37,61,250,44,75,250,180,64,236,4,177,83, +196,254,64,44,67,236,79,192,94,211,187,60,223,136,147,180,219,235,111,110,109,239,60,124,244,248,201,211,103,27, +244,75,129,11,243,93,126,192,239,39,253,49,125,106,190,122,252,126,218,139,201,238,110,111,28,44,177,95,25,98, +45,10,119,254,136,113,110,52,63,185,219,138,175,96,247,8,111,63,85,103,102,58,19,211,41,156,109,198,35,53, +240,188,0,83,217,219,188,229,253,190,249,55,146,220,217,192,243,8,244,122,139,5,54,253,17,44,236,85,150,246, +251,247,103,242,0,127,151,54,246,147,45,58,179,144,84,67,38,22,210,213,144,34,211,12,20,172,167,96,72,115, +157,41,254,91,154,255,14,1,121,74,132,97,63,28,246,22,134,49,200,49,28,17,108,56,129,48,213,237,59,224, +77,0,107,137,59,90,226,150,131,220,30,143,149,104,92,85,172,69,94,58,22,81,102,29,125,41,178,167,197,210, +36,247,239,131,57,2,169,76,172,154,153,110,38,36,64,219,14,129,227,72,42,43,227,175,149,45,223,47,173,220, +134,124,88,196,230,229,128,59,59,43,179,143,91,110,242,56,192,169,116,127,45,231,48,130,177,30,50,246,33,229, +251,132,198,20,164,187,155,200,181,164,235,40,79,196,42,101,71,49,197,235,101,32,79,28,226,191,196,26,182,64, +134,108,177,105,244,122,86,228,157,255,171,102,147,98,244,221,251,94,251,222,65,24,68,223,127,140,3,226,221,35, +163,131,239,53,29,7,10,198,254,49,208,27,101,8,239,172,32,200,8,184,126,39,119,59,214,54,191,91,103,238, +103,69,196,46,217,212,28,182,89,14,58,59,247,190,183,155,187,34,8,10,124,156,48,227,170,60,64,208,64,145, +67,216,67,156,140,192,230,21,201,42,101,59,115,251,38,144,46,25,211,26,219,238,152,150,216,66,186,10,51,46, +85,227,4,201,4,161,181,1,246,20,176,38,89,77,104,105,128,155,10,88,146,172,36,20,130,94,64,132,215,16, +206,37,196,46,115,127,78,244,66,169,103,35,113,25,112,127,160,153,189,187,87,16,148,215,244,22,182,179,166,157, +168,246,68,232,188,39,127,100,72,186,158,18,176,196,28,28,208,117,222,81,61,47,70,139,163,139,201,226,168,62, +39,202,137,174,11,70,217,15,54,91,227,180,123,171,144,198,135,255,138,11,223,174,188,131,74,223,57,77,37,33, +67,107,122,37,173,224,35,224,157,110,76,238,37,41,164,14,11,133,191,154,62,85,216,165,59,97,69,19,66,195, +196,28,242,14,106,47,6,46,222,54,126,244,200,216,90,248,107,113,83,58,94,166,192,7,72,218,4,73,155,74, +16,243,229,26,81,37,237,133,37,10,138,151,114,132,7,185,38,188,46,126,36,174,156,119,43,114,148,222,9,141, +250,205,147,174,186,41,144,1,219,77,192,207,117,158,116,240,11,161,210,207,107,202,224,131,52,111,31,93,245,246, +129,47,232,126,158,132,50,100,20,251,65,46,237,83,186,125,196,133,48,112,111,88,171,142,202,8,232,205,84,175, +134,222,68,245,74,123,162,84,79,37,120,156,54,138,26,72,129,14,168,72,39,85,19,86,63,94,54,50,75,72, +168,10,230,125,148,151,97,133,247,217,187,176,107,56,234,120,105,8,223,73,6,61,28,73,231,57,30,174,217,200, +19,97,77,58,71,129,39,134,245,104,51,139,73,134,112,49,242,234,144,33,60,205,60,240,46,246,122,48,102,238, +111,198,1,216,137,30,204,23,49,157,46,224,163,112,76,123,40,236,205,128,158,78,251,121,20,175,252,185,39,97, +105,163,42,127,49,81,73,6,16,53,132,168,151,149,247,204,41,119,151,162,125,206,71,9,111,75,170,96,238,115, +89,177,74,246,174,88,33,251,182,134,236,235,42,217,195,178,117,253,209,5,149,225,255,4,59,150,104,175,85,238, +254,92,91,238,84,169,147,230,42,10,43,20,22,59,181,190,154,194,19,32,148,100,239,77,143,44,159,89,96,22, +129,178,15,45,117,149,83,171,65,133,253,158,234,27,135,163,37,113,167,0,197,98,99,36,114,176,133,122,46,36, +217,18,81,159,35,226,219,18,33,242,75,209,244,85,205,67,127,204,160,73,112,33,23,32,83,23,63,251,166,169, +236,166,87,102,150,171,72,30,48,24,132,115,196,54,144,132,50,228,169,126,225,86,229,8,131,206,12,58,9,118, +38,208,73,157,48,121,94,152,171,58,222,126,253,144,138,57,50,83,49,34,114,88,238,80,11,76,56,213,77,237, +99,216,178,254,66,169,196,250,203,84,45,21,215,247,176,169,85,227,214,94,36,91,67,69,254,104,202,238,255,138, +252,234,242,255,73,54,107,3,6,162,24,134,238,242,106,79,160,46,204,204,169,194,204,184,126,116,185,230,155,45, +169,247,23,171,249,227,72,76,197,41,253,236,58,17,143,53,49,22,62,185,34,70,98,251,32,134,226,188,94,237, +223,103,98,32,140,255,114,79,70,172,109,250,226,122,34,122,54,238,204,122,126,78,228,68,210,180,37,242,34,217, +252,36,186,246,46,68,71,88,207,253,125,221,59,87,16,187,61,209,246,212,149,104,137,185,87,53,61,236,197,13, +179,179,169,139,255,57,27,81,52,160,23,212,196,114,71,148,132,185,123,71,217,105,67,86,82,32,67,86,197,119, +199,143,171,171,224,115,220,118,162,159,229,207,80,146,89,46,211,49,44,22,114,87,26,105,164,93,119,19,59,181, +157,77,211,126,249,190,25,57,247,75,107,45,232,61,129,53,163,55,10,152,240,126,24,38,158,117,62,124,27,222, +255,123,52,209,198,248,247,183,169,159,59,156,30,179,127,48,11,73,193,113,3,242,151,29,1,97,75,249,13,141, +151,40,110,64,112,9,234,183,221,74,122,72,21,92,232,110,164,109,21,43,246,128,221,132,254,164,117,40,61,42, +235,147,53,223,255,187,209,92,255,44,240,217,102,232,89,203,157,103,120,86,235,202,255,251,238,58,204,239,255,221, +82,238,66,142,234,231,251,30,38,85,57,229,4,180,27,215,135,253,48,176,56,193,89,139,81,122,122,49,75,235, +42,182,129,12,252,64,227,188,218,77,203,64,141,48,131,191,38,49,140,243,186,205,2,154,92,157,201,222,154,88, +25,1,189,60,139,37,140,210,73,93,182,85,96,37,167,110,125,167,214,91,177,199,143,221,102,26,48,18,246,89, +89,8,113,32,160,100,251,175,210,212,58,100,132,149,172,130,155,235,23,253,57,70,138,113,214,70,136,111,232,0, +83,90,73,82,188,122,40,59,52,70,243,167,37,119,183,116,215,161,29,187,166,150,118,27,186,121,56,147,244,107, +140,246,124,221,221,7,109,94,85,181,203,35,152,43,234,213,253,209,122,99,4,251,7,216,115,219,22,185,247,128, +43,78,173,181,108,18,223,67,29,109,91,55,36,248,177,246,101,163,243,54,2,127,37,230,148,182,96,235,4,233, +168,243,88,34,9,196,80,79,224,5,170,250,192,25,178,203,196,181,176,136,139,191,191,205,159,109,183,151,234,193, +172,108,19,158,238,14,210,181,83,167,113,183,209,158,235,86,146,98,116,125,130,7,190,73,147,146,133,214,72,139, +216,173,220,216,137,140,92,46,27,152,245,213,217,162,217,24,41,26,48,195,106,154,191,249,12,230,229,185,117,90, +103,247,108,234,84,199,70,123,185,161,203,201,93,13,50,147,146,64,220,14,211,188,244,98,145,164,206,181,74,147, +27,241,215,113,26,153,160,66,96,29,181,53,72,0,201,115,198,164,236,243,61,102,145,99,204,163,192,147,106,183, +67,31,14,47,246,75,184,40,49,47,222,169,91,135,248,184,228,142,122,153,37,207,149,175,188,18,55,24,100,41, +83,14,43,186,251,171,131,212,78,173,23,129,68,19,106,139,186,107,186,127,248,98,196,140,214,161,142,116,196,223, +78,183,218,194,196,74,168,125,175,99,109,124,84,133,188,132,6,252,131,24,135,164,98,146,32,124,37,222,38,102, +27,106,128,20,37,201,0,32,85,118,208,104,7,76,78,17,123,225,119,226,28,148,250,133,11,73,64,186,179,214, +4,204,157,162,211,178,197,114,87,251,12,40,201,17,152,76,67,130,147,28,115,227,114,82,156,84,99,27,31,162, +224,36,200,166,177,182,109,5,162,239,19,136,51,252,214,42,50,227,75,14,32,116,140,216,130,129,23,186,77,16, +100,82,62,197,16,66,132,139,28,196,37,128,168,72,172,63,134,92,82,141,224,115,25,164,53,178,97,229,63,89, +15,235,218,51,17,56,217,173,103,100,82,240,57,170,42,46,0,183,187,113,251,18,206,40,26,195,14,120,49,176, +240,174,104,50,96,141,183,198,213,54,4,41,221,142,151,55,90,33,82,43,221,94,104,52,114,230,61,128,6,153, +234,164,201,42,91,97,96,29,247,178,192,107,160,180,89,219,24,112,151,179,95,209,70,87,99,184,85,42,77,243, +225,124,90,214,227,144,129,25,188,167,233,50,17,14,253,244,116,79,63,15,199,96,226,64,172,156,106,24,175,102, +188,38,47,37,28,43,113,143,44,64,82,34,254,72,136,63,119,112,133,179,33,135,201,111,86,35,170,80,158,224, +138,133,40,19,161,14,98,130,67,208,217,246,44,28,99,44,132,96,137,18,165,2,178,209,181,86,177,184,129,98, +192,166,240,141,35,216,53,166,45,192,209,150,14,180,127,176,85,155,34,87,160,30,63,219,238,98,20,130,201,181, +32,194,184,147,57,178,85,17,129,146,12,189,241,14,96,189,131,195,152,201,48,3,13,251,23,99,146,81,80,61, +44,115,40,246,138,63,86,46,192,127,11,89,215,69,33,218,75,22,38,129,156,79,7,55,14,242,210,229,188,44, +42,96,14,103,186,174,102,117,43,19,48,17,243,203,244,226,102,93,89,101,176,224,24,8,100,77,147,75,141,158, +15,169,70,44,169,172,209,235,49,52,130,117,85,35,112,186,13,235,181,234,187,10,1,68,247,176,39,184,201,84, +121,206,2,215,247,97,132,193,6,9,205,79,34,41,192,100,213,93,77,149,198,254,18,85,176,78,2,22,65,117, +130,166,126,72,65,68,34,166,19,129,198,6,134,29,35,174,172,109,46,75,217,44,11,5,35,107,61,64,56,75, +58,65,246,193,52,195,91,108,93,100,11,56,108,104,30,116,5,44,74,24,163,50,135,183,25,85,117,114,69,13, +54,23,179,158,62,6,136,21,7,7,47,29,37,42,91,66,211,102,184,19,170,66,194,172,31,3,222,24,201,39, +25,145,134,248,201,137,22,207,232,191,199,183,237,29,206,150,126,123,254,211,217,64,255,236,255,122,198,214,171,229, +72,238,155,75,69,143,135,196,113,28,252,120,174,154,30,14,255,61,157,47,71,120,235,218,127,191,124,64,12,199, +10,147,30,47,15,114,154,29,225,12,163,49,108,37,172,240,177,245,85,255,122,192,123,218,225,163,45,141,242,173, +238,140,33,200,153,75,89,141,183,210,175,233,123,220,5,224,227,143,179,250,223,242,121,2,57,155,50,242,247,15, +226,190,132,187,109,91,249,247,171,196,122,47,167,164,5,169,182,239,91,41,195,58,105,147,110,255,52,77,235,220, +237,248,239,227,75,83,144,197,27,154,208,37,225,218,174,163,239,254,230,55,88,73,219,105,223,126,114,98,17,131, +125,48,0,6,51,131,193,121,52,69,66,251,222,169,216,199,191,49,127,253,14,70,204,239,148,68,143,197,59,53, +103,235,120,235,217,89,158,29,8,254,119,158,7,206,251,157,58,171,231,70,191,213,183,222,122,38,152,52,211,115, +109,116,76,48,246,152,96,236,49,193,216,99,130,71,1,4,148,75,156,82,88,172,151,156,204,84,106,15,233,44, +11,220,17,162,37,219,175,120,214,61,136,138,91,68,76,241,103,95,9,195,26,41,58,122,21,80,65,241,201,165, +181,167,140,214,159,50,90,127,202,192,209,37,169,251,84,15,180,230,117,176,127,45,123,152,173,100,6,118,101,34, +53,180,48,125,20,64,82,143,15,168,187,16,48,31,136,146,187,229,241,49,182,241,13,38,81,39,146,174,127,103, +156,183,182,216,170,45,182,32,60,117,133,136,144,152,211,206,75,9,81,33,97,46,207,115,72,98,185,213,163,202, +15,119,185,112,41,233,39,167,64,98,122,85,62,109,9,133,147,95,55,89,178,88,174,224,211,170,187,175,80,214, +227,75,10,144,196,165,54,188,101,157,251,145,15,38,58,206,44,202,56,219,25,133,226,157,57,242,178,149,166,167, +114,10,37,83,203,89,154,36,36,226,54,100,21,242,55,251,243,3,126,114,193,151,176,46,186,43,26,47,247,77, +23,73,234,149,220,219,107,119,87,84,56,135,162,181,118,146,132,163,33,67,115,99,4,92,133,210,162,149,164,67, +235,117,27,144,213,219,124,232,107,72,111,219,188,163,47,107,2,253,100,141,203,239,202,164,134,34,124,238,54,234, +238,115,217,190,127,46,91,223,124,46,219,183,207,100,187,174,239,178,129,196,10,81,11,12,155,7,193,132,146,82, +194,14,22,191,60,74,93,48,120,196,192,116,164,89,43,148,104,200,28,172,156,29,226,30,225,188,156,233,121,137, +215,236,178,102,191,178,198,125,77,145,53,211,138,238,234,29,78,9,150,147,90,13,66,166,142,196,91,27,209,207, +59,150,154,110,246,233,107,218,237,235,121,71,178,32,2,95,5,240,21,131,175,44,248,50,128,47,25,124,105,193, +165,44,9,84,78,73,5,87,230,4,46,5,55,93,246,65,186,75,132,170,91,21,81,132,55,219,203,218,162,6, +152,217,149,205,118,67,168,10,9,2,166,220,28,177,68,182,227,171,16,170,139,88,82,50,164,12,118,62,243,146, +133,119,54,199,85,71,107,126,85,54,42,108,18,38,102,17,74,126,192,4,236,246,231,127,154,154,249,21,233,214, +254,59,253,94,238,147,98,53,49,218,239,36,197,73,130,75,101,11,213,91,98,26,255,80,51,166,174,25,173,186, +42,205,147,109,72,170,1,118,103,244,43,80,157,253,190,18,168,214,126,95,218,162,88,142,161,218,136,44,44,201, +177,75,71,194,35,11,146,143,207,165,155,249,132,125,73,209,104,222,51,73,15,99,145,234,247,211,134,98,59,109, +134,9,127,46,147,132,62,85,98,81,172,179,122,76,33,181,23,162,191,215,108,125,147,46,106,95,151,237,175,101, +255,109,87,174,106,213,210,210,255,68,228,251,210,24,213,37,246,140,170,143,117,80,153,86,223,206,21,39,74,171, +250,185,52,243,208,123,146,152,206,29,126,231,135,249,60,89,60,92,123,255,245,180,241,184,105,159,2,39,2,123, +21,204,110,247,76,52,185,141,134,106,253,182,169,77,70,198,190,209,202,217,90,243,169,212,154,111,58,13,26,24, +48,41,237,249,2,86,185,250,156,248,7,252,60,213,134,167,12,161,191,169,71,226,85,191,103,196,93,1,86,226, +174,209,148,174,240,161,201,132,250,234,247,167,127,149,227,253,137,201,32,188,120,40,127,213,245,138,158,75,98,232, +37,73,151,174,216,156,143,61,221,201,9,107,99,28,119,67,184,158,184,100,124,145,253,179,73,42,27,249,31,232, +81,7,134,132,171,4,184,40,238,0,116,248,168,212,251,250,78,53,191,160,41,82,201,19,101,239,193,205,183,77, +105,240,126,28,94,39,125,61,74,151,185,77,79,53,234,90,181,73,129,234,87,14,158,89,59,223,107,77,46,19, +132,253,214,55,102,18,252,24,76,140,190,169,54,124,171,202,7,56,237,185,45,5,222,84,228,195,186,188,174,27, +226,221,191,248,78,17,243,111,234,170,36,158,240,70,125,33,94,68,8,2,175,186,186,108,232,163,47,219,126,214, +171,174,38,102,26,86,204,208,160,179,87,152,98,226,124,0,9,216,143,125,199,94,255,138,195,249,145,184,181,159, +24,127,215,126,190,130,41,31,210,208,87,163,1,201,236,61,47,154,37,237,120,176,242,52,91,24,160,81,150,8, +79,147,63,78,88,37,73,216,122,253,213,93,221,75,184,132,117,32,163,186,146,233,84,62,92,235,21,122,137,205, +161,39,140,114,92,79,20,94,236,29,184,174,92,151,4,164,255,175,250,45,193,237,96,239,57,146,211,237,119,220, +109,224,193,67,190,198,64,37,16,112,243,68,239,33,15,29,50,175,234,54,14,60,85,188,37,26,167,227,115,72, +194,219,78,160,237,0,137,121,250,141,190,125,91,183,49,199,170,43,111,95,241,155,220,111,28,97,253,212,126,208, +219,24,175,250,170,171,47,177,166,130,3,202,146,169,137,185,138,52,86,243,113,133,200,104,74,169,92,84,190,11, +37,140,179,182,54,35,240,208,213,171,71,241,255,50,54,158,198,218,32,210,106,173,194,18,19,107,16,93,8,180, +208,106,77,46,38,83,181,112,235,141,53,204,119,55,27,106,213,103,90,60,208,145,168,120,176,206,6,52,76,143, +111,187,218,148,151,141,226,161,59,83,20,169,232,94,154,234,28,76,160,107,190,226,134,183,81,42,65,84,178,59, +235,195,241,230,183,172,201,151,195,163,1,225,28,62,206,138,55,89,35,170,124,39,128,190,38,127,112,249,101,179, +219,225,218,46,152,208,239,36,246,158,127,149,217,195,5,112,181,181,173,161,205,103,175,158,243,140,117,119,199,248, +149,143,11,166,76,159,2,247,242,38,118,9,152,8,123,155,249,225,98,93,54,205,37,139,204,19,146,157,236,68, +18,42,6,85,237,29,198,98,17,162,150,45,162,225,112,114,56,192,101,171,191,186,187,11,185,253,180,107,70,190, +4,225,22,136,194,252,95,186,223,233,228,197,132,189,190,81,132,157,250,75,255,17,162,184,52,74,185,189,123,49, +153,250,242,18,183,174,216,17,194,240,91,131,104,147,96,94,211,14,99,33,178,158,95,171,178,191,233,212,7,117, +103,178,62,159,179,95,43,161,216,192,158,194,185,208,39,45,31,42,116,46,218,228,81,175,228,202,82,43,91,28, +233,220,189,170,150,23,112,247,3,184,208,20,184,42,187,203,242,74,209,2,210,208,120,143,1,196,23,208,77,222, +150,151,84,26,29,28,37,66,65,79,22,128,203,5,54,185,52,56,150,246,229,175,42,195,135,5,45,252,229,164, +192,137,167,119,191,194,83,202,118,115,166,100,205,113,185,104,112,21,97,157,173,165,194,181,163,245,158,187,187,249, +109,182,206,247,36,124,140,116,18,120,117,242,136,117,188,181,135,20,57,74,170,228,1,140,171,125,85,213,49,253, +167,66,87,114,125,86,157,139,149,47,113,239,219,108,5,107,159,65,121,171,60,95,212,88,159,140,238,84,22,253, +110,104,87,26,157,6,168,170,155,19,223,145,252,33,182,253,134,219,190,82,212,55,245,162,63,211,212,254,243,133, +102,38,164,130,35,141,155,192,53,116,137,82,214,140,213,241,181,127,191,122,188,143,130,111,217,131,56,32,42,200, +191,60,130,218,190,8,202,208,196,188,63,51,179,62,223,39,213,117,59,237,99,117,117,111,89,38,35,193,3,98, +183,198,53,117,208,220,228,104,5,54,196,15,161,1,18,148,249,224,31,134,101,16,159,40,240,112,38,243,15,181, +37,82,250,181,190,113,145,34,96,46,86,89,213,227,219,156,206,231,88,120,152,220,204,217,173,206,41,166,157,216, +80,16,204,48,242,138,53,2,37,169,26,123,166,152,149,204,54,164,116,207,247,191,46,49,12,85,184,96,90,197, +227,56,211,108,21,174,246,82,187,251,244,90,239,119,31,126,124,251,61,73,185,253,166,113,78,87,137,30,37,176, +252,112,72,145,231,15,9,105,199,199,110,169,75,0,56,214,125,133,111,108,74,92,124,86,137,89,5,4,129,102, +240,105,113,68,223,14,42,170,128,182,132,224,220,64,238,192,210,102,246,98,201,154,86,171,245,177,60,160,86,56, +63,36,36,236,81,180,159,190,167,193,134,223,216,252,193,59,117,168,231,101,87,161,89,98,77,227,243,103,148,92, +53,186,87,156,114,232,43,163,171,249,202,197,132,242,128,153,250,160,51,53,13,22,212,171,124,127,45,218,104,112, +141,112,46,86,83,249,115,11,249,77,221,254,223,72,255,124,99,59,48,33,214,147,211,164,104,228,154,78,158,244, +50,104,41,215,179,70,104,25,75,153,86,42,223,47,69,39,125,69,1,228,16,51,211,84,107,7,15,22,179,191, +209,255,31,114,31,49,237,40,66,139,6,64,177,138,96,74,63,229,244,212,153,152,122,214,17,24,169,9,72,255, +255,246,59,77,159,20,24,77,90,42,74,219,176,211,159,127,249,112,120,113,68,40,168,231,136,167,2,75,209,210, +255,163,125,254,239,10,216,17,254,42,149,98,128,74,210,114,128,180,180,175,28,14,163,233,250,154,167,248,231,78, +38,16,215,191,4,226,186,246,153,14,85,157,238,123,110,138,107,94,0,254,111,54,46,52,5,105,198,205,77,27, +55,246,208,211,253,63,171,88,112,151,197,255,135,110,34,234,255,72,55,211,66,87,101,191,73,231,191,104,7,25, +134,117,181,195,217,235,105,180,158,195,133,58,54,7,119,86,249,43,214,182,147,3,120,14,176,143,108,101,121,122, +147,107,124,113,137,175,55,147,92,140,175,193,35,211,221,9,100,234,107,51,83,28,34,37,67,199,44,23,7,239, +41,210,232,237,204,6,142,81,167,49,250,122,170,98,13,247,188,219,164,11,118,186,88,250,25,103,235,16,92,154, +112,85,204,2,208,150,58,227,88,59,21,234,109,186,163,253,106,47,198,63,181,217,153,38,178,124,99,193,68,64, +237,252,78,168,249,61,223,160,231,173,135,116,153,171,70,77,226,9,1,247,217,166,148,12,194,199,144,77,11,67, +153,68,18,70,33,59,102,127,184,152,114,13,111,33,123,144,36,47,67,42,227,42,43,6,213,27,84,63,110,80, +210,13,21,186,241,187,157,0,126,127,171,233,76,122,211,49,25,181,75,226,19,182,135,119,5,126,142,238,132,11, +223,219,240,61,133,21,71,20,138,147,249,240,189,13,223,139,199,141,249,143,38,226,84,104,98,68,131,189,108,122, +139,190,148,218,81,92,32,65,15,224,227,50,78,27,19,207,119,50,199,57,102,87,251,185,21,211,136,26,189,215, +185,176,108,157,247,248,192,183,225,113,116,106,168,50,226,50,6,165,83,216,19,60,179,49,114,16,155,139,191,102, +131,214,225,174,40,163,210,6,229,48,82,248,162,248,48,80,113,207,53,24,62,27,141,120,204,186,167,99,69,139, +214,3,88,161,15,237,148,250,21,37,25,139,39,169,182,110,28,87,232,153,27,64,49,199,34,175,147,70,177,234, +105,8,56,60,71,31,35,231,70,29,12,140,81,2,5,15,25,208,133,62,88,100,57,40,162,193,139,190,106,232, +16,138,36,49,36,147,24,159,236,171,178,87,232,25,167,76,1,114,24,159,116,180,29,79,79,59,230,31,149,217, +16,215,124,181,129,135,12,190,191,129,124,97,62,142,78,103,45,206,237,6,202,131,202,220,148,13,46,156,172,136, +108,232,45,136,183,88,63,74,105,166,79,196,253,2,244,139,70,170,167,50,190,234,225,235,71,84,82,61,149,245, +181,178,209,27,57,106,238,210,106,43,142,138,106,49,164,190,4,185,227,5,48,161,58,200,72,42,221,241,208,0, +66,221,63,138,155,72,39,54,201,106,83,34,244,228,154,190,169,147,251,193,15,52,175,197,125,209,138,219,162,23, +155,66,11,203,189,23,221,78,154,69,96,182,176,176,50,174,218,228,59,126,17,15,246,55,177,119,144,238,70,216, +17,103,157,91,153,145,40,242,104,41,244,81,50,145,6,168,212,31,70,229,78,251,144,156,7,8,5,184,162,159, +142,28,65,6,33,42,253,224,81,249,190,143,156,34,45,58,66,7,73,68,242,121,32,102,227,6,167,200,243,226, +243,190,97,41,204,47,234,138,174,27,102,100,62,111,37,152,159,178,127,95,77,179,101,241,239,115,250,205,151,121, +182,189,251,164,174,63,145,241,59,153,189,231,66,63,147,171,54,101,83,87,159,112,15,191,70,176,221,168,174,54, +159,110,218,94,25,146,87,92,54,176,152,204,94,204,150,103,7,179,255,126,110,255,178,57,126,142,98,163,240,165, +107,134,247,198,179,9,201,72,96,228,204,174,20,155,156,205,131,105,247,87,206,78,215,137,93,131,238,117,159,132, +174,11,127,226,145,83,5,29,178,130,162,216,122,95,220,222,77,138,40,242,7,228,229,132,28,26,73,152,6,91, +222,36,20,84,59,60,149,13,212,34,211,154,142,144,177,153,247,237,160,153,144,115,180,18,142,83,224,151,101,57, +244,149,82,24,161,41,174,206,151,237,178,147,39,228,172,9,198,10,245,153,161,31,50,99,38,16,0,69,150,211, +71,226,13,163,131,55,140,62,87,20,39,203,38,211,240,158,246,196,69,251,215,169,74,135,155,245,64,67,93,224, +37,47,102,80,10,136,115,45,185,49,16,220,10,96,187,116,129,83,227,50,206,38,142,94,38,98,226,8,11,159, +97,86,196,128,141,74,253,22,53,38,181,242,70,251,18,69,31,159,98,165,101,154,166,142,135,18,198,29,106,37, +51,79,4,182,69,167,26,123,227,157,165,200,154,197,82,44,138,248,142,183,96,103,61,254,198,137,233,132,115,48, +178,112,167,124,53,208,175,171,104,52,162,4,121,192,112,134,42,54,55,175,124,134,127,41,127,75,210,30,144,95, +235,201,79,55,144,252,160,99,186,81,243,219,178,107,179,47,190,111,89,11,205,239,0,188,224,140,47,32,254,174, +215,181,90,21,47,38,95,76,219,233,23,147,47,96,132,49,153,4,59,245,160,120,64,173,246,83,24,247,145,167, +26,4,154,13,72,18,33,194,36,1,168,131,172,26,66,57,45,68,235,245,13,200,117,235,115,220,186,212,22,11, +112,66,236,199,195,51,47,146,100,158,61,37,72,174,94,168,145,252,5,18,242,104,203,163,229,129,232,162,123,11, +125,220,17,163,163,33,130,43,89,11,38,202,61,233,228,242,176,44,72,2,110,84,202,212,85,12,37,41,101,105, +103,207,30,238,3,39,233,191,205,202,220,198,159,169,151,165,171,240,220,37,140,181,4,231,71,24,181,158,50,180, +243,170,172,54,10,2,94,155,52,33,208,102,32,61,123,184,174,91,194,28,241,66,69,191,147,181,208,242,94,103, +70,100,61,174,112,28,97,219,230,87,228,228,9,241,12,165,187,235,93,78,27,135,67,206,221,101,173,152,5,7, +35,58,207,185,180,14,247,5,210,77,239,221,208,145,219,80,124,62,84,24,214,185,48,233,225,161,245,77,230,179, +79,230,22,141,99,227,232,55,12,197,236,16,102,84,206,9,201,162,157,245,39,135,139,92,203,126,218,146,73,148, +80,112,58,213,75,93,180,82,251,246,55,26,155,110,93,180,222,157,225,219,196,53,33,234,5,17,160,186,246,156, +56,230,99,149,139,170,249,76,130,19,169,146,229,124,19,80,29,212,168,161,169,174,137,199,52,90,200,123,108,22, +121,59,157,90,104,127,210,2,218,207,14,207,225,240,168,159,205,194,245,160,147,3,226,183,142,125,33,75,239,173, +8,188,89,112,113,247,147,38,253,32,132,224,112,233,165,183,244,183,223,212,188,104,89,185,42,125,220,180,22,116, +30,27,187,110,130,102,184,158,95,56,87,157,20,12,223,52,247,122,163,90,213,245,86,194,110,130,228,237,41,237, +203,61,148,180,62,43,251,131,110,215,245,213,77,80,179,164,74,151,67,231,12,250,33,212,128,227,201,110,151,139, +159,244,156,48,242,134,104,57,83,242,36,200,123,39,23,186,125,13,151,203,211,43,190,66,98,93,186,45,158,107, +135,250,67,213,195,47,166,142,231,164,62,117,120,169,195,34,254,20,62,66,27,75,106,163,159,226,52,168,195,89, +14,136,173,131,58,214,81,239,6,155,80,63,220,82,99,53,118,207,31,251,252,82,177,118,108,186,222,253,22,6, +165,223,131,93,14,57,203,242,98,244,30,215,220,246,178,214,91,145,29,96,73,73,48,171,169,213,78,4,143,213, +139,90,231,67,161,17,169,83,20,157,110,112,96,135,78,149,219,133,68,251,57,215,123,52,215,87,171,12,3,21, +144,105,55,43,190,61,86,23,214,70,110,221,233,235,44,157,252,183,60,249,137,162,39,112,202,84,227,24,194,142, +180,97,46,151,63,92,154,12,7,39,88,142,233,108,18,116,97,19,56,114,9,14,224,30,206,78,217,9,117,144, +112,127,40,175,206,139,137,37,151,137,184,8,107,37,40,227,130,157,230,246,69,45,46,58,173,205,169,13,41,17, +245,108,173,184,184,34,49,127,217,209,223,162,23,94,165,9,94,134,26,123,214,9,26,228,250,220,237,31,97,183, +1,166,136,38,239,238,51,45,28,186,3,137,118,162,12,235,162,195,125,7,173,163,255,158,95,128,155,242,65,244, +28,177,80,96,82,19,6,185,223,104,4,5,16,116,215,240,195,159,181,232,242,156,19,38,78,223,94,43,171,15, +212,221,32,247,47,106,221,96,6,61,159,120,238,208,131,227,108,105,139,125,239,29,203,17,249,61,85,82,26,143, +166,83,174,77,217,15,234,213,20,204,163,155,97,20,172,111,233,234,207,61,224,105,34,167,88,69,23,27,79,132, +149,68,171,168,117,229,21,54,135,24,144,125,22,153,70,224,83,86,248,211,140,208,10,60,166,83,177,123,194,89, +209,67,66,35,80,159,110,49,144,32,17,231,72,173,48,68,55,55,151,239,25,172,232,219,128,80,236,228,160,168, +85,192,96,95,124,208,84,60,46,25,7,61,82,65,19,144,43,213,92,105,32,40,15,159,123,64,166,243,231,169, +170,127,68,85,90,116,99,170,210,96,190,3,25,117,145,134,180,24,81,17,0,160,162,155,198,197,125,142,134,210, +154,244,60,237,238,156,38,13,198,113,233,9,2,67,15,146,92,142,212,238,163,5,122,87,88,206,166,248,93,146, +68,97,255,243,116,232,136,48,109,248,184,133,145,6,31,37,241,17,40,166,31,99,143,17,43,75,145,160,124,76, +98,76,3,70,62,216,78,120,20,36,58,249,3,47,176,27,40,238,33,165,137,193,84,137,223,82,140,11,33,194, +33,189,232,9,236,190,119,178,246,60,79,136,21,105,217,34,41,77,212,253,105,140,250,6,158,185,150,138,207,104, +138,162,190,247,9,17,211,230,203,150,99,2,7,181,106,188,231,195,122,89,99,131,230,67,224,199,214,67,113,80, +36,246,6,194,197,114,85,110,97,73,3,30,24,171,241,227,129,50,121,244,174,60,79,140,188,8,106,51,36,236, +214,27,157,88,142,255,33,175,151,104,80,30,70,205,156,135,237,21,106,197,4,46,219,212,128,224,102,196,65,187, +213,160,141,171,65,159,172,6,122,56,253,59,12,4,118,203,82,182,168,209,155,192,24,230,244,187,121,138,249,204, +88,230,255,10,254,57,113,235,24,174,189,236,145,192,159,5,56,126,235,227,145,61,140,78,158,19,210,17,97,11, +233,84,86,66,24,76,50,94,170,23,155,66,122,42,184,10,82,190,81,175,250,216,43,157,244,170,243,107,92,185, +147,10,44,74,105,231,77,158,67,206,118,203,235,210,155,174,163,249,57,249,69,225,33,24,84,178,82,134,198,132, +143,134,211,100,183,47,243,249,63,117,221,102,147,217,201,36,159,226,239,52,30,154,75,203,53,228,194,72,204,51, +120,121,163,134,207,237,228,2,252,163,21,70,176,21,253,143,109,214,251,29,138,250,10,248,192,242,125,251,63,223, +203,100,236,66,95,137,233,240,190,128,137,205,226,78,75,115,230,64,47,141,63,166,37,206,145,13,246,189,196,170, +72,84,50,52,20,114,71,163,186,108,35,79,224,206,184,201,23,176,17,73,100,33,27,200,66,194,118,183,70,47, +43,238,221,134,146,90,94,156,6,119,141,3,42,81,16,156,193,98,181,14,178,156,216,253,95,194,4,137,84,7, +227,211,12,176,112,124,184,142,243,87,194,122,100,105,138,199,110,174,151,63,24,246,25,235,150,233,56,9,47,7, +226,226,216,9,141,78,152,200,94,95,55,252,74,47,208,9,7,188,24,101,250,8,86,47,212,212,110,30,88,46, +161,168,48,135,121,166,126,194,147,178,63,109,238,9,101,231,209,221,161,217,196,0,83,226,150,18,171,52,21,155, +34,122,183,170,17,51,132,210,71,123,190,153,39,12,160,208,104,146,25,52,137,133,218,103,96,246,192,241,225,162, +78,96,135,45,213,186,139,64,141,236,123,188,213,71,89,200,124,9,249,60,113,55,97,113,3,73,81,91,181,237, +88,22,178,104,209,160,30,159,48,95,238,29,22,196,100,14,102,143,96,222,152,154,200,27,246,69,227,157,35,196, +238,245,253,120,80,22,106,145,43,153,14,214,83,226,181,139,230,177,165,77,228,125,51,34,191,23,53,225,20,252, +55,86,201,135,93,20,242,164,139,219,183,206,231,51,239,34,125,226,171,161,25,137,89,22,143,232,5,250,6,176, +246,43,176,33,53,203,124,8,85,125,88,181,121,246,247,249,146,6,16,189,192,119,90,195,87,238,66,207,200,205, +174,51,128,86,99,71,207,45,59,81,118,132,214,70,169,74,34,197,208,125,116,72,86,91,6,50,156,102,8,15, +1,40,239,169,115,126,138,15,87,33,196,60,58,65,61,246,4,28,1,237,248,85,14,149,251,69,163,39,179,193, +126,96,54,120,49,201,115,119,214,138,132,54,60,91,217,82,127,109,188,103,223,55,239,79,191,127,251,211,187,79, +159,14,213,140,124,97,149,42,184,183,15,146,6,146,43,97,35,156,247,31,107,122,115,12,159,226,123,29,30,126, +184,155,44,73,186,90,208,239,34,158,219,154,199,83,170,230,252,112,228,139,107,39,162,147,202,3,20,222,45,173, +105,140,225,249,19,95,157,208,118,246,84,178,252,50,43,167,252,62,112,99,191,22,149,180,118,62,85,78,210,168, +138,34,108,112,131,224,102,225,215,201,118,191,18,43,250,187,241,140,207,182,35,243,48,77,170,21,82,185,104,114, +127,186,222,207,58,250,233,73,107,44,238,9,114,207,16,250,233,161,76,21,45,182,3,155,116,186,26,39,5,36, +36,77,232,227,227,227,41,147,26,240,37,175,71,202,82,81,210,131,104,103,191,33,221,233,230,184,157,29,210,113, +121,3,202,111,100,229,147,109,166,124,134,223,163,165,98,175,202,153,124,111,21,109,43,36,12,60,16,191,54,4, +81,244,77,228,75,9,207,169,28,79,216,187,94,34,226,75,78,169,109,188,11,149,209,137,114,63,240,2,171,41, +68,117,149,199,214,181,87,39,255,244,101,116,94,88,230,130,107,234,247,187,125,46,198,85,169,93,56,149,248,253, +232,112,1,2,137,248,248,94,59,145,77,138,23,135,149,71,56,169,208,23,190,112,80,113,167,59,89,82,195,27, +159,178,98,180,148,227,121,188,145,37,76,125,215,244,211,210,42,4,205,179,204,54,179,142,128,57,57,166,46,207, +254,65,122,115,184,246,248,199,185,220,204,116,0,180,0,172,103,154,122,82,225,29,64,155,177,161,108,179,77,200, +120,228,51,78,117,0,216,140,83,151,49,65,193,43,70,65,138,0,5,4,152,60,145,144,16,46,236,189,192,150, +167,117,3,119,71,58,128,24,61,79,145,140,245,96,15,217,74,7,98,1,86,18,130,233,24,51,123,13,99,141, +112,231,17,83,209,212,157,53,88,156,123,28,83,54,108,29,153,85,232,98,195,232,217,20,7,59,141,168,114,89, +45,127,52,25,37,35,57,100,78,233,108,224,28,147,204,65,167,8,67,153,106,131,5,130,59,158,1,61,191,195, +138,47,45,76,234,80,103,236,152,250,89,31,106,201,173,204,230,209,83,16,162,195,122,97,88,158,64,81,159,19, +59,117,82,11,205,84,67,16,200,197,56,155,154,30,34,35,216,97,255,32,68,135,79,54,176,144,212,74,251,37, +130,129,75,231,180,8,0,223,135,4,247,193,30,198,42,109,192,84,187,98,142,66,49,71,79,23,115,116,31,18, +60,46,38,125,8,97,192,81,37,147,5,251,20,46,236,148,237,183,229,182,135,74,95,214,126,95,168,104,95,168, +120,113,205,217,130,0,46,115,191,111,41,102,171,173,249,193,143,122,229,252,197,183,218,232,86,77,114,144,170,127, +210,131,171,169,32,150,59,243,8,197,248,2,221,159,211,128,56,245,71,35,105,241,175,112,34,57,11,163,170,167, +135,162,155,101,237,146,175,229,190,236,216,26,66,181,61,155,56,148,22,237,205,220,175,208,243,59,11,187,79,97, +247,12,59,66,58,44,205,156,6,200,243,225,123,208,254,142,58,91,110,191,98,251,154,247,48,145,37,196,48,1, +165,54,13,223,232,103,158,137,26,184,247,15,234,154,149,174,110,96,214,58,136,77,230,120,155,178,4,246,190,244, +59,66,111,114,189,212,36,198,181,169,229,236,233,166,164,90,127,209,218,156,91,175,141,102,14,23,35,3,118,97, +93,15,180,8,161,212,199,60,121,214,70,253,94,13,253,158,136,239,50,224,41,26,39,24,166,100,173,125,135,38, +105,44,230,62,252,60,213,194,251,163,190,173,177,191,91,188,88,171,231,235,237,141,81,43,54,145,160,242,71,55, +203,222,55,169,122,135,114,19,82,144,205,159,184,255,2,25,123,228,62,94,55,146,213,172,120,35,127,160,95,165, +143,70,13,85,19,255,246,200,224,155,76,246,149,84,203,201,108,50,85,164,210,11,219,133,189,68,255,159,112,123, +62,154,162,188,110,112,129,0,215,230,101,234,60,255,204,76,145,95,79,169,231,164,222,14,119,73,157,150,182,181, +90,218,214,105,105,91,175,165,109,49,83,9,108,91,27,176,245,83,162,22,202,106,168,105,140,21,180,67,95,191, +167,230,125,24,233,4,103,111,155,177,212,191,197,51,184,148,163,230,87,95,248,226,150,98,9,63,228,249,78,233, +3,91,162,66,137,7,189,94,247,202,252,173,208,194,126,253,29,194,5,107,245,109,95,38,21,213,2,146,16,43, +193,163,210,152,107,207,243,70,106,220,103,225,137,30,246,5,150,188,120,3,154,175,27,220,120,100,171,117,218,127, +36,173,81,12,248,219,108,99,215,177,42,128,254,78,32,172,92,84,161,199,32,113,76,13,241,72,149,184,212,119, +69,153,236,133,191,233,212,234,165,98,131,113,234,199,51,166,251,69,187,147,134,122,78,132,164,176,31,246,115,42, +239,180,254,141,154,135,25,228,30,19,38,216,68,116,146,40,164,23,147,109,185,66,235,241,8,190,133,184,84,68, +82,60,166,20,49,104,220,102,39,121,4,168,252,181,236,236,120,103,196,234,150,252,137,167,242,59,116,206,194,240, +101,31,131,229,178,138,27,97,9,162,216,238,162,251,62,108,39,55,51,202,198,105,166,165,253,21,91,128,108,114, +130,217,15,110,75,122,249,160,153,173,243,47,111,246,149,179,128,111,193,104,166,241,213,108,149,127,185,165,120,155, +159,18,36,184,253,48,84,53,10,123,215,91,122,5,49,29,59,195,119,156,24,188,112,33,225,158,206,141,172,221, +144,178,89,147,80,33,108,213,237,41,181,116,82,63,71,45,162,196,128,193,16,16,35,80,62,49,2,149,139,8, +131,181,48,30,95,179,202,253,54,14,111,42,160,141,162,220,71,227,62,68,43,105,101,44,131,21,159,208,98,18, +59,128,154,250,144,192,118,33,166,176,225,73,16,146,184,33,53,126,72,149,240,165,22,116,184,237,106,17,202,40, +122,132,189,168,243,55,94,34,147,65,130,103,85,56,97,77,132,146,79,28,131,120,109,20,218,209,232,53,77,203, +186,165,246,118,104,111,31,59,84,143,58,196,45,41,67,34,215,169,122,220,41,78,214,200,64,17,9,205,86,174, +131,160,124,102,31,70,147,138,101,96,173,225,89,21,228,77,207,77,165,213,120,214,45,170,153,92,57,202,95,187, +17,220,0,228,40,127,237,62,252,74,91,165,174,60,170,153,182,89,114,177,73,225,237,50,121,222,165,34,162,47, +136,97,15,83,168,146,191,213,145,115,172,68,39,154,212,232,115,51,136,222,128,105,138,152,163,248,138,142,181,16, +161,34,29,21,126,68,160,199,152,74,52,67,253,120,31,162,35,63,76,14,146,38,214,174,109,251,80,228,12,35, +184,100,130,47,124,26,217,127,217,10,7,151,250,75,175,221,5,99,101,87,199,160,188,178,86,57,212,82,108,38, +46,228,10,161,46,56,128,195,31,165,26,166,144,255,128,199,88,135,251,237,221,63,196,32,61,199,218,79,68,230, +226,217,203,84,196,62,96,54,184,146,40,212,35,196,57,41,160,151,217,179,57,101,43,186,208,103,209,249,30,163, +42,115,55,31,220,153,106,249,150,53,255,205,97,199,135,251,137,22,45,191,52,143,223,108,164,167,145,76,119,31, +100,41,15,87,4,221,150,61,174,201,38,175,55,194,122,7,215,28,23,142,153,33,130,125,131,75,148,111,157,10, +61,155,24,190,214,107,111,227,230,194,37,235,248,126,245,231,82,238,42,152,70,61,196,155,237,233,27,145,229,72, +165,239,24,36,209,66,194,167,172,85,21,108,8,97,105,152,57,131,195,237,29,236,1,61,39,178,156,194,74,216, +137,88,19,231,154,38,46,41,97,203,173,97,254,190,239,95,117,161,189,131,32,247,128,184,55,93,210,205,226,171, +230,15,21,208,38,134,246,75,5,87,242,245,28,54,233,136,241,182,243,136,56,12,112,117,114,176,196,47,133,147, +234,190,127,98,9,68,133,246,104,116,111,63,238,119,66,3,202,150,239,128,242,7,65,59,201,253,237,177,75,151, +248,238,133,198,119,131,111,45,12,190,43,124,119,162,196,247,6,223,56,45,171,128,72,2,224,218,164,242,188,103, +211,179,212,237,199,114,187,136,71,130,38,185,229,71,236,101,96,201,166,63,156,254,244,206,89,138,213,235,251,44, +24,29,53,61,54,193,44,84,99,229,160,92,50,49,224,205,220,10,215,190,129,137,166,177,35,79,57,122,228,32, +68,12,110,164,190,25,29,138,209,24,4,231,107,151,57,247,251,77,156,2,200,17,71,47,139,121,235,105,61,53, +51,5,197,164,221,56,16,103,36,1,130,53,120,146,90,165,143,166,170,2,65,199,143,143,94,88,221,137,187,247, +205,77,143,198,199,204,179,118,39,144,138,58,249,214,116,143,226,118,59,241,93,210,226,164,185,117,108,110,108,40, +160,105,43,211,68,174,246,244,132,65,221,76,107,31,198,81,221,113,108,251,49,126,235,229,111,22,195,197,119,77, +106,212,255,157,30,72,26,22,204,65,77,26,131,55,75,249,179,51,205,132,221,221,135,37,218,217,77,182,242,76, +61,62,237,76,86,117,167,220,235,157,98,16,255,190,171,117,87,155,251,65,146,115,74,211,199,52,105,28,53,127, +82,95,111,117,103,202,214,80,97,53,31,139,97,91,255,218,167,145,109,210,147,127,186,158,152,212,18,209,43,254, +31,231,21,195,238,60,219,138,224,136,57,21,80,252,85,143,94,195,157,216,123,146,203,135,75,101,110,149,106,139, +215,74,84,250,26,135,205,226,167,82,88,171,101,88,110,174,205,174,8,137,126,50,33,81,102,207,81,68,198,73, +98,67,128,100,85,169,250,236,129,229,222,69,45,84,187,42,144,251,166,5,239,214,104,189,45,90,103,20,218,239, +124,211,124,234,151,202,166,127,233,83,226,224,61,171,73,94,246,18,228,127,16,50,38,216,28,105,124,183,14,55, +92,11,10,237,185,72,189,147,74,132,254,116,73,219,203,157,36,44,177,34,199,36,98,79,215,164,138,115,111,108, +115,214,176,194,92,137,27,176,103,107,167,185,152,202,70,108,240,103,69,205,187,33,214,109,117,124,67,170,190,172, +204,72,222,248,178,57,39,41,103,206,34,55,146,194,172,242,106,54,19,27,178,25,172,94,34,27,253,241,251,211, +230,184,162,190,162,160,92,60,89,181,235,186,51,6,78,109,55,131,230,48,181,70,131,174,228,247,17,210,133,46, +139,7,63,192,165,240,88,106,18,44,85,22,75,161,113,27,46,100,109,27,183,114,141,187,217,201,48,28,98,11, +77,41,102,235,53,14,188,151,214,159,198,133,184,19,31,221,2,126,203,182,98,77,214,139,143,226,2,154,67,124, +65,16,66,82,115,142,42,51,77,17,246,209,173,134,190,145,76,220,115,212,245,167,79,183,89,46,94,113,96,143, +66,191,102,81,48,125,42,55,226,181,220,44,78,143,229,154,176,126,154,223,73,115,118,10,185,214,222,157,211,146, +100,23,178,202,238,120,108,46,168,198,143,4,185,150,77,118,193,35,69,141,245,111,44,223,103,88,82,46,101,137, +40,110,202,242,180,120,77,41,246,124,138,87,156,98,107,117,189,145,240,47,25,63,167,14,63,142,254,187,128,167, +60,119,24,201,169,165,167,226,163,188,136,230,80,177,236,103,74,93,127,166,212,109,226,173,111,244,210,29,156,27, +64,205,163,174,216,189,200,72,48,227,69,24,67,249,12,17,23,188,224,99,77,99,129,29,4,186,58,232,157,20, +183,207,25,78,62,86,81,254,117,172,67,132,253,175,52,179,67,204,31,69,204,113,155,187,199,114,13,235,176,122, +171,195,90,228,212,132,69,140,121,34,162,127,41,169,92,251,222,94,79,45,58,233,145,76,191,52,62,161,142,118, +185,26,105,31,6,164,159,206,158,39,85,97,110,78,104,79,194,29,21,81,74,86,173,53,22,111,210,76,15,233, +54,157,84,68,96,209,220,141,146,52,47,123,154,121,220,140,79,159,232,215,232,237,178,116,193,172,197,84,208,22, +107,174,73,230,165,109,84,214,204,104,149,235,221,186,7,23,129,178,147,46,127,83,48,169,20,112,238,32,74,79, +194,70,54,57,248,174,202,163,190,11,132,243,116,21,93,90,188,78,8,101,36,234,10,131,13,114,209,91,164,233, +131,44,59,117,55,133,5,167,247,11,206,121,176,233,220,219,35,237,42,42,242,104,239,184,122,44,180,176,44,176, +15,11,35,115,203,70,12,185,95,2,123,220,239,24,102,177,237,213,59,218,126,65,122,222,72,163,60,238,150,229, +180,167,248,202,214,182,166,110,191,165,164,180,242,98,146,90,123,248,158,40,45,45,251,29,106,239,32,123,203,69, +250,66,55,162,199,92,247,94,75,167,185,118,30,45,1,41,168,150,166,248,123,160,151,84,115,242,152,136,130,149, +112,234,148,130,223,84,236,179,128,84,90,77,47,210,23,173,169,215,46,166,120,240,248,38,180,97,215,105,2,214, +69,229,169,18,199,180,53,59,215,183,90,102,177,162,245,46,154,66,101,91,113,45,46,197,133,111,211,157,44,151, +179,195,130,103,223,150,72,229,218,238,96,219,41,109,90,234,108,251,178,241,147,135,68,98,119,76,229,4,190,142, +224,235,41,129,41,25,101,37,40,46,156,12,168,140,98,120,204,40,206,142,218,165,91,151,46,118,224,252,47,196, +26,217,112,214,9,202,243,173,83,248,175,169,101,235,98,107,123,225,246,12,117,182,166,170,197,37,55,132,58,70, +211,109,117,44,183,115,170,98,177,138,75,212,5,37,92,81,194,197,37,48,155,14,88,246,14,231,17,182,249,46, +38,110,209,155,136,237,65,113,45,182,135,197,5,125,193,76,221,226,61,91,97,242,53,20,17,97,43,10,15,7, +103,135,231,107,255,220,100,151,2,39,249,155,108,45,40,155,216,206,209,91,2,137,107,234,228,70,94,238,214,199, +4,127,34,65,16,174,36,10,132,196,39,221,195,200,163,22,237,245,35,136,112,14,180,202,237,169,99,6,134,0, +23,255,186,236,55,33,14,129,4,254,19,139,163,67,108,4,185,52,63,232,186,77,10,79,33,34,185,18,143,216, +24,114,49,161,213,49,180,75,222,216,112,171,76,212,188,140,78,111,53,246,254,71,39,186,157,123,236,236,107,158, +76,255,236,95,252,250,167,249,127,157,31,166,239,156,221,222,222,206,189,225,191,238,174,146,135,205,142,98,54,80, +69,87,95,222,192,90,236,15,62,112,198,14,236,126,110,6,14,236,130,207,81,69,215,6,123,19,189,132,185,233, +30,142,175,14,216,221,180,45,251,14,59,116,0,42,212,16,145,121,231,96,187,139,86,27,244,116,236,87,43,189, +70,128,29,184,35,192,234,198,222,110,93,232,244,90,67,153,61,112,213,133,17,238,150,99,161,230,238,75,144,33, +239,169,81,132,166,206,11,243,17,44,162,119,222,153,114,139,71,151,19,125,239,168,99,235,78,245,155,113,71,105, +243,26,117,232,64,12,49,113,161,173,225,166,149,208,176,189,147,43,226,102,187,98,7,152,226,105,212,197,66,73, +25,135,96,108,3,90,228,179,27,9,180,205,91,77,140,159,59,6,18,227,146,162,62,32,5,119,110,220,235,222, +180,134,187,194,121,61,175,141,186,238,221,66,58,188,183,161,165,139,117,27,190,142,247,150,172,198,198,174,136,221, +9,85,58,155,117,121,35,217,122,184,153,95,148,236,181,109,153,209,167,209,166,108,78,218,48,80,180,72,198,128, +244,9,114,202,101,200,197,92,102,176,125,67,144,150,161,44,42,48,86,122,46,180,125,114,58,95,64,107,109,253, +195,5,20,58,154,233,69,139,195,231,182,211,87,132,175,126,146,231,194,151,0,102,99,62,34,190,113,62,112,252, +56,117,78,114,209,122,130,177,215,220,166,161,251,187,124,76,183,70,40,247,176,246,152,194,243,29,12,208,94,181, +245,117,159,186,71,77,135,200,201,103,20,139,103,204,72,60,243,224,138,34,185,98,160,100,34,51,30,149,130,184, +215,120,5,233,193,55,29,96,223,125,250,222,237,236,49,61,51,78,142,99,179,216,201,229,40,58,109,99,50,197, +212,185,127,38,124,7,251,44,62,80,89,253,96,192,232,227,236,220,180,192,4,171,124,7,67,219,100,141,123,58, +67,184,236,179,227,153,247,28,174,60,142,208,138,76,205,147,137,231,166,108,50,33,132,138,100,166,92,45,157,90, +221,84,202,79,134,32,183,111,5,213,224,19,231,16,229,142,38,93,190,115,85,101,222,25,105,58,206,241,225,251, +207,181,217,37,114,26,214,100,10,170,225,20,220,129,191,253,61,4,212,107,95,206,243,211,55,116,219,251,139,11, +51,201,157,36,236,188,237,249,129,47,8,84,42,213,208,145,209,101,194,217,136,107,78,87,227,136,221,116,170,236, +172,232,57,29,229,180,201,206,248,217,56,135,130,239,173,17,225,207,141,107,230,170,151,147,196,117,251,68,124,219, +200,135,75,173,27,85,182,99,95,50,120,234,203,20,245,78,176,55,137,177,122,67,245,25,93,56,94,245,185,64, +103,249,174,47,29,200,136,200,0,12,19,139,206,69,189,141,92,246,180,222,99,244,213,192,101,109,97,118,162,101, +105,232,88,14,55,133,228,5,15,241,239,22,118,39,252,183,225,78,248,120,195,130,65,87,47,47,85,118,6,155, +10,230,243,13,27,54,6,199,244,157,139,5,80,80,10,138,0,246,252,26,26,55,149,117,43,41,21,45,11,223, +54,40,236,126,75,227,239,172,42,58,63,84,170,100,175,156,31,21,165,176,223,228,227,246,163,154,219,135,178,93, +34,158,40,169,174,39,142,42,117,16,195,85,222,195,7,155,159,5,97,22,33,232,214,235,52,187,9,243,204,231, +0,115,71,135,16,51,199,135,131,89,117,190,84,46,8,17,76,240,143,142,190,203,206,39,212,178,143,137,174,235, +94,245,158,49,0,74,82,85,73,138,168,157,223,19,131,79,155,52,54,127,24,208,242,222,97,52,253,77,155,119, +22,27,7,251,195,118,150,96,76,116,114,136,143,153,94,164,8,109,71,232,74,81,20,86,154,78,36,216,202,69, +130,209,169,212,159,195,158,142,68,164,68,31,136,40,65,96,74,71,189,80,176,235,243,115,250,33,69,133,223,165, +120,187,77,248,7,145,38,194,254,56,198,88,190,227,44,233,202,52,192,79,59,194,143,232,101,68,39,97,51,54, +53,160,146,123,88,6,194,178,246,231,139,209,216,73,168,242,176,227,119,100,23,112,220,230,98,111,52,178,233,0, +246,231,178,28,181,252,32,245,185,167,142,15,30,231,208,62,65,35,213,151,237,203,35,209,224,133,193,230,228,112, +121,52,107,138,134,130,233,244,138,42,218,67,145,168,126,27,28,133,198,37,251,169,155,105,190,3,184,187,45,107, +51,118,123,30,200,28,12,101,10,192,26,156,143,174,206,1,158,57,191,192,15,198,157,54,59,190,242,217,169,127, +22,45,223,211,245,93,79,135,106,57,161,84,208,143,252,15,246,254,181,187,109,35,73,0,134,191,191,191,194,230, +147,103,23,109,54,101,201,153,204,238,130,110,243,200,118,156,120,198,113,60,182,115,27,29,29,13,4,54,77,68, +32,192,1,64,73,12,23,255,253,173,234,174,174,110,128,160,228,76,102,191,61,39,177,136,190,223,171,171,170,235, +162,127,29,201,98,175,229,179,243,187,152,95,120,67,0,58,16,9,39,74,240,51,202,67,221,142,36,90,169,24, +5,100,15,132,172,61,24,248,32,121,185,209,185,252,11,230,54,32,155,115,191,160,80,143,156,3,57,170,111,13, +182,50,98,115,216,35,185,51,32,137,30,22,165,219,94,241,137,254,82,218,21,137,71,161,127,252,145,92,20,46, +51,238,54,247,141,155,205,125,55,37,127,1,8,165,111,214,7,248,103,222,113,145,242,173,183,205,45,160,123,108, +52,184,211,71,175,112,219,55,88,43,89,41,46,48,139,91,22,239,44,142,134,218,100,20,243,130,110,84,23,179, +40,70,173,216,155,15,171,158,158,163,134,15,81,242,52,179,107,54,36,28,255,37,167,43,204,103,50,193,110,174, +159,243,182,61,48,160,186,51,34,142,15,59,100,238,236,204,101,182,135,49,222,113,206,120,199,11,5,126,211,218, +86,86,26,181,167,135,115,96,58,154,127,14,82,235,152,71,105,22,177,131,35,180,242,58,171,51,156,79,26,29, +225,11,35,25,86,217,202,101,54,215,195,117,54,229,103,214,200,91,204,222,161,102,115,225,34,254,47,54,208,10, +66,4,126,41,251,136,128,216,5,72,144,106,164,13,241,236,247,136,98,167,79,170,35,45,90,31,32,108,19,117, +16,187,184,157,86,253,250,208,156,192,190,230,233,219,100,165,17,203,102,58,176,80,207,188,202,16,98,39,182,254, +186,87,63,26,97,235,217,183,129,83,33,12,77,86,195,159,41,24,227,173,143,124,235,192,252,241,65,132,39,80, +181,111,180,130,70,141,158,83,97,80,86,163,79,45,160,136,37,76,80,119,194,130,45,187,80,250,123,203,240,139, +186,200,157,227,16,202,90,125,145,27,106,230,16,179,149,38,222,24,231,56,229,181,71,18,207,67,211,163,47,106, +88,24,61,7,1,107,168,140,89,186,95,240,94,193,220,71,205,82,23,145,33,219,57,139,42,90,105,98,12,195, +184,215,74,191,211,253,69,146,6,173,134,30,134,13,33,216,15,195,10,171,174,84,87,131,70,38,1,105,67,247, +164,229,186,87,30,173,207,45,70,31,234,154,159,229,102,137,211,174,223,156,47,70,94,7,1,83,49,142,198,55, +18,59,79,190,153,254,15,45,139,240,42,27,78,36,83,159,165,150,7,187,80,37,126,18,118,175,10,67,179,164, +194,190,16,194,31,152,243,197,145,67,229,128,211,121,68,152,219,92,46,101,226,43,182,10,114,11,79,152,224,106, +207,97,3,205,61,6,181,67,5,6,181,244,101,74,12,47,240,108,1,110,14,53,54,50,69,54,36,141,104,193, +252,198,58,64,23,25,89,12,87,202,153,122,0,108,161,231,84,201,75,106,180,157,101,30,222,10,134,167,223,37, +207,128,2,50,250,78,1,124,144,136,213,28,135,102,52,243,129,135,170,174,169,170,207,122,172,202,206,240,234,62, +159,162,30,45,207,57,191,82,149,71,22,31,225,105,33,4,3,181,176,35,29,26,104,204,247,253,112,144,41,39, +230,220,147,229,143,29,71,176,156,41,191,131,249,67,23,249,92,90,237,153,166,215,114,71,57,241,250,252,34,132, +223,109,207,165,254,166,111,137,4,238,78,87,181,49,62,133,144,163,210,215,232,0,65,66,99,136,183,177,108,41, +208,146,248,252,166,17,131,235,68,78,59,111,240,197,172,180,207,100,240,85,119,158,202,154,52,120,104,214,70,145, +179,207,12,40,148,233,34,234,16,225,87,176,125,140,237,175,26,185,247,100,253,171,192,111,103,255,139,212,228,172, +13,176,194,6,130,150,117,202,82,252,150,232,116,144,205,24,47,51,138,125,80,189,196,245,33,137,112,248,178,85, +147,50,211,2,21,221,97,246,11,5,97,105,58,211,80,71,180,235,68,97,155,175,229,60,171,17,125,153,199,153, +25,100,200,195,30,126,82,69,22,207,135,178,106,244,252,165,125,48,248,78,195,143,19,71,170,201,232,145,125,254, +244,91,247,105,9,111,135,181,160,237,137,91,151,20,151,7,180,61,63,241,3,85,96,48,20,135,134,231,195,212, +186,34,165,145,58,51,2,30,161,106,146,161,49,232,109,208,121,182,59,150,137,170,93,87,170,167,137,81,85,194, +133,205,149,209,28,50,106,173,118,169,11,60,33,12,66,201,26,93,10,109,27,211,60,53,26,16,253,2,64,30, +190,198,2,145,110,159,235,191,107,172,121,0,248,77,5,38,53,99,149,242,201,11,215,54,75,67,141,203,240,212, +163,74,169,1,110,228,109,156,122,27,120,240,115,222,251,144,248,234,187,192,41,173,235,62,100,74,40,84,20,148, +219,216,88,208,25,152,220,245,29,199,234,200,168,210,235,57,23,11,197,181,129,95,109,211,89,146,199,87,90,164, +93,134,138,17,229,204,230,237,209,23,187,134,126,181,45,12,53,26,14,71,251,15,95,186,134,210,161,21,176,198, +216,237,210,18,190,95,90,37,23,107,24,204,5,208,134,4,94,61,63,212,186,50,178,223,117,36,66,59,96,5, +28,116,210,40,125,251,245,55,167,31,95,255,248,245,197,235,183,175,94,191,125,253,241,23,83,117,61,211,46,195, +187,239,63,188,238,100,8,85,108,211,61,21,202,179,198,184,22,34,37,99,198,57,206,52,70,227,15,70,251,26, +86,188,147,67,213,223,218,188,4,226,8,190,67,217,75,216,195,63,90,12,213,158,164,66,56,176,22,137,16,222, +147,119,152,115,18,40,40,81,183,227,33,126,60,229,87,101,202,210,134,74,230,94,35,62,92,121,122,69,209,100, +116,104,142,77,163,166,67,102,64,169,181,169,128,248,11,127,27,220,101,151,125,64,207,46,113,41,175,237,71,37, +77,131,248,220,93,200,92,193,77,116,155,213,168,212,97,63,150,170,72,141,210,71,33,240,241,54,84,180,156,243, +45,183,129,91,110,243,20,133,87,54,110,184,107,216,207,155,115,185,131,227,22,175,36,220,252,241,101,171,214,242, +66,173,131,174,241,183,89,138,185,186,64,12,1,214,12,154,149,43,33,231,104,99,231,82,206,145,253,176,86,176, +18,0,140,225,42,150,133,217,127,2,19,44,44,228,180,19,151,214,6,146,120,89,239,184,144,111,27,90,250,97, +109,104,196,197,241,44,154,73,80,74,1,150,110,204,156,133,34,126,85,218,121,33,68,163,116,76,107,65,79,232, +49,150,73,227,240,109,182,145,25,253,90,119,64,228,66,96,36,45,125,67,121,59,182,37,19,218,202,119,182,231, +106,55,202,73,115,215,52,60,197,184,79,242,2,21,235,251,59,208,105,125,169,251,147,136,0,182,66,63,36,149, +221,177,230,102,177,123,10,225,145,253,194,233,179,200,199,195,130,221,94,92,216,222,77,251,199,201,159,148,154,246, +133,45,10,96,186,132,165,96,48,102,131,103,218,199,56,172,135,164,18,41,185,117,12,145,95,51,86,47,175,52, +206,43,244,2,3,5,42,43,202,139,154,117,212,103,89,188,135,239,100,66,230,169,87,200,2,96,11,220,63,160, +93,231,186,64,120,106,59,138,104,211,14,119,80,12,87,174,54,70,99,237,109,99,29,86,17,33,122,218,28,32, +68,187,116,40,8,178,195,65,131,191,129,67,41,230,205,218,179,142,247,246,247,235,192,153,87,0,3,20,70,32, +104,194,0,63,193,225,186,170,126,86,60,43,84,49,221,31,161,15,40,187,80,157,7,57,220,23,221,44,86,251, +240,229,94,188,69,17,191,31,170,21,159,6,63,32,202,180,31,253,2,238,130,110,180,46,16,183,177,245,124,128, +58,195,238,124,65,134,86,122,45,111,139,20,133,236,249,145,132,222,230,128,102,128,211,27,6,248,26,239,207,203, +180,75,247,211,36,34,167,254,202,108,235,26,99,120,233,21,92,200,13,109,120,231,43,208,40,6,216,179,6,153, +137,160,49,167,19,249,6,126,93,173,159,31,56,93,253,46,136,112,241,155,214,55,221,239,182,165,81,246,215,86, +22,188,17,8,203,51,206,88,80,200,67,110,228,26,182,242,130,236,45,204,227,5,249,107,93,199,27,131,114,27, +255,101,175,95,130,49,215,194,125,75,0,163,141,132,236,66,152,183,253,109,144,101,219,201,178,197,44,9,98,246, +65,150,170,147,165,194,44,185,210,222,89,154,76,49,68,5,234,40,151,214,14,150,92,66,244,117,16,93,201,18, +233,80,232,162,153,13,30,162,9,129,244,246,235,57,106,140,65,247,14,38,87,152,92,29,76,78,48,57,59,152, +156,98,242,245,193,228,165,104,131,249,102,80,237,23,202,120,115,98,231,126,103,126,141,207,91,58,177,131,165,184, +82,155,197,23,19,109,183,3,205,64,105,186,239,208,14,230,5,114,160,154,165,182,227,15,120,208,251,71,192,85, +163,148,114,19,50,115,67,143,93,76,91,105,51,208,174,228,4,65,89,209,162,201,163,166,42,183,247,28,53,2, +44,160,151,90,71,62,40,241,51,60,103,246,156,136,214,164,190,88,234,244,170,95,111,56,247,120,36,200,251,86, +100,63,144,99,207,220,117,140,192,219,5,77,201,8,31,165,0,191,215,222,171,85,241,144,169,10,177,131,222,217, +119,36,126,159,218,31,205,82,27,251,191,238,142,131,38,91,205,174,159,179,250,235,219,70,23,6,81,132,86,128, +213,146,71,218,214,56,8,187,184,83,186,109,67,120,114,255,108,250,9,234,120,147,164,26,62,110,215,230,165,201, +69,27,170,229,64,62,209,94,110,178,124,254,125,245,131,89,91,238,67,184,117,238,135,60,68,89,2,228,222,239, +33,179,6,53,175,244,84,119,128,171,219,118,72,168,107,166,96,10,251,5,3,49,54,149,97,230,125,178,75,244, +194,2,56,179,65,231,101,4,248,39,190,93,249,150,96,61,46,237,254,147,218,173,95,200,239,29,130,187,116,71, +240,94,171,181,53,242,100,76,250,249,139,215,108,59,156,15,186,199,48,11,165,119,183,172,196,7,177,206,125,236, +12,25,191,215,117,137,126,245,105,11,118,196,52,69,239,186,14,43,56,162,200,67,232,67,107,6,26,133,54,232, +66,114,66,154,149,66,122,13,203,7,212,131,155,181,184,66,186,33,33,186,129,140,84,53,86,226,70,43,197,68, +251,236,225,113,92,192,92,27,182,131,76,85,131,46,66,10,55,207,103,13,138,16,25,119,116,120,40,187,195,49, +156,27,206,170,106,201,245,224,186,67,132,85,40,254,38,66,16,39,102,115,59,126,147,219,144,226,56,90,152,54, +235,111,50,254,109,32,155,61,159,221,124,97,58,168,216,172,50,196,179,195,44,222,61,93,132,151,41,208,42,108, +130,11,116,35,48,252,52,133,63,6,205,181,246,120,180,177,198,227,7,189,28,55,200,16,133,143,115,99,32,102, +99,100,243,173,76,83,170,22,98,234,7,154,183,149,223,158,176,45,247,187,213,19,112,216,167,243,96,9,121,157, +100,238,232,187,212,42,96,191,73,46,97,18,35,188,112,75,244,215,45,23,1,43,163,182,199,215,224,13,102,56, +86,119,164,70,221,17,163,38,178,86,243,113,33,23,103,243,115,101,156,100,46,225,108,217,153,139,210,51,120,173, +95,11,137,116,96,69,113,218,198,49,111,99,209,250,197,26,26,202,173,27,202,54,28,74,191,131,236,210,208,57, +52,148,41,154,48,120,154,26,145,247,165,202,177,139,74,227,100,39,208,27,228,178,184,78,46,80,23,105,137,138, +138,21,199,224,126,244,93,76,218,96,163,124,118,31,119,6,183,1,96,16,39,198,27,236,214,5,115,5,104,82, +171,58,251,92,166,251,35,50,39,66,110,220,30,130,254,215,176,143,22,102,31,205,213,18,70,180,81,26,166,93, +166,48,172,112,68,96,83,111,131,248,211,156,6,229,35,115,140,228,113,165,45,106,146,97,106,136,59,244,33,186, +63,166,231,14,191,33,88,122,87,33,132,28,88,194,24,191,254,128,224,34,234,10,157,135,8,108,185,127,141,84, +48,180,198,236,82,180,48,197,228,85,29,146,87,204,220,112,57,121,100,159,200,234,29,81,167,214,239,110,209,58, +92,252,61,40,150,233,87,192,82,167,193,239,137,7,1,43,200,214,232,4,44,233,116,207,192,60,88,92,178,117, +193,26,161,24,245,129,75,64,164,149,130,180,189,84,56,22,232,15,74,24,245,71,105,187,135,32,220,112,191,89, +124,193,4,101,101,226,147,91,175,198,110,130,16,111,208,197,172,248,46,233,59,231,239,207,98,173,24,224,192,36, +51,68,1,218,21,7,68,120,156,172,24,80,179,200,71,31,85,148,185,202,173,123,67,191,108,8,167,144,97,55, +204,139,35,102,221,65,78,94,43,77,217,165,201,180,104,85,157,70,9,131,154,169,231,119,194,213,11,208,6,182, +57,205,250,74,109,206,18,154,106,226,232,127,17,109,104,7,160,5,150,37,42,92,45,158,174,90,2,86,0,168, +42,116,66,241,16,170,114,34,54,195,219,32,149,141,52,103,164,20,86,3,14,175,163,82,236,108,69,21,188,227, +205,237,59,222,92,64,2,214,39,118,159,81,29,113,161,219,240,212,157,230,185,205,101,20,60,239,196,168,120,1, +11,64,12,189,241,183,128,77,175,251,108,122,60,60,245,57,159,159,47,162,10,13,140,146,137,79,207,246,196,142, +192,30,250,30,48,139,69,142,207,151,59,182,104,233,110,133,211,194,246,240,30,148,79,187,173,84,43,198,215,232, +80,135,32,134,189,37,96,213,192,231,69,191,32,124,1,1,13,99,155,2,14,14,45,102,60,26,145,73,255,26, +243,214,67,121,107,159,183,37,249,235,187,105,155,96,197,80,0,145,185,95,2,49,63,244,55,167,116,10,14,67, +58,120,20,70,203,38,141,28,233,201,68,166,61,14,253,121,68,187,91,190,39,45,50,55,246,241,246,230,86,234, +16,8,14,31,95,162,98,204,227,180,182,57,79,43,141,48,5,162,18,213,99,170,128,116,160,204,85,151,165,2, +145,238,120,79,80,7,41,28,216,33,79,222,246,254,177,79,22,14,183,197,29,68,159,166,88,212,72,43,59,37, +151,42,129,139,41,25,231,120,53,121,123,31,53,92,75,211,5,177,203,128,16,91,28,57,57,183,116,86,185,119, +223,120,193,149,193,164,49,186,20,186,126,91,138,10,106,242,217,112,115,26,37,143,158,28,194,108,100,235,31,121, +142,38,19,178,225,51,72,111,150,221,160,102,38,161,178,168,246,203,14,45,228,30,216,11,17,247,51,245,114,152, +21,40,68,235,49,244,254,181,55,76,29,149,6,243,125,166,176,127,79,7,123,72,243,225,45,223,30,186,114,167, +165,170,152,59,6,211,238,3,42,73,163,61,250,65,54,198,134,51,225,14,243,189,67,139,73,48,243,170,118,245, +187,91,21,112,71,230,58,171,198,10,5,208,153,15,90,239,132,85,69,29,24,208,54,243,108,13,236,13,83,167, +53,55,199,113,212,34,231,103,229,73,218,95,32,162,169,101,105,31,25,11,89,182,119,46,105,31,133,161,204,253, +92,195,244,49,188,141,161,106,222,29,219,65,234,223,87,127,175,114,141,213,31,42,35,181,226,141,30,238,48,164, +188,220,81,144,165,26,162,253,100,165,172,213,50,45,19,210,13,81,135,56,174,100,0,217,24,231,22,52,150,139, +218,120,243,97,55,210,251,68,241,82,165,221,9,27,162,141,113,120,136,214,206,206,224,197,177,105,191,69,7,5, +255,144,163,37,254,26,83,5,163,243,248,204,252,200,185,74,239,167,161,17,141,223,244,36,4,233,209,195,208,125, +114,109,200,181,254,25,64,212,79,174,84,234,206,53,202,102,205,221,52,27,194,71,46,248,214,92,5,178,18,252, +173,114,137,179,232,154,94,84,90,3,143,25,102,105,101,165,79,87,188,136,161,20,202,253,248,112,127,209,254,145, +184,226,19,99,61,148,86,175,179,54,137,23,214,245,112,158,203,61,68,130,154,205,120,14,44,220,66,45,221,194, +113,95,7,151,14,223,224,212,242,254,69,193,185,203,213,178,207,202,152,239,177,50,26,89,64,165,206,70,137,37, +136,126,41,163,26,201,227,220,143,192,43,12,152,120,239,81,3,22,100,96,17,82,180,207,100,174,140,240,81,194, +9,240,61,108,220,18,186,233,27,120,193,64,48,182,31,187,47,29,211,64,231,201,249,200,0,0,64,181,144,95, +179,168,1,20,53,152,245,11,63,174,151,36,202,209,110,66,110,155,39,78,160,112,45,102,125,161,39,119,39,13, +108,48,13,165,24,211,129,140,84,113,56,17,188,11,81,171,29,26,64,198,228,161,234,220,43,98,183,206,139,154, +110,99,223,209,198,1,226,186,43,249,199,247,182,49,195,126,184,215,69,167,223,59,167,102,252,176,166,190,237,45, +102,9,115,90,182,78,221,197,128,145,160,75,132,164,135,29,45,36,3,72,84,5,131,148,223,89,232,24,6,142, +173,241,181,18,148,15,49,189,97,84,99,10,179,189,87,61,77,111,167,99,152,254,127,215,0,14,162,207,28,237, +35,206,88,161,44,134,27,242,143,184,103,70,118,231,28,31,114,59,172,108,129,65,96,10,69,144,42,166,123,108, +110,111,229,221,91,49,208,238,179,242,244,112,41,241,208,211,144,44,59,227,24,201,227,242,89,61,179,149,102,69, +173,171,134,135,81,203,114,130,246,31,226,242,169,219,52,180,92,156,5,234,156,148,162,237,151,52,139,143,122,142, +187,67,108,126,89,18,46,100,46,81,109,237,100,178,182,127,170,158,89,81,17,26,198,88,225,21,155,122,81,208, +228,153,170,166,201,100,34,82,242,36,52,129,199,106,203,181,130,125,140,40,253,52,49,102,112,19,81,98,14,132, +130,67,232,193,180,195,63,2,72,24,241,195,3,33,83,158,185,43,241,181,154,201,158,112,14,112,184,252,98,211, +75,229,243,220,246,230,238,110,166,195,30,15,215,101,13,152,17,206,165,152,225,164,22,221,71,30,108,178,181,164, +70,152,173,53,27,7,55,232,94,253,221,125,197,94,229,188,165,201,51,28,8,42,125,224,164,161,250,164,109,36, +0,195,244,38,129,20,124,109,43,8,158,231,36,10,220,158,139,150,188,197,189,195,100,127,6,65,165,196,88,245, +101,9,53,223,157,232,108,212,219,95,35,185,119,63,134,216,253,164,145,97,67,37,114,63,186,213,117,87,98,36, +7,15,166,87,7,62,241,181,125,64,17,150,123,235,59,238,20,241,211,191,131,235,245,158,158,72,205,186,115,197, +222,172,76,158,160,43,208,123,166,70,22,190,237,31,138,154,58,124,119,161,99,217,111,10,234,104,79,155,35,66, +141,107,20,152,63,13,61,248,236,227,241,134,183,183,159,105,47,135,55,26,149,122,177,219,140,166,255,232,139,203, +164,10,132,100,14,10,136,177,229,183,80,181,103,159,145,99,212,123,84,129,120,89,154,52,17,114,116,66,185,155, +1,38,18,250,170,235,244,70,189,41,129,126,71,158,95,132,32,17,80,95,132,121,44,218,216,201,27,136,251,116, +164,28,51,199,218,209,10,70,221,200,204,202,88,209,16,224,228,216,46,147,53,117,15,8,141,40,62,170,22,124, +249,228,191,254,252,95,96,225,17,62,39,248,253,223,128,78,145,255,149,40,224,121,22,146,93,127,86,147,68,160, +127,30,132,134,149,104,239,230,116,89,87,79,217,109,192,20,194,169,18,50,39,43,75,44,155,98,171,104,140,126, +93,125,79,69,31,33,79,84,99,37,1,187,204,91,194,72,247,109,168,232,35,152,196,143,75,40,87,232,186,118, +206,8,92,225,159,16,117,139,160,117,28,43,216,222,133,21,213,159,202,106,251,78,87,104,53,47,249,164,101,101, +171,240,49,34,134,2,245,163,2,82,78,132,4,137,191,77,113,21,151,143,33,108,236,26,87,100,34,11,118,45, +118,186,6,43,90,147,242,241,147,214,119,115,62,208,77,151,27,102,2,75,16,127,59,3,131,139,16,68,219,236, +184,209,209,126,208,211,154,161,8,38,141,41,137,215,119,104,12,211,138,141,81,69,149,42,39,81,66,225,89,131, +18,212,147,230,200,118,57,193,93,40,19,159,57,81,37,250,247,102,130,18,203,150,19,222,27,198,111,220,227,39, +143,216,93,172,157,11,222,48,9,20,197,100,158,155,222,76,210,76,165,193,220,108,134,150,144,174,76,52,79,111, +28,232,5,49,39,24,19,226,36,120,158,100,226,121,243,24,166,23,208,10,13,200,79,253,118,22,207,124,79,237, +35,31,242,225,144,189,239,30,13,84,138,162,14,233,166,70,121,198,29,244,253,131,233,112,46,225,243,235,98,30, +167,93,115,104,210,184,203,149,171,164,107,24,251,111,165,31,19,187,204,201,196,44,24,107,204,77,250,177,201,162, +99,39,254,182,222,159,25,6,2,37,202,249,249,87,131,240,241,48,81,40,43,89,202,220,177,169,83,122,211,53, +232,144,210,40,206,51,46,192,11,199,210,120,225,152,43,84,17,145,11,148,98,91,56,46,174,2,86,167,67,92, +42,76,78,133,204,237,125,12,131,155,67,117,37,68,121,2,48,176,223,149,133,198,3,81,8,145,102,49,180,92, +136,145,56,159,3,34,208,159,210,190,78,185,241,104,241,93,3,213,198,17,138,111,124,91,86,217,111,37,236,39, +128,9,179,147,120,114,34,30,217,135,154,103,74,219,112,32,165,221,87,2,144,165,119,252,186,228,138,80,29,0, +251,84,235,103,217,17,178,131,173,173,76,89,56,227,153,34,230,28,224,66,103,139,57,200,186,61,230,65,179,247, +66,54,179,168,86,35,216,23,200,236,25,225,62,49,229,106,247,141,177,152,202,230,240,180,213,152,144,36,165,28, +55,210,232,59,72,91,115,71,141,98,149,246,173,162,53,100,49,231,3,152,89,90,235,121,160,149,70,74,95,187, +172,155,67,149,29,165,156,174,201,44,238,67,110,250,144,186,62,44,91,101,166,112,90,7,182,99,65,166,0,245, +83,136,71,245,220,52,242,30,117,90,141,8,72,164,141,164,48,234,199,27,71,176,181,74,99,140,179,53,6,209, +75,180,96,2,172,140,165,180,238,63,207,177,52,228,22,66,154,248,58,140,223,27,76,224,126,169,222,59,109,197, +44,202,212,165,219,73,50,83,215,152,71,35,99,32,198,0,39,248,90,56,119,104,248,178,153,233,24,127,209,136, +87,144,151,43,8,243,210,42,99,78,12,225,74,99,105,95,234,2,91,216,101,197,34,71,37,168,149,49,195,215, +180,210,40,4,117,98,85,131,21,36,155,166,132,26,224,243,100,118,244,229,151,241,49,100,182,18,172,87,217,3, +141,34,76,115,35,204,122,88,8,129,187,119,91,115,84,248,200,127,127,86,126,108,191,91,182,225,15,60,182,91, +73,8,146,129,76,96,251,45,85,213,143,90,56,64,102,89,128,43,146,126,192,103,247,98,236,229,31,86,246,17, +126,141,128,108,125,86,18,32,11,222,226,87,50,53,111,241,11,6,100,38,114,41,160,210,74,206,61,56,91,220, +247,60,93,111,214,128,249,221,157,41,48,17,68,23,202,180,180,210,56,125,82,129,5,182,163,193,231,231,18,255, +30,120,130,46,241,175,16,135,30,16,31,28,255,190,7,68,183,166,133,91,211,186,85,131,15,136,178,82,165,27, +150,76,20,0,253,74,204,70,103,163,113,101,49,139,241,72,62,192,0,108,210,241,232,124,20,223,247,104,216,125, +144,188,239,57,146,222,34,147,182,43,72,124,88,84,249,88,218,21,11,179,79,15,139,239,145,248,218,62,133,104, +226,219,223,247,182,201,196,186,165,101,228,177,212,33,109,40,155,195,244,62,75,226,123,161,121,88,34,50,250,23, +74,136,237,104,185,146,214,158,48,116,167,101,60,58,36,181,181,218,30,9,68,133,122,151,167,92,122,33,131,247, +155,92,87,200,35,86,247,60,172,105,89,35,191,121,152,255,183,16,114,99,147,122,236,87,20,151,10,39,165,91, +108,46,107,185,240,234,154,107,165,167,235,167,136,163,172,189,166,230,170,191,9,215,104,13,21,180,18,126,138,86, +78,14,65,204,118,120,67,35,20,209,201,60,206,219,216,45,76,158,110,16,190,62,79,236,94,50,115,82,99,21, +23,106,63,139,121,215,114,89,16,60,220,170,104,229,21,102,118,173,112,13,202,43,181,243,88,4,94,155,216,252, +165,193,19,228,254,245,24,63,188,133,254,102,88,153,61,58,194,146,99,183,116,99,210,55,41,33,222,198,233,236, +242,200,140,228,226,200,154,244,150,91,136,115,129,216,38,58,199,6,152,128,42,185,30,27,191,52,97,33,201,3, +194,172,159,16,219,2,237,116,3,160,231,138,133,29,65,131,248,158,61,176,150,13,8,143,17,115,217,191,246,214, +66,176,149,94,174,14,42,195,188,20,154,2,34,115,37,111,36,10,207,200,11,251,189,60,50,180,130,144,251,135, +38,194,178,114,45,175,240,58,50,114,211,70,130,201,28,145,195,140,65,143,35,215,135,105,255,64,44,180,82,117, +95,115,79,38,108,204,172,131,71,155,61,106,125,216,37,6,123,6,92,107,105,180,185,193,55,219,50,96,11,112, +133,104,186,3,16,22,195,162,32,99,68,186,227,119,202,191,211,135,229,131,141,174,197,89,152,50,112,115,216,205, +136,120,223,79,209,2,246,148,241,158,8,95,130,181,190,33,137,220,168,130,85,100,246,33,181,36,249,92,228,8, +64,66,21,60,206,83,18,199,0,185,100,241,127,46,36,151,182,30,163,151,101,37,107,28,53,192,198,207,168,8, +85,33,115,191,132,70,32,162,255,232,203,169,252,198,209,8,170,203,151,52,167,115,240,205,46,220,31,68,54,50, +129,49,171,121,212,176,241,189,141,215,210,140,125,230,105,236,184,108,25,30,246,25,252,180,166,242,62,209,27,218, +47,165,172,2,231,106,33,212,247,30,214,136,201,89,236,51,79,122,224,174,20,238,250,147,165,240,126,117,155,14, +183,35,212,222,4,10,110,137,98,244,210,242,25,226,154,72,87,203,229,173,108,107,136,253,99,12,252,216,112,205, +139,67,176,179,187,96,66,214,132,32,208,190,142,27,183,195,137,226,79,128,4,107,6,88,18,143,154,46,43,160, +109,15,130,101,214,173,28,186,226,180,23,122,46,90,182,124,107,225,126,141,244,56,84,246,198,204,113,92,210,109, +136,7,28,197,65,146,125,60,6,47,74,198,99,82,196,99,114,100,30,144,199,73,154,239,133,66,1,231,194,190, +112,4,114,148,90,38,72,201,47,141,68,46,210,178,232,122,102,161,230,147,37,62,200,10,105,140,179,171,156,9, +96,136,205,137,240,157,4,177,203,135,70,78,27,40,220,37,121,101,116,185,132,169,15,14,206,98,12,245,177,220, +221,67,100,98,193,51,97,58,171,227,133,233,237,165,210,251,27,104,101,68,5,246,245,87,12,28,204,114,116,86, +208,0,79,98,168,232,2,208,233,120,163,46,229,90,109,38,151,158,33,184,22,79,75,52,92,174,128,74,95,75, +45,43,241,168,148,75,20,85,134,216,203,137,90,131,203,29,9,229,198,230,114,86,67,85,87,130,173,240,226,80, +215,226,145,201,244,6,96,161,177,143,196,25,145,147,52,189,28,171,11,244,182,117,225,28,59,153,27,110,77,247, +172,189,230,55,146,238,195,205,120,141,108,183,131,119,121,207,90,74,77,167,181,115,180,241,206,64,203,197,111,145, +251,86,169,175,173,107,166,231,193,25,147,39,143,143,237,30,33,151,141,218,195,120,230,154,217,189,178,7,237,98, +104,149,195,48,113,117,231,244,34,150,183,200,53,42,131,165,6,192,213,50,21,241,130,191,25,59,235,195,66,255, +64,210,135,75,182,57,9,253,137,9,8,79,19,181,36,28,125,121,100,216,119,143,22,238,235,49,218,245,10,152, +125,46,131,187,161,173,108,81,162,238,135,86,141,135,86,141,232,86,170,137,243,106,170,100,196,223,44,104,50,201, +161,11,102,81,147,49,126,210,194,38,210,44,123,222,14,74,240,133,96,88,43,214,1,44,72,27,68,250,87,77, +11,149,213,177,129,202,0,130,107,3,130,247,1,45,29,124,182,140,94,156,149,36,246,198,34,131,162,109,175,80, +53,95,141,96,5,71,83,248,246,207,28,123,239,26,172,25,28,70,153,130,114,31,80,198,71,255,45,33,41,140, +248,31,134,182,64,201,132,182,62,238,183,96,229,109,144,225,12,179,183,47,57,178,88,227,8,245,115,177,247,248, +168,93,101,115,13,221,55,231,2,234,180,38,186,46,92,229,174,167,35,242,78,136,125,249,4,69,226,29,135,219, +86,94,24,210,140,10,121,147,80,151,26,92,162,157,54,127,215,85,137,249,216,60,228,119,29,86,198,31,160,230, +238,213,197,192,133,183,197,14,231,244,36,136,245,197,91,178,173,139,241,216,88,118,98,214,240,61,8,114,53,70, +227,3,6,231,103,68,35,224,188,220,219,187,48,215,253,61,163,26,18,52,166,57,6,33,164,78,79,191,142,146, +179,39,231,242,243,59,204,175,82,229,61,12,32,238,242,126,182,63,222,103,116,208,8,151,202,24,254,254,107,125, +239,114,68,238,19,213,32,83,61,22,50,208,139,154,23,21,40,172,180,121,33,180,231,191,104,137,166,66,12,1, +21,221,211,193,2,223,76,188,129,22,212,185,210,191,147,47,115,235,248,50,219,123,249,50,67,156,147,163,91,243, +44,48,148,178,69,240,204,188,156,46,31,70,31,153,95,98,183,140,34,224,232,88,198,78,50,142,242,153,249,66, +102,141,24,143,196,168,189,159,45,66,83,61,200,27,65,182,200,191,204,17,113,54,52,152,11,178,63,131,249,103, +241,53,210,3,124,13,148,173,30,228,107,208,173,76,106,92,115,149,152,143,192,36,135,158,110,12,39,99,51,30, +247,140,114,0,30,7,184,84,111,5,55,136,55,237,0,140,170,203,179,5,48,47,103,85,120,213,190,212,41,192, +254,60,58,250,74,196,213,0,194,7,69,132,188,133,162,115,44,218,231,0,197,201,64,17,200,10,122,17,6,229, +33,167,251,23,76,64,222,194,168,17,175,115,200,209,125,83,8,104,240,0,79,64,150,97,29,116,80,1,169,29, +164,247,215,114,35,47,145,216,63,200,43,74,101,45,243,251,68,125,25,203,235,78,47,27,87,34,192,117,119,37, +211,58,144,48,29,144,50,172,67,27,92,173,240,12,223,186,7,250,145,10,117,19,130,85,249,57,144,238,123,140, +250,249,128,104,48,45,82,66,90,219,126,71,40,198,230,242,18,205,51,65,240,119,99,25,198,43,203,232,95,199, +26,134,44,151,26,164,225,187,65,164,225,182,119,243,183,114,219,143,105,229,58,223,0,58,128,29,40,203,188,201, +214,241,46,37,67,154,24,151,53,185,183,96,140,202,36,29,223,111,183,105,199,49,174,58,145,53,252,179,36,246, +177,33,120,158,254,224,111,151,12,9,188,113,35,201,53,105,90,214,81,194,14,73,235,172,192,208,194,167,161,90, +30,167,97,104,163,12,139,234,90,168,103,47,53,124,162,120,221,195,99,124,118,228,139,224,70,222,60,210,242,90, +94,63,66,90,251,64,254,137,43,144,21,221,2,43,181,65,103,169,114,1,7,31,62,255,34,151,114,46,208,64, +79,244,179,141,189,197,207,177,141,159,22,192,136,156,92,192,109,2,131,6,122,235,22,191,74,53,137,86,99,19, +89,193,231,229,24,99,29,185,100,144,236,159,99,122,142,255,37,174,229,144,147,105,122,87,202,53,33,99,195,70, +82,240,204,216,79,121,23,110,70,96,178,224,55,193,208,46,72,185,105,6,227,169,87,3,113,191,80,92,215,248, +134,69,77,186,103,125,88,160,75,238,243,7,15,40,55,123,123,1,133,247,153,95,170,92,61,27,23,100,36,242, +183,168,64,241,117,199,159,184,178,239,90,230,122,236,191,109,77,75,149,66,73,120,97,42,144,59,8,32,139,140, +171,153,203,193,250,134,106,198,154,205,168,113,235,198,169,67,84,17,191,243,125,217,152,99,235,159,116,46,154,174, +74,84,69,57,38,255,115,44,76,145,23,89,149,110,86,11,93,233,34,213,135,203,165,97,54,209,105,12,13,36, +224,181,235,156,224,255,32,181,154,252,16,224,70,199,96,49,237,144,57,13,175,47,84,136,46,255,33,171,105,105, +136,19,139,136,17,115,240,14,219,217,40,68,192,253,52,4,122,119,98,12,255,118,96,224,211,38,120,73,67,128, +218,193,222,74,152,115,62,37,84,87,220,200,206,172,196,122,210,12,226,53,220,87,185,99,77,176,184,64,156,236, +30,49,85,175,164,246,220,3,214,72,140,131,4,235,89,39,42,41,50,112,165,149,194,182,10,37,93,34,30,30, +249,184,103,167,246,194,200,225,200,227,14,233,189,77,122,27,96,211,192,113,68,253,100,212,116,15,158,130,160,153, +159,76,53,161,137,19,185,235,78,205,82,242,172,45,104,235,15,110,33,233,96,208,220,193,160,13,195,160,53,195, +160,85,171,0,176,47,228,82,230,8,8,221,128,112,24,115,121,129,97,59,48,140,216,200,224,25,148,71,119,41, +47,104,200,87,106,91,242,72,3,156,67,194,32,110,124,201,171,71,57,230,190,6,136,61,185,17,143,121,4,180, +57,105,7,218,137,248,88,154,7,179,105,7,86,173,31,93,117,1,213,10,34,234,163,6,243,218,9,97,70,20, +149,223,135,129,87,147,235,71,3,19,79,123,32,156,254,62,88,245,3,233,87,10,85,166,56,178,97,65,227,99, +89,6,120,246,69,184,168,251,240,148,167,112,120,95,99,212,208,201,99,196,7,49,26,198,60,232,75,155,45,130, +174,57,238,97,77,162,4,18,171,188,123,179,14,172,255,70,10,141,179,227,184,59,217,221,206,132,117,60,42,31, +255,240,251,104,139,42,60,235,137,170,2,197,207,84,85,251,90,61,114,169,162,196,152,14,69,202,21,151,18,55, +229,2,35,155,114,13,113,246,109,15,35,231,10,16,227,212,77,138,229,89,109,212,156,134,19,46,181,92,115,116, +184,206,114,245,89,52,205,229,1,154,102,133,104,198,48,77,115,105,209,100,210,237,235,65,219,43,115,15,92,193, +29,112,101,108,105,92,137,219,49,101,236,110,166,43,89,10,202,171,49,239,184,192,220,110,158,111,14,149,193,35, +217,156,93,157,203,45,154,82,88,142,195,51,7,200,228,34,140,248,197,190,105,156,162,245,210,248,22,31,51,232, +115,124,211,131,228,55,50,152,57,0,60,193,244,198,155,118,122,1,120,249,150,233,156,203,123,95,64,175,228,245, +208,243,167,132,169,184,25,162,107,174,229,149,220,222,73,215,92,74,88,80,209,118,65,198,125,172,78,102,93,24, +123,166,102,178,173,173,99,61,104,235,184,225,179,80,3,199,133,217,155,15,45,185,87,194,155,194,157,71,210,60, +58,104,40,203,150,247,162,98,172,248,113,32,16,104,46,218,225,211,120,39,115,192,0,206,128,75,194,29,107,196, +236,135,71,17,183,3,71,74,139,248,248,247,170,198,251,161,177,58,183,101,111,212,164,213,13,94,187,117,0,44, +100,193,199,59,47,241,116,246,132,84,106,99,195,148,21,227,203,182,221,191,216,27,239,115,204,246,44,236,134,55, +33,32,233,37,225,33,228,15,141,254,14,225,85,44,40,13,249,139,125,148,170,22,128,240,169,162,143,67,213,198, +53,168,169,11,193,88,128,80,57,163,8,182,121,39,54,20,136,122,55,253,134,115,149,28,62,25,216,82,78,178, +122,198,235,55,210,190,230,176,33,233,27,98,96,152,205,79,149,81,152,63,50,10,167,207,59,177,34,176,108,28, +34,72,225,220,50,98,58,108,213,151,247,255,189,124,186,105,23,69,36,40,131,125,43,109,223,190,119,17,129,235, +218,225,171,155,123,215,71,155,77,151,238,198,138,113,166,198,131,8,89,17,78,71,47,205,63,166,243,24,190,142, +14,97,233,120,127,222,152,114,136,253,193,112,238,70,125,184,234,123,144,149,195,244,0,92,231,39,109,155,107,195, +208,152,151,155,79,203,98,211,160,197,233,223,207,210,72,170,52,96,104,196,187,14,94,193,79,36,116,167,34,111, +230,119,115,63,194,187,3,31,77,232,98,193,207,224,226,128,144,191,84,48,228,47,163,145,100,14,138,221,67,123, +172,20,194,231,145,151,34,45,38,30,143,190,58,254,127,71,30,173,62,238,93,97,95,254,249,88,86,246,198,26, +157,28,99,86,170,3,114,178,9,199,120,84,141,90,59,171,214,167,74,89,193,196,14,248,87,225,14,132,174,88, +250,105,88,83,200,229,73,234,181,78,155,247,230,161,255,196,179,113,114,253,9,229,8,44,96,132,48,132,116,5, +11,64,34,233,29,37,22,103,242,175,33,224,203,126,161,155,254,142,161,82,92,171,225,100,89,55,163,186,109,161, +42,219,172,131,210,238,92,112,197,171,100,237,252,216,177,173,134,172,15,24,143,67,226,210,43,198,214,12,236,81, +35,58,46,228,34,203,115,219,118,181,231,96,181,134,226,87,154,83,189,247,30,153,187,87,109,142,55,33,25,14, +69,218,203,52,126,152,13,94,185,118,105,209,123,189,3,57,198,99,97,89,188,200,81,53,134,120,97,154,78,94, +83,126,130,221,215,71,165,29,13,225,114,57,223,154,80,207,103,113,224,164,153,81,150,164,199,169,199,8,246,180, +50,138,225,37,32,67,103,38,112,202,26,82,131,154,178,230,67,99,204,237,55,71,181,81,98,19,18,157,15,143, +149,22,113,3,127,101,211,182,254,61,240,189,254,119,189,7,222,255,26,33,9,222,224,242,226,87,108,92,143,92, +80,36,251,186,191,83,69,60,112,250,159,144,103,245,188,85,219,212,120,172,174,196,180,111,107,55,217,51,179,155, +203,107,200,45,140,238,13,218,140,225,221,47,11,118,141,195,93,192,184,3,198,55,76,146,121,31,48,38,249,30, +150,65,80,22,228,156,91,213,93,75,17,247,88,228,152,118,168,104,116,63,132,98,26,160,54,150,134,187,25,249, +216,169,243,16,175,194,34,46,114,8,35,46,156,26,52,129,111,228,162,123,79,214,105,43,155,97,10,182,70,100, +233,143,63,17,121,41,162,220,47,121,58,240,112,180,252,44,34,107,113,128,200,130,101,156,31,32,178,80,86,214, +61,28,173,85,98,63,188,7,239,149,164,201,139,47,219,46,41,126,161,222,161,112,207,108,117,216,222,215,237,61, +219,22,180,244,129,144,102,235,219,6,19,189,82,136,115,247,31,164,244,228,196,191,227,222,40,61,189,33,66,238, +198,205,52,82,107,55,72,173,245,74,222,8,121,170,110,103,215,241,174,149,31,212,79,209,22,68,57,133,124,169, +78,225,241,75,13,188,94,109,33,94,66,161,95,213,41,228,68,201,222,15,159,245,132,149,239,201,101,37,114,43, +115,17,67,131,88,225,244,52,124,221,122,201,175,91,191,10,104,65,66,34,154,181,191,193,161,99,15,38,87,240, +231,217,133,188,132,3,121,234,76,241,108,33,27,28,88,178,164,2,163,133,85,197,116,38,24,23,247,18,140,55, +135,8,70,42,217,39,23,111,228,169,172,13,47,235,48,201,184,144,181,92,138,207,127,233,14,13,164,74,239,47, +10,20,243,249,179,143,146,215,84,2,73,36,171,87,212,243,212,83,248,87,175,179,227,207,123,14,199,167,191,10, +178,123,153,203,207,43,231,243,123,36,56,112,80,91,74,228,121,222,39,33,52,229,25,160,41,125,97,111,255,119, +6,62,134,72,172,231,244,192,253,233,197,108,5,93,54,182,157,182,125,111,49,218,60,195,115,244,254,30,108,214, +102,59,248,72,231,32,44,226,176,12,8,0,127,197,122,127,135,128,206,33,41,156,206,59,218,182,239,148,163,235, +86,130,72,80,92,243,204,89,43,164,172,30,152,18,177,140,15,238,153,55,102,106,164,79,83,163,20,185,12,125, +143,44,66,223,35,243,86,85,123,190,71,22,230,161,181,104,60,87,247,77,19,37,146,234,77,5,16,226,82,207, +138,24,98,27,153,203,1,32,146,10,204,36,228,177,44,112,155,200,249,172,228,250,160,245,78,125,75,1,124,140, +241,9,212,120,124,71,141,75,97,179,9,137,254,206,38,117,140,78,79,235,182,227,254,168,166,171,63,212,216,187, +134,249,237,153,37,109,228,214,203,176,226,135,81,29,170,157,187,144,221,45,78,156,213,249,185,197,217,179,42,62, +91,140,213,38,118,139,177,26,99,173,146,95,33,88,151,49,172,80,161,113,206,64,253,8,235,5,172,222,212,12, +234,220,71,88,57,134,177,34,12,111,109,186,118,233,216,140,13,39,108,211,171,251,250,109,12,53,149,45,57,84, +200,126,215,179,225,231,63,13,254,95,115,120,142,170,223,207,227,249,124,33,23,111,138,221,14,42,18,242,115,36, +95,194,50,135,221,12,120,232,132,227,14,184,244,124,114,180,101,63,79,176,238,5,148,33,206,51,132,225,190,19, +178,12,20,166,237,179,73,229,99,10,122,27,242,50,136,179,242,49,144,156,143,246,19,226,19,44,155,168,168,196, +167,153,230,104,143,139,64,114,227,211,189,53,46,39,201,35,143,190,238,239,141,189,103,141,228,143,112,238,247,30, +135,15,242,239,251,235,73,110,10,140,61,174,219,23,86,13,103,14,223,91,250,222,160,237,45,194,199,13,237,15, +23,220,228,232,171,71,63,79,173,46,211,70,174,232,44,94,42,32,225,237,67,19,194,11,154,40,63,30,139,104, +173,128,77,180,50,156,245,149,88,51,103,189,92,173,97,42,108,253,232,189,248,210,101,214,144,25,49,178,149,103, +244,94,0,70,182,58,39,22,254,26,112,136,245,248,96,45,242,70,85,251,116,39,162,151,75,19,157,1,132,43, +82,163,223,104,199,203,128,209,110,116,104,8,120,177,211,181,186,50,114,62,221,247,12,136,184,177,212,65,200,162, +193,232,91,117,165,54,44,50,115,141,156,254,133,220,198,243,14,95,254,184,195,179,191,57,192,235,191,98,162,225, +30,252,97,37,47,6,80,176,118,58,128,127,93,200,149,188,54,58,71,131,11,117,143,195,131,238,6,98,6,61, +51,238,204,196,177,235,79,50,8,178,115,204,110,59,173,229,249,125,220,248,18,50,20,227,113,43,100,209,118,87, +182,171,11,125,159,2,0,201,5,220,35,12,117,148,96,213,104,12,4,253,198,158,90,185,164,117,153,39,21,2, +161,209,244,52,20,77,250,163,28,187,227,246,95,150,87,226,61,242,217,108,59,228,166,116,216,103,225,62,59,110, +113,100,255,31,7,236,255,227,128,225,140,134,150,45,168,162,0,201,56,203,188,29,214,243,241,32,79,12,57,93, +210,161,239,149,219,208,200,212,77,242,55,36,42,111,14,26,4,48,203,60,171,215,121,178,69,26,32,20,161,247, +162,247,134,67,12,135,16,207,140,157,83,187,227,186,69,195,13,237,121,109,31,10,198,219,114,189,107,63,20,246, +76,103,64,199,192,183,63,205,196,157,62,254,29,156,105,110,162,131,26,254,30,116,142,181,56,238,51,94,173,67, +219,39,128,174,57,97,229,209,88,239,139,59,179,57,121,241,57,152,28,210,200,76,47,215,244,109,81,73,214,246, +11,91,199,67,237,57,109,178,65,78,186,245,233,205,74,158,213,31,227,187,85,61,190,27,171,1,238,46,208,119, +59,238,139,139,5,188,233,190,193,128,147,243,8,252,127,240,85,89,181,211,187,120,114,9,98,162,195,220,183,99, +89,127,190,156,246,192,253,88,29,68,176,146,16,127,99,158,83,14,24,78,110,48,156,220,99,56,169,106,64,34, +239,126,222,92,46,211,33,137,228,5,241,159,96,165,224,143,117,209,238,249,73,100,244,24,26,64,230,93,50,171, +28,218,23,47,142,110,229,198,196,108,57,102,43,215,136,189,204,1,123,217,216,179,11,145,230,87,34,219,41,182, +183,250,156,217,78,27,193,139,176,28,92,132,84,230,114,109,112,143,246,131,185,102,17,62,160,142,17,132,254,8, +115,161,123,199,133,172,6,109,115,227,53,134,49,59,132,244,177,189,73,13,207,224,195,225,27,240,30,96,230,129, +205,11,15,9,222,3,176,121,97,71,86,167,8,27,97,108,47,194,177,249,190,157,152,91,7,97,24,230,8,58, +145,225,236,39,169,129,70,232,84,130,135,217,21,46,254,253,128,29,149,28,232,121,99,60,146,3,32,28,117,29, +8,140,127,190,196,115,59,189,78,170,7,55,105,207,36,238,238,226,194,24,186,187,184,176,214,190,64,205,240,5, +95,197,241,85,38,159,27,193,239,32,238,187,76,190,164,183,211,32,54,215,18,231,43,136,121,175,229,59,135,156, +5,209,167,153,124,151,133,249,62,20,242,61,238,174,48,42,147,31,204,178,132,145,47,178,86,76,189,71,99,107, +13,176,42,111,30,160,85,204,175,171,10,14,235,8,244,24,235,7,43,221,44,203,249,3,248,42,202,230,65,182, +90,219,189,165,231,241,3,227,105,10,208,209,4,0,214,3,68,88,141,155,72,220,250,15,160,11,107,104,16,139, +193,132,92,195,26,207,143,70,130,248,11,186,232,50,21,176,233,208,55,19,90,100,104,237,34,213,188,180,166,143, +161,148,112,16,77,153,247,227,147,57,153,111,233,70,207,179,197,98,40,222,156,144,239,7,147,96,163,219,132,110, +52,196,243,46,86,110,58,163,172,239,65,93,23,222,0,162,204,132,221,61,87,41,64,121,156,173,88,23,1,31, +239,66,135,254,79,71,69,98,160,92,6,24,202,12,182,38,90,179,218,194,223,109,27,255,86,154,140,45,151,252, +46,237,123,19,101,143,212,1,9,207,142,169,251,46,148,49,100,93,66,235,61,151,208,145,181,82,194,15,107,173, +210,32,214,44,184,28,249,5,162,114,236,30,168,84,198,49,116,105,24,246,192,24,143,74,89,200,60,116,35,123, +218,183,22,7,127,121,147,214,182,181,82,18,191,18,45,18,101,208,191,33,227,16,198,82,183,113,191,66,79,47, +240,141,247,53,138,169,128,222,89,217,179,242,111,212,196,173,141,44,210,82,159,165,57,112,13,205,149,111,108,59, +244,173,82,7,202,192,198,175,145,34,227,15,198,122,186,225,211,65,107,110,92,208,168,143,135,29,142,213,6,14, +28,114,107,32,118,130,175,73,46,48,94,122,68,168,140,23,200,26,93,102,241,28,120,149,8,157,140,162,46,237, +60,42,32,90,159,253,24,243,150,204,79,15,38,248,69,65,19,28,94,227,247,110,13,235,37,200,91,209,180,79, +153,172,240,151,60,69,143,20,9,213,71,123,35,181,171,181,108,21,154,215,149,102,20,102,8,173,130,85,54,113, +141,172,66,53,194,141,90,128,54,149,154,119,61,28,47,225,193,102,186,166,45,83,68,107,153,202,141,104,131,33, +125,232,154,209,100,59,16,232,70,243,33,90,127,144,58,140,221,82,172,163,166,248,152,118,17,27,111,77,5,24, +170,147,26,52,236,98,99,229,33,140,223,66,252,22,226,59,175,21,245,63,43,98,72,175,203,27,88,154,39,98, +204,161,10,66,225,118,127,155,237,155,0,60,59,119,213,253,29,65,88,22,176,2,129,13,12,172,191,119,0,198, +224,93,28,216,3,102,49,53,84,192,99,176,22,213,118,248,86,78,123,13,0,68,3,72,76,1,217,201,22,197, +206,185,39,174,186,238,146,19,34,196,242,86,180,198,251,83,29,108,155,190,125,58,236,38,167,150,212,46,173,127, +64,142,164,158,143,179,108,9,43,3,178,190,142,206,14,80,242,198,250,227,142,240,172,86,93,97,213,0,231,26, +3,231,96,32,112,77,129,226,205,2,125,144,253,190,17,209,156,242,148,149,157,241,189,75,131,99,65,138,33,108, +54,187,82,31,140,191,74,187,243,15,61,202,250,233,200,35,99,145,209,45,233,92,165,123,203,81,91,98,2,68, +32,157,61,124,246,115,103,0,133,69,63,13,2,27,217,188,15,97,55,108,238,220,13,253,170,86,10,217,242,27, +1,12,196,4,77,162,158,241,52,165,221,105,90,210,52,45,90,244,116,179,18,241,10,161,38,2,73,154,221,251, +138,237,207,46,250,118,242,179,251,99,22,206,238,231,237,238,153,86,4,174,31,22,51,222,126,181,136,59,75,21, +159,157,251,102,110,6,12,106,94,104,108,69,72,92,78,60,190,246,105,40,81,21,217,159,131,155,244,103,179,52, +163,24,62,127,177,159,100,94,244,33,193,8,186,74,105,85,145,215,130,150,214,235,179,10,55,235,239,153,38,233, +55,2,0,20,89,211,185,140,114,180,131,14,201,26,146,27,188,130,82,244,18,144,207,206,206,227,178,69,220,224, +101,170,12,22,92,199,22,184,222,49,78,26,34,188,106,220,34,147,60,168,114,102,160,77,45,75,40,23,255,232, +190,209,155,187,144,73,0,118,42,231,76,50,186,239,90,96,30,103,206,12,168,84,85,112,29,18,207,127,169,114, +75,106,165,231,211,37,140,104,105,160,56,170,126,119,103,108,217,157,177,156,138,211,61,210,226,212,36,102,169,93, +190,207,27,255,214,46,228,231,78,2,158,50,55,246,103,108,151,62,177,3,10,59,40,243,125,46,91,34,44,223, +175,10,141,77,91,67,82,185,215,152,74,69,213,27,122,14,115,51,12,180,82,127,172,42,226,253,124,246,176,169, +92,48,214,86,34,169,162,235,223,93,7,207,81,48,137,166,190,96,19,82,86,58,127,59,172,36,198,237,199,5, +226,160,112,107,74,111,239,41,189,189,171,116,219,18,136,251,6,142,53,153,114,53,70,90,37,153,115,149,206,122, +107,112,73,45,8,145,102,6,31,144,158,80,105,164,213,51,13,151,51,242,38,66,236,249,170,62,152,253,155,146, +177,9,83,210,218,215,66,41,139,203,242,22,167,176,95,215,188,215,52,89,10,199,209,243,193,169,1,229,40,98, +45,75,99,148,180,112,211,95,147,20,47,212,88,210,167,51,117,53,161,110,196,46,207,196,229,104,131,182,191,15, +177,35,103,185,138,76,229,74,167,174,168,213,177,44,84,148,33,243,75,184,237,170,159,162,28,144,22,181,202,206, +52,32,112,107,226,164,196,165,55,203,100,132,172,226,74,154,95,43,46,28,39,10,16,78,228,143,209,86,183,157, +212,18,38,39,174,37,212,2,21,4,70,245,234,190,213,66,59,6,30,149,164,54,0,208,142,123,13,181,130,89, +226,126,192,111,58,3,222,181,102,128,54,168,31,0,130,238,18,169,239,133,233,81,221,169,24,69,3,237,3,60, +104,49,225,106,91,217,174,58,170,189,201,53,198,13,208,76,4,176,238,240,71,237,172,156,192,177,4,214,108,170, +231,240,65,131,57,182,22,110,142,177,199,246,113,112,60,150,21,141,112,172,202,118,127,28,31,123,228,155,25,151, +220,93,63,47,111,65,28,200,242,220,11,185,180,193,111,105,206,90,101,213,14,104,105,217,56,89,214,183,75,182, +75,96,85,75,194,112,118,200,83,252,96,77,240,168,4,119,49,122,138,63,35,235,66,72,220,164,8,180,131,41, +122,156,82,223,167,73,199,204,114,98,245,250,212,114,182,124,84,196,57,190,121,36,215,73,150,163,84,154,233,178, +132,252,118,67,215,34,230,236,133,143,198,146,117,175,164,29,93,32,146,207,147,244,117,103,177,205,94,151,90,193, +121,107,220,121,5,37,221,212,156,75,55,70,97,112,219,2,51,45,16,242,89,248,97,99,107,31,75,118,161,133, +44,57,14,65,12,101,172,56,146,32,141,117,199,127,85,147,251,126,153,211,247,118,228,246,168,159,100,45,177,73, +224,146,131,3,192,152,253,14,148,66,98,155,24,255,220,84,25,215,46,41,23,238,171,226,175,68,72,175,154,106, +123,194,97,104,255,26,95,206,128,16,246,245,215,194,87,23,158,191,114,168,238,54,96,35,48,86,181,175,127,96, +0,67,3,127,196,56,136,43,32,14,254,4,64,232,175,37,185,113,64,225,4,229,179,98,80,54,86,98,33,51, +130,12,97,34,134,101,99,126,48,217,78,116,144,129,98,100,67,31,152,201,76,161,207,67,17,178,177,191,65,151, +222,239,113,26,44,28,64,40,69,162,193,25,86,65,248,168,1,7,191,25,245,27,109,164,217,208,66,54,168,74, +77,148,13,58,86,253,2,93,218,210,185,1,198,149,61,245,36,62,116,2,176,200,100,246,253,179,97,169,195,67, +84,210,81,136,75,123,58,132,212,182,148,205,252,120,97,1,136,196,230,199,212,124,107,252,75,99,95,141,143,26, +152,239,74,134,81,81,96,82,144,27,63,150,141,21,192,48,39,115,2,11,93,201,140,206,67,112,0,242,129,18, +246,68,114,17,60,24,225,73,72,85,242,80,193,252,221,32,10,104,190,150,222,44,251,141,66,92,31,18,186,195, +222,213,201,10,9,198,18,221,225,2,181,24,219,136,37,69,132,62,5,158,135,167,190,187,76,156,71,71,1,138, +195,3,192,125,57,49,59,244,152,239,14,12,142,85,45,235,22,248,104,48,161,17,157,115,224,173,97,200,193,7, +29,209,148,224,39,143,213,119,234,117,15,94,55,131,221,42,162,128,201,176,195,170,97,123,64,123,200,92,160,155, +194,86,13,119,5,227,0,140,103,87,128,46,160,37,165,96,64,16,146,134,249,214,10,201,215,72,17,101,179,179, +206,66,162,131,191,238,50,133,231,243,86,15,242,33,248,38,145,198,233,247,192,125,34,17,197,237,221,41,50,167, +59,36,167,151,15,7,230,129,227,119,116,195,144,30,67,75,9,179,22,222,32,178,113,27,213,174,254,130,86,127, +222,170,247,214,78,94,34,11,200,241,191,106,129,212,63,117,98,169,160,238,185,204,25,198,163,22,177,65,62,18, +190,52,224,6,131,81,214,52,74,176,157,217,122,118,183,14,40,74,130,81,218,193,163,134,129,74,51,46,60,20, +210,227,90,102,124,123,101,124,169,5,172,204,122,216,241,133,217,16,56,181,232,72,92,110,141,89,245,0,71,73, +66,28,229,65,206,215,113,113,150,120,176,66,240,100,15,205,56,105,97,46,134,47,106,80,207,66,24,22,78,182, +103,64,194,186,60,90,26,215,143,181,153,189,156,198,51,189,108,162,212,90,19,20,198,213,136,11,4,115,61,131, +233,203,101,109,161,117,37,117,8,82,106,18,82,163,212,185,136,49,51,193,244,113,122,100,251,15,165,22,144,38, +169,114,85,73,151,50,86,11,89,169,156,38,221,112,61,131,62,47,187,125,182,171,209,235,114,121,184,203,165,172, +205,253,51,151,186,3,212,106,39,74,103,146,169,203,165,189,166,124,159,231,114,225,123,92,118,122,92,170,220,142, +187,109,129,223,163,176,232,86,85,237,183,71,72,185,142,224,209,190,220,160,206,2,58,0,32,248,128,175,116,107, +250,220,13,67,3,105,97,69,219,10,243,90,0,141,238,160,0,96,127,116,179,226,54,209,232,65,209,126,225,249, +21,178,225,33,43,255,9,56,237,9,164,56,116,94,241,39,96,187,22,68,52,142,232,104,120,243,32,228,191,128, +174,235,170,86,238,11,90,115,187,157,223,101,206,118,191,65,95,141,240,180,70,7,133,244,213,182,64,188,83,207, +156,223,176,86,90,207,86,52,6,15,57,41,223,140,126,153,216,106,140,97,95,253,208,82,91,148,234,92,151,105, +121,2,53,90,223,154,155,74,59,177,145,96,6,52,127,134,163,215,252,233,135,173,233,163,149,4,192,232,32,147, +31,170,46,155,173,86,57,16,213,252,154,111,151,215,157,243,142,32,38,110,45,179,71,123,226,152,26,226,237,121, +179,178,150,136,212,218,193,89,251,181,14,151,147,169,10,143,239,244,71,151,77,174,224,94,160,247,135,213,209,165, +6,104,162,223,152,126,116,223,32,186,105,145,104,29,82,176,196,29,171,231,27,152,199,104,37,47,129,42,189,196, +154,189,2,65,39,120,68,178,39,198,172,14,168,169,172,198,40,36,10,0,70,46,250,15,160,30,22,196,141,12, +14,89,172,121,191,215,178,75,33,196,165,143,160,204,149,236,80,61,224,124,233,241,178,71,249,84,160,26,32,228, +124,192,138,151,152,2,54,4,151,131,81,174,101,46,107,47,159,191,170,227,185,188,65,2,21,26,189,141,9,110, +109,99,3,10,176,50,185,86,64,152,229,14,83,78,141,215,85,184,88,18,191,185,54,114,33,215,66,66,100,30, +124,167,244,141,215,144,79,0,76,102,35,228,105,13,229,61,77,224,18,55,136,128,108,142,110,224,99,139,31,75, +155,177,75,37,80,230,144,147,74,120,197,198,118,30,193,201,198,0,58,44,232,162,199,88,47,193,22,147,58,198, +250,9,241,196,79,107,84,30,114,181,242,199,40,241,149,155,189,230,228,93,87,184,45,166,221,185,188,12,187,34, +228,165,67,2,236,64,150,242,30,164,7,54,37,11,21,252,92,238,146,244,159,155,172,210,129,143,111,177,107,43, +157,235,164,246,145,129,63,125,88,196,175,175,117,209,160,131,66,148,209,115,143,183,228,178,116,56,13,121,121,250, +58,75,237,211,159,17,125,240,15,205,39,164,128,147,173,54,43,92,223,61,151,42,58,196,143,53,162,53,132,182, +23,170,192,16,29,237,157,157,81,237,38,57,40,84,207,76,96,145,151,200,139,121,92,139,24,153,92,89,125,218, +52,230,93,51,24,225,113,203,106,45,0,232,48,193,89,247,122,149,146,4,198,224,188,113,111,129,179,28,58,4, +239,134,162,209,147,249,8,78,50,74,43,236,53,212,236,139,83,43,152,241,150,240,240,76,141,190,48,235,254,107, +61,146,191,165,10,238,177,77,186,180,10,20,163,85,185,169,245,188,188,41,70,210,68,227,90,80,44,126,82,44, +116,158,34,55,235,145,101,121,234,74,227,31,138,54,223,156,130,245,5,85,115,124,175,114,142,223,172,247,107,135, +141,196,153,1,62,113,60,124,7,177,173,252,80,43,84,158,102,43,53,248,53,26,121,188,254,219,189,199,246,26, +133,31,101,97,89,197,176,146,85,118,9,240,47,114,134,121,133,172,251,73,100,187,215,48,162,179,179,38,59,87, +78,67,54,222,209,166,41,232,92,214,210,84,239,197,0,181,3,202,146,114,106,183,239,104,219,217,77,137,130,45, +156,85,241,23,92,252,151,121,153,94,141,164,225,97,194,54,71,197,219,224,27,51,24,201,180,9,68,141,228,7, +100,133,121,58,38,65,84,151,123,95,6,142,192,34,135,35,151,2,45,80,64,185,66,8,248,160,233,161,62,154, +185,20,140,68,83,153,199,232,228,255,9,187,23,13,155,178,25,251,109,113,5,165,247,188,72,187,243,47,165,122, +15,36,230,26,97,212,181,145,64,6,161,35,191,122,191,178,178,84,118,52,8,65,254,82,138,192,5,90,144,59, +77,138,235,164,62,34,232,114,119,185,183,189,61,242,91,122,102,157,60,2,74,111,63,240,169,179,192,235,166,85, +86,156,131,153,71,152,26,107,203,242,137,27,105,197,64,226,76,66,126,239,3,161,48,98,70,88,222,199,213,38, +46,116,86,152,217,94,244,217,162,184,44,248,230,6,147,174,141,196,112,146,25,97,69,193,144,199,191,231,237,233, +150,185,137,144,181,241,163,251,221,198,74,146,126,127,89,235,10,157,144,151,112,105,216,183,24,156,246,46,177,83, +138,74,85,64,191,101,81,130,147,175,231,111,241,165,203,122,1,172,240,89,19,19,236,4,115,210,20,18,52,34, +48,158,70,46,109,91,209,188,76,141,151,82,52,221,150,229,115,92,142,216,168,144,95,54,149,54,75,223,121,249, +253,229,255,108,44,190,203,225,104,120,152,255,190,177,216,246,223,104,219,221,100,109,232,246,23,104,40,136,71,249, +207,146,149,26,50,117,147,21,0,47,143,230,189,43,111,138,166,33,94,212,112,144,160,108,38,223,104,175,194,128, +243,3,163,134,125,177,169,16,32,247,111,75,100,241,192,77,130,131,16,1,93,77,251,29,106,178,36,26,53,188, +119,194,156,20,173,252,103,41,176,93,122,225,11,170,250,27,84,101,42,154,107,148,42,131,128,236,213,74,211,125, +184,98,95,217,55,119,45,57,188,201,159,22,81,97,223,220,235,46,170,95,170,219,50,138,80,210,33,120,239,4, +46,109,158,65,163,6,55,157,106,147,44,211,167,157,104,187,192,210,246,20,119,2,174,212,123,211,55,222,86,9, +215,153,171,4,69,138,82,149,155,115,8,149,188,215,41,97,21,114,217,139,37,66,61,85,198,201,196,18,127,64, +251,27,223,170,131,29,85,241,142,170,133,52,203,130,220,230,224,16,32,76,112,222,135,205,173,0,77,20,80,61, +42,181,41,22,115,6,18,2,23,34,100,236,222,53,149,183,165,57,43,16,211,220,178,49,42,29,1,24,44,101, +38,96,62,160,27,129,230,2,202,81,81,135,207,42,89,177,77,48,247,245,203,57,15,8,1,118,1,237,214,184, +253,9,87,188,19,253,9,123,120,63,6,196,205,160,201,94,26,143,241,22,24,125,155,146,159,111,97,193,234,62, +38,202,236,63,42,103,246,145,198,219,92,56,36,149,109,69,153,104,103,214,98,122,230,174,53,119,149,158,243,1, +12,167,169,192,103,155,159,208,31,155,166,61,239,49,136,82,196,230,244,4,49,178,242,100,30,236,110,123,239,162, +72,101,79,243,19,108,157,226,165,222,105,82,219,220,208,160,170,225,15,74,40,208,117,78,191,210,30,199,7,102, +32,240,48,114,16,253,110,150,217,193,75,210,247,174,57,250,98,93,149,183,153,225,99,248,128,218,181,120,102,118, +137,65,135,227,31,83,104,214,124,253,146,74,187,49,227,111,210,246,12,175,209,191,166,211,26,62,148,107,120,24, +237,15,119,195,93,109,194,124,65,101,93,72,16,185,110,252,146,113,55,50,234,6,124,217,110,252,148,10,108,5, +247,39,214,64,119,241,221,116,198,1,168,124,31,245,241,117,206,81,93,138,129,119,226,105,225,149,42,30,62,140, +204,9,207,234,23,246,128,235,121,248,214,243,23,3,102,41,235,171,50,2,82,128,216,10,104,4,42,173,180,46, +94,152,93,221,241,112,5,188,152,7,89,97,21,9,247,179,206,94,165,241,207,41,157,210,219,174,2,49,109,140, +219,142,162,240,54,12,145,120,63,92,181,50,148,10,238,100,249,130,232,17,31,223,146,108,184,19,254,247,46,135, +240,33,122,139,26,217,88,50,16,71,35,5,179,115,143,114,113,214,118,153,212,86,117,128,39,29,12,116,216,142, +3,57,239,190,183,162,229,234,58,59,172,223,71,179,159,224,8,122,245,110,204,193,135,192,159,204,166,115,24,241, +4,154,195,79,254,74,236,196,68,98,134,33,116,48,23,99,53,246,152,214,109,123,219,117,204,238,131,239,145,13, +227,38,138,90,253,123,169,118,215,228,219,188,235,210,56,67,77,155,172,149,5,224,33,85,198,208,30,81,120,188, +106,104,4,163,227,209,128,1,188,158,238,181,179,135,167,50,156,1,237,36,109,78,88,146,214,211,209,44,99,169, +81,246,198,244,76,200,32,82,179,104,171,75,21,211,40,125,122,162,39,127,250,223,255,77,159,157,232,147,175,96, +101,140,107,222,20,47,225,108,145,165,35,228,197,253,19,71,160,69,235,128,233,170,241,109,89,87,217,251,150,103, +39,39,143,2,242,188,18,242,201,177,176,166,119,119,5,105,76,89,207,84,120,70,95,145,214,194,203,236,83,214, +212,113,34,161,170,161,248,118,88,241,62,239,236,114,242,160,110,197,217,133,4,245,246,204,10,76,203,188,252,148, +84,89,179,92,125,214,146,100,143,89,2,245,228,88,6,131,129,225,103,66,248,235,14,202,131,159,56,252,121,98, +127,190,154,253,189,60,162,181,71,11,172,185,217,235,210,54,9,91,35,180,56,97,230,150,237,226,53,110,125,191, +156,53,103,79,104,153,38,205,153,91,177,24,62,57,150,87,217,245,196,175,181,120,166,128,217,139,168,105,208,239, +12,87,87,171,108,210,137,19,82,27,105,185,119,153,218,57,165,142,170,142,255,94,182,83,226,189,27,157,142,145, +244,202,115,199,236,236,230,132,93,36,195,103,168,139,135,65,99,192,34,30,153,229,24,201,79,176,150,58,38,21, +189,176,46,175,12,121,98,88,224,214,210,33,166,96,232,251,226,5,63,216,83,212,71,172,16,3,88,51,121,53, +251,111,19,176,213,152,9,85,207,154,35,174,217,36,26,213,75,159,152,98,48,24,136,165,212,95,38,245,18,100, +230,130,208,247,54,199,49,69,81,79,91,105,148,102,66,141,66,137,200,12,172,110,247,77,226,79,142,73,247,167, +22,203,96,207,209,20,200,123,214,26,196,189,206,161,175,32,152,161,226,136,171,239,3,234,144,210,244,28,7,49, +102,48,97,99,95,202,96,74,241,145,4,125,66,135,223,239,56,163,209,227,225,97,57,61,160,248,93,118,228,215, +223,110,44,115,64,161,157,93,11,189,252,213,126,36,104,191,50,30,165,196,85,74,171,178,174,79,109,92,161,19, +167,68,133,77,60,135,90,231,0,217,205,236,210,55,245,187,250,116,153,68,79,190,250,74,62,240,127,142,143,254, +235,43,49,226,156,174,191,224,187,76,192,78,172,16,0,211,94,60,162,45,53,50,107,8,191,252,185,151,19,119, +91,55,163,215,195,61,156,61,204,115,127,33,179,19,14,244,134,44,14,94,106,127,140,46,22,52,227,56,47,61, +27,132,15,51,251,54,86,255,4,96,42,26,217,135,135,17,156,219,94,66,178,192,201,23,246,136,143,220,18,142, +40,108,20,124,170,65,27,134,126,95,187,204,56,151,207,125,108,59,208,233,186,211,107,27,55,148,145,23,102,208, +180,98,111,97,71,253,206,251,238,30,46,19,234,91,253,189,207,60,236,94,0,178,176,182,90,12,180,120,147,173, +50,96,209,124,97,68,157,106,76,192,237,76,134,243,230,51,141,47,107,120,236,75,175,20,90,25,219,77,50,65, +36,30,110,76,153,43,107,236,169,124,230,13,204,64,49,153,163,76,224,227,66,200,60,176,34,103,158,204,11,97, +243,31,91,232,190,148,11,202,49,87,229,179,19,203,189,54,234,226,81,148,160,157,144,8,218,17,150,68,50,28, +146,191,105,83,125,42,127,138,230,2,204,241,84,147,185,172,208,79,199,177,92,40,200,59,93,62,93,76,151,227, +177,224,140,245,217,242,28,255,140,79,206,249,122,226,196,196,214,227,174,152,56,25,207,209,249,104,47,27,70,241, +20,127,209,21,25,225,9,182,80,83,226,156,95,224,100,27,28,91,200,2,195,182,246,199,122,28,53,208,103,180, +16,132,177,176,16,111,40,193,117,204,223,66,129,201,126,89,135,188,144,102,217,39,148,11,136,17,108,206,22,171, +235,218,252,241,246,91,228,137,247,61,243,14,253,37,244,244,179,188,162,15,106,104,133,238,183,114,133,210,34,88, +111,254,172,118,21,231,237,80,11,190,171,122,57,44,49,26,72,138,102,161,140,168,134,246,80,16,204,238,68,36, +167,205,3,175,30,18,206,204,150,254,45,213,155,161,66,44,143,124,169,22,228,172,69,103,57,12,84,90,207,98, +89,232,89,12,37,247,209,20,9,53,147,161,236,139,172,199,99,83,79,253,40,148,59,195,173,176,175,237,132,174, +120,130,23,88,92,172,175,97,10,92,35,194,127,77,89,215,137,197,95,116,208,63,13,237,26,233,253,122,82,72, +173,242,199,193,62,200,31,107,129,27,188,132,173,125,60,21,201,120,12,129,224,156,148,227,228,145,182,11,25,224, +189,165,68,157,111,16,167,73,97,160,200,210,89,134,3,77,97,160,7,42,10,198,92,132,203,199,43,197,139,104, +60,232,60,97,94,4,77,122,134,43,160,213,9,44,104,99,132,126,33,27,174,233,4,254,0,200,64,238,141,47, +194,70,192,137,100,89,186,167,17,43,113,52,35,137,163,24,163,232,123,134,73,24,37,223,213,202,174,9,162,46, +152,1,37,14,128,204,244,197,51,208,52,27,235,24,127,38,218,3,201,151,117,23,72,158,157,7,27,241,113,131, +135,243,160,27,200,114,12,253,215,110,30,131,101,42,5,131,151,80,172,180,220,59,173,153,5,197,212,66,104,136, +169,49,166,200,144,172,233,56,222,173,48,204,110,119,19,5,180,201,159,73,175,36,11,173,144,33,80,71,174,92, +138,43,99,168,243,200,160,225,179,96,91,228,147,82,86,147,92,196,56,75,199,144,18,237,87,113,34,32,195,227, +39,113,170,32,251,64,11,8,144,209,255,231,88,213,79,155,89,26,79,82,153,63,45,39,9,8,206,60,171,198, +137,16,12,30,252,52,84,75,59,229,63,194,143,102,22,84,129,110,95,211,192,237,230,227,39,118,198,113,4,245, +51,200,79,114,98,236,129,211,113,137,172,50,8,210,171,231,211,194,9,110,28,27,93,248,96,3,111,116,199,14, +7,227,201,32,15,18,160,201,199,129,136,59,237,12,35,156,225,158,180,188,169,115,230,200,53,17,226,133,104,174, +20,1,124,142,65,194,57,221,30,136,190,129,56,68,77,197,204,252,242,13,115,34,30,105,68,194,73,34,97,236, +252,149,248,78,36,52,87,78,245,162,129,224,174,38,187,112,200,68,241,216,134,47,148,243,62,235,20,195,97,198, +218,234,129,112,105,140,236,20,78,151,93,79,89,155,34,202,120,32,26,245,93,249,248,1,179,1,35,20,71,8, +179,205,208,49,181,128,185,240,117,46,151,125,169,88,196,252,107,43,124,84,58,2,160,162,103,251,132,222,163,242, +86,101,161,167,156,212,41,207,47,91,149,155,189,97,92,44,163,31,101,175,155,54,169,229,165,74,38,102,215,100, +61,29,0,179,150,27,85,54,198,238,99,34,228,111,145,14,252,10,135,44,75,45,16,116,221,2,236,191,0,53, +81,181,132,159,125,35,127,183,98,188,154,52,86,107,22,223,184,28,206,63,91,171,40,37,113,175,113,138,98,16, +112,70,48,107,188,86,239,106,171,203,38,166,115,236,166,41,140,221,250,204,174,108,14,118,101,114,57,30,234,202, +6,187,98,229,226,216,179,8,102,141,55,65,87,214,118,78,42,107,147,86,123,160,57,249,75,252,23,186,215,173, +17,132,159,227,141,52,31,191,196,107,185,114,162,51,243,208,171,143,211,191,103,155,204,125,246,92,227,172,251,57, +211,126,115,213,192,31,27,192,93,9,65,252,57,200,154,67,206,191,106,240,175,244,108,33,103,221,142,170,41,215, +97,9,90,140,78,20,142,48,140,176,211,19,198,16,87,58,140,89,234,126,38,68,221,42,52,29,193,114,183,88, +15,139,162,120,17,20,155,27,231,44,168,149,35,191,221,175,152,160,8,136,238,12,69,91,57,157,161,148,55,122, +49,88,209,251,126,11,86,169,40,140,48,148,175,35,182,187,93,204,138,126,151,187,179,80,161,2,98,24,101,175, +54,188,80,109,6,36,29,209,222,196,235,70,175,106,243,108,67,9,166,209,225,88,196,155,59,177,100,155,134,90, +240,88,51,199,228,37,116,163,110,62,194,222,123,129,60,107,181,163,137,15,110,209,176,151,254,54,13,99,123,90, +251,204,39,190,216,212,186,250,46,28,58,71,250,249,161,230,54,159,176,35,122,206,217,247,83,250,101,204,140,245, +6,100,8,82,123,202,57,46,237,142,12,239,63,67,192,213,102,200,115,238,238,23,169,125,65,114,252,107,124,19, +218,183,132,113,84,251,119,42,226,95,115,88,136,96,167,52,230,167,55,104,19,34,139,25,184,75,68,119,166,122, +233,201,173,24,152,133,110,166,48,101,63,119,191,202,48,101,200,116,71,211,246,236,197,226,189,182,115,221,143,27, +233,122,26,107,217,233,84,92,4,97,76,175,45,123,159,233,15,181,198,39,192,67,138,219,66,106,204,160,93,134, +183,95,127,115,218,203,80,96,134,226,142,26,106,204,80,31,174,193,26,205,53,221,40,4,130,99,252,198,247,161, +208,128,238,23,81,99,210,124,88,11,235,117,38,43,96,88,228,115,196,212,164,77,29,69,88,186,14,139,150,254, +129,35,156,81,89,25,28,13,84,233,8,61,10,43,107,89,67,196,123,171,107,210,37,192,35,82,254,37,173,223, +158,149,143,164,111,229,163,82,104,91,41,52,58,231,135,128,53,35,250,133,143,109,58,48,102,42,43,252,69,109, +43,72,40,2,75,165,178,194,223,192,43,10,210,126,250,89,97,116,37,11,85,219,128,142,11,158,97,45,205,90, +105,193,243,92,72,19,13,213,183,109,168,18,67,27,207,222,6,125,184,140,34,207,120,47,244,0,60,70,219,107, +99,15,96,99,18,221,33,251,224,31,18,77,227,6,147,245,15,73,30,252,178,49,220,97,163,176,164,93,204,165, +24,42,176,5,92,51,183,93,60,10,89,36,183,182,82,180,157,96,191,240,77,47,48,155,219,118,69,130,119,159, +11,183,168,220,15,100,104,111,247,183,168,211,171,48,85,158,97,210,185,247,217,16,226,151,33,171,189,38,214,122, +73,236,229,170,103,25,31,61,27,213,9,26,22,194,235,102,106,210,186,221,232,93,221,77,255,214,214,61,76,160, +232,11,2,223,131,25,20,194,223,152,247,95,130,191,239,38,165,161,124,64,171,172,43,93,212,56,100,55,162,122, +40,210,176,78,251,217,251,215,237,224,174,96,148,105,92,24,236,10,126,130,77,189,116,52,14,138,5,23,132,145, +29,216,9,184,239,124,223,95,114,170,235,201,92,67,31,225,100,134,73,97,247,251,209,140,159,184,167,166,82,214, +226,224,46,60,22,225,204,61,223,100,249,156,14,88,184,76,54,75,144,136,251,62,232,67,88,110,202,34,56,79, +125,13,14,194,57,43,187,5,74,228,155,2,31,75,58,179,249,12,248,19,190,4,218,43,240,33,234,141,87,78, +232,116,251,133,115,165,246,38,196,233,92,158,244,96,42,247,255,96,5,149,35,134,81,119,231,136,158,79,0,132, +193,41,42,55,85,106,200,7,140,53,228,96,48,97,127,79,237,220,135,35,24,220,230,66,162,162,238,225,89,233, +86,192,35,126,149,53,110,0,11,252,12,198,242,170,23,118,7,187,229,201,99,175,181,29,120,67,88,160,68,22, +215,208,150,143,180,98,90,130,140,78,18,149,21,83,18,238,118,74,225,45,175,30,54,98,31,41,213,125,124,180, +24,68,69,155,46,38,92,76,92,57,243,238,244,177,116,217,194,97,116,146,90,154,129,97,8,203,137,1,128,29, +132,32,253,130,3,153,184,130,186,87,116,112,42,3,138,171,3,110,3,106,173,67,168,249,252,48,217,33,113,214, +5,206,158,30,236,146,130,65,9,177,79,62,237,147,96,67,180,212,32,69,118,220,14,64,208,225,121,30,158,173, +11,124,241,249,182,44,175,106,196,205,130,203,186,40,155,108,177,125,103,45,21,70,141,28,64,214,187,173,0,175, +147,107,221,3,165,84,117,208,218,168,159,103,36,218,65,72,187,179,67,188,187,50,206,194,117,245,225,233,80,169, +126,30,44,22,22,96,83,190,166,250,187,235,234,101,225,30,96,136,128,201,11,3,95,112,254,247,151,232,142,204, +60,171,206,98,51,230,34,232,20,48,147,247,165,46,122,214,240,6,92,15,22,227,177,40,141,249,7,89,90,132, +74,253,45,210,71,238,21,80,158,149,246,233,89,22,178,177,215,141,176,115,241,153,163,226,188,135,7,117,247,5, +210,175,240,174,220,92,229,240,125,19,226,164,33,70,6,51,199,111,148,123,247,165,125,158,244,18,2,198,207,34, +70,121,49,1,50,208,99,156,62,17,11,220,110,142,172,38,162,3,111,235,135,129,162,66,253,12,93,7,21,79, +81,80,197,230,237,179,14,7,248,35,142,20,108,73,221,205,251,94,124,195,23,90,132,28,181,37,194,41,93,55, +78,188,17,34,150,0,64,116,205,106,60,27,85,4,190,16,41,227,100,33,143,187,168,167,152,38,170,161,215,197, +89,39,229,113,17,111,30,71,230,205,96,49,254,243,179,196,120,6,51,49,17,23,56,250,42,70,239,38,121,15, +66,78,208,166,178,121,217,23,19,86,37,158,0,251,187,177,47,247,114,64,246,10,25,222,168,144,239,109,211,45, +30,45,198,243,71,115,124,0,91,23,252,96,105,63,146,26,190,96,128,81,127,220,227,63,139,199,137,156,156,72, +236,87,39,107,254,56,165,248,73,39,126,206,241,162,163,237,88,75,110,178,148,21,38,14,44,88,213,222,133,217, +12,31,149,123,54,54,163,30,251,197,57,137,51,35,90,226,183,60,169,110,29,59,221,154,227,150,184,221,177,246, +166,112,44,201,82,144,52,77,109,165,131,202,214,146,48,48,1,123,251,90,38,67,200,185,177,66,229,223,111,97, +113,107,169,187,171,137,89,18,184,134,135,239,96,119,185,110,52,250,152,207,241,202,61,112,223,82,5,156,83,22, +30,95,220,59,204,142,94,91,100,85,141,54,206,242,4,126,150,210,30,151,120,33,105,187,196,243,118,248,112,225, +193,113,91,246,209,19,185,86,23,77,180,191,240,66,174,232,129,181,172,163,181,144,151,54,132,91,106,109,199,205, +172,119,168,206,10,24,193,219,252,229,163,5,209,51,171,71,115,39,18,207,195,230,253,214,27,63,239,237,139,241, +70,176,114,121,191,238,21,215,125,25,214,109,162,194,170,195,69,160,100,91,177,157,14,134,171,142,253,128,214,228, +46,209,37,52,101,88,38,197,28,120,44,150,48,197,221,209,71,180,60,42,169,9,234,132,180,172,69,188,122,81, +100,252,36,68,185,232,67,196,157,218,237,111,152,179,215,34,197,118,235,71,100,173,23,67,150,88,218,253,241,118, +159,225,233,184,236,172,12,86,201,194,95,85,43,217,206,84,210,35,255,115,181,191,97,30,42,203,132,122,72,47, +201,180,115,145,253,105,45,252,225,166,25,188,38,194,171,160,255,92,138,46,94,60,185,176,8,200,133,201,96,246, +189,211,2,208,221,92,109,115,232,221,6,94,66,243,89,58,139,230,170,126,68,51,141,167,225,145,246,107,49,135, +96,112,197,212,144,70,8,115,137,195,48,196,7,62,17,81,180,141,213,197,124,52,155,187,197,139,35,254,132,119, +94,204,74,223,3,72,51,3,227,104,62,89,142,43,241,8,115,80,254,96,95,160,65,218,99,49,128,78,251,242, +155,201,226,142,242,11,44,111,79,22,206,134,223,71,198,105,126,195,129,105,56,202,8,89,234,62,85,248,193,194, +85,233,171,128,44,190,111,140,252,195,104,134,80,125,232,100,219,246,143,216,174,179,117,161,242,253,243,228,71,218, +159,67,185,159,89,116,227,186,182,142,122,29,221,207,218,43,221,183,100,180,183,8,67,217,251,117,244,77,38,237, +207,204,96,1,33,90,38,198,135,239,218,240,174,236,30,44,58,222,198,138,95,227,207,178,238,158,101,102,231,122, +9,16,243,105,219,135,80,99,79,47,84,254,138,244,234,123,76,83,190,17,41,185,61,192,129,160,85,190,139,158, +160,89,27,164,16,6,132,159,154,190,240,211,79,17,154,192,178,112,201,112,82,66,155,20,178,152,76,164,158,76, +200,233,195,93,4,64,219,191,51,153,209,209,101,189,24,52,249,78,226,37,228,146,98,29,33,114,142,221,166,17, +24,33,128,151,53,113,203,251,205,168,166,235,156,42,232,89,33,93,21,194,91,174,27,200,231,69,104,118,105,131, +239,21,123,175,126,238,173,134,12,180,194,31,47,116,5,255,140,201,85,43,57,32,87,242,82,94,200,91,121,101, +214,194,218,214,212,104,84,19,165,5,16,248,157,165,180,8,114,67,61,39,207,17,56,223,175,74,239,54,34,21, +178,48,168,148,90,171,205,81,221,160,155,97,185,82,53,250,37,197,63,160,30,133,188,70,148,17,254,148,26,187, +163,151,144,207,139,127,200,11,117,11,157,123,136,82,136,32,208,250,13,252,136,11,149,103,48,47,43,235,131,127, +133,18,50,23,104,212,230,86,93,78,205,99,63,244,209,100,196,206,91,8,55,167,57,52,210,143,227,241,82,92, +169,57,136,61,98,189,87,182,94,252,137,6,107,190,130,154,199,234,82,76,201,250,236,133,144,149,253,186,237,104, +49,92,200,164,99,193,235,22,213,7,170,101,84,123,189,171,27,229,237,89,66,230,107,85,113,48,23,114,171,78, +213,179,136,48,224,242,236,20,38,135,241,224,202,6,89,45,143,144,195,109,132,218,17,137,249,66,255,178,22,73, +196,208,141,32,60,17,3,215,66,154,90,235,184,164,10,145,227,223,238,57,117,105,194,23,194,190,56,69,71,120, +38,121,139,25,76,2,100,48,25,177,116,219,191,174,195,179,227,207,133,107,228,233,49,128,159,103,94,197,100,134, +156,206,216,100,220,107,94,159,53,78,3,37,108,229,165,241,204,108,26,31,224,12,34,136,80,39,147,70,176,104, +82,159,197,56,110,30,133,232,151,235,218,155,36,26,224,32,206,126,9,9,82,169,229,177,136,109,119,168,27,60, +23,193,184,163,102,210,111,84,60,30,106,115,160,255,179,147,9,58,168,234,184,238,229,69,24,158,39,23,139,5, +108,140,16,109,24,230,139,195,248,200,196,151,59,186,50,194,117,129,199,190,167,199,51,29,55,207,240,243,217,241, +12,73,49,207,86,27,94,87,231,218,182,121,166,160,84,243,84,7,4,13,171,97,178,241,227,130,159,226,241,33, +146,3,42,95,238,191,184,203,198,188,41,134,3,15,10,119,194,42,89,6,107,212,173,5,227,161,26,47,126,60, +204,107,33,62,139,62,68,56,21,138,149,102,152,130,66,176,94,251,120,166,165,48,190,60,64,167,85,112,193,245, +52,45,240,200,39,170,156,149,29,238,200,184,66,171,84,24,221,231,21,84,222,36,254,16,3,57,127,84,60,75, +30,213,179,4,88,33,249,227,58,206,31,213,79,147,71,197,44,135,112,242,184,110,67,34,121,120,42,136,76,229, +70,30,186,135,140,217,195,135,77,124,247,243,53,91,120,230,11,235,155,240,149,46,220,68,76,74,244,157,143,134, +157,145,59,75,233,123,92,167,106,85,141,243,229,132,203,243,33,58,95,46,247,25,102,99,32,235,79,98,0,158, +11,75,147,203,57,94,138,27,85,222,35,248,129,183,152,215,55,154,109,66,47,82,168,150,163,214,128,111,95,122, +135,36,31,249,180,2,232,40,228,71,185,22,173,185,118,205,245,42,193,151,181,220,202,83,249,65,190,148,191,202, +87,242,189,225,73,56,76,13,110,186,203,40,224,207,227,21,17,4,39,11,249,65,93,76,86,242,87,204,102,16, +219,241,74,190,87,13,165,243,101,88,121,124,47,168,19,243,67,81,83,80,190,199,88,215,12,84,185,85,23,80, +215,7,229,114,142,23,221,218,16,9,231,186,24,37,190,14,201,183,133,60,53,189,123,105,170,198,2,216,189,87, +170,177,233,221,250,48,42,172,208,100,135,162,182,32,148,130,120,91,14,123,119,141,189,131,234,57,107,208,61,109, +209,89,131,169,84,94,234,207,214,109,167,105,204,35,5,17,196,163,175,4,151,253,45,170,152,92,253,216,17,56, +172,140,192,225,107,85,157,125,60,159,98,77,1,136,177,18,152,144,176,15,140,95,11,209,250,41,166,102,253,236, +110,199,139,182,211,239,237,225,126,155,97,54,44,175,248,127,220,105,152,225,9,206,240,245,100,17,174,2,181,79, +204,221,183,32,223,79,199,170,171,52,35,209,146,106,64,81,157,72,47,202,191,124,252,86,88,193,22,64,236,166, +183,79,151,83,180,253,217,240,8,238,62,130,183,66,192,128,62,6,90,122,127,133,144,85,203,219,54,80,214,235, +42,153,167,231,235,6,146,251,122,121,114,137,177,172,251,39,63,184,160,117,180,120,227,130,207,187,117,213,97,124, +88,223,244,74,149,246,210,145,183,136,4,94,133,166,114,110,148,57,249,87,242,181,144,249,236,26,112,188,151,234, +149,186,137,183,234,131,250,85,189,87,55,114,78,214,187,155,219,147,248,90,54,219,147,120,43,155,219,39,241,41, +124,63,137,63,72,136,126,41,33,246,87,9,145,175,36,196,189,39,83,67,175,165,25,121,252,215,80,17,113,219, +236,43,34,94,55,129,170,227,178,9,84,27,63,96,32,28,82,124,211,141,225,58,234,166,13,175,224,190,108,222, +114,64,54,15,208,242,46,173,114,63,220,103,80,207,48,190,38,17,149,178,85,133,172,6,1,124,18,0,120,73, +140,174,60,212,50,76,153,235,181,116,186,146,139,86,149,114,142,208,191,176,124,126,185,81,243,241,18,32,252,98, +54,89,198,27,128,229,147,3,247,255,37,220,21,119,1,113,249,86,141,86,217,124,158,107,195,19,171,25,158,119, +193,247,90,158,122,196,224,103,244,39,103,230,200,244,216,248,175,243,103,188,14,33,248,214,67,229,223,89,5,129, +109,62,106,92,244,151,129,162,115,49,61,197,13,15,199,206,68,202,107,8,221,182,92,159,7,219,127,188,194,62, +224,174,67,0,184,85,3,96,123,19,192,191,250,0,252,171,9,254,213,8,255,182,234,115,161,223,120,211,222,55, +171,251,80,187,223,233,107,181,15,179,39,191,187,211,215,159,221,233,246,244,190,201,247,19,223,82,175,81,163,42, +224,6,190,181,187,52,206,3,38,224,91,191,237,152,134,106,134,209,89,3,211,47,212,177,188,245,130,138,23,79, +111,129,220,190,16,187,43,149,128,140,190,188,81,87,246,48,77,63,11,218,95,64,163,31,6,217,198,144,52,166, +167,96,130,233,47,239,102,68,92,32,174,243,50,228,44,188,82,223,0,149,60,187,97,197,19,234,211,107,245,10, +112,184,240,90,129,207,158,18,181,189,87,122,186,214,211,106,22,93,171,15,146,207,251,44,197,47,84,112,6,54, +219,10,249,231,179,247,106,242,234,209,175,227,95,81,125,40,216,46,24,175,155,30,114,15,27,230,53,230,141,135, +18,185,138,126,253,3,85,31,168,57,30,72,193,222,201,5,44,252,251,71,202,40,150,70,112,79,1,234,18,157, +76,94,137,71,80,183,101,211,45,141,187,182,143,71,123,106,219,110,79,195,149,154,55,144,161,167,139,43,240,114, +213,174,177,26,183,68,141,17,150,204,193,176,169,254,57,96,70,239,39,31,44,222,244,79,117,141,159,120,150,166, +245,77,6,132,70,244,22,154,73,106,237,128,108,252,124,162,110,144,183,125,89,233,228,106,138,73,180,107,41,201, +38,180,84,250,148,74,211,4,197,255,156,168,186,87,218,156,88,74,161,194,203,134,148,25,254,105,68,21,159,59, +243,128,205,248,3,117,223,241,106,110,76,148,13,208,5,237,39,194,108,159,182,189,164,59,159,117,68,86,210,58, +225,189,145,200,45,139,95,242,205,94,251,93,135,87,123,237,247,27,222,234,184,5,233,126,126,47,249,128,199,167, +248,109,72,126,227,135,244,173,108,170,164,168,115,219,212,25,92,87,231,210,117,8,208,1,239,254,230,178,61,4, +242,104,97,253,181,220,208,181,220,99,55,227,190,56,112,111,178,13,22,58,31,172,170,72,211,109,173,150,240,198, +101,198,181,21,213,10,0,85,173,168,32,39,49,188,170,249,78,146,117,123,8,22,54,123,131,209,206,202,66,128, +51,20,14,83,168,25,121,40,219,238,96,101,53,12,10,17,33,25,151,50,87,85,135,140,55,3,76,37,51,124, +2,165,162,26,223,98,152,120,194,194,5,159,237,89,234,198,91,132,231,58,74,249,91,46,199,42,135,179,25,99, +156,169,192,70,225,249,13,171,157,36,189,106,49,246,206,122,39,92,175,237,130,164,234,48,0,181,107,70,2,194, +33,152,107,175,252,227,77,77,120,4,92,107,242,199,231,37,152,15,168,221,39,236,252,241,73,229,109,188,108,187, +168,43,170,219,69,230,158,31,120,4,176,59,197,155,176,235,203,148,203,222,211,129,219,120,120,90,120,23,0,16, +15,230,147,42,35,27,229,94,112,30,191,88,248,157,160,76,40,39,111,62,91,174,120,255,197,39,172,216,33,146, +182,254,174,80,189,253,118,85,59,163,166,134,249,193,62,235,163,240,205,161,241,18,26,61,175,246,0,36,108,11, +133,180,74,142,196,227,102,238,54,241,31,181,125,210,73,174,141,220,238,17,251,201,87,154,66,104,135,47,34,135, +74,152,3,221,108,149,149,99,113,190,113,68,33,227,69,135,158,110,16,225,191,95,4,170,175,216,90,135,124,164, +5,112,238,141,27,49,52,25,69,242,103,104,185,206,219,150,4,46,232,76,223,133,222,212,66,120,66,54,62,54, +115,139,28,178,59,187,205,12,178,230,86,214,106,64,248,157,24,162,221,88,202,121,136,15,39,156,3,10,86,76, +137,140,34,62,90,95,124,184,116,62,37,224,203,220,74,200,175,117,171,84,248,17,40,202,8,113,246,150,178,107, +71,133,48,214,46,17,210,148,209,178,71,169,187,154,60,193,169,56,11,71,65,30,163,196,240,46,105,150,166,109, +180,56,247,177,140,242,35,116,129,177,165,58,48,38,133,152,20,98,184,47,152,61,220,46,184,250,126,161,189,187, +141,186,239,190,137,229,131,106,180,6,168,143,122,246,133,192,91,83,4,246,203,160,7,39,114,11,63,219,147,86, +218,240,19,27,126,210,162,149,74,237,149,171,185,68,67,69,26,46,211,80,161,6,75,237,44,6,144,123,206,4, +29,155,220,179,46,66,186,63,239,241,38,246,185,0,62,71,24,141,86,192,61,75,211,159,104,178,127,139,39,219, +203,94,89,14,108,65,247,32,106,166,223,195,48,45,85,17,50,76,235,46,195,212,156,193,178,11,55,171,187,235, +60,14,15,141,76,212,30,215,193,170,254,219,247,204,97,1,245,28,89,50,141,244,224,180,20,147,18,112,254,52, +136,175,44,92,21,227,10,18,150,106,161,18,188,137,130,12,8,53,169,220,130,163,25,106,82,193,92,165,80,80, +106,119,90,116,112,90,58,83,33,117,120,102,56,205,46,187,238,236,122,205,187,222,176,217,120,199,203,133,224,90, +48,155,223,237,102,121,253,43,61,206,249,192,29,198,64,47,92,140,125,96,51,112,41,78,97,79,111,51,132,203, +108,61,37,124,73,239,66,37,142,26,170,175,11,143,156,99,181,208,246,74,232,27,59,180,188,98,108,184,34,234, +140,102,91,137,198,204,25,235,198,231,247,224,58,241,9,12,155,248,118,241,105,118,111,248,48,46,121,16,36,96, +231,35,232,50,166,206,47,160,21,143,157,79,191,192,123,107,41,143,229,66,166,248,48,12,83,118,13,83,102,215, +230,35,10,50,30,186,75,67,36,21,243,197,5,219,79,171,233,28,90,115,54,253,21,228,165,128,166,73,56,181, +82,57,6,216,101,70,162,10,139,66,147,189,140,210,19,200,72,17,117,5,69,2,29,122,8,161,114,62,28,165, +177,170,220,158,255,38,42,204,136,141,159,213,113,167,174,71,148,228,101,182,132,136,177,44,206,169,237,168,211,165, +79,157,46,253,210,235,210,47,188,46,253,188,85,75,226,181,230,82,203,68,224,204,54,210,86,15,211,123,44,75, +7,57,11,90,220,193,106,100,128,237,45,163,196,88,16,237,18,77,142,186,236,210,78,0,86,206,91,187,106,120, +150,6,16,8,218,232,125,68,73,218,88,190,226,57,130,33,47,199,240,134,240,17,124,122,69,75,30,97,238,147, +19,135,13,70,31,71,191,225,171,98,161,190,38,137,102,76,192,223,163,223,36,44,4,161,43,67,3,225,214,129, +181,160,181,119,164,111,226,102,232,127,70,75,252,140,107,64,19,56,115,84,163,15,154,24,147,139,48,249,254,57, +169,247,103,160,109,37,214,51,62,177,53,69,136,144,244,231,13,51,13,246,133,167,205,244,168,61,244,78,25,98, +91,140,177,223,231,166,189,240,172,235,241,8,105,204,215,47,71,50,116,229,21,184,240,210,7,112,138,68,105,196, +41,18,244,103,169,108,125,184,58,209,67,120,212,78,172,213,7,165,240,56,237,185,216,170,219,97,62,215,61,154, +22,119,221,171,141,215,252,5,128,161,45,192,104,81,189,209,26,210,236,239,183,67,29,56,22,193,185,167,26,163, +187,85,35,67,93,72,16,141,112,214,50,190,209,161,137,140,208,190,177,157,27,186,113,235,180,132,128,179,140,1, +128,17,182,146,118,138,98,25,94,40,142,191,154,86,26,245,187,140,58,93,155,213,200,74,132,122,2,169,23,202, +231,55,122,86,191,115,223,223,47,216,42,167,109,223,103,147,65,17,92,162,79,104,254,184,10,151,194,86,140,211, +29,212,231,100,222,138,233,102,25,105,132,154,180,169,184,6,45,2,139,205,60,28,89,90,219,32,149,242,227,31, +143,142,70,227,146,208,169,102,89,149,55,15,208,240,249,215,72,150,70,35,59,159,243,82,215,15,138,18,184,119, +128,140,60,0,52,238,193,104,236,201,149,18,12,11,63,192,107,218,88,191,109,228,2,237,212,85,48,227,157,137, +5,10,237,91,254,142,76,55,26,14,215,168,126,128,199,44,152,82,238,54,42,94,181,155,98,96,118,194,161,21, +118,104,117,48,180,105,129,61,131,134,217,2,118,113,142,214,192,76,244,183,224,153,17,38,142,146,48,132,169,189, +30,83,234,63,27,72,11,109,48,47,246,108,79,189,208,209,192,94,145,103,122,246,45,46,30,44,8,74,170,217, +111,244,148,201,134,127,207,5,25,62,109,100,225,227,201,2,48,244,0,39,179,23,107,179,89,155,135,101,5,153, +2,203,136,77,55,45,116,112,76,198,143,194,39,131,198,219,18,239,218,142,66,225,200,38,130,173,33,12,166,190, +46,215,145,241,207,158,157,59,175,74,133,56,250,181,204,10,155,167,82,40,93,25,150,74,84,69,165,114,248,226, +156,108,204,178,148,120,13,39,162,99,84,106,233,141,74,141,178,249,8,86,9,60,22,140,220,76,153,48,157,240, +79,203,33,27,213,222,156,129,117,11,241,141,142,78,27,57,34,215,221,245,72,178,50,50,185,246,230,108,183,144, +205,197,141,40,207,218,168,6,114,22,59,111,114,68,209,148,203,190,173,112,38,173,229,200,196,112,250,5,30,217, +249,123,179,121,171,12,125,190,245,187,26,214,211,237,221,57,218,106,143,142,142,142,88,70,77,227,82,141,220,81, +24,201,198,89,79,31,200,181,41,194,124,88,213,11,223,232,221,181,202,126,31,77,241,175,169,91,247,151,229,33, +216,130,164,100,121,127,57,158,116,83,236,3,78,201,253,165,120,242,12,43,199,143,177,15,75,46,240,236,237,15, +77,142,124,96,100,170,160,113,30,46,239,135,199,187,198,150,180,3,61,92,144,199,231,182,145,45,102,6,122,184, +20,111,14,178,78,230,214,252,238,229,220,20,119,46,40,213,65,99,189,191,130,112,216,92,154,23,246,222,194,60, +116,46,203,171,123,127,81,94,95,155,131,238,243,51,40,170,189,255,133,154,1,88,137,62,189,120,14,233,232,109, +221,165,93,139,41,36,151,112,67,251,24,8,43,165,194,94,34,234,148,205,103,212,175,91,157,70,141,49,115,16, +255,24,213,178,226,166,146,59,155,170,196,180,83,65,130,142,30,240,63,138,232,58,30,253,100,124,0,252,45,42, +206,156,37,222,113,125,46,209,132,162,144,40,254,24,193,47,166,146,57,94,78,108,135,154,38,11,127,214,206,247, +49,200,147,15,2,35,150,146,31,143,253,85,54,152,147,124,44,20,71,33,246,35,216,202,36,125,132,51,104,186, +213,31,163,166,11,144,164,27,72,216,101,15,239,248,207,209,127,142,155,241,127,142,30,100,22,239,72,30,184,45, +161,231,15,254,115,92,140,205,93,194,40,173,177,40,254,177,49,64,248,211,146,28,211,173,7,175,136,11,180,218, +4,200,118,107,213,191,189,198,19,98,204,52,239,175,33,11,62,9,5,5,236,167,189,218,95,250,235,53,106,252, +149,114,65,53,250,82,178,145,35,227,115,33,207,253,35,120,169,106,218,85,243,176,30,225,124,161,195,22,27,74, +150,149,234,180,82,74,59,179,225,139,16,41,137,107,228,228,152,55,250,189,18,163,26,185,250,119,118,120,83,248, +46,203,170,189,232,207,83,173,0,213,235,120,236,47,209,169,82,35,188,143,24,218,1,136,3,156,21,198,148,177, +70,198,187,163,40,204,70,250,27,250,224,145,149,48,254,41,241,176,165,232,153,194,56,148,20,236,40,144,62,208, +212,22,112,201,51,178,177,240,83,20,216,191,17,204,30,42,243,185,181,81,22,164,202,224,219,237,181,54,156,89, +226,106,133,21,242,86,230,40,90,185,78,205,135,119,4,47,73,56,201,31,26,116,211,183,68,171,41,102,53,117, +59,84,180,239,1,135,172,145,200,26,205,225,122,71,159,252,201,183,201,174,69,236,108,181,140,252,134,168,105,102, +31,234,217,217,121,124,177,180,240,11,90,104,135,187,196,56,117,119,54,141,252,90,209,153,211,90,25,165,96,124, +187,160,61,155,128,41,113,180,87,178,210,81,174,158,37,212,45,52,69,168,84,238,67,112,6,58,27,175,54,6, +159,14,109,75,163,242,66,169,248,120,27,226,223,171,158,249,99,169,59,146,48,31,27,106,148,136,3,111,14,11, +29,110,213,79,153,172,174,1,238,89,211,189,88,198,95,224,26,200,1,33,188,111,6,87,29,206,70,175,174,34, +172,203,95,64,80,193,180,97,29,141,82,40,235,16,151,26,43,69,187,111,119,249,178,99,157,244,33,108,128,12, +215,144,116,27,204,247,241,108,215,198,153,47,114,177,28,242,9,142,118,110,67,2,221,15,223,154,106,110,66,83, +205,124,145,25,47,229,185,74,144,138,74,21,116,70,131,133,48,227,200,155,253,66,213,36,123,96,103,35,78,152, +213,120,11,253,112,155,53,145,169,132,241,5,28,7,239,194,124,191,187,60,183,31,144,98,251,171,37,74,104,0, +196,25,48,41,184,87,106,239,14,158,168,172,247,150,147,80,193,134,44,228,217,104,116,46,119,222,20,60,26,188, +55,11,224,2,0,212,176,1,116,201,22,218,98,46,186,54,139,129,152,34,122,1,168,157,192,9,83,20,53,156, +130,209,130,146,133,93,101,228,228,0,42,16,6,116,24,64,69,57,110,115,187,12,221,112,100,60,44,4,226,214, +46,253,197,104,166,85,19,219,24,243,68,120,129,16,93,43,82,186,155,129,20,86,12,191,66,106,95,237,53,85, +27,84,215,204,184,194,152,43,242,37,110,160,4,187,37,161,39,223,108,255,201,151,116,116,179,224,193,217,27,142, +230,76,219,160,226,186,232,117,5,123,77,197,32,227,44,139,209,254,33,78,12,116,193,187,122,22,144,195,176,209, +78,27,228,7,53,229,155,242,6,173,6,212,58,10,150,236,106,217,93,178,127,54,222,73,227,142,204,218,238,218, +214,240,4,108,16,23,10,41,217,34,178,25,165,217,100,67,20,123,53,20,59,228,135,171,240,180,115,232,22,174, +56,75,172,55,170,223,162,156,17,36,76,44,115,125,164,13,118,243,143,215,246,58,123,96,250,246,192,89,77,74, +204,216,160,78,27,31,63,248,98,151,180,255,16,88,89,126,116,129,62,175,182,253,250,110,146,170,128,234,62,21, +37,170,199,61,168,232,56,60,64,31,154,128,41,37,245,3,58,161,3,213,178,111,188,194,186,195,91,42,216,62, +41,25,173,13,166,109,90,130,206,158,194,63,192,91,148,21,12,79,93,31,96,118,88,141,210,180,149,185,92,64, +126,248,179,60,7,190,137,144,153,57,57,124,124,6,39,206,242,45,217,203,102,170,146,240,248,20,198,239,14,246, +45,130,213,206,233,228,5,189,12,151,102,225,151,102,206,45,108,212,22,130,50,69,85,140,228,108,195,188,87,168, +170,60,219,192,223,205,180,66,229,66,248,3,129,161,241,193,176,49,209,141,115,211,202,2,131,139,179,57,142,18, +199,217,149,161,31,28,102,133,251,3,106,202,229,217,183,78,48,51,183,155,87,82,132,153,178,42,240,139,80,14, +250,69,0,204,135,191,209,21,218,148,111,61,100,216,119,112,3,119,12,20,157,28,95,119,83,133,134,188,85,102, +14,74,198,43,165,252,167,193,4,50,178,74,168,232,131,98,125,125,223,45,15,214,167,176,45,243,37,164,25,146, +204,156,91,204,218,185,197,148,186,50,159,31,116,51,245,14,12,117,8,45,223,212,134,134,200,60,234,107,236,84, +162,150,22,36,89,183,148,90,64,69,232,195,210,104,81,105,106,230,147,246,102,237,25,207,250,75,99,253,219,21, +129,232,125,102,138,22,194,249,184,62,237,146,18,76,175,218,147,171,96,204,222,4,25,94,86,6,115,226,33,117, +152,222,85,39,13,137,255,7,240,242,214,160,43,28,175,159,23,214,126,228,146,77,94,220,39,7,242,97,82,91, +83,158,126,31,137,239,109,234,192,21,56,80,7,38,181,53,229,233,215,193,139,216,8,83,15,109,189,3,85,81, +106,91,7,57,251,21,82,60,245,139,54,236,129,250,40,213,217,167,244,231,33,204,101,241,203,52,215,137,157,231, +8,119,26,246,55,140,218,245,87,202,22,136,68,127,165,194,180,150,142,65,136,166,80,63,113,127,54,18,31,152, +206,206,254,193,32,238,139,93,211,254,67,2,74,114,206,101,79,157,123,185,160,18,169,195,106,254,129,165,142,240, +217,208,94,135,80,139,134,90,250,117,247,114,213,148,237,31,123,81,231,242,238,30,17,247,231,238,254,76,238,232, +4,49,132,184,7,97,42,134,195,116,110,124,31,225,11,60,114,122,57,37,220,178,211,78,95,138,118,98,203,118, +187,100,227,92,27,200,80,194,243,107,230,33,201,67,28,18,129,21,118,192,18,50,115,27,217,37,187,250,123,131, +68,75,11,199,183,32,92,240,33,212,101,30,94,60,228,42,136,139,143,111,25,117,219,195,94,187,246,84,29,218, +92,91,183,1,101,215,254,80,191,119,133,229,166,99,7,52,217,215,161,89,73,156,37,76,134,153,222,31,240,18, +95,51,241,61,29,231,2,225,211,146,147,22,234,217,39,141,215,169,92,8,49,148,80,203,197,96,60,220,188,165, +185,121,15,164,127,123,32,190,41,48,161,245,56,199,105,85,37,219,163,69,85,174,34,143,92,165,68,150,88,47, +185,169,165,53,6,238,97,3,219,151,73,109,94,190,42,51,235,26,175,245,180,69,196,177,59,237,253,25,39,71, +13,93,181,229,179,6,71,166,237,200,2,244,159,98,118,84,4,134,215,20,231,45,129,135,183,201,74,207,249,237, +212,114,71,20,146,32,158,8,220,125,81,67,135,244,28,9,14,185,115,96,37,174,208,55,244,187,74,47,178,91, +93,163,1,157,143,117,52,0,121,36,110,37,146,174,168,112,217,95,44,163,74,162,95,131,242,136,42,70,211,228, +133,122,133,143,49,179,34,18,113,193,19,140,245,245,104,36,220,73,40,245,0,181,105,168,168,128,41,107,61,19, +39,69,38,142,22,6,215,203,225,15,63,238,181,253,90,112,168,102,160,222,78,16,15,173,60,60,152,128,87,101, +4,64,160,19,37,84,101,175,92,100,172,150,1,125,15,149,116,220,102,100,238,248,21,40,65,200,71,46,227,135, +51,255,218,169,237,99,19,201,37,210,193,245,148,99,9,21,148,202,247,248,166,48,231,191,179,36,58,228,107,36, +93,34,4,112,210,52,223,204,97,107,141,150,248,78,56,130,125,77,71,191,150,37,138,173,181,164,199,96,60,238, +252,102,220,32,250,71,220,239,111,10,244,62,170,171,102,139,27,168,134,228,35,88,200,77,170,157,3,111,120,193, +135,5,69,191,62,66,62,60,9,28,144,189,8,137,158,93,86,127,240,132,174,150,128,201,50,165,91,192,34,32, +122,21,176,232,234,14,139,174,84,198,199,116,165,10,252,73,84,84,1,10,140,222,221,206,44,111,14,61,55,65, +31,18,32,198,62,44,225,7,126,193,101,212,55,248,197,28,57,230,209,25,103,142,75,53,250,242,232,191,142,78, +156,59,203,151,75,117,102,168,73,233,8,73,73,162,217,78,102,122,196,158,72,70,231,126,140,95,215,93,170,113, +144,40,133,208,203,37,179,86,50,102,173,96,142,144,200,126,223,173,204,197,35,211,137,35,53,16,241,80,174,128, +159,25,242,216,39,5,252,137,49,118,130,113,193,150,124,94,119,145,112,150,206,246,70,119,220,245,62,237,27,23, +181,140,217,247,186,152,227,134,145,127,179,254,125,203,226,69,137,22,96,26,45,161,169,14,86,254,253,242,243,27, +227,218,96,99,125,130,125,93,239,213,150,133,56,62,122,12,134,201,50,30,131,113,130,71,214,180,10,80,228,202, +121,178,63,242,111,96,207,183,175,231,80,56,206,96,119,120,131,52,153,241,69,37,36,198,146,11,109,27,75,1, +143,217,111,50,52,80,94,84,120,22,120,60,216,159,62,73,77,158,109,55,25,51,194,181,122,166,3,191,222,194, +62,39,7,190,76,223,236,61,194,135,228,215,254,254,47,252,254,31,215,214,87,158,10,216,214,102,247,211,131,63, +126,203,72,63,67,55,237,207,26,129,131,59,43,199,232,179,90,180,33,152,10,248,89,116,32,152,128,197,221,184, +42,55,181,46,55,205,200,178,240,138,89,19,243,3,118,22,146,20,3,56,8,209,22,8,234,78,151,198,141,25, +206,155,97,91,20,248,74,128,3,24,16,216,176,254,156,241,221,36,201,225,112,205,183,40,235,0,189,56,122,96, +4,143,193,131,117,179,124,240,250,229,131,255,28,141,75,192,181,198,163,255,124,176,218,64,171,151,250,193,220,62, +28,0,223,192,62,131,0,254,173,31,216,5,192,31,204,82,105,168,106,142,111,47,44,113,219,191,101,32,98,255, +22,30,48,174,107,80,117,38,110,112,156,81,225,130,96,137,31,188,92,35,34,213,201,116,100,113,255,23,102,102, +162,66,176,200,121,55,87,207,175,124,45,129,18,172,215,176,49,140,175,110,20,72,64,217,107,218,90,50,85,192, +216,204,157,58,196,210,132,72,233,134,173,210,205,213,109,18,9,239,45,39,161,79,187,57,243,208,189,77,215,62, +95,74,36,133,35,116,42,10,7,253,177,157,15,34,108,22,146,213,243,14,95,86,154,40,114,142,169,27,16,136, +171,123,142,121,110,181,207,145,110,170,74,23,123,238,204,59,37,24,12,119,98,47,200,163,55,183,133,150,130,140, +131,246,110,182,156,220,181,35,255,193,83,79,107,24,42,148,126,195,137,157,50,181,145,141,251,174,63,28,98,80, +112,61,161,244,197,154,102,149,29,191,115,174,37,72,90,234,2,238,190,44,13,162,147,166,233,186,110,185,240,158, +189,95,102,181,241,194,25,118,170,239,218,133,10,205,203,247,198,101,188,186,76,16,149,53,145,68,126,46,208,148, +83,101,82,95,106,99,243,246,56,180,128,79,207,42,198,50,72,118,70,155,232,220,98,249,15,19,208,135,200,197, +174,203,19,28,189,74,178,28,142,94,83,62,176,7,234,129,21,163,199,147,247,159,205,3,218,211,15,168,159,15, +16,117,54,231,243,19,76,116,241,0,95,57,248,65,180,125,215,28,217,133,177,210,174,163,148,174,153,145,124,94, +11,217,79,93,211,181,49,146,223,47,133,244,143,129,89,146,27,67,55,221,41,133,171,35,152,6,203,8,8,54, +239,30,194,189,11,18,141,205,160,172,104,224,223,105,16,171,91,82,79,40,156,86,79,45,47,194,98,101,215,200, +16,188,255,137,25,220,121,229,172,140,235,89,241,184,142,13,116,61,200,220,184,147,183,209,97,109,220,197,214,184, +139,159,177,199,206,8,39,176,91,79,23,49,8,31,156,69,215,173,184,63,71,51,140,199,176,169,45,174,44,130, +221,205,61,239,157,113,170,236,18,176,36,115,108,25,6,15,33,38,65,243,150,81,226,251,156,213,81,0,234,24, +2,82,102,124,182,243,121,97,99,153,8,76,162,12,212,105,115,187,65,114,181,41,10,192,53,108,14,122,5,183, +89,158,91,91,233,85,114,227,12,249,54,110,51,232,54,246,57,169,178,150,3,225,189,25,206,136,172,85,216,113, +36,4,6,246,30,237,230,16,250,86,189,219,4,101,127,173,123,120,99,247,169,54,247,125,105,4,224,251,179,14, +180,69,191,236,30,240,53,2,113,254,190,152,141,236,64,224,113,198,158,176,209,212,39,170,106,192,228,107,69,31, +159,115,147,184,173,146,128,148,2,34,49,67,59,128,218,151,59,252,137,171,214,200,152,0,54,105,64,31,121,45, +144,149,5,210,195,176,128,225,100,148,11,138,170,12,170,139,218,82,186,168,55,21,201,250,124,155,92,235,215,47, +235,104,88,208,56,100,230,255,24,105,137,90,33,200,168,53,239,200,117,75,150,235,191,39,119,10,166,198,187,68, +217,169,58,199,76,162,80,221,193,17,11,79,124,85,50,129,198,34,243,188,129,126,230,5,242,205,137,134,60,59, +55,250,133,165,42,157,240,99,215,163,32,236,172,117,20,202,4,105,251,148,90,23,166,90,68,49,16,27,68,173, +82,251,117,235,84,148,25,76,38,114,206,218,26,233,44,32,142,226,229,140,53,210,137,132,154,35,122,137,185,170, +100,14,48,6,21,227,146,202,100,132,174,233,79,101,181,197,172,54,182,109,145,195,243,99,84,202,176,127,21,79, +20,191,247,66,95,115,236,235,18,222,14,236,75,12,92,112,166,37,49,133,8,215,57,39,178,3,136,241,215,117, +16,47,83,1,252,115,136,130,66,46,78,224,150,11,138,134,73,18,30,61,96,162,143,73,225,5,1,184,121,231, +66,108,181,0,9,7,72,117,88,244,82,44,20,134,167,129,241,231,185,130,231,122,22,156,91,2,170,111,240,132, +121,180,203,230,113,110,121,60,75,163,128,198,74,72,102,74,77,176,21,178,56,91,152,11,121,209,194,47,186,113, +75,144,96,194,137,170,37,109,133,29,16,197,68,13,224,147,158,73,44,204,44,86,77,224,37,198,30,15,201,51, +42,100,101,184,143,207,203,91,74,51,242,94,246,190,36,164,167,191,109,61,114,71,218,190,221,215,50,162,187,216, +170,41,97,166,71,136,72,69,200,117,128,222,214,150,30,158,148,246,23,70,248,76,123,9,176,90,105,148,84,0, +13,130,90,216,6,9,215,15,84,19,144,164,8,140,162,22,19,45,218,65,132,13,50,153,60,199,194,246,0,200, +236,145,81,167,0,162,222,52,142,44,145,11,43,227,247,67,81,233,133,174,116,145,250,10,24,69,224,81,199,141, +180,166,60,221,136,99,237,212,147,220,136,217,212,35,203,100,135,56,176,108,152,229,232,160,6,243,112,80,199,22, +41,124,24,179,173,93,116,216,140,119,76,71,31,228,132,226,150,161,240,201,208,162,177,107,10,67,132,154,228,59, +167,68,90,223,21,186,239,187,130,41,86,148,93,39,71,12,142,150,10,122,27,18,67,37,189,163,134,152,9,198, +24,3,113,248,5,176,204,254,194,137,69,207,6,7,167,160,16,242,80,107,66,218,42,84,34,43,255,76,171,202, +222,147,109,210,193,82,176,144,217,41,170,180,191,128,53,187,226,104,161,137,28,116,140,70,206,64,11,68,93,91, +29,26,103,188,137,122,225,84,141,176,135,129,84,171,8,3,132,163,26,190,88,63,35,42,154,92,185,43,36,4, +44,57,1,22,191,214,104,110,117,215,125,46,249,104,224,175,196,200,48,102,217,6,178,35,248,202,219,117,91,150, +7,106,31,187,126,89,219,42,197,0,64,147,67,45,254,199,127,116,179,165,162,237,14,203,0,65,242,201,5,35, +38,97,163,206,12,181,93,59,92,125,184,36,27,60,186,220,48,70,129,103,233,253,237,45,35,122,70,29,218,30, +90,132,83,109,170,139,68,75,222,85,40,104,11,246,219,26,196,81,77,158,145,247,76,215,67,31,232,229,79,51, +77,194,98,91,93,218,91,247,57,21,250,51,57,21,178,86,7,137,200,135,69,192,137,99,65,194,77,7,69,113, +66,136,75,157,94,25,100,252,121,86,160,18,37,37,113,254,111,67,90,150,211,88,144,205,139,64,14,79,83,224, +100,15,209,185,85,57,71,175,214,94,172,18,95,33,172,184,229,158,154,39,214,118,24,212,77,15,183,230,86,142, +90,21,4,161,142,89,224,44,5,176,182,60,124,167,129,117,104,248,199,112,110,231,183,77,188,104,7,65,79,42, +36,204,58,122,199,100,14,240,194,114,128,167,139,238,24,120,87,205,59,174,77,198,11,66,233,191,135,61,176,200, +203,155,72,224,29,93,185,155,56,43,200,226,16,186,78,71,135,139,214,210,42,69,206,208,158,106,184,104,228,147, +177,50,238,50,17,207,74,17,207,226,77,223,93,97,26,8,18,142,135,169,177,222,10,182,162,195,16,242,119,238, +111,35,9,162,93,243,91,22,45,222,17,215,38,78,164,231,215,196,57,221,164,185,147,101,199,216,111,141,145,125, +88,34,36,12,226,132,239,214,206,118,196,231,13,163,245,92,71,137,37,33,100,7,185,111,195,125,238,1,5,107, +178,88,84,201,222,122,140,14,53,60,39,67,148,1,37,13,98,250,237,254,9,58,140,255,211,131,104,23,83,239, +241,172,208,171,58,231,107,142,204,196,212,128,237,62,252,193,190,17,0,155,230,225,65,158,22,220,157,77,64,163, +51,117,181,41,246,105,238,48,70,180,131,167,157,113,162,101,24,29,55,244,50,172,189,181,160,31,138,12,105,203, +151,158,203,20,9,22,75,181,149,0,94,181,44,231,113,33,141,224,108,92,203,180,220,192,78,40,91,124,226,243, +12,239,194,200,22,118,213,64,192,73,121,25,151,211,55,40,46,108,112,215,118,176,205,254,196,135,92,47,50,125, +15,179,231,144,55,7,111,134,248,99,4,132,238,198,123,1,135,227,117,34,188,14,233,173,10,30,33,96,12,165, +37,192,8,111,79,198,112,46,198,149,195,100,79,72,77,13,34,133,1,229,69,116,236,229,97,75,117,2,170,181, +122,90,2,8,202,22,102,229,107,89,68,165,16,174,207,116,91,6,15,214,181,109,14,241,74,82,120,195,186,57, +50,114,211,95,158,157,156,211,10,140,203,179,39,231,118,21,240,251,203,115,180,135,217,5,32,44,135,62,12,105, +41,27,64,133,187,0,122,213,208,37,232,57,68,251,28,4,217,183,105,206,196,166,44,156,67,146,167,10,8,60, +77,5,32,48,237,243,164,127,140,60,191,89,214,72,165,163,116,112,64,37,6,36,44,106,134,122,138,9,243,113, +32,234,1,55,196,87,80,35,136,99,224,192,16,230,208,205,201,248,62,209,63,59,72,1,88,168,202,246,14,200, +74,115,40,218,30,56,190,111,242,57,227,61,151,235,67,92,139,142,222,141,44,238,184,251,180,161,200,180,184,31, +133,10,230,107,250,47,213,222,29,112,164,229,43,220,113,179,38,114,152,173,193,146,99,221,138,24,118,199,193,9, +60,48,15,173,104,123,83,186,207,151,235,15,176,193,179,136,71,37,137,11,43,165,13,243,105,170,211,189,105,157, +222,187,46,174,59,181,93,1,84,135,14,103,143,186,134,111,104,161,154,9,228,60,188,85,246,106,22,173,187,246, +118,135,251,67,47,188,253,67,202,221,122,215,160,220,74,200,254,244,172,180,135,61,254,40,220,40,16,99,224,135, +13,199,222,58,68,36,36,60,71,239,66,150,134,32,203,18,228,143,4,97,240,48,127,213,221,52,125,150,123,171, +134,243,79,195,104,67,93,203,225,140,134,137,211,82,187,93,145,58,15,83,2,56,196,225,225,185,196,42,239,1, +119,12,197,60,96,32,122,27,33,214,83,207,50,48,30,32,126,67,56,54,30,55,2,67,118,26,187,224,79,4, +180,58,38,51,120,16,65,101,247,212,112,120,67,65,110,132,60,108,28,130,106,31,54,36,209,227,185,200,194,153, +135,112,174,46,107,133,110,26,185,83,245,211,18,153,59,254,110,215,248,152,109,110,97,38,161,97,67,21,68,16, +138,150,21,247,238,54,86,209,125,2,25,238,59,224,133,109,119,190,62,19,154,126,246,10,19,142,113,119,87,3, +192,232,141,197,76,245,51,88,244,201,68,139,189,101,53,254,128,238,133,117,212,203,238,8,247,73,208,230,214,112, +231,46,210,60,91,203,26,169,194,57,81,136,178,236,95,178,21,129,189,198,129,61,75,200,252,110,168,103,167,175, +98,224,98,77,41,105,105,109,141,90,159,225,86,127,232,56,46,77,104,98,35,201,48,31,185,19,55,89,66,147, +26,165,141,31,83,186,177,187,103,220,140,115,101,24,152,152,40,103,246,207,57,32,15,42,35,184,82,82,202,216, +101,193,11,58,132,206,4,207,172,85,35,45,100,245,251,64,52,79,67,168,185,93,159,90,226,7,52,85,191,131, +43,133,149,23,153,155,246,50,61,194,187,198,104,181,210,22,39,137,152,82,169,145,147,239,24,205,74,66,164,76, +105,116,50,180,119,139,237,14,227,175,232,47,184,232,49,120,89,156,52,224,78,34,45,75,204,73,92,13,77,146, +46,212,49,107,89,67,89,33,68,4,177,18,179,66,95,36,21,177,145,126,70,109,216,210,17,246,251,214,106,58, +216,192,54,12,24,46,92,140,242,67,204,144,227,203,216,117,41,214,242,98,157,84,181,158,99,163,4,153,64,157, +170,149,4,78,88,224,149,89,38,187,207,114,183,242,182,137,76,39,130,123,204,114,238,45,238,56,106,237,154,134, +167,29,219,216,20,97,11,247,3,6,2,6,109,159,121,120,239,226,25,58,198,107,163,78,239,70,103,122,251,168, +56,178,43,96,164,214,74,184,12,97,55,61,116,145,241,67,77,95,248,156,28,118,43,3,122,98,251,57,216,211, +148,27,120,168,219,166,252,244,201,14,59,172,68,236,246,101,36,112,92,234,225,112,60,78,246,126,29,52,252,225, +34,14,237,243,69,250,10,223,197,108,132,166,158,225,69,10,202,234,145,44,15,97,131,149,42,3,176,192,226,163, +44,51,95,71,44,42,58,189,108,140,9,177,210,172,22,156,97,158,138,66,118,164,19,28,206,116,96,146,145,41, +236,242,150,114,71,247,100,92,180,162,83,77,130,42,173,33,158,108,212,234,234,216,246,7,54,41,142,204,174,153, +41,54,60,39,112,169,181,56,19,247,103,196,251,116,159,27,191,143,35,172,180,223,170,120,132,131,233,235,134,152, +185,31,9,25,190,158,132,53,180,23,248,118,239,208,71,169,61,46,100,227,229,59,199,202,113,239,251,234,248,174, +215,42,68,153,12,174,100,114,12,143,167,165,216,59,113,106,167,206,238,152,92,246,65,223,153,151,36,230,150,109, +132,122,26,112,133,187,170,32,205,1,38,77,102,4,184,251,194,102,149,134,178,53,139,145,185,116,219,190,65,118, +189,64,24,134,134,9,207,57,117,159,103,62,144,7,186,227,110,115,165,224,104,163,89,185,63,255,233,245,42,249, +68,70,94,60,236,227,238,192,93,140,83,251,195,251,55,54,75,27,142,111,199,76,168,31,106,93,81,236,61,210, +38,152,251,61,199,185,50,113,79,186,234,184,237,215,26,114,133,66,94,155,212,93,177,10,89,56,37,242,157,198, +231,82,83,158,152,107,118,223,145,161,94,52,41,85,181,86,231,156,30,102,157,207,166,159,85,37,221,247,47,42, +145,3,252,205,82,160,44,65,56,82,98,243,73,100,41,69,165,33,236,134,199,186,59,200,250,163,139,108,48,205, +232,225,241,28,12,231,26,158,141,92,166,119,205,6,38,203,6,31,205,83,152,13,206,142,49,168,22,76,103,115, +184,32,239,61,204,45,90,89,114,113,204,193,2,188,129,152,17,166,147,187,169,192,176,47,150,168,35,39,168,130, +210,2,253,253,32,195,58,132,44,188,120,73,105,66,115,109,139,86,80,121,101,43,220,19,216,171,59,133,250,39, +219,147,162,199,242,24,235,12,186,211,74,180,187,118,74,117,133,130,75,98,150,192,238,69,158,117,120,244,153,101, +29,110,212,240,97,139,23,232,240,244,106,230,104,247,197,33,127,220,223,37,127,172,145,123,5,44,219,62,227,126, +232,50,214,104,54,223,182,52,114,22,7,209,179,63,57,79,85,202,153,203,66,117,243,3,183,53,240,93,59,23, +162,144,225,221,125,54,186,24,141,235,241,136,138,248,238,140,206,241,53,205,186,13,101,81,134,228,105,14,247,68, +34,118,104,67,44,57,103,13,22,184,195,134,218,174,186,13,7,205,78,225,125,52,61,131,118,59,13,86,78,69, +77,118,75,186,119,103,97,156,89,158,2,190,239,153,224,125,186,55,49,169,200,100,111,235,126,222,254,181,28,230, +70,236,223,178,166,187,108,182,146,112,236,170,21,129,80,206,208,104,75,97,144,208,100,95,206,251,109,249,128,234, +124,176,64,147,152,15,146,230,129,169,21,76,243,149,14,25,237,55,75,83,17,219,219,26,132,148,184,35,0,181, +30,214,104,136,151,95,50,88,6,184,232,139,0,243,221,55,248,84,132,85,136,182,123,169,117,68,228,31,116,223, +53,3,35,52,210,230,107,247,107,237,111,228,14,68,95,98,62,15,212,114,71,91,45,65,123,39,53,86,73,80, +122,119,217,71,225,22,157,136,255,248,15,84,41,161,20,218,26,6,41,141,180,108,112,211,162,240,126,105,85,133, +171,238,123,217,222,169,171,100,141,228,165,193,249,100,194,153,109,228,161,66,9,23,66,252,47,188,197,186,228,192, +206,36,197,141,172,52,154,236,237,179,75,101,230,45,140,199,127,247,246,206,92,156,236,63,115,10,188,78,42,20, +125,235,93,145,196,156,235,70,6,250,79,205,81,145,224,6,57,178,66,98,119,178,124,204,190,25,33,21,61,252, +2,29,122,215,55,121,237,130,31,5,131,97,2,171,248,92,6,65,208,168,140,64,54,19,10,154,103,167,121,95, +74,209,84,209,238,55,47,118,221,119,85,243,242,65,243,17,40,109,106,182,37,190,15,73,240,21,173,64,68,38, +87,167,134,204,73,21,40,141,52,253,35,5,121,114,49,45,248,232,245,206,218,223,34,192,115,10,179,89,228,89, +35,237,34,58,145,76,80,88,176,233,47,114,48,168,186,151,238,208,145,165,194,3,158,120,35,42,17,88,207,223, +63,237,201,161,131,141,69,241,104,239,65,131,84,46,219,193,145,19,207,5,55,198,190,70,12,237,128,51,75,101, +23,20,124,160,121,75,236,31,241,142,93,165,187,152,61,165,61,72,136,221,181,45,185,102,170,13,182,241,99,244, +41,59,202,32,2,119,80,45,65,41,41,27,150,176,0,6,123,3,248,140,19,225,153,235,69,86,104,210,220,203, +116,13,213,200,157,179,75,25,239,116,177,89,233,202,156,192,231,141,52,170,76,241,183,173,228,134,134,115,108,178, +86,178,1,212,225,44,255,108,90,89,145,101,183,225,28,31,33,7,57,39,31,206,240,110,217,74,152,44,115,142, +134,115,20,149,107,68,87,195,57,144,168,200,240,174,250,104,100,27,109,80,190,174,141,1,229,77,241,249,133,137, +130,12,203,183,129,138,99,93,117,52,186,118,117,131,135,191,248,100,76,151,175,81,134,250,59,227,3,63,174,37, +94,103,219,184,146,229,6,53,235,146,121,182,65,33,218,172,40,56,132,130,15,214,111,142,170,31,39,211,172,99, +10,63,59,74,170,212,146,20,178,152,164,82,143,83,33,243,103,245,44,194,220,121,144,158,99,26,230,65,192,44, +98,159,80,67,194,95,32,225,47,88,91,154,151,181,230,186,145,13,28,218,171,249,58,52,138,177,69,203,56,103, +35,211,241,15,198,178,149,180,129,175,139,57,124,226,16,40,158,2,24,127,30,212,246,126,192,138,18,182,64,167, +133,60,0,208,52,8,188,24,245,4,157,200,177,20,76,134,148,79,241,8,236,180,227,197,150,51,14,146,98,206, +32,75,46,196,163,226,241,19,6,188,77,148,203,99,25,100,72,133,104,29,170,225,199,19,39,81,125,228,131,66, +186,209,113,2,6,132,244,35,141,139,6,82,124,24,90,169,40,29,139,113,42,134,76,90,27,40,55,234,190,14, +30,56,196,208,227,236,17,59,88,110,4,236,148,194,197,212,104,159,51,172,160,44,92,5,94,161,217,237,174,96, +3,38,157,13,152,119,182,90,10,91,77,46,189,140,81,115,20,108,203,113,49,214,147,92,26,143,193,41,248,195, +78,49,98,156,199,86,246,121,174,156,143,152,141,170,39,9,66,67,94,214,215,54,255,164,136,143,193,171,220,18, +190,151,230,123,219,168,232,245,248,175,184,164,215,141,218,54,198,127,219,230,209,182,121,28,109,155,113,33,226,205, +116,174,162,205,228,26,87,157,0,224,218,247,238,232,248,248,68,110,30,45,39,250,241,207,226,241,82,174,48,239, +26,107,187,84,201,120,53,158,203,11,232,202,106,50,151,225,162,222,250,101,188,10,151,238,198,175,211,117,171,222, +227,229,182,144,75,121,49,185,68,167,195,203,201,173,60,133,191,87,242,131,186,28,223,62,222,202,151,234,98,114, +245,248,84,254,170,22,227,27,249,10,254,94,203,247,144,118,243,248,87,249,22,210,174,31,191,194,73,56,116,92, +151,232,44,83,200,171,103,199,126,150,96,11,156,202,151,134,97,48,181,57,95,31,221,202,215,71,91,121,5,209, +23,227,191,8,154,4,221,96,222,87,242,130,242,98,51,228,4,67,55,80,4,254,108,133,188,238,212,141,249,223, +14,213,125,141,53,203,183,99,51,175,239,94,139,22,171,227,126,46,36,14,101,33,113,96,11,35,59,117,211,171, +246,87,249,126,168,218,27,249,222,85,41,47,39,220,245,143,88,100,43,47,247,123,254,17,10,126,196,126,223,118, +26,192,220,31,134,26,184,197,106,229,7,209,118,160,86,160,232,220,7,47,187,5,224,32,47,178,42,69,179,90, +117,120,38,74,153,66,244,102,69,242,204,113,229,64,110,2,132,143,46,230,38,23,118,22,14,86,112,204,202,241, +15,254,185,46,87,112,16,158,214,64,3,230,34,51,206,52,34,232,113,253,54,121,27,85,104,220,49,81,229,184, +250,127,127,144,240,207,74,108,71,201,88,253,224,253,201,6,245,38,66,186,26,100,226,7,244,154,6,228,15,119, +33,183,253,129,132,135,187,146,225,136,19,60,220,249,129,195,61,169,240,96,167,48,220,0,30,140,43,235,234,208, +140,17,16,126,186,214,112,216,114,104,95,23,178,150,41,38,203,210,236,148,37,76,201,210,184,42,89,138,140,253, +178,152,218,14,21,207,101,137,21,12,151,13,87,247,213,114,31,218,133,40,173,220,133,110,118,42,105,67,127,41, +179,194,160,129,56,27,165,204,217,93,242,169,243,162,103,134,63,154,62,68,23,79,249,44,202,2,119,53,213,163, +39,210,134,177,26,149,128,45,60,227,197,97,36,226,110,190,126,174,75,160,57,242,17,190,23,250,245,0,62,21, +173,103,46,100,206,115,91,11,217,129,227,210,143,93,144,198,245,203,236,1,240,113,97,87,214,15,110,155,190,61, +167,122,3,152,93,143,41,218,213,147,13,55,122,39,133,183,81,55,26,26,218,139,12,55,73,24,207,59,170,31, +31,236,74,69,81,193,84,96,20,26,151,8,164,204,89,200,50,43,222,35,173,51,68,56,147,51,129,117,29,157, +129,38,146,4,107,124,198,196,240,46,161,163,48,207,96,72,116,156,175,202,168,70,15,83,13,156,24,221,66,166, +206,141,232,198,216,191,14,59,88,217,178,7,36,22,237,94,47,124,165,208,29,172,148,62,131,74,9,73,242,161, +78,165,118,4,243,158,62,217,58,73,129,192,133,123,109,13,74,77,11,153,79,18,241,76,253,0,214,35,240,185, +199,216,188,91,169,239,155,168,146,41,220,121,203,241,156,73,76,240,250,179,50,15,154,198,101,205,187,50,11,92, +63,34,118,129,248,68,8,62,106,63,19,101,56,19,125,252,180,61,48,255,242,223,50,3,13,44,79,73,254,186, +36,13,62,78,123,126,39,151,42,170,199,165,48,190,167,162,106,156,140,211,113,14,161,105,128,58,49,226,180,20, +143,22,6,117,98,196,9,99,218,182,41,203,188,201,214,239,72,218,175,111,184,188,63,113,236,243,166,7,110,116, +111,99,20,236,26,44,210,196,214,135,247,104,236,106,137,49,52,34,27,133,151,202,222,9,209,195,64,105,118,244, +229,151,241,192,233,41,158,253,48,51,35,91,228,37,64,129,226,241,15,2,178,65,65,22,76,10,102,255,169,139, +195,53,224,56,199,199,32,215,131,44,255,78,119,94,165,106,232,170,211,44,137,122,224,98,220,129,19,56,168,230, +200,121,12,210,17,175,66,46,30,85,146,87,0,67,98,0,36,61,83,63,195,213,8,13,138,182,227,254,240,168, +231,80,81,54,161,187,48,158,50,76,243,156,254,231,142,69,33,43,89,138,233,171,48,40,147,142,251,196,182,125, +153,161,130,230,8,110,162,209,244,165,247,107,161,118,193,98,196,236,87,51,104,46,30,253,63,139,197,98,180,119, +197,16,0,12,169,24,88,151,240,90,122,34,105,159,31,243,62,63,150,0,190,184,112,235,123,66,62,51,212,158, +103,201,81,47,98,20,152,22,41,137,16,69,251,32,246,82,122,145,32,16,113,211,5,33,211,87,217,244,34,240, +226,9,157,21,250,34,24,148,77,16,16,152,183,231,184,48,200,206,145,178,217,139,18,225,77,233,203,96,176,219, +45,142,17,50,188,105,125,17,19,150,77,24,194,172,225,22,241,153,105,3,133,161,0,179,248,141,209,44,143,84, +35,78,125,180,13,50,125,27,82,194,216,142,94,175,245,124,214,228,64,88,55,186,168,33,143,177,0,187,185,204, +210,215,184,99,214,165,245,159,133,172,29,203,64,42,202,166,44,244,104,166,243,248,183,165,175,185,114,75,182,107, +61,179,212,217,172,161,123,43,198,43,19,14,92,92,170,98,114,210,42,237,226,43,140,221,195,244,106,164,66,211, +144,104,54,10,178,245,83,120,155,40,159,2,166,83,63,75,224,235,89,226,160,167,145,240,102,185,251,92,230,37, +122,74,61,194,31,153,65,79,226,244,105,14,146,158,203,89,49,78,39,121,12,255,2,58,244,215,61,188,123,141, +240,19,81,238,14,130,70,173,84,212,74,98,91,201,169,129,86,193,68,212,88,11,118,21,166,187,52,64,105,135, +124,151,120,1,44,45,118,1,55,111,85,97,236,155,109,228,90,174,12,118,185,1,200,181,121,170,82,192,30,55, +98,173,234,51,192,181,163,57,80,160,155,120,35,196,255,91,157,203,135,235,163,250,42,67,175,120,11,68,242,200, +159,224,26,214,121,141,36,200,2,121,187,49,14,100,5,149,206,101,233,86,24,175,218,53,223,175,57,148,15,170, +143,143,109,229,67,229,132,124,248,48,247,147,244,211,0,239,163,57,178,19,229,102,166,148,180,168,118,74,146,112, +74,236,60,228,225,60,164,52,15,22,103,135,13,178,128,127,115,59,43,242,82,94,16,84,188,85,55,200,36,31, +71,233,44,153,220,196,55,208,229,82,94,217,55,76,244,160,126,9,99,226,141,191,148,151,66,6,161,85,39,116, +33,132,181,159,143,243,176,129,121,184,141,142,197,185,228,233,220,192,116,110,96,58,133,68,238,192,252,169,66,116, +126,142,156,84,202,61,135,220,27,179,16,2,223,191,178,98,227,248,166,55,10,11,95,43,40,46,183,234,230,127, +143,167,91,165,212,26,220,203,63,93,205,86,234,58,190,126,134,29,189,84,215,184,61,162,197,163,229,248,70,60, +30,143,23,34,142,174,162,160,151,55,18,114,172,213,214,204,198,74,153,2,23,234,186,133,76,193,153,43,6,13, +211,74,173,66,152,5,220,246,32,68,39,210,57,143,203,80,54,36,69,25,31,20,130,198,32,110,103,248,106,28, +60,192,226,195,240,224,97,8,15,176,136,219,53,198,234,253,79,203,248,215,0,62,188,29,134,60,207,255,5,200, +243,58,143,255,214,248,154,127,12,246,100,174,237,142,4,137,57,24,35,138,10,112,200,168,26,33,225,246,228,37, +192,80,140,161,77,9,68,76,72,137,11,105,111,31,154,205,144,150,9,221,57,253,178,7,46,106,253,105,165,7, +0,70,165,208,143,90,104,14,43,65,115,88,181,192,118,240,66,63,170,17,208,247,105,83,11,79,19,7,34,181, +1,145,122,140,128,19,186,220,103,121,50,165,105,91,248,97,169,72,16,206,142,56,148,170,244,215,236,223,130,49, +252,176,196,21,100,228,157,6,51,11,38,55,14,134,76,52,221,143,205,103,210,116,164,165,201,66,10,195,68,222, +133,121,38,235,70,225,118,236,198,32,14,249,166,23,75,75,220,141,49,80,169,27,71,195,234,197,242,9,0,240, +233,11,179,94,101,16,29,62,89,82,29,135,105,192,77,168,203,105,144,241,158,177,211,190,251,253,168,240,71,161, +248,156,163,32,96,205,138,224,212,13,244,61,144,62,64,20,190,248,38,89,215,51,63,181,113,119,78,167,47,242, +40,172,68,22,178,49,239,78,195,211,114,220,26,123,60,54,22,215,188,51,243,141,151,118,11,231,158,99,121,221, +14,204,121,251,201,215,205,160,35,204,106,50,80,181,123,89,40,30,97,64,111,237,127,204,7,44,248,80,170,128, +35,158,85,117,211,23,165,226,206,107,27,182,29,224,39,177,80,213,1,165,51,204,153,61,111,243,228,115,107,10, +172,75,48,191,223,214,6,7,254,28,169,148,243,54,227,189,112,183,225,29,148,172,151,101,167,246,74,253,72,178, +212,187,53,217,191,140,53,235,39,34,100,169,91,43,235,80,117,149,6,153,38,57,67,139,42,0,196,11,75,100, +165,210,178,215,172,130,113,21,234,19,143,199,105,231,1,41,94,96,3,136,239,84,104,148,126,163,202,179,197,185, +92,195,207,220,188,62,110,240,134,20,187,196,74,51,111,196,212,221,169,4,203,86,22,3,76,46,235,40,170,39, +27,84,27,120,28,173,225,199,126,11,121,169,242,200,162,11,124,22,64,70,21,210,236,76,80,197,151,204,178,76, +188,5,138,147,89,2,203,5,56,10,110,195,15,118,101,122,226,18,228,4,83,56,26,12,211,76,246,65,222,14, +175,110,169,168,160,35,74,253,153,155,106,165,173,99,87,56,231,193,42,57,181,9,189,127,85,84,255,161,74,215, +129,225,43,193,93,232,15,43,34,247,251,50,247,225,38,49,248,86,20,52,109,28,185,243,38,46,67,122,36,244, +236,255,55,166,68,177,234,144,14,237,129,121,44,52,120,166,247,161,181,104,219,31,27,67,194,34,234,51,154,66, +32,164,97,67,234,14,104,198,77,211,140,66,223,233,103,231,251,222,210,143,251,36,45,122,4,38,234,215,19,177, +95,202,52,89,63,215,191,101,196,40,65,191,36,114,24,230,198,35,234,209,72,34,105,143,190,76,28,40,197,111, +218,115,248,73,224,59,62,110,113,28,191,151,2,238,82,231,65,96,68,213,177,175,34,181,187,176,223,78,236,228, +194,123,90,193,87,117,68,205,252,188,0,114,102,98,176,243,33,153,253,170,222,199,230,61,22,185,131,195,131,24, +76,230,25,102,16,19,216,17,230,99,217,76,74,241,180,62,170,44,247,189,6,113,240,198,242,13,8,77,248,254, +15,177,126,173,242,67,39,10,81,239,110,68,83,82,196,191,200,147,229,65,211,211,227,33,86,225,222,248,215,229, +13,142,95,62,17,99,14,235,73,5,225,167,28,14,102,100,236,166,9,50,64,135,126,230,30,121,3,177,53,117, +88,98,147,26,115,253,114,103,174,45,230,186,143,93,122,120,60,172,58,193,89,91,35,223,137,183,185,2,14,224, +30,220,32,37,47,26,7,0,178,169,246,52,187,150,40,97,101,5,70,222,187,12,222,36,8,38,134,192,5,75, +147,56,12,192,49,241,232,9,1,175,131,88,18,47,63,98,72,182,7,79,143,78,64,217,253,239,13,203,106,98, +22,171,45,9,76,61,124,195,234,242,220,138,30,59,197,243,101,138,176,107,50,228,227,21,123,124,188,52,67,13, +6,219,216,173,253,217,90,133,25,179,88,253,91,63,156,64,70,26,232,192,52,193,129,105,191,183,236,60,3,55, +71,83,8,121,96,24,66,175,19,201,101,240,27,167,251,121,55,217,175,64,252,39,105,170,35,88,136,124,75,100, +110,87,54,241,75,239,91,253,184,197,6,255,157,80,139,65,77,130,228,204,30,15,255,50,169,145,123,79,138,111, +164,11,139,135,47,27,98,210,99,110,248,49,185,225,215,230,54,59,216,62,122,162,172,181,92,202,133,247,82,181, +100,183,213,179,104,161,42,96,101,39,158,157,132,14,173,66,118,147,9,167,170,152,44,36,108,5,36,201,161,76, +105,202,104,136,203,149,30,47,66,118,20,94,129,161,212,1,134,5,169,255,37,70,109,47,37,85,191,220,233,233, +45,219,224,89,180,241,192,215,117,24,180,251,24,255,240,57,191,233,27,90,246,64,186,179,97,17,120,219,240,7, +56,32,107,163,252,248,178,140,10,62,224,77,12,141,214,70,113,176,52,127,143,17,179,175,108,172,233,170,44,233, +247,24,159,51,46,109,138,237,188,44,233,195,150,202,109,26,142,86,150,246,7,203,4,3,252,107,239,221,87,23, +120,61,61,15,217,202,69,111,165,247,179,128,228,13,142,107,80,182,6,173,74,146,37,117,94,20,108,79,38,123, +19,145,35,170,245,91,84,251,169,40,215,111,244,194,76,200,195,28,157,182,67,4,254,208,120,108,50,9,194,64, +224,61,78,74,63,51,79,25,101,160,236,118,150,186,181,219,184,160,1,159,41,44,132,181,12,149,226,150,130,108, +125,65,156,159,59,54,203,241,188,9,36,49,108,209,73,99,27,46,84,67,117,64,12,238,128,90,153,221,165,97, +155,23,40,143,84,170,191,134,225,142,116,81,12,231,150,234,217,198,182,248,77,172,229,50,46,28,44,41,91,124, +126,11,115,142,97,139,184,220,240,221,96,137,9,196,193,191,10,75,194,111,3,255,46,93,13,188,48,124,172,142, +253,114,76,56,18,107,130,90,4,47,78,191,0,197,246,75,84,34,92,159,110,25,31,31,150,186,196,118,184,212, +64,83,62,161,95,14,91,107,67,123,229,63,100,3,220,83,101,189,251,161,58,57,125,226,254,5,98,30,53,138, +209,45,0,46,101,128,117,36,168,107,12,14,178,16,78,208,118,162,253,97,12,62,150,38,73,75,179,73,165,219, +67,34,0,39,127,233,114,226,104,110,129,249,198,179,134,223,60,27,65,200,36,250,138,254,73,30,17,50,32,3, +82,232,14,222,131,71,91,248,119,3,255,150,65,139,127,203,6,223,6,110,1,39,133,39,138,217,196,60,36,65, +196,214,68,108,109,68,169,34,200,50,206,142,110,108,182,49,216,70,153,65,130,152,20,178,194,180,45,164,45,109, +9,72,91,218,180,218,35,51,88,26,37,77,48,103,13,251,14,106,26,151,176,231,160,212,184,162,253,6,1,251, +209,18,170,250,230,15,161,170,254,190,233,68,227,197,213,137,48,87,88,39,198,94,102,61,97,133,5,146,250,167, +43,228,170,223,139,220,118,223,120,59,101,99,45,217,28,116,120,87,23,178,127,183,215,100,247,81,238,236,49,46, +237,251,57,222,200,0,94,48,5,129,43,108,160,138,166,77,204,150,89,252,207,37,63,192,74,72,193,245,42,143, +110,208,244,193,210,124,46,141,148,115,135,185,153,192,230,133,93,81,73,232,26,30,98,43,178,201,241,165,156,104, +212,41,239,226,96,20,138,70,40,11,95,206,231,35,204,48,84,109,41,117,175,108,237,202,134,68,107,159,34,160, +51,129,199,52,84,135,24,196,210,125,30,60,177,30,77,31,206,102,51,53,247,227,233,140,18,249,157,228,36,221, +239,192,136,124,230,30,62,95,206,0,173,174,1,11,198,202,33,84,196,81,97,194,45,35,170,161,244,0,185,182, +244,91,20,10,6,187,19,139,189,177,232,233,101,2,194,70,111,250,200,41,223,183,177,21,168,232,18,220,123,79, +202,221,77,58,66,51,111,35,198,84,131,183,228,55,255,94,156,20,61,166,252,125,233,12,17,47,42,173,129,84, +216,93,92,24,243,144,23,23,86,51,255,180,74,73,190,61,126,153,73,124,79,118,193,31,27,105,150,206,133,191, +207,228,243,164,114,161,55,89,40,80,253,69,71,218,139,29,80,195,97,65,139,240,104,163,147,252,96,176,219,227, +140,172,187,54,178,25,107,118,243,128,220,183,4,165,131,159,0,235,171,132,191,83,18,221,99,117,45,200,61,57, +161,39,52,148,0,157,7,207,138,213,89,62,30,159,171,12,188,65,202,5,148,89,60,133,42,166,11,52,251,135, +5,46,21,176,15,224,223,45,85,118,165,2,169,140,104,49,62,17,143,18,49,62,25,195,221,237,241,172,110,150, +39,54,139,212,2,114,93,171,155,201,149,105,248,86,93,77,111,159,222,76,111,161,169,203,49,116,224,246,28,238, +136,11,250,218,78,47,31,43,144,226,132,63,212,242,54,108,121,225,90,61,61,212,234,73,216,42,236,246,15,176, +199,95,182,102,156,244,156,186,86,147,19,121,171,182,208,139,83,211,139,181,58,250,234,145,103,39,126,0,41,217, +71,145,237,205,228,165,152,64,132,237,36,196,94,64,132,144,235,103,27,243,72,184,150,115,211,107,185,82,183,98, +74,51,58,151,75,181,114,92,69,158,229,244,60,116,30,217,44,250,175,83,120,181,1,238,38,153,94,225,197,178, +179,112,137,171,125,97,87,84,222,66,133,13,206,218,13,124,92,192,199,228,214,174,169,106,208,147,240,24,149,188, +43,177,75,84,102,109,94,71,9,102,17,143,111,30,21,198,133,231,150,42,189,86,249,255,26,177,152,107,99,210, +57,125,10,111,146,107,149,194,126,168,68,156,62,91,193,32,87,16,156,67,208,40,251,62,170,199,80,19,62,77, +150,161,125,214,173,170,38,39,134,79,252,83,180,0,176,14,63,115,225,118,118,176,80,48,38,33,63,120,218,8, +195,211,83,184,13,96,54,241,103,251,31,255,113,105,217,178,239,179,232,235,44,66,39,149,103,167,231,2,151,177, +110,5,148,181,121,63,28,202,251,193,231,109,171,103,32,150,186,197,2,156,51,59,219,66,6,10,36,198,185,170, +153,245,181,90,153,65,207,213,70,85,108,139,245,210,47,87,94,145,67,222,240,97,52,124,99,53,79,64,236,167, +38,200,36,125,28,102,145,67,170,43,91,216,11,35,76,29,201,29,106,103,144,241,46,47,195,81,99,235,135,188, +181,162,69,199,28,17,145,208,125,178,94,116,125,241,242,43,2,110,54,99,213,120,74,40,129,49,228,104,24,122, +240,12,111,196,108,97,101,226,68,194,247,75,211,201,121,156,99,148,11,164,173,42,241,210,65,253,242,231,8,90, +235,168,35,65,80,40,184,191,222,32,26,90,26,87,194,178,2,14,114,137,212,225,228,68,8,153,206,234,126,134, +68,0,167,3,14,44,234,40,78,138,184,86,122,82,56,102,118,65,246,249,234,214,184,180,42,22,10,109,137,143, +104,122,241,221,82,122,133,159,36,255,4,87,94,179,92,33,103,183,152,64,167,71,210,208,142,100,25,101,200,58, +106,236,125,143,226,254,213,71,84,64,236,112,214,217,121,135,71,79,201,9,205,240,98,176,225,59,82,81,195,116, +152,81,54,191,140,98,15,104,222,61,219,87,41,149,169,66,217,21,83,37,30,165,75,29,157,37,210,147,184,92, +199,185,48,206,154,193,128,95,78,246,162,137,71,222,125,155,89,170,204,59,178,37,91,51,230,97,101,217,41,149, +84,192,6,246,81,77,182,210,80,179,111,22,217,172,168,143,72,117,231,154,159,111,104,101,230,173,210,11,163,228, +201,26,19,250,168,89,86,186,94,150,249,252,127,255,247,79,143,204,109,6,242,18,27,129,251,180,230,57,253,41, +170,16,5,164,247,83,149,186,163,82,31,221,113,82,106,62,41,236,160,153,120,221,94,203,9,67,48,191,177,59, +13,253,55,64,62,156,173,172,195,108,43,122,169,180,253,89,181,173,176,55,234,122,90,223,100,13,172,174,62,226, +45,6,71,11,177,172,188,105,46,71,241,90,193,149,158,34,200,54,155,120,122,89,233,228,106,138,25,120,35,66, +158,102,225,242,184,28,180,117,227,190,30,241,63,126,40,128,168,88,27,235,57,15,252,94,127,192,173,63,248,207, +47,118,65,103,218,255,252,135,104,195,129,169,117,43,90,233,12,121,100,180,153,219,128,243,86,247,0,68,118,216, +118,142,70,185,130,1,59,61,51,125,20,26,59,10,116,119,22,135,5,80,16,221,38,32,244,181,117,145,6,187, +9,154,150,154,219,243,238,12,12,56,1,115,175,125,54,171,144,133,81,88,5,76,137,136,227,217,195,147,216,196, +29,207,70,48,39,128,251,143,226,194,247,168,90,244,121,101,166,143,184,49,127,139,10,118,8,110,21,28,10,235, +246,76,152,58,201,52,148,121,109,120,149,151,73,19,121,154,251,139,8,237,1,6,216,135,209,163,173,103,81,84, +88,27,168,163,9,156,37,247,61,30,89,207,164,205,184,198,203,84,33,23,221,8,176,214,128,232,97,91,104,203, +202,117,158,100,143,173,216,177,13,165,87,248,187,76,214,32,85,204,86,150,11,241,12,181,47,130,161,38,126,242, +173,211,245,184,1,2,219,190,80,105,128,242,52,249,6,157,173,121,137,177,131,182,197,153,103,6,197,24,141,29, +176,145,200,187,68,39,145,24,192,221,98,220,167,128,126,231,143,56,91,145,166,89,139,77,26,154,71,49,233,102, +9,187,81,248,44,248,5,58,199,196,201,128,141,245,45,211,41,145,189,189,103,5,25,197,130,79,252,64,219,63, +38,134,168,241,180,26,116,252,124,171,26,230,185,195,231,214,124,18,69,202,15,19,3,79,186,76,106,213,72,13, +57,22,64,213,117,111,100,30,103,9,244,89,105,197,31,90,217,24,77,15,99,20,69,226,205,49,135,191,38,139, +81,24,121,136,15,7,120,65,134,79,244,251,164,29,181,87,19,141,93,66,87,141,208,236,160,240,119,41,30,213, +93,225,111,19,67,74,2,33,87,41,223,223,7,126,19,32,199,47,20,13,192,245,121,131,182,253,189,101,46,89, +34,194,137,174,228,72,36,111,214,32,206,22,55,136,201,201,36,140,199,152,216,164,154,205,101,240,87,60,90,225, +174,90,170,138,183,19,134,18,179,149,224,131,246,77,188,244,187,196,110,40,33,193,75,104,149,205,173,68,117,158, +84,236,102,223,230,52,228,150,147,118,231,109,104,216,166,8,73,211,42,130,169,75,97,71,108,225,239,214,77,179, +41,250,146,116,41,94,85,229,202,146,221,92,126,41,90,210,130,10,52,160,74,139,49,222,209,108,46,151,194,123, +51,245,171,144,46,60,87,45,162,11,217,56,201,63,88,213,204,44,92,108,142,177,175,231,93,225,0,24,246,109, +218,60,203,166,205,100,226,193,153,118,150,209,28,8,187,5,96,195,129,173,16,230,194,97,47,3,190,226,229,162, +247,2,163,232,232,89,16,209,146,175,248,64,158,19,247,5,63,86,177,100,131,199,128,122,194,187,136,13,37,10, +250,143,164,77,45,88,238,190,70,202,36,85,198,37,67,241,144,160,120,68,51,13,29,201,113,229,240,228,75,31, +151,186,56,17,107,91,4,225,11,167,227,129,130,114,91,42,227,227,82,140,19,24,237,7,190,24,56,31,8,85, +177,4,34,70,230,21,66,90,227,2,170,224,113,226,137,112,51,145,168,249,194,218,158,32,97,146,165,217,115,52, +129,14,138,182,178,16,125,197,186,146,209,113,239,77,36,85,37,24,2,226,140,75,149,90,88,2,138,99,240,9, +147,57,93,66,230,205,2,5,160,145,102,79,88,114,5,183,251,143,77,180,47,153,188,107,67,154,96,222,189,242, +113,100,228,195,23,128,73,186,4,44,143,238,118,107,162,207,34,149,220,115,36,81,209,93,13,27,55,13,253,160, +20,206,71,45,91,235,104,132,69,113,74,178,2,135,134,171,55,69,189,204,22,13,228,34,188,129,71,160,125,47, +55,253,171,26,250,217,235,131,30,236,131,134,62,200,29,138,105,225,230,203,19,248,73,44,223,8,149,206,63,65, +231,236,227,185,21,104,50,111,41,21,176,209,133,161,235,42,81,112,247,114,235,123,228,1,146,123,118,93,115,0, +230,9,29,161,54,99,123,216,225,107,28,212,191,247,26,215,151,204,234,24,85,128,23,95,22,193,179,146,89,89, +184,201,50,218,100,246,13,19,69,100,114,248,19,122,112,152,166,126,27,165,126,34,150,176,141,82,228,232,192,38, +33,129,51,172,126,142,97,220,69,24,194,142,192,115,64,45,145,0,71,54,1,162,35,11,153,155,223,249,148,6, +106,251,73,51,154,216,25,205,105,70,139,224,166,89,7,39,201,136,106,242,77,227,15,18,182,248,69,164,25,211, +170,233,224,248,91,2,241,27,151,138,103,147,211,8,231,97,39,199,60,109,169,201,229,42,124,224,236,58,148,11, +128,253,179,58,94,162,120,121,184,68,75,122,108,206,53,239,126,152,81,42,255,13,212,53,67,108,19,237,231,1, +252,213,138,32,163,116,187,109,214,61,102,33,251,220,203,247,72,43,62,89,72,150,157,244,200,11,119,100,197,123, +197,238,105,228,229,88,196,216,237,101,136,32,51,95,134,40,229,89,51,235,63,173,201,184,172,119,182,81,147,179, +13,97,136,88,196,75,125,17,136,177,110,116,229,195,138,205,102,98,44,219,32,246,121,9,108,26,4,181,50,93, +106,93,137,64,91,164,166,238,247,37,156,11,233,32,144,211,56,46,141,72,63,78,115,40,197,188,39,244,92,116, +37,3,115,169,11,195,104,72,80,175,3,128,162,92,192,15,92,33,136,89,212,226,124,90,6,90,14,75,184,19, +150,168,229,80,66,51,172,187,74,9,172,97,19,100,20,178,50,52,69,136,8,102,50,33,37,132,170,133,244,89, +71,142,58,230,42,22,166,202,150,195,205,17,73,133,98,252,253,6,39,18,214,133,53,235,68,144,192,145,24,205, +89,134,80,64,195,207,52,244,180,109,112,187,145,33,29,22,13,46,77,137,191,165,16,129,208,102,214,17,218,44, +67,253,149,122,95,158,0,164,109,102,208,21,4,11,198,220,114,156,205,48,16,55,51,12,198,199,190,244,101,31, +170,133,64,170,102,32,37,75,143,30,32,87,122,96,125,115,189,191,186,211,84,241,162,50,215,2,38,73,75,179, +232,53,194,177,196,232,233,88,208,233,177,13,24,11,93,240,117,185,169,82,212,122,181,100,100,188,116,51,129,53, +96,67,88,75,187,39,61,186,48,146,175,114,25,110,197,57,118,117,225,134,186,177,61,1,113,84,130,162,230,19, +1,168,156,219,62,201,181,122,91,26,3,60,155,176,154,21,86,179,22,189,14,174,92,7,231,212,65,35,71,7, +171,179,148,27,38,246,28,99,84,180,216,245,110,22,136,112,25,178,66,224,197,206,44,102,191,94,23,221,245,218, +25,43,209,244,72,109,78,165,53,57,229,13,79,5,123,168,148,61,173,43,131,168,78,75,243,252,131,251,175,171, +136,64,207,187,21,106,248,79,42,89,79,10,191,229,69,160,26,84,15,169,6,133,55,35,38,77,225,81,155,79, +21,74,78,213,29,253,180,183,117,136,164,154,75,69,187,249,44,36,143,160,150,169,121,217,41,165,69,232,144,132, +147,137,130,77,108,159,90,252,34,185,85,201,93,45,41,13,126,105,6,191,64,159,44,137,7,74,248,236,180,247, +186,52,87,101,171,0,195,82,185,220,168,2,65,50,112,210,221,131,103,22,188,50,206,165,89,150,74,2,35,26, +246,84,141,204,125,209,211,235,160,237,191,6,200,164,123,144,41,183,76,168,149,17,138,22,187,117,15,56,153,9, +46,228,194,159,161,75,168,164,232,85,146,18,120,91,179,130,21,90,117,159,130,250,23,48,199,37,8,147,80,53, +75,89,119,77,80,176,241,134,213,140,31,88,227,81,81,22,191,233,170,28,97,170,127,55,245,11,118,187,184,107, +193,146,75,236,74,45,129,222,44,111,96,181,18,180,138,86,209,162,145,186,159,182,66,209,51,2,127,113,99,120, +197,126,130,115,183,45,75,152,121,244,115,255,173,29,64,133,108,10,33,205,142,233,55,76,219,163,118,45,249,157, +147,183,157,145,72,110,134,107,37,129,137,187,107,46,239,173,57,16,223,202,250,160,21,240,168,70,216,170,185,143, +165,196,97,187,157,92,119,60,132,226,178,224,107,206,158,16,226,206,78,240,82,165,52,197,11,149,194,54,197,227, +140,214,204,122,130,229,48,119,91,236,139,22,18,151,141,59,208,91,174,165,171,203,46,151,238,119,80,200,107,168, +69,8,195,167,223,18,159,30,187,136,18,214,3,126,74,250,163,143,122,44,245,80,246,220,144,97,123,102,49,81, +163,187,50,254,84,42,81,238,51,48,43,20,74,96,82,67,230,36,68,3,103,208,205,34,124,134,88,227,143,104, +137,56,87,108,113,122,128,233,89,9,103,155,209,98,184,213,34,74,160,71,133,32,71,166,153,157,141,208,238,230, +30,3,159,151,246,232,26,223,94,8,73,78,12,209,250,133,157,48,149,203,218,81,30,123,67,37,210,217,82,48, +185,217,5,214,122,31,246,222,6,1,193,172,13,75,10,183,97,242,9,102,27,214,69,122,15,30,253,185,215,198, +222,255,199,108,101,240,109,159,111,132,119,252,61,134,220,145,112,241,247,9,19,40,149,170,189,191,135,202,250,123, +168,92,139,137,25,130,27,237,20,125,175,71,137,1,255,71,67,218,81,165,76,204,233,23,18,118,47,158,27,227, +218,33,145,37,10,82,201,190,251,10,30,30,226,88,126,100,248,186,177,159,181,247,104,82,220,55,220,144,28,46, +252,0,107,59,192,186,75,17,243,0,203,160,219,165,12,166,107,175,255,67,171,3,156,53,104,219,207,150,81,3, +195,0,241,192,135,199,200,213,1,11,154,27,47,186,141,7,111,103,188,83,240,241,196,213,23,15,212,214,58,186, +245,199,90,97,79,145,191,131,200,29,56,157,250,150,92,131,171,70,66,200,138,119,64,247,145,252,99,57,134,13, +220,43,44,203,129,54,146,67,201,219,70,200,34,148,164,109,16,201,245,85,201,160,13,137,206,245,41,224,141,0, +73,141,51,122,189,112,61,203,152,75,212,240,87,182,103,53,190,19,129,25,50,78,49,95,83,203,118,254,165,254, +92,33,176,139,100,62,15,244,90,114,253,9,74,125,155,53,224,111,207,184,87,163,92,70,24,91,207,95,195,72, +2,235,173,243,114,243,105,89,108,26,84,49,225,42,204,162,57,244,173,43,105,214,184,47,111,248,188,193,191,97, +219,216,68,207,114,78,153,111,86,5,58,79,239,198,179,216,123,55,122,149,208,204,247,99,77,222,78,100,211,213, +193,164,155,179,219,136,94,116,43,170,186,85,179,108,209,61,226,113,23,43,99,98,36,236,170,119,119,214,45,187, +95,29,18,232,56,254,142,153,102,22,250,234,142,175,233,207,130,238,117,160,144,236,210,32,91,89,110,64,215,111, +162,227,174,83,219,25,186,161,236,230,166,54,187,239,33,179,40,24,122,167,79,193,76,134,93,177,81,225,172,250, +10,68,28,133,115,219,29,18,47,94,183,58,140,233,172,99,80,131,104,195,161,13,171,54,88,151,193,161,138,200, +223,12,31,189,208,21,204,182,45,42,207,252,30,183,103,195,250,78,116,94,5,13,152,96,215,209,133,122,214,240, +183,63,29,246,20,163,55,65,235,131,155,10,225,167,115,63,109,19,48,48,88,44,236,53,97,201,248,62,73,159, +145,216,63,80,186,197,133,236,91,208,233,56,65,32,194,117,158,213,104,23,88,236,186,203,201,107,113,220,23,52, +104,104,226,144,243,14,93,6,246,122,209,224,93,107,117,88,164,83,28,76,203,213,122,211,232,143,89,147,107,187, +142,81,8,50,147,16,76,230,173,250,17,45,65,151,94,87,83,155,122,177,210,166,2,254,175,28,220,129,105,111, +227,45,169,109,24,251,251,242,166,142,42,105,237,54,141,79,142,97,135,45,251,251,42,245,217,1,61,237,102,15, +15,54,195,253,84,54,220,152,85,52,226,160,8,129,131,47,1,135,193,55,72,69,56,44,90,238,105,207,194,26, +46,83,41,93,221,113,229,25,122,102,238,241,86,180,214,152,1,59,115,34,176,185,58,0,210,83,213,135,159,240, +238,139,252,171,113,66,238,244,27,96,175,161,211,10,107,8,105,132,231,116,36,109,20,190,122,97,73,53,90,1, +183,60,39,27,240,115,148,83,219,168,9,235,254,246,55,160,127,116,89,203,149,183,90,126,169,138,49,136,234,143, +75,64,28,18,116,231,250,17,154,136,214,166,37,65,2,44,209,202,90,84,74,207,82,70,99,206,199,151,227,39, +143,146,103,70,44,99,49,86,75,25,164,70,43,176,116,121,28,159,136,115,117,44,55,152,56,31,143,133,204,207, +86,231,202,106,183,28,27,237,150,141,172,128,74,152,147,246,206,165,247,100,39,187,77,169,203,49,226,188,139,150, +55,198,224,234,240,50,254,241,229,233,93,123,24,187,84,213,164,161,229,73,228,28,71,134,226,96,198,184,198,241, +253,179,126,41,47,252,172,223,14,206,250,101,103,214,47,80,38,109,51,174,205,52,47,237,44,207,199,137,76,137, +89,100,39,109,238,38,109,3,19,180,182,57,86,227,49,244,15,122,135,83,126,225,166,124,77,83,14,195,138,87, +52,229,183,193,148,207,189,172,221,92,222,10,92,183,218,78,59,212,122,87,171,139,54,153,255,186,169,27,55,133, +145,229,44,135,48,146,97,90,215,43,220,157,128,169,187,46,33,11,61,49,6,169,10,217,91,90,28,68,213,160, +180,51,27,246,174,117,84,250,203,47,128,32,134,73,56,0,193,44,155,221,58,56,40,17,252,115,225,113,29,92, +148,147,238,249,197,135,201,144,173,151,34,91,79,139,4,240,200,244,8,182,56,154,188,180,95,191,191,90,33,83, +163,249,98,231,10,191,176,76,106,111,243,10,127,208,110,246,155,166,2,30,253,109,148,99,118,59,66,153,143,21, +125,143,235,22,95,169,250,99,11,107,12,239,237,73,111,251,99,63,8,142,14,140,50,197,156,48,210,196,142,18, +67,127,164,1,26,175,202,105,140,52,112,252,188,99,220,54,190,55,120,170,18,70,223,118,86,153,185,219,157,45, +26,186,190,133,14,0,41,116,48,217,142,98,68,254,58,113,183,15,110,118,143,228,16,178,61,5,230,73,211,241, +133,107,107,64,118,8,42,190,81,112,31,71,224,153,194,115,224,247,71,92,32,250,192,226,32,116,48,74,123,48, +152,19,244,45,206,116,89,201,28,143,67,115,4,71,100,248,72,200,20,209,135,138,208,135,157,101,83,45,165,59, +93,139,86,85,114,174,82,139,84,108,212,252,241,19,146,120,99,175,166,230,4,163,43,192,224,238,202,253,119,100, +239,49,76,31,188,200,100,176,247,65,202,90,214,22,227,72,9,227,176,80,195,35,44,171,128,198,187,12,145,151, +11,131,188,64,103,133,188,85,44,177,119,106,236,14,227,98,89,81,135,21,32,142,43,227,194,212,134,47,33,124, +233,13,2,214,196,214,35,88,245,43,136,160,189,244,221,147,39,6,130,212,1,235,22,211,57,36,19,28,165,55, +59,247,210,125,75,107,128,128,82,187,150,227,94,246,162,228,49,102,11,204,196,189,228,128,36,123,4,162,51,103, +191,202,186,103,248,237,37,135,185,83,161,113,187,176,77,121,134,192,166,234,82,221,110,15,191,82,59,18,138,89, +89,225,247,15,127,123,255,241,9,26,230,228,172,241,203,35,31,96,141,96,136,117,159,29,85,142,95,91,249,30, +246,198,45,56,124,168,97,105,86,168,57,248,86,125,24,111,166,105,22,213,242,149,124,47,223,10,11,181,168,7, +144,200,23,84,52,7,177,123,104,253,88,96,45,1,48,192,170,176,162,66,71,47,187,86,212,167,117,200,67,39, +137,81,43,79,84,71,111,133,245,50,2,203,240,76,27,107,220,168,150,100,13,167,190,151,219,248,149,188,129,237, +182,140,89,201,240,109,43,226,218,190,106,188,135,206,194,86,196,169,37,253,160,95,177,2,228,157,178,73,165,58, +224,234,202,171,189,61,249,5,190,175,191,52,135,66,66,212,248,2,134,150,202,29,110,251,43,221,44,43,164,242, +227,151,36,29,33,249,60,197,225,217,122,233,191,81,186,91,222,168,129,235,77,94,223,113,229,78,111,102,107,133, +42,63,77,120,105,142,23,225,5,133,2,138,104,140,157,33,59,26,255,54,76,201,227,54,54,165,195,130,219,160, +46,204,124,13,113,225,53,160,161,54,7,247,93,45,242,219,50,98,151,172,141,25,212,203,172,210,102,190,4,107, +121,92,140,23,211,131,184,22,78,43,226,90,221,195,112,106,160,9,114,189,65,118,122,73,171,53,148,68,141,188, +84,117,7,59,59,13,177,51,249,107,7,176,157,242,55,48,220,194,144,170,130,101,17,242,149,90,141,55,227,151, +6,110,190,87,104,116,239,45,252,221,78,115,60,149,230,96,68,33,64,190,153,125,64,36,240,253,248,213,120,241, +140,87,1,201,85,83,108,172,182,114,109,206,239,120,44,77,117,234,238,181,179,153,225,152,199,166,222,183,227,237, +179,96,57,160,90,91,201,251,177,118,89,9,127,88,112,59,182,195,234,190,117,181,217,121,113,217,95,125,131,103, +62,122,47,166,183,112,214,228,91,121,138,7,248,34,65,83,234,99,152,26,24,49,12,54,246,157,150,230,190,18, +242,42,178,229,76,17,200,5,221,28,43,152,149,216,78,67,11,135,238,142,109,211,250,203,169,119,41,135,226,198, +13,102,144,5,94,130,154,46,193,90,229,24,160,43,208,190,44,235,97,100,182,188,239,118,173,20,247,47,81,154, +241,9,124,108,48,87,42,158,121,101,231,51,183,106,88,114,161,124,85,115,229,43,59,132,188,122,20,30,69,142, +122,200,164,144,75,143,67,162,6,75,137,10,183,6,101,144,157,125,50,23,161,190,206,186,79,12,33,44,155,111, +82,29,69,8,245,212,51,110,18,130,124,152,143,197,116,169,210,113,208,132,107,186,179,81,214,19,199,187,112,115, +60,57,12,159,156,101,186,13,246,60,129,62,47,140,193,231,224,176,149,193,145,220,20,81,98,238,179,97,84,163, +234,26,209,37,20,201,202,177,236,197,33,22,82,56,190,7,0,235,74,106,11,172,55,114,137,98,67,131,221,29, +220,104,180,201,52,110,178,134,54,89,129,155,172,225,77,198,164,164,219,103,51,141,171,72,245,142,11,154,227,248, +216,248,236,121,195,240,239,180,137,88,78,8,217,86,37,73,110,209,46,236,177,250,128,108,135,52,61,184,44,194, +74,142,150,67,244,177,44,128,216,45,188,52,217,120,92,8,68,132,84,121,86,156,75,211,28,181,69,32,200,237, +127,106,207,54,102,55,97,205,224,225,0,245,12,85,182,148,100,164,162,66,255,78,129,112,98,223,8,222,195,27, +231,176,72,106,87,119,215,247,111,111,230,88,243,92,76,247,125,29,25,63,123,129,64,66,159,29,47,75,117,13, +19,64,146,9,15,75,116,230,164,143,202,226,141,6,252,17,157,57,213,161,51,167,33,118,190,44,194,114,236,36, +170,8,203,181,70,204,176,160,76,222,83,84,152,41,124,85,191,161,87,117,103,211,43,202,186,3,66,125,106,215, +24,80,58,174,191,2,191,169,122,200,98,10,165,24,0,114,200,215,176,89,143,232,241,246,138,30,111,237,162,141, +228,133,115,80,247,11,121,103,216,151,9,178,89,141,45,205,95,234,200,176,110,232,181,139,173,138,211,3,41,154, +36,107,142,156,238,142,198,215,117,99,109,196,184,48,130,221,136,17,162,149,104,185,201,72,78,59,239,68,54,201, +181,196,190,51,93,132,123,70,27,126,91,118,185,6,155,46,248,37,69,183,246,161,218,85,226,143,59,87,208,244, +185,249,71,125,62,9,213,97,55,180,89,46,28,2,158,121,152,109,170,230,168,179,233,173,19,183,206,107,28,65, +9,124,139,115,119,74,108,8,88,153,116,13,119,187,71,12,204,201,146,29,39,242,198,130,147,19,253,165,164,133, +239,79,73,247,209,75,194,224,45,59,124,90,239,191,122,27,85,13,196,84,241,83,54,236,215,25,216,189,16,143, +238,147,187,241,39,48,22,218,134,86,24,154,54,162,13,16,33,75,180,40,154,37,179,45,51,56,53,9,254,5, +241,79,199,76,174,158,28,203,238,203,65,184,70,61,249,1,201,92,194,14,241,19,235,144,188,9,17,111,150,30, +106,91,94,111,215,39,255,126,121,193,207,195,221,119,97,227,81,178,10,28,71,86,161,12,0,22,194,6,35,61, +59,118,30,170,101,142,119,68,18,218,234,97,29,125,236,85,220,156,145,55,204,115,123,159,74,190,199,226,125,153, +15,70,118,227,82,58,119,230,44,224,41,137,80,141,147,190,165,116,71,40,114,10,6,100,151,100,229,52,31,37, +29,205,202,105,24,240,85,218,149,139,114,66,53,115,119,41,60,254,147,12,174,104,46,108,135,16,174,11,90,183, +25,162,60,19,166,60,195,149,195,220,28,236,219,19,232,120,219,164,25,109,91,7,92,165,185,188,239,221,141,124, +32,79,248,64,250,67,104,86,107,52,130,202,2,91,124,113,104,139,15,43,126,152,89,65,194,250,167,12,72,130, +81,89,140,4,159,133,126,214,179,81,119,159,143,228,200,190,120,193,7,190,97,141,206,189,199,71,212,235,107,233, +137,250,101,113,255,19,245,31,124,80,190,160,195,248,239,127,243,189,223,254,201,31,127,222,189,219,210,174,239,168, +31,23,124,62,44,238,125,191,11,70,20,78,0,63,235,117,139,113,86,30,101,191,160,102,41,247,111,162,194,18, +169,51,251,75,72,90,124,50,237,44,7,194,146,130,241,77,79,191,60,242,175,135,33,198,217,41,75,29,152,14, +189,254,133,29,44,227,96,24,101,27,230,28,70,141,253,106,49,2,28,112,98,67,174,171,225,146,158,86,159,234, +168,9,101,87,181,196,213,8,68,88,201,116,90,73,136,5,171,233,73,4,184,230,110,116,54,48,186,70,223,6, +31,54,151,150,232,192,23,81,185,80,122,140,111,149,229,164,128,91,173,10,89,195,150,195,137,217,11,200,178,176, +133,106,169,17,130,255,252,104,114,244,149,136,177,170,9,167,105,89,219,52,72,66,250,111,2,89,119,6,206,252, +28,47,165,249,248,37,94,248,135,199,212,195,183,188,37,6,244,30,159,89,246,113,226,195,100,107,135,220,45,85, +17,44,252,227,39,189,165,135,89,230,190,85,174,111,137,239,91,206,125,99,167,53,126,173,74,49,5,202,169,113, +148,211,49,252,87,56,38,51,145,90,195,53,5,192,27,104,58,109,87,78,200,144,172,139,153,172,115,222,87,176, +228,89,37,147,243,142,113,132,239,122,90,77,136,131,190,44,134,112,208,230,48,14,170,101,211,197,65,141,52,191, +153,141,231,121,153,94,41,109,48,227,83,194,140,77,66,128,24,191,44,186,136,177,233,147,102,52,214,47,102,88, +231,180,135,219,54,140,212,134,185,238,67,108,125,206,251,145,91,143,95,118,177,200,240,118,195,77,19,239,8,127, +132,227,153,207,71,109,136,101,122,92,172,135,154,242,53,40,169,240,19,253,101,43,67,11,61,140,243,141,204,207, +104,255,186,28,54,93,11,221,98,161,179,159,237,2,255,164,147,171,239,146,181,49,218,243,129,22,165,222,92,210, +186,12,18,41,180,47,254,48,109,34,127,214,200,216,187,147,78,193,60,159,48,143,48,217,237,178,66,232,238,197, +228,66,255,23,43,89,148,213,42,201,15,172,229,125,75,121,242,213,241,241,191,127,45,79,181,218,1,85,80,37, +159,12,173,101,180,87,187,102,198,31,90,219,69,70,48,0,32,58,25,201,49,207,149,120,69,107,229,242,79,155, +167,104,242,134,193,64,101,117,170,232,124,34,172,4,33,162,10,200,174,154,116,139,69,128,167,247,29,103,137,105, +49,86,9,42,33,224,207,86,142,199,101,219,178,50,118,241,184,68,197,244,199,16,39,209,124,134,174,205,86,187, +171,255,218,40,166,23,70,39,189,86,111,55,171,75,32,9,222,125,255,225,245,199,215,63,126,125,241,250,237,171, +215,111,95,127,252,69,26,217,102,51,56,99,9,200,15,174,124,90,193,224,74,215,231,28,6,87,134,131,67,143, +37,249,192,224,82,149,247,221,126,33,195,240,239,248,96,153,10,208,43,173,141,86,209,18,221,205,138,182,133,138, +18,223,70,50,48,47,90,229,102,32,168,99,219,183,14,28,216,150,184,162,25,113,119,47,180,242,13,218,139,56, +173,170,100,123,100,12,106,33,55,198,72,31,28,37,235,117,190,53,249,99,210,172,196,247,226,204,3,248,151,77, +160,66,77,238,43,50,184,149,45,235,14,121,24,161,16,247,7,19,139,174,48,88,57,238,31,255,191,127,136,103, +147,19,208,155,128,179,145,53,38,28,7,13,188,232,40,43,56,152,174,187,132,67,33,177,58,251,46,43,7,164, +205,11,17,208,123,68,123,198,21,57,91,70,247,140,172,98,127,90,204,237,58,121,179,163,78,120,220,149,178,182, +173,99,83,230,157,249,134,204,18,110,221,184,75,228,34,51,205,124,163,118,223,2,79,120,131,114,203,182,77,215, +255,184,12,250,26,9,19,109,199,84,247,135,200,99,15,173,83,214,125,67,33,166,183,8,65,81,208,108,14,139, +15,77,151,104,15,176,70,252,129,141,9,49,1,95,249,55,93,210,238,176,156,82,44,252,202,224,40,169,141,48, +165,109,204,210,198,216,138,109,212,66,57,6,165,156,179,112,57,170,222,184,207,117,135,231,74,90,51,106,77,216, +171,180,6,205,10,230,116,163,47,29,245,236,102,124,125,100,129,50,85,3,97,68,63,106,31,52,252,28,215,200, +177,97,39,162,177,50,42,246,28,70,225,242,102,54,111,16,37,23,104,66,107,172,22,143,210,16,253,142,22,19, +48,83,70,35,254,96,157,165,141,41,104,221,230,61,183,92,91,121,225,102,254,70,49,243,216,144,203,245,140,153, +244,137,204,131,202,69,28,134,166,208,248,230,209,205,24,12,151,109,196,163,48,5,163,76,39,112,29,168,15,237, +220,118,215,205,188,237,203,71,224,235,206,31,45,59,101,231,88,214,101,195,210,60,229,183,108,127,46,120,20,189, +17,187,75,197,29,190,148,58,124,134,131,84,98,20,220,10,182,131,173,157,10,142,238,74,12,200,31,35,194,122, +228,21,39,230,65,98,184,46,208,143,52,105,162,96,93,4,150,186,221,155,203,106,252,100,140,51,113,251,206,93, +137,80,85,33,111,128,161,243,99,116,67,85,98,81,12,153,253,65,1,91,51,4,90,172,246,216,117,104,25,116, +136,38,201,118,215,235,24,93,142,213,154,30,30,119,61,97,185,85,112,254,222,117,160,211,54,214,46,19,26,111, +231,201,122,10,102,121,103,230,238,142,245,179,140,182,252,4,35,29,137,197,56,66,0,89,177,234,61,27,40,166, +47,120,134,11,89,33,87,48,129,54,16,97,24,211,55,205,144,241,118,204,212,17,188,140,143,203,113,245,172,177, +35,34,198,178,33,214,48,109,82,78,42,22,195,120,24,40,152,126,127,119,23,180,155,154,74,178,218,66,236,108, +105,87,36,213,234,140,218,164,202,13,49,176,36,196,113,179,84,213,79,85,148,24,199,154,51,219,235,152,122,24, +67,74,9,177,169,162,248,250,153,170,38,16,3,167,33,117,195,16,18,230,43,149,22,105,195,4,174,90,200,52, +176,106,91,239,107,215,108,233,21,185,225,47,179,170,218,219,228,52,209,64,51,221,186,156,252,21,204,144,180,165, +59,202,247,111,22,172,212,142,119,49,77,93,17,232,92,52,126,37,102,122,162,138,184,9,102,5,198,129,113,40, +169,161,125,165,31,3,5,117,220,115,5,139,20,246,235,197,29,55,43,198,74,199,1,57,15,49,19,85,143,117, +140,63,104,50,58,208,122,171,251,235,205,251,11,214,61,220,95,232,198,186,172,2,167,173,120,169,184,137,74,220, +84,228,184,71,82,104,172,148,108,35,122,233,173,63,47,66,179,206,243,142,181,230,77,139,2,37,21,89,248,82, +48,143,141,76,28,227,100,165,62,98,56,151,169,183,109,23,238,165,196,179,5,214,99,149,198,73,184,219,163,245, +68,165,34,14,243,76,60,236,131,219,75,140,235,126,137,113,104,20,113,3,25,140,37,164,162,137,214,72,83,219, +67,53,161,195,5,251,0,83,86,152,226,78,122,195,34,249,129,69,231,62,133,209,125,248,14,215,145,7,102,109, +43,179,149,87,223,201,48,101,82,88,254,149,181,165,108,24,102,129,125,247,218,163,107,136,9,130,148,173,193,224, +132,207,242,53,111,47,202,246,22,50,200,29,225,155,113,35,233,203,188,23,198,90,34,210,23,143,40,114,212,6, +53,253,220,67,78,0,213,228,135,141,224,211,161,178,3,81,0,211,242,28,153,233,222,210,19,12,21,159,46,42, +124,239,208,136,41,90,222,106,254,217,188,213,18,175,195,102,171,156,150,74,2,157,189,214,94,239,199,188,247,56, +180,186,231,137,12,14,66,55,38,77,210,165,158,159,162,7,157,1,215,104,225,76,113,3,95,36,195,185,191,64, +12,21,198,224,35,251,140,96,4,60,23,230,171,227,122,205,124,223,207,41,198,169,125,183,239,94,205,92,214,97, +76,112,67,119,163,251,17,124,107,135,177,116,157,134,81,4,44,59,81,219,253,168,219,110,134,223,175,111,100,224, +211,207,251,81,191,116,162,12,22,111,241,138,253,120,255,18,53,144,136,200,80,167,96,155,21,176,73,146,156,60, +175,116,39,255,254,13,178,183,228,237,69,165,235,50,191,214,62,123,159,213,24,214,231,25,227,252,154,239,132,126, +194,61,81,244,60,124,3,201,105,91,141,200,66,53,135,5,138,223,20,206,198,38,104,215,112,33,222,178,96,204, +206,7,106,89,218,71,229,50,10,218,171,25,118,185,238,98,117,0,70,135,7,208,179,233,92,10,180,3,21,116, +138,193,80,103,198,200,65,156,159,65,128,90,190,15,157,65,73,140,31,56,144,214,241,139,149,81,10,140,50,48, +196,193,107,90,227,132,208,129,48,57,137,32,54,85,2,15,195,50,112,155,193,148,74,21,246,136,12,22,36,203, +65,108,172,11,190,1,28,39,8,141,161,103,210,135,202,78,168,194,16,246,251,57,31,210,208,118,57,194,119,205, +64,51,60,202,253,246,205,224,49,225,206,177,251,238,253,8,249,74,126,64,173,212,206,86,141,190,188,12,194,141, +31,102,180,240,209,66,135,127,38,93,38,232,120,69,221,192,254,39,244,109,104,109,211,81,219,37,24,165,144,38, +175,169,13,13,139,236,229,160,12,166,21,83,151,253,28,172,138,20,180,43,209,194,55,14,245,20,179,222,55,93, +166,190,131,179,245,202,64,182,207,218,43,54,235,208,102,33,248,120,120,183,80,209,127,231,118,185,72,43,157,52, +218,236,250,80,138,39,188,255,100,161,122,90,119,102,249,201,206,32,252,225,30,32,222,229,108,240,201,84,105,54, +150,246,52,53,230,248,200,218,218,139,240,64,74,13,6,95,188,9,190,64,117,48,81,9,5,162,200,152,213,22, +94,129,208,70,200,194,170,13,162,184,57,190,237,83,161,26,62,77,17,44,192,137,24,97,11,252,8,51,176,228, +13,187,192,13,217,248,165,70,219,50,180,71,22,193,149,16,108,163,37,2,34,206,209,189,28,122,217,42,159,45, +188,38,186,185,90,177,127,255,212,7,174,158,114,248,214,169,246,46,242,68,38,247,61,157,222,15,241,195,109,224, +204,56,224,130,35,203,212,49,90,68,136,58,89,97,235,168,84,59,138,136,143,219,142,4,99,162,78,245,89,193, +175,116,231,193,76,212,67,88,22,108,12,234,69,184,83,11,17,98,40,230,147,1,54,90,117,232,99,43,156,197, +195,198,48,95,152,163,151,198,231,158,51,120,112,193,185,232,228,114,22,2,7,152,206,102,19,3,52,17,56,115, +24,194,210,169,234,186,255,216,181,70,197,18,182,39,144,163,225,77,45,83,100,164,1,21,86,152,247,81,159,36, +166,33,34,181,164,143,14,42,181,164,15,25,44,203,137,188,141,23,104,149,17,254,110,137,248,36,169,15,71,51, +58,225,15,105,177,167,24,57,235,22,107,130,207,109,219,14,32,178,213,48,190,10,226,116,38,126,8,137,33,195, +17,14,64,203,6,241,8,196,215,171,34,201,195,239,96,167,16,223,53,152,31,38,65,48,217,74,109,197,186,181, +178,198,47,176,207,251,14,52,121,195,99,50,243,202,27,147,105,170,157,77,163,242,232,246,4,14,251,246,68,200, +48,238,9,198,61,233,198,125,137,113,95,138,118,160,206,144,77,226,132,150,236,154,56,119,32,50,160,167,171,46, +1,157,180,170,240,68,114,238,137,228,52,36,146,151,29,34,121,97,136,228,196,16,163,115,185,69,162,185,113,44, +153,181,231,86,41,123,170,65,29,82,222,202,43,9,156,85,54,133,26,18,151,209,141,218,140,65,43,68,214,193, +171,250,37,218,71,82,151,147,74,94,169,155,113,101,252,68,84,34,198,248,241,26,83,198,38,101,2,41,152,142, +92,182,75,72,14,170,184,128,156,76,61,163,33,214,113,21,215,1,229,138,233,235,9,231,72,229,66,76,170,248, +66,5,40,189,44,153,153,17,93,169,141,188,81,87,208,224,165,186,128,191,183,234,98,140,29,186,194,206,99,202, +216,164,140,77,10,116,21,250,117,133,19,116,18,95,202,219,39,49,76,193,151,49,28,136,147,248,74,110,159,196, +55,114,251,101,124,221,178,180,250,160,159,73,226,103,150,170,14,205,244,91,75,55,104,85,211,63,232,212,58,42, +188,60,250,109,40,140,110,159,197,128,200,249,11,157,2,66,27,237,153,45,132,212,129,84,117,234,191,163,48,31, +229,26,18,174,54,122,232,33,143,62,81,69,135,129,45,117,32,105,77,73,230,94,113,76,209,202,49,69,201,8, +170,185,204,109,33,195,6,54,54,219,192,198,43,142,2,239,227,237,184,234,136,46,216,56,21,70,142,97,138,198, +39,176,120,37,162,255,152,90,236,115,209,39,137,211,195,51,221,193,39,90,58,198,178,244,47,134,253,187,19,101, +159,19,53,120,125,98,82,96,240,36,247,194,138,169,12,120,200,203,86,149,114,129,243,86,6,143,29,115,94,32, +167,2,46,228,70,213,48,236,185,192,55,140,167,139,96,124,179,40,12,77,144,119,25,163,114,178,134,161,174,205, +222,24,214,237,186,100,221,174,64,173,30,75,135,82,118,247,138,216,117,92,75,182,112,24,235,80,45,107,35,83, +49,78,161,202,91,181,26,231,160,57,216,117,168,9,122,208,27,0,166,127,213,219,231,44,171,24,122,95,26,206, +96,28,104,26,64,34,100,183,190,42,20,22,236,84,84,245,165,33,131,90,72,223,44,244,231,89,117,93,141,158, +220,211,78,168,91,231,210,48,96,12,84,201,166,175,242,199,89,56,10,189,153,210,170,236,77,224,100,41,250,211, +90,147,2,221,70,158,8,204,48,129,125,127,107,120,149,61,229,183,174,186,219,45,169,187,93,169,103,87,86,217, +173,235,92,235,254,169,95,226,164,1,160,191,148,91,84,38,135,189,140,66,59,180,141,110,91,17,56,203,98,45, +184,123,214,161,211,3,110,224,2,27,24,159,96,19,147,39,216,8,252,29,104,6,96,238,125,125,166,188,239,81, +103,239,82,174,36,238,113,238,93,24,123,127,71,185,158,11,137,125,195,158,65,191,132,104,195,130,93,164,153,225, +132,129,238,136,203,117,239,104,60,243,94,145,55,120,102,139,75,137,33,123,115,87,178,243,20,21,39,242,179,32, +75,97,33,75,225,33,11,217,140,8,1,134,220,240,99,220,250,174,187,67,174,252,83,221,7,177,11,96,242,7, +185,182,0,121,188,177,224,119,206,112,120,62,46,91,184,9,215,193,85,66,236,116,70,3,228,86,158,154,123,41, +188,123,170,131,119,12,221,19,11,119,79,132,183,217,37,204,107,239,134,193,145,219,229,251,49,234,97,234,168,49, +186,65,245,245,203,135,30,17,168,58,207,65,32,10,7,179,58,198,191,199,112,173,31,203,173,191,126,111,158,110, +225,114,186,177,58,51,112,62,207,110,206,229,237,224,234,99,74,216,171,91,232,203,5,117,4,59,113,165,46,136, +225,0,157,185,162,250,153,85,213,185,147,180,108,96,206,214,56,206,64,233,171,179,156,185,64,116,227,88,158,42, +87,213,244,250,233,41,116,245,90,172,162,171,179,235,115,44,26,150,152,98,111,44,63,99,37,90,216,13,189,116, +154,57,155,133,38,14,87,119,162,74,179,169,153,35,209,195,89,152,88,25,64,90,58,40,75,254,251,80,22,170, +149,113,22,186,210,123,175,209,82,31,80,70,15,203,223,143,203,132,210,5,225,26,186,148,97,228,37,129,35,149, +24,228,37,233,34,47,9,48,46,126,31,242,82,116,223,207,45,150,232,161,91,151,216,96,156,191,148,14,114,56, +208,114,139,15,98,113,30,32,231,169,67,206,151,29,172,127,225,177,254,121,136,245,111,58,88,255,218,96,253,245, +81,72,62,136,105,56,67,245,30,0,213,157,59,180,238,220,161,58,184,124,187,190,137,117,231,126,208,206,166,115, +50,94,32,200,174,28,82,78,116,223,62,21,70,85,155,34,233,100,46,115,140,249,231,38,129,156,77,150,190,216, +84,84,93,42,115,105,254,142,231,84,45,191,127,150,76,41,124,94,51,88,201,114,178,30,108,136,82,37,118,102, +141,95,212,152,157,220,207,171,127,131,229,134,107,183,117,155,246,55,131,195,64,44,226,222,86,168,166,197,161,54, +36,205,190,238,88,129,213,238,238,239,44,32,106,28,107,175,18,127,97,137,112,166,203,63,26,19,162,204,16,236, +191,26,244,31,171,100,173,12,251,255,86,150,246,99,139,160,4,180,59,60,150,142,108,159,102,144,237,19,242,152, +134,57,64,200,109,170,186,146,210,201,16,63,5,245,214,247,249,41,149,244,89,133,76,145,177,2,237,88,38,203, +55,181,125,44,198,23,253,200,48,51,30,42,228,161,64,207,57,176,69,153,129,144,199,146,14,241,88,82,250,8, +95,160,18,251,219,121,167,74,232,163,243,46,5,70,83,194,71,41,8,111,229,189,12,19,230,30,26,224,19,174, +213,231,179,248,200,167,80,200,193,235,248,58,32,85,133,225,221,161,5,235,58,16,240,194,220,93,30,82,48,244, +86,150,108,146,0,249,78,118,246,218,105,161,216,129,99,33,158,30,29,31,159,160,219,116,170,186,234,62,125,203, +36,32,187,205,237,133,132,64,15,133,232,197,247,99,248,202,12,163,249,90,116,87,162,246,111,93,104,75,135,29, +226,194,3,82,94,94,38,249,105,190,94,38,138,150,187,7,253,75,217,88,53,3,180,158,32,117,79,255,93,150, +246,58,97,61,95,207,97,192,130,90,248,88,236,100,47,146,111,118,138,254,117,176,137,192,41,174,48,79,28,120, +186,216,129,94,248,112,230,207,30,146,69,109,221,207,219,103,30,251,252,200,30,54,202,115,209,174,35,141,152,144, +192,101,222,122,19,91,105,0,63,250,2,152,137,61,224,169,232,59,83,27,189,72,138,162,4,6,61,212,7,230, +232,169,145,7,73,243,192,52,240,96,52,78,88,14,167,219,3,150,135,76,173,144,101,126,206,125,106,141,191,129, +58,179,238,254,29,111,217,193,165,23,75,244,161,59,143,106,4,8,232,123,219,88,84,11,71,173,6,185,212,108, +86,18,32,0,204,250,123,195,128,52,90,162,53,104,91,218,52,58,182,160,124,41,2,221,105,130,242,144,203,154, +189,197,75,96,184,34,47,154,124,184,169,19,62,145,33,12,128,17,247,23,154,71,254,105,96,193,75,43,180,148, +28,152,156,202,2,90,168,7,231,177,146,165,0,53,62,150,182,233,207,87,37,163,218,157,37,112,153,198,236,92, +158,216,222,76,34,136,176,240,1,128,67,111,230,80,131,28,154,110,247,123,61,200,222,229,9,24,80,36,47,55, +141,51,33,236,222,51,40,248,64,51,236,233,110,90,215,214,105,131,211,141,84,63,90,122,53,238,48,87,240,33, +203,192,171,123,233,13,81,86,129,33,202,170,237,207,102,248,102,72,124,246,130,184,236,222,31,16,51,137,205,53, +90,14,95,163,129,35,190,138,252,140,68,5,124,84,120,157,213,230,99,43,218,54,47,184,56,116,10,234,51,186, +24,239,73,23,131,248,232,129,138,76,94,200,32,127,124,170,237,91,238,235,34,99,77,13,109,124,12,80,81,243, +236,159,23,17,203,70,179,182,70,43,14,232,81,100,94,204,135,63,143,2,1,10,13,229,42,93,235,230,247,20, +48,189,68,195,199,161,26,15,231,150,90,121,177,165,22,172,211,3,160,201,22,91,224,223,160,237,212,104,68,239, +250,54,135,181,168,173,133,155,85,43,161,4,145,214,56,179,144,123,197,77,227,189,210,3,10,230,217,194,207,91, +112,125,147,218,249,148,211,134,212,206,165,198,135,207,226,5,27,133,54,183,84,106,119,21,130,147,54,212,54,97, +111,170,199,210,29,64,171,212,237,245,71,72,163,99,36,247,188,113,87,159,46,147,232,88,154,255,142,254,91,140, +172,180,56,37,254,63,139,197,130,98,94,13,169,32,133,140,230,248,137,220,227,241,198,127,150,158,139,29,19,103, +21,47,237,78,253,24,225,43,193,144,109,172,53,223,157,178,76,2,114,105,138,242,229,123,84,41,116,193,198,12, +13,128,146,194,38,88,253,230,207,161,224,35,86,236,223,112,190,234,190,225,252,57,224,13,145,137,107,47,71,111, +44,206,48,195,232,80,250,62,19,141,134,215,101,67,225,18,119,196,161,153,160,235,47,165,232,251,115,103,132,62, +222,205,55,149,253,250,211,49,236,152,164,198,170,70,240,171,191,223,52,127,219,160,207,147,214,103,135,253,85,24, +221,24,248,176,34,127,54,56,114,222,20,50,93,199,222,203,189,65,16,225,215,162,133,240,97,225,158,251,248,101, +116,222,74,247,76,185,163,182,157,203,91,201,29,123,2,154,77,173,244,98,22,187,64,20,39,126,209,216,77,197, +206,159,45,86,247,236,56,0,5,104,229,21,15,155,23,107,112,150,123,11,165,103,154,138,196,199,78,191,24,111, +102,190,82,122,33,188,0,204,221,66,120,8,59,196,242,98,139,88,55,216,253,31,141,176,62,10,114,46,27,196, +148,2,41,51,91,202,32,51,79,11,190,150,206,130,104,103,138,101,52,34,168,194,227,246,120,48,134,2,145,26, +12,154,102,112,82,254,200,152,50,219,219,241,40,6,20,44,59,234,168,175,0,82,208,143,177,154,96,42,27,152, +137,6,97,214,88,97,69,130,229,211,246,202,83,179,63,69,104,16,5,243,35,54,220,74,255,226,131,3,226,210, +195,72,102,215,118,188,24,52,241,144,249,233,101,188,50,60,60,58,228,141,4,80,146,83,187,49,157,195,165,59, +220,19,255,208,192,9,24,144,253,7,8,78,245,81,93,35,9,120,4,186,124,205,3,246,52,25,160,182,178,247, +38,246,127,56,121,193,139,149,30,122,177,130,88,247,217,210,54,230,157,106,66,221,93,108,9,31,12,91,144,204, +217,56,161,221,211,117,228,155,98,180,40,11,190,28,124,12,223,92,28,113,183,45,8,244,75,224,44,58,0,242, +129,33,39,95,228,194,238,126,29,117,181,39,61,168,226,74,41,165,155,47,128,171,144,113,65,133,186,9,117,152, +50,226,104,52,100,129,96,31,191,147,252,123,179,244,31,210,114,109,160,175,241,220,148,164,24,135,0,246,249,162, +39,93,185,187,184,48,10,126,23,23,22,49,120,201,222,171,227,98,33,95,25,23,22,241,118,33,173,165,166,248, +106,33,63,108,46,45,212,249,176,144,246,227,20,62,8,169,122,191,104,221,145,126,189,80,172,59,160,158,57,77, +192,198,107,2,206,34,173,88,133,112,114,34,189,71,199,29,59,246,52,251,162,65,7,162,214,64,104,131,248,142, +245,121,42,164,22,94,135,241,213,98,223,111,149,87,41,108,172,165,80,227,116,207,193,179,215,92,194,155,95,64, +24,87,55,175,125,41,202,139,120,116,57,211,113,221,218,156,191,121,143,21,206,161,53,254,65,49,127,195,213,176, +12,129,76,200,99,200,68,182,62,214,25,203,163,107,61,44,143,222,56,211,84,70,35,26,193,96,79,184,252,26, +163,222,35,174,167,142,67,215,21,230,252,160,116,30,10,32,239,51,240,194,76,56,17,238,150,235,81,248,222,169, +112,232,122,139,244,40,105,45,202,214,24,28,70,119,41,246,21,191,48,90,154,169,142,106,180,199,58,216,39,51, +184,35,234,89,107,148,37,35,231,240,229,39,136,18,129,125,177,233,193,30,177,99,231,172,126,133,117,33,190,15, +205,159,105,236,72,3,139,3,91,160,144,141,252,218,58,38,217,235,137,144,191,45,32,201,123,129,17,237,92,195, +225,88,153,247,224,38,121,147,173,178,166,102,203,191,16,253,210,184,178,159,199,13,26,69,112,1,221,114,215,126, +168,17,86,193,74,99,247,114,109,202,196,5,102,142,107,159,235,187,172,248,46,185,69,6,192,180,7,155,177,164, +97,153,103,0,30,140,53,48,184,232,10,52,25,174,225,163,222,155,2,223,115,26,30,180,167,10,118,117,161,106, +235,223,225,35,86,23,138,137,83,86,169,149,203,218,23,1,47,237,37,227,221,54,237,79,63,68,41,35,229,167, +113,221,185,43,179,50,46,143,106,220,0,184,164,227,19,177,191,83,249,105,140,75,69,133,177,76,47,79,246,247, +59,247,22,51,29,125,21,31,139,192,69,81,3,78,149,20,96,6,227,177,168,201,22,186,85,216,173,90,127,84, +91,234,53,187,58,239,30,135,225,93,213,24,183,236,13,187,170,157,161,131,102,0,60,94,173,159,206,232,81,16, +51,228,126,193,137,161,95,16,245,111,60,168,3,139,102,32,82,180,123,78,217,3,37,108,2,151,15,149,195,170, +113,127,216,254,211,9,194,93,224,160,15,192,198,216,164,5,85,90,80,158,71,81,51,233,207,178,120,220,95,165, +78,103,112,7,237,205,154,217,165,60,95,232,8,191,121,166,253,62,192,110,116,187,192,163,194,185,36,111,243,216, +138,137,132,68,147,41,24,114,0,57,251,253,29,187,138,105,76,65,233,71,131,35,97,231,245,61,196,136,92,60, +183,107,184,27,230,10,40,143,70,127,42,171,237,104,10,17,142,108,86,59,51,212,152,133,185,99,72,244,90,240, +253,237,21,106,210,127,187,239,178,121,103,207,121,140,182,248,244,58,46,37,2,9,99,102,197,184,157,131,241,212, +198,144,140,76,33,95,19,167,152,98,78,112,188,52,96,39,251,4,80,41,94,72,178,31,245,220,214,54,71,237, +186,141,42,81,28,102,173,96,254,165,1,62,43,83,239,37,62,44,94,168,135,63,69,40,126,135,191,137,144,87, +248,155,10,121,163,162,203,201,74,60,142,22,112,88,205,97,191,86,223,20,145,141,92,63,6,165,227,141,220,74, +99,20,25,175,138,235,167,39,122,114,242,39,48,142,120,1,255,110,29,239,140,206,221,170,149,244,117,217,158,79, +95,218,163,158,234,44,143,46,31,95,139,137,9,46,242,18,14,240,10,194,242,229,179,53,236,98,211,222,203,71, +215,212,154,144,63,69,57,158,155,173,45,190,46,111,162,147,99,124,168,185,14,234,187,126,180,21,143,183,66,6, +0,115,22,157,170,94,11,143,174,229,135,78,39,48,74,196,144,113,5,9,151,66,194,32,192,99,20,92,91,47, +147,40,74,38,149,120,92,202,235,199,96,119,79,204,34,236,62,239,64,22,195,178,185,174,229,82,96,135,108,232, +165,60,85,21,84,152,136,248,10,123,113,49,171,226,83,8,223,206,146,24,230,77,165,147,19,204,251,97,114,10, +121,161,249,151,244,125,45,111,116,244,82,6,205,188,20,174,249,78,235,80,40,152,76,8,178,9,116,15,77,255, +90,68,48,165,240,247,20,82,187,115,135,19,58,251,53,134,41,60,13,107,61,53,115,40,63,132,113,31,76,156, +217,7,175,200,84,7,204,81,4,239,31,167,200,59,4,28,173,15,104,229,233,83,16,211,127,53,30,227,96,130, +154,162,211,241,43,152,108,219,70,37,255,130,214,223,111,100,38,132,201,44,98,87,76,76,95,61,125,9,175,241, +175,68,183,234,3,85,121,200,14,11,7,221,250,128,110,14,102,154,37,51,110,16,190,120,56,68,128,70,38,216, +129,196,118,96,54,144,65,37,113,183,249,164,133,133,122,8,60,211,15,10,26,128,78,119,147,63,180,29,141,91, +168,28,145,199,221,146,65,127,172,241,100,191,119,52,78,209,122,60,244,162,137,240,253,71,69,218,218,0,168,97, +91,213,194,14,56,45,107,248,134,237,143,47,96,178,82,71,255,245,213,163,230,81,52,2,18,219,221,247,110,252, +188,37,27,216,180,149,32,53,199,213,231,163,149,6,158,118,16,74,40,70,225,123,16,79,200,120,15,58,26,226, +117,76,186,55,48,48,143,249,243,85,6,87,71,104,3,196,90,88,17,0,95,24,181,27,55,194,222,40,227,134, +94,77,16,38,154,166,44,129,227,17,53,35,145,112,218,252,93,87,37,92,216,221,23,144,16,139,211,33,22,87, +220,131,197,213,152,217,241,223,253,163,164,122,86,43,61,171,227,92,38,24,40,85,1,248,143,97,224,52,94,128, +230,59,227,245,57,197,223,18,106,124,10,136,69,250,244,120,6,12,56,17,231,207,48,132,127,42,8,182,68,148, +148,168,184,141,69,79,166,81,249,204,89,156,249,238,244,231,139,15,167,175,208,226,204,199,175,191,249,250,61,176, +243,159,114,218,235,183,157,52,227,76,210,191,168,150,143,142,142,191,66,77,159,168,28,163,44,29,188,104,69,245, +36,23,45,163,141,181,71,27,141,114,29,78,47,98,192,135,141,38,35,204,181,243,67,23,148,201,30,107,115,175, +25,230,39,78,170,100,20,161,152,69,117,0,195,92,115,143,139,206,189,224,58,4,209,227,19,89,63,3,88,8, +67,193,30,148,185,62,186,73,170,34,250,135,241,191,9,146,7,228,112,109,222,218,206,28,113,195,15,190,216,21, +237,131,155,114,147,207,31,84,186,134,75,252,1,217,99,4,58,243,193,102,253,160,41,33,75,221,62,176,229,30, +96,207,49,9,227,79,142,143,143,143,254,129,23,11,130,97,17,59,188,155,205,73,243,188,72,141,143,95,39,168, +18,0,93,172,67,71,136,181,128,10,218,189,34,124,18,14,217,16,234,35,236,195,86,209,121,238,67,186,40,104, +101,90,248,91,225,137,44,130,247,121,198,37,10,73,104,72,67,116,135,65,67,26,236,190,217,233,13,22,14,208, +17,125,196,223,22,111,209,60,219,22,81,129,8,243,27,224,40,22,38,112,24,38,44,128,142,3,168,114,8,47, +161,186,32,4,18,184,93,124,7,146,59,97,243,36,211,242,3,103,133,112,193,190,231,203,74,1,18,86,203,210, +163,246,3,148,214,77,25,85,210,100,31,25,24,54,18,178,113,239,117,179,40,124,186,11,0,167,219,193,30,116, +186,205,11,187,198,231,227,88,206,199,37,133,172,60,69,209,95,112,191,200,218,87,81,112,89,4,50,195,20,72, +143,138,67,62,114,151,188,175,85,84,76,0,231,247,126,55,249,54,148,39,2,4,193,245,68,213,178,24,171,186, +221,187,5,116,255,2,40,246,97,63,84,62,72,116,209,2,124,13,129,64,115,137,187,155,151,120,174,229,62,148, +33,78,176,104,233,138,251,222,107,242,175,178,221,61,68,187,165,214,99,61,72,128,51,248,251,2,237,91,53,177, +247,139,9,49,26,16,133,248,196,198,28,186,115,134,206,120,184,138,253,45,14,75,57,243,226,49,161,92,140,44, +16,47,216,31,124,120,14,16,42,69,141,71,26,138,0,105,40,24,105,40,85,71,104,8,187,134,140,77,215,231, +99,62,9,30,24,235,199,12,188,192,92,115,25,10,62,214,226,110,186,244,223,73,113,30,166,5,255,8,1,216, +182,223,23,72,221,185,199,163,41,4,15,211,118,239,252,195,67,85,31,1,130,162,171,44,13,105,186,127,134,6, +50,178,199,33,158,29,82,31,198,102,134,128,217,57,241,88,226,175,61,114,176,155,223,192,92,33,100,17,222,146, +24,215,105,67,11,17,242,101,214,208,142,129,12,135,251,209,96,186,16,80,174,234,182,72,170,192,65,92,217,105, +170,130,228,92,85,128,171,132,177,140,84,64,50,88,177,157,151,15,186,188,23,244,249,247,107,89,197,48,79,37, +234,180,142,199,137,76,112,30,142,141,94,238,9,68,84,88,45,112,88,102,39,72,147,148,33,253,145,60,234,246, +224,17,40,189,228,211,155,101,134,42,157,79,1,168,87,80,21,220,183,201,83,190,215,82,154,132,228,214,67,249, +94,159,210,94,159,106,130,36,111,138,255,123,100,249,32,90,204,204,214,85,200,78,192,244,158,45,0,169,207,13, +191,186,64,134,155,216,217,74,209,127,191,122,200,6,138,105,216,95,68,5,50,65,193,213,99,97,248,247,237,191, +15,58,242,109,129,60,108,83,121,23,88,134,25,116,144,129,193,89,128,152,195,78,232,140,66,220,3,101,135,19, +254,8,111,246,65,225,47,213,154,47,85,102,252,3,50,15,25,102,200,135,233,161,249,17,26,21,82,207,14,31, +183,92,136,113,42,166,184,88,53,202,222,60,133,125,30,149,17,160,137,21,100,22,34,134,64,18,21,210,176,109, +43,248,172,37,124,193,55,230,4,94,4,198,216,196,218,68,84,38,55,134,121,202,96,254,92,231,1,243,161,205, +183,249,244,73,215,141,158,195,2,194,14,64,162,53,226,17,30,195,166,224,86,127,63,179,216,227,158,102,207,144, +52,104,13,178,19,132,46,6,49,201,109,43,11,5,144,206,222,228,247,161,93,197,97,180,171,248,191,67,187,138, +97,252,132,175,52,58,191,179,209,241,40,254,215,209,149,65,212,142,122,53,29,196,221,186,64,4,97,247,62,255, +28,163,121,36,19,12,220,117,65,71,126,52,214,100,55,110,133,200,247,195,243,140,157,207,189,70,220,121,151,99, +118,42,140,92,251,200,116,224,95,189,222,249,38,60,120,157,243,254,9,15,220,30,54,160,7,184,190,237,27,186, +245,203,79,73,149,53,203,85,150,142,166,111,62,255,234,247,229,232,246,8,197,167,58,246,82,211,162,39,92,134, +245,34,192,102,43,133,40,160,70,159,60,102,107,237,18,27,158,87,229,250,93,207,170,215,215,228,138,8,177,247, +5,11,255,124,235,191,197,88,59,137,106,42,18,152,229,251,169,111,161,75,171,111,44,62,11,207,99,114,119,19, +127,145,96,6,167,18,163,133,92,198,142,37,246,168,9,208,191,214,215,249,119,54,253,38,107,174,215,60,119,90, +107,129,245,108,103,86,36,110,38,232,55,11,95,135,155,49,124,181,113,246,20,179,60,11,51,216,228,54,118,49, +46,123,208,222,219,69,56,171,187,60,206,172,243,162,204,27,63,199,176,172,226,140,156,100,5,41,228,168,12,146, +154,114,29,20,193,160,188,132,104,203,248,15,138,80,76,43,245,190,238,128,113,8,117,118,238,236,157,96,161,50, +43,232,209,136,166,13,13,254,50,28,8,146,165,113,37,66,38,124,57,118,246,243,227,50,62,230,167,44,167,80, +157,143,199,94,87,188,10,133,246,141,193,90,95,1,69,227,101,51,69,181,107,149,58,225,120,186,194,150,138,75, +120,11,4,185,204,140,192,34,100,67,89,193,49,150,148,137,176,154,136,41,25,156,159,43,220,61,198,62,236,66, +118,134,138,217,225,98,195,230,230,212,204,70,45,168,111,230,145,252,180,248,148,107,232,212,24,42,93,135,104,221, +186,136,54,2,213,20,97,23,173,37,168,86,200,249,209,141,60,150,39,255,125,44,228,165,139,221,66,236,82,254, +207,177,124,242,95,128,126,252,104,238,16,185,49,158,28,219,204,204,134,159,71,36,90,39,176,103,209,38,231,4, +110,13,116,48,7,225,6,194,151,16,190,20,221,190,91,195,20,191,192,200,204,254,245,219,236,71,140,218,19,27, +102,36,151,105,45,205,248,51,199,35,221,133,241,83,246,225,139,126,184,236,245,243,20,186,55,139,18,101,186,73, +113,248,184,0,155,216,51,138,32,32,49,61,17,34,46,240,138,2,105,63,107,242,38,50,65,28,151,45,84,121, +142,78,102,198,90,141,161,144,172,185,177,102,22,229,42,194,57,168,93,99,21,148,107,194,198,26,51,71,57,52, +86,83,99,151,134,77,24,153,32,78,154,45,116,25,54,118,41,33,126,156,135,246,255,204,44,6,24,44,157,141, +225,99,81,250,99,1,243,7,160,178,68,45,246,68,117,118,34,58,39,14,15,205,224,129,169,131,3,147,162,94, +236,83,224,196,242,129,25,222,241,169,76,198,213,88,159,165,231,248,128,180,232,109,73,216,187,176,154,184,103,199, +127,17,2,183,126,131,57,55,234,155,69,228,118,227,2,119,242,15,139,104,129,187,247,111,24,111,247,238,26,78, +2,209,24,183,49,70,110,227,77,224,22,97,109,61,95,172,156,79,117,107,105,99,140,37,201,23,198,6,2,203, +86,176,51,53,63,191,208,90,214,129,176,199,22,194,194,97,153,57,141,49,0,170,24,236,218,65,13,172,152,238, +95,0,129,41,200,137,106,98,221,181,34,138,113,168,228,152,5,182,62,135,234,248,31,232,11,254,194,9,157,217, +50,113,164,159,65,8,162,159,254,207,177,160,170,58,21,253,213,84,20,250,133,15,28,135,7,171,30,23,104,11, +150,215,184,86,205,228,100,90,63,131,165,174,39,19,127,54,139,207,128,139,181,16,86,75,180,180,80,141,117,44, +131,37,74,101,206,230,70,227,5,45,209,156,151,167,85,251,0,4,237,116,239,220,149,109,5,254,214,173,50,94, +253,224,53,117,205,150,219,87,120,185,151,251,151,123,168,131,185,150,218,43,173,47,39,43,123,155,45,224,3,239, +168,249,100,57,94,145,18,213,102,178,128,111,103,32,20,60,118,232,30,92,198,110,193,25,234,105,172,202,202,249, +242,40,143,204,111,56,246,97,95,29,29,207,28,139,170,111,235,149,28,91,103,56,94,45,234,163,164,74,97,1, +110,45,84,134,62,109,233,11,93,137,252,32,166,236,223,188,28,58,154,70,20,170,70,149,81,50,156,131,26,81, +161,116,199,9,72,119,20,70,184,99,176,60,52,67,158,150,131,242,97,255,127,30,16,1,35,207,17,40,239,155, +85,233,38,79,120,142,170,192,241,85,130,166,153,209,251,223,195,2,180,91,42,248,151,224,246,134,61,30,57,23, +212,61,175,206,85,199,231,115,210,115,233,220,4,242,155,3,94,166,155,125,241,206,174,99,100,179,18,86,179,68, +214,161,90,39,247,2,63,67,101,47,255,48,57,108,171,213,10,111,105,210,135,106,200,68,171,223,82,163,214,189, +39,62,215,33,179,245,78,22,9,109,132,174,149,206,253,184,16,240,135,241,225,150,102,99,168,251,119,184,85,79, +123,153,173,116,81,247,13,96,246,61,42,165,69,135,163,42,140,157,100,213,119,230,68,46,110,216,64,175,44,66, +223,80,156,231,219,174,169,222,105,103,204,253,39,44,131,170,106,116,215,99,62,5,207,198,96,110,68,82,11,204, +140,95,98,127,150,130,252,193,43,83,129,227,249,215,57,60,39,61,14,15,108,119,166,0,251,172,112,78,180,84, +196,241,239,231,138,239,241,156,251,131,124,220,95,44,209,210,163,157,169,199,110,11,220,116,29,118,217,126,150,80, +25,74,12,108,172,190,200,151,85,94,180,82,169,12,39,254,214,233,74,23,53,33,114,81,158,65,153,243,46,147, +163,134,55,89,188,175,103,53,250,147,19,108,183,208,214,190,175,252,104,60,52,102,121,214,108,163,2,79,237,193, +231,214,105,64,74,54,97,119,92,244,12,200,37,211,149,24,255,246,112,101,86,245,104,123,241,140,247,134,123,121, +28,108,55,100,221,107,220,102,188,127,247,115,20,147,154,115,132,11,58,97,220,115,127,173,159,72,135,98,114,31, +68,219,165,34,2,238,192,15,143,163,189,117,100,173,221,19,209,149,94,36,252,215,84,130,166,123,104,105,22,48, +218,71,122,140,178,23,182,169,151,153,21,56,120,85,149,43,59,172,144,123,210,19,69,133,157,207,154,9,123,131, +97,118,204,132,185,42,211,33,233,123,230,108,249,252,226,145,142,163,134,203,65,48,100,147,236,119,241,190,174,97, +95,238,239,221,221,221,227,252,58,118,37,198,186,29,66,179,194,53,234,47,16,28,5,171,81,217,23,165,244,84, +3,202,1,82,79,240,158,218,211,75,151,13,250,104,106,247,110,126,242,39,212,179,103,210,223,61,147,191,140,11, +182,207,31,10,215,192,46,8,183,59,96,133,76,228,113,34,163,50,134,58,136,235,253,94,248,205,34,117,247,153, +106,160,191,20,127,215,158,211,194,11,42,114,193,187,43,246,93,128,125,206,109,96,13,206,145,80,119,213,184,94, +198,197,13,2,172,13,2,92,16,2,92,59,4,184,108,213,224,5,236,23,237,158,226,125,107,40,174,209,190,202, +76,35,63,85,217,60,222,57,140,12,221,215,236,235,237,242,182,97,143,122,211,194,225,98,69,31,91,194,44,159, +51,233,221,183,108,33,181,60,4,103,176,145,16,241,42,66,171,82,20,194,104,143,132,217,241,127,3,67,187,223, +27,160,220,153,125,134,8,99,29,23,118,62,106,52,24,92,238,173,193,176,137,65,29,222,9,254,170,248,235,130, +13,106,6,177,24,229,89,213,95,39,233,50,138,82,185,196,251,15,170,90,62,52,175,76,137,250,140,249,75,73, +140,119,234,108,232,222,105,104,194,72,170,79,127,166,62,45,100,2,253,66,77,252,192,61,39,244,199,27,89,168, +84,9,4,96,133,4,96,229,9,192,84,21,123,141,12,2,167,74,8,135,220,47,3,228,126,209,170,116,250,112, +1,72,253,18,133,209,2,180,125,209,179,36,183,236,89,143,75,67,52,126,223,114,92,186,143,198,127,214,36,106, +90,139,46,252,69,204,141,223,43,114,53,12,89,42,228,222,245,205,196,17,69,213,129,113,33,76,163,174,99,30, +116,7,6,206,192,66,139,112,162,109,250,123,248,185,25,22,236,98,19,34,188,233,254,61,93,40,26,153,181,37, +50,232,235,114,24,120,31,11,103,101,120,26,88,220,112,126,36,245,221,67,171,202,6,243,212,248,237,173,76,121, +103,191,7,108,74,13,29,10,56,92,116,40,114,171,235,240,80,187,85,226,81,220,181,39,3,46,173,92,6,60, +86,156,144,242,115,182,6,119,10,89,175,116,214,100,138,46,187,237,50,60,39,6,3,32,112,61,191,69,149,106, +58,46,154,72,171,149,28,53,117,172,233,165,71,29,142,6,159,101,164,159,246,89,24,129,165,189,73,245,248,201, +100,97,249,22,147,114,178,52,239,18,24,101,56,24,213,120,65,141,217,4,8,6,44,140,70,82,143,228,49,148, +149,75,119,82,83,203,170,104,69,219,49,95,226,237,175,226,54,108,159,107,243,180,131,70,7,147,252,13,137,117, +64,36,191,237,4,30,216,73,95,78,119,157,177,179,59,162,145,12,64,111,88,204,67,140,147,80,83,243,236,124, +95,63,243,184,237,95,97,40,141,198,104,40,210,75,244,214,180,183,114,216,210,61,178,39,50,100,145,245,184,79, +68,62,187,72,175,130,238,7,66,142,36,113,9,192,233,103,203,173,133,60,198,150,117,217,191,146,123,172,87,28, +76,235,103,151,148,42,213,110,228,39,206,46,26,240,33,3,213,216,145,12,248,9,62,71,74,105,56,33,253,88, +106,133,245,46,85,120,45,134,186,142,56,219,35,118,68,249,49,195,119,233,60,207,106,13,17,184,12,229,106,85, +22,56,118,59,106,35,178,88,163,59,253,86,14,231,193,52,202,245,103,152,35,128,190,48,198,189,76,127,214,127, +10,50,45,203,77,181,151,229,203,63,235,175,40,207,147,63,181,114,158,108,247,178,252,247,159,255,196,121,190,132, +122,110,180,190,242,153,78,168,169,227,63,253,55,231,130,138,32,173,89,238,85,245,228,207,79,254,91,255,217,13, +239,73,43,255,137,74,249,186,218,171,238,191,254,251,191,255,4,25,185,186,173,78,6,250,126,242,213,159,244,127, +161,58,125,227,222,192,174,244,182,142,62,102,34,16,127,178,204,92,222,58,147,192,119,208,23,53,166,221,165,199, +167,145,169,154,204,147,53,66,238,157,145,117,169,226,66,90,59,6,181,204,234,242,39,152,13,156,181,178,197,172, +152,3,249,10,245,148,53,190,58,250,80,133,82,35,215,60,178,177,43,85,152,123,255,11,248,11,183,123,165,124, +62,86,51,37,25,27,99,151,61,230,0,50,140,59,186,155,81,109,234,51,2,11,184,68,88,253,59,29,149,80, +111,169,20,10,173,64,77,230,140,127,191,128,186,70,212,119,180,7,28,135,9,200,138,30,87,1,75,174,41,247, +249,146,169,19,193,100,254,103,9,113,172,175,10,11,80,62,173,39,39,161,27,209,74,125,204,206,210,6,92,137, +158,155,55,70,179,182,51,250,141,15,200,140,27,87,170,71,184,238,232,169,197,51,99,34,61,129,23,35,144,190, +170,236,227,178,120,170,216,226,128,105,163,245,1,232,199,121,240,44,220,121,63,11,123,207,66,165,211,242,89,56, +24,13,131,241,232,92,165,76,253,216,49,24,79,117,206,157,243,27,5,48,135,5,202,240,22,178,18,207,84,227, +85,118,171,160,87,122,214,105,34,62,14,250,248,5,206,32,247,173,233,206,236,248,68,106,140,233,121,162,181,253, +129,138,155,115,234,83,56,33,77,80,187,166,245,180,46,100,181,200,32,21,182,135,97,127,63,216,215,174,221,229, +37,58,100,203,16,193,191,46,172,114,106,169,52,152,161,133,177,205,240,55,214,103,53,76,200,89,137,213,180,193, +198,153,239,111,156,224,56,149,106,92,243,190,107,206,142,157,234,75,129,59,27,186,220,87,121,177,207,150,50,55, +91,46,81,37,88,197,84,213,52,193,90,224,50,136,18,121,2,69,69,174,180,177,140,105,168,248,168,65,60,196, +72,67,224,1,224,195,232,251,152,149,131,111,131,59,35,156,221,35,93,76,195,21,98,247,198,28,103,37,18,232, +102,5,217,161,65,85,201,162,171,143,35,77,179,120,25,113,187,165,125,27,123,168,103,69,140,147,131,123,80,106, +199,185,126,173,63,87,184,143,124,14,41,99,63,12,111,120,107,1,4,191,224,202,129,159,150,178,109,138,172,65, +75,24,219,17,69,152,62,253,128,177,29,70,182,149,188,198,97,83,132,117,223,12,39,107,14,130,234,20,199,176, +45,116,22,213,51,180,6,200,223,10,149,127,237,7,212,231,221,113,208,178,27,67,75,87,233,209,133,181,6,120, +68,209,245,17,134,197,20,112,103,198,188,95,25,220,162,150,78,66,169,142,132,216,235,11,3,229,35,251,97,97, +51,4,205,111,0,161,33,202,7,90,217,81,234,222,31,181,62,242,1,47,3,57,36,115,101,128,239,23,108,203, +177,117,182,75,182,128,118,176,214,109,55,242,51,23,113,144,85,126,88,205,34,156,101,233,214,226,8,182,0,176, +91,204,30,232,107,7,201,64,14,177,10,229,16,147,97,57,68,127,169,230,81,42,118,15,43,102,178,167,134,77, +215,213,42,169,165,141,21,242,97,18,102,76,110,49,99,25,168,85,75,27,43,218,200,190,93,97,122,78,188,14, +199,248,118,125,16,50,114,146,121,15,89,50,15,221,200,145,74,13,224,56,169,198,20,59,155,35,168,11,171,234, +191,36,24,161,228,47,162,90,112,207,106,1,76,240,177,191,8,97,202,113,15,220,68,2,1,82,9,153,75,159, +185,20,179,18,51,195,97,237,103,29,159,176,184,98,56,23,37,220,2,254,133,130,199,94,67,246,82,180,253,97, +246,22,217,165,126,132,5,133,254,173,214,78,40,84,31,246,238,93,184,164,183,95,127,115,218,73,98,40,232,173, +67,107,133,240,87,22,93,176,43,172,82,174,54,187,165,104,63,91,217,103,165,237,246,195,156,104,99,149,86,69, +249,85,153,29,28,22,137,102,186,103,18,24,231,144,32,102,237,187,206,147,221,145,242,132,119,13,28,16,79,120, +71,224,19,19,235,96,152,44,69,235,234,146,149,151,218,76,212,50,199,245,147,21,131,241,0,182,186,243,5,64, +43,217,52,229,135,171,108,61,107,202,72,99,45,8,102,37,87,201,21,118,247,245,139,196,154,140,66,217,246,248, +239,196,106,74,168,115,242,142,122,132,216,3,233,192,179,48,1,111,183,208,247,20,103,15,145,232,209,204,130,173, +24,112,12,159,74,117,33,52,180,36,98,109,249,17,108,13,48,9,100,91,179,146,122,217,239,128,104,131,10,240, +210,178,91,244,216,58,211,151,229,244,78,109,31,132,30,125,185,74,102,44,224,246,20,210,101,69,217,146,153,86, +39,147,58,214,42,58,92,6,214,214,188,245,200,242,96,197,225,134,239,183,80,168,50,46,84,84,78,62,163,240, +147,115,129,143,74,172,106,233,18,158,126,137,86,32,142,158,124,53,213,170,64,73,8,84,18,144,5,126,23,246, +187,123,3,147,128,161,21,56,44,228,34,65,4,32,62,121,28,233,241,201,24,95,23,248,104,244,207,32,131,254, +65,245,43,185,103,91,179,182,39,181,82,37,237,97,216,183,37,239,55,45,139,67,59,85,27,45,140,175,163,210, +171,212,157,24,197,11,166,60,102,101,112,213,26,35,66,234,157,54,74,242,185,161,68,228,18,48,3,3,192,22, +74,203,185,220,32,54,157,194,6,88,168,113,195,16,120,17,208,40,200,145,234,37,166,51,115,169,197,56,133,22, +227,46,164,70,140,251,68,127,245,40,217,51,203,170,199,163,7,73,49,7,219,95,5,126,85,26,232,154,242,193, +34,169,30,192,184,42,240,119,158,53,203,7,110,64,15,202,5,100,76,32,227,104,92,9,239,209,32,188,101,216, +200,24,89,192,118,111,164,29,32,141,24,227,92,45,140,95,132,57,72,135,204,113,12,136,173,206,101,130,139,191, +25,143,133,46,209,73,175,92,59,248,2,5,140,144,42,155,88,9,238,185,13,110,76,184,135,184,140,12,9,223, +165,32,39,114,40,129,168,158,173,38,151,246,181,120,165,158,141,87,226,126,195,34,33,246,16,30,85,220,40,172, +22,235,172,59,90,236,108,166,9,57,139,26,217,75,18,113,152,214,69,234,44,186,135,245,194,229,135,131,179,209, +175,8,193,184,219,46,170,217,179,88,182,143,40,38,202,131,52,153,171,30,120,146,41,58,104,168,144,58,88,170, +28,191,242,115,216,84,104,129,71,206,49,2,64,208,2,254,183,64,84,110,186,115,226,135,82,3,184,159,207,150, +113,42,80,212,174,164,61,225,216,84,110,158,214,179,191,69,107,121,182,145,254,213,61,222,12,74,8,16,156,196, +241,154,253,98,33,38,19,31,26,182,205,120,172,69,173,26,236,41,121,132,160,206,13,76,93,77,100,148,70,100, +148,129,214,189,186,112,225,171,238,208,27,108,40,179,63,184,123,24,134,201,226,48,180,13,239,208,97,213,59,194, +192,198,248,182,124,100,193,223,125,162,248,247,180,205,69,30,187,10,39,6,115,243,93,161,71,227,226,209,208,184, +25,0,34,96,8,27,222,215,164,144,254,177,175,195,204,110,28,27,219,216,82,24,208,179,156,105,108,116,88,153, +24,145,207,224,53,88,86,42,120,254,149,201,125,202,147,134,63,226,30,64,111,226,226,81,57,6,174,137,92,194, +87,5,95,101,219,238,67,248,67,131,36,212,110,239,220,213,120,136,204,45,114,254,191,255,91,28,5,188,77,119, +243,14,159,241,99,198,37,128,5,177,135,77,132,150,174,59,107,80,118,181,0,7,39,212,203,76,61,174,142,110, +66,197,85,8,47,197,228,132,221,142,130,238,89,18,159,180,251,208,27,207,101,207,77,177,177,22,136,130,2,120, +170,241,210,114,167,180,239,175,216,99,50,208,207,116,9,124,60,35,58,147,107,180,76,8,85,99,217,62,205,201, +104,45,215,214,111,89,33,82,219,179,99,120,154,231,239,144,60,181,79,194,150,12,21,1,28,169,251,112,4,6, +132,85,164,9,250,16,209,159,85,157,152,30,234,144,137,224,33,160,106,207,62,82,63,236,241,249,40,247,82,23, +56,207,159,61,161,4,54,239,30,163,101,197,56,170,28,199,25,14,162,223,9,213,95,138,89,19,239,143,44,8, +48,8,125,3,251,215,222,185,255,92,160,79,131,215,246,161,7,15,202,104,250,58,124,224,33,251,5,22,99,144, +142,235,129,166,128,49,115,76,108,12,68,150,240,28,225,111,133,37,240,163,139,75,17,134,22,143,130,99,54,146, +157,19,137,213,182,252,132,99,144,149,120,132,244,201,104,79,51,232,164,237,232,10,179,113,107,92,18,131,182,171, +204,43,219,151,210,62,224,235,25,74,201,100,192,250,67,163,220,40,44,3,129,154,2,81,200,48,124,211,64,133, +35,136,31,201,6,159,183,225,43,46,237,136,147,214,212,96,227,42,27,151,183,166,34,17,115,253,24,237,27,192, +208,96,11,152,64,77,152,138,74,137,181,82,19,20,87,97,28,55,193,47,161,213,164,116,59,35,157,37,227,8, +213,27,224,34,152,148,226,113,26,39,196,155,251,232,21,111,95,223,199,155,107,112,106,189,60,40,172,215,187,210, +241,204,194,44,70,6,145,226,67,202,41,56,47,12,252,252,105,66,243,102,88,218,42,231,115,109,244,109,72,244, +55,101,121,181,89,99,46,115,227,118,186,1,43,172,37,95,110,123,221,161,116,86,215,11,203,50,203,44,164,241, +218,253,22,169,251,29,222,129,233,157,247,191,236,133,65,208,45,172,227,171,202,196,99,61,213,211,196,176,88,83, +203,98,77,159,41,141,102,104,208,79,11,169,74,167,214,38,166,163,179,158,176,105,47,179,216,218,44,246,113,75, +107,95,152,224,73,123,30,180,85,247,219,90,2,124,173,198,39,192,137,195,143,9,124,164,248,113,46,67,211,78, +203,49,186,53,68,163,234,64,176,148,182,43,182,141,212,180,81,193,171,4,240,122,2,166,111,59,188,128,131,247, +11,32,145,8,17,239,130,132,90,29,162,54,100,113,7,187,136,106,241,134,167,10,250,154,53,125,40,174,221,253, +80,8,17,55,74,187,140,56,141,123,221,85,141,108,238,68,51,35,216,82,193,54,147,77,119,87,137,199,253,61, +248,127,139,237,245,122,83,60,234,55,63,238,236,121,99,145,254,99,193,80,189,214,85,166,235,209,20,162,24,182, +7,112,222,120,37,208,243,123,140,212,190,32,179,125,31,80,35,55,94,103,210,138,4,216,224,247,133,124,227,85, +57,109,220,155,66,190,15,100,7,108,228,115,45,113,133,109,224,53,5,76,247,108,212,199,130,109,217,66,239,207, +110,82,249,247,37,24,208,149,122,126,238,33,62,154,121,165,141,40,1,104,101,192,51,65,62,224,174,245,106,45, +168,207,194,210,144,211,122,12,4,104,161,126,70,254,5,190,251,176,217,228,20,54,116,163,222,68,163,52,41,174, +147,122,36,228,235,34,50,18,145,114,21,25,207,167,223,195,79,3,31,50,59,251,243,57,194,14,185,134,24,120, +54,18,59,155,85,173,202,72,203,179,26,59,33,32,57,139,55,144,31,255,204,35,160,5,225,114,121,23,53,84, +28,39,82,180,193,107,83,61,119,119,215,167,236,168,2,9,163,26,229,79,143,142,96,233,12,179,212,242,217,97, +104,158,207,78,246,161,225,219,49,225,219,115,185,5,3,251,87,116,53,111,223,67,61,48,131,152,130,198,161,172, +157,248,218,154,230,24,153,24,246,117,161,118,54,203,218,122,83,136,209,227,56,70,36,134,198,66,72,167,86,9, +140,242,140,80,128,17,214,5,63,84,1,124,81,201,17,204,233,162,136,34,20,229,73,204,115,201,167,44,202,93, +219,210,14,67,114,187,146,27,52,66,40,47,42,91,18,85,64,162,196,98,105,133,76,172,27,145,26,62,168,160, +250,255,19,119,221,205,141,227,200,254,255,253,20,60,237,157,87,122,7,106,68,37,219,210,112,166,54,94,220,80, +123,155,85,174,89,136,164,76,158,153,134,84,180,206,247,217,223,175,209,32,68,210,178,119,230,197,9,34,98,163, +209,104,116,55,26,32,152,33,172,171,186,5,194,250,115,37,61,2,242,141,6,130,245,50,64,4,116,214,234,208, +237,233,174,60,212,54,2,66,188,103,118,252,91,180,88,189,238,232,203,143,113,56,130,100,98,231,134,33,196,46, +24,174,75,215,33,2,59,115,19,251,239,127,79,7,208,86,200,15,186,87,98,77,220,132,127,252,230,106,16,1, +38,20,57,147,41,74,45,172,201,3,186,23,202,93,169,222,244,152,114,38,103,36,74,228,80,82,207,16,211,100, +142,69,134,76,157,218,51,36,54,249,19,81,32,95,167,246,208,190,139,230,129,196,2,195,69,11,114,101,242,132, +55,218,10,200,124,99,5,252,184,62,111,5,224,211,70,235,234,203,38,165,47,82,95,220,175,5,115,215,80,168, +17,28,153,161,27,155,161,155,52,94,100,42,234,115,114,110,246,37,233,234,127,205,5,157,165,196,233,19,86,163, +101,125,178,102,152,172,217,203,180,154,172,25,77,214,146,200,11,89,73,19,205,216,192,138,175,50,191,123,132,96, +202,203,89,137,65,87,51,248,207,192,30,195,3,201,37,111,233,67,42,106,246,146,243,251,248,57,114,4,133,68, +64,219,188,34,71,50,148,99,37,154,165,91,92,56,175,49,129,83,177,248,188,232,102,202,97,155,170,153,60,3, +130,0,138,33,239,74,154,213,244,181,70,218,36,140,90,109,137,140,97,163,76,70,101,228,249,124,231,65,248,148, +253,173,66,168,33,3,164,145,1,45,86,75,21,171,13,30,179,26,84,92,125,216,215,213,80,199,239,55,212,210, +23,5,15,117,99,36,211,204,140,228,35,49,233,71,91,200,200,91,244,161,67,77,146,0,216,217,67,43,196,255, +229,173,189,132,254,179,71,131,129,165,204,142,192,183,87,152,121,150,92,150,89,188,89,147,239,50,183,135,253,137, +197,47,146,35,212,81,3,165,182,218,191,198,131,229,174,223,13,64,105,150,154,13,81,153,213,133,62,241,85,63, +149,9,60,164,29,145,62,194,179,204,101,218,33,219,226,151,110,208,194,151,14,11,218,183,133,60,216,99,96,74, +231,182,108,182,34,58,103,132,254,151,90,25,228,156,158,93,56,152,128,48,163,186,16,75,53,12,224,220,244,149, +110,65,81,35,245,91,248,123,245,9,162,167,169,177,38,197,138,60,202,34,23,137,48,31,136,119,73,175,193,228, +201,48,104,91,213,97,243,89,37,154,73,176,41,137,34,230,162,78,254,24,144,153,28,60,125,163,197,240,198,76, +93,240,87,153,35,16,109,213,57,189,68,70,233,26,255,63,46,115,168,253,111,37,10,97,105,101,166,55,80,165, +219,231,205,113,61,94,118,209,81,226,54,59,236,112,201,72,223,83,182,253,207,248,77,79,57,229,41,152,185,159, +98,88,182,183,8,22,20,164,128,164,64,14,59,21,225,152,194,5,16,65,216,171,133,195,90,153,85,45,125,163, +218,202,79,13,108,47,46,182,10,139,68,229,44,79,57,111,78,65,72,138,125,67,82,128,55,36,164,54,248,226, +203,129,53,8,135,227,237,112,252,231,193,189,98,242,88,116,232,24,38,242,188,77,81,160,52,31,185,211,89,250, +179,41,200,237,79,171,180,61,98,206,176,138,81,222,88,71,204,151,88,70,85,130,249,36,139,99,146,10,85,191, +63,81,113,239,233,214,189,51,173,123,220,250,101,21,161,172,107,142,180,27,247,106,141,95,113,74,171,237,80,147, +100,98,57,215,161,51,145,142,229,88,32,143,51,176,134,127,30,155,152,141,191,63,84,81,199,25,90,131,173,51, +185,103,0,79,35,191,122,38,235,76,191,86,154,170,151,85,140,242,28,135,99,237,158,173,106,61,51,213,27,61, +43,76,235,105,150,6,245,36,187,216,196,1,210,233,139,79,153,239,171,172,204,52,48,28,112,220,192,55,9,219, +40,192,225,109,106,99,96,129,62,99,252,171,234,238,147,88,217,72,225,122,157,207,94,188,216,237,118,253,221,168, +159,21,183,47,134,131,193,224,5,205,6,42,89,26,225,228,187,191,174,226,96,111,209,143,237,101,177,21,209,203, +32,54,159,22,181,254,185,41,233,203,87,85,116,103,99,60,66,250,249,253,49,90,140,110,30,140,216,141,111,127, +37,184,249,73,232,53,160,26,64,202,105,109,221,202,220,30,104,242,164,231,170,20,217,174,137,8,213,224,78,190, +57,149,15,149,180,87,169,203,223,106,24,114,197,226,242,86,17,196,146,190,46,215,86,43,40,110,147,20,183,193, +87,137,63,171,71,227,219,83,116,106,13,247,241,41,58,182,128,108,21,12,237,197,104,60,200,247,55,86,110,79, +172,6,38,220,5,210,89,187,16,93,179,248,72,173,126,176,118,112,160,29,178,109,80,172,98,64,12,35,223,15, +82,67,224,125,29,111,210,26,7,241,49,180,6,30,107,4,88,32,38,164,58,208,67,86,34,1,63,82,122,64, +246,211,163,100,5,3,45,64,15,168,2,126,196,252,240,248,17,242,99,197,245,54,252,200,89,218,161,137,188,214, +68,194,143,37,61,160,68,122,2,198,207,94,188,209,37,238,180,253,115,16,139,143,97,255,28,160,91,94,239,96, +207,236,92,165,96,88,128,51,214,1,86,208,232,66,119,215,247,187,14,18,93,134,208,253,221,221,191,254,245,241, +197,21,116,29,105,192,255,50,175,30,30,241,42,212,103,99,22,244,196,161,173,237,94,111,95,111,251,185,162,243, +172,187,117,161,250,14,61,193,242,254,68,7,224,189,5,222,91,198,123,203,120,107,91,244,31,100,113,126,124,49, +70,254,63,120,129,113,128,110,236,137,61,219,120,255,32,27,15,48,143,119,100,227,181,180,132,56,16,253,216,198, +163,50,242,124,62,217,120,148,125,96,229,207,108,224,119,245,96,81,224,91,84,108,24,5,161,177,253,106,139,61, +94,147,149,0,72,38,130,219,233,204,91,134,161,132,97,104,86,26,146,150,4,228,80,230,117,19,112,164,98,122, +101,228,118,245,187,21,42,205,143,138,245,225,2,198,53,246,141,189,176,155,242,2,228,232,201,50,80,68,158,209, +194,4,13,86,166,28,77,1,132,181,76,182,39,3,75,25,79,148,135,200,0,31,146,90,22,129,188,155,107,71, +194,169,58,207,160,225,249,234,148,199,213,31,234,88,97,96,104,197,148,153,181,110,218,223,191,145,251,168,172,173, +120,57,107,214,57,243,225,188,15,199,222,229,104,53,233,168,210,168,123,80,117,31,110,232,10,241,174,67,212,132, +192,46,217,167,69,231,217,20,97,116,142,3,162,161,175,61,60,22,218,48,171,76,234,213,251,153,212,161,47,188, +218,234,105,208,48,172,179,71,199,54,35,253,21,15,99,197,209,218,131,183,76,211,83,189,130,234,213,119,92,81, +2,231,75,239,200,244,213,54,36,167,108,41,5,214,100,101,14,99,127,125,22,137,85,84,148,107,246,225,144,225, +22,156,172,161,180,110,158,253,194,251,116,100,59,201,83,186,135,116,204,253,16,233,239,174,31,118,44,218,91,163, +141,234,242,61,170,171,10,193,83,21,184,204,83,114,219,200,117,35,176,53,54,101,98,145,220,207,247,246,180,163, +29,201,138,60,110,64,82,28,70,56,164,56,30,1,2,144,162,65,37,175,51,142,21,252,144,244,144,194,227,88, +72,203,3,174,186,118,241,32,78,46,73,64,86,195,100,214,7,25,228,62,231,199,148,111,6,173,202,247,68,12, +88,62,121,39,86,36,66,130,134,168,240,155,78,47,112,136,178,248,191,148,185,40,213,234,192,156,8,43,220,87, +133,106,217,172,176,249,252,239,201,119,140,21,54,131,146,174,98,202,146,186,22,187,89,87,210,133,59,36,15,99, +17,96,157,236,22,25,66,18,19,227,236,66,176,5,63,104,192,167,250,36,159,231,13,181,254,88,11,143,204,120, +211,16,128,155,49,4,120,172,17,104,93,221,101,224,199,12,63,190,49,74,150,198,0,149,22,18,170,77,18,133, +65,253,130,191,58,248,54,162,33,195,68,113,68,129,142,166,128,252,29,29,184,85,245,68,150,181,125,118,5,214, +225,44,192,77,243,18,205,203,83,243,146,155,151,55,144,233,141,33,218,52,164,121,185,166,77,173,148,61,106,109, +17,94,146,8,87,37,72,134,151,149,12,47,251,42,77,9,162,74,4,221,190,159,8,218,248,194,103,17,164,64, +181,100,144,124,39,25,116,249,88,8,197,239,82,113,56,120,92,209,251,77,119,2,189,40,144,6,197,159,191,251, +242,239,238,175,47,97,21,91,116,106,15,117,93,152,237,195,142,165,204,104,247,57,43,218,210,70,184,171,108,240, +209,96,98,93,15,58,22,145,15,245,118,246,226,138,172,64,50,35,17,118,28,138,116,94,189,44,233,197,188,87, +47,95,232,39,173,49,45,56,204,147,65,255,202,114,250,215,225,104,242,214,185,236,95,147,81,127,77,73,195,254, +24,63,244,28,225,7,173,244,47,145,231,92,245,71,182,142,218,14,234,169,2,118,85,101,20,218,163,201,125,50, +28,246,167,214,116,220,191,10,9,204,91,2,234,140,81,97,130,242,147,254,212,158,170,95,103,130,74,3,251,186, +127,109,87,81,4,46,109,4,199,244,8,109,170,125,159,56,163,75,128,27,58,4,125,56,234,95,198,54,126,40, +111,130,248,149,138,35,159,227,200,191,142,145,56,182,175,166,125,39,28,78,251,87,247,137,61,118,0,122,58,160, +162,87,4,234,26,160,208,217,209,125,114,57,5,74,83,84,125,107,59,4,101,96,19,238,54,161,114,77,191,104, +134,81,155,198,232,250,53,186,252,22,184,89,232,57,22,139,84,151,67,131,62,45,10,233,97,115,167,70,212,23, +106,180,63,180,209,9,123,64,117,65,106,100,140,0,132,2,72,117,128,61,250,15,130,32,123,138,28,122,18,98, +128,0,34,95,245,39,182,3,92,17,152,218,151,253,49,209,96,172,2,4,10,13,34,115,104,77,144,61,6,65, +29,34,193,149,34,116,76,144,39,170,212,91,123,76,45,80,239,56,0,2,1,58,232,133,240,80,255,142,21,250, +84,123,132,48,126,9,152,234,28,34,99,64,2,110,22,194,220,81,64,194,255,43,170,139,166,169,198,37,254,79, +81,119,138,212,75,252,31,2,45,74,161,167,163,40,51,164,142,2,95,116,28,100,68,161,161,26,74,10,220,39, +87,224,12,140,214,214,30,17,35,14,241,179,85,163,103,83,16,169,35,21,30,247,71,91,29,28,110,121,120,17, +160,42,247,29,240,53,49,52,30,152,31,175,172,15,44,235,37,38,91,53,37,10,233,5,246,242,64,165,144,250, +234,215,230,210,171,225,176,27,88,244,234,41,30,228,181,163,167,190,212,113,80,169,224,144,31,239,190,22,32,161, +239,212,116,244,61,12,197,247,242,4,230,109,79,154,17,64,188,241,96,124,96,90,2,146,114,36,191,213,115,114, +231,163,58,121,126,211,54,97,183,225,2,102,13,4,201,63,208,72,132,30,122,37,83,243,35,246,131,145,175,41, +109,251,154,214,207,219,51,198,72,105,58,41,55,57,132,60,89,233,86,14,171,186,115,114,111,127,141,199,26,1, +65,129,128,2,159,163,81,118,120,151,198,225,93,24,119,55,173,129,10,165,30,37,43,26,87,187,188,91,142,238, +146,23,65,105,219,145,93,214,29,221,103,243,141,163,59,211,139,32,245,12,212,210,39,109,57,190,147,103,44,26, +234,3,37,73,147,68,123,81,209,98,124,99,238,222,220,184,175,54,164,113,250,145,111,52,53,29,89,221,188,244, +42,77,189,49,134,78,238,42,5,230,137,13,57,8,195,110,94,25,58,9,12,157,205,141,27,102,8,229,176,5, +168,240,10,13,77,170,134,124,110,200,185,60,211,208,234,92,67,74,197,174,184,33,159,26,146,186,161,130,26,90, +113,67,207,90,84,6,126,208,128,15,68,217,162,42,21,127,101,79,213,42,26,181,208,234,25,59,140,94,238,181, +232,135,166,107,73,178,149,142,218,16,43,150,118,34,247,150,164,23,250,107,14,34,118,15,101,239,12,128,43,49, +171,110,68,14,86,197,99,141,128,193,53,1,174,201,169,135,9,247,48,169,217,116,115,170,83,162,142,160,64,246, +184,114,209,168,92,112,229,76,87,142,153,255,185,245,252,194,25,211,210,223,115,55,96,33,17,69,160,159,54,13, +115,17,10,71,108,132,167,76,195,63,167,34,100,8,34,6,67,167,40,8,47,243,197,104,136,202,43,84,158,232, +202,5,85,46,80,217,167,202,124,207,6,85,94,233,202,178,170,76,51,106,195,111,45,198,167,215,35,115,224,159, +159,248,52,39,252,49,225,130,69,142,237,174,102,161,85,187,80,65,133,184,123,52,21,55,109,168,65,163,130,60, +15,181,104,23,50,80,105,2,19,208,141,54,130,159,3,78,144,201,8,158,83,97,90,63,170,103,246,124,99,212, +82,219,114,14,91,75,220,150,72,207,101,115,91,99,189,24,178,72,135,4,202,210,110,135,157,29,29,177,94,92, +66,154,61,181,238,93,70,103,164,114,123,21,104,36,235,243,2,117,237,102,243,166,80,157,146,84,101,207,18,208, +251,223,148,170,231,165,233,234,121,18,174,252,54,9,157,75,144,240,255,137,90,163,97,141,90,192,228,255,156,90, +203,167,119,227,220,223,241,198,155,151,129,177,153,120,63,21,134,120,161,44,255,161,173,138,233,141,64,167,210,192, +91,7,62,239,180,233,29,13,28,149,66,124,84,231,80,47,164,99,58,29,17,45,174,110,122,70,133,45,18,95, +228,62,29,232,95,212,142,183,248,44,179,244,240,109,0,136,188,160,228,139,166,101,78,7,23,194,87,105,165,49, +61,58,248,204,135,237,104,165,242,239,110,225,82,255,122,68,100,119,69,11,249,16,63,148,194,70,144,135,222,41, +223,233,90,105,18,229,18,162,237,192,206,185,17,111,107,27,65,7,44,80,155,21,194,249,149,61,7,202,176,136, +210,59,86,5,180,250,50,238,244,131,93,122,116,124,183,145,184,175,188,236,185,125,69,214,225,226,195,213,21,253, +189,121,222,33,196,13,18,36,99,76,209,151,108,67,106,34,8,210,150,153,138,98,208,78,167,220,123,152,162,70, +75,105,194,112,88,176,198,210,129,0,1,230,238,192,184,220,3,81,242,3,92,247,111,248,12,64,232,154,2,18, +39,5,68,162,245,184,33,143,187,71,58,136,221,211,30,187,167,103,30,248,220,115,137,217,4,143,9,33,176,238, +231,146,56,233,171,204,167,195,103,21,199,36,52,123,242,139,41,169,178,164,95,177,162,187,1,43,146,150,226,100, +195,147,72,31,170,244,43,78,55,204,137,140,209,105,198,37,252,90,230,210,45,230,196,54,27,190,163,97,249,218, +116,137,117,232,172,43,1,70,41,63,76,187,213,98,121,35,28,252,85,126,84,138,241,49,27,214,122,68,143,215, +154,245,132,124,45,13,136,58,55,162,33,98,35,20,198,52,151,194,161,168,161,94,111,38,21,192,74,127,198,231, +164,193,134,171,50,169,123,90,25,202,51,165,36,151,50,202,141,199,153,66,66,171,57,122,26,89,113,26,206,182, +158,122,227,55,206,26,107,39,61,253,20,181,25,42,36,153,174,49,253,120,244,19,170,174,96,150,119,118,37,60, +24,157,63,250,153,183,33,220,212,247,45,8,112,63,204,202,245,31,59,47,124,89,134,59,218,2,57,73,131,13, +230,43,59,198,201,79,109,146,115,36,183,220,217,39,63,247,48,24,245,4,87,162,168,15,3,52,245,187,127,253, +199,215,95,233,171,139,176,28,235,170,155,73,100,234,207,58,56,138,56,139,213,107,222,157,7,88,44,178,60,164, +158,117,50,211,187,59,72,240,226,160,186,188,157,83,104,235,42,88,252,138,249,78,239,59,160,39,94,72,251,31, +141,15,9,146,248,164,119,141,69,231,47,233,86,198,48,26,169,170,149,4,101,41,73,32,114,109,113,168,188,72, +15,219,190,198,139,4,28,43,118,141,220,76,93,193,69,159,176,239,142,21,117,169,143,19,225,233,208,64,145,29, +156,243,12,132,20,235,42,0,216,242,234,7,252,172,67,230,58,170,3,29,32,211,7,100,163,217,161,31,137,59, +252,222,137,45,126,183,250,205,163,78,135,34,112,171,233,86,193,193,104,82,22,62,1,228,80,19,96,92,1,244, +9,162,175,142,84,33,4,51,87,38,20,72,5,95,191,208,110,66,148,135,100,153,197,8,150,245,116,254,170,49, +2,244,208,25,142,142,210,73,123,4,245,39,6,17,194,47,99,74,52,99,76,67,89,112,223,57,216,196,213,251, +77,92,121,87,6,193,189,56,84,193,3,183,65,163,209,123,98,4,144,146,103,41,152,190,172,15,192,25,250,27, +115,246,99,24,143,31,191,148,149,241,248,49,94,59,132,25,45,177,127,217,143,0,24,232,245,142,24,155,223,85, +183,26,176,19,85,229,223,185,200,32,71,61,168,120,46,127,235,34,3,249,188,129,69,122,218,140,32,7,158,195, +40,110,97,20,43,140,124,70,201,7,78,173,54,199,66,149,80,99,172,218,141,9,175,242,92,25,30,112,23,185, +92,136,134,244,108,57,26,121,206,230,130,24,240,115,229,144,236,170,76,93,74,238,207,149,66,178,171,50,69,108, +40,82,231,20,14,61,59,74,94,139,38,222,35,154,52,91,158,8,85,130,121,201,69,174,240,8,193,195,185,66, +135,170,208,129,11,113,252,141,218,11,124,186,60,231,187,205,226,194,51,29,108,179,41,152,243,150,174,31,58,137, +205,16,74,34,14,100,81,201,216,16,73,110,123,11,209,231,27,43,161,38,196,20,82,183,18,131,219,39,197,96, +150,26,217,183,237,213,45,210,238,238,153,58,1,189,15,12,105,105,196,58,212,165,105,186,166,159,160,21,124,151, +42,254,24,44,255,145,121,119,1,84,53,149,204,82,85,214,221,168,112,150,7,169,155,35,104,144,113,19,138,113, +51,238,242,1,189,252,11,249,187,192,179,220,77,72,125,131,92,156,221,118,59,165,2,222,7,45,253,3,89,33, +232,142,95,139,245,26,49,208,215,224,211,255,244,239,95,255,227,243,207,32,197,77,71,128,117,111,246,100,249,175, +191,249,252,171,139,11,173,195,62,58,118,244,152,209,153,221,8,67,246,240,145,166,249,51,244,83,111,83,91,81, +122,162,138,181,163,42,129,34,41,150,209,147,96,84,217,88,123,119,135,254,158,87,153,80,86,193,90,70,49,134, +121,14,164,117,141,59,170,193,123,223,166,128,217,239,105,239,197,55,183,188,131,238,84,164,110,37,226,94,145,17, +179,144,122,49,194,167,121,113,20,176,218,8,218,191,223,70,208,27,95,44,79,199,59,137,24,123,172,1,215,178, +32,117,111,44,16,68,62,143,3,10,126,114,248,139,223,237,200,60,239,208,139,32,31,124,240,242,5,223,17,247, +234,3,11,127,120,187,196,82,103,50,212,25,202,23,30,172,240,87,31,252,135,152,241,61,49,98,38,87,224,152, +227,50,131,225,31,221,131,98,51,189,45,139,148,185,14,242,23,57,7,85,84,193,156,97,196,34,191,74,226,27, +10,63,12,38,193,101,176,124,104,193,182,237,245,14,37,210,53,208,157,89,184,253,59,92,39,241,145,206,181,219, +124,46,107,134,205,135,185,189,11,150,119,209,218,38,44,9,149,192,150,62,124,208,200,28,12,254,48,183,147,236, +222,94,203,165,202,153,141,231,118,86,143,213,130,202,199,187,146,73,20,31,102,155,200,46,101,90,218,244,38,197, +10,106,25,130,55,177,55,145,176,65,174,56,176,57,65,124,2,76,238,190,148,222,63,84,148,94,105,21,255,8, +110,179,192,250,254,47,226,219,12,142,115,184,136,130,120,27,96,193,38,173,175,130,77,32,62,46,34,25,139,175, +144,99,253,3,240,69,173,145,206,199,4,218,82,199,27,172,207,147,236,159,17,214,60,21,184,42,193,196,255,161, +20,135,232,16,168,70,157,135,101,230,31,142,9,70,61,74,65,248,58,173,162,52,68,75,235,135,176,56,114,10, +242,153,252,58,167,26,147,117,150,235,145,115,242,253,131,92,46,139,217,14,5,130,238,98,77,183,69,98,113,211, +32,185,31,120,89,193,47,5,111,82,212,167,54,45,63,91,99,73,50,255,173,2,15,161,35,194,161,8,71,34, +28,139,112,34,194,233,17,227,192,195,104,208,82,41,187,102,39,228,177,129,122,187,33,83,110,41,48,107,32,234, +143,117,32,32,29,208,120,240,104,181,117,135,73,83,202,36,167,15,51,31,91,60,144,64,116,150,185,244,2,241, +143,47,190,68,216,198,171,22,234,251,22,95,6,105,156,9,36,73,47,19,159,42,33,36,75,188,33,179,12,184, +121,11,89,148,177,41,162,160,192,208,239,132,1,53,63,245,207,9,146,135,50,145,113,92,235,243,213,224,15,15, +229,6,88,111,242,90,234,229,228,15,102,40,121,224,204,173,155,213,193,180,57,86,35,196,104,177,173,124,185,179, +165,190,141,149,160,29,245,253,209,118,127,56,65,155,4,27,99,140,40,197,212,91,69,71,69,191,40,245,105,166, +153,249,122,150,59,144,24,203,188,12,102,85,0,175,215,1,122,42,162,52,167,109,245,124,77,7,117,114,129,230, +177,44,21,4,88,66,212,215,105,107,64,154,30,242,92,61,195,173,45,14,53,140,93,221,173,57,168,90,231,230, +184,31,234,86,91,186,11,97,150,102,169,193,111,161,132,25,71,110,116,172,8,160,250,170,8,40,149,224,245,112, +195,221,152,236,129,4,40,47,152,113,173,249,233,24,146,166,141,106,137,215,238,245,204,40,129,142,229,198,103,74, +0,173,32,127,75,82,43,71,88,18,212,205,25,185,207,117,38,56,45,226,197,18,75,211,80,250,217,142,107,231, +69,118,11,28,203,227,83,163,59,155,85,216,170,205,45,187,132,146,180,117,135,79,121,104,180,153,167,37,0,99, +161,123,143,190,122,225,217,222,19,81,87,81,16,251,115,141,189,157,241,37,173,246,48,223,215,80,96,16,181,105, +120,14,24,83,197,212,89,69,16,168,155,60,206,164,175,113,123,134,254,196,47,102,106,151,155,4,236,112,48,7, +214,227,168,4,21,214,96,233,101,12,165,255,118,147,193,22,240,99,58,30,241,72,202,136,176,16,252,145,72,193, +51,191,98,172,7,213,79,244,237,120,134,213,248,136,252,241,148,0,17,188,137,5,180,233,230,72,173,107,5,71, +61,60,199,169,102,42,20,129,226,249,106,76,31,212,204,153,49,51,160,43,94,16,42,1,101,230,206,227,172,163, +62,101,55,115,230,90,131,94,123,114,36,87,12,10,197,75,91,133,206,131,51,217,239,1,114,118,30,177,119,0, +96,230,95,145,197,102,254,29,225,158,42,81,70,93,103,11,89,60,195,40,146,32,242,171,12,125,200,240,33,74, +110,5,246,180,197,54,242,131,76,240,123,123,66,110,252,40,19,209,170,144,73,32,130,100,25,248,34,83,111,51, +26,102,80,44,208,150,137,124,47,53,129,100,112,24,227,61,107,58,150,63,141,73,193,222,201,27,3,81,241,109, +219,252,97,19,197,220,162,109,239,103,214,96,222,74,59,152,52,190,70,219,68,203,187,0,110,208,102,244,112,138, +2,111,5,208,169,199,15,38,158,203,148,114,79,145,131,137,68,41,102,225,125,150,37,85,10,187,95,237,50,149, +185,77,230,173,183,78,33,84,102,22,196,203,62,74,48,102,92,44,43,192,169,50,54,181,160,212,194,192,183,233, +235,184,85,154,190,64,217,230,201,211,78,37,13,71,188,222,46,92,72,181,94,169,210,73,16,130,1,49,203,26, +41,44,84,244,128,88,56,182,243,56,143,25,203,250,112,181,90,213,50,117,106,113,187,236,78,112,200,98,68,231, +242,167,214,11,171,63,233,61,6,193,226,21,240,241,247,195,1,254,212,138,156,207,123,62,153,91,15,252,118,54, +157,155,5,82,58,76,199,41,52,209,231,198,172,45,100,105,40,64,187,255,106,136,171,132,112,19,48,195,152,148, +40,37,126,54,163,35,177,226,168,229,150,65,30,201,255,172,238,58,151,28,87,149,240,255,251,20,186,121,85,101, +115,4,10,14,186,249,37,110,242,254,152,224,9,85,227,77,222,217,164,210,187,31,62,104,160,133,89,99,109,157, +60,199,59,226,235,230,163,65,24,16,221,104,92,2,47,174,38,251,28,228,222,103,77,118,197,96,48,48,22,121, +75,99,129,55,57,150,112,219,99,17,85,34,134,105,236,136,241,184,150,65,64,213,29,133,11,95,25,252,146,200, +33,163,160,197,17,147,17,162,101,38,218,197,172,131,116,79,27,133,13,123,25,240,203,2,104,16,32,248,77,144, +139,133,161,245,148,5,193,163,118,162,53,84,162,83,237,187,253,193,229,6,78,4,36,33,125,171,60,81,117,122, +65,75,90,181,218,0,75,171,230,232,150,82,16,223,210,168,218,154,44,21,101,71,144,205,240,101,105,255,20,153, +170,0,200,0,72,13,216,197,220,147,190,175,131,185,220,110,244,127,163,96,7,47,6,231,238,127,62,188,218,2, +67,24,220,119,5,93,141,194,31,189,144,86,83,167,73,205,107,49,165,38,82,106,156,82,51,138,195,178,117,211, +173,175,214,225,147,7,151,184,41,78,210,19,102,155,129,171,99,216,158,228,0,48,85,7,162,117,209,218,132,135, +251,0,88,86,28,87,78,112,29,76,161,59,239,203,133,19,202,79,17,72,140,2,245,244,16,18,163,176,115,201, +116,42,17,214,69,229,86,98,152,128,128,237,254,47,43,221,175,118,47,189,0,73,72,20,33,100,47,16,116,47, +2,125,255,2,69,221,76,40,76,18,18,89,57,72,121,146,186,115,216,198,209,54,14,233,8,89,251,108,4,248, +60,146,120,184,15,110,64,34,88,255,225,97,20,31,109,93,195,92,171,33,109,231,218,152,73,48,18,128,21,165, +137,25,103,73,8,81,30,106,9,241,247,1,108,181,98,108,72,0,94,187,188,164,214,185,156,196,197,60,151,3, +187,222,202,81,216,171,101,53,193,43,202,114,175,123,242,66,224,223,193,167,145,201,63,123,12,254,106,27,254,202, +134,126,245,194,139,120,201,80,46,18,232,231,178,44,48,130,242,60,54,173,5,88,41,120,152,150,17,4,255,55, +194,193,99,198,232,255,144,32,172,44,156,228,191,145,4,121,70,97,151,97,75,90,159,197,203,53,97,31,183,150, +232,201,126,189,142,87,151,46,173,192,174,129,177,106,77,161,199,83,48,6,70,49,137,47,178,131,135,135,220,211, +3,68,110,131,168,143,84,152,104,20,60,114,201,114,233,231,132,55,104,105,26,224,142,122,102,120,179,215,142,67, +169,224,83,209,153,181,239,64,222,189,43,75,186,223,254,70,235,205,188,189,93,205,104,200,10,193,17,11,45,237, +40,120,56,225,96,195,172,12,180,181,208,40,38,167,243,6,74,249,93,47,176,90,81,208,196,51,72,74,15,130, +160,69,69,198,138,113,177,215,251,247,31,241,157,141,245,204,102,133,147,142,194,70,113,14,250,151,255,22,2,106, +13,34,133,10,80,99,33,159,174,118,194,106,9,201,180,64,69,51,47,146,107,203,236,197,202,230,8,250,181,5, +86,22,137,14,234,13,46,189,181,105,166,240,217,78,11,33,77,115,64,56,41,50,208,190,6,254,240,201,243,209, +23,193,206,180,199,42,152,41,97,120,56,211,22,115,76,41,212,73,33,114,34,127,255,110,8,59,111,52,207,59, +38,53,165,186,118,170,52,17,165,181,173,10,105,178,173,60,65,136,154,138,20,68,209,153,26,251,96,67,160,95, +156,201,233,206,16,22,220,170,169,11,213,52,180,226,166,145,36,206,89,150,190,0,182,99,50,76,200,152,192,43, +227,232,217,227,241,193,252,190,216,34,217,22,221,166,80,109,155,181,199,87,88,205,169,176,210,228,181,212,159,92, +1,20,241,76,204,247,140,53,218,72,34,102,152,76,159,9,243,125,196,186,251,255,142,98,101,118,47,47,39,111, +214,5,125,206,146,191,211,110,35,116,128,121,86,43,77,92,87,231,136,113,15,103,18,75,52,116,163,155,163,57, +75,124,143,53,199,60,102,85,105,86,133,219,120,190,45,246,183,179,120,97,169,82,29,62,103,121,63,239,159,48, +22,205,166,110,112,130,224,124,223,160,238,124,57,49,239,206,249,86,110,103,48,215,141,182,118,85,108,154,172,193, +179,104,101,133,227,39,154,91,173,243,253,173,158,67,220,172,140,193,170,173,47,185,123,115,108,86,104,139,213,166, +184,192,226,110,78,11,235,214,221,100,239,28,31,68,243,212,241,32,154,31,137,20,126,102,141,68,170,160,79,198, +110,157,251,98,214,170,192,79,230,219,60,243,166,109,138,110,141,255,207,177,250,227,206,9,98,209,78,84,54,41, +149,205,68,165,75,169,116,163,192,10,201,109,36,99,149,4,164,245,8,214,95,4,54,1,180,136,30,13,28,20, +30,118,123,135,208,227,172,160,236,159,66,126,122,232,230,202,244,200,237,121,177,176,155,80,119,42,73,14,152,242, +76,51,72,145,202,0,216,103,168,167,37,172,82,250,64,201,252,122,106,62,36,113,5,184,122,23,213,86,196,234, +236,9,215,120,150,104,81,141,107,218,83,182,8,137,143,7,230,180,19,107,91,250,212,95,173,24,219,211,61,83, +151,66,170,132,254,138,233,127,58,114,250,4,187,81,53,42,59,156,86,218,189,156,184,61,53,2,169,127,3,210, +68,216,120,225,113,127,120,132,135,116,34,238,32,246,7,147,98,55,155,23,224,249,87,127,151,208,122,122,109,185, +71,12,161,110,29,191,47,44,42,213,194,68,126,222,137,122,60,32,254,149,228,67,81,87,200,174,214,3,168,31, +140,226,12,101,73,156,102,245,210,230,57,177,98,213,148,155,38,203,136,81,51,207,200,55,158,179,148,52,149,230, +57,253,12,154,165,196,216,150,39,12,67,90,150,144,38,185,60,167,159,219,242,156,184,225,121,70,62,195,103,57, +105,133,157,33,100,11,235,44,33,166,180,238,26,63,187,151,23,25,74,159,51,188,118,31,30,143,154,145,55,65, +233,231,95,109,148,254,103,137,127,248,52,38,100,185,208,23,107,13,203,10,10,221,169,66,218,11,17,211,146,93, +145,102,130,61,173,215,51,151,52,105,164,252,41,139,224,253,40,23,83,189,51,10,36,243,109,20,55,144,212,102, +213,250,83,165,218,6,66,5,227,229,229,77,227,9,211,181,141,120,127,25,141,178,196,142,32,123,116,34,212,187, +194,228,93,123,183,153,58,170,136,42,110,129,146,182,39,141,43,196,130,199,129,33,56,243,178,127,167,123,185,17, +45,226,85,209,130,63,11,47,240,18,173,133,253,11,199,139,116,60,16,21,209,127,107,9,73,54,94,238,15,207, +60,191,70,250,133,187,184,159,46,36,115,123,243,124,173,61,158,215,251,47,143,120,179,107,179,168,22,66,45,100, +201,179,220,62,91,178,173,144,237,113,114,75,16,11,148,170,147,198,127,216,50,247,122,174,94,62,153,23,245,14, +103,136,173,198,40,232,0,185,238,127,207,143,20,48,87,72,12,51,61,33,111,246,248,126,73,209,28,9,161,222, +105,214,240,4,129,204,45,39,10,61,48,37,98,152,184,35,165,63,221,44,237,211,155,153,206,111,111,123,125,84, +84,217,219,77,46,18,146,249,101,63,221,144,211,251,222,55,47,210,249,245,128,162,202,62,218,97,236,19,155,251, +255,123,81,149,190,169,40,76,96,112,150,234,183,58,157,248,39,123,227,124,236,173,203,177,103,126,32,50,217,92, +198,29,153,85,132,198,34,55,53,9,233,75,167,224,4,86,120,154,125,110,43,184,111,133,253,243,207,168,11,10, +252,215,103,26,112,124,87,40,11,211,193,30,95,33,242,72,7,226,222,225,133,236,251,254,27,179,165,28,41,75, +152,95,230,91,103,252,135,179,89,159,123,52,241,34,199,194,22,62,84,127,28,190,206,60,190,127,157,148,90,225, +248,143,31,146,205,70,216,80,64,26,206,13,190,252,90,220,20,191,141,172,238,124,239,244,148,108,107,130,207,124, +224,25,120,18,74,33,136,235,233,209,236,211,62,60,31,174,179,118,232,117,3,117,39,92,70,221,137,158,238,152, +157,136,219,64,232,6,115,178,46,27,159,111,43,5,230,93,19,31,140,9,253,74,187,96,240,35,170,118,50,227, +98,94,54,31,104,155,201,179,170,228,85,31,185,168,242,85,124,247,252,202,252,137,137,165,121,102,33,191,96,23, +226,128,54,176,140,25,127,125,139,159,168,146,53,170,232,136,195,80,138,33,187,16,237,177,79,64,89,67,31,48, +218,228,204,101,150,153,182,106,55,11,89,87,11,213,116,11,177,41,147,213,63,28,227,170,70,182,240,154,67,251, +178,250,164,59,226,116,231,156,11,78,144,175,24,75,125,240,215,217,215,96,191,121,207,168,158,52,244,80,124,81, +63,250,218,119,55,193,54,255,222,19,1,30,213,127,56,115,136,109,190,57,240,12,155,196,175,252,38,83,53,126, +136,49,4,92,63,240,0,18,155,151,185,81,226,227,149,46,232,213,125,152,223,135,48,101,147,76,199,237,23,234, +232,39,103,63,244,229,20,249,148,25,52,48,109,250,176,209,177,101,41,81,99,210,12,178,120,34,255,102,30,97, +26,103,183,37,215,105,103,27,43,132,85,161,139,121,165,143,78,78,119,54,18,123,191,72,87,85,164,119,185,223, +197,108,106,173,206,120,71,120,33,87,159,191,161,140,85,91,172,91,184,8,46,45,162,157,93,139,6,78,130,74, +127,100,182,144,200,21,65,37,253,128,14,9,42,135,182,237,226,146,242,219,140,40,32,179,69,100,198,191,221,214, +133,193,99,45,20,45,175,194,128,195,38,184,40,110,30,74,227,63,14,251,219,199,171,226,5,130,165,40,228,117, +213,233,29,153,114,16,135,219,221,214,14,16,120,243,134,11,208,91,218,8,61,8,195,251,144,79,131,244,176,255, +104,47,72,247,242,136,62,168,39,2,218,0,39,35,216,32,224,175,87,24,144,152,6,126,65,5,177,89,82,242, +80,47,147,98,194,245,154,11,145,10,228,80,168,43,155,59,196,107,3,33,165,179,225,57,80,200,133,232,64,231, +194,168,25,168,158,188,193,98,8,8,181,19,41,198,239,191,224,97,40,22,25,83,221,64,86,170,49,253,224,233, +158,221,190,38,186,123,81,12,37,116,47,236,22,201,50,213,186,50,101,126,122,98,60,245,41,79,237,120,234,52, +79,91,119,134,103,87,171,9,85,162,243,249,190,167,70,82,191,180,182,35,142,43,218,23,57,226,85,119,223,61, +232,163,164,230,10,167,223,112,65,239,191,211,39,146,113,194,145,94,124,103,5,38,131,209,211,28,56,80,248,183, +223,124,15,242,210,81,83,178,8,4,0 +}; diff --git a/lib/ESPDASH/src/dash_webpage.h b/lib/ESPDASH/src/dash_webpage.h new file mode 100644 index 0000000..139825b --- /dev/null +++ b/lib/ESPDASH/src/dash_webpage.h @@ -0,0 +1,8 @@ +#ifndef dash_webpage_h +#define dash_webpage_h + +#include + +extern const uint8_t DASH_HTML[90101]; + +#endif diff --git a/lib/ESPDASH/src/vector.h b/lib/ESPDASH/src/vector.h new file mode 100644 index 0000000..1c55e7f --- /dev/null +++ b/lib/ESPDASH/src/vector.h @@ -0,0 +1,243 @@ +#ifndef VectorClass +#define VectorClass + + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#define SWAP(type, a, b) type tmp ## a = a; a = b; b = tmp ## a; + +// This class implements missing vector from STL +template class Vector +{ + VectorType *begin; + VectorType *storage; + int head; + +public: + VectorType OB; + + // We can save a few re-sizings if we know how large the array is likely to grow to be + Vector(int initialSize = 0) + { + begin = new VectorType[initialSize]; //points to the beginning of the new array + head = initialSize - 1; + storage = begin + initialSize; //points to the element one outside of the array (such that end - begin = capacity) + } + + Vector(Vector &obj) + { + begin = new VectorType[0]; // Points to the beginning of the new array, it's zero but this line keeps malloc from seg faulting should we delete begin before resizing it + head = -1; + storage = begin; //points to the element one outside of the array (such that end - begin = capacity) + + *this = obj; + } + + // If there's anything in the vector then delete the array, if there's no array then doing will will cause seg faults + virtual ~Vector() { delete[] begin; } + + Vector &operator=(Vector &obj) + { + // Reallocate the underlying buffer to the same size as the + Resize(obj.Size()); + + for(int i = 0; i < obj.Size(); i++) + (*this)[i] = obj[i]; + + head = obj.head; + + return *this; + } + + // Swaps the underlying array and characteristics of this vector with another of the same type, very quickly + void Swap(Vector &obj) + { + SWAP(int, head, obj.head); + SWAP(VectorType*, begin, obj.begin); + SWAP(VectorType*, storage, obj.storage); + } + + // Checks the entire Vector to see whether a matching item exists. Bear in mind that the VectorType might need to implement + // equality operator (operator==) for this to work properly. + bool Contains(VectorType element) + { + for(int i = 0; i < Size(); i++) + if(operator [](i) == element) + return true; + + return false; + } + + int Find(VectorType element) + { + for(int i = 0; i < Size(); i++) + if(operator [](i) == element) + return i; + + return -1; + } + + void PushBack(VectorType element) { PushBack(&element, 1); } + + void PushBack(const VectorType *elements, int len) + { + // If the length plus this's size is greater than the capacity, reallocate to that size. + if(len + Size() > Capacity()) + ReAllocate(MAX(Size() + len, Size() * 2)); + + int append = MIN(storage - begin - head - 1, len), prepend = len - append; + + // memcpy the data starting at the head all the way up to the last element *(storage - 1) + memcpy((begin + head + 1), elements, sizeof(VectorType) * append); + + // If there's still data to copy memcpy whatever remains, starting at the first element *(begin) until the end of data. The first step will have ensured + // that we don't crash into the tail during this process. + memcpy(begin,(elements + append), sizeof(VectorType) * prepend); + + // Re-recalculate head and size. + head += len; + } + + void Erase(unsigned int position) { Erase(position, position + 1); } + + // Erase an arbitrary section of the vector from first up to last minus one. Like the stl counterpart, this is pretty labour intensive so go easy on it. + void Erase(int first, int last) + { + // For this we'll set the value of the array at first to the value of the array at last plus one. We'll do that all the way up to toIndex + for(int i = 0; i < (Size() - first); i++) + { + // If by trying to fill in the next element with the ones ahead of it we'll be running off the end of the vector, stop. + if((i + last) > (Size() - 1)) + break; + + begin[first + i] = begin[last + i]; + } + + // Adjust the head to reflect the new size + head -= last - first; + } + + // Remove the most recent element in the array + void PopBack() + { + if(Size() > 0) + head--; + } + + // Empty the vector, or to be precise - forget the fact that there was ever anything in there. + void Clear() { head = -1; } + + // Returns a bool indicating whether or not there are any elements in the array + bool Empty() { return head == -1; } + + // Returns the oldest element in the array (the one added before any other) + VectorType const &Back() { return *begin; } + + // Returns the newest element in the array (the one added after every other) + VectorType const &Front() { return begin[head]; } + + // Returns the nth element in the vector + VectorType &operator[](int n) + { + if(n < Size()) + return begin[n]; + else + return OB; // out of bounds + } + + // Returns a pointer such that the vector's data is laid out between ret to ret + size + VectorType *Data() { return begin; } + + // Recreates the vector to hold len elements, all being copies of val + void Assign(int len, const VectorType &val) + { + delete[] begin; + + // Allocate an array the same size as the one passed in + begin = new VectorType[len]; + storage = begin + len; + + // Refresh the head and tail, assuming the array is in order, which it really has to be + head = len - 1; + + for(int i = 0 ; i < Size(); i++) + begin[i] = val; + } + + // Recreates the vector using an external array + void Assign(VectorType *array, int len) + { + delete[] begin; + + // Allocate an array the same size as the one passed in + begin = new VectorType[len]; + storage = begin + len; + + // Refresh the head and tail, assuming the array is in order, which it really has to be + head = len - 1; + + // Copy over the memory + memcpy(begin, array, sizeof(VectorType) * len); + } + + // Returns the number of elements that the vector will support before needing resizing + int Capacity() { return (storage - begin); } + + // Returns the number of elements in vector + int Size() { return head + 1; } + + // Requests that the capacity of the allocated storage space for the elements + // of the vector be at least enough to hold size elements. + void Reserve(unsigned int size) + { + if(size > Capacity()) + ReAllocate(size); + } + + // Resizes the vector + void Resize(unsigned int size) + { + // If necessary, resize the underlying array to fit the new size + if(size > Capacity()) + ReAllocate(size); + + // Now revise the head and size (tail needn't change) to reflect the new size + head = size - 1; + } + +private: + + void ReAllocate(unsigned int size) + { + // Just in case we're re-allocating less room than we had before, make sure that we don't overrun the buffer by trying to write more elements than + // are now possible for this vector to hold. + if(Size() > (int)size) + head = size - 1; + + // Allocate an array twice the size of that of the old + VectorType *_begin = new VectorType[size]; + VectorType *_storage = _begin + size; + + int _head = Size() - 1; + + // Copy across all the old array's data and rearrange it! + for(int i = 0; i < Size(); i++) + _begin[i] = (*this)[i]; + + // Free the old memory + delete[] begin; + + // Redirect the old array to point to the new one + begin = _begin; + storage = _storage; + head = _head; + } +}; + +#endif \ No newline at end of file diff --git a/lib/MycilaAppInfo/LICENSE b/lib/MycilaAppInfo/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaAppInfo/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaAppInfo/MycilaAppInfo.cpp b/lib/MycilaAppInfo/MycilaAppInfo.cpp new file mode 100644 index 0000000..9be1849 --- /dev/null +++ b/lib/MycilaAppInfo/MycilaAppInfo.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include + +#ifndef APP_NAME +#define APP_NAME "YaSolR" +#endif + +#ifndef APP_MANUFACTURER +#define APP_MANUFACTURER "Mathieu Carbou" +#endif + +#ifdef APP_VERSION_PRO +#ifdef APP_VERSION_TRIAL +#define APP_MODEL "Trial" +#else +#define APP_MODEL "Pro" +#endif +#endif + +#ifdef APP_VERSION_OSS +#define APP_MODEL "OSS" +#endif + +#ifndef BUILD_TAG +#define BUILD_TAG "" +#endif + +#ifndef BUILD_HASH +#define BUILD_HASH "" +#endif + +#ifndef BUILD_TIMESAMP +#define BUILD_TIMESAMP "" +#endif + +#ifndef BUILD_NAME +#define BUILD_NAME "" +#endif + +Mycila::AppInfoClass::AppInfoClass() : id(_getEspId()), + name(APP_NAME), + version(BUILD_TAG), + model(APP_MODEL), + manufacturer(APP_MANUFACTURER), + firmware(String(APP_NAME) + (!version.startsWith("v") || version.indexOf("_") >= 0 ? "-local" : ("-" + version)) + "-" + BUILD_NAME), + buildHash(BUILD_HASH), + buildDate(strlen(BUILD_TIMESAMP) == 0 ? __DATE__ " " __TIME__ : BUILD_TIMESAMP), + debug(firmware.indexOf("debug") >= 0), + trial(firmware.indexOf("trial") >= 0) {} + +void Mycila::AppInfoClass::toJson(const JsonObject& root) const { + root["buildDate"] = buildDate; + root["buildHash"] = buildHash; + root["debug"] = debug; + root["firmware"] = firmware; + root["id"] = id; + root["manufacturer"] = manufacturer; + root["model"] = model; + root["name"] = name; + root["trial"] = trial; + root["version"] = version; +} + +String Mycila::AppInfoClass::_getEspId() const { + uint32_t chipId = 0; + for (int i = 0; i < 17; i += 8) { + chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; + } + String espId = String(chipId, HEX); + espId.toUpperCase(); + return espId; +} + +namespace Mycila { + AppInfoClass AppInfo; +} // namespace Mycila diff --git a/lib/MycilaAppInfo/MycilaAppInfo.h b/lib/MycilaAppInfo/MycilaAppInfo.h new file mode 100644 index 0000000..9e3fa2b --- /dev/null +++ b/lib/MycilaAppInfo/MycilaAppInfo.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +namespace Mycila { + class AppInfoClass { + public: + AppInfoClass(); + + void toJson(const JsonObject& root) const; + + public: + const String id; + const String name; + const String version; + const String model; + const String manufacturer; + const String firmware; + const String buildHash; + const String buildDate; + const bool debug; + const bool trial; + + private: + String _getEspId() const; + }; + + extern AppInfoClass AppInfo; +} // namespace Mycila diff --git a/lib/MycilaAppInfo/library.properties b/lib/MycilaAppInfo/library.properties new file mode 100644 index 0000000..808ac96 --- /dev/null +++ b/lib/MycilaAppInfo/library.properties @@ -0,0 +1,5 @@ +name=MycilaAppInfo +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaButton/LICENSE b/lib/MycilaButton/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaButton/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaButton/MycilaButton.cpp b/lib/MycilaButton/MycilaButton.cpp new file mode 100644 index 0000000..655f904 --- /dev/null +++ b/lib/MycilaButton/MycilaButton.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#define TAG "BUTTON" + +void Mycila::ButtonClass::begin(const uint8_t pin, const String& pressAction, const String& longPressAction) { + if (_enabled) + return; + + if (GPIO_IS_VALID_GPIO(pin)) { + _pin = (gpio_num_t)pin; + } else { + Logger.error(TAG, "Invalid pin: %u", _pin); + _pin = GPIO_NUM_NC; + return; + } + + Logger.debug(TAG, "Enable Push Button..."); + Logger.debug(TAG, "- Pin: %u", _pin); + + _button = new ::Button(_pin, false); + const int32_t duration = 8000; + _button->setParam(button_param_t::BUTTON_LONG_PRESS_TIME_MS, reinterpret_cast(duration)); + + _pressAction = pressAction; + _longPressAction = longPressAction; + + if (!_pressAction.isEmpty()) { + _button->attachSingleClickEventCb( + +[](void* handle, void* data) { + Mycila::ButtonClass* self = (Mycila::ButtonClass*)data; + self->_lastEvent = ButtonEvent::BUTTON_CLICKED; + }, + this); + } + + if (!_longPressAction.isEmpty()) { + _button->attachLongPressHoldEventCb( + +[](void* handle, void* data) { + Mycila::ButtonClass* self = (Mycila::ButtonClass*)data; + self->_lastEvent = ButtonEvent::BUTTON_LONG_PRESS_HOLD; + }, + this); + } + + _enabled = true; +} + +void Mycila::ButtonClass::end() { + if (!_enabled) + return; + Logger.debug(TAG, "Disable Push Button..."); + _enabled = false; + _button->del(); + delete _button; + _button = nullptr; + _pin = GPIO_NUM_NC; + _lastEvent = ButtonEvent::IDLE; +} + +void Mycila::ButtonClass::processEvents() { + if (!_enabled) + return; + ButtonEvent evt = _lastEvent; + _lastEvent = ButtonEvent::IDLE; + switch (evt) { + case ButtonEvent::BUTTON_CLICKED: + _callback(_pressAction); + break; + case ButtonEvent::BUTTON_LONG_PRESS_HOLD: + _callback(_longPressAction); + break; + default: + break; + } +} + +void Mycila::ButtonClass::toJson(const JsonObject& root) const { + root["enabled"] = _enabled; +} + +namespace Mycila { + ButtonClass Button; +} // namespace Mycila diff --git a/lib/MycilaButton/MycilaButton.h b/lib/MycilaButton/MycilaButton.h new file mode 100644 index 0000000..1f397c1 --- /dev/null +++ b/lib/MycilaButton/MycilaButton.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include +#include + +namespace Mycila { + enum class ButtonEvent { + IDLE, + BUTTON_CLICKED, + BUTTON_LONG_PRESS_HOLD + }; + + typedef std::function ButtonActionCallback; + + class ButtonClass { + public: + ~ButtonClass() { end(); } + + void begin(const uint8_t pin, const String& pressAction = emptyString, const String& longPressAction = emptyString); + void end(); + + void processEvents(); + + void toJson(const JsonObject& root) const; + + void listen(ButtonActionCallback callback) { _callback = callback; } + bool isEnabled() const { return _enabled; } + gpio_num_t getPin() const { return _pin; } + + private: + bool _enabled = false; + ::Button* _button = nullptr; + ButtonEvent _lastEvent = ButtonEvent::IDLE; + ButtonActionCallback _callback; + gpio_num_t _pin = GPIO_NUM_NC; + String _pressAction = emptyString; + String _longPressAction = emptyString; + }; + + extern ButtonClass Button; +} // namespace Mycila diff --git a/lib/MycilaButton/library.properties b/lib/MycilaButton/library.properties new file mode 100644 index 0000000..059c616 --- /dev/null +++ b/lib/MycilaButton/library.properties @@ -0,0 +1,5 @@ +name=MycilaButton +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaBuzzer/LICENSE b/lib/MycilaBuzzer/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaBuzzer/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaBuzzer/MycilaBuzzer.cpp b/lib/MycilaBuzzer/MycilaBuzzer.cpp new file mode 100644 index 0000000..f0f7d3e --- /dev/null +++ b/lib/MycilaBuzzer/MycilaBuzzer.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#define TAG "BUZZER" + +void Mycila::BuzzerClass::begin(const uint8_t pin) { + if (_enabled) + return; + + if (GPIO_IS_VALID_OUTPUT_GPIO(pin)) { + _pin = (gpio_num_t)pin; + } else { + Logger.error(TAG, "Invalid pin: %u", _pin); + _pin = GPIO_NUM_NC; + return; + } + + ledcSetup(0, 1000, 8); + ledcAttachPin(_pin, 0); + Logger.debug(TAG, "Enable Buzzer..."); + Logger.debug(TAG, "- Pin: %u", _pin); + + _enabled = true; +} + +void Mycila::BuzzerClass::end() { + if (_enabled) { + Logger.debug(TAG, "Disable Buzzer..."); + _enabled = false; + ledcDetachPin(_pin); + digitalWrite(_pin, LOW); + pinMode(_pin, INPUT); + _pin = GPIO_NUM_NC; + } +} + +void Mycila::BuzzerClass::beep(uint8_t count, uint32_t duration) const { + if (_enabled && count > 0) + while (count-- > 0) { + ledcWriteTone(0, 1000); + delay(duration); + ledcWriteTone(0, 0); + if (count > 0) + delay(duration + duration / 2); + } +} + +void Mycila::BuzzerClass::toJson(const JsonObject& root) const { + root["enabled"] = _enabled; +} + +namespace Mycila { + BuzzerClass Buzzer; +} // namespace Mycila diff --git a/lib/MycilaBuzzer/MycilaBuzzer.h b/lib/MycilaBuzzer/MycilaBuzzer.h new file mode 100644 index 0000000..f3174d8 --- /dev/null +++ b/lib/MycilaBuzzer/MycilaBuzzer.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include + +namespace Mycila { + class BuzzerClass { + public: + void begin(const uint8_t pin); + void end(); + + void toJson(const JsonObject& root) const; + + void beep(uint8_t count = 1, uint32_t duration = 100) const; + bool isEnabled() const { return _enabled; } + gpio_num_t getPin() const { return _pin; } + + private: + bool _enabled = false; + gpio_num_t _pin = GPIO_NUM_NC; + }; + + extern BuzzerClass Buzzer; +} // namespace Mycila diff --git a/lib/MycilaBuzzer/library.properties b/lib/MycilaBuzzer/library.properties new file mode 100644 index 0000000..0cdc099 --- /dev/null +++ b/lib/MycilaBuzzer/library.properties @@ -0,0 +1,5 @@ +name=MycilaBuzzer +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaDimmer/LICENSE b/lib/MycilaDimmer/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaDimmer/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaDimmer/MycilaDimmer.cpp b/lib/MycilaDimmer/MycilaDimmer.cpp new file mode 100644 index 0000000..aaee4ac --- /dev/null +++ b/lib/MycilaDimmer/MycilaDimmer.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include + +#define TAG "DIMMER" + +static const char* DimmerTypeNames[] = { + "Robodyn / TRIAC-based", + "Random SSR", + "Zero-Cross SSR"}; + +void Mycila::Dimmer::begin(const uint32_t pin, const DimmerType type) { + if (_enabled) + return; + + if (GPIO_IS_VALID_OUTPUT_GPIO(pin)) { + _pin = (gpio_num_t)pin; + } else { + Logger.error(TAG, "Disable Dimmer '%s': Invalid pin: %u", name, _pin); + _pin = GPIO_NUM_NC; + return; + } + + Logger.debug(TAG, "Enable Dimmer '%s'...", name); + Logger.debug(TAG, "- Pin: %u", _pin); + Logger.debug(TAG, "- Type: %s", DimmerTypeNames[static_cast(type)]); + + pinMode(_pin, OUTPUT); + digitalWrite(_pin, LOW); + + _dimmer = new DimmableLightLinearized(_pin); + _level = 0; + _dimmer->setBrightness(0); + _enabled = true; +} + +void Mycila::Dimmer::end() { + if (_enabled) { + Logger.debug(TAG, "Disable Dimmer '%s'", name); + _enabled = false; + _level = 0; + _dimmer->setBrightness(0); + delete _dimmer; + _dimmer = nullptr; + _pin = GPIO_NUM_NC; + digitalWrite(_pin, LOW); + } +} + +void Mycila::Dimmer::setLevel(uint8_t newLevel) { + if (!_enabled) + return; + if (newLevel > 100) + newLevel = 100; + if (_level == newLevel) + return; + const uint8_t oldLevel = _level; + _level = newLevel; + _dimmer->setBrightness(map(_level, 0, 100, 0, 255)); + if (_callback && (oldLevel == 0 || oldLevel == 100 || newLevel == 0 || newLevel == 100)) + _callback(newLevel == 0 ? DimmerLevel::OFF : (newLevel == 100 ? DimmerLevel::FULL : DimmerLevel::DIM)); +} + +void Mycila::Dimmer::toJson(const JsonObject& root) const { + root["enabled"] = _enabled; + root["level"] = _level; + root["state"] = _level > 0 ? "on" : "off"; +} diff --git a/lib/MycilaDimmer/MycilaDimmer.h b/lib/MycilaDimmer/MycilaDimmer.h new file mode 100644 index 0000000..ea386ad --- /dev/null +++ b/lib/MycilaDimmer/MycilaDimmer.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace Mycila { + enum class DimmerType { TRIAC = 0, + SSR_RANDOM, + SSR_ZC }; + + enum class DimmerLevel { OFF, + FULL, + DIM }; + + typedef std::function DimmerLevelCallback; + + class Dimmer { + public: + explicit Dimmer(const char* name) : name(name) {} + ~Dimmer() { end(); } + + void begin(const uint32_t pin, const DimmerType type); + void end(); + + void listen(DimmerLevelCallback callback) { _callback = callback; } + + // level: 0-100 (%) + void setLevel(uint8_t level); + // level: 0-100 (%) + uint8_t getLevel() const { return _level; } + + inline void off() { setLevel(0); } + bool isOff() const { return _level == 0; } + bool isOn() const { return _level > 0; } + bool isOnAtFullPower() const { return _level >= 100; } + + const char* getName() const { return name; } + gpio_num_t getPin() const { return _pin; } + bool isEnabled() const { return _enabled; } + + void toJson(const JsonObject& root) const; + + private: + const char* name; + bool _enabled = false; + uint8_t _level = 0; + gpio_num_t _pin = GPIO_NUM_NC; + DimmerLevelCallback _callback = nullptr; + DimmableLightLinearized* _dimmer = nullptr; + }; +} // namespace Mycila diff --git a/lib/MycilaDimmer/library.properties b/lib/MycilaDimmer/library.properties new file mode 100644 index 0000000..b6af791 --- /dev/null +++ b/lib/MycilaDimmer/library.properties @@ -0,0 +1,5 @@ +name=MycilaDimmer +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaGrid/LICENSE b/lib/MycilaGrid/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaGrid/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaGrid/MycilaGrid.cpp b/lib/MycilaGrid/MycilaGrid.cpp new file mode 100644 index 0000000..eaf41f4 --- /dev/null +++ b/lib/MycilaGrid/MycilaGrid.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include +#include + +#define TAG "POWER" + +void Mycila::GridClass::begin(const String& gridPowerMQTTTopic) { + if (!_topic.isEmpty()) + return; + + _topic = gridPowerMQTTTopic; + + Logger.debug(TAG, "Enable MQTT Grid Power..."); + Logger.debug(TAG, "- MQTT Topic: %s", _topic.c_str()); + + if (!_topic.isEmpty()) { + MQTT.subscribe(_topic, [this](const String& topic, const String& payload) { + _mqttGridPower = payload.toFloat(); + _mqttGridPowerUpdateTime = millis(); + }); + } +} + +void Mycila::GridClass::end() { + if (!_topic.isEmpty()) { + Logger.debug(TAG, "Disable Grid Power from MQTT..."); + MQTT.unsubscribe(_topic); + _topic = emptyString; + _mqttGridPower = 0; + _mqttGridPowerUpdateTime = 0; + } +} + +float Mycila::GridClass::getFrequency() const { + float freq = JSY.frequency; + return freq > 0 ? freq : ZCD.getFrequency(); +} + +float Mycila::GridClass::getPower() const { + return !_topic.isEmpty() && !isMQTTGridPowerExpired() ? _mqttGridPower : (JSY.isEnabled() ? JSY.power2 : 0); +} + +float Mycila::GridClass::getVoltage() const { + return JSY.voltage2; +} + +void Mycila::GridClass::toJson(const JsonObject& root) const { + const uint8_t gridFrequency = getFrequency(); + root["frequency"] = gridFrequency; + root["power"] = getPower(); + root["voltage"] = getVoltage(); + root["online"] = gridFrequency > 0; + JsonObject jsy = root["jsy"].to(); + Mycila::JSY.toJson(jsy); + JsonObject zcd = root["zcd"].to(); + Mycila::ZCD.toJson(zcd); +} + +namespace Mycila { + GridClass Grid; +} // namespace Mycila diff --git a/lib/MycilaGrid/MycilaGrid.h b/lib/MycilaGrid/MycilaGrid.h new file mode 100644 index 0000000..19363b6 --- /dev/null +++ b/lib/MycilaGrid/MycilaGrid.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +#ifndef YASOLR_MQTT_GRID_POWER_EXPIRATION +#define YASOLR_MQTT_GRID_POWER_EXPIRATION 60 +#endif + +namespace Mycila { + class GridClass { + public: + void begin(const String& gridPowerMQTTTopic = emptyString); + void end(); + + void toJson(const JsonObject& root) const; + + float getFrequency() const; + float getPower() const; + float getVoltage() const; + inline bool isOnline() const { return getFrequency() > 0; } + bool isMQTTGridPowerExpired() const { return millis() - _mqttGridPowerUpdateTime >= YASOLR_MQTT_GRID_POWER_EXPIRATION * 1000; } + + private: + float _mqttGridPower = 0; + uint32_t _mqttGridPowerUpdateTime = 0; + String _topic; + }; + + extern GridClass Grid; +} // namespace Mycila diff --git a/lib/MycilaGrid/library.properties b/lib/MycilaGrid/library.properties new file mode 100644 index 0000000..8c331dd --- /dev/null +++ b/lib/MycilaGrid/library.properties @@ -0,0 +1,5 @@ +name=MycilaGrid +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaHTTPd/LICENSE b/lib/MycilaHTTPd/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaHTTPd/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaHTTPd/MycilaHTTPd.cpp b/lib/MycilaHTTPd/MycilaHTTPd.cpp new file mode 100644 index 0000000..48704bf --- /dev/null +++ b/lib/MycilaHTTPd/MycilaHTTPd.cpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#include + +#ifndef MYCILA_API_PATH +#define MYCILA_API_PATH "/api" +#endif + +#define TAG "HTTP" + +void Mycila::HTTPdClass::init(AsyncWebServer* server, const String& username, const String& password) { + if (_server) + return; + + _username = username; + _password = password; + _auth = !_username.isEmpty() && !_password.isEmpty(); + + Logger.debug(TAG, "Initializing HTTPd..."); + Logger.debug(TAG, "- Authentication: %s", _auth ? "true" : "false"); + Logger.debug(TAG, "- Username: %s", _username.c_str()); + Logger.debug(TAG, "- Password: %s", _password.isEmpty() ? "" : "********"); + + _server = server; + _running = false; +} + +void Mycila::HTTPdClass::begin() { + if (!_server) + return; + if (_running) + return; + Logger.info(TAG, "Enable HTTPd..."); + _server->onNotFound([](AsyncWebServerRequest* request) { + request->send(404); + }); + _server->begin(); + MDNS.addService("http", "tcp", 80); + _running = true; +} + +void Mycila::HTTPdClass::end() { + if (!_server) + return; + if (!_running) + return; + Logger.info(TAG, "Disable HTTPd..."); + mdns_service_remove("_http", "_tcp"); + _server->end(); + _server = nullptr; + _running = false; +} + +AsyncWebHandler& Mycila::HTTPdClass::get(const char* path, ArRequestHandlerFunction onRequest, bool anonymous) { + Logger.debug(TAG, "[GET ] %s", path); + return _authenticate(&_server->on(path, HTTP_GET, onRequest), anonymous); +} + +AsyncWebHandler& Mycila::HTTPdClass::post(const char* path, ArRequestHandlerFunction onRequest, bool anonymous) { + Logger.debug(TAG, "[POST ] %s", path); + return _authenticate(&_server->on(path, HTTP_POST, onRequest), anonymous); +} + +AsyncWebHandler& Mycila::HTTPdClass::del(const char* path, ArRequestHandlerFunction onRequest, bool anonymous) { + Logger.debug(TAG, "[DELETE] %s", path); + return _authenticate(&_server->on(path, HTTP_DELETE, onRequest), anonymous); +} + +AsyncWebHandler& Mycila::HTTPdClass::any(const char* path, ArRequestHandlerFunction onRequest, bool anonymous) { + Logger.debug(TAG, "[ANY ] %s", path); + return _authenticate(&_server->on(path, HTTP_ANY, onRequest), anonymous); +} + +AsyncWebHandler& Mycila::HTTPdClass::apiGET(const char* path, ArRequestHandlerFunction onRequest, bool anonymous) { + String url; + url.reserve(strlen(MYCILA_API_PATH) + strlen(path)); + url += MYCILA_API_PATH; + url += path; + Logger.debug(TAG, "[GET ] %s", url.c_str()); + return _authenticate(&_server->on(url.c_str(), HTTP_GET, onRequest), anonymous); +} + +AsyncWebHandler& Mycila::HTTPdClass::apiPOST(const char* path, ArRequestHandlerFunction onRequest, bool anonymous) { + String url; + url.reserve(strlen(MYCILA_API_PATH) + strlen(path)); + url += MYCILA_API_PATH; + url += path; + Logger.debug(TAG, "[POST ] %s", url.c_str()); + return _authenticate(&_server->on(url.c_str(), HTTP_POST, onRequest), anonymous); +} + +AsyncWebHandler& Mycila::HTTPdClass::apiUPLOAD(const char* path, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, bool anonymous) { + String url; + url.reserve(strlen(MYCILA_API_PATH) + strlen(path)); + url += MYCILA_API_PATH; + url += path; + Logger.debug(TAG, "[UPLOAD] %s", url.c_str()); + return _authenticate(&_server->on(url.c_str(), HTTP_POST, onRequest, onUpload), anonymous); +} + +AsyncWebHandler& Mycila::HTTPdClass::apiDELETE(const char* path, ArRequestHandlerFunction onRequest, bool anonymous) { + String url; + url.reserve(strlen(MYCILA_API_PATH) + strlen(path)); + url += MYCILA_API_PATH; + url += path; + Logger.debug(TAG, "[DELETE] %s", url.c_str()); + return _authenticate(&_server->on(url.c_str(), HTTP_DELETE, onRequest), anonymous); +} + +AsyncWebHandler& Mycila::HTTPdClass::apiANY(const char* path, ArRequestHandlerFunction onRequest, bool anonymous) { + String url; + url.reserve(strlen(MYCILA_API_PATH) + strlen(path)); + url += MYCILA_API_PATH; + url += path; + Logger.debug(TAG, "[ANY ] %s", url.c_str()); + return _authenticate(&_server->on(url.c_str(), HTTP_ANY, onRequest), anonymous); +} + +AsyncWebSocket& Mycila::HTTPdClass::webSocket(const char* path) { + AsyncWebSocket* ws = new AsyncWebSocket(path); + _authenticate(ws, false); + _server->addHandler(ws); + return *ws; +} + +AsyncWebHandler& Mycila::HTTPdClass::_authenticate(AsyncWebHandler* handler, bool anonymous) { + return handler->setAuthentication(anonymous ? "" : (_auth ? _username.c_str() : ""), anonymous ? "" : (_auth ? _password.c_str() : "")); +} + +namespace Mycila { + HTTPdClass HTTPd; +} // namespace Mycila diff --git a/lib/MycilaHTTPd/MycilaHTTPd.h b/lib/MycilaHTTPd/MycilaHTTPd.h new file mode 100644 index 0000000..6ea8d25 --- /dev/null +++ b/lib/MycilaHTTPd/MycilaHTTPd.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +namespace Mycila { + class HTTPdClass { + public: + void init(AsyncWebServer* server, const String& username = emptyString, const String& password = emptyString); + void begin(); + void end(); + + AsyncWebHandler& get(const char* path, ArRequestHandlerFunction onRequest, bool anonymous = false); + AsyncWebHandler& post(const char* path, ArRequestHandlerFunction onRequest, bool anonymous = false); + AsyncWebHandler& del(const char* path, ArRequestHandlerFunction onRequest, bool anonymous = false); + AsyncWebHandler& any(const char* path, ArRequestHandlerFunction onRequest, bool anonymous = false); + + AsyncWebHandler& apiGET(const char* path, ArRequestHandlerFunction onRequest, bool anonymous = false); + AsyncWebHandler& apiPOST(const char* path, ArRequestHandlerFunction onRequest, bool anonymous = false); + AsyncWebHandler& apiUPLOAD(const char* path, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, bool anonymous = false); + AsyncWebHandler& apiDELETE(const char* path, ArRequestHandlerFunction onRequest, bool anonymous = false); + AsyncWebHandler& apiANY(const char* path, ArRequestHandlerFunction onRequest, bool anonymous = false); + + AsyncWebSocket& webSocket(const char* path); + + bool isEnabled() const { return _server != nullptr; } + bool isRunning() const { return _running; } + bool isAuthEnabled() const { return _auth; } + const String& getUsername() const { return _username; } + const String& getPassword() const { return _password; } + + private: + AsyncWebServer* _server = nullptr; + String _username = emptyString; + String _password = emptyString; + bool _auth = false; + bool _running = false; + + private: + AsyncWebHandler& _authenticate(AsyncWebHandler* handler, bool anonymous); + }; + + extern HTTPdClass HTTPd; +} // namespace Mycila diff --git a/lib/MycilaHTTPd/library.properties b/lib/MycilaHTTPd/library.properties new file mode 100644 index 0000000..af7d0dc --- /dev/null +++ b/lib/MycilaHTTPd/library.properties @@ -0,0 +1,5 @@ +name=MycilaHTTPd +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaLights/LICENSE b/lib/MycilaLights/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaLights/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaLights/MycilaLights.cpp b/lib/MycilaLights/MycilaLights.cpp new file mode 100644 index 0000000..5d23cb9 --- /dev/null +++ b/lib/MycilaLights/MycilaLights.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#define TAG "LIGHTS" + +void Mycila::LightsClass::begin(const uint8_t greenPin, const uint8_t yellowPin, const uint8_t redPin) { + Logger.debug(TAG, "Enable Physical LEDs..."); + + if (GPIO_IS_VALID_OUTPUT_GPIO(greenPin)) { + _greenPin = (gpio_num_t)greenPin; + Logger.debug(TAG, "- Pin: %u (green)", _greenPin); + pinMode(_greenPin, OUTPUT); + } else { + Logger.error(TAG, "Invalid pin: %u", _greenPin); + _greenPin = GPIO_NUM_NC; + } + + if (GPIO_IS_VALID_OUTPUT_GPIO(yellowPin)) { + _yellowPin = (gpio_num_t)yellowPin; + Logger.debug(TAG, "- Pin: %u (yellow)", _yellowPin); + pinMode(_yellowPin, OUTPUT); + } else { + Logger.error(TAG, "Invalid pin: %u", _yellowPin); + _yellowPin = GPIO_NUM_NC; + } + + if (GPIO_IS_VALID_OUTPUT_GPIO(redPin)) { + _redPin = (gpio_num_t)redPin; + Logger.debug(TAG, "- Pin: %u (red)", _redPin); + pinMode(_redPin, OUTPUT); + } else { + Logger.error(TAG, "Invalid pin: %u", _redPin); + _redPin = GPIO_NUM_NC; + } +} + +void Mycila::LightsClass::end() { + set(LightState::OFF, LightState::OFF, LightState::OFF); + if (_greenPin != GPIO_NUM_NC) + digitalWrite(_greenPin, LOW); + if (_yellowPin != GPIO_NUM_NC) + digitalWrite(_yellowPin, LOW); + if (_redPin != GPIO_NUM_NC) + digitalWrite(_redPin, LOW); + _greenPin = GPIO_NUM_NC; + _yellowPin = GPIO_NUM_NC; + _redPin = GPIO_NUM_NC; +} + +void Mycila::LightsClass::set(LightState green, LightState yellow, LightState red) { + if (green != LightState::NONE) + _green = green == LightState::ON; + if (yellow != LightState::NONE) + _yellow = yellow == LightState::ON; + if (red != LightState::NONE) + _red = red == LightState::ON; + if (_greenPin != GPIO_NUM_NC) + digitalWrite(_greenPin, _green ? HIGH : LOW); + if (_yellowPin != GPIO_NUM_NC) + digitalWrite(_yellowPin, _yellow ? HIGH : LOW); + if (_redPin != GPIO_NUM_NC) + digitalWrite(_redPin, _red ? HIGH : LOW); +} + +void Mycila::LightsClass::toJson(const JsonObject& root) const { + root["code"] = toString(); + root["green"] = _green ? "on" : "off"; + root["red"] = _red ? "on" : "off"; + root["yellow"] = _yellow ? "on" : "off"; +} + +namespace Mycila { + LightsClass Lights; +} // namespace Mycila diff --git a/lib/MycilaLights/MycilaLights.h b/lib/MycilaLights/MycilaLights.h new file mode 100644 index 0000000..288ff69 --- /dev/null +++ b/lib/MycilaLights/MycilaLights.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include + +namespace Mycila { + enum class LightState { + NONE = 0, + ON, + OFF + }; + + class LightsClass { + public: + void begin(const uint8_t greenPin, const uint8_t yellowPin, const uint8_t redPin); + void end(); + + bool isEnabled() const { return _greenPin != GPIO_NUM_NC || _yellowPin != GPIO_NUM_NC || _redPin != GPIO_NUM_NC; } + + inline void setAllOn() { set(LightState::ON, LightState::ON, LightState::ON); } + inline void setAllOff() { set(LightState::OFF, LightState::OFF, LightState::OFF); } + + inline void setGreen(bool state) { set(state ? LightState::ON : LightState::OFF, LightState::NONE, LightState::NONE); } + inline void setYellow(bool state) { set(LightState::NONE, state ? LightState::ON : LightState::OFF, LightState::NONE); } + inline void setRed(bool state) { set(LightState::NONE, LightState::NONE, state ? LightState::ON : LightState::OFF); } + void set(LightState green, LightState yellow, LightState red); + + LightState getGreen() const { return _green ? LightState::ON : LightState::OFF; } + LightState getYellow() const { return _yellow ? LightState::ON : LightState::OFF; } + LightState getRed() const { return _red ? LightState::ON : LightState::OFF; } + + inline bool isGreenOn() const { return getGreen() == LightState::ON; } + inline bool isYellowOn() const { return getYellow() == LightState::ON; } + inline bool isRedOn() const { return getRed() == LightState::ON; } + + inline bool areAllOn() const { return isGreenOn() && isYellowOn() && isRedOn(); } + inline bool areAllOff() const { return !isGreenOn() && !isYellowOn() && !isRedOn(); } + + inline String toString() const { return String(isGreenOn() ? "🟢 " : "⚫ ") + (isYellowOn() ? "🟡 " : "⚫ ") + (isRedOn() ? "🔴" : "⚫"); } + + void toJson(const JsonObject& root) const; + + gpio_num_t getGreenPin() const { return _greenPin; } + gpio_num_t getYellowPin() const { return _yellowPin; } + gpio_num_t getRedPin() const { return _redPin; } + + private: + gpio_num_t _greenPin = GPIO_NUM_NC; + gpio_num_t _yellowPin = GPIO_NUM_NC; + gpio_num_t _redPin = GPIO_NUM_NC; + bool _green = false; + bool _yellow = false; + bool _red = false; + }; + + extern LightsClass Lights; +} // namespace Mycila diff --git a/lib/MycilaLights/library.properties b/lib/MycilaLights/library.properties new file mode 100644 index 0000000..c00ff5b --- /dev/null +++ b/lib/MycilaLights/library.properties @@ -0,0 +1,5 @@ +name=MycilaLights +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaRelayManager/LICENSE b/lib/MycilaRelayManager/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaRelayManager/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaRelayManager/MycilaRelayManager.cpp b/lib/MycilaRelayManager/MycilaRelayManager.cpp new file mode 100644 index 0000000..159bdbf --- /dev/null +++ b/lib/MycilaRelayManager/MycilaRelayManager.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include + +#ifndef MYCILA_RELAY_THRESHOLD_TOLERANCE_START +#define MYCILA_RELAY_THRESHOLD_TOLERANCE_START 20 +#endif + +#ifndef MYCILA_RELAY_THRESHOLD_TOLERANCE_STOP +#define MYCILA_RELAY_THRESHOLD_TOLERANCE_STOP 20 +#endif + +#define TAG "RELAY-M" + +void Mycila::RelayManagerClass::toJson(const JsonObject& root) const { + for (const auto& relay : _relays) { + relay->toJson(root[relay->getName()].to()); + } +} + +void Mycila::RelayManagerClass::autoCommute() { + const float grid = Router.getVirtualGridPower(); + + // check if we need to start a relay + { + for (auto& relay : _relays) { + if (relay->isEnabled() && relay->isOff()) { + const float threshold = RelayManagerConfig.getPowerThreshold(relay->getName()); + if (threshold > 0 && grid <= -(threshold + round(threshold * MYCILA_RELAY_THRESHOLD_TOLERANCE_START / 100.0))) { + Logger.info(TAG, "Auto starting relay '%s'...", relay->getName()); + relay->setState(true); + // we stop the loop and let time for routing to adjust before checking again + return; + } + } + } + } + + // check if we need to stop a relay + { + for (int i = _relays.size() - 1; i >= 0; i--) { + auto& relay = _relays[i]; + if (relay->isEnabled() && relay->isOn()) { + const float threshold = RelayManagerConfig.getPowerThreshold(relay->getName()); + if (threshold > 0 && grid - threshold >= -(threshold - round(threshold * MYCILA_RELAY_THRESHOLD_TOLERANCE_STOP / 100.0))) { + Logger.info(TAG, "Auto stopping relay '%s'...", relay->getName()); + relay->setState(false); + // we stop the loop and let time for routing to adjust before checking again + return; + } + } + } + } +} + +bool Mycila::RelayManagerClass::tryRelayState(Relay* relay, bool switchOn, uint32_t duration) { + if (!relay->isEnabled()) + return false; + + if (RelayManagerConfig.getPowerThreshold(relay->getName()) > 0) { + Logger.warn(TAG, "Auto Mode '%s' is activated: unable to switch Relay...", relay->getName()); + return false; + } + + relay->setState(switchOn, duration); + return true; +} + +namespace Mycila { + RelayManagerClass RelayManager; + RelayManagerConfigClass RelayManagerConfig; +} // namespace Mycila diff --git a/lib/MycilaRelayManager/MycilaRelayManager.h b/lib/MycilaRelayManager/MycilaRelayManager.h new file mode 100644 index 0000000..e82a653 --- /dev/null +++ b/lib/MycilaRelayManager/MycilaRelayManager.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +#include +#include + +namespace Mycila { + + class RelayManagerConfigClass { + public: + uint16_t getPowerThreshold(const char* name) const; + }; + + class RelayManagerClass { + public: + void setRelays(const std::vector relays) { _relays = relays; } + + void toJson(const JsonObject& root) const; + + void autoCommute(); + bool tryRelayState(Relay* relay, bool state, uint32_t duration = 0); + + private: + std::vector _relays = {}; + }; + + extern RelayManagerConfigClass RelayManagerConfig; + extern RelayManagerClass RelayManager; +} // namespace Mycila diff --git a/lib/MycilaRelayManager/library.properties b/lib/MycilaRelayManager/library.properties new file mode 100644 index 0000000..3c3b9e9 --- /dev/null +++ b/lib/MycilaRelayManager/library.properties @@ -0,0 +1,5 @@ +name=MycilaRelayManager +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaRouter/LICENSE b/lib/MycilaRouter/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaRouter/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaRouter/MycilaRouter.cpp b/lib/MycilaRouter/MycilaRouter.cpp new file mode 100644 index 0000000..a930ea0 --- /dev/null +++ b/lib/MycilaRouter/MycilaRouter.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include + +#define TAG "ROUTER" + +void Mycila::RouterClass::toJson(const JsonObject& root) const { + root["energy"] = getTotalRoutedEnergy(); + root["power_factor"] = getTotalPowerFactor(); + root["power"] = getTotalRoutedPower(); + root["thdi"] = getTotalTHDi(); + root["virtual_grid_power"] = getVirtualGridPower(); + for (const auto& output : _outputs) { + output->toJson(root[output->getName()].to()); + } +} + +float Mycila::RouterClass::getVirtualGridPower() const { + return Grid.getPower() - getTotalRoutedPower(); +} + +float Mycila::RouterClass::getTotalRoutedPower() const { + for (auto& output : _outputs) { + if (output->getState() == RouterOutputState::OUTPUT_ROUTING) { + return abs(JSY.power1); + } + } + return 0; +} + +float Mycila::RouterClass::getTotalTHDi() const { + for (auto& output : _outputs) { + if (output->getState() == RouterOutputState::OUTPUT_ROUTING) { + // https://fr.electrical-installation.org/frwiki/Indicateur_de_distorsion_harmonique_:_facteur_de_puissance + const float phi = 0; // no phase shift for resistive load between voltage and current (cos(phi) = 1) + const float pf = getTotalPowerFactor(); + return sqrt(pow(cos(phi), 2) / pow(pf, 2) - 1); + } + } + return 0; +} + +float Mycila::RouterClass::getTotalPowerFactor() const { + for (auto& output : _outputs) { + if (output->getState() == RouterOutputState::OUTPUT_ROUTING) { + return JSY.powerFactor1; + } + } + return 0; +} + +float Mycila::RouterClass::getTotalRoutedEnergy() const { + return JSY.energy1 + JSY.energyReturned1; +} + +bool Mycila::RouterClass::isRouting() const { + for (const auto& output : _outputs) { + if (output->getState() == RouterOutputState::OUTPUT_ROUTING) { + return true; + } + } + return false; +} + +namespace Mycila { + RouterClass Router; +} // namespace Mycila + +//TODO: do we need power per output ? +// float Mycila::RouterClass::getOutputPower(const RouterOutput* output) const { +// const int idx = &output - &_outputs[0]; + +// int routing = 0; +// float dimmerLevel = 0; + +// for (size_t i = 0, max = _outputs.size(); i < max; i++) { +// const uint8_t level = _outputs[i].getDimmer()->getLevel(); +// if (level > 0) { +// routing++; +// if (i == idx) +// dimmerLevel = level; +// } else if (i == idx) { +// return 0; +// } +// } + +// // more accurate +// if (routing == 1) +// return abs(JSY.power1); + +// // approximation with P = U * U / R +// const float u = Grid.getVoltage(); +// const float r = output.getResistance(idx); + +// return (u * u / r) * (dimmerLevel / 100.0); +// } diff --git a/lib/MycilaRouter/MycilaRouter.h b/lib/MycilaRouter/MycilaRouter.h new file mode 100644 index 0000000..7b5976a --- /dev/null +++ b/lib/MycilaRouter/MycilaRouter.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +#include +#include + +namespace Mycila { + class RouterClass { + public: + void setOutputs(const std::vector& outputs) { _outputs = outputs; } + + void toJson(const JsonObject& root) const; + + float getVirtualGridPower() const; + float getTotalRoutedPower() const; + float getTotalPowerFactor() const; + float getTotalTHDi() const; + float getTotalRoutedEnergy() const; + + bool isRouting() const; + + private: + std::vector _outputs = {}; + }; + + extern RouterClass Router; +} // namespace Mycila diff --git a/lib/MycilaRouter/library.properties b/lib/MycilaRouter/library.properties new file mode 100644 index 0000000..d54acb7 --- /dev/null +++ b/lib/MycilaRouter/library.properties @@ -0,0 +1,5 @@ +name=MycilaRouter +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaRouterOutput/LICENSE b/lib/MycilaRouterOutput/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaRouterOutput/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaRouterOutput/MycilaRouterOutput.cpp b/lib/MycilaRouterOutput/MycilaRouterOutput.cpp new file mode 100644 index 0000000..992b3c7 --- /dev/null +++ b/lib/MycilaRouterOutput/MycilaRouterOutput.cpp @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include + +#define TAG "OUTPUT" + +static const char* RouterOutputStateNames[] = { + "Disabled", + "Idle", + "Routing", + "Manual Bypass", + "Auto Bypass", +}; + +static const char* DaysOfWeek[] = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"}; + +void Mycila::RouterOutput::applyDimmerLimit() { + if (!_dimmer->isEnabled()) + return; + if (_autoBypassEnabled) + return; + if (_dimmer->isOff()) + return; + uint8_t limit = RouterOutputConfig.getDimmerLevelLimit(_dimmer->getName()); + if (_dimmer->getLevel() > limit) { + Logger.warn(TAG, "Dimmer '%s' reached its limit at %u %", _dimmer->getName(), limit); + _dimmer->setLevel(limit); + } +} + +bool Mycila::RouterOutput::tryBypassRelayState(bool switchOn) { + if (_autoBypassEnabled && !switchOn) { + Logger.warn(TAG, "Auto Bypass '%s' is activated: unable to turn of bypass relay", _name); + return false; + } + _setBypassRelay(switchOn); + return true; +} + +bool Mycila::RouterOutput::tryDimmerLevel(uint8_t level) { + if (!_dimmer->isEnabled()) + return false; + + if (_autoBypassEnabled) { + Logger.warn(TAG, "Auto Bypass '%s' is activated: unable to change dimmer level", _name); + return false; + } + + if (RouterOutputConfig.isAutoDimmerEnabled(_name)) { + Logger.warn(TAG, "Auto Dimmer '%s' is activated: unable to change dimmer level", _name); + return false; + } + + Logger.debug(TAG, "Setting Dimmer '%s' level to %u %...", _dimmer->getName(), level); + _relay->off(); + _dimmer->setLevel(level); + return true; +} + +Mycila::RouterOutputState Mycila::RouterOutput::getState() const { + if (!_dimmer->isEnabled()) + return RouterOutputState::OUTPUT_DISABLED; + if (_dimmer->getLevel() > 0) + return RouterOutputState::OUTPUT_ROUTING; + // dimmer level is 0 + if (_autoBypassEnabled) + return RouterOutputState::OUTPUT_BYPASS_AUTO; + if (_relay->isOn()) + return RouterOutputState::OUTPUT_BYPASS_MANUAL; + return RouterOutputState::OUTPUT_IDLE; +} + +const char* Mycila::RouterOutput::getStateString() const { return RouterOutputStateNames[static_cast(getState())]; } + +void Mycila::RouterOutput::toJson(const JsonObject& root) const { + root["enabled"] = _dimmer->isEnabled(); + root["state"] = RouterOutputStateNames[static_cast(getState())]; + JsonObject dimmerJson = root["dimmer"].to(); + _dimmer->toJson(dimmerJson); + JsonObject relayJson = root["bypass_relay"].to(); + _relay->toJson(relayJson); + JsonObject temperatureSensorJson = root["temp_sensor"].to(); + _temperatureSensor->toJson(temperatureSensorJson); +} + +float Mycila::RouterOutput::getResistance() const { + // TODO: auto-calibration or config ? + return 227.34; +} + +void Mycila::RouterOutput::autoBypass() { + if (!RouterOutputConfig.isAutoBypassEnabled(_name)) { + if (_autoBypassEnabled) { + Logger.warn(TAG, "Auto Bypass disabled: stopping Auto Bypass '%s'...", _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + if (!_relay->isEnabled() && !_dimmer->isEnabled()) { + if (_autoBypassEnabled) { + Logger.warn(TAG, "Relay and dimmer disabled: stopping Auto Bypass '%s'...", _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + if (!_temperatureSensor->isEnabled()) { + if (_autoBypassEnabled) { + Logger.warn(TAG, "Temperature Sensor disabled: stopping Auto Bypass '%s'...", _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + if (!_temperatureSensor->isValid()) { + if (_autoBypassEnabled) { + Logger.warn(TAG, "Temperature sensor is not answering: stopping Auto Bypass '%s'...", _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + const float temp = _temperatureSensor->getTemperature(); + + if (temp >= RouterOutputConfig.getAutoStopTemperature(_name)) { + if (_autoBypassEnabled) { + Logger.info(TAG, "Temperature reached %.02f °C: stopping Auto Bypass '%s'...", temp, _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + if (temp >= RouterOutputConfig.getAutoStartTemperature(_name)) { + // temperature OK, no need to start + return; + } + + if (!NTP.isSynced()) { + if (_autoBypassEnabled) { + Logger.warn(TAG, "NTP not available: stopping Auto Bypass '%s'...", _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + struct tm timeInfo; + if (!getLocalTime(&timeInfo, 5)) { + if (_autoBypassEnabled) { + Logger.warn(TAG, "Unable to get time: stopping Auto Bypass '%s'...", _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + const String autoStart = RouterOutputConfig.getAutoStartTime(_name); + const String autoStop = RouterOutputConfig.getAutoStopTime(_name); + const int inRange = Time::timeInRange(&timeInfo, autoStart, autoStop); + if (inRange == -1) { + if (_autoBypassEnabled) { + Logger.warn(TAG, "Time range %s to %s is invalid: stopping Auto Bypass '%s'...", autoStart.c_str(), autoStop.c_str(), _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + if (!inRange) { + if (_autoBypassEnabled) { + Logger.info(TAG, "Time reached %s: stopping Auto Bypass '%s'...", autoStop.c_str(), _name); + _autoBypassEnabled = false; + _setBypassRelay(false); + } + return; + } + + // time and temp OK, let's start + if (!_autoBypassEnabled) { + // auto bypass is not enabled, let's start it + const char* wday = DaysOfWeek[timeInfo.tm_wday]; + if (RouterOutputConfig.getWeekDays(_name).indexOf(wday) >= 0) { + Logger.info(TAG, "Time within %s-%s on %s: starting Auto Bypass '%s' at %.02f °C...", autoStart.c_str(), autoStop.c_str(), wday, _name, temp); + _autoBypassEnabled = true; + _setBypassRelay(true); + } + return; + } + + // auto bypass is enabled + + // relay is on ? + if (_relay->isOn()) + return; + + // or relay is disabled and dimmer at full power tu replace it ? + if (!_relay->isEnabled() && _dimmer->isOnAtFullPower()) + return; + + // start bypass + Logger.info(TAG, "Auto Bypass '%s' is activated: restarting Relay...", _name); + _setBypassRelay(true); +} + +void Mycila::RouterOutput::_setBypassRelay(bool state) { + if (_relay->isEnabled()) { + Logger.debug(TAG, "Turning %s Bypass Relay '%s'...", state ? "on" : "off", _relay->getName()); + if (state) + _dimmer->off(); + _relay->setState(state); + + } else { + Logger.debug(TAG, "Turning %s Dimmer '%s'...", state ? "on" : "off", _dimmer->getName()); + _dimmer->setLevel(state ? 100 : 0); + } +} + +namespace Mycila { + RouterOutputConfigClass RouterOutputConfig; +} // namespace Mycila diff --git a/lib/MycilaRouterOutput/MycilaRouterOutput.h b/lib/MycilaRouterOutput/MycilaRouterOutput.h new file mode 100644 index 0000000..f1d07e2 --- /dev/null +++ b/lib/MycilaRouterOutput/MycilaRouterOutput.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include + +#include + +namespace Mycila { + enum class RouterOutputState { + // output disabled + OUTPUT_DISABLED = 0, + // idle + OUTPUT_IDLE, + // excess power sent to load + OUTPUT_ROUTING, + // full power sent to load through relay (manual trigger) + OUTPUT_BYPASS_MANUAL, + // full power sent to load through relay (auto trigger) + OUTPUT_BYPASS_AUTO + }; + + typedef std::function RouterOutputStateCallback; + + class RouterOutputConfigClass { + public: + bool isAutoDimmerEnabled(const char* name) const; + uint8_t getDimmerLevelLimit(const char* name) const; + bool isAutoBypassEnabled(const char* name) const; + uint8_t getAutoStartTemperature(const char* name) const; + uint8_t getAutoStopTemperature(const char* name) const; + String getAutoStartTime(const char* name) const; + String getAutoStopTime(const char* name) const; + String getWeekDays(const char* name) const; + }; + + class RouterOutput { + public: + RouterOutput(const char* name, Dimmer* dimmer, TemperatureSensor* temperatureSensor, Relay* relay) : _name(name), + _dimmer(dimmer), + _temperatureSensor(temperatureSensor), + _relay(relay) {} + + void autoBypass(); + void applyDimmerLimit(); + + const char* getName() const { return _name; } + RouterOutputState getState() const; + const char* getStateString() const; + bool isEnabled() const { return _dimmer->isEnabled(); } + const Dimmer* getDimmer() const { return _dimmer; } + const TemperatureSensor* getTemperatureSensor() const { return _temperatureSensor; } + const Relay* getBypassRelay() const { return _relay; } + + void listen(RouterOutputStateCallback callback) { _callback = callback; } + + bool isBypassRelayOn() const { return _relay->isEnabled() ? _relay->isOn() : _dimmer->isOn(); } + bool isBypassRelayEnabled() const { return _relay->isEnabled() || _dimmer->isEnabled(); } + bool tryBypassRelayToggle() { return tryBypassRelayState(!isBypassRelayOn()); } + bool tryBypassRelayState(bool state); + // level: 0-100 (%) + bool tryDimmerLevel(uint8_t level); + + float getResistance() const; + + void toJson(const JsonObject& root) const; + + private: + const char* _name; + Dimmer* _dimmer; + const TemperatureSensor* _temperatureSensor; + Relay* _relay; + bool _autoBypassEnabled = false; + RouterOutputStateCallback _callback = nullptr; + + private: + void _setBypassRelay(bool state); + }; + + extern RouterOutputConfigClass RouterOutputConfig; +} // namespace Mycila diff --git a/lib/MycilaRouterOutput/library.properties b/lib/MycilaRouterOutput/library.properties new file mode 100644 index 0000000..1758f57 --- /dev/null +++ b/lib/MycilaRouterOutput/library.properties @@ -0,0 +1,5 @@ +name=MycilaRouterOutput +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaSystem/LICENSE b/lib/MycilaSystem/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaSystem/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaSystem/MycilaSystem.cpp b/lib/MycilaSystem/MycilaSystem.cpp new file mode 100644 index 0000000..b9f2db0 --- /dev/null +++ b/lib/MycilaSystem/MycilaSystem.cpp @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#include +#include +#include + +#define TAG "SYSTEM" + +#define KEY_BOOTS "boots" +#define KEY_RESETS "resets" + +#ifndef MYCILA_NVM_RESET_BOOT_DELAY +#define MYCILA_NVM_RESET_BOOT_DELAY 3 +#endif + +void Mycila::SystemClass::begin() { + Logger.info(TAG, "Initializing File System..."); + nvs_flash_init(); + if (LittleFS.begin(false)) + Logger.debug(TAG, "File System initialized"); + else { + Logger.error(TAG, "Failed. Trying to format..."); + if (LittleFS.begin(true)) + Logger.info(TAG, "Successfully formatted and initialized"); + else + Logger.error(TAG, "Failed to format"); + } + + Preferences prefs; + prefs.begin(TAG, false); + + _boots = (prefs.isKey(KEY_BOOTS) ? prefs.getULong(KEY_BOOTS, 0) : 0) + 1; + prefs.putULong(KEY_BOOTS, _boots); + Logger.debug(TAG, "Booted %llu times", _boots); + +#ifdef MYCILA_BOOT_WAIT_FOR_RESET + const int count = (prefs.isKey(KEY_RESETS) ? prefs.getInt(KEY_RESETS, 0) : 0) + 1; + prefs.putInt(KEY_RESETS, count); + if (count >= 3) { + prefs.end(); + reset(); + } else { + Logger.warn(TAG, "WAITING FOR HARD RESET..."); + for (uint32_t d = 0; d < MYCILA_NVM_RESET_BOOT_DELAY * 1000UL; d += 500) { + delay(500); + } + prefs.remove(KEY_RESETS); + Logger.debug(TAG, "No hard reset"); + } +#endif + + prefs.end(); +} + +void Mycila::SystemClass::reset() { + Logger.debug(TAG, "Triggering System Reset..."); + nvs_flash_erase(); + nvs_flash_init(); + esp_restart(); +} + +void Mycila::SystemClass::restart(uint32_t delayMillisBeforeRestart) { + Logger.debug(TAG, "Triggering System Restart..."); + if (delayMillisBeforeRestart == 0) + esp_restart(); + else { + _delayedTask.once_ms(delayMillisBeforeRestart, esp_restart); + } +} + +const Mycila::SystemMemory Mycila::SystemClass::getMemory() const { + multi_heap_info_t info; + heap_caps_get_info(&info, MALLOC_CAP_INTERNAL); + return { + .total = info.total_free_bytes + info.total_allocated_bytes, + .used = info.total_allocated_bytes, + .free = info.total_free_bytes, + .usage = round(static_cast(info.total_allocated_bytes) / static_cast(info.total_free_bytes + info.total_allocated_bytes) * 10000) / 100}; +} + +void Mycila::SystemClass::toJson(const JsonObject& root) const { + SystemMemory memory = getMemory(); + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + root["boots"] = _boots; + root["chip_cores"] = chip_info.cores; + root["chip_model"] = ESP.getChipModel(); + root["chip_revision"] = chip_info.revision; + root["cpu_freq"] = ESP.getCpuFreqMHz(); + root["heap_total"] = memory.total; + root["heap_usage"] = memory.usage; + root["heap_used"] = memory.used; + root["uptime"] = getUptime(); +} + +namespace Mycila { + SystemClass System; +} // namespace Mycila diff --git a/lib/MycilaSystem/MycilaSystem.h b/lib/MycilaSystem/MycilaSystem.h new file mode 100644 index 0000000..b12ff97 --- /dev/null +++ b/lib/MycilaSystem/MycilaSystem.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace Mycila { + typedef struct { + uint32_t total; + uint32_t used; + uint32_t free; + float usage; + } SystemMemory; + + class SystemClass { + public: + void begin(); + + void reset(); + void restart(uint32_t delayMillisBeforeRestart = 0); + + inline int64_t getUptime() const { return esp_timer_get_time() / (int64_t)1000000; } + const SystemMemory getMemory() const; + uint32_t getBootCount() const { return _boots; } + + void toJson(const JsonObject& root) const; + + private: + uint32_t _boots = 0; + Ticker _delayedTask; + }; + + extern SystemClass System; +} // namespace Mycila diff --git a/lib/MycilaSystem/library.properties b/lib/MycilaSystem/library.properties new file mode 100644 index 0000000..1646a9d --- /dev/null +++ b/lib/MycilaSystem/library.properties @@ -0,0 +1,5 @@ +name=MycilaSystem +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaTaskManager/LICENSE b/lib/MycilaTaskManager/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaTaskManager/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaTaskManager/MycilaTaskManager.h b/lib/MycilaTaskManager/MycilaTaskManager.h new file mode 100644 index 0000000..2cc74db --- /dev/null +++ b/lib/MycilaTaskManager/MycilaTaskManager.h @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +#ifdef MYCILA_TASK_MANAGER_JSON_SUPPORT +#include +#endif + +#define MYCILA_TASK_MANAGER_VERSION "1.0.0" +#define MYCILA_TASK_MANAGER_VERSION_MAJOR 1 +#define MYCILA_TASK_MANAGER_VERSION_MINOR 0 +#define MYCILA_TASK_MANAGER_VERSION_REVISION 0 + +namespace Mycila { + namespace TimeUnit { + constexpr uint32_t MICROSECONDS = 1UL; + constexpr uint32_t MILLISECONDS = 1000UL; + constexpr uint32_t SECONDS = 1000000UL; + constexpr uint32_t MINUTES = 60 * SECONDS; + constexpr uint32_t HOURS = 60 * MINUTES; + constexpr uint32_t DAYS = 24 * HOURS; + } // namespace TimeUnit + + enum class TaskType { + // once enabled, the task will run once and then disable itself + ONCE, + // the task will run at the specified interval as long as it is enabled + FOREVER + }; + + class Task; + + typedef std::function TaskFunction; + typedef std::function TaskPredicate; + typedef std::function TaskIntervalSupplier; + typedef std::function TaskDoneCallback; + + class Task { + public: + Task(const char* name, TaskFunction fn, TaskType type = TaskType::FOREVER, uint32_t intervalMicros = 0, bool enabled = false) : _name(name), + _fn(fn), + _type(type) { + assert(_fn); + setInterval(intervalMicros); + setEnabled(enabled); + } + + const char* getName() const { return _name; } + TaskType getType() const { return _type; } + uint32_t getInterval() const { return _intervalSupplier ? _intervalSupplier() : 0; } + bool isEnabled() const { return _enabledPredicate && _enabledPredicate(); } + bool isRunning() const { return _running; } + bool isEarlyRunRequested() const { return _requestEarlyRun; } + bool isDebug() const { return _debugPredicate && _debugPredicate(); } + uint32_t getLastStart() const { return _lastStart; } + uint32_t getLastEnd() const { return _lastEnd; } + uint32_t getLastRuntime(uint32_t unit = TimeUnit::MICROSECONDS) const { return _lastRuntime / unit; } + bool shouldRun() const { + if (!isEnabled()) + return false; + const uint32_t itvl = getInterval(); + return itvl == 0 || isEarlyRunRequested() || micros() - getLastEnd() >= itvl; + } + + Task& setEnabled(bool enabled) { return enabled ? resume() : pause(); } + Task& pause() { + _enabledPredicate = nullptr; + return *this; + } + Task& resume() { + _enabledPredicate = []() { + return true; + }; + return *this; + } + Task& when(TaskPredicate predicate) { + _enabledPredicate = predicate; + return *this; + } + + Task& setInterval(uint32_t intervalMicros) { + if (intervalMicros == 0) { + _intervalSupplier = nullptr; + } + _intervalSupplier = [intervalMicros]() { + return intervalMicros; + }; + return *this; + } + Task& interval(TaskIntervalSupplier supplier) { + _intervalSupplier = supplier; + return *this; + } + + Task& setData(void* params) { + _params = params; + return *this; + } + + Task& debug() { + _debugPredicate = []() { + return true; + }; + return *this; + } + Task& debugIf(TaskPredicate predicate) { + _debugPredicate = predicate; + return *this; + } + + bool tryRun() { + if (!isEnabled()) + return false; + const uint32_t itvl = getInterval(); + const uint32_t now = micros(); + if (itvl != 0 && !isEarlyRunRequested() && now - getLastEnd() < itvl) + return false; + _run(now); + return true; + } + + Task& forceRun() { + _run(micros()); + return *this; + } + + Task& requestEarlyRun() { + _requestEarlyRun = true; + return *this; + } + + Task& onDone(TaskDoneCallback doneCallback) { + _onDone = doneCallback; + return *this; + } + +#ifdef MYCILA_TASK_MANAGER_JSON_SUPPORT + void toJson(const JsonObject& root) const { + root["debug"] = isDebug(); + root["earlyRunRequested"] = isEarlyRunRequested(); + root["enabled"] = isEnabled(); + root["interval"] = getInterval(); + root["lastEnd"] = getLastEnd(); + root["lastRuntime"] = getLastRuntime(); + root["lastStart"] = getLastStart(); + root["name"] = getName(); + root["running"] = isRunning(); + root["type"] = getType() == TaskType::ONCE ? "ONCE" : "FOREVER"; + } +#endif + + private: + const char* _name; + const TaskFunction _fn; + bool _requestEarlyRun = false; + bool _running = false; + TaskDoneCallback _onDone = nullptr; + TaskIntervalSupplier _intervalSupplier = nullptr; + TaskPredicate _debugPredicate = nullptr; + TaskPredicate _enabledPredicate = nullptr; + TaskType _type; + uint32_t _lastEnd = 0; + uint32_t _lastRuntime = 0; + uint32_t _lastStart = 0; + void* _params = nullptr; + + private: + void _run(uint32_t now) { + _lastStart = now; + _running = true; + _fn(_params); + _running = false; + _requestEarlyRun = false; + const uint32_t end = micros(); + _lastEnd = end; + _lastRuntime = end - now; + if (_type == TaskType::ONCE) + pause(); + if (isDebug()) + ESP_LOGD("TASK", "Finished '%s' in %u us", _name, (_lastEnd - _lastStart)); + if (_onDone) + _onDone(*this); + } + }; + + class TaskManagerClass { + public: + void loop(); + + private: + std::vector _tasks; + }; + + extern TaskManagerClass TaskManager; +} // namespace Mycila diff --git a/lib/MycilaTaskManager/library.properties b/lib/MycilaTaskManager/library.properties new file mode 100644 index 0000000..ff78cba --- /dev/null +++ b/lib/MycilaTaskManager/library.properties @@ -0,0 +1,5 @@ +name=MycilaTaskManager +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaTemperatureSensor/LICENSE b/lib/MycilaTemperatureSensor/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaTemperatureSensor/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.cpp b/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.cpp new file mode 100644 index 0000000..d973b0c --- /dev/null +++ b/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#define TAG "DS18B20" + +void Mycila::TemperatureSensor::begin(const uint8_t pin, uint32_t expirationDelay) { + if (_enabled) + return; + + if (GPIO_IS_VALID_GPIO(pin)) { + _pin = (gpio_num_t)pin; + } else { + Logger.error(TAG, "Disable Temperature Sensor '%s': Invalid pin: %u", name, _pin); + _pin = GPIO_NUM_NC; + return; + } + + _oneWire.begin(_pin); + _dallas.setOneWire(&_oneWire); + _dallas.begin(); + if (_dallas.getDS18Count() == 0) { + Logger.error(TAG, "Disable Temperature Sensor '%s': No DS18B20 sensor found on pin: %u", name, _pin); + return; + } else { + _dallas.setWaitForConversion(false); + _dallas.requestTemperatures(); + } + + Logger.debug(TAG, "Enable Temperature Sensor '%s'...", name); + Logger.debug(TAG, "- Pin: %u", _pin); + Logger.debug(TAG, "- Expiration: %u seconds", expirationDelay); + + _expirationDelay = expirationDelay; + _enabled = true; +} + +void Mycila::TemperatureSensor::end() { + if (_enabled) { + Logger.debug(TAG, "Disable Temperature Sensor '%s'...", name); + _enabled = false; + _temperature = 0; + _lastUpdate = 0; + _pin = GPIO_NUM_NC; + } +} + +void Mycila::TemperatureSensor::read() { + if (!_enabled) + return; + float read = _dallas.getTempCByIndex(0); + _dallas.requestTemperatures(); + if (!isnan(read) && read > 0) { + read = round(read * 100) / 100; + Logger.debug(TAG, "Temperature Sensor '%s': %.02f °C", name, read); + _lastUpdate = millis(); + if (abs(read - _temperature) > 0.2 || !isValid()) { + _temperature = read; + if (_callback) + _callback(name, _temperature); + } + } +} + +String Mycila::TemperatureSensor::getTemperatureAsString() const { + if (!_enabled || !isValid()) + return "??.??"; + char buffer[6]; + snprintf(buffer, sizeof(buffer), "%5.2f", _temperature); + return buffer; +} + +void Mycila::TemperatureSensor::toJson(const JsonObject& root) const { + root["enabled"] = _enabled; + root["temperature"] = _temperature; + root["valid"] = isValid(); +} diff --git a/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.h b/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.h new file mode 100644 index 0000000..1291ba1 --- /dev/null +++ b/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include + +#ifndef MYCILA_TEMPERATURE_EXPIRATION_DELAY +#define MYCILA_TEMPERATURE_EXPIRATION_DELAY 60 +#endif + +namespace Mycila { + typedef std::function TemperatureChangeCallback; + class TemperatureSensor { + public: + explicit TemperatureSensor(const char* name) : name(name) {} + ~TemperatureSensor() { end(); } + + void begin(const uint8_t pin, uint32_t expirationDelay = 0); + void end(); + + void read(); + + void listen(TemperatureChangeCallback callback) { _callback = callback; } + + float getTemperature() const { return _temperature; } + bool isEnabled() const { return _enabled; } + bool isValid() const { return _expirationDelay == 0 ? _enabled : (millis() - _lastUpdate < _expirationDelay * 1000); } + uint32_t getLastUpdate() const { return _lastUpdate; } + uint32_t getExpirationDelay() const { return _expirationDelay; } + + String getTemperatureAsString() const; + gpio_num_t getPin() const { return _pin; }; + + void toJson(const JsonObject& root) const; + + public: + const char* name; + + private: + OneWire _oneWire; + DallasTemperature _dallas; + bool _enabled = false; + gpio_num_t _pin = GPIO_NUM_NC; + float _temperature = 0; + uint32_t _lastUpdate = 0; + uint32_t _expirationDelay = 0; + TemperatureChangeCallback _callback = nullptr; + }; +} // namespace Mycila diff --git a/lib/MycilaTemperatureSensor/library.properties b/lib/MycilaTemperatureSensor/library.properties new file mode 100644 index 0000000..7838757 --- /dev/null +++ b/lib/MycilaTemperatureSensor/library.properties @@ -0,0 +1,5 @@ +name=MycilaTemperatureSensor +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaTime/LICENSE b/lib/MycilaTime/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaTime/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaTime/MycilaTime.h b/lib/MycilaTime/MycilaTime.h new file mode 100644 index 0000000..6ac6986 --- /dev/null +++ b/lib/MycilaTime/MycilaTime.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +#include +#include + +namespace Mycila { + class Time { + public: + static String toISO8601Str(time_t unixTime) { + if (unixTime == 0) + return emptyString; + char buffer[21]; + strftime(buffer, sizeof(buffer), "%FT%TZ", gmtime(&unixTime)); + return buffer; + } + + static String toLocalStr(time_t unixTime) { + if (unixTime == 0) + return emptyString; + struct tm timeInfo; + localtime_r(&unixTime, &timeInfo); + char buffer[20]; + strftime(buffer, sizeof(buffer), "%F %T", &timeInfo); + return buffer; + } + + static time_t getUnixTime() { + time_t now; + struct tm timeinfo; + if (!NTP.isSynced() || !getLocalTime(&timeinfo, 5)) + return 0; + time(&now); + return now; + } + + inline static String getISO8601Str() { return toISO8601Str(getUnixTime()); } + + inline static String getLocalStr() { return toLocalStr(getUnixTime()); } + + static int toMinutes(const String& time, char sep = ':') { + int i = time.indexOf(sep); + if (i < 0) + return i; + return time.substring(0, i).toInt() * 60 + time.substring(i + 1).toInt(); + } + + static int timeInRange(struct tm* timeInfo, const String& start, const String& end, char sep = ':') { + const int startMinutes = toMinutes(start, sep); + if (startMinutes == -1) + return -1; + + const int stopMinutes = toMinutes(end, sep); + if (stopMinutes == -1) + return -1; + + if (startMinutes == stopMinutes) + return false; // range is empty + + const uint16_t timeMinutes = timeInfo->tm_hour * 60 + timeInfo->tm_min; + + // cases: + // startMinutes < stopMinutes : i.e. 06:00 -> 22:00 + // startMinutes > stopMinutes : i.e. 22:00 -> 06:00 + return (startMinutes < stopMinutes && timeMinutes >= startMinutes && timeMinutes < stopMinutes) || (startMinutes > stopMinutes && (timeMinutes >= startMinutes || timeMinutes < stopMinutes)); + } + }; + +} // namespace Mycila diff --git a/lib/MycilaTime/library.properties b/lib/MycilaTime/library.properties new file mode 100644 index 0000000..622e818 --- /dev/null +++ b/lib/MycilaTime/library.properties @@ -0,0 +1,5 @@ +name=MycilaTime +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/lib/MycilaZeroCrossDetection/LICENSE b/lib/MycilaZeroCrossDetection/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaZeroCrossDetection/LICENSE @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2023-2024, Mathieu Carbou + +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/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.cpp b/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.cpp new file mode 100644 index 0000000..a025183 --- /dev/null +++ b/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#include +#include + +#define TAG "ZCD" + +void Mycila::ZeroCrossDetectionClass::begin(const uint8_t pin, const uint8_t frequency) { + if (_enabled) + return; + + if (GPIO_IS_VALID_GPIO(pin)) { + _pin = (gpio_num_t)pin; + } else { + Logger.error(TAG, "Invalid Zero-Cross Detection pin: %u", _pin); + _pin = GPIO_NUM_NC; + return; + } + + Logger.debug(TAG, "Enable Zero-Cross Detection..."); + Logger.debug(TAG, "- Pin: %u", _pin); + Logger.debug(TAG, "- Grid Frequency: %u Hz", frequency); + +#if defined(ESP32) && defined(FILTER_INT_PERIOD) + // https://github.com/fabianoriccardi/dimmable-light/wiki/Notes-about-specific-architectures#interrupt-issue + Thyristor::semiPeriodShrinkMargin = 400; +#endif + DimmableLightLinearized::setFrequency(frequency); + DimmableLightLinearized::frequencyMonitorAlwaysOn(true); + DimmableLightLinearized::setSyncPin(_pin); + DimmableLightLinearized::begin(); + + _enabled = true; +} + +void Mycila::ZeroCrossDetectionClass::end() { + if (_enabled) { + Logger.debug(TAG, "Disable Zero-Cross Detection..."); + _enabled = false; + DimmableLightLinearized::setFrequency(0); + _pin = GPIO_NUM_NC; + } +} + +float Mycila::ZeroCrossDetectionClass::getFrequency() { return _enabled ? DimmableLightLinearized::getDetectedFrequency() : 0; } + +void Mycila::ZeroCrossDetectionClass::toJson(const JsonObject& root) { + root["enabled"] = _enabled; + root["frequency"] = getFrequency(); +} + +namespace Mycila { + ZeroCrossDetectionClass ZCD; +} // namespace Mycila diff --git a/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.h b/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.h new file mode 100644 index 0000000..420251f --- /dev/null +++ b/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +namespace Mycila { + class ZeroCrossDetectionClass { + public: + void begin(const uint8_t pin, const uint8_t frequency = 50); + void end(); + + bool isEnabled() const { return _enabled; } + gpio_num_t getPin() const { return _pin; } + // if activated, the monitored frequency based on zero-cross detection. Can be close to 50 or 60 Hz or more depending on the ZCD module. + float getFrequency(); + + void toJson(const JsonObject& root); + + private: + bool _enabled = false; + gpio_num_t _pin = GPIO_NUM_NC; + }; + + extern ZeroCrossDetectionClass ZCD; +} // namespace Mycila diff --git a/lib/MycilaZeroCrossDetection/library.properties b/lib/MycilaZeroCrossDetection/library.properties new file mode 100644 index 0000000..7af734b --- /dev/null +++ b/lib/MycilaZeroCrossDetection/library.properties @@ -0,0 +1,5 @@ +name=MycilaZeroCrossDetection +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +architectures=esp32 diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 0000000..437d302 --- /dev/null +++ b/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1F0000, +app1, app, ota_1, 0x200000, 0x1F0000, +spiffs, data, spiffs, 0x3F0000, 0x10000, diff --git a/pio/data.py b/pio/data.py new file mode 100644 index 0000000..5d7267a --- /dev/null +++ b/pio/data.py @@ -0,0 +1,10 @@ +import gzip +import os + +os.makedirs('.pio/data', exist_ok=True) + +for filename in ['config.html', 'logo-icon.png', 'logo.png']: + with open('data/' + filename, 'rb') as f_in: + with gzip.open('.pio/data/' + filename + '.gz', 'wb') as f_out: + print('gzip \'data/' + filename + '\' to \'.pio/data/' + filename + '.gz\'') + f_out.writelines(f_in) diff --git a/pio/factory.py b/pio/factory.py new file mode 100644 index 0000000..6358072 --- /dev/null +++ b/pio/factory.py @@ -0,0 +1,87 @@ +# Part of ESPEasy build toolchain. +# +# Combines separate bin files with their respective offsets into a single file +# This single file must then be flashed to an ESP32 node with 0 offset. +# +# Original implementation: Bartłomiej Zimoń (@uzi18) +# Maintainer: Gijs Noorlander (@TD-er) +# +# Special thanks to @Jason2866 (Tasmota) for helping debug flashing to >4MB flash +# Thanks @jesserockz (esphome) for adapting to use esptool.py with merge_bin +# +# Typical layout of the generated file: +# Offset | File +# 0x1000 | bootloader.bin +# 0x8000 | partitions.bin +# 0xe000 | boot_app0.bin +# 0x10000 | firmware.bin +# 0x3F0000 | littlefs.bin + +Import("env") + +platform = env.PioPlatform() + +import sys +import subprocess +from os.path import join + +sys.path.append(join(platform.get_package_dir("tool-esptoolpy"))) +import esptool + +def esp32_create_combined_bin(source, target, env): + print("Generating combined binary for serial flashing") + + # subprocess.run(["pio", "run", "-e", env.get("PIOENV"), "-t", "buildfs"]) + + # The offset from begin of the file where the app0 partition starts + # This is defined in the partition .csv file + app_offset = 0x10000 + # offset for spdiff partition + # fs_offset = 0x3F0000 + + new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin") + sections = env.subst(env.get("FLASH_EXTRA_IMAGES")) + firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") + # fs_name = env.subst("$BUILD_DIR/littlefs.bin") + chip = env.get("BOARD_MCU") + flash_size = env.BoardConfig().get("upload.flash_size") + flash_freq = env.BoardConfig().get("build.f_flash", '40m') + flash_freq = flash_freq.replace('000000L', 'm') + flash_mode = env.BoardConfig().get("build.flash_mode", "dio") + memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi") + if flash_mode == "qio" or flash_mode == "qout": + flash_mode = "dio" + if memory_type == "opi_opi" or memory_type == "opi_qspi": + flash_mode = "dout" + cmd = [ + "--chip", + chip, + "merge_bin", + "-o", + new_file_name, + "--flash_mode", + flash_mode, + "--flash_freq", + flash_freq, + "--flash_size", + flash_size, + ] + + print(" Offset | File") + for section in sections: + sect_adr, sect_file = section.split(" ", 1) + print(f" - {sect_adr} | {sect_file}") + cmd += [sect_adr, sect_file] + + print(f" - {hex(app_offset)} | {firmware_name}") + cmd += [hex(app_offset), firmware_name] + + # print(f" - {hex(fs_offset)} | {fs_name}") + # cmd += [hex(fs_offset), fs_name] + + print('Using esptool.py arguments: %s' % ' '.join(cmd)) + + esptool.main(cmd) + + +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) \ No newline at end of file diff --git a/pio/version.py b/pio/version.py new file mode 100644 index 0000000..938aa5c --- /dev/null +++ b/pio/version.py @@ -0,0 +1,40 @@ +import subprocess +import os +from datetime import datetime, timezone + +def get_build_flag(): + ret = subprocess.run(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE, text=True) #Uses any tags + full_hash = ret.stdout.strip() + ret = subprocess.run(["git", "symbolic-ref", "--short", "HEAD"], stdout=subprocess.PIPE, text=True) # retrieve branch name + branch = ret.stdout.strip() + short_hash = full_hash[:8] + + build_version = branch + "_" + short_hash + + # get the GITHUB_REF_NAME + ref_name = os.environ.get('GITHUB_REF_NAME') + if ref_name: + if ref_name.startswith("v"): + build_version = ref_name + else: + build_version = ref_name + "_" + short_hash + + # Check if the source has been modified since the last commit + ret = subprocess.run(["git", "diff-index", "--quiet", "HEAD", "--"], stdout=subprocess.PIPE, text=True) + if ret.returncode != 0: + build_version += "_modified" + + build_flags = "-D BUILD_TAG=\\\"" + build_version + "\\\" " + "-D BUILD_HASH=\\\"" + short_hash + "\\\" " + "-D BUILD_TIMESAMP=\\\"" + datetime.now(timezone.utc).isoformat() + "\\\"" + + return build_flags + +build_flags = get_build_flag() + +if "SCons.Script" == __name__: + print ("Firmware Revision: " + build_flags) + Import("env") + env.Append( + BUILD_FLAGS=[get_build_flag()] + ) +elif "__main__" == __name__: + print(build_flags) diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..5dd6123 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,906 @@ +; 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] +name = YaSolR +extra_configs = platformio_override.ini + +[env] +framework = arduino +platform = espressif32@6.5.0 +board = esp32dev +monitor_filters = esp32_exception_decoder, log2file +monitor_speed = 115200 +upload_protocol = esptool +upload_speed = 921600 +extra_scripts = + pre:pio/data.py + pre:pio/version.py + post:pio/factory.py +lib_compat_mode = strict +lib_ldf_mode = deep+ +; https://registry.platformio.org/ +lib_deps = + ArduinoOTA + bertmelis/espMqttClient @ 1.6.0 + bblanchon/ArduinoJson @ 7.0.3 + bblanchon/StreamUtils @ 1.8.0 + esp-arduino-libs/ESP32_Button @ 0.0.1 + esphome/AsyncTCP-esphome @ 2.1.2 + mathieucarbou/ESP Async WebServer @ 2.7.0 + mathieucarbou/MycilaHADiscovery @ 1.0.1 + mathieucarbou/MycilaConfig @ 1.4.0 + mathieucarbou/MycilaEasyDisplay @ 1.0.1 + mathieucarbou/MycilaESPConnect @ 3.2.1 + mathieucarbou/MycilaJSY @ 3.3.1 + mathieucarbou/MycilaLogger @ 2.0.2 + mathieucarbou/MycilaMQTT @ 1.3.0 + mathieucarbou/MycilaNTP @ 2.1.0 + mathieucarbou/MycilaRelay @ 2.0.0 + mathieucarbou/MycilaTaskMonitor @ 2.0.0 + milesburton/DallasTemperature @ 3.11.0 + olikraus/U8g2 @ 2.35.9 + paulstoffregen/OneWire @ 2.3.8 + ; fabianoriccardi/Dimmable Light for Arduino @ 1.6.0 + https://github.com/mathieucarbou/fabianoriccardi-dimmable-light#fixes +build_flags = + -D BUILD_NAME=\"$PIOENV\" + -Wall -Wextra + ; -Werror + ; Have to remove -Werror because of + ; https://github.com/espressif/arduino-esp32/issues/9044 and + ; https://github.com/espressif/arduino-esp32/issues/9045 + -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference + -std=c++17 + -std=gnu++17 + -D ARDUINO_LOOP_STACK_SIZE=256*15 + -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 + -D CONFIG_ASYNC_TCP_STACK_SIZE=256*15 + -D CONFIG_ETH_ENABLED + -D DASH_DEFAULT_CARD_SIZE_LG=6 + -D DASH_DEFAULT_CARD_SIZE_MD=6 + -D DASH_DEFAULT_CARD_SIZE_SM=6 + -D DASH_DEFAULT_CARD_SIZE_XL=6 + -D DASH_DEFAULT_CARD_SIZE_XS=12 + -D DASH_DEFAULT_CARD_SIZE_XXL=3 + -D DASH_JSON_SIZE=512*4 + -D DASH_MAX_WS_CLIENTS=3 + -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 + -D EMC_ALLOW_NOT_CONNECTED_PUBLISH=0 + -D EMC_RX_BUFFER_SIZE=512 + -D EMC_TASK_STACK_SIZE=256*12 + -D EMC_TX_BUFFER_SIZE=512 + -D EMC_TX_TIMEOUT=5000 + -D EMC_WAIT_FOR_CONNACK=0 + -D FILTER_INT_PERIOD + -D MONITOR_FREQUENCY + -D MYCILA_CONFIG_JSON_SUPPORT + -D MYCILA_JSY_ASYNC_STACK_SIZE=256*6 + -D MYCILA_JSY_JSON_SUPPORT + -D MYCILA_LOGGER_CUSTOM_LEVEL + -D MYCILA_NTP_JSON_SUPPORT + -D MYCILA_RELAY_JSON_SUPPORT + -D MYCILA_TASK_MANAGER_JSON_SUPPORT + -D MYCILA_TASK_MONITOR_JSON_SUPPORT + -D MYCILA_TASK_MONITOR_STACK_FREE_MAX=1024 + -D MYCILA_TASK_MONITOR_STACK_FREE_MIN=256 + -D NETWORK_FREQ_RUNTIME + -D WEBSERIAL_MAX_WS_CLIENTS=3 + -D WS_MAX_QUEUED_MESSAGES=32 + -D YASOLR_DISABLE_BROWNOUT_DETECTOR +build_unflags = + -std=gnu++11 +board_build.partitions = partitions.csv +board_build.filesystem = littlefs +board_build.embed_files = + .pio/data/logo.png.gz + .pio/data/logo-icon.png.gz + .pio/data/config.html.gz + +; -------------------------------------------------------------------- +; RELEASES +; -------------------------------------------------------------------- + +[release] +; https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/kconfig.html#config-compiler-optimization-assertion-level +build_flags = + -O2 + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO + -D NDEBUG + -D RELEASE=1 + +[debug] +build_flags = + -O0 + -ggdb -ggdb3 -g3 + -D CONFIG_ARDUHAL_LOG_COLORS + ; -D CONFIG_LOG_COLORS + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + ; -D DEBUG + ; -D DEBUG_ESP_MQTT_CLIENT + ; -D ENABLE_DEBUG + ; -D ESPCONNECT_DEBUG + ; -D MYCILA_BOOT_WAIT_FOR_RESET + -D YASOLR_RELAY_PAUSE_DURATION=10 + ; -D WEBSERIAL_DEBUG + +; -------------------------------------------------------------------- +; MODELS +; -------------------------------------------------------------------- + +[oss] +build_flags = -D APP_VERSION_OSS +lib_deps = + https://github.com/ayushsharma82/ElegantOTA +lib_ignore = + ElegantOTAPro + ESPDASHPro + WebSerialLite + +[pro] +build_flags = -D APP_VERSION_PRO +lib_deps = +lib_ignore = + ElegantOTA + ESPDASH + +[trial] +build_flags = + ${pro.build_flags} + -D APP_VERSION_TRIAL + -D MYCILA_TRIAL_DURATION=259200 +lib_deps = + mathieucarbou/MycilaTrial @ 1.0.0 +lib_ignore = + ElegantOTA + ESPDASH + +; -------------------------------------------------------------------- +; SHORTCUTS +; -------------------------------------------------------------------- + +[unpin] +build_flags = + -D YASOLR_BUTTON_PIN=-1 + -D YASOLR_BUZZER_PIN=-1 + -D YASOLR_DISPLAY_CLOCK_PIN=-1 + -D YASOLR_DISPLAY_DATA_PIN=-1 + -D YASOLR_JSY_RX_PIN=-1 + -D YASOLR_JSY_TX_PIN=-1 + -D YASOLR_LIGHTS_GREEN_PIN=-1 + -D YASOLR_LIGHTS_RED_PIN=-1 + -D YASOLR_LIGHTS_YELLOW_PIN=-1 + -D YASOLR_OUTPUT1_DIMMER_PIN=-1 + -D YASOLR_OUTPUT1_RELAY_PIN=-1 + -D YASOLR_OUTPUT1_TEMP_PIN=-1 + -D YASOLR_OUTPUT2_DIMMER_PIN=-1 + -D YASOLR_OUTPUT2_RELAY_PIN=-1 + -D YASOLR_OUTPUT2_TEMP_PIN=-1 + -D YASOLR_RELAY1_PIN=-1 + -D YASOLR_RELAY2_PIN=-1 + -D YASOLR_SYSTEM_TEMP_PIN=-1 + -D YASOLR_ZCD_PIN=-1 + + +; -------------------------------------------------------------------- +; ENVIRONMENTs +; -------------------------------------------------------------------- + +; esp32dev (defaul) + +[env:oss-esp32] +build_type = release +lib_deps = + ${env.lib_deps} + ${oss.lib_deps} +lib_ignore = + ${env.lib_ignore} + ${oss.lib_ignore} +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + +[env:oss-esp32-debug] +build_type = debug +lib_deps = + ${env.lib_deps} + ${oss.lib_deps} +lib_ignore = + ${env.lib_ignore} + ${oss.lib_ignore} +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + +[env:pro-esp32] +build_type = release +lib_deps = + ${env.lib_deps} + ${pro.lib_deps} +lib_ignore = + ${env.lib_ignore} + ${pro.lib_ignore} +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + +[env:pro-esp32-debug] +build_type = debug +lib_deps = + ${env.lib_deps} + ${pro.lib_deps} +lib_ignore = + ${env.lib_ignore} + ${pro.lib_ignore} +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + +[env:trial-esp32] +build_type = release +lib_deps = + ${env.lib_deps} + ${trial.lib_deps} +lib_ignore = + ${env.lib_ignore} + ${trial.lib_ignore} +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + +[env:trial-esp32-debug] +build_type = debug +lib_deps = + ${env.lib_deps} + ${trial.lib_deps} +lib_ignore = + ${env.lib_ignore} + ${trial.lib_ignore} +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${debug.build_flags} + +; nodemcu-32s + +[env:oss-esp32s] +extends = env:oss-esp32 +board = nodemcu-32s + +[env:oss-esp32s-debug] +extends = env:oss-esp32-debug +board = nodemcu-32s + +[env:pro-esp32s] +extends = env:pro-esp32 +board = nodemcu-32s + +[env:pro-esp32s-debug] +extends = env:pro-esp32-debug +board = nodemcu-32s + +[env:trial-esp32s] +extends = env:trial-esp32 +board = nodemcu-32s + +[env:trial-esp32s-debug] +extends = env:trial-esp32-debug +board = nodemcu-32s + +; esp32c3 + +[esp32c3] +build_flags = + -D MYCILA_JSY_SERIAL=Serial1 + -D YASOLR_BUTTON_PIN=10 + -D YASOLR_BUZZER_PIN=18 + -D YASOLR_DISPLAY_CLOCK_PIN=6 + -D YASOLR_DISPLAY_DATA_PIN=7 + -D YASOLR_JSY_RX_PIN=20 + -D YASOLR_JSY_TX_PIN=21 + -D YASOLR_LIGHTS_GREEN_PIN=-1 + -D YASOLR_LIGHTS_RED_PIN=-1 + -D YASOLR_LIGHTS_YELLOW_PIN=-1 + -D YASOLR_OUTPUT1_DIMMER_PIN=1 + -D YASOLR_OUTPUT1_RELAY_PIN=2 + -D YASOLR_OUTPUT1_TEMP_PIN=0 + -D YASOLR_OUTPUT2_DIMMER_PIN=8 + -D YASOLR_OUTPUT2_RELAY_PIN=9 + -D YASOLR_OUTPUT2_TEMP_PIN=5 + -D YASOLR_RELAY1_PIN=20 + -D YASOLR_RELAY2_PIN=21 + -D YASOLR_SYSTEM_TEMP_PIN=4 + -D YASOLR_ZCD_PIN=3 + +[env:oss-esp32c3] +extends = env:oss-esp32 +board = esp32-c3-devkitc-02 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${esp32c3.build_flags} + +[env:oss-esp32c3-debug] +extends = env:oss-esp32-debug +board = esp32-c3-devkitc-02 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${esp32c3.build_flags} + +[env:pro-esp32c3] +extends = env:pro-esp32 +board = esp32-c3-devkitc-02 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${esp32c3.build_flags} + +[env:pro-esp32c3-debug] +extends = env:pro-esp32-debug +board = esp32-c3-devkitc-02 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${esp32c3.build_flags} + +[env:trial-esp32c3] +extends = env:trial-esp32 +board = esp32-c3-devkitc-02 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${esp32c3.build_flags} + +; esp32s3 + +[esp32s3] +build_flags = + -D ARDUINO_USB_MODE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D YASOLR_OUTPUT2_DIMMER_PIN=36 + -D YASOLR_OUTPUT1_DIMMER_PIN=37 + -D YASOLR_DISPLAY_CLOCK_PIN=38 + -D YASOLR_BUTTON_PIN=39 + -D YASOLR_OUTPUT1_RELAY_PIN=40 + -D YASOLR_BUZZER_PIN=8 + +[env:oss-esp32s3] +extends = env:oss-esp32 +board = esp32-s3-devkitc-1 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${esp32s3.build_flags} + +[env:oss-esp32s3-debug] +extends = env:oss-esp32-debug +board = esp32-s3-devkitc-1 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${esp32s3.build_flags} + +[env:pro-esp32s3] +extends = env:pro-esp32 +board = esp32-s3-devkitc-1 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${esp32s3.build_flags} + +[env:pro-esp32s3-debug] +extends = env:pro-esp32-debug +board = esp32-s3-devkitc-1 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${esp32s3.build_flags} + +[env:trial-esp32s3] +extends = env:trial-esp32 +board = esp32-s3-devkitc-1 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${esp32s3.build_flags} + +[env:trial-esp32s3-debug] +extends = env:trial-esp32-debug +board = esp32-s3-devkitc-1 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${debug.build_flags} + ${esp32s3.build_flags} + +; lolin32_lite + +[lolin32_lite] +build_flags = + -D YASOLR_BUZZER_PIN=14 + -D YASOLR_DISPLAY_DATA_PIN=19 + +[env:oss-lolin32_lite] +extends = env:oss-esp32 +board = lolin32_lite +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${lolin32_lite.build_flags} + +[env:oss-lolin32_lite-debug] +extends = env:oss-esp32-debug +board = lolin32_lite +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${lolin32_lite.build_flags} + +[env:pro-lolin32_lite] +extends = env:pro-esp32 +board = lolin32_lite +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${lolin32_lite.build_flags} + +[env:pro-lolin32_lite-debug] +extends = env:pro-esp32-debug +board = lolin32_lite +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${lolin32_lite.build_flags} + +[env:trial-lolin32_lite] +extends = env:trial-esp32 +board = lolin32_lite +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${lolin32_lite.build_flags} + +; lolin_s2_mini + +[lolin_s2_mini] +build_flags = + -D MYCILA_JSY_SERIAL=Serial1 + -D YASOLR_BUTTON_PIN=7 + -D YASOLR_BUZZER_PIN=14 + -D YASOLR_DISPLAY_CLOCK_PIN=9 + -D YASOLR_DISPLAY_DATA_PIN=8 + -D YASOLR_JSY_RX_PIN=39 + -D YASOLR_JSY_TX_PIN=37 + -D YASOLR_LIGHTS_GREEN_PIN=3 + -D YASOLR_LIGHTS_RED_PIN=6 + -D YASOLR_OUTPUT1_DIMMER_PIN=10 + -D YASOLR_OUTPUT1_RELAY_PIN=21 + -D YASOLR_OUTPUT2_DIMMER_PIN=11 + +[env:oss-lolin_s2_mini] +extends = env:oss-esp32 +board = lolin_s2_mini +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${lolin_s2_mini.build_flags} + +[env:oss-lolin_s2_mini-debug] +extends = env:oss-esp32-debug +board = lolin_s2_mini +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${lolin_s2_mini.build_flags} + +[env:pro-lolin_s2_mini] +extends = env:pro-esp32 +board = lolin_s2_mini +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${lolin_s2_mini.build_flags} + +[env:pro-lolin_s2_mini-debug] +extends = env:pro-esp32-debug +board = lolin_s2_mini +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${lolin_s2_mini.build_flags} + +[env:trial-lolin_s2_mini] +extends = env:trial-esp32 +board = lolin_s2_mini +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${lolin_s2_mini.build_flags} + +; esp32_poe + +[esp32_poe] +build_flags = + -D ESPCONNECT_ETH_RESET_ON_START + -D ESPCONNECT_ETH_SUPPORT + -D YASOLR_BUTTON_PIN=-1 + -D YASOLR_BUZZER_PIN=-1 + -D YASOLR_DISPLAY_CLOCK_PIN=16 + -D YASOLR_DISPLAY_DATA_PIN=13 + -D YASOLR_JSY_RX_PIN=33 + -D YASOLR_JSY_TX_PIN=35 + -D YASOLR_LIGHTS_GREEN_PIN=-1 + -D YASOLR_LIGHTS_RED_PIN=-1 + -D YASOLR_LIGHTS_YELLOW_PIN=-1 + -D YASOLR_OUTPUT1_DIMMER_PIN=2 + -D YASOLR_OUTPUT1_RELAY_PIN=4 + -D YASOLR_OUTPUT1_TEMP_PIN=5 + -D YASOLR_OUTPUT2_DIMMER_PIN=-1 + -D YASOLR_OUTPUT2_RELAY_PIN=-1 + -D YASOLR_OUTPUT2_TEMP_PIN=-1 + -D YASOLR_RELAY1_PIN=14 + -D YASOLR_RELAY2_PIN=15 + -D YASOLR_SYSTEM_TEMP_PIN=0 + -D YASOLR_ZCD_PIN=36 + +[env:oss-esp32_poe] +extends = env:oss-esp32 +board = esp32-poe +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${esp32_poe.build_flags} + +[env:oss-esp32_poe-debug] +extends = env:oss-esp32-debug +board = esp32-poe +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${esp32_poe.build_flags} + +[env:pro-esp32_poe] +extends = env:pro-esp32 +board = esp32-poe +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${esp32_poe.build_flags} + +[env:pro-esp32_poe-debug] +extends = env:pro-esp32-debug +board = esp32-poe +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${esp32_poe.build_flags} + +[env:trial-esp32_poe] +extends = env:trial-esp32 +board = esp32-poe +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${esp32_poe.build_flags} + +; wt32_eth01 +; https://github.com/egnor/wt32-eth01 + +[wt32_eth01] +build_flags = + -D ESPCONNECT_ETH_RESET_ON_START + -D ESPCONNECT_ETH_SUPPORT + -D ETH_PHY_POWER=16 + -D ETH_PHY_ADDR=1 + -D YASOLR_BUTTON_PIN=-1 + -D YASOLR_BUZZER_PIN=-1 + -D YASOLR_DISPLAY_CLOCK_PIN=-1 + -D YASOLR_DISPLAY_DATA_PIN=-1 + -D YASOLR_JSY_TX_PIN=5 + -D YASOLR_LIGHTS_GREEN_PIN=-1 + -D YASOLR_LIGHTS_RED_PIN=-1 + -D YASOLR_LIGHTS_YELLOW_PIN=-1 + -D YASOLR_OUTPUT1_DIMMER_PIN=2 + -D YASOLR_OUTPUT1_RELAY_PIN=12 + -D YASOLR_OUTPUT1_TEMP_PIN=15 + -D YASOLR_OUTPUT2_DIMMER_PIN=-1 + -D YASOLR_OUTPUT2_RELAY_PIN=-1 + -D YASOLR_OUTPUT2_TEMP_PIN=-1 + -D YASOLR_RELAY1_PIN=14 + -D YASOLR_RELAY2_PIN=-1 + +[env:oss-wt32_eth01] +extends = env:oss-esp32 +board = wt32-eth01 +upload_speed = 460800 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${wt32_eth01.build_flags} + +[env:oss-wt32_eth01-debug] +extends = env:oss-esp32-debug +board = wt32-eth01 +upload_speed = 460800 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${wt32_eth01.build_flags} + +[env:pro-wt32_eth01] +extends = env:pro-esp32 +board = wt32-eth01 +upload_speed = 460800 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${wt32_eth01.build_flags} + +[env:pro-wt32_eth01-debug] +extends = env:pro-esp32-debug +board = wt32-eth01 +upload_speed = 460800 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${wt32_eth01.build_flags} + +[env:trial-wt32_eth01] +extends = env:trial-esp32 +board = wt32-eth01 +upload_speed = 460800 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${wt32_eth01.build_flags} + +[env:trial-wt32_eth01-debug] +extends = env:trial-esp32-debug +board = wt32-eth01 +upload_speed = 460800 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${debug.build_flags} + ${wt32_eth01.build_flags} + +; lilygo_eth_lite_s3 +; https://github.com/Xinyuan-LilyGO/LilyGO-T-ETH-Series/ + +[lilygo_eth_lite_s3] +build_flags = + -UARDUINO_USB_CDC_ON_BOOT + -D ESPCONNECT_ETH_RESET_ON_START + -D ESPCONNECT_ETH_SUPPORT + -D ESPCONNECT_ETH_CS=9 + -D ESPCONNECT_ETH_INT=13 + -D ESPCONNECT_ETH_MISO=11 + -D ESPCONNECT_ETH_MOSI=12 + -D ESPCONNECT_ETH_RST=14 + -D ESPCONNECT_ETH_SCLK=10 + -D YASOLR_BUTTON_PIN=1 + -D YASOLR_BUZZER_PIN=2 + -D YASOLR_DISPLAY_CLOCK_PIN=40 + -D YASOLR_DISPLAY_DATA_PIN=41 + -D YASOLR_JSY_RX_PIN=17 + -D YASOLR_JSY_TX_PIN=18 + -D YASOLR_LIGHTS_GREEN_PIN=38 + -D YASOLR_LIGHTS_RED_PIN=46 + -D YASOLR_LIGHTS_YELLOW_PIN=21 + -D YASOLR_OUTPUT1_DIMMER_PIN=19 + -D YASOLR_OUTPUT1_RELAY_PIN=20 + -D YASOLR_OUTPUT1_TEMP_PIN=3 + -D YASOLR_OUTPUT2_DIMMER_PIN=7 + -D YASOLR_OUTPUT2_RELAY_PIN=15 + -D YASOLR_OUTPUT2_TEMP_PIN=16 + -D YASOLR_RELAY1_PIN=5 + -D YASOLR_RELAY2_PIN=6 + -D YASOLR_SYSTEM_TEMP_PIN=4 + -D YASOLR_ZCD_PIN=8 + +[env:oss-lilygo_eth_lite_s3] +extends = env:oss-esp32 +board = esp32s3box +upload_speed = 115200 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${lilygo_eth_lite_s3.build_flags} + +[env:oss-lilygo_eth_lite_s3-debug] +extends = env:oss-esp32-debug +board = esp32s3box +upload_speed = 115200 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${lilygo_eth_lite_s3.build_flags} + +[env:pro-lilygo_eth_lite_s3] +extends = env:pro-esp32 +board = esp32s3box +upload_speed = 115200 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${lilygo_eth_lite_s3.build_flags} + +[env:pro-lilygo_eth_lite_s3-debug] +extends = env:pro-esp32-debug +board = esp32s3box +upload_speed = 115200 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${lilygo_eth_lite_s3.build_flags} + +[env:trial-lilygo_eth_lite_s3] +extends = env:trial-esp32 +board = esp32s3box +upload_speed = 115200 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${lilygo_eth_lite_s3.build_flags} + +[env:trial-lilygo_eth_lite_s3-debug] +extends = env:trial-esp32 +board = esp32s3box +upload_speed = 115200 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${debug.build_flags} + ${lilygo_eth_lite_s3.build_flags} + +; m5stack-atom +; TODO: add default pinout for m5stack-atom + +[m5stack_atom] +build_flags = + ${unpin.build_flags} + +[env:oss-m5stack_atom] +extends = env:oss-esp32 +board = m5stack-atom +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${m5stack_atom.build_flags} + +[env:oss-m5stack_atom-debug] +extends = env:oss-esp32-debug +board = m5stack-atom +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${m5stack_atom.build_flags} + +[env:pro-m5stack_atom] +extends = env:pro-esp32 +board = m5stack-atom +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${m5stack_atom.build_flags} + +[env:pro-m5stack_atom-debug] +extends = env:pro-esp32-debug +board = m5stack-atom +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${m5stack_atom.build_flags} + +[env:trial-m5stack_atom] +extends = env:trial-esp32 +board = m5stack-atom +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${m5stack_atom.build_flags} + +; m5stack-atoms3 +; TODO: add default pinout for m5stack-atoms3 + +[m5stack_atoms3] +build_flags = + ${unpin.build_flags} + +[env:oss-m5stack_atoms3] +extends = env:oss-esp32 +board = m5stack-atoms3 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + ${m5stack_atoms3.build_flags} + +[env:oss-m5stack_atoms3-debug] +extends = env:oss-esp32-debug +board = m5stack-atoms3 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + ${m5stack_atoms3.build_flags} + +[env:pro-m5stack_atoms3] +extends = env:pro-esp32 +board = m5stack-atoms3 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + ${m5stack_atoms3.build_flags} + +[env:pro-m5stack_atoms3-debug] +extends = env:pro-esp32-debug +board = m5stack-atoms3 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + ${m5stack_atoms3.build_flags} + +[env:trial-m5stack_atoms3] +extends = env:trial-esp32 +board = m5stack-atoms3 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + ${m5stack_atoms3.build_flags} diff --git a/platformio_override.ini.template b/platformio_override.ini.template new file mode 100644 index 0000000..5c196f3 --- /dev/null +++ b/platformio_override.ini.template @@ -0,0 +1,22 @@ +; *** PlatformIO Project Configuration Override File *** +; *** Changes done here override settings in platformio.ini *** +; +; Place your personal settings like monitor_port and upload_port here +; instead of editing platformio.ini +; to avoid annoying merge conflicts when you pull updates into your +; personal clone of the repository +; +; Please visit documentation for the options and examples +; http://docs.platformio.org/en/stable/projectconf.html + +[platformio] +; default_envs = pro-esp32-debug, oss-esp32-debug, trial-esp32-debug +; default_envs = oss-esp32-debug +; default_envs = pro-esp32-debug +; default_envs = pro-lilygo_eth_lite_s3-debug +; default_envs = pro-wt32_eth01-debug +; default_envs = trial-esp32-debug +; default_envs = trial-esp32s3-debug +; default_envs = trial-wt32_eth01-debug +; default_envs = trial-lilygo_eth_lite_s3-debug +; default_envs = trial-esp32_poe diff --git a/src/YaSolRClass.cpp b/src/YaSolRClass.cpp new file mode 100644 index 0000000..ad73d65 --- /dev/null +++ b/src/YaSolRClass.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include + +void YaSolR::YaSolRClass::_initHTTPd() { + Mycila::HTTPd.init(&webServer, YASOLR_ADMIN_USERNAME, Mycila::Config.get(KEY_ADMIN_PASSWORD)); +} + +void YaSolR::YaSolRClass::_initWebsite() { + YaSolR::Website.init(); +} + +void YaSolR::YaSolRClass::updateWebsite() { + YaSolR::Website.update(); +} + +namespace YaSolR { + YaSolRClass YaSolR; +} // namespace YaSolR diff --git a/src/YaSolR_API_MQTT.cpp b/src/YaSolR_API_MQTT.cpp new file mode 100644 index 0000000..0c4cbe3 --- /dev/null +++ b/src/YaSolR_API_MQTT.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#define TAG "YASOLR" + +void YaSolR::YaSolRClass::_initMQTT() { + Mycila::Logger.info(TAG, "Initializing MQTT API..."); + + const String baseTopic = Mycila::Config.get(KEY_MQTT_TOPIC); + + // config + + Mycila::MQTT.subscribe(baseTopic + "/config/+/set", [](const String& topic, const String& payload) { + const int end = topic.lastIndexOf("/"); + const int start = topic.lastIndexOf("/", end - 1); + const String key = topic.substring(start + 1, end); + const char* keyRef = Mycila::Config.keyRef(key.c_str()); + if (keyRef) + Mycila::Config.set(keyRef, payload); + }); + + // relays + + Mycila::MQTT.subscribe(baseTopic + "/relays/" NAME_RELAY1 "/state/set", [this](const String& topic, const String& payload) { + if (relay1.isEnabled()) { + int start = payload.indexOf("="); + String state = start >= 0 ? payload.substring(0, start) : payload; + uint32_t duration = start >= 0 ? payload.substring(start + 1).toInt() : 0; + if (state == YASOLR_ON) + Mycila::RelayManager.tryRelayState(&relay1, true, duration); + else if (state == YASOLR_OFF) + Mycila::RelayManager.tryRelayState(&relay1, false, duration); + } + }); + Mycila::MQTT.subscribe(baseTopic + "/relays/" NAME_RELAY2 "/state/set", [this](const String& topic, const String& payload) { + if (relay2.isEnabled()) { + int start = payload.indexOf("="); + String state = start >= 0 ? payload.substring(0, start) : payload; + uint32_t duration = start >= 0 ? payload.substring(start + 1).toInt() : 0; + if (state == YASOLR_ON) + Mycila::RelayManager.tryRelayState(&relay2, true, duration); + else if (state == YASOLR_OFF) + Mycila::RelayManager.tryRelayState(&relay2, false, duration); + } + }); + + // router + + Mycila::MQTT.subscribe(baseTopic + "/router/" NAME_OUTPUT1 "/dimmer/level/set", [](const String& topic, const String& payload) { + output1.tryDimmerLevel(payload.toInt()); + }); + + Mycila::MQTT.subscribe(baseTopic + "/router/" NAME_OUTPUT2 "/dimmer/level/set", [](const String& topic, const String& payload) { + output2.tryDimmerLevel(payload.toInt()); + }); + + Mycila::MQTT.subscribe(baseTopic + "/router/" NAME_OUTPUT1 "/bypass_relay/state/set", [](const String& topic, const String& payload) { + if (output1.isBypassRelayEnabled()) { + if (payload == YASOLR_ON) + output1.tryBypassRelayState(true); + else if (payload == YASOLR_OFF) + output1.tryBypassRelayState(false); + } + }); + + Mycila::MQTT.subscribe(baseTopic + "/router/" NAME_OUTPUT2 "/bypass_relay/state/set", [](const String& topic, const String& payload) { + if (output2.isBypassRelayEnabled()) { + if (payload == YASOLR_ON) + output2.tryBypassRelayState(true); + else if (payload == YASOLR_OFF) + output2.tryBypassRelayState(false); + } + }); + + // system + + Mycila::MQTT.subscribe(baseTopic + "/system/restart", [=](const String& topic, const String& payload) { + restartTask.resume(); + }); + + Mycila::MQTT.subscribe(baseTopic + "/system/reset", [=](const String& topic, const String& payload) { + resetTask.resume(); + }); +} diff --git a/src/YaSolR_API_REST.cpp b/src/YaSolR_API_REST.cpp new file mode 100644 index 0000000..e15e61d --- /dev/null +++ b/src/YaSolR_API_REST.cpp @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include + +#define TAG "YASOLR" + +void YaSolR::YaSolRClass::_initREST() { + Mycila::Logger.info(TAG, "Initializing REST API..."); + + // app + + Mycila::HTTPd.apiGET("/app", [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + Mycila::AppInfo.toJson(response->getRoot()); + response->setLength(); + request->send(response); + }); + + // config + + Mycila::HTTPd.apiGET("/config/backup", [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", Mycila::Config.backup()); + response->addHeader("Content-Disposition", "attachment; filename=\"config.txt\""); + request->send(response); + }); + + Mycila::HTTPd.apiUPLOAD( + "/config/restore", + [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", "OK"); + if (!LittleFS.exists("/config.txt")) { + return request->send(400, "text/plain", "No config.txt file uploaded"); + } + File cfg = LittleFS.open("/config.txt", "r"); + const String data = cfg.readString(); + cfg.close(); + Mycila::Config.restore(data); + response->addHeader("Connection", "close"); + response->addHeader("Access-Control-Allow-Origin", "*"); + request->send(response); + }, + [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { + if (!index) + request->_tempFile = LittleFS.open("/config.txt", "w"); + if (len) + request->_tempFile.write(data, len); + if (final) + request->_tempFile.close(); + }); + + Mycila::HTTPd.apiPOST("/config", [](AsyncWebServerRequest* request) { + std::map settings; + for (size_t i = 0, max = request->params(); i < max; i++) { + AsyncWebParameter* p = request->getParam(i); + if (p->isPost() && !p->isFile()) { + const char* keyRef = Mycila::Config.keyRef(p->name().c_str()); + settings[keyRef] = p->value(); + } + } + request->send(200); + Mycila::Config.set(settings); + }); + + Mycila::HTTPd.apiGET("/config", [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(false); + Mycila::Config.toJson(response->getRoot()); + response->setLength(); + request->send(response); + }); + + // grid + + Mycila::HTTPd.apiGET("/grid", [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + Mycila::Grid.toJson(root); + response->setLength(); + request->send(response); + }); + + // network + + Mycila::HTTPd.apiGET("/network", [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + ESPConnect.toJson(response->getRoot()); + response->setLength(); + request->send(response); + }); + + // ntp + + Mycila::HTTPd.apiGET("/ntp/timezones", [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(true); + Mycila::NTP.timezonesToJsonArray(response->getRoot().as()); + response->setLength(); + request->send(response); + }); + + Mycila::HTTPd.apiGET("/ntp", [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + response->getRoot()["synced"] = Mycila::NTP.isSynced(); + response->setLength(); + request->send(response); + }); + + // relays + + Mycila::HTTPd.apiPOST("/relays/" NAME_RELAY1, [this](AsyncWebServerRequest* request) { + if (relay1.isEnabled() && request->hasParam("state", true)) { + String state = request->getParam("state", true)->value(); + uint32_t duration = request->hasParam("duration", true) ? request->getParam("duration", true)->value().toInt() : 0; + if (state == YASOLR_ON) + Mycila::RelayManager.tryRelayState(&relay1, true, duration); + else if (state == YASOLR_OFF) + Mycila::RelayManager.tryRelayState(&relay1, false, duration); + } + request->send(200); + }); + Mycila::HTTPd.apiPOST("/relays/" NAME_RELAY2, [this](AsyncWebServerRequest* request) { + if (relay2.isEnabled() && request->hasParam("state", true)) { + String state = request->getParam("state", true)->value(); + uint32_t duration = request->hasParam("duration", true) ? request->getParam("duration", true)->value().toInt() : 0; + if (state == YASOLR_ON) + Mycila::RelayManager.tryRelayState(&relay2, true, duration); + else if (state == YASOLR_OFF) + Mycila::RelayManager.tryRelayState(&relay2, false, duration); + } + request->send(200); + }); + + Mycila::HTTPd.apiGET("/relays", [this](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + Mycila::RelayManager.toJson(root); + response->setLength(); + request->send(response); + }); + + // router + + Mycila::HTTPd.apiPOST("/router/" NAME_OUTPUT1 "/dimmer", [](AsyncWebServerRequest* request) { + if (request->hasParam("level", true)) + output1.tryDimmerLevel(request->getParam("level", true)->value().toInt()); + request->send(200); + }); + Mycila::HTTPd.apiPOST("/router/" NAME_OUTPUT2 "/dimmer", [](AsyncWebServerRequest* request) { + if (request->hasParam("level", true)) + output2.tryDimmerLevel(request->getParam("level", true)->value().toInt()); + request->send(200); + }); + + Mycila::HTTPd.apiPOST("/router/" NAME_OUTPUT1 "/bypass_relay", [](AsyncWebServerRequest* request) { + if (output1.isBypassRelayEnabled() && request->hasParam("state", true)) { + String state = request->getParam("state", true)->value(); + if (state == YASOLR_ON) + output1.tryBypassRelayState(true); + else if (state == YASOLR_OFF) + output1.tryBypassRelayState(false); + } + request->send(200); + }); + Mycila::HTTPd.apiPOST("/router/" NAME_OUTPUT2 "/bypass_relay", [](AsyncWebServerRequest* request) { + if (output2.isBypassRelayEnabled() && request->hasParam("state", true)) { + String state = request->getParam("state", true)->value(); + if (state == YASOLR_ON) + output2.tryBypassRelayState(true); + else if (state == YASOLR_OFF) + output2.tryBypassRelayState(false); + } + request->send(200); + }); + + Mycila::HTTPd.apiGET("/router", [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + Mycila::Router.toJson(root); + response->setLength(); + request->send(response); + }); + + // system + + Mycila::HTTPd.apiANY("/system/restart", [=](AsyncWebServerRequest* request) { + restartTask.resume(); + request->send(200); + }); + + Mycila::HTTPd.apiANY("/system/reset", [=](AsyncWebServerRequest* request) { + resetTask.resume(); + request->send(200); + }); + + Mycila::HTTPd.apiGET("/system", [this](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + Mycila::System.toJson(root); + JsonObject buzzer = root["buzzer"].to(); + Mycila::Buzzer.toJson(buzzer); + JsonObject lights = root["lights"].to(); + Mycila::Lights.toJson(lights); + JsonObject tasks = root["tasks"].to(); + Mycila::TaskMonitor.toJson(tasks); + JsonObject temp_sensor = root["temp_sensor"].to(); + systemTemperatureSensor.toJson(temp_sensor); + response->setLength(); + request->send(response); + }); +} diff --git a/src/YaSolR_Config.cpp b/src/YaSolR_Config.cpp new file mode 100644 index 0000000..d163a9f --- /dev/null +++ b/src/YaSolR_Config.cpp @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#define TAG "YASOLR" + +void YaSolR::YaSolRClass::_initConfig() { + Mycila::Logger.info(TAG, "Initializing Config System..."); + + Mycila::Config.begin(84); + + String mqttName = String(Mycila::AppInfo.name + "_" + Mycila::AppInfo.id); + mqttName.toLowerCase(); + + String hostname = String(Mycila::AppInfo.name + "-" + Mycila::AppInfo.id); + hostname.toLowerCase(); + + /// others + Mycila::Config.configure(KEY_HOSTNAME, hostname); + Mycila::Config.configure(KEY_ADMIN_PASSWORD); + // logger + Mycila::Config.configure(KEY_DEBUG_ENABLE, YASOLR_BOOL(YASOLR_DEBUG_ENABLE)); + // ap + Mycila::Config.configure(KEY_AP_MODE_ENABLE, YASOLR_BOOL(YASOLR_AP_MODE_ENABLE)); + /// wifi + Mycila::Config.configure(KEY_WIFI_SSID); + Mycila::Config.configure(KEY_WIFI_PASSWORD); + Mycila::Config.configure(KEY_WIFI_CONNECTION_TIMEOUT, String(ESPCONNECT_CONNECTION_TIMEOUT)); + // portal + Mycila::Config.configure(KEY_CAPTURE_PORTAL_TIMEOUT, String(ESPCONNECT_PORTAL_TIMEOUT)); + // ntp + Mycila::Config.configure(KEY_NTP_SERVER, YASOLR_NTP_SERVER); + Mycila::Config.configure(KEY_NTP_TIMEZONE, YASOLR_NTP_TIMEZONE); + // mqtt + Mycila::Config.configure(KEY_MQTT_ENABLE, YASOLR_BOOL(YASOLR_MQTT_ENABLE)); + Mycila::Config.configure(KEY_MQTT_SERVER); + Mycila::Config.configure(KEY_MQTT_PORT, String(YASOLR_MQTT_PORT)); + Mycila::Config.configure(KEY_MQTT_USERNAME, YASOLR_MQTT_USER); + Mycila::Config.configure(KEY_MQTT_PASSWORD); + Mycila::Config.configure(KEY_MQTT_TOPIC, mqttName); + Mycila::Config.configure(KEY_MQTT_SECURED, YASOLR_BOOL(YASOLR_MQTT_SECURED)); + Mycila::Config.configure(KEY_MQTT_PUBLISH_INTERVAL, String(YASOLR_MQTT_PUBLISH_INTERVAL)); + // ha + Mycila::Config.configure(KEY_HA_DISCOVERY_ENABLE, YASOLR_BOOL(YASOLR_HA_DISCOVERY_ENABLE)); + Mycila::Config.configure(KEY_HA_DISCOVERY_TOPIC, MYCILA_HA_DISCOVERY_TOPIC); + // buzzer + Mycila::Config.configure(KEY_BUZZER_ENABLE, YASOLR_BOOL(YASOLR_BUZZER_ENABLE)); + Mycila::Config.configure(KEY_BUZZER_PIN, String(YASOLR_BUZZER_PIN)); + // button + Mycila::Config.configure(KEY_BUTTON_ENABLE, YASOLR_BOOL(YASOLR_BUTTON_ENABLE)); + Mycila::Config.configure(KEY_BUTTON_PIN, String(YASOLR_BUTTON_PIN)); + Mycila::Config.configure(KEY_BUTTON_ACTION, String(YASOLR_BUTTON_ACTION)); + // leds + Mycila::Config.configure(KEY_LIGHTS_ENABLE, YASOLR_BOOL(YASOLR_LIGHTS_ENABLE)); + Mycila::Config.configure(KEY_LIGHTS_GREEN_PIN, String(YASOLR_LIGHTS_GREEN_PIN)); + Mycila::Config.configure(KEY_LIGHTS_YELLOW_PIN, String(YASOLR_LIGHTS_YELLOW_PIN)); + Mycila::Config.configure(KEY_LIGHTS_RED_PIN, String(YASOLR_LIGHTS_RED_PIN)); + // display + Mycila::Config.configure(KEY_DISPLAY_ENABLE, YASOLR_BOOL(YASOLR_DISPLAY_ENABLE)); + Mycila::Config.configure(KEY_DISPLAY_TYPE, YASOLR_DISPLAY_TYPE); + Mycila::Config.configure(KEY_DISPLAY_CLOCK_PIN, String(YASOLR_DISPLAY_CLOCK_PIN)); + Mycila::Config.configure(KEY_DISPLAY_DATA_PIN, String(YASOLR_DISPLAY_DATA_PIN)); + Mycila::Config.configure(KEY_DISPLAY_ROTATION, String(YASOLR_DISPLAY_ROTATION)); + Mycila::Config.configure(KEY_DISPLAY_POWER_SAVE_DELAY, String(YASOLR_DISPLAY_POWER_SAVE_DELAY)); + // system temperature + Mycila::Config.configure(KEY_SYSTEM_TEMP_ENABLE, YASOLR_BOOL(YASOLR_SYSTEM_TEMP_ENABLE)); + Mycila::Config.configure(KEY_SYSTEM_TEMP_PIN, String(YASOLR_SYSTEM_TEMP_PIN)); + // relay 1 + Mycila::Config.configure(KEY_RELAY1_ENABLE, YASOLR_BOOL(YASOLR_RELAY_ENABLE)); + Mycila::Config.configure(KEY_RELAY1_PIN, String(YASOLR_RELAY1_PIN)); + Mycila::Config.configure(KEY_RELAY1_TYPE, YASOLR_RELAY_TYPE); + Mycila::Config.configure(KEY_RELAY1_POWER, String(YASOLR_RELAY_POWER)); + // relay 2 + Mycila::Config.configure(KEY_RELAY2_ENABLE, YASOLR_BOOL(YASOLR_RELAY_ENABLE)); + Mycila::Config.configure(KEY_RELAY2_PIN, String(YASOLR_RELAY2_PIN)); + Mycila::Config.configure(KEY_RELAY2_TYPE, YASOLR_RELAY_TYPE); + Mycila::Config.configure(KEY_RELAY2_POWER, String(YASOLR_RELAY_POWER)); + // output 1 + Mycila::Config.configure(KEY_OUTPUT1_DIMMER_ENABLE, YASOLR_BOOL(YASOLR_OUTPUT_DIMMER_ENABLE)); + Mycila::Config.configure(KEY_OUTPUT1_DIMMER_PIN, String(YASOLR_OUTPUT1_DIMMER_PIN)); + Mycila::Config.configure(KEY_OUTPUT1_DIMMER_TYPE, YASOLR_OUTPUT_DIMMER_TYPE); + Mycila::Config.configure(KEY_OUTPUT1_DIMMER_AUTO, YASOLR_BOOL(YASOLR_OUTPUT_DIMMER_AUTO)); + Mycila::Config.configure(KEY_OUTPUT1_DIMMER_LEVEL_LIMIT, String(YASOLR_OUTPUT_DIMMER_LEVEL_LIMIT)); + Mycila::Config.configure(KEY_OUTPUT1_RELAY_ENABLE, YASOLR_BOOL(YASOLR_OUTPUT_RELAY_ENABLE)); + Mycila::Config.configure(KEY_OUTPUT1_RELAY_PIN, String(YASOLR_OUTPUT1_RELAY_PIN)); + Mycila::Config.configure(KEY_OUTPUT1_RELAY_TYPE, YASOLR_OUTPUT_RELAY_TYPE); + Mycila::Config.configure(KEY_OUTPUT1_TEMP_ENABLE, YASOLR_BOOL(YASOLR_OUTPUT_TEMP_ENABLE)); + Mycila::Config.configure(KEY_OUTPUT1_TEMP_PIN, String(YASOLR_OUTPUT1_TEMP_PIN)); + Mycila::Config.configure(KEY_OUTPUT1_AUTO_BYPASS_ENABLE, YASOLR_BOOL(YASOLR_OUTPUT_AUTO_BYPASS_ENABLE)); + Mycila::Config.configure(KEY_OUTPUT1_AUTO_START_TEMPERATURE, String(YASOLR_OUTPUT_AUTO_START_TEMPERATURE)); + Mycila::Config.configure(KEY_OUTPUT1_AUTO_STOP_TEMPERATURE, String(YASOLR_OUTPUT_AUTO_STOP_TEMPERATURE)); + Mycila::Config.configure(KEY_OUTPUT1_AUTO_START_TIME, String(YASOLR_OUTPUT_AUTO_START_TIME)); + Mycila::Config.configure(KEY_OUTPUT1_AUTO_STOP_TIME, String(YASOLR_OUTPUT_AUTO_STOP_TIME)); + Mycila::Config.configure(KEY_OUTPUT1_AUTO_WEEK_DAYS, String(YASOLR_OUTPUT_AUTO_WEEK_DAYS)); + // output 2 + Mycila::Config.configure(KEY_OUTPUT2_DIMMER_ENABLE, YASOLR_BOOL(YASOLR_OUTPUT_DIMMER_ENABLE)); + Mycila::Config.configure(KEY_OUTPUT2_DIMMER_PIN, String(YASOLR_OUTPUT2_DIMMER_PIN)); + Mycila::Config.configure(KEY_OUTPUT2_DIMMER_TYPE, YASOLR_OUTPUT_DIMMER_TYPE); + Mycila::Config.configure(KEY_OUTPUT2_DIMMER_AUTO, YASOLR_BOOL(YASOLR_OUTPUT_DIMMER_AUTO)); + Mycila::Config.configure(KEY_OUTPUT2_DIMMER_LEVEL_LIMIT, String(YASOLR_OUTPUT_DIMMER_LEVEL_LIMIT)); + Mycila::Config.configure(KEY_OUTPUT2_RELAY_ENABLE, YASOLR_BOOL(YASOLR_OUTPUT_RELAY_ENABLE)); + Mycila::Config.configure(KEY_OUTPUT2_RELAY_PIN, String(YASOLR_OUTPUT2_RELAY_PIN)); + Mycila::Config.configure(KEY_OUTPUT2_RELAY_TYPE, YASOLR_OUTPUT_RELAY_TYPE); + Mycila::Config.configure(KEY_OUTPUT2_TEMP_ENABLE, YASOLR_BOOL(YASOLR_OUTPUT_TEMP_ENABLE)); + Mycila::Config.configure(KEY_OUTPUT2_TEMP_PIN, String(YASOLR_OUTPUT2_TEMP_PIN)); + Mycila::Config.configure(KEY_OUTPUT2_AUTO_BYPASS_ENABLE, YASOLR_BOOL(YASOLR_OUTPUT_AUTO_BYPASS_ENABLE)); + Mycila::Config.configure(KEY_OUTPUT2_AUTO_START_TEMPERATURE, String(YASOLR_OUTPUT_AUTO_START_TEMPERATURE)); + Mycila::Config.configure(KEY_OUTPUT2_AUTO_STOP_TEMPERATURE, String(YASOLR_OUTPUT_AUTO_STOP_TEMPERATURE)); + Mycila::Config.configure(KEY_OUTPUT2_AUTO_START_TIME, String(YASOLR_OUTPUT_AUTO_START_TIME)); + Mycila::Config.configure(KEY_OUTPUT2_AUTO_STOP_TIME, String(YASOLR_OUTPUT_AUTO_STOP_TIME)); + Mycila::Config.configure(KEY_OUTPUT2_AUTO_WEEK_DAYS, String(YASOLR_OUTPUT_AUTO_WEEK_DAYS)); + // zcd + Mycila::Config.configure(KEY_ZCD_ENABLE, YASOLR_BOOL(YASOLR_ZCD_ENABLE)); + Mycila::Config.configure(KEY_ZCD_PIN, String(YASOLR_ZCD_PIN)); + Mycila::Config.configure(KEY_GRID_FREQ, String(YASOLR_GRID_FREQUENCY)); + // jsy + Mycila::Config.configure(KEY_JSY_ENABLE, YASOLR_BOOL(YASOLR_JSY_ENABLE)); + Mycila::Config.configure(KEY_JSY_RX_PIN, String(YASOLR_JSY_RX_PIN)); + Mycila::Config.configure(KEY_JSY_TX_PIN, String(YASOLR_JSY_TX_PIN)); + Mycila::Config.configure(KEY_GRID_POWER_MQTT_TOPIC, YASOLR_GRID_POWER_MQTT_TOPIC); +} + +// yasolr + +Mycila::EasyDisplayType YaSolR::YaSolRClass::getDisplayType() const { + const String val = Mycila::Config.get(KEY_DISPLAY_TYPE); + if (val == "SSD1306") + return Mycila::EasyDisplayType::SSD1306; + if (val == "SH1107") + return Mycila::EasyDisplayType::SH1107; + return Mycila::EasyDisplayType::SH1106; +} + +uint32_t YaSolR::YaSolRClass::getDisplayRotation() const { + const uint32_t val = Mycila::Config.get(KEY_DISPLAY_ROTATION).toInt(); + return val == 90 || val == 180 || val == 270 ? val : 0; +} + +Mycila::DimmerType YaSolR::YaSolRClass::getDimmerType(const char* name) const { + const char* key = nullptr; + if (strcmp(name, NAME_OUTPUT1) == 0) + key = KEY_OUTPUT1_DIMMER_TYPE; + else if (strcmp(name, NAME_OUTPUT2) == 0) + key = KEY_OUTPUT2_DIMMER_TYPE; + else + return Mycila::DimmerType::TRIAC; + String val = Mycila::Config.get(key); + if (val == "TRIAC") + return Mycila::DimmerType::TRIAC; + if (val == "SSR_RANDOM") + return Mycila::DimmerType::SSR_RANDOM; + if (val == "SSR_ZC") + return Mycila::DimmerType::SSR_ZC; + return Mycila::DimmerType::TRIAC; +} + +// logger + +uint8_t Mycila::LoggerClass::getLevel() const { return Mycila::Config.getBool(KEY_DEBUG_ENABLE) ? ARDUHAL_LOG_LEVEL_DEBUG : ARDUHAL_LOG_LEVEL_INFO; } + +// mqtt + +const Mycila::MQTTConfig Mycila::MQTTClass::getConfig() { + return { + Mycila::Config.getBool(KEY_MQTT_ENABLE) && !Mycila::Config.getBool(KEY_AP_MODE_ENABLE), + Mycila::Config.get(KEY_MQTT_SERVER), + static_cast(Mycila::Config.get(KEY_MQTT_PORT).toInt()), + Mycila::Config.getBool(KEY_MQTT_SECURED), + Mycila::Config.get(KEY_MQTT_USERNAME), + Mycila::Config.get(KEY_MQTT_PASSWORD), + AppInfo.name + "-" + AppInfo.id, + Mycila::Config.get(KEY_MQTT_TOPIC), + Mycila::Config.get(KEY_MQTT_TOPIC) + MYCILA_MQTT_WILL_TOPIC}; +} + +// relay manager + +uint16_t Mycila::RelayManagerConfigClass::getPowerThreshold(const char* name) const { + if (strcmp(name, NAME_RELAY1) == 0) + return Mycila::Config.get(KEY_RELAY1_POWER).toInt(); + if (strcmp(name, NAME_RELAY2) == 0) + return Mycila::Config.get(KEY_RELAY1_POWER).toInt(); + return 0; +} + +// router output + +bool Mycila::RouterOutputConfigClass::isAutoDimmerEnabled(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.getBool(KEY_OUTPUT1_DIMMER_AUTO); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.getBool(KEY_OUTPUT2_DIMMER_AUTO); + return false; +} +uint8_t Mycila::RouterOutputConfigClass::getDimmerLevelLimit(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.get(KEY_OUTPUT1_DIMMER_LEVEL_LIMIT).toInt(); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.get(KEY_OUTPUT2_DIMMER_LEVEL_LIMIT).toInt(); + return 100; +} +bool Mycila::RouterOutputConfigClass::isAutoBypassEnabled(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.getBool(KEY_OUTPUT1_AUTO_BYPASS_ENABLE); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.getBool(KEY_OUTPUT2_AUTO_BYPASS_ENABLE); + return false; +} +uint8_t Mycila::RouterOutputConfigClass::getAutoStartTemperature(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.get(KEY_OUTPUT1_AUTO_START_TEMPERATURE).toInt(); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.get(KEY_OUTPUT2_AUTO_START_TEMPERATURE).toInt(); + return 100; +} +uint8_t Mycila::RouterOutputConfigClass::getAutoStopTemperature(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.get(KEY_OUTPUT1_AUTO_STOP_TEMPERATURE).toInt(); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.get(KEY_OUTPUT2_AUTO_STOP_TEMPERATURE).toInt(); + return 100; +} +String Mycila::RouterOutputConfigClass::getAutoStartTime(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.get(KEY_OUTPUT1_AUTO_START_TIME); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.get(KEY_OUTPUT2_AUTO_START_TIME); + return "00:00"; +} +String Mycila::RouterOutputConfigClass::getAutoStopTime(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.get(KEY_OUTPUT1_AUTO_STOP_TIME); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.get(KEY_OUTPUT2_AUTO_STOP_TIME); + return "00:00"; +} +String Mycila::RouterOutputConfigClass::getWeekDays(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.get(KEY_OUTPUT1_AUTO_WEEK_DAYS); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.get(KEY_OUTPUT2_AUTO_WEEK_DAYS); + return "sun,mon,tue,wed,thu,fri,sat"; +} diff --git a/src/YaSolR_Display.cpp b/src/YaSolR_Display.cpp new file mode 100644 index 0000000..bf12444 --- /dev/null +++ b/src/YaSolR_Display.cpp @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +/* +| **LINE** | **CONTENT** | +| -------: | :-------------------------- | +| 1 | `YaSolR Pro v1.2.3 17:30` | +| 1 | `YaSolR Pro v1.2.3 ??:??` | +| 2 | `192.168.100.100 12.10 C` | +| 2 | `YaSolR-XXXX 12.10 C` | +| 2 | `Disconnected! 12.10 C` | +| 2 | `Connecting... 12.10 C` | +| 2 | `Reconnecting... 12.10 C` | +| 3 | `O1: 1000 W 12.10 C` | +| 3 | `O1: 1000 W ??.?? C` | +| 3 | `O1: RELAY OFF 12.10 C` | +| 4 | `O1: 12:22-12:23 12-20 C` | +| 4 | `O1: AUTO OFF` | +| 5 | `O2: 1000 W 12.10 C` | +| 6 | `O2: 12:22-12:23 12-20 C` | +| ALT | `RELAY1: ON RELAY2: ON` | +| ALT | `RELAY1: OFF RELAY2: OFF` | +*/ +void YaSolR::YaSolRClass::updateDisplay() { + if (Mycila::EasyDisplay.isEnabled()) { + char lines[MYCILA_DISPLAY_LINE_COUNT][MYCILA_DISPLAY_LINE_LENGTH]; + bzero(lines, sizeof(lines)); + + int next = 0; + + struct tm timeInfo; + if (Mycila::NTP.isSynced() && getLocalTime(&timeInfo, 5)) + snprintf(lines[next], MYCILA_DISPLAY_LINE_LENGTH, "%-6.6s %-3.3s %-8.8s %02u:%02u", Mycila::AppInfo.name.c_str(), Mycila::AppInfo.model.c_str(), Mycila::AppInfo.version.c_str(), timeInfo.tm_hour, timeInfo.tm_min); + else + snprintf(lines[next], MYCILA_DISPLAY_LINE_LENGTH, "%-6.6s %-3.3s %-8.8s ??:??", Mycila::AppInfo.name.c_str(), Mycila::AppInfo.model.c_str(), Mycila::AppInfo.version.c_str()); + next++; + + String networkState; + switch (ESPConnect.getState()) { + case ESPConnectState::NETWORK_CONNECTED: + networkState = ESPConnect.getIPAddress().toString(); + break; + case ESPConnectState::NETWORK_DISCONNECTED: + networkState = "Disconnected!"; + break; + case ESPConnectState::NETWORK_CONNECTING: + networkState = "Connecting..."; + break; + case ESPConnectState::NETWORK_RECONNECTING: + networkState = "Reconnecting..."; + break; + case ESPConnectState::NETWORK_TIMEOUT: + networkState = "Connect Timeout!"; + break; + case ESPConnectState::AP_STARTED: + case ESPConnectState::PORTAL_STARTED: + networkState = ESPConnect.getWiFiSSID(); + break; + default: + networkState = "Starting..."; + break; + } + snprintf(lines[next], MYCILA_DISPLAY_LINE_LENGTH, "%-17.17s %-5.5s C", networkState.c_str(), systemTemperatureSensor.getTemperatureAsString().c_str()); + next++; + + snprintf(lines[next], MYCILA_DISPLAY_LINE_LENGTH, "G: %5d W R: %4d W", static_cast(round(Mycila::Grid.getPower())), static_cast(round(Mycila::Router.getTotalRoutedPower()))); + next++; + + const Mycila::RouterOutput outputs[2] = {output1, output2}; + for (size_t i = 0; i < 2; i++) { + snprintf(lines[next], MYCILA_DISPLAY_LINE_LENGTH, "O%u: %-13.13s %-5.5s C", (i + 1), outputs[i].getStateString(), outputs[i].getTemperatureSensor()->getTemperatureAsString().c_str()); + next++; + } + + snprintf(lines[next], MYCILA_DISPLAY_LINE_LENGTH, "Relay1: %-3.3s Relay2: %-3.3s", relay1.isOn() ? "ON" : "OFF", relay2.isOn() ? "ON" : "OFF"); + next++; + + for (; next < 6; next++) + lines[next][0] = '\0'; + + Mycila::EasyDisplay.print(lines); + } +} diff --git a/src/YaSolR_EventHandlers.cpp b/src/YaSolR_EventHandlers.cpp new file mode 100644 index 0000000..98a2eb8 --- /dev/null +++ b/src/YaSolR_EventHandlers.cpp @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ + +#include + +#define TAG "EVENTS" + +void refresh() { + websiteTask.requestEarlyRun(); + publisherTask.requestEarlyRun(); +} + +void YaSolR::YaSolRClass::_initEventHandlers() { + Mycila::Button.listen([this](const String& action) { + if (action == "restart") + restartTask.resume(); + else if (action == "reset") + resetTask.resume(); + else if (action == "bypass") { + output1.tryBypassRelayToggle(); + output2.tryBypassRelayToggle(); + refresh(); + } + }); + + Mycila::MQTT.onConnect([](void) { + Mycila::Logger.info(TAG, "MQTT connected!"); + haDiscoTask.resume(); + }); + + ESPConnect.listen([this](ESPConnectState previous, ESPConnectState state) { + Mycila::Logger.debug(TAG, "NetworkState: %s => %s", ESPConnect.getStateName(previous), ESPConnect.getStateName(state)); + switch (state) { + case ESPConnectState::NETWORK_CONNECTED: + Mycila::Lights.setRed(false); + Mycila::Logger.info(TAG, "Connected with IP address %s", ESPConnect.getIPAddress().toString().c_str()); + startNetworkServicesTask.resume(); + break; + case ESPConnectState::AP_STARTED: + Mycila::Lights.setRed(false); + Mycila::Logger.info(TAG, "Access Point %s started with IP address %s", ESPConnect.getWiFiSSID().c_str(), ESPConnect.getIPAddress().toString().c_str()); + startNetworkServicesTask.resume(); + break; + case ESPConnectState::NETWORK_DISCONNECTED: + Mycila::Lights.setRed(true); + Mycila::Logger.warn(TAG, "Disconnected!"); + stopNetworkServicesTask.resume(); + break; + case ESPConnectState::NETWORK_DISABLED: + Mycila::Lights.setRed(true); + Mycila::Logger.debug(TAG, "Disable Network..."); + break; + case ESPConnectState::NETWORK_CONNECTING: + Mycila::Lights.setRed(true); + Mycila::Logger.info(TAG, "Connecting to network..."); + break; + case ESPConnectState::AP_STARTING: + Mycila::Lights.setRed(true); + Mycila::Logger.info(TAG, "Starting Access Point %s...", ESPConnect.getAccessPointSSID().c_str()); + break; + case ESPConnectState::PORTAL_STARTING: + Mycila::Lights.setRed(true); + Mycila::Logger.info(TAG, "Starting Captive Portal %s for %u seconds...", ESPConnect.getAccessPointSSID().c_str(), ESPConnect.getCaptivePortalTimeout()); + break; + case ESPConnectState::PORTAL_STARTED: + Mycila::Lights.setRed(true); + Mycila::Logger.info(TAG, "Captive Portal started at %s with IP address %s", ESPConnect.getWiFiSSID().c_str(), ESPConnect.getIPAddress().toString().c_str()); + Mycila::Buzzer.beep(2); + break; + case ESPConnectState::PORTAL_COMPLETE: { + bool ap = ESPConnect.hasConfiguredAPMode(); + if (ap) { + Mycila::Logger.info(TAG, "Captive Portal: Access Point configured"); + Mycila::Config.setBool(KEY_AP_MODE_ENABLE, true); + } else { + Mycila::Logger.info(TAG, "Captive Portal: WiFi configured"); + Mycila::Config.setBool(KEY_AP_MODE_ENABLE, false); + Mycila::Config.set(KEY_WIFI_SSID, ESPConnect.getConfiguredWiFiSSID()); + Mycila::Config.set(KEY_WIFI_PASSWORD, ESPConnect.getConfiguredWiFiPassword()); + } + restartTask.resume(); + break; + } + case ESPConnectState::PORTAL_TIMEOUT: + Mycila::Logger.warn(TAG, "Captive Portal: timed out."); + restartTask.resume(); + break; + case ESPConnectState::NETWORK_TIMEOUT: + Mycila::Logger.warn(TAG, "Unable to connect!"); + break; + case ESPConnectState::NETWORK_RECONNECTING: + Mycila::Logger.info(TAG, "Trying to reconnect..."); + break; + default: + break; + } + }); + + Mycila::Config.listen([this](const String& key, const String& oldValue, const String& newValue) { + if (key == KEY_MQTT_ENABLE || key == KEY_HA_DISCOVERY_ENABLE) + configureMQTTTask.resume(); + else if (key == KEY_AP_MODE_ENABLE && (ESPConnect.getState() == ESPConnectState::AP_STARTED || ESPConnect.getState() == ESPConnectState::NETWORK_CONNECTING || ESPConnect.getState() == ESPConnectState::NETWORK_CONNECTED || ESPConnect.getState() == ESPConnectState::NETWORK_TIMEOUT || ESPConnect.getState() == ESPConnectState::NETWORK_DISCONNECTED || ESPConnect.getState() == ESPConnectState::NETWORK_RECONNECTING)) + configureNetworkTask.resume(); + else if (key == KEY_BUTTON_ENABLE) + configureButtonTask.resume(); + else if (key == KEY_BUZZER_ENABLE) + configureBuzzerTask.resume(); + else if (key == KEY_DISPLAY_ENABLE) + configureDisplayTask.resume(); + else if (key == KEY_DISPLAY_POWER_SAVE_DELAY) + Mycila::EasyDisplay.setPowerSaveDelay(Mycila::Config.get(KEY_DISPLAY_POWER_SAVE_DELAY).toInt()); + else if (key == KEY_GRID_POWER_MQTT_TOPIC) + configureGridTask.resume(); + else if (key == KEY_JSY_ENABLE) + configureJSYTask.resume(); + else if (key == KEY_LIGHTS_ENABLE) + configureLightsTask.resume(); + else if (key == KEY_NTP_TIMEZONE || key == KEY_NTP_SERVER) + configureNTPTask.resume(); + else if (key == KEY_OUTPUT1_DIMMER_ENABLE) + configureOutput1DimmerTask.resume(); + else if (key == KEY_OUTPUT1_RELAY_ENABLE) + configureOutput1BypassRelayTask.resume(); + else if (key == KEY_OUTPUT1_TEMP_ENABLE) + configureOutput1TemperatureSensorTask.resume(); + else if (key == KEY_OUTPUT2_DIMMER_ENABLE) + configureOutput2DimmerTask.resume(); + else if (key == KEY_OUTPUT2_RELAY_ENABLE) + configureOutput2BypassRelayTask.resume(); + else if (key == KEY_OUTPUT2_TEMP_ENABLE) + configureOutput2TemperatureSensorTask.resume(); + else if (key == KEY_RELAY1_ENABLE) + configureRelay1Task.resume(); + else if (key == KEY_RELAY2_ENABLE) + configureRelay2Task.resume(); + else if (key == KEY_SYSTEM_TEMP_ENABLE) + configureSystemTemperatureSensorTask.resume(); + else if (key == KEY_ZCD_ENABLE) + configureZCDTask.resume(); + }); + + Mycila::Config.listen([this]() { + Mycila::Logger.info(TAG, "Configuration restored. Restarting..."); + restartTask.resume(); + }); + + relay1.listen([this](bool state) { refresh(); }); + relay2.listen([this](bool state) { refresh(); }); + + output1Dimmer.listen([this](Mycila::DimmerLevel event) { refresh(); }); + output2Dimmer.listen([this](Mycila::DimmerLevel event) { refresh(); }); + + output1BypassRelay.listen([this](bool state) { refresh(); }); + output2BypassRelay.listen([this](bool state) { refresh(); }); + + systemTemperatureSensor.listen([this](const char* name, float temperature) { + Mycila::Logger.info(TAG, "Temperature '%s' changed to %.02f °C", name, temperature); + refresh(); + }); + + output1TemperatureSensor.listen([this](const char* name, float temperature) { + Mycila::Logger.info(TAG, "Temperature '%s' changed to %.02f °C", name, temperature); + refresh(); + }); + + output2TemperatureSensor.listen([this](const char* name, float temperature) { + Mycila::Logger.info(TAG, "Temperature '%s' changed to %.02f °C", name, temperature); + refresh(); + }); +} diff --git a/src/YaSolR_HADiscovery.cpp b/src/YaSolR_HADiscovery.cpp new file mode 100644 index 0000000..3f9110b --- /dev/null +++ b/src/YaSolR_HADiscovery.cpp @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#define TAG "HA-DISCO" + +void YaSolR::YaSolRClass::publishHADiscovery() { + if (!Mycila::Config.getBool(KEY_HA_DISCOVERY_ENABLE)) + return; + + if (Mycila::Config.getBool(KEY_AP_MODE_ENABLE)) + return; + + if (!Mycila::MQTT.isConnected()) + return; + + if (Mycila::Config.get(KEY_HA_DISCOVERY_TOPIC).isEmpty()) + return; + + // GLOBAL SETTINGS + + Mycila::MQTTConfig mqttConfig = Mycila::MQTT.getConfig(); + + Mycila::HADiscovery.setDiscoveryTopic(Mycila::Config.get(KEY_HA_DISCOVERY_TOPIC)); + Mycila::HADiscovery.setSensorExpirationTime(4 * Mycila::Config.get(KEY_MQTT_PUBLISH_INTERVAL).toInt()); + + Mycila::HADiscovery.setBaseTopic(mqttConfig.baseTopic); + Mycila::HADiscovery.setWillTopic(mqttConfig.willTopic); + + Mycila::HADiscovery.setPublisher([](const String& topic, const String& payload) { + Mycila::MQTT.publish(topic, payload, true); + }); + + String appNameLow = Mycila::AppInfo.name; + appNameLow.toLowerCase(); + + Mycila::HADiscovery.setDevice({ + .id = appNameLow + "_" + Mycila::AppInfo.id, + .name = Mycila::AppInfo.name + "-" + Mycila::AppInfo.id, + .version = Mycila::AppInfo.version, + .model = Mycila::AppInfo.name + " " + Mycila::AppInfo.model, + .manufacturer = Mycila::AppInfo.manufacturer, + .url = "http://" + ESPConnect.getIPAddress().toString(), + }); + + // DIAGNOSTIC + + Mycila::HADiscovery.publish(Mycila::HAButton("restart", "Restart", "/system/restart", "restart", nullptr, Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HACounter("boots", "Boot Count", "/system/boots", nullptr, nullptr, nullptr, Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HACounter("uptime", "Uptime", "/system/uptime", "duration", nullptr, "s", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAGauge("cpu_cores", "CPU Cores", "/system/chip_cores", nullptr, "mdi:chip", nullptr, Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAGauge("memory_total", "Memory Total", "/system/heap_total", "data_size", "mdi:memory", "B", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAGauge("memory_usage", "Memory Usage", "/system/heap_usage", nullptr, "mdi:memory", "%", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAGauge("memory_used", "Memory Used", "/system/heap_used", "data_size", "mdi:memory", "B", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAGauge("wifi_quality", "WiFi Signal", "/network/wifi_quality", nullptr, "mdi:signal", "%", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAGauge("wifi_rssi", "WiFi RSSI", "/network/wifi_rssi", "signal_strength", "mdi:signal", "dBm", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAState("ntp", "NTP", "/ntp/synced", YASOLR_TRUE, YASOLR_FALSE, "connectivity", nullptr, Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAText("cpu_model", "CPU Model", "/system/chip_model", nullptr, "mdi:car-esp", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAText("firmware", "Firmware", "/app/firmware", nullptr, "mdi:file", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAText("hostname", "Hostname", "/config/" KEY_HOSTNAME, nullptr, "mdi:lan", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAText("id", "ID", "/app/id", nullptr, "mdi:identifier", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAText("mac_address", "MAC Address", "/network/mac_address", nullptr, "mdi:lan", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAText("system_status", "System Status", "/system/lights/code", nullptr, "mdi:cards-heart", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAText("wifi_ip_address", "WiFi IP Address", "/network/ip_address", nullptr, "mdi:ip", Mycila::HACategory::DIAGNOSTIC)); + Mycila::HADiscovery.publish(Mycila::HAText("wifi_ssid", "WiFi SSID", "/network/wifi_ssid", nullptr, "mdi:wifi", Mycila::HACategory::DIAGNOSTIC)); + + Mycila::HAGauge haTempSys("system_temperature", "System Temperature", "/system/temp_sensor/temperature", "temperature", "mdi:thermometer", "°C", Mycila::HACategory::DIAGNOSTIC); + haTempSys.availabilityTopic = "/system/temp_sensor/valid"; + haTempSys.payloadAvailable = YASOLR_TRUE; + haTempSys.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(haTempSys); + + // CONFIG + + Mycila::HADiscovery.publish(Mycila::HATextField("mqtt_publish_interval", "MQTT Publish Interval", "/config/" KEY_MQTT_PUBLISH_INTERVAL "/set", "/config/" KEY_MQTT_PUBLISH_INTERVAL, "^\\d+$", "mdi:timer-sand", Mycila::HACategory::CONFIG)); + + Mycila::HADiscovery.publish(Mycila::HATextField("display_power_save", "Display Power Save", "/config/" KEY_DISPLAY_POWER_SAVE_DELAY "/set", "/config/" KEY_DISPLAY_POWER_SAVE_DELAY, "^\\d+$", "mdi:monitor-off", Mycila::HACategory::CONFIG)); + + Mycila::HADiscovery.publish(Mycila::HANumber("relay1_power_threshold", "Relay 1 Power Threshold", "/config/" KEY_RELAY1_POWER "/set", "/config/" KEY_RELAY1_POWER, Mycila::HANumberMode::SLIDER, 0, 3000, 50, "mdi:flash", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HANumber("relay2_power_threshold", "Relay 2 Power Threshold", "/config/" KEY_RELAY2_POWER "/set", "/config/" KEY_RELAY2_POWER, Mycila::HANumberMode::SLIDER, 0, 3000, 50, "mdi:flash", Mycila::HACategory::CONFIG)); + + Mycila::HADiscovery.publish(Mycila::HANumber("output1_dimmer_level_limit", "Output 1 Dimmer Level Limit", "/config/" KEY_OUTPUT1_DIMMER_LEVEL_LIMIT "/set", "/config/" KEY_OUTPUT1_DIMMER_LEVEL_LIMIT, Mycila::HANumberMode::SLIDER, 0, 100, 1, "mdi:flash", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HASwitch("output1_auto_bypass_enable", "Output 1 Auto Bypass", "/config/" KEY_OUTPUT1_AUTO_BYPASS_ENABLE "/set", "/config/" KEY_OUTPUT1_AUTO_BYPASS_ENABLE, YASOLR_TRUE, YASOLR_FALSE, "mdi:water-boiler-auto", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HASwitch("output1_auto_dimmer_enable", "Output 1 Auto Dimmer", "/config/" KEY_OUTPUT1_DIMMER_AUTO "/set", "/config/" KEY_OUTPUT1_DIMMER_AUTO, YASOLR_TRUE, YASOLR_FALSE, "mdi:water-boiler-auto", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output1_auto_start_temperature", "Output 1 Auto Start Temperature", "/config/" KEY_OUTPUT1_AUTO_START_TEMPERATURE "/set", "/config/" KEY_OUTPUT1_AUTO_START_TEMPERATURE, "^\\d{1,3}$", "mdi:thermometer-low", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output1_auto_stop_temperature", "Output 1 Auto Stop Temperature", "/config/" KEY_OUTPUT1_AUTO_STOP_TEMPERATURE "/set", "/config/" KEY_OUTPUT1_AUTO_STOP_TEMPERATURE, "^\\d{1,3}$", "mdi:thermometer-alert", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output1_auto_start_time", "Output 1 Auto Start Time", "/config/" KEY_OUTPUT1_AUTO_START_TIME "/set", "/config/" KEY_OUTPUT1_AUTO_START_TIME, "^\\d?\\d:\\d\\d$", "mdi:clock-time-ten", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output1_auto_stop_time", "Output 1 Auto Stop Time", "/config/" KEY_OUTPUT1_AUTO_STOP_TIME "/set", "/config/" KEY_OUTPUT1_AUTO_STOP_TIME, "^\\d?\\d:\\d\\d$", "mdi:clock-time-six", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output1_auto_wdays", "Output 1 Auto Week Days", "/config/" KEY_OUTPUT1_AUTO_WEEK_DAYS "/set", "/config/" KEY_OUTPUT1_AUTO_WEEK_DAYS, nullptr, "mdi:calendar", Mycila::HACategory::CONFIG)); + + Mycila::HADiscovery.publish(Mycila::HANumber("output2_dimmer_level_limit", "Output 2 Dimmer Level Limit", "/config/" KEY_OUTPUT2_DIMMER_LEVEL_LIMIT "/set", "/config/" KEY_OUTPUT2_DIMMER_LEVEL_LIMIT, Mycila::HANumberMode::SLIDER, 0, 100, 1, "mdi:flash", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HASwitch("output2_auto_bypass_enable", "Output 2 Auto Bypass", "/config/" KEY_OUTPUT2_AUTO_BYPASS_ENABLE "/set", "/config/" KEY_OUTPUT2_AUTO_BYPASS_ENABLE, YASOLR_TRUE, YASOLR_FALSE, "mdi:water-boiler-auto", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HASwitch("output2_auto_dimmer_enable", "Output 2 Auto Dimmer", "/config/" KEY_OUTPUT2_DIMMER_AUTO "/set", "/config/" KEY_OUTPUT2_DIMMER_AUTO, YASOLR_TRUE, YASOLR_FALSE, "mdi:water-boiler-auto", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output2_auto_start_temperature", "Output 2 Auto Start Temperature", "/config/" KEY_OUTPUT2_AUTO_START_TEMPERATURE "/set", "/config/" KEY_OUTPUT2_AUTO_START_TEMPERATURE, "^\\d{1,3}$", "mdi:thermometer-low", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output2_auto_stop_temperature", "Output 2 Auto Stop Temperature", "/config/" KEY_OUTPUT2_AUTO_STOP_TEMPERATURE "/set", "/config/" KEY_OUTPUT2_AUTO_STOP_TEMPERATURE, "^\\d{1,3}$", "mdi:thermometer-alert", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output2_auto_start_time", "Output 2 Auto Start Time", "/config/" KEY_OUTPUT2_AUTO_START_TIME "/set", "/config/" KEY_OUTPUT2_AUTO_START_TIME, "^\\d?\\d:\\d\\d$", "mdi:clock-time-ten", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output2_auto_stop_time", "Output 2 Auto Stop Time", "/config/" KEY_OUTPUT2_AUTO_STOP_TIME "/set", "/config/" KEY_OUTPUT2_AUTO_STOP_TIME, "^\\d?\\d:\\d\\d$", "mdi:clock-time-six", Mycila::HACategory::CONFIG)); + Mycila::HADiscovery.publish(Mycila::HATextField("output2_auto_wdays", "Output 2 Auto Week Days", "/config/" KEY_OUTPUT2_AUTO_WEEK_DAYS "/set", "/config/" KEY_OUTPUT2_AUTO_WEEK_DAYS, nullptr, "mdi:calendar", Mycila::HACategory::CONFIG)); + + // SENSORS + + Mycila::HADiscovery.publish(Mycila::HAState("grid", "Grid Electricity", "/grid/online", YASOLR_TRUE, YASOLR_FALSE, "connectivity")); + + Mycila::HAGauge freq = Mycila::HAGauge("grid_frequency", "Grid Frequency", "/grid/frequency", "frequency", nullptr, "Hz"); + freq.availabilityTopic = "/grid/online"; + freq.payloadAvailable = YASOLR_TRUE; + freq.payloadNotAvailable = YASOLR_FALSE; + + Mycila::HAGauge volt = Mycila::HAGauge("grid_voltage", "Grid Voltage", "/grid/voltage", "voltage", nullptr, "V"); + volt.availabilityTopic = "/grid/online"; + volt.payloadAvailable = YASOLR_TRUE; + volt.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(volt); + + Mycila::HADiscovery.publish(Mycila::HAGauge("router_energy", "Routed Energy", "/router/energy", "energy", nullptr, "kWh")); + Mycila::HADiscovery.publish(Mycila::HAGauge("router_power", "Routed Power", "/router/power", "power", nullptr, "W")); + Mycila::HADiscovery.publish(Mycila::HAGauge("router_power_factor", "Router Power Factor", "/router/power_factor", "power_factor")); + Mycila::HADiscovery.publish(Mycila::HAGauge("router_thdi", "Router THDi", "/router/thdi")); + + Mycila::HADiscovery.publish(Mycila::HACounter(NAME_RELAY1 "_switch_count", "Relay 1 Switch Count", "/relays/" NAME_RELAY1 "/switch_count", nullptr, "mdi:counter", nullptr)); + Mycila::HADiscovery.publish(Mycila::HACounter(NAME_RELAY2 "_switch_count", "Relay 2 Switch Count", "/relays/" NAME_RELAY2 "/switch_count", nullptr, "mdi:counter", nullptr)); + + Mycila::HAOutlet relay1Commute(NAME_RELAY1 "_commute", "Relay 1", "/relays/" NAME_RELAY1 "/state/set", "/relays/" NAME_RELAY1 "/state", YASOLR_ON, YASOLR_OFF); + relay1Commute.availabilityTopic = "/relays/" NAME_RELAY1 "/enabled"; + relay1Commute.payloadAvailable = YASOLR_TRUE; + relay1Commute.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(relay1Commute); + + Mycila::HAOutlet relay2Commute(NAME_RELAY2 "_commute", "Relay 2", "/relays/" NAME_RELAY2 "/state/set", "/relays/" NAME_RELAY2 "/state", YASOLR_ON, YASOLR_OFF); + relay2Commute.availabilityTopic = "/relays/" NAME_RELAY2 "/enabled"; + relay2Commute.payloadAvailable = YASOLR_TRUE; + relay2Commute.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(relay2Commute); + + Mycila::HANumber haDimmer1(NAME_OUTPUT1 "_dimmer_level", "Output 1 Dimmer Level", "/router/" NAME_OUTPUT1 "/dimmer/level/set", "/router/" NAME_OUTPUT1 "/dimmer/level", Mycila::HANumberMode::SLIDER, 0, 100, 1, "mdi:water-boiler"); + haDimmer1.availabilityTopic = "/router/" NAME_OUTPUT1 "/dimmer/enabled"; + haDimmer1.payloadAvailable = YASOLR_TRUE; + haDimmer1.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(haDimmer1); + + Mycila::HADiscovery.publish(Mycila::HACounter(NAME_OUTPUT1 "_relay_switch_count", "Output 1 Bypass Switch Count", "/router/" NAME_OUTPUT1 "/bypass_relay/switch_count", nullptr, "mdi:counter", nullptr)); + Mycila::HAOutlet output1RelayCommute(NAME_OUTPUT1 "_relay_commute", "Output 1 Bypass Relay", "/router/" NAME_OUTPUT1 "/bypass_relay/state/set", "/router/" NAME_OUTPUT1 "/bypass_relay/state", YASOLR_ON, YASOLR_OFF); + output1RelayCommute.availabilityTopic = "/router/" NAME_OUTPUT1 "/bypass_relay/enabled"; + output1RelayCommute.payloadAvailable = YASOLR_TRUE; + output1RelayCommute.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(output1RelayCommute); + + Mycila::HAGauge haTempExt1(NAME_OUTPUT1 "_temperature", "Output 1 Temperature", "/router/" NAME_OUTPUT1 "/temp_sensor/temperature", "temperature", "mdi:thermometer", "°C"); + haTempExt1.availabilityTopic = "/router/" NAME_OUTPUT1 "/temp_sensor/valid"; + haTempExt1.payloadAvailable = YASOLR_TRUE; + haTempExt1.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(haTempExt1); + + Mycila::HANumber haDimmer2(NAME_OUTPUT2 "_dimmer_level", "Output 2 Dimmer Level", "/router/" NAME_OUTPUT2 "/dimmer/level/set", "/router/" NAME_OUTPUT2 "/dimmer/level", Mycila::HANumberMode::SLIDER, 0, 100, 1, "mdi:water-boiler"); + haDimmer2.availabilityTopic = "/router/" NAME_OUTPUT2 "/dimmer/enabled"; + haDimmer2.payloadAvailable = YASOLR_TRUE; + haDimmer2.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(haDimmer2); + + Mycila::HADiscovery.publish(Mycila::HACounter(NAME_OUTPUT2 "_relay_switch_count", "Output 2 Bypass Switch Count", "/router/" NAME_OUTPUT2 "/bypass_relay/switch_count", nullptr, "mdi:counter", nullptr)); + Mycila::HAOutlet output2RelayCommute(NAME_OUTPUT2 "_relay_commute", "Output 2 Bypass Relay", "/router/" NAME_OUTPUT2 "/bypass_relay/state/set", "/router/" NAME_OUTPUT2 "/bypass_relay/state", YASOLR_ON, YASOLR_OFF); + output2RelayCommute.availabilityTopic = "/router/" NAME_OUTPUT2 "/bypass_relay/enabled"; + output2RelayCommute.payloadAvailable = YASOLR_TRUE; + output2RelayCommute.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(output2RelayCommute); + + Mycila::HAGauge haTempExt2(NAME_OUTPUT2 "_temperature", "Output 2 Temperature", "/router/" NAME_OUTPUT2 "/temp_sensor/temperature", "temperature", "mdi:thermometer", "°C"); + haTempExt2.availabilityTopic = "/router/" NAME_OUTPUT2 "/temp_sensor/valid"; + haTempExt2.payloadAvailable = YASOLR_TRUE; + haTempExt2.payloadNotAvailable = YASOLR_FALSE; + Mycila::HADiscovery.publish(haTempExt2); +} diff --git a/src/YaSolR_MQTT.cpp b/src/YaSolR_MQTT.cpp new file mode 100644 index 0000000..34f70da --- /dev/null +++ b/src/YaSolR_MQTT.cpp @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#define TAG "MQTT" + +void YaSolR::YaSolRClass::publishMQTT() { + if (Mycila::MQTT.isConnected()) { + const String baseTopic = Mycila::Config.get(KEY_MQTT_TOPIC); + + // app + { + Mycila::MQTT.publish(baseTopic + "/app/firmware", Mycila::AppInfo.firmware); + Mycila::MQTT.publish(baseTopic + "/app/id", Mycila::AppInfo.id); + Mycila::MQTT.publish(baseTopic + "/app/manufacturer", Mycila::AppInfo.manufacturer); + Mycila::MQTT.publish(baseTopic + "/app/model", Mycila::AppInfo.model); + Mycila::MQTT.publish(baseTopic + "/app/name", Mycila::AppInfo.name); + Mycila::MQTT.publish(baseTopic + "/app/trial", YASOLR_BOOL(Mycila::AppInfo.trial)); + Mycila::MQTT.publish(baseTopic + "/app/version", Mycila::AppInfo.version); + } + yield(); + + // config + { + for (auto& key : Mycila::Config.keys) { + String value = Mycila::Config.get(key); + if (!value.isEmpty() && Mycila::Config.isPasswordKey(key)) + value = "********"; + Mycila::MQTT.publish(baseTopic + "/config/" + key, value); + } + } + yield(); + + // grid + { + Mycila::MQTT.publish(baseTopic + "/grid/frequency", String(Mycila::Grid.getFrequency())); + Mycila::MQTT.publish(baseTopic + "/grid/power", String(Mycila::Grid.getPower())); + Mycila::MQTT.publish(baseTopic + "/grid/voltage", String(Mycila::Grid.getVoltage())); + Mycila::MQTT.publish(baseTopic + "/grid/online", YASOLR_BOOL(Mycila::Grid.isOnline())); + } + yield(); + + // network + { + ESPConnectMode mode = ESPConnect.getMode(); + Mycila::MQTT.publish(baseTopic + "/network/ip_address", ESPConnect.getIPAddress().toString()); + Mycila::MQTT.publish(baseTopic + "/network/mac_address", ESPConnect.getMACAddress()); + Mycila::MQTT.publish(baseTopic + "/network/mode", mode == ESPConnectMode::AP ? "AP" : (mode == ESPConnectMode::STA ? "STA" : (mode == ESPConnectMode::ETH ? "ETH" : "NONE"))); + Mycila::MQTT.publish(baseTopic + "/network/state", ESPConnect.getStateName()); + Mycila::MQTT.publish(baseTopic + "/network/wifi_bssid", ESPConnect.getWiFiBSSID()); + Mycila::MQTT.publish(baseTopic + "/network/wifi_quality", String(ESPConnect.getWiFiSignalQuality())); + Mycila::MQTT.publish(baseTopic + "/network/wifi_rssi", String(ESPConnect.getWiFiRSSI())); + Mycila::MQTT.publish(baseTopic + "/network/wifi_ssid", ESPConnect.getWiFiSSID()); + } + + yield(); + + // ntp + { + Mycila::MQTT.publish(baseTopic + "/ntp/synced", YASOLR_BOOL(Mycila::NTP.isSynced())); + } + yield(); + + // relays + { + const std::vector relays = {relay1, relay2}; + for (auto& relay : relays) { + const String base = baseTopic + "/relays/" + relay.getName(); + Mycila::MQTT.publish(base + "/enabled", YASOLR_BOOL(relay.isEnabled())); + Mycila::MQTT.publish(base + "/state", YASOLR_STATE(relay.isOn())); + Mycila::MQTT.publish(base + "/switch_count", String(relay.getSwitchCount())); + } + } + yield(); + + // router + { + Mycila::MQTT.publish(baseTopic + "/router/energy", String(Mycila::Router.getTotalRoutedEnergy())); + Mycila::MQTT.publish(baseTopic + "/router/power_factor", String(Mycila::Router.getTotalPowerFactor())); + Mycila::MQTT.publish(baseTopic + "/router/power", String(Mycila::Router.getTotalRoutedPower())); + Mycila::MQTT.publish(baseTopic + "/router/thdi", String(Mycila::Router.getTotalTHDi())); + Mycila::MQTT.publish(baseTopic + "/router/virtual_grid_power", String(Mycila::Router.getVirtualGridPower())); + const std::vector outputs = {output1, output2}; + for (auto& output : outputs) { + const String base = baseTopic + "/router/" + output.getName(); + + Mycila::MQTT.publish(base + "/enabled", YASOLR_BOOL(output.isEnabled())); + Mycila::MQTT.publish(base + "/state", output.getStateString()); + + Mycila::MQTT.publish(base + "/dimmer/enabled", YASOLR_BOOL(output.getDimmer()->isEnabled())); + Mycila::MQTT.publish(base + "/dimmer/level", String(output.getDimmer()->getLevel())); + Mycila::MQTT.publish(base + "/dimmer/state", YASOLR_STATE(output.getDimmer()->isOn())); + + Mycila::MQTT.publish(base + "/temp_sensor/enabled", YASOLR_BOOL(output.getTemperatureSensor()->isEnabled())); + Mycila::MQTT.publish(base + "/temp_sensor/temperature", String(output.getTemperatureSensor()->getTemperature())); + Mycila::MQTT.publish(base + "/temp_sensor/valid", YASOLR_BOOL(output.getTemperatureSensor()->isValid())); + + Mycila::MQTT.publish(base + "/bypass_relay/enabled", YASOLR_BOOL(output.getBypassRelay()->isEnabled())); + Mycila::MQTT.publish(base + "/bypass_relay/state", YASOLR_STATE(output.getBypassRelay()->isOn())); + Mycila::MQTT.publish(base + "/bypass_relay/switch_count", String(output.getBypassRelay()->getSwitchCount())); + } + } + yield(); + + // system + { + Mycila::SystemMemory memory = Mycila::System.getMemory(); + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + Mycila::MQTT.publish(baseTopic + "/system/boots", String(Mycila::System.getBootCount())); + Mycila::MQTT.publish(baseTopic + "/system/chip_cores", String(chip_info.cores)); + Mycila::MQTT.publish(baseTopic + "/system/chip_model", ESP.getChipModel()); + Mycila::MQTT.publish(baseTopic + "/system/chip_revision", String(chip_info.revision)); + Mycila::MQTT.publish(baseTopic + "/system/cpu_freq", String(ESP.getCpuFreqMHz())); + Mycila::MQTT.publish(baseTopic + "/system/heap_total", String(memory.total)); + Mycila::MQTT.publish(baseTopic + "/system/heap_usage", String(memory.usage)); + Mycila::MQTT.publish(baseTopic + "/system/heap_used", String(memory.used)); + Mycila::MQTT.publish(baseTopic + "/system/uptime", String(Mycila::System.getUptime())); + Mycila::MQTT.publish(baseTopic + "/system/lights/code", Mycila::Lights.toString()); + Mycila::MQTT.publish(baseTopic + "/system/lights/green", YASOLR_STATE(Mycila::Lights.isGreenOn())); + Mycila::MQTT.publish(baseTopic + "/system/lights/red", YASOLR_STATE(Mycila::Lights.isRedOn())); + Mycila::MQTT.publish(baseTopic + "/system/lights/yellow", YASOLR_STATE(Mycila::Lights.isYellowOn())); + Mycila::MQTT.publish(baseTopic + "/system/temp_sensor/enabled", YASOLR_BOOL(systemTemperatureSensor.isEnabled())); + Mycila::MQTT.publish(baseTopic + "/system/temp_sensor/temperature", String(systemTemperatureSensor.getTemperature())); + Mycila::MQTT.publish(baseTopic + "/system/temp_sensor/valid", YASOLR_BOOL(systemTemperatureSensor.isValid())); + } + yield(); + } +} diff --git a/src/YaSolR_Tasks.cpp b/src/YaSolR_Tasks.cpp new file mode 100644 index 0000000..2b4ce2d --- /dev/null +++ b/src/YaSolR_Tasks.cpp @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#define TAG "YASOLR" + +const Mycila::TaskPredicate DEBUG_ENABLED = []() { return Mycila::Logger.isDebugEnabled(); }; +const Mycila::TaskDoneCallback LOG_EXEC_TIME = [](const Mycila::Task& me) { Mycila::Logger.debug(TAG, "%s in %d ms", me.getName(), me.getLastRuntime(Mycila::TimeUnit::MILLISECONDS)); }; + +// Core tasks + +Mycila::Task buttonTask("Button.processEvents()", [](void* params) { Mycila::Button.processEvents(); }, Mycila::TaskType::FOREVER, 0, true); + +Mycila::Task displayTask("YaSolR.updateDisplay()", [](void* params) { YaSolR::YaSolR.updateDisplay(); }, Mycila::TaskType::FOREVER, 500 * Mycila::TimeUnit::MILLISECONDS, true); + +Mycila::Task espConnectTask("ESPConnect.loop()", [](void* params) { ESPConnect.loop(); }, Mycila::TaskType::FOREVER, 0, true); + +Mycila::Task websiteTask = Mycila::Task("YaSolR.updateWebsite()", [](void* params) { YaSolR::YaSolR.updateWebsite(); }, Mycila::TaskType::FOREVER, 1 * Mycila::TimeUnit::SECONDS) + .onDone(LOG_EXEC_TIME) + .when([]() { return Mycila::HTTPd.isRunning() && !dashboard.isAsyncAccessInProgress(); }); + +Mycila::Task stackMonitorTask = Mycila::Task("TaskMonitor.log()", [](void* params) { Mycila::TaskMonitor.log(); }, Mycila::TaskType::FOREVER, 20 * Mycila::TimeUnit::SECONDS) + .debugIf(DEBUG_ENABLED) + .when(DEBUG_ENABLED); + +Mycila::Task systemTemperatureTask = Mycila::Task("systemTemperatureSensor.read()", [](void* params) { systemTemperatureSensor.read(); }, Mycila::TaskType::FOREVER, YASOLR_TEMPERATURE_READ_INTERVAL * Mycila::TimeUnit::SECONDS) + .debugIf(DEBUG_ENABLED) + .when([]() { return systemTemperatureSensor.isEnabled(); }); + +// Core tasks not on OTA + +Mycila::Task lightsTask = Mycila::Task("Lights", [](void* params) { Mycila::Lights.setYellow(relay1.isOn() || relay2.isOn() || output1BypassRelay.isOn() || output2BypassRelay.isOn() || output1Dimmer.isOn() || output2Dimmer.isOn()); }, Mycila::TaskType::FOREVER, 500 * Mycila::TimeUnit::MILLISECONDS, true); + +#ifdef APP_VERSION_TRIAL +Mycila::Task trialTask = Mycila::Task("Trial.validate()", [](void* params) { Mycila::Trial.validate(); }, Mycila::TaskType::FOREVER, 20 * Mycila::TimeUnit::SECONDS, true) + .debugIf(DEBUG_ENABLED); +#endif + +// Network tasks not on OTA and and only when mqtt is enabled / connected + +Mycila::Task mqttTask = Mycila::Task("MQTT.loop()", [](void* params) { Mycila::MQTT.loop(); }) + .when([]() { return Mycila::MQTT.isEnabled(); }); + +Mycila::Task publisherTask = Mycila::Task("YaSolR.publishMQTT()", [](void* params) { YaSolR::YaSolR.publishMQTT(); }, Mycila::TaskType::FOREVER) + .onDone(LOG_EXEC_TIME) + .interval([]() { return Mycila::Config.get(KEY_MQTT_PUBLISH_INTERVAL).toInt() * Mycila::TimeUnit::SECONDS; }) + .when([]() { return Mycila::MQTT.isConnected(); }); + +// Router tasks + +Mycila::Task autoRelayTask = Mycila::Task("RelayManager.autoCommute()", [](void* params) { Mycila::RelayManager.autoCommute(); }, Mycila::TaskType::FOREVER, YASOLR_RELAY_PAUSE_DURATION * Mycila::TimeUnit::SECONDS) + .when([]() { return relay1.isEnabled() || relay2.isEnabled(); }); + +Mycila::Task output1AutoBypassTask = Mycila::Task("output1.autoBypass()", [](void* params) { output1.autoBypass(); }, Mycila::TaskType::FOREVER, 500 * Mycila::TimeUnit::MILLISECONDS, true); + +Mycila::Task output1DimmerLimitTask = Mycila::Task("output1.applyDimmerLimit()", [](void* params) { output1.applyDimmerLimit(); }, Mycila::TaskType::FOREVER, 500 * Mycila::TimeUnit::MILLISECONDS) + .when([]() { return output1Dimmer.isEnabled(); }); + +Mycila::Task output1TemperatureTask = Mycila::Task("output1TemperatureSensor.read()", [](void* params) { output1TemperatureSensor.read(); }, Mycila::TaskType::FOREVER, YASOLR_TEMPERATURE_READ_INTERVAL * Mycila::TimeUnit::SECONDS) + .debugIf(DEBUG_ENABLED) + .when([]() { return output1TemperatureSensor.isEnabled(); }); + +Mycila::Task output2AutoBypassTask = Mycila::Task("output2.autoBypass()", [](void* params) { output2.autoBypass(); }, Mycila::TaskType::FOREVER, 500 * Mycila::TimeUnit::MILLISECONDS, true); + +Mycila::Task output2DimmerLimitTask = Mycila::Task("output2.applyDimmerLimit()", [](void* params) { output2.applyDimmerLimit(); }, Mycila::TaskType::FOREVER, 500 * Mycila::TimeUnit::MILLISECONDS) + .when([]() { return output2Dimmer.isEnabled(); }); + +Mycila::Task output2TemperatureTask = Mycila::Task("output2TemperatureSensor.read()", [](void* params) { output2TemperatureSensor.read(); }, Mycila::TaskType::FOREVER, YASOLR_TEMPERATURE_READ_INTERVAL * Mycila::TimeUnit::SECONDS) + .debugIf(DEBUG_ENABLED) + .when([]() { return output2TemperatureSensor.isEnabled(); }); + +// On Demand tasks + +Mycila::Task haDiscoTask = Mycila::Task("YaSolR.publishHADiscovery()", [](void* params) { YaSolR::YaSolR.publishHADiscovery(); }, Mycila::TaskType::ONCE) + .onDone(LOG_EXEC_TIME); + +Mycila::Task restartTask = Mycila::Task("restartTask", [](void* params) { + Mycila::Logger.warn(TAG, "Restarting %s %s %s...", Mycila::AppInfo.name.c_str(), Mycila::AppInfo.model.c_str(), Mycila::AppInfo.version.c_str()); + Mycila::Lights.set(Mycila::LightState::OFF, Mycila::LightState::OFF, Mycila::LightState::ON); + Mycila::Buzzer.beep(2); + Mycila::Buzzer.end(); + Mycila::System.restart(500); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task resetTask = Mycila::Task("resetTask", [](void* params) { + Mycila::Logger.warn(TAG, "Resetting %s %s %s...", Mycila::AppInfo.name.c_str(), Mycila::AppInfo.model.c_str(), Mycila::AppInfo.version.c_str()); + Mycila::Lights.set(Mycila::LightState::OFF, Mycila::LightState::ON, Mycila::LightState::ON); + Mycila::Buzzer.beep(3); + Mycila::Buzzer.end(); + Mycila::Config.clear(); + Mycila::System.restart(500); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task startNetworkServicesTask = Mycila::Task("startNetworkServicesTask", [](void* params) { + Mycila::Logger.info(TAG, "Starting network services..."); + Mycila::HTTPd.begin(); + Mycila::MQTT.begin(); + if (!Mycila::Config.getBool(KEY_AP_MODE_ENABLE)) + Mycila::NTP.sync(Mycila::Config.get(KEY_NTP_SERVER)); + Mycila::Buzzer.beep(); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task stopNetworkServicesTask = Mycila::Task("stopNetworkServicesTask", [](void* params) { + Mycila::Logger.info(TAG, "Stopping network services..."); + Mycila::MQTT.end(); + Mycila::HTTPd.end(); + Mycila::Buzzer.beep(2); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task otaPrepareTask = Mycila::Task("otaPrepareTask", [](void* params) { + Mycila::Logger.info(TAG, "Preparing OTA update..."); + lightsTask.pause(); + mqttTask.pause(); + publisherTask.pause(); + output1Dimmer.end(); + output2Dimmer.end(); + output1BypassRelay.end(); + output2BypassRelay.end(); + relay1.end(); + relay2.end(); + Mycila::Lights.setAllOn(); + Mycila::JSY.end(); + Mycila::ZCD.end(); + Mycila::MQTT.end(); +#ifdef APP_VERSION_TRIAL + trialTask.pause(); + Mycila::Trial.end(); +#endif +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +// reconfigure tasks + +Mycila::Task configureButtonTask = Mycila::Task("configureButtonTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Push Button..."); + Mycila::Button.end(); + if (Mycila::Config.getBool(KEY_BUTTON_ENABLE)) { + Mycila::Button.begin(Mycila::Config.get(KEY_BUTTON_PIN).toInt(), Mycila::Config.get(KEY_BUTTON_ACTION), "reset"); + } else { + Mycila::Logger.warn(TAG, "Button not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureBuzzerTask = Mycila::Task("configureBuzzerTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Buzzer..."); + Mycila::Buzzer.end(); + if (Mycila::Config.getBool(KEY_BUZZER_ENABLE)) { + Mycila::Buzzer.begin(Mycila::Config.get(KEY_BUZZER_PIN).toInt()); + } else { + Mycila::Logger.warn(TAG, "Buzzer not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureDisplayTask = Mycila::Task("configureDisplayTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Display..."); + Mycila::EasyDisplay.end(); + if (Mycila::Config.getBool(KEY_DISPLAY_ENABLE)) { + Mycila::EasyDisplay.setPowerSaveDelay(Mycila::Config.get(KEY_DISPLAY_POWER_SAVE_DELAY).toInt()); + Mycila::EasyDisplay.begin(YaSolR::YaSolR.getDisplayType(), Mycila::Config.get(KEY_DISPLAY_CLOCK_PIN).toInt(), Mycila::Config.get(KEY_DISPLAY_DATA_PIN).toInt(), YaSolR::YaSolR.getDisplayRotation()); + } else { + Mycila::Logger.warn(TAG, "Display not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureGridTask = Mycila::Task("configureGridTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Grid Power..."); + Mycila::Grid.end(); + Mycila::Grid.begin(Mycila::Config.get(KEY_GRID_POWER_MQTT_TOPIC)); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureJSYTask = Mycila::Task("configureJSYTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure JSY..."); + Mycila::JSY.end(); + if (Mycila::Config.getBool(KEY_JSY_ENABLE)) { + Mycila::JSY.begin(Mycila::Config.get(KEY_JSY_RX_PIN).toInt(), Mycila::Config.get(KEY_JSY_TX_PIN).toInt(), &MYCILA_JSY_SERIAL, true); + if (Mycila::JSY.isEnabled()) { + if (Mycila::JSY.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { + Mycila::Logger.info(TAG, "Trying to update JSY Baud Rate to 38400..."); + if (!Mycila::JSY.updateBaudRate(Mycila::JSYBaudRate::BAUD_38400)) { + Mycila::Logger.warn(TAG, "Failed to update JSY Baud Rate to 38400"); + } + } + } else { + Mycila::Logger.warn(TAG, "JSY failed to start"); + } + } else { + Mycila::Logger.warn(TAG, "JSY not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureLightsTask = Mycila::Task("configureLightsTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Lights..."); + Mycila::LightState g = Mycila::Lights.getGreen(); + Mycila::LightState y = Mycila::Lights.getYellow(); + Mycila::LightState r = Mycila::Lights.getRed(); + Mycila::Lights.end(); + if (Mycila::Config.getBool(KEY_LIGHTS_ENABLE)) { + Mycila::Lights.begin(Mycila::Config.get(KEY_LIGHTS_GREEN_PIN).toInt(), Mycila::Config.get(KEY_LIGHTS_YELLOW_PIN).toInt(), Mycila::Config.get(KEY_LIGHTS_RED_PIN).toInt()); + } else { + Mycila::Logger.warn(TAG, "Physical LEDs not enabled"); + } + Mycila::Lights.set(g, y, r); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureMQTTTask = Mycila::Task("configureMQTTTask", [](void* params) { + Mycila::Logger.info(TAG, "Configuring MQTT..."); + Mycila::MQTT.end(); + Mycila::MQTT.begin(); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureNetworkTask = Mycila::Task("configureNetworkTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Network..."); + Mycila::MQTT.end(); + Mycila::HTTPd.end(); + ESPConnect.end(); + Mycila::Logger.info(TAG, "Hostname: %s", Mycila::Config.get(KEY_HOSTNAME).c_str()); + ESPConnect.setAutoRestart(false); + ESPConnect.setBlocking(false); + ESPConnect.setCaptivePortalTimeout(Mycila::Config.get(KEY_CAPTURE_PORTAL_TIMEOUT).toInt()); + ESPConnect.setConnectTimeout(Mycila::Config.get(KEY_WIFI_CONNECTION_TIMEOUT).toInt()); + ESPConnect.begin(&webServer, Mycila::Config.get(KEY_HOSTNAME), Mycila::AppInfo.name + "-" + Mycila::AppInfo.id, Mycila::Config.get(KEY_ADMIN_PASSWORD), {Mycila::Config.get(KEY_WIFI_SSID), Mycila::Config.get(KEY_WIFI_PASSWORD), Mycila::Config.getBool(KEY_AP_MODE_ENABLE)}); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureNTPTask = Mycila::Task("configureNTPTask", [](void* params) { + Mycila::Logger.info(TAG, "Configuring NTP..."); + Mycila::NTP.setTimeZone(Mycila::Config.get(KEY_NTP_TIMEZONE)); + if (ESPConnect.isConnected() && !Mycila::Config.getBool(KEY_AP_MODE_ENABLE)) { + Mycila::NTP.sync(Mycila::Config.get(KEY_NTP_SERVER)); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureOutput1BypassRelayTask = Mycila::Task("configureOutput1BypassRelayTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Output 1 Bypass Relay..."); + output1BypassRelay.end(); + if (Mycila::Config.getBool(KEY_OUTPUT1_RELAY_ENABLE)) { + output1BypassRelay.begin(Mycila::Config.get(KEY_OUTPUT1_RELAY_PIN).toInt(), Mycila::Config.get(KEY_OUTPUT1_RELAY_TYPE) == "NC" ? Mycila::RelayType::NC : Mycila::RelayType::NO); + } else { + Mycila::Logger.warn(TAG, "Output 1 Bypass Relay not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureOutput1DimmerTask = Mycila::Task("configureOutput1DimmerTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Output 1 Dimmer..."); + output1Dimmer.end(); + if (Mycila::Config.getBool(KEY_OUTPUT1_DIMMER_ENABLE)) { + output1Dimmer.begin(Mycila::Config.get(KEY_OUTPUT1_DIMMER_PIN).toInt(), YaSolR::YaSolR.getDimmerType(output1Dimmer.getName())); + } else { + Mycila::Logger.warn(TAG, "Output 1 Dimmer not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureOutput1TemperatureSensorTask = Mycila::Task("configureOutput1TemperatureSensorTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Output 1 Temperature Sensor..."); + output1TemperatureSensor.end(); + if (Mycila::Config.getBool(KEY_OUTPUT1_TEMP_ENABLE)) { + output1TemperatureSensor.begin(Mycila::Config.get(KEY_OUTPUT1_TEMP_PIN).toInt(), 6 * YASOLR_TEMPERATURE_READ_INTERVAL); + } else { + Mycila::Logger.warn(TAG, "Output 1 Temperature Sensor not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureOutput2BypassRelayTask = Mycila::Task("configureOutput2BypassRelayTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Output 2 Bypass Relay..."); + output2BypassRelay.end(); + if (Mycila::Config.getBool(KEY_OUTPUT2_RELAY_ENABLE)) { + output2BypassRelay.begin(Mycila::Config.get(KEY_OUTPUT2_RELAY_PIN).toInt(), Mycila::Config.get(KEY_OUTPUT2_RELAY_TYPE) == "NC" ? Mycila::RelayType::NC : Mycila::RelayType::NO); + } else { + Mycila::Logger.warn(TAG, "Output 2 Bypass Relay not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureOutput2DimmerTask = Mycila::Task("configureOutput2DimmerTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Output 2 Dimmer..."); + output2Dimmer.end(); + if (Mycila::Config.getBool(KEY_OUTPUT2_DIMMER_ENABLE)) { + output2Dimmer.begin(Mycila::Config.get(KEY_OUTPUT2_DIMMER_PIN).toInt(), YaSolR::YaSolR.getDimmerType(output2Dimmer.getName())); + } else { + Mycila::Logger.warn(TAG, "Output 2 Dimmer not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureOutput2TemperatureSensorTask = Mycila::Task("configureOutput2TemperatureSensorTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Output 2 Temperature Sensor..."); + output2TemperatureSensor.end(); + if (Mycila::Config.getBool(KEY_OUTPUT2_TEMP_ENABLE)) { + output2TemperatureSensor.begin(Mycila::Config.get(KEY_OUTPUT2_TEMP_PIN).toInt(), 6 * YASOLR_TEMPERATURE_READ_INTERVAL); + } else { + Mycila::Logger.warn(TAG, "Output 2 Temperature Sensor not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureRelay1Task = Mycila::Task("configureRelay1Task", [](void* params) { + Mycila::Logger.info(TAG, "Configure Relay 1..."); + relay1.end(); + if (Mycila::Config.getBool(KEY_RELAY1_ENABLE)) { + relay1.begin(Mycila::Config.get(KEY_RELAY1_PIN).toInt(), Mycila::Config.get(KEY_RELAY1_TYPE) == "NC" ? Mycila::RelayType::NC : Mycila::RelayType::NO); + } else { + Mycila::Logger.warn(TAG, "Relay 1 not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureRelay2Task = Mycila::Task("configureRelay2Task", [](void* params) { + Mycila::Logger.info(TAG, "Configure Relay 2..."); + relay2.end(); + if (Mycila::Config.getBool(KEY_RELAY2_ENABLE)) { + relay2.begin(Mycila::Config.get(KEY_RELAY2_PIN).toInt(), Mycila::Config.get(KEY_RELAY2_TYPE) == "NC" ? Mycila::RelayType::NC : Mycila::RelayType::NO); + } else { + Mycila::Logger.warn(TAG, "Relay 2 not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureSystemTemperatureSensorTask = Mycila::Task("configureSystemTemperatureSensorTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure System Temperature Sensor..."); + systemTemperatureSensor.end(); + if (Mycila::Config.getBool(KEY_SYSTEM_TEMP_ENABLE)) { + systemTemperatureSensor.begin(Mycila::Config.get(KEY_SYSTEM_TEMP_PIN).toInt(), 6 * YASOLR_TEMPERATURE_READ_INTERVAL); + } else { + Mycila::Logger.warn(TAG, "System Temperature Sensor not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureTaskMonitorTask = Mycila::Task("configureTaskMonitorTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Task Stack Monitor..."); + Mycila::TaskMonitor.begin(5); + Mycila::TaskMonitor.addTask("loopTask"); + Mycila::TaskMonitor.addTask("async_tcp"); + Mycila::TaskMonitor.addTask("mqttclient"); + Mycila::TaskMonitor.addTask("jsyTask"); + Mycila::TaskMonitor.addTask("ioTask"); +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + +Mycila::Task configureZCDTask = Mycila::Task("configureZCDTask", [](void* params) { + Mycila::Logger.info(TAG, "Configure Zero-Cross Detection..."); + Mycila::ZCD.end(); + if (Mycila::Config.getBool(KEY_ZCD_ENABLE)) { + Mycila::ZCD.begin(Mycila::Config.get(KEY_ZCD_PIN).toInt(), Mycila::Config.get(KEY_GRID_FREQ).toInt() == 60 ? 60 : 50); + } else { + Mycila::Logger.warn(TAG, "Zero-Cross Detection not enabled"); + } +}, Mycila::TaskType::ONCE).debugIf(DEBUG_ENABLED); + diff --git a/src/YaSolR_Website_Init.cpp b/src/YaSolR_Website_Init.cpp new file mode 100644 index 0000000..5c2b63b --- /dev/null +++ b/src/YaSolR_Website_Init.cpp @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#ifdef APP_VERSION_PRO +#include +#else +#include +#endif + +#define TAG "WEBSITE" + +extern const uint8_t logo_png_gz_start[] asm("_binary__pio_data_logo_png_gz_start"); +extern const uint8_t logo_png_gz_end[] asm("_binary__pio_data_logo_png_gz_end"); +extern const uint8_t logo_icon_png_gz_start[] asm("_binary__pio_data_logo_icon_png_gz_start"); +extern const uint8_t logo_icon_png_gz_end[] asm("_binary__pio_data_logo_icon_png_gz_end"); +extern const uint8_t logo_icon_png_gz_start[] asm("_binary__pio_data_logo_icon_png_gz_start"); +extern const uint8_t logo_icon_png_gz_end[] asm("_binary__pio_data_logo_icon_png_gz_end"); +extern const uint8_t config_html_gz_start[] asm("_binary__pio_data_config_html_gz_start"); +extern const uint8_t config_html_gz_end[] asm("_binary__pio_data_config_html_gz_end"); + +void YaSolR::WebsiteClass::init() { +// pro mode +#ifdef APP_VERSION_PRO + dashboard.setTitle((Mycila::AppInfo.name + " " + Mycila::AppInfo.model).c_str()); + + // dropdowns + + _displayRotation.update("", "0°,90°,180°,270°"); + _displayType.update("", "SH1106,SH1107,SSD1306"); + _gridFreq.update("", "50 Hz,60 Hz"); + _output1BypassType.update("", "NO,NC"); + _output2BypassType.update("", "NO,NC"); + _pushButtonAction.update("", "restart,bypass"); + _relay1Type.update("", "NO,NC"); + _relay2Type.update("", "NO,NC"); + _output1DimmerType.update("", "TRIAC,SSR_RANDOM,SSR_ZC"); + _output2DimmerType.update("", "TRIAC,SSR_RANDOM,SSR_ZC"); + _ntpTimezone.update("", "/api/ntp/timezones"); + + // tabs + + _configBackup.setTab(&_managementTab); + _configRestore.setTab(&_managementTab); + _consoleLink.setTab(&_managementTab); + _debugMode.setTab(&_managementTab); + _otaLink.setTab(&_managementTab); + _reset.setTab(&_managementTab); + _restart.setTab(&_managementTab); + + _stateBuzzer.setTab(&_stateTab); + _stateDisplay.setTab(&_stateTab); + _stateGridPowerMQTT.setTab(&_stateTab); + _stateJSY.setTab(&_stateTab); + _stateLEDs.setTab(&_stateTab); + _stateMQTT.setTab(&_stateTab); + _stateNTP.setTab(&_stateTab); + _stateOutput1AutoBypass.setTab(&_stateTab); + _stateOutput1AutoDimmer.setTab(&_stateTab); + _stateOutput1Bypass.setTab(&_stateTab); + _stateOutput1Dimmer.setTab(&_stateTab); + _stateOutput1Temp.setTab(&_stateTab); + _stateOutput2AutoBypass.setTab(&_stateTab); + _stateOutput2AutoDimmer.setTab(&_stateTab); + _stateOutput2Bypass.setTab(&_stateTab); + _stateOutput2Dimmer.setTab(&_stateTab); + _stateOutput2Temp.setTab(&_stateTab); + _statePushButton.setTab(&_stateTab); + _stateRelay1.setTab(&_stateTab); + _stateRelay2.setTab(&_stateTab); + _stateSystemTemp.setTab(&_stateTab); + _stateZCD.setTab(&_stateTab); + + _pinLiveBuzzer.setTab(&_pinLiveTab); + _pinLiveDisplayClock.setTab(&_pinLiveTab); + _pinLiveDisplayData.setTab(&_pinLiveTab); + _pinLiveJsyRX.setTab(&_pinLiveTab); + _pinLiveJsyTX.setTab(&_pinLiveTab); + _pinLiveLEDGreen.setTab(&_pinLiveTab); + _pinLiveLEDRed.setTab(&_pinLiveTab); + _pinLiveLEDYellow.setTab(&_pinLiveTab); + _pinLiveOutput1Bypass.setTab(&_pinLiveTab); + _pinLiveOutput1Dimmer.setTab(&_pinLiveTab); + _pinLiveOutput1Temp.setTab(&_pinLiveTab); + _pinLiveOutput2Bypass.setTab(&_pinLiveTab); + _pinLiveOutput2Dimmer.setTab(&_pinLiveTab); + _pinLiveOutput2Temp.setTab(&_pinLiveTab); + _pinLivePushButton.setTab(&_pinLiveTab); + _pinLiveRelay1.setTab(&_pinLiveTab); + _pinLiveRelay2.setTab(&_pinLiveTab); + _pinLiveSystemTemp.setTab(&_pinLiveTab); + _pinLiveZCD.setTab(&_pinLiveTab); + + _pinConfigBuzzer.setTab(&_pinConfigTab); + _pinConfigDisplayClock.setTab(&_pinConfigTab); + _pinConfigDisplayData.setTab(&_pinConfigTab); + _pinConfigJsyRX.setTab(&_pinConfigTab); + _pinConfigJsyTX.setTab(&_pinConfigTab); + _pinConfigLEDGreen.setTab(&_pinConfigTab); + _pinConfigLEDRed.setTab(&_pinConfigTab); + _pinConfigLEDYellow.setTab(&_pinConfigTab); + _pinConfigOutput1Bypass.setTab(&_pinConfigTab); + _pinConfigOutput1Dimmer.setTab(&_pinConfigTab); + _pinConfigOutput1Temp.setTab(&_pinConfigTab); + _pinConfigOutput2Bypass.setTab(&_pinConfigTab); + _pinConfigOutput2Dimmer.setTab(&_pinConfigTab); + _pinConfigOutput2Temp.setTab(&_pinConfigTab); + _pinConfigPushButton.setTab(&_pinConfigTab); + _pinConfigRelay1.setTab(&_pinConfigTab); + _pinConfigRelay2.setTab(&_pinConfigTab); + _pinConfigSystemTemp.setTab(&_pinConfigTab); + _pinConfigZCD.setTab(&_pinConfigTab); + + _buzzer.setTab(&_systemTab); + _buzzerPin.setTab(&_systemTab); + + _display.setTab(&_displayTab); + _displayClockPin.setTab(&_displayTab); + _displayDataPin.setTab(&_displayTab); + _displayPowerSaveDelay.setTab(&_displayTab); + _displayRotation.setTab(&_displayTab); + _displayType.setTab(&_displayTab); + + _mqtt.setTab(&_mqttTab); + _mqttPort.setTab(&_mqttTab); + _mqttPublishInterval.setTab(&_mqttTab); + _mqttPwd.setTab(&_mqttTab); + _mqttSecured.setTab(&_mqttTab); + _mqttServer.setTab(&_mqttTab); + _mqttTopic.setTab(&_mqttTab); + _mqttUser.setTab(&_mqttTab); + + _haDiscovery.setTab(&_mqttTab); + _haDiscoveryTopic.setTab(&_mqttTab); + + _adminPwd.setTab(&_networkTab); + _apMode.setTab(&_networkTab); + _hostname.setTab(&_networkTab); + _wifiPwd.setTab(&_networkTab); + _wifiSSID.setTab(&_networkTab); + + _ntpServer.setTab(&_networkTab); + _ntpTimezone.setTab(&_networkTab); + _ntpSync.setTab(&_networkTab); + + _output1AutoBypass.setTab(&_output1Tab); + _output1AutoDimmer.setTab(&_output1Tab); + _output1AutoStartTemp.setTab(&_output1Tab); + _output1AutoStartTime.setTab(&_output1Tab); + _output1AutoStartWDays.setTab(&_output1Tab); + _output1AutoStoptTemp.setTab(&_output1Tab); + _output1AutoStoptTime.setTab(&_output1Tab); + _output1Bypass.setTab(&_output1Tab); + _output1BypassPin.setTab(&_output1Tab); + _output1BypassType.setTab(&_output1Tab); + _output1Dimmer.setTab(&_output1Tab); + _output1DimmerPin.setTab(&_output1Tab); + _output1DimmerType.setTab(&_output1Tab); + _output1DimmerPowerLimit.setTab(&_output1Tab); + _output1Temp.setTab(&_output1Tab); + _output1TempPin.setTab(&_output1Tab); + + _output2AutoBypass.setTab(&_output2Tab); + _output2AutoDimmer.setTab(&_output2Tab); + _output2AutoStartTemp.setTab(&_output2Tab); + _output2AutoStartTime.setTab(&_output2Tab); + _output2AutoStartWDays.setTab(&_output2Tab); + _output2AutoStoptTemp.setTab(&_output2Tab); + _output2AutoStoptTime.setTab(&_output2Tab); + _output2Bypass.setTab(&_output2Tab); + _output2BypassPin.setTab(&_output2Tab); + _output2BypassType.setTab(&_output2Tab); + _output2Dimmer.setTab(&_output2Tab); + _output2DimmerPin.setTab(&_output2Tab); + _output2DimmerType.setTab(&_output2Tab); + _output2DimmerPowerLimit.setTab(&_output2Tab); + _output2Temp.setTab(&_output2Tab); + _output2TempPin.setTab(&_output2Tab); + + _pushButton.setTab(&_systemTab); + _pushButtonPin.setTab(&_systemTab); + _pushButtonAction.setTab(&_systemTab); + + _relay1.setTab(&_relay1Tab); + _relay1Pin.setTab(&_relay1Tab); + _relay1Power.setTab(&_relay1Tab); + _relay1Type.setTab(&_relay1Tab); + + _relay2.setTab(&_relay2Tab); + _relay2Pin.setTab(&_relay2Tab); + _relay2Power.setTab(&_relay2Tab); + _relay2Type.setTab(&_relay2Tab); + + _systemTemp.setTab(&_systemTab); + _systemTempPin.setTab(&_systemTab); + + _led.setTab(&_systemTab); + _ledGreenPin.setTab(&_systemTab); + _ledRedPin.setTab(&_systemTab); + _ledYellowPin.setTab(&_systemTab); + + _gridFreq.setTab(&_electricityTab); + _gridPowerMQTT.setTab(&_electricityTab); + _jsy.setTab(&_electricityTab); + _jsyRXPin.setTab(&_electricityTab); + _jsyTXPin.setTab(&_electricityTab); + _jsyReset.setTab(&_electricityTab); + _zcd.setTab(&_electricityTab); + _zcdPin.setTab(&_electricityTab); +#endif + + // logo + + webServer.rewrite("/dash/assets/logo/mini", "/logo-icon"); + webServer.rewrite("/dash/assets/logo/large", "/logo"); + webServer.rewrite("/dash/assets/logo", "/logo"); + webServer.rewrite("/ota/logo/dark", "/logo"); + webServer.rewrite("/ota/logo/light", "/logo"); + Mycila::HTTPd.get( + "/logo-icon", + [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse_P(200, "image/png", logo_icon_png_gz_start, logo_icon_png_gz_end - logo_icon_png_gz_start); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", "public, max-age=900"); + request->send(response); + }, + true); + Mycila::HTTPd.get( + "/logo", + [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse_P(200, "image/png", logo_png_gz_start, logo_png_gz_end - logo_png_gz_start); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", "public, max-age=900"); + request->send(response); + }, + true); + + // ping + + Mycila::HTTPd.get( + "/ping", + [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", "pong"); + request->send(response); + }, + true); + + // dashboard + + dashboard.setAuthentication(YASOLR_ADMIN_USERNAME, Mycila::Config.get(KEY_ADMIN_PASSWORD).c_str()); + webServer.rewrite("/", "/dashboard").setFilter([](AsyncWebServerRequest* request) { return ESPConnect.getState() != ESPConnectState::PORTAL_STARTED; }); + + // config + + Mycila::HTTPd.get("/config", [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse_P(200, "text/html", config_html_gz_start, config_html_gz_end - config_html_gz_start); + response->addHeader("Content-Encoding", "gzip"); + request->send(response); + }); + + // ota + +// &webServer, YASOLR_ADMIN_USERNAME, , , , , +#ifdef APP_VERSION_PRO + ElegantOTA.setID(Mycila::AppInfo.firmware.c_str()); + ElegantOTA.setTitle((Mycila::AppInfo.name + " Web Updater").c_str()); + ElegantOTA.setFWVersion(Mycila::AppInfo.version.c_str()); + ElegantOTA.setFirmwareMode(true); + ElegantOTA.setFilesystemMode(false); +#endif + ElegantOTA.setAutoReboot(false); + ElegantOTA.onStart([this]() { otaPrepareTask.resume(); }); + ElegantOTA.onEnd([this](bool success) { + if (success) { + Mycila::Logger.info(TAG, "OTA Update Success! Restarting..."); + } else { + Mycila::Logger.error(TAG, "OTA Failed! Restarting..."); + } + restartTask.resume(); + }); + ElegantOTA.begin(&webServer, YASOLR_ADMIN_USERNAME, Mycila::Config.get(KEY_ADMIN_PASSWORD).c_str()); + + // app stats + + _firmName.set((Mycila::AppInfo.name.c_str())); + _firmModel.set((Mycila::AppInfo.model.c_str())); + _firmManufacturerStat.set(Mycila::AppInfo.manufacturer.c_str()); + _firmVersionStat.set(Mycila::AppInfo.version.c_str()); + _firmFilenameStat.set(Mycila::AppInfo.firmware.c_str()); + _deviceIdStat.set(Mycila::AppInfo.id.c_str()); + _cpuCoresStat.set(String(ESP.getChipCores()).c_str()); + _cpuModelStat.set(ESP.getChipModel()); + _heapMemoryTotalStat.set((String(ESP.getHeapSize()) + " bytes").c_str()); + _bootCountStat.set(String(Mycila::System.getBootCount()).c_str()); + _firmTimeStat.set(Mycila::AppInfo.buildDate.c_str()); + _firmHashStat.set(Mycila::AppInfo.buildHash.c_str()); + + // home callbacks + + _outputRelaySwitch(&_output1BypassSwitch, &output1); + _outputRelaySwitch(&_output2BypassSwitch, &output2); + _outputDimmerSlider(&_output1DimmerSlider, &output1); + _outputDimmerSlider(&_output2DimmerSlider, &output2); + _relaySwitch(&_relay1Switch, &relay1); + _relaySwitch(&_relay2Switch, &relay2); + +#ifdef APP_VERSION_PRO + // management + + _boolConfig(&_debugMode, KEY_DEBUG_ENABLE); + _restart.attachCallback([=] PUSH_BUTTON_CARD_CB { restartTask.resume(); }); + _reset.attachCallback([=] PUSH_BUTTON_CARD_CB { resetTask.resume(); }); + _otaLink.update("/update"); + _consoleLink.update("/console"); + _configBackup.update("/api/config/backup"); + _configRestore.update("/api/config/restore"); + + // display callbacks + + _boolConfig(&_display, KEY_DISPLAY_ENABLE); + _numConfig(&_displayClockPin, KEY_DISPLAY_CLOCK_PIN); + _numConfig(&_displayDataPin, KEY_DISPLAY_DATA_PIN); + _textConfig(&_displayType, KEY_DISPLAY_TYPE); + _numConfig(&_displayRotation, KEY_DISPLAY_ROTATION); + _numConfig(&_displayPowerSaveDelay, KEY_DISPLAY_POWER_SAVE_DELAY); + + // electricity callbacks + + _boolConfig(&_zcd, KEY_ZCD_ENABLE); + _numConfig(&_zcdPin, KEY_ZCD_PIN); + _boolConfig(&_jsy, KEY_JSY_ENABLE); + _numConfig(&_jsyRXPin, KEY_JSY_RX_PIN); + _numConfig(&_jsyTXPin, KEY_JSY_TX_PIN); + _numConfig(&_gridFreq, KEY_GRID_FREQ); + _textConfig(&_gridPowerMQTT, KEY_GRID_POWER_MQTT_TOPIC); + _jsyReset.attachCallback([] PUSH_BUTTON_CARD_CB { Mycila::JSY.resetEnergy(); }); + + // mqtt callbacks + + _boolConfig(&_mqtt, KEY_MQTT_ENABLE); + _boolConfig(&_mqttSecured, KEY_MQTT_SECURED); + _textConfig(&_mqttServer, KEY_MQTT_SERVER); + _numConfig(&_mqttPort, KEY_MQTT_PORT); + _textConfig(&_mqttUser, KEY_MQTT_USERNAME); + _passwordConfig(&_mqttPwd, KEY_MQTT_PASSWORD); + _textConfig(&_mqttTopic, KEY_MQTT_TOPIC); + _numConfig(&_mqttPublishInterval, KEY_MQTT_PUBLISH_INTERVAL); + _boolConfig(&_haDiscovery, KEY_HA_DISCOVERY_ENABLE); + _textConfig(&_haDiscoveryTopic, KEY_HA_DISCOVERY_TOPIC); + + // network callbacks + + _textConfig(&_hostname, KEY_HOSTNAME); + _passwordConfig(&_adminPwd, KEY_ADMIN_PASSWORD); + _textConfig(&_wifiSSID, KEY_WIFI_SSID); + _passwordConfig(&_wifiPwd, KEY_WIFI_PASSWORD); + _boolConfig(&_apMode, KEY_AP_MODE_ENABLE); + _textConfig(&_ntpServer, KEY_NTP_SERVER); + _textConfig(&_ntpTimezone, KEY_NTP_TIMEZONE); + _ntpSync.attachCallback([](const char* value) { + const String str = String(value); + const size_t len = str.length(); + const timeval tv = {str.substring(0, len - 3).toInt(), str.substring(len - 3).toInt()}; + Mycila::NTP.sync(&tv); + }); + + // output1 callbacks + + _boolConfig(&_output1AutoBypass, KEY_OUTPUT1_AUTO_BYPASS_ENABLE); + _boolConfig(&_output1AutoDimmer, KEY_OUTPUT1_DIMMER_AUTO); + _boolConfig(&_output1Bypass, KEY_OUTPUT1_RELAY_ENABLE); + _boolConfig(&_output1Dimmer, KEY_OUTPUT1_DIMMER_ENABLE); + _boolConfig(&_output1Temp, KEY_OUTPUT1_TEMP_ENABLE); + _daysConfig(&_output1AutoStartWDays, KEY_OUTPUT1_AUTO_WEEK_DAYS); + _numConfig(&_output1AutoStartTemp, KEY_OUTPUT1_AUTO_START_TEMPERATURE); + _numConfig(&_output1AutoStoptTemp, KEY_OUTPUT1_AUTO_STOP_TEMPERATURE); + _numConfig(&_output1BypassPin, KEY_OUTPUT1_RELAY_PIN); + _numConfig(&_output1DimmerPin, KEY_OUTPUT1_DIMMER_PIN); + _numConfig(&_output1TempPin, KEY_OUTPUT1_TEMP_PIN); + _sliderConfig(&_output1DimmerPowerLimit, KEY_OUTPUT1_DIMMER_LEVEL_LIMIT); + _textConfig(&_output1AutoStartTime, KEY_OUTPUT1_AUTO_START_TIME); + _textConfig(&_output1AutoStoptTime, KEY_OUTPUT1_AUTO_STOP_TIME); + _textConfig(&_output1BypassType, KEY_OUTPUT1_RELAY_TYPE); + _textConfig(&_output1DimmerType, KEY_OUTPUT1_DIMMER_TYPE); + + // output2 callbacks + + _boolConfig(&_output2AutoBypass, KEY_OUTPUT2_AUTO_BYPASS_ENABLE); + _boolConfig(&_output2AutoDimmer, KEY_OUTPUT2_DIMMER_AUTO); + _boolConfig(&_output2Bypass, KEY_OUTPUT2_RELAY_ENABLE); + _boolConfig(&_output2Dimmer, KEY_OUTPUT2_DIMMER_ENABLE); + _boolConfig(&_output2Temp, KEY_OUTPUT2_TEMP_ENABLE); + _daysConfig(&_output2AutoStartWDays, KEY_OUTPUT2_AUTO_WEEK_DAYS); + _numConfig(&_output2AutoStartTemp, KEY_OUTPUT2_AUTO_START_TEMPERATURE); + _numConfig(&_output2AutoStoptTemp, KEY_OUTPUT2_AUTO_STOP_TEMPERATURE); + _numConfig(&_output2BypassPin, KEY_OUTPUT2_RELAY_PIN); + _numConfig(&_output2DimmerPin, KEY_OUTPUT2_DIMMER_PIN); + _numConfig(&_output2TempPin, KEY_OUTPUT2_TEMP_PIN); + _sliderConfig(&_output2DimmerPowerLimit, KEY_OUTPUT2_DIMMER_LEVEL_LIMIT); + _textConfig(&_output2AutoStartTime, KEY_OUTPUT2_AUTO_START_TIME); + _textConfig(&_output2AutoStoptTime, KEY_OUTPUT2_AUTO_STOP_TIME); + _textConfig(&_output2BypassType, KEY_OUTPUT2_RELAY_TYPE); + _textConfig(&_output2DimmerType, KEY_OUTPUT2_DIMMER_TYPE); + + // relay1 callbacks + + _boolConfig(&_relay1, KEY_RELAY1_ENABLE); + _numConfig(&_relay1Pin, KEY_RELAY1_PIN); + _textConfig(&_relay1Type, KEY_RELAY1_TYPE); + _sliderConfig(&_relay1Power, KEY_RELAY1_POWER); + + // relay2 callbacks + + _boolConfig(&_relay2, KEY_RELAY2_ENABLE); + _numConfig(&_relay2Pin, KEY_RELAY2_PIN); + _textConfig(&_relay2Type, KEY_RELAY2_TYPE); + _sliderConfig(&_relay2Power, KEY_RELAY2_POWER); + + // system callbacks + + _boolConfig(&_systemTemp, KEY_SYSTEM_TEMP_ENABLE); + _boolConfig(&_pushButton, KEY_BUTTON_ENABLE); + _boolConfig(&_led, KEY_LIGHTS_ENABLE); + _boolConfig(&_buzzer, KEY_BUZZER_ENABLE); + _numConfig(&_systemTempPin, KEY_SYSTEM_TEMP_PIN); + _numConfig(&_pushButtonPin, KEY_BUTTON_PIN); + _textConfig(&_pushButtonAction, KEY_BUTTON_ACTION); + _numConfig(&_ledGreenPin, KEY_LIGHTS_GREEN_PIN); + _numConfig(&_ledYellowPin, KEY_LIGHTS_YELLOW_PIN); + _numConfig(&_ledRedPin, KEY_LIGHTS_RED_PIN); + _numConfig(&_buzzerPin, KEY_BUZZER_PIN); +#endif + + _update(true); +} + +void YaSolR::WebsiteClass::_sliderConfig(Card* card, const char* key) { + card->attachCallback([key](int value) { + Mycila::Config.set(key, String(MAX(0, value))); + }); +} + +void YaSolR::WebsiteClass::_numConfig(Card* card, const char* key) { +#ifdef APP_VERSION_PRO + card->attachCallback([key](const char* value) { + if (strlen(value) == 0) { + Mycila::Config.unset(key); + } else { + Mycila::Config.set(key, String(strtol(value, nullptr, 10))); + } + }); +#endif +} + +void YaSolR::WebsiteClass::_boolConfig(Card* card, const char* key) { + card->attachCallback([key, card, this](int value) { + card->update(value); + dashboard.refreshCard(card); + Mycila::Config.setBool(key, value); + }); +} + +void YaSolR::WebsiteClass::_textConfig(Card* card, const char* key) { +#ifdef APP_VERSION_PRO + card->attachCallback([key](const char* value) { Mycila::Config.set(key, value); }); +#endif +} + +void YaSolR::WebsiteClass::_daysConfig(Card* card, const char* key) { +#ifdef APP_VERSION_PRO + card->attachCallback([key, card, this](const char* value) { + card->update(value); + dashboard.refreshCard(card); + Mycila::Config.set(key, strlen(value) == 0 ? "none" : value); + }); +#endif +} + +void YaSolR::WebsiteClass::_passwordConfig(Card* card, const char* key) { +#ifdef APP_VERSION_PRO + card->attachCallback([key, card, this](const char* value) { + if (strlen(value) == 0) { + Mycila::Config.unset(key); + } else if (strlen(value) >= 8) { + Mycila::Config.set(key, value); + } else { + card->update(emptyString); + dashboard.refreshCard(card); + } + }); +#endif +} + +void YaSolR::WebsiteClass::_relaySwitch(Card* card, Mycila::Relay* relay) { + card->attachCallback([card, relay, this](int value) { + if (relay->isEnabled()) { + Mycila::RelayManager.tryRelayState(relay, value); + } + card->update(relay->isOn()); + dashboard.refreshCard(card); + }); +} + +void YaSolR::WebsiteClass::_outputRelaySwitch(Card* card, Mycila::RouterOutput* output) { + card->attachCallback([card, output, this](int value) { + if (output->isBypassRelayEnabled()) { + output->tryBypassRelayState(value); + } + card->update(output->isBypassRelayOn()); + dashboard.refreshCard(card); + }); +} + +void YaSolR::WebsiteClass::_outputDimmerSlider(Card* card, Mycila::RouterOutput* output) { + card->attachCallback([card, output, this](int value) { // 0-100 + if (output->getDimmer()->isEnabled()) { + output->tryDimmerLevel(value); + } + card->update(static_cast(output->getDimmer()->getLevel())); + dashboard.refreshCard(card); + }); +} + +namespace YaSolR { + WebsiteClass Website; +} // namespace YaSolR diff --git a/src/YaSolR_Website_Update.cpp b/src/YaSolR_Website_Update.cpp new file mode 100644 index 0000000..da09eaa --- /dev/null +++ b/src/YaSolR_Website_Update.cpp @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#define TAG "WEBSITE" + +void YaSolR::WebsiteClass::_update(bool skipWebSocketPush) { + if (dashboard.isAsyncAccessInProgress()) { + return; + } + + // stats + + Mycila::SystemMemory memory = Mycila::System.getMemory(); + ESPConnectMode mode = ESPConnect.getMode(); + _timeStat.set(Mycila::Time::getLocalStr().c_str()); + _gridFreqStat.set((String(Mycila::Grid.getFrequency()) + " Hz").c_str()); + _gridVoltStat.set((String(Mycila::Grid.getVoltage()) + " V").c_str()); + _netModeStat.set(mode == ESPConnectMode::AP ? "AP" : (mode == ESPConnectMode::STA ? "WiFi" : (mode == ESPConnectMode::ETH ? "Ethernet" : ""))); + _apIPStat.set(ESPConnect.getIPAddress(ESPConnectMode::AP).toString().c_str()); + _apMACStat.set(ESPConnect.getMACAddress(ESPConnectMode::AP).c_str()); + _ethIPStat.set(ESPConnect.getIPAddress(ESPConnectMode::ETH).toString().c_str()); + _ethMACStat.set(ESPConnect.getMACAddress(ESPConnectMode::ETH).c_str()); + _wifiIPStat.set(ESPConnect.getIPAddress(ESPConnectMode::STA).toString().c_str()); + _wifiMACStat.set(ESPConnect.getMACAddress(ESPConnectMode::STA).c_str()); + _heapMemoryUsageStat.set((String(memory.usage) + " %").c_str()); + _heapMemoryUsedStat.set((String(memory.used) + " bytes").c_str()); + _output1SwitchCountStat.set(String(output1BypassRelay.getSwitchCount()).c_str()); + _output2SwitchCountStat.set(String(output2BypassRelay.getSwitchCount()).c_str()); + _relay1SwitchCountStat.set(String(relay1.getSwitchCount()).c_str()); + _relay2SwitchCountStat.set(String(relay2.getSwitchCount()).c_str()); + _systemTempStat.set(systemTemperatureSensor.isValid() ? (String(systemTemperatureSensor.getTemperature()) + " °C").c_str() : ""); + _uptimeStat.set((String(Mycila::System.getUptime()) + " s").c_str()); + _wifiRSSIStat.set((String(ESPConnect.getWiFiRSSI()) + " dBm").c_str()); + _wifiSignalStat.set((String(ESPConnect.getWiFiSignalQuality()) + " %").c_str()); + _wifiSSIDStat.set(ESPConnect.getWiFiSSID().c_str()); +#ifdef APP_VERSION_TRIAL + _trialRemainingStat.set((String(Mycila::Trial.getRemaining()) + " s").c_str()); +#endif + + // home + + switch (output1.getState()) { + case Mycila::RouterOutputState::OUTPUT_DISABLED: + case Mycila::RouterOutputState::OUTPUT_IDLE: + _output1State.update(output1.getStateString(), DASH_STATUS_IDLE); + break; + case Mycila::RouterOutputState::OUTPUT_BYPASS_AUTO: + case Mycila::RouterOutputState::OUTPUT_BYPASS_MANUAL: + _output1State.update(output1.getStateString(), DASH_STATUS_WARNING); + break; + case Mycila::RouterOutputState::OUTPUT_ROUTING: + _output1State.update(output1.getStateString(), DASH_STATUS_SUCCESS); + break; + default: + _output1State.update("Unknown", DASH_STATUS_DANGER); + break; + } + _temperature(&_output1TempState, &output1TemperatureSensor); + _output1BypassSwitch.update(output1.isBypassRelayOn()); + _output1DimmerSlider.update(static_cast(output1Dimmer.getLevel())); + + switch (output2.getState()) { + case Mycila::RouterOutputState::OUTPUT_DISABLED: + case Mycila::RouterOutputState::OUTPUT_IDLE: + _output2State.update(output2.getStateString(), DASH_STATUS_IDLE); + break; + case Mycila::RouterOutputState::OUTPUT_BYPASS_AUTO: + case Mycila::RouterOutputState::OUTPUT_BYPASS_MANUAL: + _output2State.update(output2.getStateString(), DASH_STATUS_WARNING); + break; + case Mycila::RouterOutputState::OUTPUT_ROUTING: + _output2State.update(output2.getStateString(), DASH_STATUS_SUCCESS); + break; + default: + _output2State.update("Unknown", DASH_STATUS_DANGER); + break; + } + _temperature(&_output2TempState, &output2TemperatureSensor); + _output2BypassSwitch.update(output2.isBypassRelayOn()); + _output2DimmerSlider.update(static_cast(output2Dimmer.getLevel())); + + _relay1Switch.update(relay1.isOn()); + _relay2Switch.update(relay2.isOn()); + _gridPower.update(Mycila::Grid.getPower()); + _routerPower.update(Mycila::Router.getTotalRoutedPower()); + _routerPowerFactor.update(Mycila::Router.getTotalPowerFactor() * 100); + _routerEnergy.update(Mycila::Router.getTotalRoutedEnergy()); + _routerTHDi.update(Mycila::Router.getTotalTHDi() * 100); + +#ifdef APP_VERSION_PRO + // management tab + + _debugMode.update(Mycila::Config.getBool(KEY_DEBUG_ENABLE)); + + // health + + const bool gridOnline = Mycila::Grid.isOnline(); + _status(&_stateBuzzer, KEY_BUZZER_ENABLE, Mycila::Buzzer.isEnabled()); + _status(&_stateDisplay, KEY_DISPLAY_ENABLE, Mycila::EasyDisplay.isEnabled()); + _status(&_stateJSY, KEY_JSY_ENABLE, Mycila::JSY.isEnabled(), gridOnline, "No grid voltage"); + _status(&_stateLEDs, KEY_LIGHTS_ENABLE, Mycila::Lights.isEnabled()); + _status(&_stateMQTT, KEY_MQTT_ENABLE, Mycila::MQTT.isEnabled(), Mycila::MQTT.isConnected(), Mycila::MQTT.getDisconnectReason() ? Mycila::MQTT.getDisconnectReason() : "Disconnected"); + _status(&_stateOutput1AutoBypass, KEY_OUTPUT1_AUTO_BYPASS_ENABLE, true, Mycila::NTP.isSynced(), "Waiting for time sync"); + _status(&_stateOutput1AutoDimmer, KEY_OUTPUT1_DIMMER_AUTO, true, output1Dimmer.isEnabled(), "Dimmer disabled"); + _status(&_stateOutput1Bypass, KEY_OUTPUT1_RELAY_ENABLE, output1.isBypassRelayEnabled()); + _status(&_stateOutput1Dimmer, KEY_OUTPUT1_DIMMER_ENABLE, output1Dimmer.isEnabled(), gridOnline, "No grid voltage"); + _status(&_stateOutput1Temp, KEY_OUTPUT1_TEMP_ENABLE, output1TemperatureSensor.isEnabled(), output1TemperatureSensor.isValid(), "Read error"); + _status(&_stateOutput2AutoBypass, KEY_OUTPUT2_AUTO_BYPASS_ENABLE, true, Mycila::NTP.isSynced(), "Waiting for time sync"); + _status(&_stateOutput2AutoDimmer, KEY_OUTPUT2_DIMMER_AUTO, true, output1Dimmer.isEnabled(), "Dimmer disabled"); + _status(&_stateOutput2Bypass, KEY_OUTPUT2_RELAY_ENABLE, output2.isBypassRelayEnabled()); + _status(&_stateOutput2Dimmer, KEY_OUTPUT2_DIMMER_ENABLE, output2Dimmer.isEnabled(), gridOnline, "No grid voltage"); + _status(&_stateOutput2Temp, KEY_OUTPUT2_TEMP_ENABLE, output2TemperatureSensor.isEnabled(), output2TemperatureSensor.isValid(), "Read error"); + _status(&_statePushButton, KEY_BUTTON_ENABLE, Mycila::Button.isEnabled()); + _status(&_stateRelay1, KEY_RELAY1_ENABLE, relay1.isEnabled()); + _status(&_stateRelay2, KEY_RELAY2_ENABLE, relay2.isEnabled()); + _status(&_stateSystemTemp, KEY_SYSTEM_TEMP_ENABLE, systemTemperatureSensor.isEnabled(), systemTemperatureSensor.isValid(), "Read error"); + _status(&_stateZCD, KEY_ZCD_ENABLE, Mycila::ZCD.isEnabled(), gridOnline, "No grid voltage"); + // ntp + if (Mycila::NTP.isSynced() && !Mycila::NTP.getTimezoneInfo().isEmpty()) + _stateNTP.update("Enabled", DASH_STATUS_SUCCESS); + else if (Mycila::NTP.getTimezoneInfo().isEmpty()) + _stateNTP.update("Invalid timezone", DASH_STATUS_DANGER); + else if (!Mycila::NTP.isSynced()) + _stateNTP.update("Waiting for time sync", DASH_STATUS_WARNING); + // _stateGridPowerMQTT + if (Mycila::Config.get(KEY_GRID_POWER_MQTT_TOPIC).isEmpty()) + _stateGridPowerMQTT.update("Disabled", DASH_STATUS_IDLE); + else if (Mycila::Grid.isMQTTGridPowerExpired()) + _stateGridPowerMQTT.update("Expired", DASH_STATUS_WARNING); + else + _stateGridPowerMQTT.update("Enabled", DASH_STATUS_SUCCESS); + + // pinout live + + std::map pinout = {}; + _pinout(&_pinLiveBuzzer, Mycila::Buzzer.getPin(), &pinout); + _pinout(&_pinLiveDisplayClock, Mycila::EasyDisplay.getClockPin(), &pinout); + _pinout(&_pinLiveDisplayData, Mycila::EasyDisplay.getDataPin(), &pinout); + _pinout(&_pinLiveJsyRX, Mycila::JSY.getRXPin(), &pinout); + _pinout(&_pinLiveJsyTX, Mycila::JSY.getTXPin(), &pinout); + _pinout(&_pinLiveLEDGreen, Mycila::Lights.getGreenPin(), &pinout); + _pinout(&_pinLiveLEDRed, Mycila::Lights.getRedPin(), &pinout); + _pinout(&_pinLiveLEDYellow, Mycila::Lights.getYellowPin(), &pinout); + _pinout(&_pinLiveOutput1Bypass, output1BypassRelay.getPin(), &pinout); + _pinout(&_pinLiveOutput1Dimmer, output1Dimmer.getPin(), &pinout); + _pinout(&_pinLiveOutput1Temp, output1TemperatureSensor.getPin(), &pinout); + _pinout(&_pinLiveOutput2Bypass, output2BypassRelay.getPin(), &pinout); + _pinout(&_pinLiveOutput2Dimmer, output2Dimmer.getPin(), &pinout); + _pinout(&_pinLiveOutput2Temp, output2TemperatureSensor.getPin(), &pinout); + _pinout(&_pinLivePushButton, Mycila::Button.getPin(), &pinout); + _pinout(&_pinLiveRelay1, relay1.getPin(), &pinout); + _pinout(&_pinLiveRelay2, relay2.getPin(), &pinout); + _pinout(&_pinLiveSystemTemp, systemTemperatureSensor.getPin(), &pinout); + _pinout(&_pinLiveZCD, Mycila::ZCD.getPin(), &pinout); + pinout.clear(); + + // pinout config + + _pinout(&_pinConfigRelay1, Mycila::Config.get(KEY_RELAY1_PIN).toInt(), &pinout); + _pinout(&_pinConfigOutput1Dimmer, Mycila::Config.get(KEY_OUTPUT1_DIMMER_PIN).toInt(), &pinout); + _pinout(&_pinConfigOutput1Bypass, Mycila::Config.get(KEY_OUTPUT1_RELAY_PIN).toInt(), &pinout); + _pinout(&_pinConfigOutput1Temp, Mycila::Config.get(KEY_OUTPUT1_TEMP_PIN).toInt(), &pinout); + _pinout(&_pinConfigRelay2, Mycila::Config.get(KEY_RELAY2_PIN).toInt(), &pinout); + _pinout(&_pinConfigOutput2Dimmer, Mycila::Config.get(KEY_OUTPUT2_DIMMER_PIN).toInt(), &pinout); + _pinout(&_pinConfigOutput2Bypass, Mycila::Config.get(KEY_OUTPUT2_RELAY_PIN).toInt(), &pinout); + _pinout(&_pinConfigOutput2Temp, Mycila::Config.get(KEY_OUTPUT2_TEMP_PIN).toInt(), &pinout); + _pinout(&_pinConfigZCD, Mycila::Config.get(KEY_ZCD_PIN).toInt(), &pinout); + _pinout(&_pinConfigJsyRX, Mycila::Config.get(KEY_JSY_RX_PIN).toInt(), &pinout); + _pinout(&_pinConfigJsyTX, Mycila::Config.get(KEY_JSY_TX_PIN).toInt(), &pinout); + _pinout(&_pinConfigSystemTemp, Mycila::Config.get(KEY_SYSTEM_TEMP_PIN).toInt(), &pinout); + _pinout(&_pinConfigDisplayClock, Mycila::Config.get(KEY_DISPLAY_CLOCK_PIN).toInt(), &pinout); + _pinout(&_pinConfigDisplayData, Mycila::Config.get(KEY_DISPLAY_DATA_PIN).toInt(), &pinout); + _pinout(&_pinConfigPushButton, Mycila::Config.get(KEY_BUTTON_PIN).toInt(), &pinout); + _pinout(&_pinConfigBuzzer, Mycila::Config.get(KEY_BUZZER_PIN).toInt(), &pinout); + _pinout(&_pinConfigLEDGreen, Mycila::Config.get(KEY_LIGHTS_GREEN_PIN).toInt(), &pinout); + _pinout(&_pinConfigLEDYellow, Mycila::Config.get(KEY_LIGHTS_YELLOW_PIN).toInt(), &pinout); + _pinout(&_pinConfigLEDRed, Mycila::Config.get(KEY_LIGHTS_RED_PIN).toInt(), &pinout); + pinout.clear(); + + // display + + _display.update(Mycila::Config.getBool(KEY_DISPLAY_ENABLE)); + _displayClockPin.update(String(Mycila::Config.get(KEY_DISPLAY_CLOCK_PIN).toInt())); + _displayDataPin.update(String(Mycila::Config.get(KEY_DISPLAY_DATA_PIN).toInt())); + _displayType.update(Mycila::Config.get(KEY_DISPLAY_TYPE)); + _displayRotation.update(String(Mycila::Config.get(KEY_DISPLAY_ROTATION).toInt()) + "°"); + _displayPowerSaveDelay.update(String(Mycila::Config.get(KEY_DISPLAY_POWER_SAVE_DELAY).toInt())); + + // electricity + + _zcd.update(Mycila::Config.getBool(KEY_ZCD_ENABLE)); + _zcdPin.update(String(Mycila::Config.get(KEY_ZCD_PIN).toInt())); + _jsy.update(Mycila::Config.getBool(KEY_JSY_ENABLE)); + _jsyRXPin.update(String(Mycila::Config.get(KEY_JSY_RX_PIN).toInt())); + _jsyTXPin.update(String(Mycila::Config.get(KEY_JSY_TX_PIN).toInt())); + _gridFreq.update(Mycila::Config.get(KEY_GRID_FREQ).toInt() == 60 ? "60 Hz" : "50 Hz"); + _gridPowerMQTT.update(Mycila::Config.get(KEY_GRID_POWER_MQTT_TOPIC)); + + // mqtt + + _mqtt.update(Mycila::Config.getBool(KEY_MQTT_ENABLE)); + _mqttSecured.update(Mycila::Config.getBool(KEY_MQTT_SECURED)); + _mqttServer.update(Mycila::Config.get(KEY_MQTT_SERVER)); + _mqttPort.update(String(Mycila::Config.get(KEY_MQTT_PORT).toInt())); + _mqttUser.update(Mycila::Config.get(KEY_MQTT_USERNAME)); + String pwd = Mycila::Config.get(KEY_MQTT_PASSWORD); + _mqttPwd.update(pwd.isEmpty() ? "" : HIDDEN_PWD); + _mqttTopic.update(Mycila::Config.get(KEY_MQTT_TOPIC)); + _mqttPublishInterval.update(String(Mycila::Config.get(KEY_MQTT_PUBLISH_INTERVAL).toInt())); + _haDiscovery.update(Mycila::Config.getBool(KEY_HA_DISCOVERY_ENABLE)); + _haDiscoveryTopic.update(Mycila::Config.get(KEY_HA_DISCOVERY_TOPIC)); + + // network + + _hostname.update(Mycila::Config.get(KEY_HOSTNAME)); + pwd = Mycila::Config.get(KEY_ADMIN_PASSWORD); + _adminPwd.update(pwd.isEmpty() ? "" : HIDDEN_PWD); + _wifiSSID.update(Mycila::Config.get(KEY_WIFI_SSID)); + pwd = Mycila::Config.get(KEY_WIFI_PASSWORD); + _wifiPwd.update(pwd.isEmpty() ? "" : HIDDEN_PWD); + _apMode.update(Mycila::Config.getBool(KEY_AP_MODE_ENABLE)); + _ntpServer.update(Mycila::Config.get(KEY_NTP_SERVER)); + _ntpTimezone.update(Mycila::Config.get(KEY_NTP_TIMEZONE)); + + // output1 + + _output1AutoBypass.update(Mycila::Config.getBool(KEY_OUTPUT1_AUTO_BYPASS_ENABLE)); + _output1AutoDimmer.update(Mycila::Config.getBool(KEY_OUTPUT1_DIMMER_AUTO)); + _output1AutoStartTemp.update(String(Mycila::Config.get(KEY_OUTPUT1_AUTO_START_TEMPERATURE).toInt())); + _output1AutoStartTime.update(Mycila::Config.get(KEY_OUTPUT1_AUTO_START_TIME)); + _output1AutoStartWDays.update(Mycila::Config.get(KEY_OUTPUT1_AUTO_WEEK_DAYS)); + _output1AutoStoptTemp.update(String(Mycila::Config.get(KEY_OUTPUT1_AUTO_STOP_TEMPERATURE).toInt())); + _output1AutoStoptTime.update(Mycila::Config.get(KEY_OUTPUT1_AUTO_STOP_TIME)); + _output1Bypass.update(Mycila::Config.getBool(KEY_OUTPUT1_RELAY_ENABLE)); + _output1BypassPin.update(String(Mycila::Config.get(KEY_OUTPUT1_RELAY_PIN).toInt())); + _output1BypassType.update(Mycila::Config.get(KEY_OUTPUT1_RELAY_TYPE)); + _output1Dimmer.update(Mycila::Config.getBool(KEY_OUTPUT1_DIMMER_ENABLE)); + _output1DimmerPin.update(String(Mycila::Config.get(KEY_OUTPUT1_DIMMER_PIN).toInt())); + _output1DimmerType.update(Mycila::Config.get(KEY_OUTPUT1_DIMMER_TYPE)); + _output1DimmerPowerLimit.update(static_cast(Mycila::Config.get(KEY_OUTPUT1_DIMMER_LEVEL_LIMIT).toInt())); + _output1Temp.update(Mycila::Config.getBool(KEY_OUTPUT1_TEMP_ENABLE)); + _output1TempPin.update(String(Mycila::Config.get(KEY_OUTPUT1_TEMP_PIN).toInt())); + + // output2 + + _output2AutoBypass.update(Mycila::Config.getBool(KEY_OUTPUT2_AUTO_BYPASS_ENABLE)); + _output2AutoDimmer.update(Mycila::Config.getBool(KEY_OUTPUT2_DIMMER_AUTO)); + _output2AutoStartTemp.update(String(Mycila::Config.get(KEY_OUTPUT2_AUTO_START_TEMPERATURE).toInt())); + _output2AutoStartTime.update(Mycila::Config.get(KEY_OUTPUT2_AUTO_START_TIME)); + _output2AutoStartWDays.update(Mycila::Config.get(KEY_OUTPUT2_AUTO_WEEK_DAYS)); + _output2AutoStoptTemp.update(String(Mycila::Config.get(KEY_OUTPUT2_AUTO_STOP_TEMPERATURE).toInt())); + _output2AutoStoptTime.update(Mycila::Config.get(KEY_OUTPUT2_AUTO_STOP_TIME)); + _output2Bypass.update(Mycila::Config.getBool(KEY_OUTPUT2_RELAY_ENABLE)); + _output2BypassPin.update(String(Mycila::Config.get(KEY_OUTPUT2_RELAY_PIN).toInt())); + _output2BypassType.update(Mycila::Config.get(KEY_OUTPUT2_RELAY_TYPE)); + _output2Dimmer.update(Mycila::Config.getBool(KEY_OUTPUT2_DIMMER_ENABLE)); + _output2DimmerPin.update(String(Mycila::Config.get(KEY_OUTPUT2_DIMMER_PIN).toInt())); + _output2DimmerType.update(Mycila::Config.get(KEY_OUTPUT2_DIMMER_TYPE)); + _output2DimmerPowerLimit.update(static_cast(Mycila::Config.get(KEY_OUTPUT2_DIMMER_LEVEL_LIMIT).toInt())); + _output2Temp.update(Mycila::Config.getBool(KEY_OUTPUT2_TEMP_ENABLE)); + _output2TempPin.update(String(Mycila::Config.get(KEY_OUTPUT2_TEMP_PIN).toInt())); + + // relay1 + + _relay1.update(Mycila::Config.getBool(KEY_RELAY1_ENABLE)); + _relay1Pin.update(String(Mycila::Config.get(KEY_RELAY1_PIN).toInt())); + _relay1Type.update(Mycila::Config.get(KEY_RELAY1_TYPE)); + _relay1Power.update(static_cast(Mycila::Config.get(KEY_RELAY1_POWER).toInt())); + + // relay2 + + _relay2.update(Mycila::Config.getBool(KEY_RELAY2_ENABLE)); + _relay2Pin.update(String(Mycila::Config.get(KEY_RELAY2_PIN).toInt())); + _relay2Type.update(Mycila::Config.get(KEY_RELAY2_TYPE)); + _relay2Power.update(static_cast(Mycila::Config.get(KEY_RELAY2_POWER).toInt())); + + // system + + _systemTemp.update(Mycila::Config.getBool(KEY_SYSTEM_TEMP_ENABLE)); + _pushButton.update(Mycila::Config.getBool(KEY_BUTTON_ENABLE)); + _led.update(Mycila::Config.getBool(KEY_LIGHTS_ENABLE)); + _buzzer.update(Mycila::Config.getBool(KEY_BUZZER_ENABLE)); + _systemTempPin.update(String(Mycila::Config.get(KEY_SYSTEM_TEMP_PIN).toInt())); + _pushButtonPin.update(String(Mycila::Config.get(KEY_BUTTON_PIN).toInt())); + _pushButtonAction.update(Mycila::Config.get(KEY_BUTTON_ACTION)); + _ledGreenPin.update(String(Mycila::Config.get(KEY_LIGHTS_GREEN_PIN).toInt())); + _ledYellowPin.update(String(Mycila::Config.get(KEY_LIGHTS_YELLOW_PIN).toInt())); + _ledRedPin.update(String(Mycila::Config.get(KEY_LIGHTS_RED_PIN).toInt())); + _buzzerPin.update(String(Mycila::Config.get(KEY_BUZZER_PIN).toInt())); +#endif + + if (!skipWebSocketPush && dashboard.hasClient()) { + dashboard.sendUpdates(); + } +} + +void YaSolR::WebsiteClass::_temperature(Card* card, Mycila::TemperatureSensor* sensor) { + if (!sensor->isEnabled()) { + card->update("Disabled", ""); + } else if (!sensor->isValid()) { + card->update("Pending...", ""); + } else { + card->update(sensor->getTemperature(), "°C"); + } +} + +void YaSolR::WebsiteClass::_status(Card* card, const char* key, bool enabled, bool active, const char* err) { + const bool configEnabled = Mycila::Config.getBool(key); + if (!configEnabled) + card->update("Disabled", DASH_STATUS_IDLE); + else if (!enabled) + card->update("Unable to start", DASH_STATUS_DANGER); + else if (!active) + card->update(err, DASH_STATUS_WARNING); + else + card->update("Enabled", DASH_STATUS_SUCCESS); +} + +void YaSolR::WebsiteClass::_pinout(Card* card, int32_t pin, std::map* pinout) { + if (pin == GPIO_NUM_NC) { + card->update("Disabled", DASH_STATUS_IDLE); + } else if (pinout->find(pin) != pinout->end()) { + String v = String(pin) + " (Duplicate)"; + pinout->at(pin)->update(v, DASH_STATUS_WARNING); + card->update(v, DASH_STATUS_WARNING); + } else if (!GPIO_IS_VALID_GPIO(pin)) { + pinout->insert(std::pair(pin, card)); + card->update(String(pin) + " (Invalid)", DASH_STATUS_WARNING); + } else if (!GPIO_IS_VALID_OUTPUT_GPIO(pin)) { + pinout->insert(std::pair(pin, card)); + card->update(String(pin) + " (Input Only)", DASH_STATUS_SUCCESS); + } else { + pinout->insert(std::pair(pin, card)); + card->update(String(pin) + " (I/O)", DASH_STATUS_SUCCESS); + } +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..c00db11 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#ifdef YASOLR_DISABLE_BROWNOUT_DETECTOR +#include "soc/rtc_cntl_reg.h" +#include "soc/soc.h" +#endif + +#include + +#define TAG "YASOLR" + +AsyncWebServer webServer(80); +ESPDash dashboard = ESPDash(&webServer, "/dashboard", false); + +Mycila::Dimmer output1Dimmer(NAME_OUTPUT1); +Mycila::Dimmer output2Dimmer(NAME_OUTPUT2); + +Mycila::Relay relay1(NAME_RELAY1); +Mycila::Relay relay2(NAME_RELAY2); +Mycila::Relay output1BypassRelay(NAME_OUTPUT1); +Mycila::Relay output2BypassRelay(NAME_OUTPUT2); + +Mycila::TemperatureSensor output1TemperatureSensor(NAME_OUTPUT1); +Mycila::TemperatureSensor output2TemperatureSensor(NAME_OUTPUT2); +Mycila::TemperatureSensor systemTemperatureSensor(NAME_SYSTEM); + +Mycila::RouterOutput output1(NAME_OUTPUT1, &output1Dimmer, &output1TemperatureSensor, &output1BypassRelay); +Mycila::RouterOutput output2(NAME_OUTPUT2, &output2Dimmer, &output2TemperatureSensor, &output2BypassRelay); + +void loop() { + // TODO: Task Manager: auto yield, task.managedBy(&tm), profiler with json output, status json output of task status (enable / disabled / last time / last elapsed, etc.) + if (autoRelayTask.tryRun()) + yield(); + if (buttonTask.tryRun()) + yield(); + if (configureButtonTask.tryRun()) + yield(); + if (configureBuzzerTask.tryRun()) + yield(); + if (configureDisplayTask.tryRun()) + yield(); + if (configureGridTask.tryRun()) + yield(); + if (configureJSYTask.tryRun()) + yield(); + if (configureLightsTask.tryRun()) + yield(); + if (configureMQTTTask.tryRun()) + yield(); + if (configureNetworkTask.tryRun()) + yield(); + if (configureNTPTask.tryRun()) + yield(); + if (configureOutput1BypassRelayTask.tryRun()) + yield(); + if (configureOutput1DimmerTask.tryRun()) + yield(); + if (configureOutput1TemperatureSensorTask.tryRun()) + yield(); + if (configureOutput2BypassRelayTask.tryRun()) + yield(); + if (configureOutput2DimmerTask.tryRun()) + yield(); + if (configureOutput2TemperatureSensorTask.tryRun()) + yield(); + if (configureRelay1Task.tryRun()) + yield(); + if (configureRelay2Task.tryRun()) + yield(); + if (configureSystemTemperatureSensorTask.tryRun()) + yield(); + if (configureTaskMonitorTask.tryRun()) + yield(); + if (configureZCDTask.tryRun()) + yield(); + if (displayTask.tryRun()) + yield(); + if (espConnectTask.tryRun()) + yield(); + if (lightsTask.tryRun()) + yield(); + if (mqttTask.tryRun()) + yield(); + if (otaPrepareTask.tryRun()) + yield(); + if (output1AutoBypassTask.tryRun()) + yield(); + if (output1DimmerLimitTask.tryRun()) + yield(); + if (output1TemperatureTask.tryRun()) + yield(); + if (output2AutoBypassTask.tryRun()) + yield(); + if (output2DimmerLimitTask.tryRun()) + yield(); + if (output2TemperatureTask.tryRun()) + yield(); + if (resetTask.tryRun()) + yield(); + if (restartTask.tryRun()) + yield(); + if (stackMonitorTask.tryRun()) + yield(); + if (startNetworkServicesTask.tryRun()) + yield(); + if (stopNetworkServicesTask.tryRun()) + yield(); + if (systemTemperatureTask.tryRun()) + yield(); + if (websiteTask.tryRun()) + yield(); + +#ifdef APP_VERSION_TRIAL + if (trialTask.tryRun()) + yield(); +#endif +} + +// I/O loop + +void ioTask(void* params) { + while (!restartTask.isEnabled() && !resetTask.isEnabled()) { + if (haDiscoTask.tryRun()) + yield(); + if (publisherTask.tryRun()) + yield(); + } + vTaskDelete(NULL); +} + +// setup +void setup() { +#ifdef YASOLR_DISABLE_BROWNOUT_DETECTOR + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); +#endif + + Serial.begin(YASOLR_SERIAL_BAUDRATE, SERIAL_8N1, -1, -1, false, 5000); +#if ARDUINO_USB_CDC_ON_BOOT + Serial.setTxTimeoutMs(0); + delay(100); +#endif + + // logger + Mycila::Logger.getOutputs().reserve(2); + Mycila::Logger.forwardTo(&Serial); + Mycila::Logger.info(TAG, "Booting %s %s %s...", Mycila::AppInfo.name.c_str(), Mycila::AppInfo.model.c_str(), Mycila::AppInfo.version.c_str()); + + // system + Mycila::System.begin(); + + // trial +#ifdef APP_VERSION_TRIAL + Mycila::Trial.begin(); +#endif + + // load config + YaSolR::YaSolR.begin(); + + // router + Mycila::Router.setOutputs({&output1, &output2}); + + // relay manager + Mycila::RelayManager.setRelays({&relay1, &relay2}); + + // start logging +#ifdef APP_VERSION_PRO + WebSerial.begin(&webServer, "/console", YASOLR_ADMIN_USERNAME, Mycila::Config.get(KEY_ADMIN_PASSWORD)); + Mycila::Logger.forwardTo(&WebSerial); +#endif + + Mycila::Logger.info(TAG, "Starting %s %s %s...", Mycila::AppInfo.name.c_str(), Mycila::AppInfo.model.c_str(), Mycila::AppInfo.version.c_str()); + + // order must be respected below + + configureLightsTask.forceRun(); + Mycila::Lights.set(Mycila::LightState::ON, Mycila::LightState::OFF, Mycila::LightState::ON); + configureZCDTask.forceRun(); + configureOutput1DimmerTask.forceRun(); + configureOutput2DimmerTask.forceRun(); + configureJSYTask.forceRun(); + + // any order below + + configureButtonTask.forceRun(); + configureBuzzerTask.forceRun(); + configureDisplayTask.forceRun(); + configureGridTask.forceRun(); + configureNTPTask.forceRun(); + configureOutput1BypassRelayTask.forceRun(); + configureOutput1TemperatureSensorTask.forceRun(); + configureOutput2BypassRelayTask.forceRun(); + configureOutput2TemperatureSensorTask.forceRun(); + configureRelay1Task.forceRun(); + configureRelay2Task.forceRun(); + configureSystemTemperatureSensorTask.forceRun(); + configureTaskMonitorTask.forceRun(); + + configureNetworkTask.forceRun(); + + // STARTUP READY! + Mycila::Logger.info(TAG, "Started %s %s %s", Mycila::AppInfo.name.c_str(), Mycila::AppInfo.model.c_str(), Mycila::AppInfo.version.c_str()); + + assert(xTaskCreateUniversal(ioTask, "ioTask", YASOLR_IO_ASYNC_STACK_SIZE, nullptr, 1, nullptr, xPortGetCoreID()) == pdPASS); +} diff --git a/test/config.txt b/test/config.txt new file mode 100644 index 0000000..53107c7 --- /dev/null +++ b/test/config.txt @@ -0,0 +1,10 @@ +tmp_ext_enable=true +tmp_sys_enable=true +ha_disco_topic=homeassistant/discovery +mqtt_enable=true +ntp_enable=true +portal_timeout=120 +rel_bp_enable=true +rel_ext1_enable=true +timezone=Europe/Paris +wifi_timeout=10 diff --git a/test/init.sh b/test/init.sh new file mode 100755 index 0000000..c1b0d21 --- /dev/null +++ b/test/init.sh @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +post /api/config/restore -F "data=@${DIR}/config.txt" diff --git a/test/test-all.sh b/test/test-all.sh new file mode 100755 index 0000000..4575b21 --- /dev/null +++ b/test/test-all.sh @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +set -eE -o functrace + +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) + +# read-only +"${DIR}/test-api.sh" $@ +"${DIR}/test-api-app.sh" $@ +"${DIR}/test-api-config.sh" $@ +"${DIR}/test-api-grid.sh" $@ +"${DIR}/test-api-network.sh" $@ + +# read-write +"${DIR}/test-api-mqtt.sh" $@ +"${DIR}/test-api-ntp.sh" $@ +"${DIR}/test-api-relays.sh" $@ +"${DIR}/test-api-router.sh" $@ +"${DIR}/test-api-system.sh" $@ diff --git a/test/test-api-app.sh b/test/test-api-app.sh new file mode 100755 index 0000000..cb77c56 --- /dev/null +++ b/test/test-api-app.sh @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(keys /api/app)" "debug,firmware,id,manufacturer,model,name,trial,version" +assert "$(value /api/app 'name')" "YaSolR" +assert "$(value /api/app 'manufacturer')" "Mathieu Carbou" +assert "$(value /api/app 'trial')" "false" + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api-config.sh b/test/test-api-config.sh new file mode 100755 index 0000000..1c4cd6d --- /dev/null +++ b/test/test-api-config.sh @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(keys /api/config)" "admin_pwd,ap_mode_enable,button_action,button_enable,button_pin,buzzer_enable,buzzer_pin,debug_enable,dp_clock_pin,dp_data_pin,dp_enable,dp_powsave,dp_rotation,dp_type,grid_freq,grid_pow_mqtt,ha_disco_enable,ha_disco_topic,hostname,jsy_enable,jsy_rx_pin,jsy_tx_pin,lights_enable,lights_g_pin,lights_r_pin,lights_y_pin,mqtt_enable,mqtt_port,mqtt_pub_itvl,mqtt_pwd,mqtt_secure,mqtt_server,mqtt_topic,mqtt_user,ntp_server,ntp_timezone,out1_aut_enable,out1_bp_enable,out1_bp_pin,out1_bp_type,out1_dim_auto,out1_dim_enable,out1_dim_limit,out1_dim_pin,out1_dim_type,out1_end_time,out1_start_time,out1_tmp_enable,out1_tmp_high,out1_tmp_low,out1_tmp_pin,out1_wdays,out2_aut_enable,out2_bp_enable,out2_bp_pin,out2_bp_type,out2_dim_auto,out2_dim_enable,out2_dim_limit,out2_dim_pin,out2_dim_type,out2_end_time,out2_start_time,out2_tmp_enable,out2_tmp_high,out2_tmp_low,out2_tmp_pin,out2_wdays,portal_timeout,relay1_enable,relay1_pin,relay1_power,relay1_type,relay2_enable,relay2_pin,relay2_power,relay2_type,sys_tmp_enable,sys_tmp_pin,wifi_pwd,wifi_ssid,wifi_timeout,zcd_enable,zcd_pin" +assert "$(value /api/config 'admin_pwd')" "" +assert "$(value /api/config 'ap_mode_enable')" "false" +assert "$(value /api/config 'button_action')" "restart" +assert "$(value /api/config 'button_enable')" "true" +assert "$(value /api/config 'button_pin')" "23" +assert "$(value /api/config 'buzzer_enable')" "false" +assert "$(value /api/config 'buzzer_pin')" "19" +assert "$(value /api/config 'debug_enable')" "true" +assert "$(value /api/config 'dp_clock_pin')" "22" +assert "$(value /api/config 'dp_data_pin')" "21" +assert "$(value /api/config 'dp_enable')" "true" +assert "$(value /api/config 'dp_powsave')" "0" +assert "$(value /api/config 'dp_rotation')" "0" +assert "$(value /api/config 'dp_type')" "SH1106" +assert "$(value /api/config 'grid_pow_mqtt')" "homeassistant/states/sensor/grid_power/state" +assert "$(value /api/config 'ha_disco_enable')" "true" +assert "$(value /api/config 'ha_disco_topic')" "homeassistant/discovery" +assert "$(value /api/config 'hostname')" "yasolr-a1c48" +assert "$(value /api/config 'jsy_enable')" "true" +assert "$(value /api/config 'jsy_rx_pin')" "17" +assert "$(value /api/config 'jsy_tx_pin')" "16" +assert "$(value /api/config 'lights_enable')" "true" +assert "$(value /api/config 'lights_g_pin')" "0" +assert "$(value /api/config 'lights_r_pin')" "15" +assert "$(value /api/config 'lights_y_pin')" "2" +assert "$(value /api/config 'mqtt_enable')" "true" +assert "$(value /api/config 'mqtt_port')" "1883" +assert "$(value /api/config 'mqtt_pub_itvl')" "5" +assert "$(value /api/config 'mqtt_pwd')" "********" +assert "$(value /api/config 'mqtt_secure')" "false" +assert "$(value /api/config 'mqtt_user')" "homeassistant" +assert "$(value /api/config 'ntp_server')" "pool.ntp.org" +assert "$(value /api/config 'ntp_timezone')" "Europe/Paris" +assert "$(value /api/config 'out1_aut_enable')" "true", "false" +assert "$(value /api/config 'out1_bp_enable')" "true" +assert "$(value /api/config 'out1_bp_pin')" "32" +assert "$(value /api/config 'out1_bp_type')" "NO" +assert "$(value /api/config 'out1_dim_enable')" "true" +assert "$(value /api/config 'out1_dim_limit')" "100" +assert "$(value /api/config 'out1_dim_pin')" "25" +assert "$(value /api/config 'out1_dim_type')" "TRIAC" +assert "$(value /api/config 'out1_end_time')" "06:00" +assert "$(value /api/config 'out1_start_time')" "22:00" +assert "$(value /api/config 'out1_tmp_enable')" "true" +assert "$(value /api/config 'out1_tmp_high')" "60" +assert "$(value /api/config 'out1_tmp_low')" "50" +assert "$(value /api/config 'out1_tmp_pin')" "18" +assert "$(value /api/config 'out1_wdays')" "sun,mon,tue,wed,thu,fri,sat" +assert "$(value /api/config 'out2_aut_enable')" "true" +assert "$(value /api/config 'out2_bp_enable')" "true" +assert "$(value /api/config 'out2_bp_pin')" "33" +assert "$(value /api/config 'out2_bp_type')" "NO" +assert "$(value /api/config 'out2_dim_enable')" "true" +assert "$(value /api/config 'out2_dim_limit')" "100" +assert "$(value /api/config 'out2_dim_pin')" "26" +assert "$(value /api/config 'out2_dim_type')" "TRIAC" +assert "$(value /api/config 'out2_end_time')" "06:00" +assert "$(value /api/config 'out2_start_time')" "22:00" +assert "$(value /api/config 'out2_tmp_enable')" "true" +assert "$(value /api/config 'out2_tmp_high')" "60" +assert "$(value /api/config 'out2_tmp_low')" "50" +assert "$(value /api/config 'out2_tmp_pin')" "5" +assert "$(value /api/config 'out2_wdays')" "sun,mon,tue,wed,thu,fri,sat" +assert "$(value /api/config 'portal_timeout')" "180" +assert "$(value /api/config 'relay1_enable')" "true" +assert "$(value /api/config 'relay1_pin')" "13" +assert "$(value /api/config 'relay1_type')" "NO" +assert "$(value /api/config 'relay2_enable')" "true" +assert "$(value /api/config 'relay2_pin')" "12" +assert "$(value /api/config 'relay2_type')" "NO" +assert "$(value /api/config 'sys_tmp_enable')" "true" +assert "$(value /api/config 'sys_tmp_pin')" "4" +assert "$(value /api/config 'wifi_pwd')" "" +assert "$(value /api/config 'wifi_ssid')" "IoT" +assert "$(value /api/config 'wifi_timeout')" "15" +assert "$(value /api/config 'zcd_enable')" "true" +assert "$(value /api/config 'zcd_pin')" "35" +check "$(value /api/config 'grid_freq')" "!=60" + +assert "$(body /api/config/backup | wc -l | xargs)" "84" + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api-grid.sh b/test/test-api-grid.sh new file mode 100755 index 0000000..4161adb --- /dev/null +++ b/test/test-api-grid.sh @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(keys /api/grid)" "frequency,jsy,online,power,voltage,zcd" + +check "$(value /api/grid 'frequency')" ">48" +check "$(value /api/grid 'frequency')" "<52" +assert "$(value /api/grid 'online')" "true" +check "$(value /api/grid 'power')" "!=0" +check "$(value /api/grid 'voltage')" ">200" + +check "$(value /api/grid 'jsy.current1')" ">=0" +check "$(value /api/grid 'jsy.current2')" ">=0" +assert "$(value /api/grid 'jsy.enabled')" "true" +check "$(value /api/grid 'jsy.energy1')" ">=0" +check "$(value /api/grid 'jsy.energy2')" ">=0" +check "$(value /api/grid 'jsy.energyReturned1')" "==0" +check "$(value /api/grid 'jsy.energyReturned2')" "==0" +check "$(value /api/grid 'jsy.frequency')" ">48" +check "$(value /api/grid 'jsy.frequency')" "<52" +check "$(value /api/grid 'jsy.power_factor1')" ">=0" +check "$(value /api/grid 'jsy.power_factor2')" ">=0" +check "$(value /api/grid 'jsy.power1')" ">=0" +check "$(value /api/grid 'jsy.power2')" ">=0" +check "$(value /api/grid 'jsy.voltage1')" ">200" +check "$(value /api/grid 'jsy.voltage2')" ">200" + +assert "$(value /api/grid 'zcd.enabled')" "true" +check "$(value /api/grid 'zcd.frequency')" ">48" +check "$(value /api/grid 'zcd.frequency')" "<52" + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api-mqtt.sh b/test/test-api-mqtt.sh new file mode 100755 index 0000000..8e9709a --- /dev/null +++ b/test/test-api-mqtt.sh @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +post "/api/config" -F "mqtt_enable=false" +assert "$(value /api/config 'mqtt_enable')" "false" + +post "/api/config" -F "mqtt_enable=true" +assert "$(value /api/config 'mqtt_enable')" "true" +sleep 6 + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api-network.sh b/test/test-api-network.sh new file mode 100755 index 0000000..042c7d4 --- /dev/null +++ b/test/test-api-network.sh @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(keys /api/network)" "ip_address,mac_address,state,wifi_bssid,wifi_rssi,wifi_signal,wifi_ssid" +assert "$(value /api/network 'state')" "STA_CONNECTED" +assert "$(value /api/network 'ip_address')" "$HOST" +check "$(value /api/network 'wifi_rssi')" ">-80" +assert "$(value /api/network 'wifi_ssid')" "IoT" +check "$(value /api/network 'wifi_signal')" ">50" + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api-ntp.sh b/test/test-api-ntp.sh new file mode 100755 index 0000000..f162142 --- /dev/null +++ b/test/test-api-ntp.sh @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(keys /api/ntp)" "synced" +assert "$(value /api/ntp 'synced')" "true" + +post "/api/ntp/update" + +post "/api/config" -F "ntp_timezone=Etc/UTC" +sleep 2 + +post "/api/config" -F "ntp_timezone=Europe/Paris" +sleep 2 + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api-relays.sh b/test/test-api-relays.sh new file mode 100755 index 0000000..6046975 --- /dev/null +++ b/test/test-api-relays.sh @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(value /api/relays 'relay1.enabled')" "true" +assert "$(value /api/relays 'relay1.state')" "off" +check "$(value /api/relays 'relay1.switch_count')" ">=0" + +assert "$(value /api/relays 'relay2.enabled')" "true" +assert "$(value /api/relays 'relay2.state')" "off" +check "$(value /api/relays 'relay2.switch_count')" ">=0" + +post "/api/config" -F "relay2_enable=false" + +assert "$(value /api/relays 'relay2.enabled')" "false" +assert "$(value /api/relays 'relay2.state')" "off" + +post "/api/config" -F "relay2_enable=true" + +assert "$(value /api/relays 'relay2.enabled')" "true" +assert "$(value /api/relays 'relay2.state')" "off" +check "$(value /api/relays 'relay2.switch_count')" ">=0" + +post "/api/relays/relay2" -F "state=on" -F "duration=4000" +assert "$(value /api/relays 'relay2.enabled')" "true" +assert "$(value /api/relays 'relay2.state')" "on" +check "$(value /api/relays 'relay2.switch_count')" ">=1" +assert "$(value /api/system 'lights.green')" "on" +assert "$(value /api/system 'lights.red')" "off" +assert "$(value /api/system 'lights.yellow')" "on" + +sleep 3 +assert "$(value /api/relays 'relay2.enabled')" "true" +assert "$(value /api/relays 'relay2.state')" "off" +check "$(value /api/relays 'relay2.switch_count')" ">=2" +assert "$(value /api/system 'lights.green')" "on" +assert "$(value /api/system 'lights.red')" "off" +assert "$(value /api/system 'lights.yellow')" "on", "off" + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api-router.sh b/test/test-api-router.sh new file mode 100755 index 0000000..28518bf --- /dev/null +++ b/test/test-api-router.sh @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(keys /api/router)" "energy,output1,output2,power,power_factor,virtual_grid_power" + +check "$(value /api/router 'energy')" ">=0" +check "$(value /api/router 'power')" ">=0" +check "$(value /api/router 'power_factor')" ">=0" +check "$(value /api/router 'virtual_grid_power')" "!=0" + +# output1 + +assert "$(value /api/router 'output1.enabled')" "true" +check "$(value /api/router 'output1.power')" ">=0" +assert "$(value /api/router/output1 'output1.state')" "Disabled" "Routing" "manual Bypass" "Auto Bypass" "Idle" + +assert "$(value /api/router 'output1.bypass_relay.enabled')" "true" +assert "$(value /api/router 'output1.bypass_relay.state')" "off" +check "$(value /api/router 'output1.bypass_relay.switch_count')" ">=0" + +assert "$(value /api/router 'output1.dimmer.enabled')" "true" +assert "$(value /api/router 'output1.dimmer.level')" "0" +assert "$(value /api/router 'output1.dimmer.state')" "off" + +assert "$(value /api/router 'output1.temp_sensor.enabled')" "true" +assert "$(value /api/router 'output1.temp_sensor.valid')" "true" +check "$(value /api/router 'output1.temp_sensor.temperature')" ">0" + +# output 2 + +assert "$(value /api/router 'output2.enabled')" "true" +check "$(value /api/router 'output2.power')" "==0" +assert "$(value /api/router/output2 'output2.state')" "Idle" + +assert "$(value /api/router 'output2.bypass_relay.enabled')" "true" +assert "$(value /api/router 'output2.bypass_relay.state')" "off" +check "$(value /api/router 'output2.bypass_relay.switch_count')" ">=0" + +assert "$(value /api/router 'output2.dimmer.enabled')" "true" +assert "$(value /api/router 'output2.dimmer.level')" "0" +assert "$(value /api/router 'output2.dimmer.state')" "off" + +assert "$(value /api/router 'output2.temp_sensor.enabled')" "false" +assert "$(value /api/router 'output2.temp_sensor.valid')" "false" +check "$(value /api/router 'output2.temp_sensor.temperature')" ">=0" + +# posts + +post "/api/router/output1/bypass_relay" -F "state=on" +assert "$(value /api/router 'output1.bypass_relay.state')" "on" +check "$(value /api/router 'output1.bypass_relay.switch_count')" ">=1" + +post "/api/router/output1/bypass_relay" -F "state=off" +check "$(value /api/router 'output1.bypass_relay.switch_count')" ">=2" + +post "/api/router/output1/dimmer" -F "level=50" +assert "$(value /api/router 'output1.dimmer.level')" "50" +assert "$(value /api/router 'output1.dimmer.state')" "on" + +post "/api/router/output1/dimmer" -F "level=0" +assert "$(value /api/router 'output1.dimmer.level')" "0" +assert "$(value /api/router 'output1.dimmer.state')" "off" + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api-system.sh b/test/test-api-system.sh new file mode 100755 index 0000000..4883d58 --- /dev/null +++ b/test/test-api-system.sh @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(keys /api/system)" "boots,buzzer,chip_cores,chip_model,chip_revision,cpu_freq,heap_total,heap_usage,heap_used,lights,tasks,temp_sensor,uptime" +check "$(value /api/system 'boots')" ">0" +check "$(value /api/system 'chip_cores')" "==2" +check "$(value /api/system 'heap_total')" ">1000" +check "$(value /api/system 'heap_usage')" ">40" +check "$(value /api/system 'heap_used')" ">1000" +check "$(value /api/system 'uptime')" ">1" + +assert "$(value /api/system 'buzzer.enabled')" "false" + +assert "$(value /api/system 'lights.code')" "🟢 ⚫ ⚫" "🟢 🟡 ⚫" +assert "$(value /api/system 'lights.green')" "on" +assert "$(value /api/system 'lights.red')" "off" +assert "$(value /api/system 'lights.yellow')" "on" "off" + +assert "$(value /api/system 'temp_sensor.enabled')" "true" +check "$(value /api/system 'temp_sensor.temperature')" ">20" +assert "$(value /api/system 'temp_sensor.valid')" "true" + +# rw buzzer tests + +post "/api/config" -F "buzzer_enable=true" +assert "$(value /api/system 'buzzer.enabled')" "true" + +post "/api/config" -F "buzzer_enable=false" +assert "$(value /api/system 'buzzer.enabled')" "false" + +# rw temp tests + +post "/api/config" -F "sys_tmp_enable=false" +post "/api/config" -F "out1_tmp_enable=false" + +assert "$(value /api/system 'temp_sensor.enabled')" "false" +check "$(value /api/system 'temp_sensor.temperature')" "==0" +assert "$(value /api/system 'temp_sensor.valid')" "false" + +assert "$(value /api/router 'output1.temp_sensor.enabled')" "false" +check "$(value /api/router 'output1.temp_sensor.temperature')" "==0" +assert "$(value /api/router 'output1.temp_sensor.valid')" "false" + +post "/api/config" -F "sys_tmp_enable=true" +post "/api/config" -F "out1_tmp_enable=true" + +# update interval is 10 sec for temp +sleep 10 + +assert "$(value /api/system 'temp_sensor.enabled')" "true" +check "$(value /api/system 'temp_sensor.temperature')" ">20" +assert "$(value /api/system 'temp_sensor.valid')" "true" + +assert "$(value /api/router 'output1.temp_sensor.enabled')" "true" +check "$(value /api/router 'output1.temp_sensor.temperature')" ">20" +assert "$(value /api/router 'output1.temp_sensor.valid')" "true" + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/test-api.sh b/test/test-api.sh new file mode 100755 index 0000000..f8a2d39 --- /dev/null +++ b/test/test-api.sh @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +if [ "$1" == "" ]; then + echo "Usage: $0 " + exit 1 +fi +HOST="$1" +DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${DIR}/utils/api.sh" + +assert "$(body /ping)" "pong" + +echo "====================================================================" +echo "SUCCESS: $0" +echo "====================================================================" diff --git a/test/utils/api.sh b/test/utils/api.sh new file mode 100755 index 0000000..0731292 --- /dev/null +++ b/test/utils/api.sh @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023-2024 Mathieu Carbou +# +#!/usr/bin/env bash + +set -eE -o functrace + +failure() { + local lineno=$1 + local msg=$2 + echo "Failed at $lineno: $msg" +} +trap 'failure ${LINENO} "$BASH_COMMAND"' ERR + +function body() { + echo "BODY http://${HOST}$1" >&2 + curl -s -X GET "http://${HOST}$1" +} + +function post() { + echo "POST http://${HOST}$@" >&2 + curl -s -o /dev/null -X POST "http://${HOST}$@" >&2 + sleep 2 +} + +function keys() { + echo "KEYS http://${HOST}$1" >&2 + curl -X GET -s "http://${HOST}$1" | jq -cr 'keys|join(",")' +} + +function value() { + echo "VALUE http://${HOST}$1 .$2" >&2 + curl -X GET -s "http://${HOST}$1" | jq -cr ".$2" +} + +function assert() { + if [ "$#" -lt 2 ]; then + echo "Usage: assert ..." + exit 1 + fi + actual=$1 + # echo "> assert $@" + match=0 + for (( i=2; i<=$#; i+=1 )) # loop from 1 to N (where N is number of args) + do + expected=${!i} + echo "Expected: <$expected>" + if [ "$expected" == "$actual" ]; then + match=1 + break + fi + done + if [ $match -eq 0 ]; then + echo "Actual: <$actual>" + exit 1 + fi +} + +function check() { + if [ "$#" -ne 2 ]; then + echo "Usage: check " + exit 1 + fi + echo "> check $@" + if [ $(bc <<<"$@") -eq 0 ]; then + echo "Failed test: $@" + exit 1 + fi +}