diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a0498d9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,16 @@ +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +AllowShortIfStatementsOnASingleLine: false +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..7362f6f --- /dev/null +++ b/.github/workflows/cpplint.yml @@ -0,0 +1,45 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Cpplint + +on: + workflow_dispatch: + push: + 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..f3037c6 --- /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}", + "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..e04cd31 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +[![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.yml/badge.svg?branch=main)](https://github.com/mathieucarbou/YaSolR-OSS/actions/workflows/build.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 +- `docs`: Random documentation +- `include`: Firmware include code +- `lib`: Firmware libraries +- `pio`: pio scripts +- `site`: Website +- `src`: Firmware source code +- `test`: Firmware tests +- `tools`: Some random tools +- `build.sh`: project build script for development +- `partitions.csv`: ESP32 custom partition table +- `platformio_override.ini.template` (to copy to `platformio_override.ini`): PlatformIO configuration override +- `platformio.ini`: PlatformIO configuration +- `upload.sh`: project upload script to upload the default firmware to the ESP32 + +## 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/build.sh b/build.sh new file mode 100755 index 0000000..c22eba6 --- /dev/null +++ b/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -e + +[ -x "$(command -v python)" ] && PYTHON=python || PYTHON=python3 + +echo "=======================" +echo "Install..." +echo "=======================" +echo "" + +$PYTHON -m pip install --upgrade pip +pip install --upgrade platformio + +echo "" +echo "=======================" +echo "cpplint..." +echo "=======================" +echo "" + +if [ -z "$1" ]; then + echo "" + echo "==========================" + echo "Building default env..." + echo "==========================" + echo "" + pio run +else + echo "" + echo "==========================" + echo "Building env: $1..." + echo "==========================" + echo "" + pio run -e $1 +fi 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 0000000..c0462a5 Binary files /dev/null and b/data/logo-icon.png differ diff --git a/data/logo.png b/data/logo.png new file mode 100644 index 0000000..a2f48bb Binary files /dev/null and b/data/logo.png differ diff --git a/include/YaSolR.h b/include/YaSolR.h new file mode 100644 index 0000000..8099200 --- /dev/null +++ b/include/YaSolR.h @@ -0,0 +1,138 @@ +// 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 + +#ifndef YASOLR_SERIAL_BAUDRATE +#define YASOLR_SERIAL_BAUDRATE 115200 +#endif + +#ifndef YASOLR_ADMIN_USERNAME +#define YASOLR_ADMIN_USERNAME "admin" +#endif + +#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" diff --git a/include/YaSolRConfig.h b/include/YaSolRConfig.h new file mode 100644 index 0000000..cc58d6f --- /dev/null +++ b/include/YaSolRConfig.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +namespace YaSolR { + class ConfigClass { + public: + void begin(); + }; + + extern YaSolR::ConfigClass Config; +} // namespace YaSolR diff --git a/include/YaSolRConnector.h b/include/YaSolRConnector.h new file mode 100644 index 0000000..2eb284e --- /dev/null +++ b/include/YaSolRConnector.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +namespace YaSolR { + class Connector { + public: + explicit Connector(Mycila::TemperatureSensor* systemTempSensor) : _systemTempSensor(systemTempSensor) {} + + void begin(); + void loop(); + + void publish(); + + private: + Mycila::TemperatureSensor* _systemTempSensor; + uint32_t _lastPublish = 0; + bool _publishHADiscovery = false; + + private: + void _publish(); + }; +} // namespace YaSolR diff --git a/include/YaSolRController.h b/include/YaSolRController.h new file mode 100644 index 0000000..3f601c0 --- /dev/null +++ b/include/YaSolRController.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +#include +#include +#include + +#include + +namespace YaSolR { + enum class Event { + MQTT_RECONFIGURED, + NTP_RECONFIGURED, + RELAY1_RECONFIGURED, + RELAY2_RECONFIGURED, + BUZZER_RECONFIGURED, + LIGHTS_RECONFIGURED, + OUT1_TEMP_RECONFIGURED, + OUT1_RELAY_RECONFIGURED, + OUT1_DIMMER_RECONFIGURED, + OUT2_TEMP_RECONFIGURED, + OUT2_RELAY_RECONFIGURED, + OUT2_DIMMER_RECONFIGURED, + JSY_RECONFIGURED, + ZCD_RECONFIGURED, + POWER_MANAGER_RECONFIGURED, + SYS_TEMP_RECONFIGURED, + BUTTON_RECONFIGURED, + NETWORK_RECONFIGURED, + NETWORK_CONNECTED, + NETWORK_DISCONNECTED, + DISPLAY_RECONFIGURED + }; + + class Controller { + public: + explicit Controller(Mycila::TemperatureSensor* systemTemp, + YaSolR::Website* website, + YaSolR::Connector* connector) : _systemTemp(systemTemp), + _website(website), + _connector(connector) {} + + void begin(); + void loop(); + void updateDisplay(); + + private: + Mycila::TemperatureSensor* _systemTemp; + YaSolR::Website* _website; + YaSolR::Connector* _connector; + std::queue _events; + Mycila::Throttle _restartThrottle = Mycila::Throttle(false); + Mycila::Throttle _resetThrottle = Mycila::Throttle(false); + Mycila::Throttle _updateOnceThrottle = Mycila::Throttle(false); + uint32_t _lastUpdate = 0; + + private: + void _triggerRefresh(); + }; +} // namespace YaSolR diff --git a/include/YaSolRHADiscoveryController.h b/include/YaSolRHADiscoveryController.h new file mode 100644 index 0000000..0e191c3 --- /dev/null +++ b/include/YaSolRHADiscoveryController.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +namespace YaSolR { + class HADiscoveryControllerClass { + public: + void publish(); + }; + + extern YaSolR::HADiscoveryControllerClass HADiscoveryController; +} // namespace YaSolR diff --git a/include/YaSolRWebsite.h b/include/YaSolRWebsite.h new file mode 100644 index 0000000..46fc625 --- /dev/null +++ b/include/YaSolRWebsite.h @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include +#include +#include + +#ifndef YASOLR_WEBSITE_UPDATE_INTERVAL +#define YASOLR_WEBSITE_UPDATE_INTERVAL 1 +#endif + +#ifdef APP_VERSION_PRO +#include +#define PUSH_BUTTON_CARD_CB []() +#else +#include +#define ENERGY_CARD GENERIC_CARD +#define PUSH_BUTTON_CARD BUTTON_CARD +#define PUSH_BUTTON_CARD_CB [](int32_t value) +#endif + +namespace YaSolR { + class Website { + public: + explicit Website(Mycila::TemperatureSensor* systemTempSensor) : _systemTempSensor(systemTempSensor) {} + void begin(); + void loop(); + void update(bool skipWebSocketPush = false); + + private: + Mycila::TemperatureSensor* _systemTempSensor; + uint32_t _lastUpdate = 0; + + ESPDash _dashboard = ESPDash(&Mycila::HTTPd.server, "/dashboard", false); + + Statistic _appFirmwareStat = Statistic(&_dashboard, "App Firmware"); + Statistic _appIdStat = Statistic(&_dashboard, "App Id"); + Statistic _appManufacturerStat = Statistic(&_dashboard, "App Manufacturer"); + Statistic _appName = Statistic(&_dashboard, "App Name"); + Statistic _appVersionStat = Statistic(&_dashboard, "App Version"); + Statistic _bootCountStat = Statistic(&_dashboard, "Boot Count"); + Statistic _cpuCoresStat = Statistic(&_dashboard, "CPU Cores"); + Statistic _cpuModelStat = Statistic(&_dashboard, "CPU Model"); + Statistic _gridFreqStat = Statistic(&_dashboard, "Grid Frequency"); + Statistic _gridVoltStat = Statistic(&_dashboard, "Grid Voltage"); + Statistic _memoryTotalStat = Statistic(&_dashboard, "Heap Memory Total"); + Statistic _memoryUsageStat = Statistic(&_dashboard, "Heap Memory Usage"); + Statistic _memoryUsedStat = Statistic(&_dashboard, "Heap Memory Used"); + Statistic _ipAddressStat = Statistic(&_dashboard, "IP Address"); + Statistic _macAddressStat = Statistic(&_dashboard, "MAC Address"); + 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"); + Statistic _wifiRSSIStat = Statistic(&_dashboard, "WiFi RSSI"); + Statistic _wifiSignalStat = Statistic(&_dashboard, "WiFi Signal"); + Statistic _wifiSSIDStat = Statistic(&_dashboard, "WiFi SSID"); + + // 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"); + + 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"); + + 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 _stateJSY = Card(&_dashboard, STATUS_CARD, "JSY", DASH_STATUS_IDLE); + Card _stateLEDs = Card(&_dashboard, STATUS_CARD, "LEDs", 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", 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", 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 _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 _pinLiveLEDRed = Card(&_dashboard, STATUS_CARD, "LED Red Pin", DASH_STATUS_IDLE); + Card _pinLiveLEDYellow = Card(&_dashboard, STATUS_CARD, "LED Yellow Pin", DASH_STATUS_IDLE); + Card _pinLiveOutput1Bypass = Card(&_dashboard, STATUS_CARD, "Output 1 Bypass 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 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 _pinConfigLEDRed = Card(&_dashboard, STATUS_CARD, "LED Red Pin", DASH_STATUS_IDLE); + Card _pinConfigLEDYellow = Card(&_dashboard, STATUS_CARD, "LED Yellow Pin", DASH_STATUS_IDLE); + Card _pinConfigOutput1Bypass = Card(&_dashboard, STATUS_CARD, "Output 1 Bypass 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 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"); + Card _output1BypassPin = Card(&_dashboard, TEXT_INPUT_CARD, "Output 1 Bypass Pin"); + Card _output1BypassType = Card(&_dashboard, DROPDOWN_CARD, "Output 1 Bypass 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"); + Card _output2BypassPin = Card(&_dashboard, TEXT_INPUT_CARD, "Output 2 Bypass Pin"); + Card _output2BypassType = Card(&_dashboard, DROPDOWN_CARD, "Output 2 Bypass 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 _ledRedPin = Card(&_dashboard, TEXT_INPUT_CARD, "LED Red Pin"); + Card _ledYellowPin = Card(&_dashboard, TEXT_INPUT_CARD, "LED Yellow 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 _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, int idx); + void _outputRelaySwitch(Card* card, int idx); + void _relaySwitch(Card* card, int idx); + + 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); + }; +} // 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/docs/dash-github-splash.png b/lib/ESPDASH/docs/dash-github-splash.png new file mode 100644 index 0000000..4ee26c5 Binary files /dev/null and b/lib/ESPDASH/docs/dash-github-splash.png differ diff --git a/lib/ESPDASH/docs/dash-github-splash.psd b/lib/ESPDASH/docs/dash-github-splash.psd new file mode 100644 index 0000000..ecaafb9 Binary files /dev/null and b/lib/ESPDASH/docs/dash-github-splash.psd differ diff --git a/lib/ESPDASH/docs/logo-collapsed.svg b/lib/ESPDASH/docs/logo-collapsed.svg new file mode 100644 index 0000000..21cfafd --- /dev/null +++ b/lib/ESPDASH/docs/logo-collapsed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/ESPDASH/docs/logo.svg b/lib/ESPDASH/docs/logo.svg new file mode 100644 index 0000000..d8fc1d2 --- /dev/null +++ b/lib/ESPDASH/docs/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/ESPDASH/docs/preview.png b/lib/ESPDASH/docs/preview.png new file mode 100644 index 0000000..9a277f6 Binary files /dev/null and b/lib/ESPDASH/docs/preview.png differ diff --git a/lib/ESPDASH/docs/pro-preview.png b/lib/ESPDASH/docs/pro-preview.png new file mode 100644 index 0000000..50eff08 Binary files /dev/null and b/lib/ESPDASH/docs/pro-preview.png differ diff --git a/lib/ESPDASH/examples/AccessPoint/AccessPoint.ino b/lib/ESPDASH/examples/AccessPoint/AccessPoint.ino new file mode 100644 index 0000000..4e42878 --- /dev/null +++ b/lib/ESPDASH/examples/AccessPoint/AccessPoint.ino @@ -0,0 +1,82 @@ +/* + ----------------------------- + ESPDASH Lite - AccessPoint Example + ----------------------------- + + Skill Level: Intermediate + + In this example we will be booting up our ESP Module in Access Point (SoftAP) mode + and initialize our dashboard which can be accessed via 192.168.4.1 + + Github: https://github.com/ayushsharma82/ESP-DASH + WiKi: https://docs.espdash.pro + + Works with both ESP8266 & ESP32 + + ------------------------------- + + Upgrade to ESP-DASH Pro: https://espdash.pro + +*/ + +#include +#if defined(ESP8266) + /* ESP8266 Dependencies */ + #include + #include + #include +#elif defined(ESP32) + /* ESP32 Dependencies */ + #include + #include + #include +#endif +#include + + +/* Your SoftAP WiFi Credentials */ +const char* ssid = ""; // SSID +const char* password = ""; // Password + +/* Start Webserver */ +AsyncWebServer server(80); + +/* Attach ESP-DASH to AsyncWebServer */ +ESPDash dashboard(&server); + +/* + Dashboard Cards + Format - (Dashboard Instance, Card Type, Card Name, Card Symbol(optional) ) +*/ +Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); +Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%"); + + +void setup() { + Serial.begin(115200); + + /* Start Access Point */ + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0)); + WiFi.softAP(ssid, password); + Serial.print("IP Address: "); + Serial.println(WiFi.softAPIP()); + + /* Start AsyncWebServer */ + server.begin(); +} + +void loop() { + /* Update Card Values */ + temperature.update((int)random(0, 50)); + humidity.update((int)random(0, 100)); + + /* Send Updates to our Dashboard (realtime) */ + dashboard.sendUpdates(); + + /* + Delay is just for demonstration purposes in this example, + Replace this code with 'millis interval' in your final project. + */ + delay(3000); +} \ No newline at end of file diff --git a/lib/ESPDASH/examples/Basic/Basic.ino b/lib/ESPDASH/examples/Basic/Basic.ino new file mode 100644 index 0000000..1500810 --- /dev/null +++ b/lib/ESPDASH/examples/Basic/Basic.ino @@ -0,0 +1,85 @@ +/* + ----------------------- + ESPDASH Lite - Basic Example + ----------------------- + + Skill Level: Intermediate + + In this example we will be creating a basic dashboard which consists + of some cards and then update them in realtime ( at 3s interval ). + + Github: https://github.com/ayushsharma82/ESP-DASH + WiKi: https://docs.espdash.pro + + Works with both ESP8266 & ESP32 + + ------------------------------- + + Upgrade to ESP-DASH Pro: https://espdash.pro + +*/ + +#include +#if defined(ESP8266) + /* ESP8266 Dependencies */ + #include + #include + #include +#elif defined(ESP32) + /* ESP32 Dependencies */ + #include + #include + #include +#endif +#include + + +/* Your WiFi Credentials */ +const char* ssid = ""; // SSID +const char* password = ""; // Password + +/* Start Webserver */ +AsyncWebServer server(80); + +/* Attach ESP-DASH to AsyncWebServer */ +ESPDash dashboard(&server); + +/* + Dashboard Cards + Format - (Dashboard Instance, Card Type, Card Name, Card Symbol(optional) ) +*/ +Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); +Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%"); + + +void setup() { + Serial.begin(115200); + + /* Connect WiFi */ + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + /* Start AsyncWebServer */ + server.begin(); +} + +void loop() { + /* Update Card Values */ + temperature.update((int)random(0, 50)); + humidity.update((int)random(0, 100)); + + /* Send Updates to our Dashboard (realtime) */ + dashboard.sendUpdates(); + + /* + Delay is just for demonstration purposes in this example, + Replace this code with 'millis interval' in your final project. + */ + delay(3000); +} \ No newline at end of file diff --git a/lib/ESPDASH/examples/Benchmark/Benchmark.ino b/lib/ESPDASH/examples/Benchmark/Benchmark.ino new file mode 100644 index 0000000..e64afb2 --- /dev/null +++ b/lib/ESPDASH/examples/Benchmark/Benchmark.ino @@ -0,0 +1,118 @@ +/* + ----------------------------- + ESPDASH Lite - Benchmark Example + ----------------------------- + Use this benchmark example to test if ESP-DASH is working properly on your platform. + + Github: https://github.com/ayushsharma82/ESP-DASH + WiKi: https://docs.espdash.pro + + Works with both ESP8266 & ESP32 + ------------------------------- + + Upgrade to ESP-DASH Pro: https://espdash.pro +*/ + +#include +#if defined(ESP8266) + /* ESP8266 Dependencies */ + #include + #include + #include +#elif defined(ESP32) + /* ESP32 Dependencies */ + #include + #include + #include +#endif + +#include + + +/* Your WiFi Credentials */ +const char* ssid = ""; // SSID +const char* password = ""; // Password + +/* Start Webserver */ +AsyncWebServer server(80); + +/* Attach ESP-DASH to AsyncWebServer */ +ESPDash dashboard(&server); + +Card generic(&dashboard, GENERIC_CARD, "Generic"); +Card temp(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); +Card hum(&dashboard, HUMIDITY_CARD, "Humidity", "%"); +Card status1(&dashboard, STATUS_CARD, "Status 1", "success"); +Card status2(&dashboard, STATUS_CARD, "Status 2", "warning"); +Card status3(&dashboard, STATUS_CARD, "Status 3", "danger"); +Card status4(&dashboard, STATUS_CARD, "Status 4", "idle"); +Card progress(&dashboard, PROGRESS_CARD, "Progress", "", 0, 100); +Card button(&dashboard, BUTTON_CARD, "Test Button"); +Card slider(&dashboard, SLIDER_CARD, "Test Slider", "", 0, 255); + +Chart bar(&dashboard, BAR_CHART, "Power Usage (kWh)"); + +// Bar Chart Data +String XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; + + +void setup() { + Serial.begin(115200); + + /* Connect WiFi */ + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + bar.updateX(XAxis, 7); + + /* Attach Button Callback */ + button.attachCallback([&](bool value){ + /* Print our new button value received from dashboard */ + Serial.println("Button Triggered: "+String((value)?"true":"false")); + /* Make sure we update our button's value and send update to dashboard */ + button.update(value); + dashboard.sendUpdates(); + }); + + /* Attach Slider Callback */ + slider.attachCallback([&](int value){ + /* Print our new slider value received from dashboard */ + Serial.println("Slider Triggered: "+String(value)); + /* Make sure we update our slider's value and send update to dashboard */ + slider.update(value); + dashboard.sendUpdates(); + }); + + /* Start AsyncWebServer */ + server.begin(); +} + +void loop() { + // Randomize YAxis Values ( for demonstration purposes only ) + for(int i=0; i < 7; i++){ + YAxis[i] = (int)random(0, 200); + } + + /* Update Chart Y Axis (yaxis_array, array_size) */ + bar.updateY(YAxis, 7); + + // Update all cards with random values + generic.update((int)random(0, 100)); + temp.update((int)random(0, 100)); + hum.update((int)random(0, 100)); + status1.update("success"); + status2.update("warning"); + status3.update("danger"); + status4.update("idle"); + progress.update((int)random(0, 100)); + + dashboard.sendUpdates(); + delay(2000); +} \ No newline at end of file diff --git a/lib/ESPDASH/examples/Chart/Chart.ino b/lib/ESPDASH/examples/Chart/Chart.ino new file mode 100644 index 0000000..34185d6 --- /dev/null +++ b/lib/ESPDASH/examples/Chart/Chart.ino @@ -0,0 +1,100 @@ +/* + ----------------------- + ESPDASH Lite - Chart Example + ----------------------- + + Skill Level: Intermediate + + In this example we will learn how to create a bar chart + and then update it at a regular interval. + + Github: https://github.com/ayushsharma82/ESP-DASH + WiKi: https://docs.espdash.pro + + Works with both ESP8266 & ESP32 + + ------------------------------- + + Upgrade to ESP-DASH Pro: https://espdash.pro + +*/ + +#include +#if defined(ESP8266) + /* ESP8266 Dependencies */ + #include + #include + #include +#elif defined(ESP32) + /* ESP32 Dependencies */ + #include + #include + #include +#endif +#include + + +/* Your WiFi Credentials */ +const char* ssid = ""; // SSID +const char* password = ""; // Password + +/* Start Webserver */ +AsyncWebServer server(80); + +/* Attach ESP-DASH to AsyncWebServer */ +ESPDash dashboard(&server); + +/* + Dashboard Charts + Format - (Dashboard Instance, Chart Type, Chart Name ) +*/ +// Bar Chart Data +String XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; + +// Bar Chart Instance +Chart power(&dashboard, BAR_CHART, "Power Usage (kWh)"); + + +void setup() { + Serial.begin(115200); + + /* Connect WiFi */ + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + /* + Update Chart X Axis (xaxis_array, array_size) + ------------------- + We need to update X Axis once only as it will not be changing in this example + */ + power.updateX(XAxis, 7); + + /* Start AsyncWebServer */ + server.begin(); +} + +void loop() { + // Randomize YAxis Values ( for demonstration purposes only ) + for(int i=0; i < 7; i++){ + YAxis[i] = (int)random(0, 200); + } + + /* Update Chart Y Axis (yaxis_array, array_size) */ + power.updateY(YAxis, 7); + + /* Send Updates to our Dashboard (realtime) */ + dashboard.sendUpdates(); + + /* + Delay is just for demonstration purposes in this example, + Replace this code with 'millis interval' in your final project. + */ + delay(3000); +} diff --git a/lib/ESPDASH/examples/Dynamic/Dynamic.ino b/lib/ESPDASH/examples/Dynamic/Dynamic.ino new file mode 100644 index 0000000..f7df0c9 --- /dev/null +++ b/lib/ESPDASH/examples/Dynamic/Dynamic.ino @@ -0,0 +1,107 @@ +/* + ------------------------- + ESPDASH Lite - Dynamic Example + ------------------------- + + Skill Level: Advanced ( Must have good knowledge about heap, pointers and destructors ) + + In this example we will be creating a dynamic dashboard which consists + of some cards and then remove a card from dashboard after some time. + + Github: https://github.com/ayushsharma82/ESP-DASH + WiKi: https://docs.espdash.pro + + Works with both ESP8266 & ESP32 + + ------------------------------- + + Upgrade to ESP-DASH Pro: https://espdash.pro + +*/ + +#include +#if defined(ESP8266) + /* ESP8266 Dependencies */ + #include + #include + #include +#elif defined(ESP32) + /* ESP32 Dependencies */ + #include + #include + #include +#endif +#include + + +/* Your WiFi Credentials */ +const char* ssid = ""; // SSID +const char* password = ""; // Password + +/* Start Webserver */ +AsyncWebServer server(80); + +/* Attach ESP-DASH to AsyncWebServer */ +ESPDash dashboard(&server); + +/* + Dashboard Cards + Format - (Dashboard Instance, Card Type, Card Name, Card Symbol(optional) ) +*/ +Card* temperature = new Card(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); +Card* humidity = new Card(&dashboard, HUMIDITY_CARD, "Humidity", "%"); + +/* + Removal Time for demonstration purposes only + In your final project, + You will decide when to add or remove cards, these variables are not requried. +*/ +unsigned long removalTime = 30000; // Remove after 30s ( 30000 Millis ) +bool cardRemoved = false; + +void setup() { + Serial.begin(115200); + + /* Connect WiFi */ + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + /* Start AsyncWebServer */ + server.begin(); +} + +void loop() { + /* Update Card Values */ + if(temperature != nullptr) // Check if our pointer has not been deleted & then only access 'update' method + temperature->update((int)random(0, 50)); + + if(humidity != nullptr) // Check if our pointer has not been deleted & then only access 'update' method + humidity->update((int)random(0, 100)); + + + /* Remove our card when removal time is elapsed */ + if(millis() > removalTime && !cardRemoved){ + if(humidity != nullptr){ + /* Calling 'delete' will remove this card from our dashboard instance and will free-up consumed heap */ + delete humidity; + humidity = nullptr; // Make sure to set this pointer to 'nullptr' when deleted + } + // Set our removal flag + cardRemoved = true; + } + + /* Send Updates to our Dashboard (realtime) */ + dashboard.sendUpdates(); + + /* + Delay is just for demonstration purposes in this example, + Replace this code with 'millis interval' in your final project. + */ + delay(3000); +} diff --git a/lib/ESPDASH/examples/Interactive/Interactive.ino b/lib/ESPDASH/examples/Interactive/Interactive.ino new file mode 100644 index 0000000..775ca40 --- /dev/null +++ b/lib/ESPDASH/examples/Interactive/Interactive.ino @@ -0,0 +1,97 @@ +/* + ----------------------------- + ESPDASH Lite - Interactive Example + ----------------------------- + + Skill Level: Intermediate + + In this example we will be creating a interactive dashboard which consists + of a button and a slider. + + Github: https://github.com/ayushsharma82/ESP-DASH + WiKi: https://docs.espdash.pro + + Works with both ESP8266 & ESP32 + + ------------------------------- + + Upgrade to ESP-DASH Pro: https://espdash.pro + +*/ + +#include +#if defined(ESP8266) + /* ESP8266 Dependencies */ + #include + #include + #include +#elif defined(ESP32) + /* ESP32 Dependencies */ + #include + #include + #include +#endif +#include + + +/* Your WiFi Credentials */ +const char* ssid = ""; // SSID +const char* password = ""; // Password + +/* Start Webserver */ +AsyncWebServer server(80); + +/* Attach ESP-DASH to AsyncWebServer */ +ESPDash dashboard(&server); + +/* + Button Card + Format - (Dashboard Instance, Card Type, Card Name) +*/ +Card button(&dashboard, BUTTON_CARD, "Test Button"); + +/* + Slider Card + Format - (Dashboard Instance, Card Type, Card Name, Card Symbol(optional), int min, int max) +*/ +Card slider(&dashboard, SLIDER_CARD, "Test Slider", "", 0, 255); + + +void setup() { + Serial.begin(115200); + + /* Connect WiFi */ + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + /* Attach Button Callback */ + button.attachCallback([&](bool value){ + /* Print our new button value received from dashboard */ + Serial.println("Button Triggered: "+String((value)?"true":"false")); + /* Make sure we update our button's value and send update to dashboard */ + button.update(value); + dashboard.sendUpdates(); + }); + + /* Attach Slider Callback */ + slider.attachCallback([&](int value){ + /* Print our new slider value received from dashboard */ + Serial.println("Slider Triggered: "+String(value)); + /* Make sure we update our slider's value and send update to dashboard */ + slider.update(value); + dashboard.sendUpdates(); + }); + + /* Start AsyncWebServer */ + server.begin(); +} + +void loop() { + /* Nothing so far */ +} \ No newline at end of file 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..ae2c2ab --- /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.3", + "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..9aed56d --- /dev/null +++ b/lib/ESPDASH/library.properties @@ -0,0 +1,9 @@ +name=ESP-DASH +version=4.0.3 +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..97ff7f2 --- /dev/null +++ b/lib/ESPDASH/src/ESPDash.cpp @@ -0,0 +1,517 @@ +#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); + // Serial.write(buffer->get(), len); + // Serial.println(); + 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..a197bf9 --- /dev/null +++ b/lib/ESPDASH/src/dash_webpage.cpp @@ -0,0 +1,3008 @@ +#include "dash_webpage.h" + +const uint8_t DASH_HTML[90104] PROGMEM = { +31,139,8,0,0,0,0,0,2,3,204,57,103,115,227,56,178,223,239,87,200,124,126,58,162,6,162,229,9,47, +80,198,184,38,110,222,153,90,237,165,81,169,124,20,217,146,112,166,0,46,0,218,214,200,250,239,175,27,12,160, +61,179,117,95,94,218,32,53,209,141,206,137,242,197,201,219,15,111,126,253,219,199,119,163,173,219,149,47,255,112, +65,95,163,50,83,27,17,129,138,94,254,97,52,186,216,66,86,16,128,224,14,92,54,202,183,153,177,224,68,244, +167,95,223,79,254,35,26,157,181,200,82,170,235,145,129,82,68,50,215,42,26,109,13,172,69,116,182,206,110,232, +57,193,15,164,29,50,82,217,14,68,116,35,225,182,210,198,69,35,164,114,160,144,241,173,44,220,86,20,128,23, +97,226,31,248,72,42,233,100,86,78,108,158,149,32,206,147,105,96,230,164,43,225,229,223,178,185,46,127,25,125, +152,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,86,255,153,191,248,143,167,197,121,242,15,251,135,155,204,140,10,35,62,172,254,1,185,75,10,88,75, +5,31,141,174,192,184,61,175,191,142,144,96,103,116,111,211,163,55,224,62,220,170,238,222,91,104,212,208,166,161, +123,167,190,78,55,223,239,86,186,108,104,170,158,87,101,180,211,100,64,178,205,236,128,154,239,190,66,82,181,200, +239,236,59,85,239,192,100,171,18,60,191,95,148,136,37,119,28,152,120,233,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,239,164,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,127,81,72,14, +220,45,96,201,102,114,29,191,83,44,220,211,107,244,76,236,24,219,253,254,29,3,174,54,168,252,145,255,210,139, +174,201,142,141,161,155,51,212,222,186,209,202,136,117,173,114,39,181,138,217,161,57,115,162,208,57,26,170,92,146, +27,200,28,188,43,129,158,226,136,82,55,98,137,129,242,71,105,29,169,229,198,99,151,216,186,162,204,180,67,56, +110,115,168,66,98,157,21,17,99,141,66,51,178,162,145,99,201,142,94,212,111,53,152,253,28,74,140,146,54,175, +208,166,63,146,180,5,21,202,35,86,203,63,50,166,98,203,102,10,110,71,63,213,46,35,237,63,172,44,152,27, +48,177,69,15,7,25,154,100,88,134,154,234,196,167,182,16,81,190,149,101,65,6,68,44,16,26,34,212,73,86, +20,80,252,172,11,176,204,36,46,219,252,156,237,252,157,31,191,251,249,135,104,60,54,100,187,16,143,52,66,132, +138,13,59,178,68,55,90,196,157,85,252,208,11,163,172,176,245,202,25,0,4,143,108,214,249,125,4,177,237,92, +175,49,19,186,200,217,68,42,7,27,35,221,126,60,142,117,120,18,3,12,227,22,85,90,131,49,96,42,93,202, +220,211,246,71,31,253,145,120,76,67,183,114,163,173,213,70,110,164,34,131,106,11,19,140,118,1,138,218,135,141, +46,117,50,120,20,145,84,121,89,23,16,165,95,220,204,148,86,251,157,174,191,188,163,119,210,69,233,163,67,139, +30,157,52,183,35,174,143,189,23,20,121,1,227,100,19,168,186,92,33,88,156,76,103,157,115,128,162,190,6,151, +111,145,140,250,38,215,236,120,156,173,76,60,112,103,237,48,147,3,223,191,58,159,252,33,39,218,186,99,18,203, +68,56,252,8,165,18,120,232,88,178,67,119,30,179,128,121,173,226,30,225,123,74,87,36,177,170,203,114,64,232, +128,88,200,4,229,190,203,80,225,90,15,144,87,102,192,159,242,18,147,79,162,47,59,130,40,144,126,246,250,7, +101,78,132,188,116,66,184,20,33,129,221,67,142,199,3,6,218,171,20,221,223,127,149,105,9,110,244,25,130,171, +182,106,200,27,81,216,140,62,195,239,150,127,134,85,204,63,131,247,188,112,28,121,139,246,41,168,123,215,91,22, +28,116,13,123,139,167,73,9,106,227,182,120,107,26,232,247,77,107,5,174,124,244,101,87,9,86,108,116,143,233, +35,180,152,46,49,5,142,225,250,128,232,208,19,157,47,177,32,47,49,240,144,228,238,46,177,152,242,16,51,78, +136,216,183,77,150,122,76,96,115,243,72,139,197,83,98,17,116,161,3,188,10,204,247,98,151,20,210,184,61,26, +114,163,101,49,154,178,174,98,9,217,58,222,134,96,132,226,94,44,185,17,63,101,110,155,236,178,187,142,77,235, +22,110,91,128,249,22,73,161,202,196,116,150,93,152,89,246,68,156,51,189,200,150,162,189,130,224,189,197,143,206, +47,250,216,2,45,254,222,62,58,8,150,222,246,150,114,203,53,89,27,186,143,33,159,55,56,141,134,38,85,108, +248,3,111,95,227,229,198,65,228,190,86,223,151,207,158,134,201,129,22,130,24,98,207,158,61,237,237,81,104,143, +186,128,153,122,242,132,185,133,90,138,201,121,103,129,107,21,158,156,7,105,32,67,58,56,106,140,143,74,88,50, +192,124,192,42,136,78,177,3,199,142,234,89,14,103,159,11,188,126,106,18,93,38,89,85,129,42,222,80,87,142, +29,11,4,31,26,183,16,137,84,22,140,123,13,40,14,200,29,247,247,143,74,251,99,83,217,85,102,64,57,26, +22,137,129,157,190,129,134,171,28,80,254,56,40,134,223,169,170,33,249,155,127,74,254,243,60,142,182,206,85,233, +217,217,237,237,109,114,251,44,209,102,115,246,116,58,157,158,217,155,77,196,135,220,62,253,62,183,95,225,206,43, +254,64,250,223,226,150,156,174,70,163,104,128,90,201,7,184,33,74,195,151,21,72,99,244,221,13,138,163,193,7, +10,76,220,16,240,24,151,16,217,122,235,107,4,131,194,238,226,1,66,144,251,47,187,107,175,156,51,114,85,59, +192,240,165,146,246,194,225,9,102,3,140,199,50,177,15,142,57,12,56,127,215,54,189,54,147,254,233,34,138,201, +126,117,229,247,198,171,43,54,200,65,213,140,17,74,228,223,85,81,177,212,207,72,235,246,37,68,72,224,129,36, +183,150,34,32,232,110,67,112,117,69,27,163,39,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,189,10,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,190,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,155,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,103,12,230,64,244,190,163,162,5,28,138,145,174,157,149,5,140,114,189,171,180,66,105,221,171, +152,252,156,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,151,180,31,205,35,158,24,77,103,244,254,75,202,34,44,253,82,208,133, +86,69,95,141,135,175,197,65,188,12,51,145,184,147,241,171,44,191,182,11,183,156,13,103,137,22,115,243,37,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,71,3,20,64,40,218, +49,129,11,244,177,145,83,1,205,156,31,36,125,126,242,240,103,69,159,111,141,192,90,222,73,75,205,217,234,242, +6,208,58,10,250,95,37,218,50,11,125,223,160,11,254,42,113,241,33,196,20,239,37,110,11,42,174,52,27,56, +234,123,63,138,62,5,103,55,194,95,75,65,25,49,7,231,89,127,11,98,26,56,87,122,224,92,76,181,66,251, +29,116,246,45,92,84,208,78,199,89,152,111,21,44,190,133,37,98,159,60,225,123,106,101,252,71,116,32,250,152, +29,233,218,190,93,48,121,127,87,76,185,23,248,131,236,153,33,88,233,42,102,113,216,33,28,82,184,139,79,189, +64,135,187,68,232,125,159,128,226,246,90,210,11,115,12,12,189,128,48,54,111,132,57,196,232,129,79,65,218,241, +22,123,3,196,189,252,70,198,236,179,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,125,47,89,151,82,167, +240,32,170,223,184,16,83,73,29,225,27,39,14,38,157,242,60,197,60,171,210,111,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,154,86,203,134,204,245,12,104,159,31,168,247,109, +219,233,189,154,231,248,175,23,248,152,124,112,225,55,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,167,172,226,119,29,228,175,85,162,166,155,125,237,237,197,58,38,69,42,198,95,9,136,247, +77,159,152,11,67,59,65,252,138,205,230,151,232,177,57,38,209,30,237,76,227,185,200,227,87,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,188, +90,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,149,144,139, +130,128,185,104,46,191,21,175,232,123,70,47,49,175,46,227,192,168,152,76,136,25,75,175,124,106,189,101,151,39, +198,67,115,236,14,215,45,116,73,218,165,183,29,5,222,73,239,200,89,136,122,233,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,47,248,54,68,199,112,110,232,92,49,255,5,158,159,89,182,111,160,189,11,32,184,224,221,112,201,251, +242,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,95,220,195,124,240,122,192,227,217,67,111,145,16,195,96, +103,98,28,6,19,37,192,73,65,19,122,72,41,2,210,243,226,64,19,7,149,26,106,209,182,84,146,222,190,201, +79,151,66,224,123,48,74,174,250,237,130,211,82,194,3,17,249,161,140,167,108,120,182,112,103,207,206,239,167,203, +123,113,126,113,225,254,245,217,224,189,249,47,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,173,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,188,50,113,79,50,203,7,117,16, +224,164,140,215,140,175,251,162,251,200,154,110,246,117,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, +179,59,156,182,249,134,251,23,22,187,219,74,75,147,147,190,147,14,37,106,119,60,213,244,118,211,27,169,4,81, +60,126,147,193,36,251,218,177,88,132,89,163,154,98,69,247,198,195,151,34,149,248,63,148,125,88,35,102,102,79, +154,210,86,137,173,252,203,14,169,116,60,158,210,12,71,167,183,34,44,160,143,78,238,208,215,148,12,237,225,32, +35,40,172,129,20,169,248,215,104,136,113,248,145,218,183,126,202,227,190,13,204,194,68,123,190,236,94,171,120,38, +246,38,54,92,114,185,120,182,228,84,238,157,129,135,28,29,233,196,143,113,180,170,157,163,215,87,158,141,199,25, +69,144,111,208,131,145,119,124,196,65,252,125,93,194,221,136,62,38,244,138,188,201,170,201,115,156,102,202,77,44, +236,228,74,151,197,200,25,244,160,84,27,250,35,37,140,110,39,107,20,52,90,105,83,128,153,60,237,0,36,82, +182,249,77,109,116,122,160,31,107,47,163,213,102,178,42,107,144,118,235,191,91,210,135,103,212,4,38,248,110,226, +32,74,233,2,129,237,233,226,95,254,109,69,255,46,71,91,125,3,38,69,236,198,100,251,201,139,105,123,224,169, +134,220,162,227,8,111,239,236,36,71,53,192,140,170,59,52,166,218,79,158,38,47,188,82,211,229,241,239,236,200, +119,49,54,72,118,248,128,95,14,129,198,53,52,209,188,11,185,162,152,89,204,34,45,52,52,206,146,249,117,196, +233,103,100,198,45,98,145,71,133,151,23,57,182,101,127,185,162,70,128,195,48,31,255,7,166,193,173,137,51,110, +120,201,75,31,151,203,27,138,146,135,243,70,68,122,109,98,122,102,173,196,246,238,51,156,81,152,116,241,255,84, +84,202,255,143,81,41,155,168,160,219,30,36,38,186,88,198,37,59,160,99,98,233,208,159,109,96,240,92,211,121, +22,206,206,143,188,160,35,92,0,62,198,174,13,39,157,112,139,72,78,221,102,80,93,223,181,63,161,251,10,163, +117,178,212,206,166,126,189,236,118,75,123,20,174,255,171,21,253,202,50,35,82,175,25,253,69,49,53,136,231,135, +12,249,221,64,154,5,226,82,228,216,76,52,246,104,90,216,48,179,250,191,6,202,166,252,61,62,234,25,69,18, +199,11,198,60,158,114,35,242,164,63,103,60,242,204,3,193,57,207,144,192,31,18,182,81,52,160,159,113,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,151,182,243,58,254,157,225,175,13,255,236,248,192,234,41,111,45,62,63,14,187,213,251,135,221,234, +97,3,122,19,71,244,195,54,227,64,96,149,185,45,194,106,0,111,98,224,17,45,72,17,143,148,86,208,29,21, +248,252,211,116,52,221,62,125,126,243,244,249,183,255,197,173,117,168,71,138,195,224,87,241,109,111,118,108,99,6, +216,11,123,189,247,222,139,167,79,62,15,228,131,73,79,222,253,126,217,195,142,57,114,189,95,10,77,46,146,144, +228,223,66,242,220,19,170,45,65,73,145,235,156,41,237,140,40,82,70,7,174,132,81,134,249,227,219,158,52,18, +201,200,112,127,100,17,209,143,68,150,118,186,118,21,44,237,231,18,242,161,165,47,222,120,177,62,69,91,112,196, +180,193,95,71,242,117,27,32,104,217,61,89,206,86,139,229,38,126,212,201,55,57,106,200,17,95,170,93,221,220, +240,97,200,127,195,121,15,167,13,46,30,189,19,48,53,157,42,144,167,148,142,107,131,77,199,6,124,254,79,40, +60,17,42,211,164,85,238,149,77,135,160,78,30,52,174,70,158,24,180,29,52,31,200,255,15,157,191,30,235,124, +176,198,174,214,196,246,18,208,68,180,205,4,170,191,177,55,181,237,178,43,190,113,245,162,190,129,13,176,231,11, +13,40,186,197,171,48,36,226,95,229,242,240,244,27,182,158,62,198,181,202,115,220,116,146,88,71,146,189,237,7, +33,1,0,8,72,128,10,2,180,158,229,10,81,114,200,242,103,127,148,229,189,53,82,224,67,190,163,55,96,228, +31,99,241,221,136,197,104,191,243,27,45,186,45,191,160,237,11,73,133,165,142,160,78,83,226,43,215,123,88,139, +200,136,126,135,205,95,111,60,129,84,31,218,9,42,55,110,60,18,38,118,16,173,152,150,86,49,197,200,92,21, +199,213,235,38,190,231,234,211,66,152,98,247,136,137,36,31,113,145,21,46,135,219,104,173,35,146,194,125,206,164, +203,89,159,18,58,161,207,167,90,254,221,62,85,63,53,87,11,20,154,0,72,192,88,151,171,233,116,86,1,35, +220,24,194,170,190,111,61,162,199,45,61,166,139,58,208,241,186,3,148,162,126,192,78,88,184,9,255,216,210,13, +39,32,128,16,51,129,142,189,85,223,159,219,237,185,238,187,66,53,116,5,182,241,89,187,113,125,239,34,108,145, +187,245,156,80,98,248,232,103,55,171,118,179,154,96,178,221,34,248,125,67,128,125,54,229,110,65,136,178,99,243, +224,8,45,231,103,91,132,67,236,94,125,255,168,195,22,23,1,175,99,175,123,208,124,179,67,25,23,180,71,95, +129,79,202,206,210,198,102,135,51,115,32,67,111,254,175,99,69,142,55,89,59,239,122,5,20,114,25,242,139,103, +36,41,145,110,226,66,164,246,142,242,213,68,65,174,178,26,202,5,223,33,157,163,205,83,1,203,190,128,213,207, +9,72,221,149,76,180,31,97,43,85,121,17,54,239,109,200,64,4,217,104,227,83,19,160,163,56,48,243,128,174, +47,22,217,199,172,3,119,104,99,175,167,171,109,40,249,208,203,222,123,197,7,127,101,40,249,40,82,217,223,18, +76,164,144,121,194,148,20,169,83,66,75,45,50,173,172,72,69,26,162,0,254,69,158,230,111,43,205,84,38,70, +169,227,133,208,121,202,209,32,235,183,227,212,46,140,193,105,16,223,37,21,73,234,114,154,130,27,208,207,215,146, +25,145,253,220,76,142,231,34,203,88,42,244,72,199,65,77,200,132,201,64,228,158,248,139,147,147,104,189,201,223, +81,90,164,30,155,37,178,112,52,146,218,206,115,189,188,9,3,143,42,101,153,48,186,184,102,186,136,151,56,158, +130,209,191,42,98,234,97,196,212,255,245,136,249,233,206,157,6,208,193,66,56,202,2,85,179,230,245,143,223,121, +187,188,187,15,133,177,80,205,124,3,250,97,65,15,225,122,171,192,50,210,31,243,234,47,127,78,251,79,246,23, +140,220,166,12,30,230,175,121,115,228,0,83,232,75,114,61,157,162,9,249,44,155,150,125,167,195,243,61,34,68, +100,165,69,146,25,166,10,81,36,133,75,69,110,10,111,177,201,11,34,75,241,71,54,34,85,38,210,162,96,74, +190,174,146,79,141,48,89,102,35,42,87,52,4,23,70,141,28,247,35,176,193,8,35,97,164,98,202,188,174,212, +49,172,82,39,241,0,190,63,117,63,15,194,148,189,112,176,99,122,111,129,127,104,224,201,221,96,82,84,157,183, +53,212,22,186,58,179,109,237,232,43,123,131,188,145,88,207,58,203,11,41,55,52,252,110,236,44,21,249,246,225, +37,246,218,9,237,186,19,214,79,43,244,131,122,127,147,238,179,4,200,48,236,118,249,189,4,65,180,74,248,28, +195,138,204,134,79,72,154,182,159,168,136,115,9,222,228,253,39,136,247,112,234,32,55,149,9,13,193,225,23,63, +99,130,211,213,241,143,140,240,251,125,60,99,94,224,146,228,77,169,70,223,210,194,7,229,2,236,118,36,59,134, +6,81,219,195,248,166,62,228,74,50,55,155,111,184,100,13,153,44,206,235,83,110,143,54,53,107,125,41,51,15, +230,55,197,50,180,176,99,50,191,185,117,45,158,108,173,253,214,69,110,174,110,48,235,168,182,134,1,34,151,55, +168,148,230,241,106,109,23,179,189,195,106,241,236,216,182,179,145,121,180,250,244,7,150,173,99,239,33,109,143,62, +144,129,68,55,220,123,235,189,11,51,37,4,17,189,62,253,253,238,57,231,159,138,213,126,75,25,1,109,239,120, +232,114,61,128,191,177,252,240,35,134,245,223,63,85,252,251,66,111,4,76,240,55,13,62,118,11,248,27,40,151, +48,8,255,182,25,178,131,240,243,3,93,240,55,210,29,118,37,2,58,107,43,118,7,254,70,43,196,79,90,28, +0,160,243,255,252,37,0,46,202,126,176,6,127,68,38,20,207,208,11,203,35,144,79,190,35,82,186,141,105,188, +15,248,38,137,104,166,145,32,213,4,43,245,169,77,79,4,229,93,188,108,177,39,109,212,255,124,240,85,32,255, +125,201,210,79,102,80,175,200,168,255,251,223,247,35,219,145,72,232,149,191,255,36,250,195,234,127,216,63,206,156, +13,125,161,175,223,253,55,108,25,49,141,213,220,207,248,222,141,206,111,60,240,246,245,174,170,176,219,231,229,98, +177,181,199,43,179,49,251,199,224,56,86,103,101,76,111,145,112,176,239,112,91,208,210,111,183,254,29,116,173,195, +94,210,207,178,249,17,111,163,26,23,220,129,235,143,110,97,193,188,177,206,108,191,236,117,153,152,177,106,223,51, +71,112,115,98,139,31,206,167,230,119,107,78,171,126,53,152,205,127,118,195,237,89,166,111,48,69,157,247,160,62, +236,219,24,242,108,25,45,159,29,150,137,157,87,1,254,113,137,95,62,157,211,218,74,203,76,172,191,1,115,90, +182,169,234,106,62,158,10,207,150,20,99,54,239,151,191,97,47,119,163,145,213,108,129,189,45,12,182,20,205,88, +239,237,116,175,185,184,190,198,103,97,196,125,175,245,97,75,101,74,99,200,227,143,109,142,201,38,179,189,136,217, +101,214,63,45,127,107,43,249,185,242,215,250,204,41,198,106,55,199,222,126,35,81,176,196,62,48,88,209,232,239, +172,181,171,112,119,170,223,85,25,159,211,9,186,147,23,224,170,50,198,135,137,219,193,105,231,155,241,235,159,125, +47,12,206,178,35,190,255,124,135,101,134,157,165,126,175,232,200,149,191,12,175,239,157,87,123,235,211,190,219,252, +31,235,204,85,252,122,82,5,62,151,169,34,173,89,178,41,245,62,87,47,138,209,63,213,222,132,56,75,124,237, +121,98,102,125,159,232,237,213,97,243,200,164,88,254,197,231,196,87,51,84,101,253,119,120,253,117,176,207,81,232, +236,99,32,71,205,107,39,247,26,159,195,63,239,103,139,179,100,255,215,255,175,154,139,16,223,199,124,197,11,162, +188,211,231,254,10,199,63,141,112,148,173,38,62,135,212,217,127,41,213,139,21,62,103,241,194,26,151,67,110,111, +103,111,227,196,194,223,56,185,55,251,92,100,79,58,143,85,139,127,27,23,189,254,69,161,157,57,136,243,230,185, +84,228,209,225,115,156,86,251,175,28,155,176,229,100,108,219,171,116,88,219,232,177,147,71,181,228,194,142,147,91, +81,236,202,129,239,125,178,147,143,230,79,59,113,230,207,7,107,7,250,249,137,195,125,159,67,92,9,22,103,238, +250,189,138,254,242,190,253,210,113,239,253,87,124,223,70,69,58,115,84,56,179,147,226,115,219,253,245,242,247,91, +22,248,189,165,248,189,190,254,178,215,127,219,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,247,185,240,205,97,241,93,193,232,170,192,120,92,249,91, +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,190,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,63,39,168,19,60,190,26,145,78,184,115,30,137,172,186,210,113,14,249,33,47,28, +120,183,133,29,28,156,115,133,171,30,255,180,174,137,52,63,254,209,95,32,73,61,188,179,11,204,166,7,128,10, +28,25,167,53,126,64,154,236,43,15,202,35,63,15,108,150,148,57,41,2,241,72,76,69,32,9,182,160,240,163, +178,103,92,193,17,20,81,45,52,133,207,30,55,63,222,226,125,224,113,112,80,10,45,124,72,21,224,4,48,69, +33,235,149,55,235,109,222,226,27,118,155,55,202,115,40,182,191,164,73,98,207,192,62,146,37,214,181,184,36,148, +118,78,107,170,62,166,85,186,30,193,222,139,87,5,188,166,2,168,17,40,174,40,110,155,79,94,63,202,165,74, +200,61,245,57,59,145,108,188,89,156,156,95,228,96,231,54,47,3,95,160,120,138,126,158,97,105,187,18,151,88, +241,212,188,48,51,60,146,218,217,102,0,244,224,2,146,123,131,157,196,233,106,177,189,215,155,172,170,109,91,195, +212,204,83,134,59,168,253,177,189,170,252,144,108,104,112,200,116,113,83,117,107,80,27,114,111,71,252,176,210,59, +183,189,157,18,95,83,47,168,53,25,132,3,212,22,146,100,59,79,46,76,151,176,23,193,227,113,167,247,85,249, +0,4,58,132,250,123,144,121,116,82,176,158,100,1,82,152,158,37,135,27,170,32,160,146,50,216,68,182,187,69, +77,174,199,112,110,233,115,32,239,90,220,39,1,216,60,191,102,36,214,64,222,214,43,38,1,58,93,15,3,8, +42,245,0,126,9,247,98,227,196,220,157,48,28,112,128,37,240,172,180,213,78,159,29,242,119,95,7,244,222,160, +195,100,241,59,218,134,59,36,22,107,145,158,192,56,43,14,101,96,1,17,164,179,243,72,91,104,67,233,112,129, +171,234,80,8,113,23,29,240,13,40,200,59,110,11,226,122,4,162,118,241,192,49,14,120,85,240,86,243,213,179, +77,188,178,237,103,201,211,233,130,113,226,87,20,55,224,131,137,210,230,65,172,221,247,81,184,50,172,90,234,184, +223,178,168,162,0,105,2,111,120,173,114,0,9,40,59,123,28,7,150,235,162,140,147,53,153,126,247,7,134,239, +134,101,93,45,160,50,130,38,188,46,141,99,16,214,253,105,99,80,165,144,159,179,150,12,226,106,167,250,83,23, +202,254,71,176,199,92,96,219,143,81,8,213,158,99,255,70,77,19,206,85,123,11,180,45,79,81,200,160,7,200, +205,163,173,170,202,126,62,187,58,248,22,126,38,249,77,22,160,34,238,179,179,168,190,114,202,251,207,87,13,19, +241,195,88,253,206,227,11,20,194,228,192,23,147,36,201,150,255,189,114,54,168,175,68,11,242,140,213,38,191,251, +232,207,51,78,195,103,116,113,254,71,142,77,168,45,240,232,212,35,160,209,67,70,10,97,240,188,47,59,46,217, +200,143,191,190,12,67,153,102,34,190,238,47,118,198,241,243,245,98,191,183,5,106,183,27,160,78,137,66,16,6, +179,242,250,158,141,80,246,139,108,15,55,243,167,207,135,126,36,135,71,191,50,157,206,47,106,82,27,210,247,250, +140,86,253,204,82,107,127,202,104,199,211,105,66,30,37,16,198,129,141,211,91,152,237,254,148,53,247,202,78,162, +121,164,237,35,221,36,189,181,184,54,149,146,141,8,218,222,35,76,40,174,16,92,66,210,28,174,181,60,171,231, +113,185,0,229,105,4,18,161,76,12,127,68,199,74,214,103,20,199,250,113,13,252,208,108,100,190,103,123,230,183, +193,146,160,52,217,70,111,0,71,198,89,158,37,144,23,94,170,2,26,160,43,63,142,9,172,164,241,228,101,1, +129,165,150,193,235,247,123,38,219,47,111,219,223,255,207,20,160,125,7,245,132,10,63,170,170,246,174,174,226,204, +172,42,152,80,55,126,237,126,196,25,28,126,155,103,190,207,171,107,164,118,196,152,175,159,66,4,251,203,192,236, +46,22,198,199,164,89,251,249,232,32,196,31,121,90,51,9,10,128,102,120,49,48,133,215,41,224,24,168,125,62, +148,185,243,53,111,183,22,147,188,253,109,98,20,2,65,36,128,73,254,26,254,149,175,24,156,163,44,247,219,162, +45,48,188,117,134,31,130,17,51,120,79,113,211,228,43,175,77,24,27,79,166,44,76,159,55,69,70,133,70,242, +132,81,108,95,211,209,130,17,124,11,64,245,194,75,206,248,234,150,169,189,78,206,73,40,251,205,12,21,92,110, +182,184,200,153,89,92,182,88,146,162,180,109,162,200,43,39,154,194,174,92,173,215,88,28,64,173,237,131,101,230, +42,196,56,165,152,142,144,227,135,202,176,176,78,109,64,202,33,115,17,183,5,107,164,162,124,73,250,93,122,40, +74,42,195,31,24,50,2,98,245,193,112,239,10,40,230,13,75,167,242,208,253,166,191,149,141,27,153,240,29,18, +93,60,132,105,192,220,90,198,176,56,127,50,147,222,212,199,83,122,104,239,136,146,145,232,197,143,59,66,2,52, +251,162,9,107,50,132,211,46,189,164,24,203,120,115,2,53,140,75,109,246,40,135,4,112,154,197,178,46,29,79, +174,31,233,207,148,70,78,188,126,117,58,45,124,212,204,174,143,134,41,12,41,251,227,53,166,154,185,95,161,122, +230,157,163,113,242,134,140,191,62,116,54,217,211,59,74,180,111,0,142,231,95,166,101,193,25,66,253,190,65,174, +61,228,10,235,115,248,125,28,49,43,12,233,16,229,70,43,54,199,45,117,10,166,246,115,253,144,246,245,89,202, +138,238,250,134,68,155,72,161,26,168,217,60,195,111,14,73,184,157,173,116,212,241,85,155,200,18,191,117,86,64, +255,11,63,22,209,46,253,52,26,35,181,186,245,18,27,210,201,225,239,221,197,225,245,131,167,50,147,96,173,227, +29,164,233,98,175,29,94,195,124,8,207,133,62,19,105,170,229,222,77,94,89,246,133,249,19,74,170,11,189,164, +114,174,124,192,113,58,115,31,126,9,191,21,73,230,170,63,11,115,97,14,45,69,48,179,162,78,48,246,74,45, +185,60,58,214,197,51,94,195,44,40,2,48,76,86,88,47,47,165,122,188,81,94,114,240,40,241,233,29,178,216, +15,234,53,52,250,159,34,79,134,59,29,99,10,101,21,128,166,254,12,105,23,86,220,158,124,113,47,252,219,128, +57,237,137,95,2,92,103,156,35,182,87,139,109,188,221,251,167,122,21,143,97,61,147,44,80,19,221,147,90,27, +3,111,224,214,130,91,31,126,68,134,140,0,173,0,231,222,182,9,103,156,152,183,66,172,57,82,32,49,228,126, +181,231,121,177,74,161,142,105,174,47,45,129,12,178,53,234,111,171,74,246,140,112,76,113,125,240,215,239,248,248, +89,34,64,246,68,154,224,199,12,181,219,33,181,186,139,219,190,252,9,159,75,126,137,244,223,34,145,153,254,121, +31,13,60,125,116,127,53,215,69,94,13,52,78,126,191,231,84,60,170,154,36,123,132,106,168,117,105,68,88,225, +134,17,122,228,18,129,27,129,65,138,195,204,62,101,244,17,188,223,248,170,109,36,112,247,71,227,194,145,111,7, +70,112,232,227,128,180,44,102,98,31,148,83,243,23,244,19,206,130,146,62,253,48,146,252,36,202,146,91,49,82, +45,69,107,62,92,212,70,6,30,197,93,226,23,33,43,252,184,55,161,139,195,173,130,93,100,111,19,238,206,148, +133,164,130,162,42,163,14,253,83,202,19,100,205,44,242,199,217,247,165,46,41,252,111,253,60,226,4,250,110,2, +23,255,169,23,9,12,73,89,192,196,238,66,161,15,76,181,117,81,243,144,207,232,250,68,205,34,130,221,90,100, +130,196,17,214,224,76,44,4,72,92,146,54,172,155,188,249,149,248,67,142,61,32,106,53,15,235,158,211,220,34, +139,67,218,49,181,136,199,247,124,21,193,69,212,133,243,123,125,114,48,241,23,190,191,29,240,240,129,96,21,150, +220,224,184,170,242,219,53,190,174,222,28,160,254,56,238,179,67,130,126,21,108,181,205,52,219,61,224,219,63,179, +159,251,231,197,104,99,244,82,248,245,185,13,31,21,0,1,247,146,160,170,136,210,24,162,55,111,183,106,254,49, +205,228,35,243,103,172,201,101,79,167,56,166,8,212,192,175,103,125,36,180,162,210,176,136,99,114,230,57,170,196, +13,117,108,244,12,186,7,16,157,83,68,198,248,215,63,246,220,202,138,117,111,224,16,188,200,225,90,154,252,137, +232,248,166,52,29,7,15,240,40,18,225,228,176,74,17,232,195,22,219,157,150,75,148,167,130,191,160,175,155,86, +103,69,142,43,219,90,203,123,45,54,151,253,244,7,68,203,43,93,9,70,84,79,185,190,239,117,164,152,50,158, +141,255,95,187,57,106,245,192,60,116,198,75,253,16,134,253,187,104,124,185,18,237,125,48,41,174,69,229,83,78, +52,122,83,241,46,113,147,175,47,171,90,83,227,191,145,56,36,156,253,11,228,149,245,135,59,213,26,128,45,171, +4,248,59,86,148,152,151,57,99,190,95,130,194,61,4,229,171,194,111,46,38,168,122,94,249,234,40,222,71,113, +181,98,83,192,160,121,133,213,69,53,130,53,98,221,96,207,218,43,199,229,138,131,234,159,239,144,162,126,100,210, +184,76,20,237,21,108,201,111,203,127,199,56,185,94,162,187,80,173,2,234,23,135,42,122,104,0,192,145,76,82, +171,167,115,95,149,0,136,106,99,128,63,172,87,241,76,87,252,71,60,102,101,159,54,121,193,86,170,45,151,132, +119,121,68,148,152,95,89,69,144,160,74,104,111,182,44,50,28,81,50,184,92,123,160,79,52,161,197,108,190,176, +94,191,241,86,29,8,240,163,163,251,180,163,167,32,34,249,39,208,248,30,47,187,69,250,43,203,59,79,114,66, +133,172,240,237,248,212,252,120,11,66,205,180,244,163,131,132,215,30,64,0,170,193,63,44,136,172,126,178,61,253, +237,176,230,207,105,31,24,78,18,29,252,50,54,84,221,90,198,52,239,225,37,194,1,114,42,85,201,204,55,63, +177,17,77,86,94,108,143,59,134,106,213,118,90,188,156,170,156,189,253,113,42,182,160,187,205,59,60,95,149,234, +137,54,80,123,90,4,114,0,28,23,248,135,18,44,68,62,4,243,55,33,106,228,169,71,150,195,193,124,83,102, +161,174,106,205,11,172,150,190,191,206,44,161,79,160,42,64,209,124,241,24,31,86,76,120,181,38,131,185,27,82, +34,94,239,7,175,187,73,241,182,205,115,33,143,250,113,183,58,27,31,239,70,215,124,144,91,86,243,43,30,98, +162,186,144,229,60,191,24,74,181,79,123,74,174,157,182,115,52,242,81,125,125,156,101,115,24,184,147,163,202,139, +97,213,176,38,140,62,128,193,71,160,239,164,56,94,34,55,211,36,214,196,132,90,185,65,248,218,46,96,119,246, +226,243,79,19,184,227,35,92,21,190,209,149,42,47,87,246,248,244,226,108,105,249,247,226,91,137,114,253,0,169, +64,55,37,230,135,212,61,117,127,46,206,43,193,87,22,156,153,196,44,206,179,57,83,213,235,231,227,140,94,53, +101,12,154,55,121,47,209,115,162,135,234,79,248,123,147,232,184,220,31,178,137,4,36,14,9,160,249,182,120,167, +29,56,116,57,189,11,235,93,245,96,111,0,0,219,114,91,248,165,210,157,206,132,190,17,62,107,10,0,3,247, +6,205,23,0,235,175,117,154,151,251,0,206,25,185,168,7,110,135,10,2,190,43,207,85,36,18,78,35,177,35, +146,188,121,92,98,18,53,74,134,187,234,94,32,108,68,77,68,200,247,68,158,63,84,8,16,132,76,25,229,43, +195,23,246,177,18,227,251,102,104,255,153,47,245,15,245,129,141,47,192,159,119,78,209,254,240,208,54,26,25,246, +178,184,210,92,213,98,82,138,143,187,140,80,156,26,34,130,98,221,111,65,50,36,155,81,240,154,223,90,122,67, +240,209,139,197,13,217,122,229,45,214,64,173,150,239,181,139,250,242,174,113,117,138,18,229,2,43,94,24,230,222, +62,127,177,164,127,60,228,202,18,66,125,191,243,198,241,40,124,180,126,124,226,59,254,101,187,78,204,22,105,243, +237,116,111,33,27,128,168,152,107,182,209,165,14,126,41,11,57,48,200,83,216,28,210,245,26,204,69,39,179,210, +238,201,230,150,204,163,67,233,208,182,214,211,212,168,143,227,119,33,104,118,87,21,117,83,134,225,115,185,106,91, +144,61,118,166,60,1,214,147,241,219,27,161,33,160,251,179,59,153,5,173,128,140,230,155,157,192,89,85,61,100, +9,207,53,16,39,28,30,46,27,210,219,115,65,77,1,209,94,126,169,142,197,47,123,174,246,183,122,124,54,251, +116,65,7,184,10,185,241,83,104,38,117,26,78,65,112,85,31,192,24,234,214,2,173,230,153,241,24,182,199,62, +144,10,211,45,192,137,5,87,172,98,161,174,249,33,232,92,85,229,92,225,147,159,31,173,22,101,230,41,21,97, +128,60,245,19,188,189,40,138,68,50,4,78,38,248,45,28,191,47,209,57,80,1,0,74,18,156,98,156,85,80, +78,250,45,13,224,116,68,78,36,163,81,210,53,29,136,151,27,74,126,19,126,25,171,109,101,177,6,208,126,53, +169,154,212,106,244,104,42,73,125,50,97,34,183,4,8,214,52,206,219,207,77,231,229,91,19,49,146,20,106,31, +145,173,136,185,38,132,11,112,17,127,234,174,172,129,90,5,178,218,233,5,23,223,121,29,46,111,251,228,24,79, +21,14,159,158,98,84,176,94,55,150,45,145,74,66,10,46,4,190,222,164,96,47,65,9,170,91,8,130,66,58, +192,66,108,114,104,2,160,57,31,25,120,187,134,201,170,86,111,117,71,3,205,69,8,34,240,215,9,123,35,145, +162,87,29,0,232,215,135,92,121,81,241,42,183,48,49,158,95,32,172,8,106,64,176,6,71,17,218,7,187,18, +172,194,121,84,253,91,167,224,243,42,68,54,160,154,81,6,90,136,196,19,43,247,226,240,242,235,19,106,134,136, +152,167,222,144,118,49,188,65,39,249,143,79,247,6,238,118,228,23,7,34,139,230,205,86,28,44,205,189,222,130, +170,6,212,83,200,244,75,243,77,224,1,81,187,184,205,54,124,0,128,235,8,234,16,158,95,160,25,247,41,100, +188,250,102,155,131,89,213,69,51,253,195,94,222,4,197,47,182,164,113,52,250,235,10,158,89,28,125,35,219,31, +250,195,116,4,33,84,75,176,127,24,99,233,144,88,123,127,136,27,211,186,247,65,71,126,232,8,204,27,248,173, +27,111,207,248,164,35,86,120,76,183,157,168,22,113,23,0,104,84,29,116,170,207,219,39,185,178,250,116,181,192, +3,38,4,240,122,163,158,84,107,87,50,163,31,240,192,33,181,141,242,4,186,5,154,223,181,188,70,46,116,95, +72,229,195,116,22,105,103,94,41,246,76,252,3,100,150,40,31,3,217,33,219,68,165,195,247,53,190,88,120,169, +61,67,251,25,149,45,104,107,145,179,121,237,193,74,182,44,54,1,218,79,180,140,93,171,130,163,62,232,235,228, +174,242,99,250,6,84,169,66,4,165,241,49,159,196,248,88,55,107,97,232,90,52,244,160,23,159,179,89,69,149, +160,86,154,242,121,176,2,75,178,180,250,77,191,136,74,165,243,163,202,94,45,134,166,100,201,14,50,198,86,238, +33,72,0,42,4,158,21,190,237,112,152,60,141,23,21,39,36,31,219,2,213,227,131,138,1,139,211,28,78,57, +179,12,209,38,215,125,150,128,156,199,48,99,39,10,32,236,213,0,220,63,5,205,252,49,40,17,225,165,9,0, +46,106,187,160,93,88,164,199,11,218,169,163,250,191,31,110,140,36,18,168,96,194,61,60,95,189,122,155,156,175, +1,67,26,134,53,40,2,160,206,43,78,191,85,190,173,1,201,46,175,227,237,138,224,24,248,231,110,249,235,178, +235,90,236,243,198,77,91,50,188,116,100,158,37,231,35,134,17,63,128,167,127,172,100,132,189,86,220,140,37,52, +169,209,191,170,96,255,138,158,160,74,66,79,79,238,248,12,250,250,253,13,203,129,124,226,243,235,214,64,253,16, +139,159,7,18,218,1,6,170,192,55,208,83,33,153,63,181,36,13,224,82,41,168,202,223,219,77,84,46,230,223, +33,203,177,168,175,188,161,16,177,223,24,212,122,114,170,103,135,2,211,228,126,33,163,8,138,62,123,13,198,149, +59,203,157,110,193,213,245,62,27,3,40,239,21,115,32,148,163,40,204,206,107,26,95,218,123,137,239,253,117,185, +181,223,212,77,177,11,70,168,246,3,109,124,152,82,225,57,17,107,21,199,223,123,157,221,31,161,166,88,6,5, +166,111,172,143,202,240,133,122,235,197,54,87,225,9,23,213,170,63,74,34,90,149,67,86,117,125,242,167,218,237, +177,135,140,76,243,225,119,194,101,86,193,8,13,182,165,187,35,181,47,210,255,145,95,10,78,104,100,31,151,60, +72,87,209,233,199,179,15,3,117,48,224,156,171,83,198,93,174,218,131,76,76,4,146,114,40,23,208,20,93,202, +53,0,230,24,161,164,20,230,245,253,243,167,80,46,144,174,56,170,3,103,43,40,92,124,205,115,88,8,47,141, +0,141,139,35,207,146,152,73,68,123,167,141,138,8,109,139,141,139,103,42,0,221,3,160,182,149,104,196,17,186, +42,95,2,188,138,12,253,207,2,109,225,185,75,103,169,74,67,74,139,174,191,41,58,104,55,115,17,64,76,21, +24,64,88,99,173,164,217,196,106,184,4,243,41,234,31,127,58,220,44,121,196,107,33,61,248,154,83,143,111,108, +39,160,48,10,27,22,124,34,96,97,252,40,205,70,116,39,91,176,253,203,240,188,86,21,129,199,124,148,158,210, +126,0,98,184,89,41,218,54,164,11,1,66,245,123,161,180,241,95,114,37,173,34,45,21,47,66,123,61,125,0, +97,35,178,214,241,117,130,218,173,181,23,62,227,63,231,55,45,59,187,146,145,11,102,37,77,90,103,28,101,27, +197,64,119,232,83,58,128,193,96,161,16,191,7,143,122,47,133,237,13,210,246,167,29,254,76,186,182,188,177,75, +146,9,197,141,199,226,69,69,113,119,104,161,26,32,133,66,228,58,187,133,155,252,45,182,148,63,124,159,231,103, +149,54,209,223,190,97,117,81,102,94,127,11,237,45,112,53,185,57,181,41,36,193,74,40,37,201,205,238,24,191, +244,54,205,169,248,17,200,201,29,248,184,119,145,177,217,0,207,22,87,237,57,50,26,7,72,189,222,126,183,55, +124,187,225,27,1,17,106,170,33,207,154,111,55,164,235,127,4,198,170,245,2,96,114,85,192,35,101,110,239,192, +150,223,78,178,31,178,179,35,189,125,198,217,221,15,209,101,178,119,163,138,162,102,180,170,223,161,166,11,85,32, +124,9,188,128,53,164,116,55,4,167,162,6,173,197,174,225,214,139,89,216,214,180,239,214,107,129,86,68,31,162, +161,2,7,184,2,206,89,24,167,213,84,229,204,55,235,35,73,79,128,121,55,207,136,88,67,233,93,64,91,168, +165,47,64,145,225,203,160,219,94,44,141,217,23,187,57,191,238,132,245,168,104,70,58,25,138,49,66,33,241,122, +45,239,218,1,5,192,57,91,76,253,145,131,14,98,35,78,208,17,54,138,218,149,191,213,84,56,82,78,195,26, +120,205,21,62,234,67,50,204,1,192,113,6,254,207,85,245,107,115,118,82,114,199,14,0,93,114,51,51,246,231, +87,91,119,117,242,2,63,14,3,158,13,204,26,84,10,59,57,109,155,56,100,2,85,253,243,152,220,225,22,199, +79,180,32,144,172,190,0,21,172,218,191,149,218,169,0,230,224,13,153,184,188,34,140,212,105,208,128,16,141,142, +10,133,226,13,167,39,161,166,56,120,48,85,12,7,197,114,91,204,172,125,78,172,157,143,194,115,10,134,239,212, +217,81,133,147,51,182,26,208,101,154,251,13,36,245,230,108,210,151,166,155,242,106,160,83,154,106,57,162,160,239, +81,230,240,236,184,78,20,192,60,188,190,83,240,144,232,70,36,190,18,222,92,16,50,43,124,185,186,15,181,26, +158,2,126,10,159,36,182,250,27,22,178,196,180,203,183,214,162,190,126,72,107,71,244,174,121,52,15,230,42,190, +79,208,1,51,255,62,148,26,142,158,16,203,238,197,201,79,127,194,136,139,141,155,45,15,213,221,122,4,152,172, +234,0,0,206,193,204,202,249,238,182,130,88,219,247,194,182,46,195,65,122,78,11,234,95,232,78,247,31,75,123, +125,17,74,117,82,3,81,56,41,31,140,162,42,198,192,235,226,89,178,230,72,154,97,36,21,95,5,120,107,245, +208,93,255,15,149,212,141,35,86,94,139,35,122,97,95,233,150,66,139,213,50,193,157,232,216,199,194,177,169,222, +184,237,52,70,133,42,193,125,75,47,149,2,250,174,92,245,189,190,238,53,85,215,104,240,106,15,105,163,164,75, +82,164,121,58,53,246,89,35,62,158,74,236,156,200,172,83,15,234,192,104,149,241,76,104,231,146,20,253,54,147, +91,206,62,145,126,144,151,22,106,174,130,74,193,208,234,174,187,76,191,70,60,101,202,60,71,10,197,247,49,206, +72,181,84,157,86,84,42,15,199,62,74,88,35,105,60,10,118,98,7,252,183,8,212,198,133,53,228,244,194,77, +174,180,211,22,219,79,132,64,165,21,40,135,153,40,90,152,41,59,194,8,232,111,221,193,72,39,204,143,81,108, +10,187,167,131,232,139,33,80,76,169,85,3,55,181,154,72,236,228,213,110,162,230,75,113,88,162,26,170,184,95, +161,74,192,160,54,122,26,35,227,172,112,143,102,115,147,233,87,228,2,24,187,211,218,126,253,35,127,190,120,192, +231,157,192,217,65,173,60,83,81,218,4,102,15,248,26,71,141,23,130,102,244,244,34,144,51,80,212,62,82,9, +15,167,22,80,200,249,205,64,133,194,192,119,82,2,28,153,191,209,78,7,169,18,166,223,99,152,187,244,27,61, +6,163,163,13,251,166,64,245,66,98,240,220,42,50,188,199,7,11,239,98,125,18,120,101,167,88,210,74,166,243, +49,133,242,43,210,157,6,130,160,254,181,252,4,19,216,83,117,135,38,55,92,188,242,148,218,51,234,211,126,178, +66,217,49,177,56,85,33,98,120,59,80,126,151,8,42,100,136,181,61,182,39,197,148,54,153,45,219,60,100,192, +156,117,99,165,205,211,16,149,186,4,35,71,251,117,237,13,218,75,216,26,255,82,213,183,214,171,72,119,43,251, +34,175,6,111,77,168,119,106,214,173,254,32,232,234,90,76,168,238,215,149,152,131,175,161,143,56,142,171,48,179, +95,223,245,144,107,214,48,71,83,133,2,52,166,159,230,250,46,228,165,183,12,130,95,186,62,86,16,138,170,230, +22,139,45,113,162,89,167,116,175,153,54,119,251,158,100,82,234,31,171,155,25,36,227,246,213,137,151,58,161,166, +5,90,71,100,18,172,72,162,244,198,133,16,85,223,127,236,243,5,157,212,72,135,20,25,3,100,220,208,118,127, +84,119,122,152,79,156,250,218,197,239,33,19,67,8,200,203,4,149,220,127,52,237,53,104,133,182,230,19,215,120, +171,253,69,162,245,121,82,254,236,91,114,29,96,20,102,244,30,155,188,17,111,151,142,61,225,192,164,227,116,88, +37,205,211,159,154,184,220,239,103,51,184,151,80,179,15,242,175,136,218,22,252,243,112,59,186,207,218,193,12,119, +213,71,131,59,117,15,159,46,204,203,124,120,207,4,14,32,33,242,124,189,80,13,50,107,37,30,8,185,170,161, +209,28,214,166,77,0,159,170,195,6,29,247,87,203,36,151,162,232,62,10,144,195,56,97,55,244,186,211,46,41, +127,177,68,247,105,123,219,166,103,52,107,168,75,209,164,230,15,246,76,121,197,125,131,203,145,84,195,75,127,143, +231,74,206,39,125,65,183,0,180,126,191,94,250,21,246,204,29,219,175,172,136,24,241,48,223,202,247,120,63,45, +7,18,1,6,110,139,164,111,161,237,143,243,42,230,8,101,99,42,31,106,108,110,110,18,88,133,106,194,190,114, +19,66,242,189,129,252,221,25,107,122,29,211,114,159,216,127,203,252,21,89,195,244,133,165,251,227,72,104,31,8, +157,66,33,92,5,69,192,169,251,52,123,72,137,98,114,217,158,170,123,244,194,231,222,241,4,154,179,196,75,92, +153,154,107,88,170,8,100,7,19,218,161,51,193,231,117,181,200,41,141,0,165,211,241,39,245,185,146,23,40,136, +99,106,191,86,93,244,170,223,92,153,217,198,151,134,15,253,51,255,36,154,195,155,220,251,23,188,132,53,200,90, +141,202,8,247,48,241,237,23,188,53,215,90,160,29,140,129,206,7,153,88,74,212,215,34,2,2,14,128,134,70, +226,187,75,37,82,87,247,177,34,238,243,185,248,77,87,31,208,185,211,81,242,70,187,154,213,172,162,199,7,89, +121,53,116,213,96,176,227,32,251,166,141,219,35,234,98,150,71,207,230,181,75,162,146,121,59,66,159,229,161,10, +149,80,219,196,67,109,83,198,7,38,215,1,255,43,28,223,225,3,154,184,38,199,233,153,175,143,12,63,16,254, +8,48,215,184,153,82,112,107,10,243,9,87,102,174,195,28,216,83,192,50,233,240,9,88,43,124,50,141,177,165, +244,138,151,48,149,41,7,37,17,127,155,163,58,235,111,102,102,233,227,122,254,116,243,81,40,170,2,40,73,28, +173,207,46,62,234,159,135,47,50,103,10,215,107,16,41,254,173,38,242,154,92,133,76,221,134,246,145,97,64,129, +108,49,211,182,22,79,224,211,104,184,5,61,40,101,249,54,29,46,86,129,118,151,10,167,131,146,24,182,94,1, +206,150,106,207,230,232,6,164,83,69,194,129,219,235,5,127,236,5,34,221,18,67,51,96,56,233,37,209,76,230, +71,149,210,158,39,35,48,115,191,45,173,224,65,89,27,137,215,98,89,93,202,67,210,164,143,33,117,226,88,108, +95,78,38,210,69,127,68,211,83,149,237,193,69,74,45,130,177,188,11,134,36,2,147,118,131,90,245,63,101,246, +196,67,187,205,234,238,204,186,46,208,94,3,33,34,119,136,2,61,146,186,105,208,66,167,102,188,73,138,151,130, +0,193,194,81,80,57,52,19,148,169,2,143,88,106,87,71,23,50,17,47,187,136,234,92,53,242,69,76,241,199, +16,53,47,127,41,30,31,146,107,195,23,240,159,218,176,60,207,177,230,36,175,182,28,88,243,60,226,2,221,253, +104,143,71,246,251,144,5,134,111,129,121,48,146,7,149,230,155,163,172,105,220,64,29,183,22,118,210,211,95,148, +239,60,85,177,96,134,209,145,90,219,75,186,253,90,113,236,77,45,67,196,164,172,254,25,134,220,36,52,113,136, +92,53,141,205,140,162,196,0,162,34,58,174,103,126,50,52,74,244,42,35,222,117,144,245,4,52,93,158,161,23, +174,82,151,216,125,123,84,107,225,114,51,162,58,74,68,17,113,197,67,160,196,122,117,128,142,148,152,79,178,207, +205,115,157,247,20,149,26,13,138,230,222,249,54,123,237,206,88,140,209,54,13,119,41,157,176,215,143,91,141,186, +249,59,152,98,151,248,117,240,97,152,45,9,122,36,29,110,3,2,91,150,0,2,54,104,2,254,161,20,93,226, +133,170,75,135,109,87,196,69,10,68,29,124,108,255,85,223,121,52,139,236,39,58,178,240,181,218,170,192,153,47, +229,10,98,118,75,252,172,46,205,190,113,208,40,182,249,226,1,13,87,7,101,182,214,151,5,243,128,141,100,104, +63,174,19,32,88,64,176,32,11,86,117,254,18,65,173,206,238,187,103,202,220,71,1,112,119,161,211,10,41,200, +91,178,124,54,158,46,218,192,7,119,130,24,1,83,89,154,171,223,8,136,83,7,220,177,35,172,207,142,131,224, +48,48,124,186,55,43,117,192,39,17,191,20,114,235,175,215,34,47,17,196,31,77,36,54,94,197,74,119,223,224, +19,13,207,227,51,122,94,112,204,246,94,221,209,51,39,243,190,49,232,69,194,191,46,74,15,55,1,202,56,187, +10,126,202,46,250,164,110,59,202,19,201,249,238,135,171,94,29,184,201,218,24,242,68,245,161,0,174,195,200,42, +40,3,222,195,167,43,170,7,51,77,62,15,176,134,135,126,72,208,213,208,230,158,195,53,175,92,73,226,115,76, +87,160,79,138,245,237,170,20,94,45,212,125,38,11,44,66,85,80,181,2,129,239,44,190,91,248,243,20,138,231, +105,103,175,250,237,243,134,232,161,169,232,1,141,26,35,139,167,212,228,0,145,99,210,60,132,237,43,6,123,110, +124,72,85,27,184,150,2,141,173,219,32,201,4,254,135,127,126,218,67,123,86,142,141,161,38,186,117,236,157,195, +139,222,147,224,110,61,239,122,163,249,52,53,117,123,155,135,77,251,124,240,147,11,12,87,56,236,141,249,186,158, +157,87,225,164,223,4,61,50,240,128,229,131,197,141,30,186,120,77,196,92,203,185,22,124,95,106,185,100,26,109, +22,248,78,225,43,151,113,227,247,117,145,228,77,62,242,75,8,35,25,120,253,188,200,150,250,250,96,111,147,157, +122,165,44,177,8,239,42,223,127,163,222,231,60,185,226,91,165,99,106,124,93,56,134,93,95,5,186,181,6,131, +148,93,42,95,226,227,240,159,7,82,120,238,197,69,7,203,3,3,94,135,16,122,11,24,37,70,57,248,23,217, +63,30,142,73,26,219,30,84,176,201,137,134,113,103,48,75,239,10,246,164,162,97,244,246,179,51,252,137,35,156, +107,123,117,133,14,109,81,134,99,206,140,194,61,47,209,195,12,226,120,150,251,55,60,59,209,192,8,22,164,247, +171,246,16,215,219,111,153,127,211,250,33,131,67,87,245,95,76,91,178,105,202,70,172,57,178,133,151,24,131,57, +52,126,200,50,218,173,177,83,251,75,2,255,31,168,121,33,45,206,157,86,215,245,223,83,23,248,223,125,189,101, +179,255,143,187,235,236,110,219,134,162,223,251,43,216,109,157,3,182,150,247,81,195,248,152,217,81,119,118,151,13, +69,116,24,71,34,37,2,20,165,52,63,190,239,226,17,160,192,74,206,222,11,48,136,55,240,0,61,128,58,184, +55,23,131,207,130,214,175,11,146,57,164,215,95,242,13,248,122,111,100,111,247,186,107,174,246,90,239,139,94,214, +157,143,86,92,214,53,215,115,231,106,249,154,238,197,59,19,130,244,14,147,64,231,1,209,112,93,248,94,94,60, +241,111,20,187,123,196,208,98,224,201,187,112,226,207,47,79,183,240,251,111,252,80,131,27,194,189,77,231,192,22, +60,128,29,139,4,14,119,94,234,78,240,157,21,200,248,213,119,131,19,131,228,200,150,144,28,10,72,142,203,142, +199,34,25,82,91,11,132,183,21,164,244,151,92,47,200,215,221,205,198,111,96,171,77,247,204,117,207,215,94,233, +47,132,164,177,80,161,169,34,80,73,108,37,67,229,39,32,117,168,231,132,123,74,92,226,207,113,137,63,143,138, +149,151,248,61,179,38,58,5,49,195,112,116,132,41,147,186,244,49,49,191,189,110,180,46,189,64,168,30,21,73, +146,125,2,193,186,214,14,150,207,109,37,134,162,20,19,49,22,3,113,44,230,226,137,168,196,76,44,196,145,184, +37,46,139,123,13,241,211,213,141,88,76,29,111,79,12,23,159,22,189,171,133,97,44,252,61,186,74,70,196,89, +244,59,21,141,76,162,255,47,116,175,232,93,103,161,219,81,162,33,117,35,186,13,169,242,57,176,181,220,194,30, +223,24,104,237,231,53,160,181,178,5,90,219,255,155,201,87,250,17,19,240,222,203,188,81,46,86,140,242,110,209, +123,192,163,156,233,104,97,134,153,234,104,166,61,193,91,45,65,144,24,252,86,244,238,176,96,165,163,91,70,80, +233,168,66,101,221,18,247,200,48,206,12,19,70,102,150,189,106,58,221,48,205,185,105,46,154,102,217,32,102,31, +154,103,169,167,44,109,131,97,108,130,174,194,189,32,13,247,218,40,24,252,89,3,3,1,10,100,21,218,197,0, +89,182,131,93,139,105,234,18,248,42,237,90,136,83,183,75,42,27,184,169,65,155,62,165,238,221,205,151,233,79, +232,23,95,255,222,114,119,26,135,223,31,219,208,227,137,239,167,5,178,56,16,203,169,9,215,176,9,37,1,26, +75,159,53,102,98,186,244,191,249,166,111,130,63,48,63,30,55,18,169,54,237,79,76,123,213,180,207,154,170,66, +23,134,225,185,20,197,73,33,112,120,15,3,247,216,10,66,6,122,236,6,21,253,77,195,174,37,173,192,84,113, +173,238,177,7,44,182,183,205,121,248,152,229,141,84,142,70,65,34,85,18,18,229,125,34,139,22,172,198,229,69, +221,84,7,69,160,82,57,204,171,165,93,55,104,237,218,38,133,170,102,64,214,29,148,214,163,243,0,60,42,45, +136,129,63,164,1,134,219,123,192,181,236,154,125,250,32,224,200,56,124,12,195,192,165,129,129,143,214,34,215,101, +141,0,31,193,43,249,34,200,117,233,193,194,209,146,250,231,7,223,221,214,80,16,23,203,43,178,25,84,112,155, +254,158,230,68,37,212,203,75,141,72,27,112,78,221,196,209,12,235,39,110,108,240,138,217,77,12,61,158,241,162, +240,189,104,208,80,110,39,113,190,12,18,93,37,73,198,113,243,220,229,120,166,97,13,7,28,58,157,227,232,196, +102,88,86,102,231,203,164,215,192,91,80,92,121,4,219,110,178,176,4,79,71,52,148,5,227,144,152,249,5,214, +78,96,232,216,25,154,191,140,33,59,160,36,27,90,141,228,254,88,211,250,55,106,103,107,98,178,134,183,5,58, +236,238,47,233,231,214,230,207,147,167,198,173,73,100,158,153,186,112,146,38,126,149,179,191,56,119,88,231,226,215, +236,200,6,24,26,2,230,31,90,142,162,19,63,40,214,14,105,102,246,28,152,26,203,121,72,235,120,123,19,139, +154,12,214,11,107,62,178,53,69,43,43,9,187,52,158,167,200,30,150,27,199,186,233,150,158,225,25,226,141,235, +23,42,52,85,24,222,137,205,199,28,120,201,34,55,101,92,40,236,63,224,167,104,158,228,92,20,40,10,33,185, +120,200,69,202,207,78,185,24,26,250,173,82,12,157,244,80,76,234,180,58,166,122,163,115,192,197,49,50,43,61, +58,94,122,244,132,139,10,5,157,111,144,88,169,199,172,238,113,203,16,236,18,122,251,94,244,103,158,208,8,154, +173,127,151,240,234,212,148,46,53,237,253,221,33,26,95,203,149,20,139,63,167,127,19,112,27,39,55,156,130,226, +14,216,178,206,192,77,202,231,161,152,10,180,80,34,247,2,212,233,136,219,16,194,41,168,150,186,1,41,62,15, +197,84,160,133,164,188,224,185,255,32,229,106,2,36,252,212,32,225,175,38,22,10,31,183,207,74,29,49,197,193, +18,93,214,33,225,227,243,145,240,83,70,194,67,69,27,10,63,53,80,248,152,79,76,42,209,212,165,35,98,58, +34,29,246,15,55,250,223,33,56,83,40,216,49,156,106,125,209,5,219,102,63,186,151,97,120,188,41,214,237,162, +153,75,234,211,39,107,96,180,21,18,79,29,117,107,63,194,115,178,150,209,51,80,75,221,122,246,204,140,110,140, +56,154,252,20,191,149,252,132,201,241,82,33,237,209,26,54,113,182,179,147,151,106,204,158,61,230,197,168,152,86, +12,211,91,144,158,235,115,168,153,27,215,223,64,198,131,35,94,42,237,136,74,71,100,1,103,201,218,81,165,15, +149,174,231,166,183,161,140,215,246,140,25,163,66,94,43,120,237,125,72,124,175,23,240,122,193,94,191,145,132,6, +199,188,100,185,108,237,8,214,86,36,185,248,173,38,185,22,109,215,145,97,121,136,137,138,29,44,15,173,179,158, +136,121,37,115,42,161,158,57,122,202,21,189,164,237,5,198,7,116,138,235,23,56,100,12,124,210,241,239,239,36, +88,103,184,33,47,30,20,138,139,203,36,11,58,197,123,222,75,94,31,220,95,30,127,160,101,247,10,20,4,254, +173,143,227,183,229,160,151,27,146,47,247,109,65,77,250,149,74,5,54,8,229,211,126,149,244,185,195,171,80,42, +179,71,201,23,162,236,16,195,113,182,76,139,90,70,163,156,184,8,111,233,156,70,153,128,99,247,6,237,172,27, +95,224,176,117,76,243,52,146,19,133,215,236,31,74,75,231,85,34,65,233,162,4,104,186,161,250,52,42,185,195, +231,170,35,60,165,106,141,82,161,14,89,81,175,70,47,83,228,211,55,166,233,212,104,26,109,44,39,211,97,221, +230,113,141,180,169,208,16,179,47,154,120,131,203,172,100,46,180,156,168,56,155,7,68,119,230,38,193,117,234,138, +2,157,108,59,245,177,19,227,186,108,9,73,93,108,51,72,209,248,133,94,53,175,244,150,28,237,126,241,82,228, +104,253,66,92,3,57,154,183,92,54,69,179,84,186,194,45,147,45,143,43,237,231,236,124,76,251,250,175,89,240, +70,16,110,183,14,90,222,219,13,232,244,24,198,142,218,75,125,173,118,246,202,110,45,146,17,54,131,221,23,119, +44,168,228,99,13,218,66,153,61,30,255,199,222,113,104,57,114,194,126,133,244,230,225,16,157,244,222,123,47,175, +205,219,231,45,239,188,158,203,120,211,147,127,143,164,25,108,121,103,247,182,94,191,20,64,66,18,48,8,33,56, +159,104,169,249,11,117,245,227,227,215,52,183,139,240,66,133,37,21,122,156,23,44,175,100,28,52,186,39,58,160, +112,43,28,130,179,98,56,212,71,5,254,218,0,34,222,135,175,152,26,239,163,162,86,53,88,90,152,37,245,41, +166,89,189,67,169,14,193,218,236,83,80,16,180,247,41,65,112,179,162,32,98,242,14,196,90,77,168,36,169,9, +66,57,137,165,149,49,5,131,60,148,85,58,5,101,45,20,64,89,67,233,59,118,93,207,72,144,12,12,169,79, +49,75,72,104,53,24,31,98,161,78,91,55,75,58,151,224,157,9,4,20,164,66,169,1,201,48,139,149,82,9, +38,24,228,97,231,28,165,239,64,229,39,8,36,19,144,160,79,129,7,45,154,217,48,36,229,102,36,135,199,232, +100,143,221,56,66,27,104,52,158,71,232,107,45,227,130,36,15,60,62,158,133,196,9,205,196,214,167,77,27,129, +69,101,234,210,166,18,161,34,105,11,205,194,240,249,97,76,121,18,64,54,9,32,39,1,44,119,17,172,156,4, +112,146,129,33,20,198,67,102,141,249,185,42,17,107,229,59,221,33,254,93,19,92,79,223,236,183,183,230,181,234, +148,88,66,84,197,23,18,24,200,247,240,201,177,188,160,192,194,96,181,225,127,102,106,93,124,1,215,234,17,85, +54,62,172,81,27,150,70,240,52,130,169,6,58,226,238,225,86,177,115,115,191,91,28,54,7,100,47,155,27,205, +135,221,225,28,179,175,113,249,207,251,74,75,129,123,111,206,55,145,146,4,178,169,11,10,110,19,84,169,94,133, +161,136,122,199,52,137,191,227,111,253,57,141,192,67,200,179,99,248,224,150,181,66,171,66,157,208,112,145,232,64, +100,177,112,59,33,139,213,161,197,234,69,232,30,50,100,156,241,181,53,6,220,70,91,134,17,7,187,169,45,251, +117,99,203,196,101,240,32,183,62,13,134,39,149,241,9,158,215,224,141,85,251,242,178,253,111,120,139,107,206,119, +158,175,45,143,221,120,174,216,37,61,122,13,159,124,170,157,36,228,80,152,213,222,98,199,134,154,37,57,141,36, +172,99,239,124,197,110,46,139,237,48,35,12,203,35,1,71,226,197,172,25,189,194,246,223,48,48,228,33,226,19, +198,247,243,244,74,253,178,214,186,59,209,114,127,141,132,232,113,45,230,77,184,78,251,109,141,102,155,242,169,181, +92,32,235,167,157,205,222,38,32,208,122,76,117,76,16,146,45,12,33,29,18,97,97,77,167,36,139,87,131,36, +143,34,13,23,200,134,87,9,4,66,145,188,4,177,72,40,178,37,201,2,170,246,242,103,245,25,140,253,117,99, +119,125,149,100,9,12,82,70,24,37,7,217,158,164,247,202,141,93,133,177,167,102,100,206,137,97,208,6,124,112, +38,186,121,3,81,138,49,163,236,102,155,68,54,191,37,11,212,216,241,43,89,62,17,69,237,73,163,29,35,187, +113,38,105,64,17,63,163,211,49,196,28,60,131,64,195,168,8,134,28,146,240,87,114,222,129,15,89,73,6,163, +128,82,36,49,181,158,192,34,233,11,203,40,82,174,164,142,44,34,210,84,81,33,35,169,182,46,135,20,109,32, +48,205,32,232,20,193,91,151,8,64,89,152,154,145,198,41,65,157,73,20,10,0,95,25,8,37,169,9,226,17, +109,73,21,244,64,50,40,253,185,126,43,254,236,95,160,153,105,32,84,220,105,223,251,244,13,224,29,52,12,156, +29,210,195,161,119,109,3,176,126,219,248,19,226,142,25,126,73,249,242,159,244,196,237,137,244,80,74,185,193,213, +211,63,229,60,121,171,224,172,219,236,24,180,5,76,45,234,211,151,180,168,53,188,161,176,174,19,139,58,141,95, +120,246,202,163,111,235,43,64,11,48,84,96,61,183,113,192,8,107,235,42,166,39,118,208,97,107,201,6,77,110, +242,62,184,22,168,52,132,247,3,163,220,126,115,28,215,56,12,93,216,132,253,52,37,77,19,202,179,244,186,147, +118,68,187,187,175,146,103,107,223,245,234,210,81,123,62,239,99,197,143,117,115,228,208,39,223,88,180,47,111,35, +113,210,222,104,39,200,63,158,124,163,71,228,3,228,171,204,219,251,250,92,41,183,184,88,145,117,139,179,64,251, +151,206,49,148,226,8,100,7,34,85,4,67,190,58,19,222,218,152,0,145,81,71,254,135,206,69,38,23,231,82, +118,180,49,32,101,210,110,252,71,214,5,201,30,88,100,144,205,100,13,206,27,19,83,224,46,80,71,234,206,46, +183,24,91,81,23,223,97,240,89,207,131,189,37,22,4,241,227,45,230,154,78,35,7,151,90,1,183,186,197,95, +123,221,242,212,69,80,125,56,194,32,49,190,29,179,218,124,44,74,106,190,173,212,153,253,54,202,138,118,224,146, +201,180,43,212,226,204,90,214,47,75,199,246,152,163,45,192,184,40,201,35,139,96,7,53,37,36,176,69,37,109, +77,12,37,100,94,9,217,27,176,224,21,95,166,100,99,99,113,150,14,250,129,60,57,55,122,111,41,72,79,110, +104,86,58,148,132,10,130,154,160,64,7,122,41,148,46,121,82,113,222,120,144,13,67,22,157,35,168,186,136,180, +6,197,133,2,82,194,204,143,160,13,4,133,235,191,134,8,52,44,217,140,96,243,108,24,206,240,211,47,187,170, +127,152,163,210,204,123,44,125,59,63,196,187,214,22,181,110,222,236,183,139,221,135,117,133,95,203,122,93,94,98, +189,74,79,240,108,239,79,110,50,218,184,72,43,33,90,88,88,157,109,110,56,93,255,36,72,60,176,177,24,170, +20,165,69,213,178,36,110,166,212,34,45,91,212,48,82,15,169,108,91,178,200,126,72,218,42,236,76,103,246,210, +138,147,143,43,78,62,67,113,174,87,71,78,85,143,213,221,84,143,168,83,36,243,154,97,75,35,162,85,160,93, +196,57,78,88,25,228,252,187,108,181,73,105,209,56,29,26,39,152,156,177,88,29,32,227,244,165,100,149,213,46, +69,21,181,243,177,137,250,17,156,200,174,61,225,135,169,23,158,204,157,131,126,103,65,159,163,19,126,107,95,203, +87,57,3,202,83,83,197,237,240,201,206,174,193,191,182,192,158,32,115,142,3,25,75,129,10,253,37,216,132,219, +60,32,132,215,156,42,166,103,126,134,250,211,219,233,101,59,4,81,59,169,2,219,205,244,162,153,138,169,205,220, +183,202,215,206,22,164,124,45,42,223,226,182,7,70,206,122,86,69,250,169,245,84,21,251,141,42,62,86,197,116, +182,38,230,107,208,68,136,39,168,226,99,85,108,167,170,120,69,69,164,154,123,167,124,15,151,163,213,221,165,253, +249,238,223,169,157,122,157,182,120,200,20,242,174,187,118,143,174,234,236,72,213,121,237,128,174,82,151,237,225,252, +165,39,159,148,127,15,233,181,39,126,225,223,83,253,246,63,109,215,193,228,170,13,132,255,202,61,38,97,32,195, +235,169,188,48,158,150,94,166,164,87,31,166,28,228,144,33,96,230,41,115,190,255,158,253,180,139,5,24,94,47, +119,39,88,105,87,210,106,23,173,108,125,168,115,254,34,192,73,82,245,105,214,121,40,109,78,155,244,93,183,180, +31,194,6,73,84,185,238,207,251,37,60,140,32,188,246,132,240,202,214,142,60,194,249,173,192,189,32,47,113,221, +228,236,224,88,199,238,237,166,207,252,248,196,143,125,221,170,184,226,81,180,5,23,14,3,169,68,119,69,144,67, +119,69,48,108,99,134,210,112,223,225,30,23,53,103,144,10,56,137,165,57,138,174,177,225,18,139,100,22,147,187, +15,79,167,167,22,35,21,146,94,210,131,183,199,168,228,238,35,115,211,6,133,233,158,97,241,86,20,91,140,20, +91,88,197,250,65,181,73,54,201,61,174,53,244,146,136,148,92,248,1,107,104,212,48,63,76,72,120,194,31,82, +39,145,52,54,165,178,55,133,108,102,52,105,39,105,45,61,75,189,137,109,20,207,176,141,103,142,236,139,15,215, +242,161,136,123,168,150,233,245,72,181,221,162,106,215,15,78,204,95,204,182,189,145,237,110,70,215,192,244,161,236, +25,6,239,121,86,77,201,229,197,221,115,180,237,229,186,173,91,106,66,84,26,233,194,152,127,110,119,193,165,184, +44,247,77,127,112,102,48,152,153,253,191,184,163,164,182,96,87,149,192,71,12,219,116,105,183,220,80,162,59,100, +141,19,244,172,62,220,8,93,149,123,39,104,152,76,215,3,53,214,78,160,132,26,107,161,194,148,169,146,22,59, +84,121,204,117,112,141,49,215,52,230,215,115,32,2,146,122,234,121,72,98,128,15,192,82,128,5,23,57,243,230, +0,32,124,190,167,138,88,155,187,227,209,219,2,38,64,20,217,22,43,160,128,128,105,172,197,1,39,176,19,156, +0,183,233,122,100,111,122,217,149,175,79,174,172,77,125,134,165,2,11,217,146,30,217,146,158,219,146,72,73,130, +74,184,122,112,245,145,30,116,75,249,99,165,75,169,6,165,154,72,139,170,81,200,142,128,148,145,13,246,90,20, +143,50,118,60,164,205,208,17,183,25,62,163,253,27,45,15,3,147,22,146,230,164,65,217,192,188,157,184,83,250, +60,119,226,19,194,141,77,7,131,93,4,39,83,184,119,168,63,47,117,150,122,15,160,9,70,183,70,35,119,35, +162,117,180,55,228,98,49,149,35,27,170,70,164,130,72,137,127,14,14,236,199,27,78,171,55,224,93,189,45,56, +236,161,253,144,138,21,216,136,58,131,44,141,225,124,39,230,238,240,95,149,193,197,46,77,40,17,2,51,196,234, +29,182,101,45,40,249,246,221,203,153,103,179,75,175,227,165,184,61,240,120,246,76,21,236,224,153,42,0,90,103, +221,51,99,78,42,36,21,240,64,96,201,193,130,139,148,121,105,40,225,89,44,115,55,242,44,181,236,89,59,156, +66,222,162,72,27,41,244,85,193,148,212,208,75,181,108,74,44,32,198,195,194,212,145,64,64,18,169,177,105,73, +161,34,72,80,200,250,148,213,45,215,103,117,123,94,43,116,235,186,147,177,49,142,164,252,27,37,142,100,210,92, +210,116,122,170,221,254,204,123,164,113,203,211,169,98,83,176,243,233,162,73,78,97,110,175,50,159,114,35,94,112, +70,237,227,103,0,213,5,122,253,253,222,76,92,214,76,239,208,245,67,202,160,157,222,35,100,117,202,207,91,238, +247,133,30,34,30,131,60,176,95,197,56,155,50,14,39,153,69,175,202,20,75,139,77,54,203,145,56,106,115,152, +209,155,182,190,106,179,142,114,222,105,167,57,60,235,57,155,223,103,116,65,87,111,254,109,195,175,25,233,221,71, +169,87,2,47,220,79,224,225,106,189,19,182,214,52,94,174,53,143,151,107,45,226,48,137,77,173,187,72,161,214, +109,180,91,197,150,35,128,43,76,252,151,113,196,224,186,249,50,190,188,49,228,214,144,99,75,222,130,60,65,251, +214,51,28,207,51,177,178,0,9,217,87,124,8,174,241,22,233,66,215,142,199,9,21,182,235,108,28,198,100,86, +49,64,38,125,219,213,237,221,166,46,45,190,141,163,157,181,183,127,140,145,75,100,0,237,193,0,151,232,59,198, +249,195,154,74,223,197,20,0,32,182,74,195,211,237,135,23,213,213,233,142,58,163,43,123,71,231,125,142,111,31, +93,80,109,34,228,162,32,192,241,3,131,62,198,251,71,214,145,171,12,164,178,48,166,5,36,42,201,25,235,80, +0,78,75,145,18,143,182,242,14,35,224,100,198,131,14,234,34,174,179,153,225,58,91,78,98,12,189,242,98,201, +169,40,104,74,162,58,243,186,25,180,178,178,225,209,31,215,4,163,212,0,15,22,84,186,136,140,167,179,241,113, +163,50,223,15,11,64,234,4,103,23,177,108,61,122,20,32,252,216,228,196,158,71,255,128,157,141,85,90,79,236, +57,177,231,204,158,11,59,208,72,30,28,80,99,239,77,179,105,238,113,172,22,122,13,10,178,87,106,74,136,21, +70,62,233,178,143,216,231,19,121,111,199,235,89,182,22,203,214,139,150,173,95,193,178,93,119,226,119,52,53,161, +171,228,245,220,213,237,102,123,234,234,22,93,229,71,129,166,132,186,10,199,157,140,161,15,252,27,190,181,173,241, +103,28,224,177,217,164,195,227,1,23,80,29,184,241,183,66,184,151,76,215,126,87,241,26,90,237,198,156,50,217, +17,182,21,160,180,26,188,109,244,32,136,113,81,69,142,35,11,78,129,141,21,145,34,172,213,157,250,120,196,180, +140,140,227,17,51,188,162,30,48,148,74,249,1,3,166,200,206,130,46,59,252,88,170,140,176,244,140,96,147,156, +135,212,53,242,18,244,48,63,9,236,134,205,233,145,34,123,234,206,158,175,30,214,180,85,22,183,131,196,4,171, +171,89,13,128,206,245,13,117,41,115,0,184,83,42,222,167,161,136,8,121,241,144,146,43,164,97,119,175,76,3, +83,97,168,110,125,233,228,109,240,24,173,10,187,185,61,184,238,162,92,206,13,141,131,45,202,197,220,27,121,102, +149,137,198,97,18,208,116,127,119,231,223,247,182,244,247,61,210,194,0,102,211,159,62,32,37,234,136,212,166,45, +230,143,149,42,186,241,49,62,15,78,24,190,62,66,145,179,198,110,152,231,206,192,20,162,107,118,146,107,104,194, +105,163,239,90,64,208,78,114,13,139,127,59,195,213,97,104,28,216,7,144,112,138,17,117,93,164,238,129,4,235, +4,214,146,181,194,24,192,50,247,12,205,128,149,177,68,201,17,174,91,18,130,149,194,147,74,167,25,126,247,180, +60,36,5,143,59,204,52,238,178,73,228,16,2,96,88,69,8,190,219,186,99,68,233,232,205,80,31,60,224,216, +9,121,28,59,61,217,181,89,124,253,4,130,108,148,97,165,32,132,95,147,130,188,5,41,18,145,132,167,166,114, +176,37,141,237,198,194,249,85,70,203,210,57,243,92,124,58,233,99,150,174,177,35,235,140,249,233,152,153,161,116, +107,252,146,59,22,33,47,232,177,34,100,117,177,44,0,121,99,246,91,219,14,27,39,189,150,166,217,97,95,79, +6,178,206,135,228,131,169,136,79,134,151,182,156,10,96,81,183,252,102,51,158,241,167,149,136,203,133,246,225,245, +112,243,22,170,156,15,204,170,72,206,127,174,196,215,30,248,91,194,192,118,188,86,224,21,254,128,127,109,226,151, +194,191,94,197,65,31,3,255,202,211,208,131,9,196,85,153,73,139,23,40,89,244,135,147,20,101,149,182,25,118, +73,201,123,17,254,39,237,200,251,218,198,149,255,191,79,145,178,45,63,203,150,29,219,73,56,76,76,94,203,150, +125,189,219,109,121,101,127,52,143,117,130,154,168,4,153,181,148,158,225,187,191,25,201,138,101,194,190,115,143,72, +158,25,205,104,78,29,6,98,122,139,82,225,125,43,8,155,22,192,217,150,71,145,255,128,58,10,146,205,163,236, +148,31,59,175,38,159,216,84,69,151,236,155,244,56,33,172,185,164,149,4,86,29,113,38,199,57,135,15,98,171, +178,184,233,250,247,254,210,241,59,71,243,162,82,209,39,217,249,220,139,118,163,4,65,246,175,236,225,133,254,20, +209,159,36,222,234,35,202,155,146,78,26,167,105,51,236,168,20,170,226,16,47,224,9,164,248,149,45,24,68,208, +69,103,41,112,3,169,230,172,243,226,201,187,206,115,62,101,66,50,160,232,154,105,159,151,185,53,136,183,62,154, +96,141,4,109,190,112,113,81,126,129,202,143,60,62,114,1,107,208,104,77,204,215,212,248,51,209,153,161,141,42, +246,199,146,73,245,208,254,198,236,113,85,92,177,27,247,36,247,181,188,189,85,96,96,153,42,63,124,88,129,251, +35,72,112,85,162,252,72,46,96,174,209,20,14,182,94,69,156,223,124,47,243,179,177,53,223,122,54,81,20,85, +228,71,153,11,160,165,18,24,2,105,76,207,75,51,222,76,142,234,69,68,243,224,81,113,125,189,248,166,47,40, +110,136,27,20,19,55,40,238,18,35,26,43,141,188,214,134,1,215,57,119,195,192,169,217,164,90,97,58,38,5, +161,234,230,166,94,96,69,206,243,67,94,159,70,43,220,3,46,216,71,168,42,26,4,65,14,128,74,191,7,202, +182,234,191,225,65,75,149,215,246,107,143,84,206,32,150,121,42,96,164,155,210,243,162,166,166,162,166,247,132,21, +82,179,38,64,142,8,43,97,84,143,205,84,227,179,35,5,177,81,79,250,107,225,6,12,154,137,231,241,134,161, +154,224,8,130,155,150,255,95,186,145,147,219,77,94,29,113,188,21,108,206,159,188,195,65,176,252,155,24,225,82, +183,219,219,173,71,76,56,195,247,94,92,167,168,202,77,66,58,81,165,202,183,144,39,98,102,2,131,175,19,81, +69,114,57,145,170,242,98,186,75,208,28,103,165,30,10,123,179,53,42,220,209,24,45,109,236,204,238,187,171,82, +189,111,221,222,254,119,162,93,41,117,225,0,166,102,226,247,49,48,60,199,40,98,121,53,97,21,30,137,59,28, +8,10,49,69,204,75,13,197,247,105,242,152,11,174,152,23,184,247,13,215,58,6,215,83,187,15,50,71,60,83, +205,196,31,183,240,119,250,0,3,171,158,212,55,19,76,16,72,14,165,212,42,129,149,120,132,85,249,61,87,115, +111,235,1,68,213,117,81,73,118,188,40,11,5,98,187,176,180,102,188,171,232,183,242,255,226,225,171,44,224,141, +130,111,108,41,193,157,225,246,182,101,169,13,12,124,45,221,22,169,53,180,153,200,96,6,206,93,155,77,17,19, +206,230,71,133,128,35,6,29,129,182,204,121,180,96,98,166,230,64,130,5,95,230,101,152,28,200,67,136,123,25, +134,68,25,135,50,138,149,157,74,114,192,22,146,117,52,33,82,12,203,3,25,4,127,66,5,236,49,122,52,219, +42,111,47,31,180,204,43,43,248,79,56,85,192,107,76,241,211,209,71,242,166,132,217,247,251,32,231,30,95,173, +238,169,213,202,42,3,129,170,234,174,205,155,68,175,103,44,143,169,88,235,124,192,134,226,32,8,24,90,66,194, +226,197,198,48,47,133,141,212,27,117,201,212,19,136,150,175,192,174,108,1,240,200,197,45,198,244,26,57,54,81, +155,89,151,28,179,200,154,125,237,176,171,226,218,43,33,168,173,161,126,220,202,236,105,197,10,136,123,115,246,103, +183,45,40,114,102,213,48,235,71,172,53,60,144,90,39,73,64,15,180,96,142,194,77,183,41,8,235,211,74,51, +199,207,101,147,233,103,91,231,231,58,183,207,207,97,155,176,206,114,232,59,187,146,173,177,81,252,213,71,147,239, +97,226,48,43,154,184,67,247,32,115,171,120,93,191,36,24,154,163,189,25,52,7,223,61,9,169,254,221,43,201, +232,136,121,24,166,130,100,72,128,211,47,157,0,0,236,173,37,246,23,79,17,200,228,51,5,94,203,133,53,9, +74,253,238,90,251,128,225,98,252,227,166,22,15,114,163,43,86,205,88,181,90,125,46,180,229,208,138,21,214,251, +161,4,3,86,122,230,42,23,103,213,152,2,43,69,8,140,84,92,44,89,205,162,112,61,2,248,53,147,5,4, +217,52,47,214,222,25,78,129,223,130,148,94,113,182,24,83,51,253,187,60,192,220,146,101,53,253,97,102,153,125, +41,110,28,51,124,169,13,252,167,230,21,198,188,210,154,87,104,243,74,50,2,41,130,74,146,109,148,240,121,33, +95,125,17,175,171,242,154,193,177,210,228,161,162,28,247,118,214,17,146,144,186,94,94,22,120,191,241,2,62,163, +173,166,102,253,38,140,6,134,134,229,124,29,33,47,10,128,219,240,99,58,90,70,54,11,51,214,232,245,180,46, +235,104,122,160,186,44,26,255,161,101,77,250,130,24,84,221,132,59,212,70,113,200,14,8,135,252,93,175,102,140, +138,144,145,49,101,185,8,18,103,196,166,209,103,173,221,158,222,139,62,84,94,140,47,86,78,174,193,18,71,144, +243,30,9,184,217,178,121,137,53,192,68,229,188,41,244,247,220,101,133,30,183,112,110,177,166,39,194,172,17,246, +196,47,249,119,166,139,21,118,218,165,170,54,98,7,121,16,244,178,66,23,121,140,220,81,105,26,23,60,44,90, +250,216,251,141,171,114,41,217,242,26,23,89,11,51,151,155,109,72,41,240,176,114,197,196,210,174,214,167,249,139, +66,205,163,215,79,232,73,158,250,167,244,109,145,159,4,167,180,226,185,89,160,163,215,175,222,62,121,247,228,239, +143,207,159,188,60,126,242,242,201,187,223,232,81,145,159,118,147,189,152,62,133,54,165,83,6,77,159,190,17,249, +169,159,118,123,244,74,25,150,139,114,150,196,244,69,253,36,249,76,52,90,252,2,94,177,97,84,19,232,11,72, +15,171,101,254,197,100,6,239,38,172,135,169,207,235,152,103,134,242,186,252,226,33,103,236,127,92,148,96,202,43, +133,249,65,176,242,119,237,198,215,19,195,60,25,37,25,52,233,40,197,102,48,26,100,73,76,124,39,28,95,23, +238,60,206,48,160,204,108,255,168,144,165,142,73,161,189,37,242,228,64,12,217,129,8,2,194,31,8,176,38,94, +73,65,126,45,229,220,19,132,214,61,222,21,164,149,7,30,91,197,144,154,53,154,33,161,44,129,57,214,64,188, +161,10,75,2,10,93,123,128,112,166,197,26,39,223,227,242,101,241,210,107,109,39,220,93,19,119,171,134,45,158, +54,64,180,50,197,68,122,60,84,100,232,40,254,115,209,206,100,215,5,205,252,67,53,204,33,255,88,160,14,115, +238,136,41,173,24,180,143,89,164,141,141,98,42,155,197,87,12,165,54,87,9,153,43,198,184,234,26,85,74,93, +114,240,141,163,22,139,29,243,8,22,161,208,41,190,106,56,118,60,253,8,112,71,201,115,229,102,128,239,233,88, +116,240,215,162,141,7,108,247,212,193,63,211,120,76,184,251,78,85,69,77,84,158,80,102,151,89,215,34,190,34, +93,5,105,204,15,136,242,115,136,61,22,4,214,72,142,81,47,203,182,81,85,244,53,228,209,87,42,160,247,13, +122,223,168,116,194,139,249,44,16,190,48,65,86,26,68,161,10,145,122,194,41,101,229,48,140,6,254,41,196,90, +25,228,39,132,254,208,127,137,35,43,233,5,55,91,233,76,186,127,11,132,183,150,152,70,216,58,113,236,156,82, +18,56,48,51,187,212,53,243,171,194,101,133,1,20,188,45,200,131,147,240,180,161,249,232,184,194,227,15,78,130, +19,32,112,130,140,53,187,4,187,41,208,67,104,137,173,34,180,194,150,17,90,96,91,134,146,208,5,246,42,236, +77,177,135,249,65,231,166,87,173,141,34,33,177,224,90,28,27,88,222,5,188,243,48,189,226,16,206,46,211,225, +188,153,130,80,27,25,97,35,139,174,131,143,81,238,106,254,220,45,177,154,65,216,75,119,119,246,40,126,238,186, +38,82,86,191,60,97,225,78,19,116,135,77,96,163,236,16,102,200,135,77,80,35,44,16,117,1,126,207,236,65, +58,94,173,176,73,232,169,104,206,201,97,227,186,148,38,177,15,142,200,19,66,124,227,90,16,160,83,219,63,233, +50,66,232,83,103,160,51,44,132,113,252,206,33,65,66,47,89,254,195,252,69,229,12,231,65,241,230,229,137,120, +179,44,46,244,179,111,32,175,150,202,130,66,142,147,72,73,77,233,96,160,250,229,209,128,12,147,17,132,44,140, +204,48,116,189,208,14,8,19,59,230,104,57,225,83,195,222,17,176,134,26,29,17,21,36,86,138,69,111,138,65, +65,40,7,71,165,102,84,74,26,53,42,101,5,185,186,88,120,232,53,194,124,222,204,176,161,217,148,103,85,115, +69,194,88,71,40,23,142,80,87,172,197,184,66,93,45,93,146,77,169,155,154,182,181,125,11,110,212,74,105,79, +79,75,233,113,255,41,248,216,138,183,120,27,8,136,182,99,29,180,86,109,205,226,212,231,142,231,30,127,189,46, +51,27,175,163,56,219,8,78,136,77,43,206,165,197,149,56,220,8,73,87,113,75,253,158,153,163,62,31,70,3, +84,253,182,4,63,69,25,218,14,109,134,13,178,177,200,17,175,76,156,29,194,12,192,105,94,83,17,147,144,55, +154,225,116,45,173,75,98,253,100,249,53,100,142,127,26,123,181,25,103,183,225,214,111,36,104,236,137,175,76,249, +212,85,252,20,55,217,81,188,59,160,81,175,177,229,38,221,83,135,206,178,107,40,157,18,118,219,162,40,192,79, +105,148,36,41,140,237,15,112,162,1,192,145,33,26,208,193,220,212,124,31,21,211,75,135,35,198,157,231,37,209, +110,156,12,246,2,52,80,88,63,144,27,59,227,246,16,99,200,141,97,129,29,22,36,86,148,51,212,172,201,53, +137,221,214,185,105,225,153,137,120,202,7,170,65,58,32,102,46,138,16,55,79,238,32,9,20,4,73,163,93,9, +21,93,135,126,18,94,178,200,42,160,161,232,78,66,91,32,215,16,195,164,155,70,187,131,209,110,52,216,73,49, +79,209,204,169,11,195,164,64,209,26,134,162,161,65,154,26,226,82,165,81,234,144,237,247,128,176,133,221,105,161, +247,250,64,224,88,173,209,66,59,26,20,113,181,67,215,18,63,26,100,27,10,154,164,241,117,12,220,28,212,87, +250,127,189,92,86,151,11,214,213,175,71,58,159,227,40,137,246,221,123,253,25,87,243,229,4,10,196,85,215,165, +252,169,98,197,197,21,115,174,249,227,206,211,229,229,101,209,121,6,84,108,81,252,55,215,251,51,149,255,136,179, +152,38,89,66,211,44,165,189,172,71,251,89,159,14,178,1,221,201,118,232,110,182,75,247,178,61,186,159,237,211, +135,89,18,211,71,89,146,208,163,44,73,233,207,89,210,163,143,179,164,79,143,179,100,64,11,196,78,16,59,69, +236,5,98,25,98,63,2,246,134,222,231,249,86,156,164,189,254,96,103,119,111,255,225,163,163,159,31,31,111,209, +119,5,46,204,247,249,25,223,78,6,99,250,216,62,122,124,59,237,199,228,240,176,63,14,214,216,151,12,177,13, +10,119,254,136,113,110,52,127,117,183,21,47,193,238,17,222,126,234,206,204,118,38,182,83,56,219,140,71,122,224, +231,2,76,213,220,230,173,239,247,237,55,146,252,180,133,231,17,232,245,87,43,108,6,35,88,216,171,44,29,12, +182,103,234,12,127,46,109,236,39,187,116,214,64,82,3,153,52,144,158,129,20,153,97,160,97,125,13,67,154,155, +76,243,223,53,252,247,9,200,211,34,44,251,225,176,191,178,140,65,142,229,136,96,203,9,132,233,238,192,1,239, +0,216,72,220,55,18,119,29,228,222,120,172,69,227,170,210,88,228,137,99,17,109,214,209,187,34,123,92,172,77, +178,189,13,230,8,148,54,177,110,102,166,153,144,0,109,59,4,142,35,165,173,140,63,173,220,240,125,215,202,109, +200,135,85,108,223,28,112,103,103,101,247,113,235,77,30,7,56,85,238,79,203,57,140,96,172,135,140,125,72,249, +1,161,49,5,233,238,38,242,78,210,187,40,175,196,38,101,87,51,197,235,101,32,79,28,226,63,238,32,246,129, +12,217,98,83,235,117,92,228,221,127,84,179,73,49,250,224,125,144,190,119,22,6,209,135,139,113,64,188,7,100, +116,246,65,210,113,160,97,236,95,3,189,81,134,240,238,6,130,140,128,235,7,114,191,219,216,230,123,235,204,125, +92,68,236,43,155,218,195,54,203,65,103,231,222,247,94,125,87,4,65,129,47,39,236,184,42,15,16,116,160,201, +33,236,33,78,70,96,243,138,100,149,182,157,189,125,19,72,151,140,169,196,182,55,166,37,182,144,174,194,142,75, +245,56,65,50,65,168,180,192,190,6,74,146,73,66,75,11,220,209,192,146,100,37,161,16,244,2,34,92,66,56, +151,16,187,204,253,113,162,191,105,245,154,72,92,7,220,239,104,102,239,254,15,8,202,27,218,193,118,86,183,19, +221,94,9,147,247,228,247,12,73,239,166,4,44,177,7,7,116,157,55,151,139,98,180,154,127,153,172,230,242,51, +209,78,116,93,48,202,46,216,236,14,167,61,216,132,212,62,252,79,92,248,98,227,61,168,242,157,211,84,18,50, +180,166,87,210,10,62,2,222,237,197,228,65,146,66,234,176,80,248,155,233,83,133,61,186,31,86,16,191,52,76, +236,33,239,76,122,49,112,241,246,240,163,79,198,141,133,95,22,183,165,227,101,10,124,128,164,29,144,180,163,5, +49,95,221,33,170,164,253,176,68,65,241,90,142,240,32,215,132,215,195,143,196,149,243,105,67,142,214,59,161,209, +192,4,171,212,55,5,42,96,135,9,248,89,230,73,23,31,8,85,126,46,41,131,15,82,191,251,232,233,119,31, +248,6,221,207,147,80,133,140,98,63,200,85,243,42,189,121,137,11,97,224,222,176,86,93,157,17,208,155,233,158, +132,222,68,247,202,230,68,201,48,101,240,56,109,21,181,144,2,29,80,145,110,170,39,172,127,120,217,202,44,33, +161,42,152,247,60,47,195,10,239,179,15,97,215,48,239,122,105,8,207,36,131,30,142,164,139,28,15,215,108,228, +137,80,146,238,60,240,196,80,142,118,178,152,100,8,23,35,79,134,12,225,105,230,129,119,177,215,135,49,11,127, +39,14,192,78,244,108,177,138,233,116,5,31,133,99,218,115,97,79,206,54,85,218,175,71,241,202,159,123,10,150, +54,170,243,23,19,149,100,220,51,67,136,126,179,242,138,57,229,238,171,104,159,243,81,194,139,146,106,152,251,186, +172,216,36,251,84,108,144,253,118,7,217,203,77,178,135,101,235,250,163,7,42,195,255,4,59,13,209,73,171,220, +189,191,179,220,233,82,167,236,85,20,86,40,44,118,122,125,181,133,39,64,40,201,94,217,222,63,217,182,10,165, +136,98,24,248,47,139,67,112,198,130,187,115,248,201,8,218,115,119,123,255,206,166,197,97,44,91,141,92,111,155, +153,244,77,125,150,89,104,5,187,24,31,169,4,150,243,183,65,221,240,138,225,207,2,183,69,210,50,5,146,5, +54,203,235,140,133,47,23,78,233,231,64,163,109,3,201,207,129,242,58,227,26,176,231,60,251,61,50,20,139,118, +145,151,169,51,144,223,87,77,211,199,205,223,204,116,205,159,228,85,199,69,102,163,73,166,221,226,108,79,255,194, +173,190,110,125,4,25,130,69,3,207,4,75,223,142,201,161,143,157,175,255,253,172,126,52,195,230,98,241,89,48, +58,231,117,103,94,240,15,23,224,210,87,49,236,147,127,141,42,201,170,206,115,105,57,26,51,209,160,248,197,189, +156,246,207,172,169,199,40,168,78,241,237,76,87,241,250,84,47,64,82,138,98,248,216,53,169,168,59,72,66,193, +39,87,144,184,34,83,135,220,43,74,238,53,215,42,65,238,20,212,223,228,156,109,133,163,184,85,84,138,144,27, +10,206,220,225,250,39,48,123,133,77,202,128,41,44,108,231,6,228,154,168,12,185,82,208,159,90,171,146,99,223, +190,34,155,131,92,114,85,5,18,83,60,113,171,11,46,118,144,115,90,71,113,166,240,207,217,32,7,84,200,13, +78,21,47,89,200,161,130,182,115,143,35,118,83,229,113,80,68,149,39,138,78,22,204,135,233,98,140,217,120,194, +41,222,152,186,178,5,199,77,32,248,47,243,154,11,29,72,104,159,114,237,125,123,38,135,243,214,208,224,81,198, +150,188,146,60,142,243,245,169,110,52,27,139,61,168,2,90,116,81,141,30,157,76,114,41,221,124,75,195,210,127, +57,197,243,243,69,72,138,158,91,144,95,78,4,132,39,247,15,52,109,48,220,130,224,26,212,191,167,173,70,208, +9,62,246,59,89,107,147,229,0,216,207,136,39,171,99,29,48,89,127,89,243,217,141,209,222,240,54,242,199,195, +56,176,142,251,192,80,86,231,202,255,143,253,93,92,158,221,56,42,125,44,49,253,211,121,64,74,182,164,146,128, +78,211,254,114,30,71,22,17,188,115,216,101,160,247,139,172,182,169,139,100,160,3,77,203,246,52,175,27,53,194, +140,225,142,36,49,46,155,174,136,88,114,251,81,222,214,38,107,4,12,242,91,44,113,146,32,77,221,217,200,74, +206,253,254,65,179,119,146,79,152,250,195,60,14,136,17,138,186,18,226,66,64,57,247,127,100,169,243,232,8,43, +93,5,187,187,247,195,39,134,101,156,107,140,16,127,208,5,169,116,210,100,120,251,66,94,104,140,246,175,71,30, +238,233,161,199,58,246,109,35,235,14,180,123,177,144,196,53,70,35,223,245,143,81,151,91,219,248,50,129,185,165, +65,229,79,46,24,35,56,60,199,155,187,174,42,67,0,220,114,94,173,99,179,104,15,119,116,93,211,146,224,87, +26,203,37,31,156,196,250,77,210,169,93,197,206,11,210,93,151,169,70,19,184,219,94,195,207,152,26,34,23,232, +174,7,215,33,35,174,110,190,229,159,142,199,141,42,88,212,93,198,243,195,69,66,123,21,141,251,131,70,110,58, +105,138,17,250,10,143,188,203,135,82,196,206,200,138,212,111,253,212,139,141,124,41,15,152,253,237,199,213,179,41, +81,50,96,198,237,188,252,241,19,210,43,75,231,117,206,233,237,220,171,143,141,70,217,209,102,246,183,163,156,164, +52,16,247,227,188,172,81,28,154,204,185,83,107,114,43,122,61,29,35,19,92,8,172,187,118,6,13,32,43,103, +76,238,190,59,227,20,57,165,50,9,188,154,118,63,14,241,242,254,188,150,139,18,203,170,78,211,121,212,199,134, +123,26,228,148,2,219,96,131,18,59,108,178,150,35,71,22,253,227,237,69,102,231,213,171,65,146,137,141,195,220, +61,61,190,120,63,225,68,155,216,36,122,194,127,206,247,186,194,36,43,212,121,208,189,182,33,169,67,62,192,3, +225,121,74,99,118,49,73,17,254,37,106,19,179,139,13,64,174,18,77,64,144,58,59,106,181,3,102,81,36,95, +232,78,92,130,82,93,184,146,6,164,47,235,76,196,217,41,186,30,91,51,247,77,40,128,212,142,130,201,180,36, +56,219,177,52,190,36,193,171,107,92,27,98,18,156,13,217,182,206,117,157,64,196,190,130,243,242,211,94,77,102, +66,205,17,132,238,17,79,52,80,161,63,68,65,102,237,235,139,80,34,92,149,32,54,0,226,34,201,126,45,185, +213,53,130,63,201,38,157,145,7,55,255,213,125,216,52,129,137,192,201,107,3,163,147,139,207,147,181,92,1,30, +79,211,241,3,196,168,90,195,30,120,77,176,10,190,106,11,96,173,183,214,55,46,70,25,61,78,155,157,78,72, +212,73,88,41,177,218,113,17,2,128,22,153,250,164,45,172,179,216,88,207,131,92,240,90,40,93,209,181,6,220, +102,9,91,58,232,109,12,89,101,210,188,92,62,205,235,125,28,11,48,99,8,52,111,50,225,17,103,160,71,250, +123,124,42,38,142,196,202,169,135,241,53,227,61,5,25,225,100,69,30,185,128,100,68,244,200,136,127,246,144,194, +187,88,34,229,175,183,17,89,99,86,184,101,33,234,76,168,64,76,16,4,193,142,31,227,83,141,197,24,29,145, +82,217,64,46,249,206,41,22,25,40,69,60,10,191,10,193,190,53,93,5,142,142,116,161,243,243,163,230,148,216, +130,122,245,246,120,74,73,8,38,223,129,136,211,73,206,200,217,42,1,101,27,6,19,60,192,254,4,193,152,201, +48,3,141,103,20,144,218,40,170,31,214,51,148,124,69,143,173,143,208,111,37,155,166,170,196,123,57,195,108,144, +79,243,197,79,163,124,186,228,82,117,9,204,229,163,222,171,69,211,201,1,204,196,252,33,127,220,156,175,109,129, +12,158,10,129,156,105,75,153,49,240,37,207,72,53,213,13,162,62,149,70,116,222,182,2,231,251,184,223,171,191, +109,140,32,250,23,3,65,38,99,203,146,5,238,31,227,132,132,141,52,224,255,43,41,34,101,245,93,67,86,107, +127,173,42,100,39,5,187,22,213,138,230,97,204,69,68,98,166,43,131,166,54,1,175,21,87,55,174,148,171,108, +145,139,130,209,117,1,32,126,204,62,65,247,249,188,64,45,118,62,177,3,28,15,180,140,18,164,169,106,36,163, +54,135,218,140,169,235,167,36,70,87,74,90,111,94,1,36,203,209,67,165,213,162,250,100,52,31,198,7,161,44, +26,78,253,63,198,206,67,185,109,24,6,160,191,210,93,209,162,121,86,211,233,59,166,123,239,189,7,156,242,20, +249,28,42,165,216,93,255,123,1,238,236,180,73,200,128,27,154,0,30,227,120,193,79,38,148,247,167,17,184,75, +188,0,45,238,67,21,95,219,59,164,165,185,221,66,3,157,208,219,137,173,119,33,146,155,182,138,198,144,56,198, +193,35,171,230,194,225,127,61,47,7,210,98,84,60,24,136,42,86,24,92,188,92,17,102,7,18,132,81,155,116, +89,161,217,250,78,191,239,241,157,182,151,14,4,184,171,109,117,194,145,75,205,121,134,44,29,124,148,239,251,213, +213,230,252,41,178,39,48,119,209,103,232,231,199,140,34,209,252,230,42,175,241,141,123,191,158,19,196,60,87,146, +86,204,231,74,56,58,222,255,101,103,249,126,194,221,255,143,44,189,121,207,145,56,18,182,127,208,255,140,244,76, +66,154,241,227,218,208,76,176,222,76,176,222,76,176,222,76,136,42,32,7,229,101,180,82,188,91,175,176,204,84, +201,67,6,178,32,152,16,26,217,175,108,235,78,114,224,150,10,106,250,49,82,220,186,136,20,154,94,83,10,65, +57,203,69,123,43,67,71,43,67,71,43,131,76,151,98,236,231,253,150,168,121,151,248,87,24,8,91,169,44,113, +101,188,4,45,236,144,29,144,184,226,9,111,241,123,134,223,224,150,21,245,177,157,241,77,72,212,170,92,33,111, +45,181,237,188,182,58,175,45,114,158,134,78,120,170,236,234,10,144,228,42,68,205,49,198,200,19,235,102,189,109, +240,102,201,120,168,137,9,219,2,149,220,131,221,73,40,178,252,16,157,254,67,197,83,103,173,134,253,10,208,109, +223,164,64,158,184,146,225,133,142,197,35,159,16,157,128,69,217,192,206,40,234,222,131,194,100,91,90,2,233,166, +74,150,228,108,165,37,186,184,45,82,33,111,124,114,143,18,198,221,38,172,207,166,197,227,21,242,63,0,55,191, +200,163,71,245,178,197,206,221,111,5,248,159,171,184,98,242,161,197,99,132,186,74,189,101,74,50,168,117,67,39, +101,13,190,29,173,53,213,247,115,94,98,206,35,208,187,142,120,249,14,20,35,76,83,118,185,174,126,237,215,236, +238,94,205,134,197,126,205,110,239,209,108,163,251,85,109,241,88,81,17,121,23,108,20,17,66,137,53,137,131,165, +212,29,37,147,128,71,58,48,6,35,107,83,197,23,136,131,193,184,161,125,132,2,198,189,0,250,52,187,106,49, +90,243,112,223,98,90,45,234,53,220,171,215,212,40,99,172,110,200,201,100,100,51,70,224,87,24,231,53,93,31, +97,174,54,163,30,127,138,115,40,110,147,184,117,226,214,139,103,73,60,115,226,153,23,131,4,20,65,93,53,99, +96,40,6,238,166,46,135,228,221,197,19,181,215,42,171,136,62,179,29,58,175,26,210,204,18,22,155,235,168,170, +84,33,105,42,92,35,254,36,195,110,20,24,101,178,150,148,76,53,19,231,35,192,57,239,124,139,214,224,61,31, +17,74,85,197,38,54,55,225,74,162,187,29,245,59,18,43,181,21,45,198,214,46,97,58,27,137,166,41,160,125, +35,177,76,162,92,42,223,105,191,137,47,141,135,154,70,29,166,161,85,11,118,215,57,20,195,144,118,199,152,114, +26,206,231,91,204,207,66,126,230,187,114,126,12,165,179,178,232,150,156,151,116,134,71,101,145,231,99,191,122,227, +88,113,0,44,6,171,246,170,218,228,46,213,193,117,83,183,166,183,91,43,62,133,162,98,172,85,16,197,217,75, +151,206,144,46,58,209,159,244,142,190,41,111,106,215,65,255,128,225,182,129,175,157,210,120,235,223,165,240,9,88, +171,76,193,51,170,33,143,129,125,250,120,187,27,184,8,90,117,123,213,17,105,245,232,49,21,65,191,162,97,34, +223,60,226,124,191,253,103,239,207,219,219,54,146,197,97,244,255,251,41,44,254,50,103,0,179,41,75,158,229,156, +3,186,205,199,137,227,196,25,103,25,219,217,70,175,30,13,4,54,77,196,16,192,1,64,75,10,7,223,253,86, +85,87,119,23,64,80,114,206,204,185,235,251,36,22,209,251,86,93,93,93,93,203,184,240,120,91,142,69,11,134, +61,139,30,11,62,35,138,220,6,65,181,102,83,228,109,4,194,190,65,202,217,74,243,25,41,205,55,157,250,23, +24,36,82,202,243,57,74,229,86,231,64,63,224,207,88,31,198,4,161,95,228,3,246,170,59,51,194,169,128,82, +226,220,105,200,151,184,208,100,2,99,117,231,211,63,82,121,62,121,156,237,61,30,234,15,85,190,4,119,73,20, +123,9,220,165,119,36,206,71,150,238,244,132,94,99,152,186,129,185,158,112,54,82,100,191,51,75,102,19,255,15, +56,117,224,24,214,28,64,69,113,142,128,203,71,102,190,203,111,76,241,26,187,162,141,126,106,172,30,220,49,16, +116,45,250,143,67,239,164,207,7,249,34,62,244,76,97,174,76,41,42,52,31,40,120,102,229,124,175,42,48,153, +160,236,119,181,109,39,222,142,193,164,173,182,217,218,106,85,113,128,242,158,219,90,208,154,138,222,173,210,171,188, +0,218,253,247,95,26,32,254,219,60,75,225,53,122,107,126,175,30,132,24,12,60,171,243,180,128,143,38,45,155, +89,99,234,28,136,105,148,98,198,23,116,178,10,147,76,216,6,144,66,249,177,47,201,234,95,114,122,252,88,93, +219,79,92,127,238,63,169,96,234,157,12,125,58,88,144,200,234,121,193,46,41,135,139,21,203,98,126,129,6,69, +124,188,204,62,146,49,243,89,88,197,227,217,77,222,104,52,9,203,81,176,179,83,130,83,189,187,170,150,56,74, +60,28,26,152,81,74,107,0,194,147,163,19,30,202,85,10,145,240,239,89,179,129,120,187,216,71,12,114,85,249, +37,13,27,231,193,197,124,134,11,37,98,144,154,7,120,247,101,224,146,249,46,47,195,194,67,195,27,128,113,184, +62,251,44,116,236,120,216,246,49,161,76,179,174,174,95,229,101,40,177,172,211,235,103,228,147,251,115,6,172,111, +203,183,213,38,164,155,38,171,243,75,196,169,72,1,69,114,107,190,176,199,169,194,56,36,171,34,33,93,106,56, +41,238,124,13,195,162,165,45,136,243,80,231,203,189,244,127,180,54,29,214,186,197,68,251,106,229,81,76,104,65, +213,62,80,226,171,214,228,98,50,53,115,198,55,86,48,159,53,27,114,211,68,149,218,193,149,40,217,89,99,3, +21,138,30,95,215,121,155,94,22,134,150,14,34,146,157,1,189,52,83,115,156,194,161,185,134,11,58,70,161,6, +149,233,250,172,241,215,155,95,163,34,94,244,175,6,48,231,104,227,44,249,60,42,84,22,119,10,167,175,136,119, +92,94,23,93,135,106,187,72,132,126,169,241,236,249,71,26,237,46,112,174,54,182,55,112,248,28,229,199,180,99, +89,119,140,188,124,92,16,100,186,28,168,151,55,177,40,96,162,172,54,243,238,98,149,22,197,37,177,204,5,200, +78,58,37,66,73,175,169,163,211,80,45,134,186,78,28,16,127,19,151,3,84,182,250,134,117,23,98,251,105,113, +70,188,64,192,77,48,9,247,255,130,127,167,147,7,19,178,250,6,9,118,235,47,220,135,79,162,218,32,231,230, +230,193,100,234,234,19,102,93,241,68,240,203,111,5,162,91,49,243,21,156,48,54,70,131,90,149,73,155,109,109, +222,154,155,54,106,226,99,178,107,165,140,21,176,111,226,24,174,191,37,93,42,170,88,149,194,169,151,80,89,42, +117,137,87,58,214,171,42,9,129,243,15,198,171,10,2,239,210,250,50,125,103,0,129,20,176,222,195,8,160,11, +64,147,183,36,148,10,171,131,87,9,95,209,104,5,168,92,96,179,235,22,175,165,77,10,138,58,240,193,81,115, +86,78,10,148,184,212,253,242,174,148,237,225,12,217,138,39,233,188,64,85,132,85,180,210,6,213,142,86,71,172, +187,249,69,180,138,143,52,218,24,169,53,206,43,243,35,86,65,107,15,115,196,88,83,166,79,80,184,218,53,149, +61,129,127,80,233,82,175,206,178,115,181,116,53,30,125,17,45,81,218,167,87,223,50,142,231,57,226,167,182,170, +77,20,236,110,84,92,27,220,6,160,169,237,83,55,144,120,23,250,190,165,190,47,13,140,205,60,104,206,42,232, +255,249,188,34,34,36,67,67,26,91,79,53,212,226,81,182,29,62,199,231,206,127,245,240,28,69,186,229,8,217, +1,225,129,252,209,99,124,182,79,252,99,168,16,239,143,218,89,19,63,132,167,235,114,218,132,230,242,198,146,76, +173,70,26,16,79,107,84,83,71,152,155,60,94,34,25,226,150,176,133,73,64,205,105,231,24,150,162,232,70,129, +142,51,137,126,200,45,144,194,175,181,141,139,57,252,204,133,38,179,124,168,205,201,54,199,188,99,242,246,152,204, +234,188,193,109,167,214,16,68,98,24,203,170,21,4,144,100,221,54,4,49,75,29,173,225,209,61,126,248,89,138, +203,144,121,5,211,44,92,199,9,102,51,175,218,11,253,110,164,90,239,151,111,191,126,245,18,184,220,238,208,56, +7,85,162,189,12,150,30,246,57,226,120,39,64,59,56,187,197,33,65,4,147,238,75,252,198,67,137,170,143,50, +53,203,112,130,16,102,224,147,231,8,190,57,86,101,126,218,4,192,241,66,118,72,210,70,86,177,100,5,216,106, +245,68,159,64,47,216,14,9,48,123,12,156,167,64,175,175,209,110,108,188,99,163,14,168,215,94,103,216,45,181, +130,245,249,30,107,206,138,170,49,148,179,111,43,163,206,73,229,98,2,101,144,152,122,91,69,102,234,37,168,151, +241,195,149,42,131,192,53,134,99,181,156,234,191,150,200,191,201,203,255,149,252,7,59,91,35,17,98,45,57,77, +146,66,175,224,230,121,250,103,212,174,152,21,170,210,161,150,105,102,226,135,169,170,117,104,136,163,120,98,102,21, +180,90,163,5,139,217,79,240,239,171,216,37,76,107,72,168,84,129,145,106,25,162,33,255,148,242,195,96,66,238, +89,13,209,144,27,35,225,223,79,247,116,125,146,224,106,2,170,72,109,199,222,252,245,245,219,211,139,199,15,87, +80,12,211,161,194,84,149,240,239,241,67,250,199,21,116,48,127,153,145,51,0,53,85,186,55,105,114,172,20,246, +171,201,99,141,195,252,243,32,69,12,143,79,196,240,208,238,24,80,86,87,77,67,93,241,221,227,200,127,173,115, +190,43,62,143,236,174,236,220,208,66,79,253,255,178,134,21,13,89,253,191,97,152,152,244,111,25,166,172,116,153, +54,107,185,255,85,217,43,208,111,171,236,239,94,7,163,249,49,154,80,199,195,129,239,42,63,34,110,123,122,130, +150,3,172,147,173,40,150,154,92,67,197,37,82,111,6,190,24,169,193,99,161,155,167,200,83,95,181,51,67,33, +120,100,168,137,228,162,224,45,36,182,213,102,102,3,79,176,205,182,173,174,166,38,180,112,75,167,141,68,216,18, +89,186,29,103,219,80,84,155,226,38,102,62,210,214,58,163,84,187,21,242,141,60,209,62,88,197,248,177,195,174, +45,2,201,55,100,76,248,169,61,190,81,230,248,150,52,232,233,232,129,183,204,37,160,226,112,67,64,125,182,41, +100,67,230,163,47,86,169,22,10,41,17,198,74,58,34,127,168,154,116,133,214,66,142,144,147,188,240,185,90,110, +44,233,53,223,98,243,195,14,137,97,24,63,140,123,7,129,243,251,107,14,119,210,109,77,96,84,46,128,78,216, +156,222,36,248,243,248,70,113,248,214,134,111,33,108,40,33,49,148,205,133,111,109,248,86,237,119,230,147,54,204, +169,170,128,16,245,242,178,82,139,62,213,21,67,156,7,65,23,65,215,101,188,109,76,28,221,73,20,231,144,92, +109,142,45,155,70,229,56,250,42,86,150,172,115,22,31,72,27,30,175,78,5,52,6,84,70,175,118,8,59,128, +39,50,70,247,82,99,245,77,212,235,29,234,138,210,84,218,160,238,39,42,174,202,94,6,50,26,121,133,4,159, +77,142,149,221,117,227,169,170,196,222,99,100,134,99,40,167,48,174,192,201,152,143,66,109,94,48,85,232,137,27, +136,197,61,22,104,29,153,68,79,79,253,136,211,115,28,99,160,220,96,128,158,48,18,177,72,67,250,233,194,49, +216,201,226,88,76,70,90,244,25,112,109,75,204,18,66,90,164,184,108,159,166,141,193,145,113,206,16,161,251,233, +98,160,229,112,123,218,53,127,111,218,53,80,205,239,214,104,33,131,244,55,176,156,223,143,131,219,89,137,247,246, +22,31,15,178,118,155,22,168,112,178,4,176,1,95,16,175,16,127,164,186,157,142,164,189,198,233,87,133,54,99, +5,159,53,104,235,71,101,218,140,21,125,110,108,242,90,15,186,187,176,175,21,143,147,108,222,135,62,49,185,67, +4,40,160,14,121,36,89,85,211,210,96,12,12,255,113,56,68,106,181,22,216,38,197,208,40,78,95,231,66,63, +120,7,251,90,221,38,165,186,78,26,181,78,42,101,169,247,164,238,224,54,232,137,45,68,172,52,87,165,248,14, +95,64,131,253,164,142,78,228,105,132,39,226,172,102,204,140,153,2,141,38,99,247,178,41,25,128,90,191,26,212, +59,109,124,118,90,32,172,128,171,30,79,28,196,244,66,80,251,201,94,253,110,140,148,67,86,29,98,123,89,148, +248,60,81,179,97,135,229,228,57,246,121,83,16,23,230,181,121,7,234,134,17,136,207,91,14,230,63,163,255,107, +57,141,22,201,255,117,12,191,241,34,142,54,55,255,52,87,255,68,225,247,79,30,197,170,58,80,42,111,83,184, +176,254,19,245,240,115,12,150,107,83,231,237,63,183,101,99,90,224,87,92,22,40,49,25,61,152,45,206,78,102, +255,125,110,255,146,56,126,140,213,6,230,75,93,244,245,198,163,9,240,72,80,200,153,76,41,22,49,137,7,195, +233,111,88,78,151,217,174,254,237,245,33,48,93,231,238,198,163,167,40,67,171,12,60,20,179,245,197,205,205,36, +9,44,127,140,249,29,16,51,143,52,136,6,51,109,226,43,114,230,146,210,2,159,69,166,57,92,33,67,55,111, +203,126,55,119,157,42,53,26,78,65,187,44,139,190,173,148,164,85,21,164,229,241,162,92,212,250,41,24,107,66, +97,133,252,172,133,159,243,56,129,40,140,72,162,24,62,132,53,140,26,173,97,52,177,129,52,157,22,81,133,214, +211,70,20,237,159,135,39,29,238,214,14,150,58,65,79,94,68,160,36,200,206,181,224,70,145,72,173,96,92,39, +17,156,25,214,113,54,97,120,153,168,9,3,22,126,250,93,17,2,54,73,218,45,42,90,41,229,141,253,19,15, +125,116,139,213,150,104,154,50,13,165,90,190,212,106,34,158,166,142,150,146,47,246,173,51,150,162,115,98,75,17, +43,226,75,58,130,89,122,252,115,102,211,41,54,48,50,231,91,190,233,189,175,155,32,52,98,20,88,192,96,65, +21,91,154,48,95,107,127,227,121,9,220,30,4,191,210,129,95,85,32,231,7,7,86,21,230,248,58,173,203,232, +247,47,75,122,133,38,63,0,15,168,224,3,100,127,231,171,220,44,147,7,147,223,79,203,233,239,39,191,71,33, +140,201,196,203,169,251,135,7,108,213,126,170,150,63,98,249,130,0,187,1,179,132,24,213,138,0,62,7,217,103, +8,195,175,16,165,123,111,192,82,215,174,196,53,231,182,179,128,70,136,221,122,56,226,69,3,207,179,129,12,66, +245,194,12,248,47,200,33,15,178,60,149,62,81,117,48,111,81,61,169,129,208,169,144,5,151,210,43,152,74,143, +52,243,229,81,178,64,4,120,85,82,105,42,6,178,164,58,181,187,231,8,245,129,69,254,47,162,52,182,233,103, +230,119,41,55,120,206,25,67,43,222,248,17,174,90,131,60,207,227,44,205,214,6,25,188,54,171,0,208,162,199, +61,219,93,229,37,204,28,208,66,73,211,233,92,85,250,182,138,90,21,53,168,194,241,24,143,109,242,34,167,159, +2,205,144,178,174,119,58,45,120,14,169,116,29,149,106,230,13,140,84,113,76,181,213,168,47,32,15,189,95,250, +134,220,250,236,243,254,131,97,30,171,86,94,30,188,78,7,221,125,34,70,26,79,90,134,95,191,20,179,83,20, +163,98,35,36,243,114,214,60,61,157,199,149,110,166,37,136,68,41,131,70,167,26,93,37,165,174,92,255,139,10, +15,221,60,41,157,57,195,87,194,52,33,182,139,64,128,205,149,231,64,49,63,49,177,202,138,59,50,60,213,70, +160,243,181,159,106,255,140,234,187,202,93,124,2,171,133,101,159,180,243,184,156,78,109,108,243,180,196,216,102,118, +122,142,6,143,154,217,204,171,7,61,61,1,122,235,137,171,100,225,172,21,33,109,230,77,220,125,91,193,251,32, +50,193,209,164,87,181,129,191,205,58,39,164,101,249,170,240,177,45,109,212,121,232,236,138,207,30,178,86,116,193, +166,58,33,232,191,97,239,53,173,41,77,221,88,14,123,235,57,111,99,175,47,183,248,72,235,138,146,61,232,114, +149,191,219,250,103,22,249,232,114,202,198,160,119,190,5,188,158,116,93,172,190,173,142,97,70,62,7,88,142,140, +126,234,249,189,147,139,170,124,142,38,151,167,239,72,133,196,154,116,155,31,234,135,249,168,230,209,46,102,21,238, +73,141,52,120,89,121,36,62,54,31,190,143,41,244,209,109,113,88,212,254,46,199,24,219,6,12,172,134,209,245, +14,161,166,127,164,134,102,236,153,63,180,249,101,66,235,120,232,122,243,91,176,40,205,17,202,229,128,177,44,199, +70,111,80,205,237,40,42,157,20,217,9,162,20,49,179,21,244,154,89,240,136,189,160,119,28,10,157,144,70,81, +42,121,192,33,57,244,198,240,41,164,202,187,76,239,193,94,95,46,35,92,40,63,153,246,176,34,237,177,60,177, +50,114,171,186,186,138,228,230,191,166,205,15,16,61,65,163,76,57,94,67,200,144,54,138,203,197,187,203,54,194, +139,19,74,142,85,209,196,191,133,77,208,144,139,55,0,183,59,123,67,70,168,61,135,251,109,250,238,60,153,88, +112,153,168,11,143,43,17,50,46,200,104,110,147,228,234,162,174,170,246,141,13,25,229,235,78,74,117,241,14,216, +252,105,13,127,147,70,185,39,77,164,101,160,179,103,181,130,69,206,207,249,252,240,167,13,206,20,192,228,205,109, +84,41,158,110,15,162,181,74,61,94,228,185,175,241,213,209,125,31,95,32,53,229,130,56,114,76,197,7,76,232, +66,175,244,231,21,6,21,78,208,77,65,142,63,115,85,199,49,101,20,70,223,158,27,251,30,88,213,189,210,175, +205,170,192,29,116,56,243,49,79,15,94,103,83,91,237,119,206,176,28,128,223,88,77,50,157,22,173,83,235,180, +233,181,91,65,48,14,102,134,177,226,234,26,84,127,110,49,94,102,226,135,85,122,11,113,64,152,105,236,21,244, +46,125,135,135,67,8,232,38,10,68,35,206,167,206,240,79,49,152,86,156,71,185,21,235,17,99,69,59,1,35, +248,124,186,193,133,68,16,97,67,106,73,11,112,179,189,164,245,69,88,105,90,4,20,222,28,234,98,233,103,176, +73,222,86,80,61,42,25,251,119,164,4,54,32,53,90,81,163,1,160,56,254,152,35,240,252,58,12,85,205,30, +84,85,170,30,66,85,133,196,183,7,163,58,192,80,165,6,80,132,17,8,69,219,130,211,238,130,33,217,82,117, +44,135,123,12,155,6,215,113,225,0,2,151,30,65,114,49,120,118,31,32,232,46,177,148,77,114,47,72,98,101, +191,29,14,25,8,101,199,135,61,12,48,184,151,197,37,96,53,205,222,236,229,116,105,81,98,202,135,32,70,48, +208,234,157,29,132,155,2,241,38,127,226,24,118,189,135,123,228,210,132,160,124,196,47,33,133,67,152,192,147,158, +52,16,205,223,157,206,29,205,227,83,149,172,91,137,218,84,222,188,9,73,47,208,50,215,194,208,29,205,64,210, +75,159,17,82,74,184,216,81,138,167,160,150,133,179,124,152,47,114,60,160,233,18,248,190,116,177,120,81,4,242, +6,153,139,233,50,221,160,36,13,210,192,124,120,15,22,170,141,131,117,229,99,33,228,5,177,182,128,39,183,16, +106,133,228,248,71,89,189,196,14,197,126,213,218,115,127,188,226,179,162,136,215,165,20,32,216,14,40,104,198,6, +101,192,6,141,192,6,85,127,251,215,184,16,120,90,166,186,196,22,157,8,76,75,148,126,125,44,103,62,106,45, +241,255,14,237,115,162,214,49,154,246,178,87,2,119,23,160,244,141,75,199,226,126,117,226,24,38,29,19,108,37, +128,73,82,100,6,3,143,23,121,191,117,220,187,21,188,243,92,190,193,168,154,48,170,74,140,170,118,56,46,237, +180,65,18,37,181,251,38,142,145,207,118,77,120,233,243,186,134,253,57,121,109,208,17,12,54,178,52,45,172,9, +93,13,167,226,180,79,227,227,95,170,188,140,38,179,167,147,120,138,127,167,225,210,156,90,170,33,86,173,198,125, +134,86,222,160,227,199,118,115,97,252,123,203,140,32,41,250,175,203,168,113,39,20,140,21,227,123,146,239,155,223, +62,74,177,118,126,172,64,116,56,91,192,64,102,209,160,117,123,198,81,191,107,221,53,77,24,71,110,241,220,19, +82,69,42,211,190,163,200,119,132,77,16,173,245,83,52,103,12,92,30,148,17,17,188,144,53,242,66,252,113,183, +194,81,102,52,186,53,100,181,180,56,44,238,10,47,168,0,65,104,12,22,177,181,231,229,132,225,191,246,27,36, +64,29,10,159,70,24,231,175,15,87,97,255,106,148,30,89,180,201,190,153,235,197,87,45,217,140,101,52,29,54, +225,101,143,93,28,6,81,225,32,218,64,94,95,21,228,165,23,167,19,13,240,226,42,195,135,151,122,129,174,214, +199,158,228,82,6,42,227,153,39,232,135,121,50,246,167,140,29,160,116,110,186,107,236,54,16,192,72,27,66,102, +35,115,145,40,162,51,171,26,102,6,166,116,239,204,111,143,5,1,168,42,236,82,219,235,18,49,181,207,144,216, +67,138,15,21,117,60,57,108,161,150,21,129,10,221,52,232,171,15,138,128,248,18,148,243,192,93,120,228,134,32, +5,125,173,236,192,34,95,164,82,5,182,227,50,198,139,163,211,4,136,204,222,238,81,68,27,67,23,233,192,190, +40,216,56,130,52,166,221,12,23,101,110,230,177,209,114,177,198,216,107,80,215,158,164,77,160,125,1,71,162,18, +200,131,18,233,111,196,146,187,46,48,121,36,114,251,130,109,62,211,41,210,8,91,13,197,128,205,50,223,131,23, +124,111,64,210,126,137,100,72,78,60,31,152,170,198,99,109,218,253,77,188,128,5,196,81,224,183,108,225,83,86, +232,25,152,217,101,1,104,51,52,244,92,146,17,101,6,180,50,112,85,4,23,163,106,130,65,178,220,18,144,254, +54,3,243,224,35,245,45,12,206,109,241,62,22,194,148,189,27,212,190,37,224,16,81,14,189,114,152,216,33,141, +6,196,6,155,158,216,224,197,36,142,249,174,21,0,173,127,183,178,181,126,40,156,101,223,207,191,123,243,242,213, +183,223,252,243,159,167,102,6,182,176,82,227,205,219,123,78,3,240,149,240,32,60,110,222,231,224,115,12,63,213, +203,202,59,126,184,153,44,128,187,138,140,213,128,8,174,139,253,45,149,83,121,52,228,139,106,39,170,214,198,69, +24,244,91,154,195,26,163,229,79,252,170,85,101,119,79,166,211,71,81,58,37,255,192,133,253,154,103,218,202,249, +100,49,112,163,50,181,230,224,26,131,235,185,195,147,229,195,76,45,225,239,218,17,62,155,26,196,195,170,109,147, +192,147,75,5,230,79,87,15,163,26,126,26,120,53,86,183,16,115,75,49,240,211,224,99,170,42,225,56,224,172, +211,229,48,43,198,248,172,2,62,222,239,111,25,41,192,39,188,71,234,20,105,254,147,32,103,191,134,183,211,245, +147,114,118,10,215,229,53,66,126,161,51,151,109,61,165,59,252,17,160,138,163,44,38,240,189,54,112,172,0,51, +240,68,125,40,32,198,192,55,128,47,100,60,135,122,28,96,119,141,198,132,71,148,179,178,233,28,74,131,17,229, +166,103,5,182,130,16,180,149,62,177,166,189,106,253,135,71,193,120,97,26,43,106,169,121,88,63,164,106,184,201, +138,195,113,39,176,42,207,5,2,72,152,143,151,21,179,108,228,188,240,172,236,205,73,134,99,33,133,131,140,6, +93,235,20,58,94,184,156,25,77,75,58,220,199,107,157,162,168,239,10,126,74,192,66,248,242,172,163,245,172,134, +200,24,12,83,167,103,127,135,119,115,52,237,241,247,115,189,158,85,62,162,196,136,213,172,130,145,100,232,7,208, +22,44,160,216,108,237,11,62,118,5,167,149,143,176,5,167,92,80,76,193,51,154,2,57,1,6,39,160,141,5, +135,4,230,194,234,5,150,180,173,11,52,119,84,249,40,154,158,49,144,177,22,236,145,183,82,91,214,115,161,4, +192,212,52,51,71,5,205,26,204,157,155,152,12,182,238,172,64,228,220,224,53,101,77,210,145,81,134,67,44,104, +122,214,201,73,87,97,82,186,200,22,95,183,17,100,3,62,100,12,249,108,224,28,55,25,199,78,49,140,143,169, +54,152,96,176,163,29,208,144,31,86,252,170,122,172,220,159,135,134,169,15,218,80,19,90,153,197,158,43,8,85, +35,190,104,137,159,0,73,119,177,157,106,93,169,138,160,6,98,144,47,70,197,204,244,20,11,34,57,236,28,66, +212,248,73,2,22,26,122,105,191,148,23,112,169,249,21,1,163,111,125,134,91,47,15,99,31,109,144,168,230,106, +30,251,106,30,143,87,243,248,214,103,216,175,70,58,66,232,81,84,98,179,224,57,133,10,59,105,249,69,186,105, +240,73,95,231,238,92,200,224,92,200,8,185,198,36,65,128,38,115,95,150,144,178,169,172,248,193,215,213,146,237, +197,151,85,91,149,102,18,35,168,58,151,30,212,76,134,108,185,51,55,161,184,190,56,221,119,189,128,240,243,71, +161,1,249,103,120,35,57,11,246,182,166,167,170,158,69,229,130,212,114,127,87,147,52,132,41,27,18,113,72,237, +180,23,199,14,67,31,223,216,184,91,25,119,75,113,143,49,31,162,102,206,3,211,200,225,91,132,253,14,29,139, +108,62,37,249,154,239,80,68,22,38,134,0,72,202,52,188,168,14,184,137,234,153,247,247,207,53,203,42,219,162, +88,107,47,85,236,241,82,146,4,86,95,250,27,152,94,161,94,218,10,225,90,41,57,251,102,157,66,171,175,171, +170,61,183,86,27,219,99,52,49,210,35,23,86,121,239,21,193,215,186,79,147,71,101,120,223,203,241,125,79,5, +191,12,232,138,134,25,195,144,173,180,126,104,68,103,113,239,163,157,167,92,177,61,106,152,17,60,223,237,188,88, +169,231,171,205,182,53,75,18,145,128,250,7,154,101,223,21,242,121,7,74,195,164,96,49,119,227,254,1,121,236, +129,250,120,94,104,122,102,69,31,249,189,247,85,248,40,76,255,105,226,47,123,2,223,187,14,189,124,44,38,179, +201,212,192,147,158,63,46,172,18,253,31,81,123,62,136,162,60,47,80,129,0,213,230,181,52,158,127,214,78,177, +124,53,133,145,195,243,182,215,37,229,87,218,210,190,210,150,252,74,91,186,87,218,146,94,105,75,222,169,126,182, +190,21,207,66,81,142,207,52,173,101,180,227,123,253,17,208,57,126,165,197,156,189,42,134,92,255,18,221,224,66, +137,156,188,190,144,226,150,33,14,63,242,243,249,209,7,101,137,18,163,118,213,106,213,152,246,167,164,82,246,235, +103,100,46,88,169,111,235,153,84,101,115,228,132,88,14,30,212,70,84,123,28,23,186,66,125,22,218,232,254,92, +32,206,139,19,160,249,172,64,141,71,146,90,135,243,71,3,142,162,136,159,102,107,139,199,50,31,245,51,68,33, +230,130,6,221,12,2,197,84,0,141,148,169,203,234,38,73,59,225,97,170,146,82,47,25,9,140,195,56,14,136, +238,39,37,250,90,109,52,0,18,249,244,108,142,161,190,55,249,175,208,61,220,65,236,76,24,226,38,170,214,0, +33,141,154,108,210,37,246,30,157,224,219,24,206,5,32,69,107,10,9,189,206,173,59,77,43,0,245,175,116,109, +215,59,2,82,55,165,79,116,149,95,227,224,56,14,190,172,51,88,170,43,217,42,11,16,201,166,11,230,251,240, +56,217,206,160,24,229,153,166,246,87,109,48,202,102,135,56,251,65,125,145,202,7,197,108,21,63,218,62,52,44, +1,95,34,161,41,211,179,217,50,126,180,129,116,91,30,50,136,185,125,91,12,60,49,208,225,160,221,3,49,92, +59,253,119,216,24,136,184,172,76,73,21,3,234,226,37,37,177,38,101,124,216,62,183,75,104,169,117,117,8,90, +84,138,11,134,130,128,184,2,233,200,10,100,156,224,23,107,222,186,249,154,101,252,91,240,188,25,63,109,144,196, +31,5,127,168,82,3,102,76,189,20,159,170,212,36,12,0,91,106,124,6,59,132,144,195,134,39,158,73,194,75, +218,186,37,53,202,213,154,192,229,182,206,149,175,35,105,48,236,88,157,191,18,138,20,139,132,150,85,209,8,171, +96,74,142,92,131,8,55,170,138,97,244,10,182,101,142,186,94,53,246,183,9,3,202,7,3,162,158,164,156,201, +15,42,31,14,138,178,21,218,67,132,128,217,140,7,136,144,79,228,195,96,83,17,15,172,108,105,87,121,126,211, +161,173,180,28,238,186,121,54,211,75,134,252,21,175,224,26,163,24,242,87,252,225,48,109,38,77,121,100,179,202, +22,137,213,90,198,151,11,225,222,37,3,160,79,128,96,247,91,40,211,191,230,129,114,204,84,173,10,41,244,185, +238,37,175,145,104,10,51,7,233,25,92,107,145,133,138,249,160,242,199,16,181,63,83,226,101,168,25,158,67,112, +229,71,145,3,209,197,156,251,246,16,31,114,250,9,84,51,196,207,93,30,221,60,42,21,199,235,234,145,123,221, +69,194,202,98,71,255,120,101,165,114,160,167,120,152,112,136,43,129,33,112,4,207,31,228,234,231,208,127,71,139, +177,60,247,155,155,191,171,94,126,155,74,159,152,24,171,131,202,84,64,62,224,110,224,154,32,212,96,136,74,66, +160,90,68,7,75,234,82,213,126,204,170,118,35,198,166,218,155,227,158,206,84,73,90,214,244,55,70,57,62,212, +79,180,211,242,186,216,247,217,8,174,145,218,250,214,243,82,118,104,238,99,147,54,168,38,43,188,55,162,244,14, +84,211,205,153,152,1,128,253,28,149,40,95,241,19,122,52,105,73,173,215,106,227,198,138,179,213,164,95,125,87, +206,46,67,209,168,93,208,108,151,62,34,211,193,147,62,19,72,170,68,14,159,97,169,42,144,33,68,73,195,136, +5,14,55,55,40,15,232,40,145,197,20,165,132,153,197,42,140,107,182,1,165,248,35,55,71,241,247,135,206,171, +11,156,29,16,115,139,49,236,211,69,30,22,159,22,31,85,65,41,4,237,23,6,77,201,67,61,73,11,255,74, +47,59,143,9,167,62,222,60,61,89,224,47,132,69,115,47,71,80,32,54,104,175,70,183,246,227,182,83,21,198, +146,228,59,198,210,7,196,214,154,198,219,224,41,157,226,119,163,42,252,46,240,187,82,45,126,103,248,93,171,20, +191,215,248,13,27,93,184,206,129,8,84,155,52,142,246,44,26,226,186,125,157,110,194,106,189,40,132,150,31,144, +151,158,36,155,126,245,230,219,111,88,82,44,95,221,70,94,232,168,104,240,16,140,124,51,150,15,74,53,3,1, +94,28,91,230,218,11,20,209,108,237,202,67,137,6,74,224,68,244,52,82,63,31,92,138,177,51,24,60,94,113, +225,216,157,55,97,11,96,137,176,122,81,40,155,79,243,105,59,51,248,48,105,15,14,76,107,53,68,120,105,112, +145,219,72,167,169,38,193,32,211,227,3,15,171,157,186,249,174,216,54,216,249,80,120,86,118,10,115,193,32,95, +181,245,94,90,215,169,47,69,143,67,119,165,253,161,208,81,140,149,189,148,153,184,117,121,195,128,97,202,214,251, +105,208,118,88,219,102,56,191,249,226,87,59,195,201,151,133,20,234,255,178,234,113,26,230,68,65,77,138,22,125, +150,210,103,221,22,19,50,119,239,81,52,203,77,150,250,204,236,223,118,38,203,188,54,236,189,83,245,210,191,171, +243,170,206,219,219,94,150,115,200,211,132,60,50,13,186,63,201,175,54,85,221,166,101,11,149,229,116,45,70,217, +250,231,46,143,46,197,72,126,228,145,180,82,18,145,95,164,71,202,170,254,112,14,246,194,27,98,150,12,138,111, +170,129,55,220,137,213,147,92,236,46,77,123,109,76,153,60,55,42,171,174,240,178,153,124,155,42,43,181,140,146, +155,171,182,75,124,166,111,91,159,41,178,247,40,0,99,145,185,133,8,129,85,178,38,218,17,223,59,201,149,41, +151,9,150,222,150,72,187,21,85,181,73,74,22,10,109,58,215,53,151,251,119,198,230,255,157,203,137,23,239,89, +14,252,178,223,33,248,159,248,130,98,54,7,47,190,27,158,27,108,133,42,109,168,202,170,211,70,249,241,212,162, +239,105,167,97,150,232,33,167,21,108,79,238,82,70,165,215,182,59,43,148,194,92,170,45,146,103,43,126,185,152, +234,66,173,241,207,18,186,183,5,210,109,249,100,11,79,125,81,26,1,191,241,119,197,57,112,57,99,98,185,1, +23,102,25,103,179,153,90,131,204,96,246,59,44,6,127,220,249,180,126,146,193,88,177,162,88,141,54,205,67,103, +97,96,41,187,233,95,14,165,52,26,190,149,220,63,33,181,31,178,218,185,5,78,149,155,165,66,204,82,102,103, +201,119,110,77,149,172,108,231,150,220,185,109,167,253,114,168,13,190,148,226,110,189,194,11,239,165,181,167,113,161, +110,212,123,70,224,215,36,43,86,68,141,122,175,46,240,229,16,191,98,100,122,170,15,148,148,70,21,36,88,167, +91,5,124,99,54,117,75,73,87,255,252,231,117,20,171,103,20,56,130,208,135,40,48,166,223,232,181,122,174,215, +243,55,79,244,10,102,253,77,124,163,219,179,55,200,215,58,186,225,87,146,232,66,103,209,13,173,205,5,180,248, +30,98,174,116,17,93,208,74,65,103,157,143,229,219,8,81,202,165,78,49,137,186,178,120,147,60,135,28,222,11, +243,51,202,177,177,111,189,1,240,47,105,126,222,240,252,48,252,215,126,158,226,152,103,36,134,158,190,81,239,245, +69,16,135,10,117,31,168,117,117,71,173,27,97,173,111,224,233,14,141,27,224,51,143,121,71,230,69,6,140,25, +199,194,232,243,103,0,184,208,10,62,226,52,98,216,33,67,183,242,239,78,134,250,199,130,147,251,79,148,223,12, +223,16,81,254,87,183,179,83,220,63,6,136,227,50,102,103,185,45,189,97,53,246,13,107,30,67,23,230,33,101, +36,161,249,157,134,122,173,191,189,6,122,244,180,193,108,213,239,90,151,177,10,114,185,21,230,221,245,65,95,236, +158,209,167,48,222,19,149,3,225,26,170,72,53,61,173,21,108,222,161,157,158,130,54,157,54,0,96,65,220,13, +178,20,191,107,96,231,81,55,254,249,79,248,109,171,205,34,229,96,84,146,127,121,59,107,220,165,246,119,182,83, +192,99,0,44,215,48,222,235,80,168,162,214,92,190,72,8,84,18,52,238,160,108,101,196,145,44,98,164,187,50, +111,135,193,3,206,120,19,181,172,190,18,128,50,96,117,249,197,70,112,169,54,152,167,241,188,108,105,110,10,17, +78,227,16,206,185,151,233,60,58,130,215,85,108,200,77,123,141,205,19,162,69,201,2,235,88,24,11,151,36,196, +16,59,20,216,160,126,71,191,136,237,111,213,193,241,139,160,231,132,52,210,39,245,34,157,54,144,158,217,214,86, +48,236,87,144,21,48,47,110,82,43,15,223,0,164,201,186,127,193,214,107,228,189,197,74,122,232,198,228,33,213, +125,84,194,109,174,60,14,146,128,16,52,139,54,249,217,195,139,124,57,217,7,34,47,37,44,141,82,144,79,197, +38,242,147,10,216,244,66,122,180,134,81,115,74,178,115,243,13,211,134,167,78,225,103,93,101,14,42,241,154,182, +34,227,250,246,149,89,45,1,223,5,81,168,104,163,174,212,165,186,112,125,186,209,233,98,118,154,208,238,219,0, +168,92,217,19,108,51,133,67,203,156,109,224,172,226,205,3,44,177,27,130,114,136,190,10,209,87,83,136,134,108, +80,20,98,81,225,164,7,101,144,66,107,6,105,118,213,46,25,47,93,116,72,249,95,168,21,22,195,187,142,127, +60,223,240,131,255,10,122,182,74,54,118,20,124,102,152,179,21,52,173,46,169,35,48,176,233,41,156,172,122,131, +14,220,231,203,128,162,46,32,227,18,50,206,47,113,102,229,130,69,191,224,125,132,100,190,147,9,35,189,137,218, +156,36,87,106,115,154,92,192,23,138,169,219,121,143,150,184,249,10,72,8,113,80,171,234,47,78,135,238,107,191, +47,162,75,133,55,249,109,180,82,80,76,109,142,113,180,16,165,174,96,144,107,125,217,173,158,64,252,72,6,207, +92,17,15,8,194,38,221,110,96,81,11,206,250,65,140,98,3,90,233,230,13,19,3,253,8,78,127,158,54,107, +159,134,1,17,255,45,177,163,125,106,136,226,60,95,193,206,151,149,139,24,37,84,226,49,53,132,56,197,247,58, +132,58,225,99,131,177,76,120,121,25,220,222,114,60,251,247,110,116,29,59,59,251,140,54,211,47,205,131,15,127, +56,254,207,227,83,233,231,236,250,250,250,216,9,254,87,245,59,225,216,236,113,40,134,80,81,231,151,91,148,22, +251,72,7,103,100,192,238,175,69,207,128,157,183,57,106,64,109,176,105,131,149,48,222,238,254,250,170,56,223,182, +44,201,118,216,41,71,64,165,45,0,153,51,14,214,93,148,85,139,35,29,218,213,146,106,4,120,2,215,16,177, +220,90,237,214,121,37,213,26,210,104,71,77,39,173,98,45,199,196,28,243,151,2,65,222,55,173,129,105,170,29, +51,31,131,73,176,206,59,51,140,60,234,24,224,187,131,129,173,106,211,172,135,3,133,195,107,48,160,19,213,159, +137,139,202,10,110,90,14,13,201,59,113,21,219,205,146,12,96,170,241,169,11,149,194,99,28,6,125,31,108,143, +184,120,171,113,218,142,203,10,8,63,190,6,2,225,34,167,222,79,10,234,220,176,119,111,192,225,92,57,225,243, +188,53,87,13,35,210,190,222,70,165,57,149,15,252,42,232,45,217,23,27,139,17,235,167,208,232,108,86,199,133, +38,233,225,226,248,34,37,171,109,139,8,62,219,170,77,139,167,165,95,40,64,146,33,160,93,134,24,74,181,96, +98,14,160,155,30,103,224,96,199,186,160,194,208,232,185,170,172,203,233,120,110,95,173,209,20,143,159,66,134,153, +70,149,120,249,4,10,255,29,204,87,51,137,99,229,106,64,98,227,120,8,124,131,114,72,241,227,173,115,18,171, +210,1,140,85,115,155,250,225,119,241,16,110,91,101,216,177,246,16,194,227,14,5,208,158,149,249,85,35,205,163, +202,37,98,254,140,33,246,76,59,96,207,236,184,42,224,43,122,72,6,48,163,85,73,128,122,13,42,72,59,215, +117,140,230,225,99,150,174,195,107,58,86,205,124,28,91,196,110,46,134,104,217,71,177,197,204,185,115,19,222,161, +124,22,93,168,236,251,160,159,209,253,226,212,53,79,4,155,184,67,65,91,129,227,198,11,120,101,159,142,118,222, +161,185,114,115,132,189,136,204,177,216,120,188,101,197,134,80,38,128,153,225,86,106,179,220,102,198,109,6,207,183, +47,21,180,224,50,199,200,202,29,108,186,184,227,166,34,150,197,235,173,115,112,124,127,87,159,57,19,191,176,138, +45,104,250,91,176,67,250,246,190,9,200,87,92,207,29,219,215,15,219,217,139,243,59,137,111,18,118,223,54,228, +224,11,25,42,153,41,224,202,200,133,240,110,68,45,75,108,28,102,87,110,149,206,178,158,229,42,203,46,179,240, +115,203,6,5,191,179,66,132,127,45,184,155,203,70,79,132,233,118,112,244,83,232,221,101,85,21,38,29,58,199, +50,232,234,11,78,234,78,145,53,137,225,243,134,105,34,80,56,94,54,177,194,193,146,174,47,92,200,0,200,48, +210,111,44,184,23,53,54,113,209,0,190,199,213,55,61,147,181,73,219,169,146,184,161,67,62,220,20,57,47,232, +136,191,155,219,147,240,47,253,147,112,255,192,66,129,174,70,95,154,232,12,101,42,136,206,111,73,176,209,27,166, +175,57,21,35,21,228,128,4,156,61,135,67,195,161,178,42,53,228,2,180,240,69,129,149,221,110,96,253,89,170, +162,118,75,101,82,178,202,249,222,64,14,251,13,54,110,193,115,170,117,148,205,153,104,163,200,183,158,176,170,48, +64,92,174,244,22,109,176,185,93,224,119,17,6,25,95,203,226,173,223,103,174,4,18,119,112,9,105,137,202,227, +56,251,156,175,13,7,145,5,227,237,163,227,216,117,173,92,253,186,9,153,174,242,198,52,142,48,176,83,50,52, +45,110,99,59,62,19,131,77,27,153,26,239,122,176,124,116,26,68,127,101,247,206,66,231,80,254,176,156,137,25, +83,181,238,207,199,172,154,203,9,45,135,211,37,166,200,99,154,90,137,217,138,149,152,81,56,95,238,154,189,42, +0,145,81,141,7,34,49,129,18,142,26,101,80,174,207,237,233,157,156,10,62,165,236,113,43,232,7,37,51,249, +243,81,204,88,220,81,17,137,153,122,243,83,14,230,71,53,58,76,39,204,102,232,170,159,74,26,97,234,1,203, +202,159,207,7,107,167,241,41,15,79,124,208,166,48,79,202,88,29,13,86,86,46,96,115,174,211,65,207,79,164, +205,61,243,228,100,191,68,229,50,20,218,60,42,127,247,88,21,232,97,176,120,122,186,120,60,43,146,2,130,114, +123,133,39,218,83,37,158,126,11,188,10,13,107,118,91,55,170,72,7,176,187,78,243,118,104,246,220,131,57,18, +148,50,2,113,112,60,80,157,195,248,136,237,2,239,90,190,109,214,164,242,89,155,95,144,141,209,197,158,128,150, +75,181,152,64,46,120,31,129,92,19,85,238,181,124,118,126,23,243,11,79,8,32,7,162,216,137,18,252,132,242, +80,55,19,133,86,42,38,226,218,3,33,107,15,6,62,88,94,110,114,174,190,194,220,132,178,125,238,207,56,52, +184,206,129,28,213,151,68,173,76,188,57,236,137,218,17,74,226,135,69,229,192,43,57,53,127,80,118,69,146,137, +244,143,63,81,171,210,101,70,104,115,223,8,108,238,187,173,252,23,160,80,254,246,250,0,255,40,122,46,82,190, +12,182,185,99,232,158,55,26,44,251,40,12,219,14,13,214,42,161,20,231,205,226,86,229,119,76,163,129,36,29, +199,124,230,78,84,142,89,149,147,46,222,155,15,171,158,94,160,134,15,223,228,121,102,55,222,144,112,242,85,193, +71,88,200,68,193,126,174,159,138,174,59,48,160,166,55,34,31,47,59,68,103,118,238,50,219,205,152,236,124,206, +100,231,23,10,252,166,117,157,170,13,106,79,143,231,192,116,52,255,44,82,155,196,143,146,22,177,71,35,116,234, +67,222,228,56,159,60,58,166,23,38,74,86,217,169,117,190,52,227,117,182,213,71,214,232,65,204,158,161,8,92, +180,136,255,196,6,186,152,9,129,159,171,33,33,16,239,4,17,164,91,101,67,126,246,7,151,98,167,79,106,34, +19,119,33,192,212,38,234,32,246,105,59,163,135,245,177,57,129,129,230,233,55,233,149,65,42,219,223,3,75,64, +27,254,228,67,234,196,214,223,12,234,71,35,108,3,251,54,176,43,98,186,147,53,240,103,14,198,120,155,227,208, +58,48,127,66,16,241,9,84,29,26,173,161,81,210,115,42,137,100,37,125,234,24,138,216,139,9,234,78,88,180, +101,23,202,124,107,25,126,81,159,184,115,28,66,213,232,79,10,186,205,28,98,182,242,196,147,113,142,103,126,237, +241,138,23,176,233,241,39,13,44,140,89,130,128,53,84,230,89,186,159,120,88,193,220,199,237,218,148,17,93,219, +125,22,93,118,138,98,136,97,60,104,101,216,233,225,34,41,34,171,161,135,178,33,68,251,50,172,177,234,90,247, +53,104,84,42,174,54,246,156,100,174,123,29,200,250,194,82,244,82,215,252,172,160,37,206,250,126,115,62,153,4, +29,4,76,197,56,30,31,72,82,133,235,27,245,127,108,89,226,160,178,225,68,50,205,89,102,121,176,43,93,225, +39,83,247,186,164,59,75,22,219,23,66,248,3,115,190,58,118,164,28,112,58,143,153,114,91,170,181,74,69,197, +164,32,183,10,23,19,92,237,37,0,208,50,80,80,59,84,96,208,235,80,166,194,240,10,247,22,208,230,80,99, +171,50,100,67,242,136,86,158,223,216,8,114,209,19,139,114,165,156,169,7,160,22,6,78,149,130,164,70,215,91, +230,113,80,32,158,126,255,122,6,55,32,210,119,18,248,65,33,85,115,34,205,104,22,35,15,85,125,83,85,31, +245,88,149,159,225,209,125,62,71,61,90,63,231,254,149,170,58,182,244,136,159,22,38,48,80,11,59,50,210,64, +99,177,239,135,131,77,57,121,206,61,91,254,216,249,8,47,103,234,223,193,194,166,139,66,46,163,247,76,211,27, +181,227,156,120,124,126,34,241,119,55,112,169,191,29,90,34,129,179,211,85,77,198,167,16,115,212,230,3,58,64, +80,208,24,210,109,94,182,20,238,146,248,252,102,144,130,235,69,206,123,111,240,229,162,178,207,100,240,213,244,158, +202,218,76,60,52,27,82,228,28,50,3,74,77,93,68,29,34,252,18,224,67,182,191,26,228,222,179,245,175,18, +191,157,253,47,86,147,179,54,192,74,27,16,45,155,204,75,241,219,75,167,195,108,100,188,140,20,251,160,122,133, +235,195,18,225,240,101,171,102,101,166,21,42,186,195,236,151,26,194,138,58,211,114,71,140,235,68,105,155,111,212, +50,111,144,124,89,38,57,13,82,242,176,199,159,84,145,197,243,166,170,91,179,124,110,31,12,190,54,240,227,196, +145,26,54,122,100,159,63,3,232,62,169,224,237,176,137,25,60,17,116,89,113,121,68,219,243,157,127,160,18,6, +67,113,104,184,63,168,214,43,86,26,105,114,18,240,144,170,73,116,199,224,183,65,231,217,238,68,165,186,113,93, +169,159,164,164,170,132,11,91,104,210,28,34,181,86,187,212,37,238,16,143,66,217,26,93,6,109,147,105,158,6, +13,136,126,2,40,15,95,99,225,146,110,159,235,191,110,173,121,0,248,205,98,76,106,167,58,243,59,79,174,109, +158,73,141,75,185,235,81,165,148,144,27,123,27,231,222,10,15,126,206,123,31,94,190,134,46,112,42,235,186,207, +192,31,141,138,130,234,54,33,11,58,35,147,187,185,99,91,29,147,42,189,89,250,98,82,92,27,248,213,54,221, +75,242,132,74,203,172,207,80,33,81,206,124,217,29,127,178,107,249,215,216,194,80,35,113,56,186,191,135,210,13, +148,150,86,192,90,178,219,101,20,124,63,183,74,46,214,48,152,11,160,13,9,60,122,190,111,76,77,178,223,77, +20,75,59,96,37,108,116,214,40,253,230,243,47,158,189,125,249,195,231,23,47,191,121,241,242,155,151,111,127,166, +170,155,133,113,25,190,251,246,205,203,94,6,169,98,155,237,169,80,158,181,228,90,136,149,140,61,205,113,102,48, +26,127,48,58,212,112,229,33,89,170,254,54,244,18,136,35,248,26,101,47,1,134,127,176,20,170,221,73,101,236, +208,90,20,75,124,207,222,97,206,89,160,160,66,221,142,35,252,120,226,95,149,57,75,39,149,204,131,70,188,92, +121,126,69,49,108,116,104,137,77,163,166,67,78,168,212,218,84,64,250,197,127,19,237,178,203,223,160,103,23,0, +176,15,246,163,86,212,32,62,119,151,170,208,112,18,221,228,13,42,117,216,143,181,46,51,82,250,40,99,124,188, +149,138,150,75,127,202,109,225,148,219,62,65,225,149,173,27,238,6,224,121,123,174,118,176,221,146,43,5,39,127, +114,217,233,141,186,208,27,209,53,255,77,75,177,212,23,72,33,192,154,65,179,234,42,86,75,180,177,115,169,150, +200,126,216,104,88,9,64,198,112,20,171,146,224,47,198,4,139,11,125,218,169,75,235,132,36,94,62,216,46,236, +219,134,151,126,92,27,26,105,113,220,139,52,9,90,107,160,210,201,204,153,20,241,171,51,249,66,72,70,233,252, +93,11,122,194,143,177,254,106,44,223,102,91,149,243,175,117,7,196,46,4,38,202,222,111,56,111,207,182,100,202, +160,124,103,123,174,118,82,78,90,186,166,225,41,198,125,178,23,168,196,220,223,129,94,235,107,51,156,68,68,176, +53,250,33,169,45,196,210,201,66,48,5,0,237,190,112,250,44,241,113,84,122,183,23,23,182,119,243,225,118,10, +59,165,97,184,176,69,1,77,87,176,20,30,141,217,224,153,9,49,142,234,97,169,68,78,238,28,67,228,199,220, +171,151,215,6,231,21,122,129,129,18,149,21,213,69,227,117,212,23,121,178,71,239,228,177,42,178,160,144,5,200, +22,184,127,112,119,93,154,18,240,41,119,20,201,166,29,66,80,2,71,174,33,163,177,246,180,177,14,171,248,34, +250,172,61,112,17,237,223,67,65,144,29,54,26,252,21,14,165,60,111,22,247,186,61,183,191,221,8,103,94,2, +7,104,140,64,212,132,1,255,4,135,235,170,135,89,113,175,112,197,124,126,72,31,80,118,161,122,15,114,8,23, +253,44,86,251,240,249,94,188,37,17,191,29,171,21,159,6,223,32,201,180,31,253,25,156,5,253,104,83,34,109, +99,235,121,3,117,202,238,124,194,134,86,6,45,223,150,25,10,217,251,71,18,126,155,131,59,3,236,94,25,240, +199,248,112,94,230,131,123,63,79,98,145,151,239,9,172,27,140,241,75,175,225,64,110,25,224,157,175,64,82,12, +176,123,13,50,243,133,134,118,39,242,13,194,186,90,63,63,176,187,134,93,136,229,226,183,93,104,122,216,109,123, +71,217,95,91,85,122,64,96,42,143,156,177,160,144,135,218,170,13,128,242,138,237,45,44,147,21,251,107,221,36, +91,34,185,201,127,217,203,231,96,204,181,116,223,10,208,104,171,32,123,28,211,219,254,173,200,114,219,203,114,139, +89,82,164,236,69,150,186,151,165,198,44,133,54,193,89,154,202,48,196,5,154,168,80,214,14,150,90,67,244,7, +17,93,171,10,239,161,208,69,154,13,63,68,10,129,244,246,203,37,106,140,65,247,14,38,215,152,92,31,76,78, +49,57,63,152,156,97,242,135,131,201,235,184,19,243,237,81,117,88,40,242,230,228,157,251,157,133,53,62,239,120, +199,142,150,242,149,218,44,161,88,220,245,59,208,142,148,230,243,14,237,96,94,32,7,170,93,27,59,126,193,131, +222,223,2,174,26,173,181,155,144,133,27,122,226,98,186,218,208,64,251,146,19,140,101,227,14,77,30,181,117,117, +123,207,86,99,196,2,122,169,77,20,130,10,63,229,62,179,251,36,238,40,245,179,181,201,222,15,235,149,115,143, +91,130,189,111,69,246,3,57,246,158,187,142,17,120,186,160,41,153,56,68,105,160,239,77,240,106,85,30,249,91, +69,188,131,222,217,119,164,225,251,148,24,205,218,144,253,95,119,198,65,147,157,241,174,159,243,230,243,155,214,148, +68,40,66,43,192,106,41,34,99,107,28,197,93,190,83,166,235,36,62,185,127,54,195,4,245,188,73,114,13,111, +111,55,244,210,228,162,233,214,114,32,95,220,93,110,243,98,249,109,253,61,173,173,239,131,4,157,251,49,15,223, +44,1,115,239,247,208,179,6,141,95,233,185,233,33,87,7,118,120,81,55,254,6,83,218,47,24,8,217,84,134, +153,15,201,46,49,8,11,224,204,138,206,171,8,232,79,124,187,10,45,193,122,92,90,248,83,198,173,159,224,247, +142,226,93,62,35,60,172,53,198,26,121,34,147,126,225,224,37,176,195,249,224,115,12,179,80,250,16,100,21,62, +136,245,206,99,103,200,248,181,105,42,244,171,207,32,216,19,211,140,7,199,181,172,224,152,35,15,145,15,29,13, +52,146,54,232,228,117,66,209,74,225,125,13,203,139,219,131,155,181,164,198,123,67,202,247,6,54,82,213,90,137, +27,163,181,191,180,47,142,78,146,18,230,154,216,14,42,211,45,186,8,41,221,60,159,181,40,66,68,238,232,112, +83,246,135,67,156,27,159,85,55,202,215,131,235,14,17,86,161,248,139,8,81,92,188,88,218,241,83,110,186,138, +227,104,97,218,172,191,201,228,215,145,108,118,127,246,243,201,116,80,177,185,202,145,206,150,89,130,123,186,8,15, +83,184,171,120,19,92,160,27,129,225,39,25,252,33,50,215,218,227,49,100,141,39,12,122,61,109,145,33,10,31, +231,100,32,102,75,178,249,86,166,41,211,171,120,30,6,90,116,117,0,79,0,203,253,110,13,4,28,246,239,121, +176,132,126,157,84,225,238,119,153,85,192,126,149,94,194,36,70,120,224,86,48,140,90,173,4,43,163,177,219,151, +232,6,26,142,213,29,105,80,119,132,212,68,54,122,57,45,213,234,108,121,174,201,73,230,26,246,150,157,185,40, +59,131,215,250,77,172,240,30,88,115,156,177,113,158,183,177,234,6,139,53,24,202,141,27,202,173,28,202,176,131, +222,165,161,115,104,8,99,3,128,124,146,145,200,251,90,23,216,69,109,112,178,83,232,13,114,89,92,39,87,168, +139,180,70,69,197,218,199,32,60,134,46,166,157,4,148,143,237,227,142,104,27,64,6,73,74,222,96,111,93,176, +208,64,38,65,14,9,231,42,219,31,17,237,8,181,117,48,4,253,111,0,142,86,4,71,75,189,134,17,109,181, +129,105,87,25,12,75,142,8,108,234,109,145,126,90,134,65,113,100,129,145,126,92,89,135,154,100,152,58,164,29, +36,70,15,219,244,220,209,55,140,75,239,42,132,152,3,75,144,241,235,55,136,46,162,129,208,185,32,96,171,253, +99,164,134,161,181,8,165,100,15,207,95,175,26,121,189,242,204,13,151,211,143,236,29,91,189,227,219,169,245,187, +91,118,142,22,127,13,138,101,230,5,176,212,221,224,135,226,65,192,10,178,53,58,1,75,222,221,11,48,15,150, +84,222,186,96,131,88,140,251,224,75,64,164,149,130,180,189,212,56,22,232,15,74,24,13,71,105,187,135,40,156, +184,223,94,124,129,130,170,166,248,244,38,168,177,83,16,226,137,92,204,203,175,211,161,115,254,225,44,54,218,35, +28,152,100,143,81,224,238,138,3,98,58,78,213,30,81,123,145,143,33,169,168,10,93,88,247,134,97,217,16,79, +33,195,110,156,23,199,204,186,131,156,188,78,81,217,53,101,90,117,186,201,162,212,163,154,121,224,119,194,209,11, +216,6,192,156,103,253,74,111,207,82,158,106,230,232,127,18,109,25,2,208,2,203,26,21,174,86,79,174,58,70, +86,128,168,106,116,66,113,4,85,57,17,155,113,48,200,84,171,104,143,84,177,213,128,195,227,168,138,119,182,162, +26,222,241,150,246,29,111,25,67,2,214,23,239,62,162,58,230,66,119,114,215,61,43,10,155,139,20,60,239,164, +168,252,2,150,64,24,6,227,111,130,77,111,134,108,122,220,60,205,185,223,63,159,68,53,26,24,101,19,159,129, +237,137,29,1,24,250,22,40,139,85,129,207,151,59,111,209,210,157,10,207,74,219,195,123,72,62,227,64,169,209, +158,94,227,77,45,81,140,247,150,128,85,3,159,23,253,130,248,3,8,238,48,182,41,224,224,240,98,38,147,9, +155,244,111,48,111,51,150,183,9,121,59,150,191,190,251,110,35,86,12,5,16,61,247,43,70,202,15,253,205,105, +147,129,195,144,30,29,133,209,170,205,34,119,245,244,151,76,187,29,134,243,136,118,183,66,79,58,100,110,236,211, +237,237,141,50,18,9,142,111,95,190,197,208,227,180,177,57,159,213,6,113,10,68,165,122,192,84,1,233,64,85, +232,62,75,5,34,221,246,158,161,14,146,24,216,65,79,222,246,252,177,79,22,142,182,69,8,226,79,42,22,181, +202,202,78,169,181,78,225,96,74,167,5,30,77,193,222,71,3,199,210,124,197,236,50,184,136,173,142,157,156,91, +182,168,221,187,111,178,242,149,193,164,121,114,73,186,126,91,199,53,212,20,178,33,112,146,146,199,64,14,97,49, +177,245,79,2,71,211,95,100,229,51,200,96,150,221,160,22,148,80,91,82,251,121,239,46,228,30,216,203,56,25, +102,26,228,160,21,40,227,46,80,232,195,99,111,252,118,84,17,229,251,84,99,255,158,140,246,144,231,35,88,190, +61,116,228,206,43,93,123,238,24,76,123,8,232,52,139,246,238,15,170,37,27,206,76,59,44,247,54,45,38,193, +204,235,198,213,239,78,85,160,29,61,215,89,183,86,40,128,247,188,104,189,23,214,53,119,96,95,219,76,112,188, +176,55,254,118,218,248,230,124,28,183,232,243,123,229,73,134,47,16,209,52,170,178,143,140,165,170,186,59,151,116, +72,194,112,230,97,174,241,251,49,188,141,161,106,222,29,224,160,204,111,171,127,80,185,193,234,15,149,81,70,123, +64,151,16,134,55,47,183,21,84,165,199,238,126,170,214,214,106,153,81,41,235,134,232,67,28,87,54,128,76,198, +185,99,30,203,69,67,222,124,188,27,233,253,75,241,90,103,131,9,27,185,27,227,240,144,172,93,156,193,139,99, +219,125,137,14,10,254,174,38,107,252,37,83,5,147,243,228,140,126,212,82,103,247,223,161,145,140,223,14,36,4, +249,209,131,238,125,106,67,215,181,225,30,64,210,79,93,233,204,237,107,148,205,90,186,105,166,139,143,90,249,83, +243,74,200,74,248,111,93,40,156,69,215,244,170,54,6,120,204,48,75,87,86,250,244,202,47,162,148,66,185,159, +30,30,46,218,223,83,87,124,70,214,67,121,245,122,107,147,6,97,221,128,231,125,185,35,188,80,123,51,158,35, +11,183,210,107,183,112,190,175,163,75,135,111,112,122,125,255,162,224,220,21,122,61,100,101,44,247,88,25,173,42, +161,82,103,163,196,94,136,126,174,162,6,175,199,69,24,65,80,24,160,248,224,81,3,22,100,100,17,50,180,207, +68,71,134,124,148,112,2,124,71,173,91,66,55,125,35,47,24,140,198,6,177,251,210,49,45,116,158,157,143,140, +32,0,84,11,249,49,143,90,32,81,197,172,95,132,113,61,103,81,142,110,43,185,109,225,114,2,133,155,120,49, +20,122,114,103,210,8,128,25,40,229,41,29,200,200,21,251,137,144,80,136,90,237,208,0,50,38,15,85,231,94, +17,251,117,94,52,124,26,135,142,182,14,17,55,125,201,63,127,110,147,25,246,195,189,46,123,253,222,57,53,227, +163,134,251,182,183,152,21,204,105,213,57,117,23,66,35,162,75,76,164,203,142,150,202,35,72,84,5,131,148,223, +88,232,4,6,110,91,99,56,23,229,37,165,55,78,106,204,97,182,247,170,231,233,237,117,12,211,255,247,26,192, +65,12,153,163,67,194,25,43,84,229,120,67,225,17,247,140,100,119,206,241,33,183,199,202,142,49,8,76,161,8, +82,227,249,30,155,59,88,121,15,86,12,140,251,172,195,125,184,82,184,233,237,144,152,157,113,130,215,227,234,105, +179,176,149,230,101,99,234,214,15,163,81,213,12,237,63,36,213,19,7,52,188,92,62,11,212,57,171,226,110,88, +146,22,31,245,28,119,135,216,252,170,98,90,136,14,81,99,237,100,122,109,255,76,63,181,162,34,60,140,169,198, +35,54,11,162,160,233,83,93,207,211,217,44,206,216,147,208,12,30,171,45,215,10,224,24,73,250,121,74,102,112, +211,184,194,28,136,5,199,200,131,121,143,127,4,152,48,242,15,15,76,76,5,230,174,194,215,106,127,237,145,115, +128,195,245,47,54,131,84,191,159,187,225,220,221,201,116,216,227,225,186,172,130,25,225,92,138,17,39,181,236,63, +242,96,147,157,189,106,200,108,29,1,14,2,232,94,253,125,184,242,94,229,130,165,201,51,28,8,42,125,224,164, +161,250,164,109,68,160,97,126,147,192,27,124,99,43,16,207,115,10,5,110,207,227,142,189,197,125,135,201,97,15, +130,74,201,150,166,133,215,88,128,121,116,54,25,192,215,68,237,157,143,146,186,159,181,74,54,84,33,247,163,95, +157,92,9,95,221,112,99,6,117,224,211,80,219,27,20,97,185,183,190,147,94,145,48,253,59,56,94,239,233,137, +50,94,119,174,220,155,149,217,99,116,5,122,207,212,168,50,180,253,125,217,112,135,239,46,116,162,134,77,65,29, +221,179,246,152,73,227,6,5,230,159,73,15,62,251,116,60,241,246,246,51,237,229,8,70,163,178,32,118,155,243, +244,31,127,114,153,214,66,72,230,160,128,152,183,252,38,85,123,246,25,57,164,222,163,75,164,203,178,180,141,144, +163,35,229,110,70,152,72,232,171,174,215,27,253,170,130,251,59,242,252,34,68,137,64,250,34,206,243,162,141,189, +188,66,220,167,39,229,152,59,214,142,209,48,234,86,229,86,198,138,135,0,59,199,118,153,173,169,7,68,72,162, +248,168,90,240,135,199,255,249,231,255,4,11,143,240,57,195,239,255,2,114,138,253,175,68,130,231,89,42,239,250, +179,158,165,49,250,231,65,108,88,199,221,221,156,46,235,234,41,191,17,76,33,156,170,88,21,108,101,201,203,166, +216,42,90,210,175,107,238,169,232,45,228,137,26,172,68,176,203,130,37,140,108,223,134,138,57,134,73,124,187,134, +114,165,105,26,231,140,192,21,254,6,73,183,8,90,199,177,130,237,93,88,81,243,174,170,111,191,51,53,90,205, +75,223,25,85,219,42,66,76,156,64,129,230,97,9,41,167,177,2,137,191,109,249,62,169,30,65,152,236,26,215, +108,34,11,160,22,59,221,128,21,173,89,245,232,177,16,126,91,142,116,211,229,134,153,192,18,204,223,206,193,224, +34,4,209,54,59,2,58,218,15,122,210,120,44,130,73,83,78,242,235,59,54,134,121,237,141,81,69,181,174,102, +81,202,225,69,139,18,212,179,246,216,118,57,69,40,84,105,200,156,234,10,253,123,251,11,37,150,173,102,30,54, +200,111,220,163,199,15,189,187,88,59,23,30,96,82,40,138,201,126,110,6,51,201,51,149,137,185,217,142,45,33, +31,153,104,158,158,28,232,137,152,83,140,145,52,9,238,39,149,6,222,60,134,249,5,180,70,3,242,243,0,206, +241,211,208,83,251,200,135,124,56,100,239,187,71,3,157,161,168,67,182,109,80,158,113,7,125,127,67,29,46,20, +124,126,94,46,147,172,111,14,77,145,187,92,226,156,75,195,216,127,173,194,152,188,203,156,60,94,136,177,38,190, +201,48,54,136,150,118,226,111,154,253,153,241,72,160,66,57,191,240,106,32,31,15,83,141,178,146,149,42,28,155, +58,227,55,93,34,135,180,65,113,158,105,9,94,56,214,228,133,99,169,81,69,68,173,80,138,109,229,184,184,26, +88,157,142,112,169,49,57,139,85,97,207,99,24,220,18,170,171,32,42,92,0,133,253,174,92,26,15,68,33,68, +158,69,105,185,16,35,113,62,71,68,160,223,13,37,44,115,242,104,241,117,11,213,38,17,138,111,124,89,213,249, +175,21,192,19,224,132,197,105,50,59,141,31,218,135,154,167,218,216,176,144,210,30,42,1,168,42,56,126,93,251, +138,80,29,0,251,212,152,167,249,49,178,131,173,173,76,85,58,227,153,113,226,115,128,11,157,91,204,193,214,237, +49,15,154,189,135,165,91,68,141,158,0,92,32,179,103,130,112,66,229,26,247,141,177,152,234,205,225,25,171,49, +161,88,74,57,105,21,233,59,40,91,115,79,141,226,42,27,90,69,107,217,98,206,27,48,179,180,49,75,161,149, +198,74,95,187,188,159,67,87,61,165,156,190,201,44,223,135,130,250,144,185,62,172,59,77,83,56,111,132,237,88, +144,41,64,253,20,230,81,125,74,141,188,38,157,86,20,5,136,12,73,10,163,126,60,57,130,109,116,150,96,156, +173,81,68,175,209,130,9,176,50,214,202,186,255,60,199,210,144,59,142,21,197,55,50,126,111,48,97,110,32,235, +112,183,149,139,40,215,151,14,146,84,174,63,96,30,131,140,129,4,3,62,33,212,226,115,75,195,151,237,194,36, +248,139,70,188,68,94,95,129,204,203,171,140,57,49,132,43,141,165,67,169,11,108,97,151,151,171,2,149,160,174, +200,12,95,219,41,82,8,234,197,234,22,43,72,183,109,5,53,192,231,233,226,248,15,127,72,78,32,179,149,96, +125,159,63,48,40,194,180,36,97,214,195,66,8,190,123,55,141,143,146,143,252,247,103,245,143,237,119,203,54,252, +11,143,237,86,18,130,101,32,83,0,191,181,174,135,81,43,135,200,44,11,240,138,165,31,240,217,189,156,6,249, +135,43,251,8,191,65,68,182,57,171,24,145,137,183,248,43,149,209,91,252,202,35,50,138,92,199,80,105,13,9, +30,157,173,238,123,158,110,182,27,160,252,238,206,36,76,4,241,129,50,175,172,52,206,240,170,224,5,182,163,209, +231,231,10,255,30,120,130,174,240,111,28,31,122,64,124,112,242,219,30,16,221,154,150,110,77,155,78,143,62,32, +170,90,87,110,88,42,213,128,244,235,120,49,57,155,76,107,75,89,76,39,234,1,6,0,72,167,147,243,73,114, +207,163,225,224,65,242,190,231,72,126,139,76,187,190,32,241,97,81,229,19,101,87,76,102,159,31,22,223,99,241, +181,253,27,34,197,119,191,237,109,211,95,214,237,93,70,157,40,35,239,134,170,61,124,223,247,146,248,65,104,30, +150,136,141,254,73,9,177,29,47,87,218,177,136,88,161,83,242,232,0,253,38,74,58,138,145,20,26,28,158,106, +29,132,12,94,111,11,83,67,212,74,223,243,176,102,84,131,252,230,113,254,223,42,86,91,155,52,96,191,162,184, +148,156,148,126,177,165,106,212,42,168,107,110,180,153,111,158,32,141,178,9,154,154,87,67,32,220,160,53,84,208, +74,248,38,186,114,114,8,241,98,135,39,52,98,17,147,46,147,162,75,220,194,20,217,22,241,235,167,169,133,37, +154,147,6,171,184,208,251,89,232,93,203,101,65,244,112,163,163,171,160,48,179,235,98,215,160,122,175,119,129,138, +192,99,19,155,191,36,58,65,237,31,143,201,209,13,244,55,135,202,120,235,196,246,58,118,195,39,38,127,179,18, +226,77,146,45,46,143,105,36,23,199,214,164,183,186,133,56,23,72,108,162,115,108,128,9,168,146,27,168,241,75, +10,199,138,61,32,44,134,9,137,45,208,205,183,128,122,222,123,97,71,208,32,190,7,6,54,170,5,225,49,102, +46,135,215,222,38,142,189,149,94,95,29,84,134,121,57,52,7,66,230,189,186,86,40,60,163,46,236,247,250,152, +238,10,204,44,27,240,220,73,72,77,189,199,227,136,228,166,73,130,137,182,200,97,198,96,160,145,155,131,119,127, +41,22,90,235,102,168,185,167,82,111,204,172,71,71,19,140,90,31,118,41,81,207,64,107,173,73,155,27,124,179, +173,5,91,192,87,136,166,59,128,96,33,22,5,27,35,50,61,191,83,225,157,94,150,23,128,110,226,51,153,50, +114,114,176,20,14,180,240,77,180,2,152,66,239,137,248,21,123,173,111,72,98,55,170,96,21,217,251,144,90,179, +124,46,114,4,32,161,22,143,243,156,228,99,224,186,100,233,127,95,72,173,109,61,164,151,101,37,107,220,109,192, +27,63,227,34,92,133,42,194,18,146,64,196,240,209,215,167,250,55,142,54,230,186,66,73,218,157,163,111,118,18, +62,248,218,232,47,24,139,198,143,26,0,63,216,120,173,104,236,139,112,199,78,170,206,227,195,33,131,159,215,84, +221,39,122,195,240,82,169,90,56,87,147,88,63,120,88,99,38,103,185,207,60,25,160,187,42,118,199,159,170,226, +224,87,183,237,113,59,164,246,38,220,224,214,40,70,175,44,159,33,105,248,234,106,185,188,181,109,13,169,127,140, +129,31,27,110,252,226,48,238,236,47,88,172,26,38,16,24,174,147,214,65,56,223,248,83,184,130,181,35,44,137, +135,109,159,21,208,117,7,209,178,215,173,28,59,226,76,16,122,46,59,111,249,214,226,253,6,239,227,80,217,43, +154,227,164,226,211,16,55,56,138,131,164,251,116,12,30,148,158,142,201,144,142,41,144,121,192,30,39,121,190,87, +26,5,156,75,251,194,33,228,40,141,74,241,38,191,38,137,92,188,203,162,235,153,149,94,206,214,248,32,27,43, +50,206,174,11,127,1,134,216,130,47,190,51,17,187,62,34,57,109,184,225,174,217,43,163,203,21,83,125,176,113, +86,83,168,207,203,221,29,33,19,11,158,9,179,69,147,172,168,183,151,218,236,3,208,21,137,10,236,235,175,16, +30,204,11,116,86,208,2,79,98,172,232,10,200,233,100,171,47,213,70,111,103,151,129,33,184,137,159,84,104,184, +92,195,45,125,163,140,170,227,135,149,90,163,168,50,196,94,206,244,6,92,238,40,40,55,165,195,89,143,85,93, +199,222,10,47,14,117,19,63,164,76,175,0,23,146,125,36,159,17,57,73,243,203,169,190,64,111,91,23,206,177, +19,157,112,27,62,103,237,49,191,85,124,30,110,167,27,100,187,29,60,203,7,214,82,26,222,173,114,107,227,153, +65,150,139,191,65,238,91,173,63,183,174,153,62,21,123,76,157,62,58,177,48,194,46,27,77,192,241,158,107,102, +97,101,15,219,37,208,170,15,195,196,53,114,247,18,149,183,42,12,42,131,101,132,224,26,149,197,201,202,127,123, +234,108,136,11,195,3,201,16,47,217,230,20,244,39,97,36,60,79,245,154,105,244,245,49,177,239,30,174,220,215, +35,180,235,37,152,125,46,131,59,161,173,108,81,170,239,199,86,109,192,86,109,220,175,212,48,231,149,170,244,132, +63,45,104,58,43,160,11,180,168,233,20,63,121,97,83,69,203,94,116,163,18,124,18,13,27,237,117,0,75,214, +6,33,225,61,233,153,86,159,16,86,6,20,220,16,10,222,71,180,188,241,189,101,244,242,172,98,177,55,47,50, +24,119,221,123,84,205,215,19,88,193,201,28,190,195,51,199,222,187,134,215,12,150,81,84,80,237,35,202,228,248, +191,20,36,201,136,255,246,216,22,110,50,210,214,199,253,22,172,132,13,50,232,147,247,246,165,38,150,106,156,160, +126,46,246,30,31,181,235,124,105,160,251,180,47,160,78,107,162,235,194,85,238,122,58,97,239,132,216,151,119,80, +36,217,249,112,215,169,11,186,154,113,161,96,18,234,210,128,75,180,103,237,223,76,93,97,62,111,30,242,235,30, +43,227,95,184,205,221,171,139,129,11,111,139,221,145,211,95,65,172,47,222,202,219,186,152,78,201,178,147,59,42, +238,187,36,213,83,52,62,64,52,191,39,52,4,231,229,222,222,201,92,247,247,140,107,72,209,152,230,20,132,144, +100,79,1,131,165,103,143,207,213,199,119,216,191,74,85,146,1,116,95,151,57,219,191,173,207,232,160,17,14,149, +41,252,253,159,245,125,192,17,185,71,84,195,154,234,97,204,192,47,106,65,84,160,180,210,230,101,108,2,255,197, +40,52,21,66,23,168,232,158,14,150,248,102,18,12,180,160,206,149,249,141,124,153,27,199,151,185,189,151,47,51, +198,57,57,190,161,103,129,177,148,91,68,207,158,151,211,231,195,152,99,250,101,118,203,36,2,142,142,101,236,164, +211,168,88,208,23,50,107,226,233,36,158,116,247,179,69,120,170,71,121,35,200,22,249,31,115,68,156,13,13,207, +5,217,159,193,226,163,248,26,217,1,190,6,202,86,143,242,53,248,84,102,53,174,165,78,233,67,152,228,48,243, +45,113,50,182,211,233,192,40,7,208,113,64,75,13,86,112,139,116,211,14,208,168,190,60,91,157,3,29,81,203, +163,246,185,201,0,247,23,209,241,159,226,164,30,33,248,160,72,172,110,160,232,18,139,14,57,64,73,58,82,4, +178,130,94,4,145,60,236,116,255,194,95,32,111,96,212,72,215,57,226,232,190,41,4,50,120,132,39,160,42,89, +7,111,84,32,106,71,239,251,27,181,85,151,120,217,63,200,43,202,84,163,138,251,68,125,61,149,215,159,94,111, +92,137,17,215,221,149,204,27,33,97,58,34,101,216,72,27,92,93,28,24,190,205,0,245,227,45,212,77,8,86, +21,230,64,185,239,41,234,231,3,161,225,239,34,21,164,117,221,215,76,98,108,47,47,209,60,19,4,127,51,149, +65,94,89,38,191,145,106,184,199,114,41,17,13,95,143,18,13,55,131,147,191,83,183,195,152,78,109,138,45,144, +3,216,129,170,42,218,124,147,236,50,54,164,137,113,121,91,4,11,198,19,200,223,243,253,118,147,245,28,227,234, +83,213,192,63,123,197,62,161,11,207,147,239,195,233,146,227,5,111,218,42,118,77,154,85,77,148,122,135,164,77, +94,98,104,21,210,80,45,207,167,97,104,171,137,69,245,33,214,79,159,27,248,68,241,186,163,19,124,118,244,7, +193,181,186,126,104,212,7,245,225,33,222,181,15,228,159,185,2,121,217,47,0,90,84,232,44,85,173,96,227,195, +231,87,106,173,150,200,149,220,68,63,217,216,27,252,156,218,248,121,9,140,200,217,5,156,38,48,104,184,111,221, +224,87,165,103,209,213,148,34,107,248,188,156,98,172,187,46,17,145,253,83,194,207,241,63,39,141,26,115,50,205, +239,74,133,97,98,108,220,72,10,238,25,251,169,238,162,205,24,77,150,254,77,80,218,5,169,182,237,104,60,247, +106,36,238,103,142,235,27,223,176,164,201,112,175,143,9,116,169,125,254,224,1,229,230,96,47,160,12,62,243,43, +93,232,167,211,146,141,68,254,26,149,40,190,238,248,19,239,237,187,22,29,143,195,183,173,121,165,51,40,9,47, +76,37,114,7,1,101,177,113,53,58,28,172,111,168,118,106,188,25,53,223,58,57,117,136,106,230,119,190,174,90, +218,182,225,73,231,162,237,171,68,213,156,99,246,223,39,49,21,249,44,175,193,189,253,202,212,166,204,204,225,114, +153,204,22,247,26,67,3,9,120,236,58,39,248,223,43,163,103,223,11,218,232,4,44,166,29,50,167,17,244,133, +202,184,207,127,200,27,94,26,230,196,34,97,228,57,120,135,237,108,148,177,224,126,210,5,189,63,49,196,191,29, +25,248,188,21,47,105,136,80,123,212,91,5,115,238,119,9,215,149,180,170,55,43,137,153,181,163,116,141,239,171, +218,121,77,176,164,68,154,236,30,49,213,160,164,246,105,64,172,81,60,21,9,214,179,78,84,113,164,112,165,149, +1,88,73,73,151,200,15,143,125,220,123,167,246,49,201,225,168,147,222,213,251,54,29,0,192,182,133,237,136,250, +201,168,233,46,158,130,160,153,31,169,26,105,226,68,237,250,83,179,86,126,214,86,12,250,163,32,164,28,14,90, +58,28,180,245,56,104,227,113,208,85,167,1,177,175,212,90,21,136,8,221,128,112,24,75,117,1,97,30,24,70, +108,149,120,6,245,163,187,84,23,60,228,247,250,182,242,35,21,52,135,130,65,92,135,146,239,31,22,152,251,3, +96,236,217,117,252,200,143,128,129,147,33,208,78,196,219,138,30,204,230,61,92,181,121,248,190,143,168,174,32,162, +57,110,49,175,157,16,207,136,226,242,251,56,240,253,236,195,195,145,137,103,24,144,211,63,68,171,97,32,195,74, +161,202,12,71,54,46,104,124,162,42,65,103,95,200,69,29,226,83,49,133,227,112,141,81,99,59,207,19,62,72, +209,120,202,131,191,12,129,8,186,230,184,135,53,137,18,72,65,229,221,155,117,240,250,111,172,208,184,56,73,250, +147,221,239,140,172,227,97,245,232,251,223,118,183,168,229,94,79,117,45,20,63,51,93,239,107,245,168,181,142,82, +50,29,138,55,87,92,74,4,202,21,70,182,213,6,226,236,219,30,70,46,53,16,198,153,155,20,203,179,218,234, +37,15,71,46,181,218,248,104,185,206,234,234,163,238,52,151,7,238,52,87,72,102,140,223,105,46,45,153,204,186, +125,3,108,251,158,206,129,247,112,6,188,39,91,26,239,227,155,41,103,236,3,211,123,85,197,156,215,96,222,105, +137,185,221,60,95,31,42,131,91,178,61,123,127,174,110,209,148,194,122,42,247,28,16,147,43,25,241,179,125,211, +120,134,214,75,147,27,124,204,224,207,233,245,0,147,95,43,49,115,128,120,196,244,38,219,110,126,1,116,249,173, +191,231,92,222,251,2,250,94,125,24,123,254,84,48,21,215,99,247,154,15,234,189,186,189,243,94,115,169,96,65, +227,110,128,50,238,97,117,122,214,5,217,51,165,201,182,182,142,205,168,173,227,214,239,133,6,56,46,158,189,121, +100,175,123,21,188,41,220,185,37,233,209,193,64,89,111,121,47,42,167,218,63,14,8,129,230,178,27,221,141,119, +51,7,8,113,10,46,137,239,88,27,47,190,127,24,249,118,96,75,153,56,249,141,146,45,165,216,200,94,157,219, +178,55,26,214,234,6,175,221,70,32,11,85,250,237,93,84,184,59,7,66,42,13,217,48,245,138,241,85,215,237, +31,236,109,240,57,102,123,38,187,17,76,8,40,126,73,56,130,252,210,232,239,24,93,229,5,165,33,127,185,79, +82,53,49,16,124,186,28,210,80,13,185,6,165,186,16,141,9,130,202,25,69,176,205,59,177,33,33,234,221,14, +27,46,116,122,120,103,96,75,5,203,234,145,215,111,188,251,210,102,195,171,175,164,192,48,91,152,42,82,152,63, +38,133,211,79,123,177,177,176,108,44,9,36,57,183,158,48,29,183,234,235,225,255,94,62,221,188,79,34,50,150, +193,190,85,182,111,223,186,8,225,186,118,252,232,246,189,27,146,205,212,165,187,169,98,156,169,233,40,65,86,202, +233,24,164,133,199,116,63,134,207,163,67,84,58,158,159,215,84,14,169,63,24,206,221,164,143,175,250,48,177,114, +207,125,0,142,243,211,174,43,12,49,52,150,213,246,221,186,220,182,104,113,250,183,179,52,210,58,19,12,141,100, +215,163,43,240,89,66,158,169,200,155,249,205,220,15,121,118,224,163,9,31,44,248,41,14,14,8,133,67,5,67, +225,48,154,40,207,65,177,48,180,199,74,97,122,30,121,41,202,82,226,201,228,79,39,191,155,4,178,250,100,112, +132,253,225,207,39,170,182,39,214,228,244,4,179,114,29,144,211,153,112,132,164,122,210,217,89,181,62,85,170,26, +38,118,196,191,138,239,128,116,197,50,76,195,154,36,151,39,109,54,38,107,95,211,67,255,105,96,227,20,230,29, +202,17,88,196,8,97,8,153,26,22,128,69,210,123,74,44,206,228,95,203,200,215,251,133,110,135,16,195,165,124, +173,196,201,178,110,70,77,215,65,85,182,89,135,165,221,190,240,21,95,165,27,239,212,209,217,106,200,135,136,241, +68,94,46,131,98,108,227,145,61,106,68,39,165,90,229,69,97,219,174,247,28,172,54,80,252,189,241,169,193,123, +143,42,220,171,182,143,167,144,146,67,81,246,48,77,142,242,209,35,215,46,45,122,175,119,40,135,60,22,130,47, +156,2,85,99,152,23,102,120,231,181,213,59,128,190,33,41,237,238,16,46,151,243,173,9,245,124,20,7,78,209, +140,122,73,122,156,122,140,240,158,86,38,9,188,4,228,232,204,4,118,89,203,106,80,115,175,249,208,146,185,253, +246,184,33,37,182,88,161,243,225,169,54,113,210,194,95,213,118,93,120,15,124,109,254,93,239,129,247,191,70,40, +198,55,184,188,248,149,144,235,145,11,142,244,190,238,239,84,17,23,78,255,83,246,172,94,116,250,54,35,143,213, +117,60,31,218,218,77,247,204,236,194,235,10,228,142,73,247,6,109,198,120,232,87,165,119,141,227,187,128,113,7, +140,111,80,18,189,15,144,73,190,163,74,4,85,201,206,185,117,211,183,20,113,143,69,142,121,239,22,141,238,135, +80,76,3,212,198,50,9,205,200,199,206,156,135,120,45,139,184,200,49,138,184,116,106,208,140,190,145,139,30,60, +89,103,0,153,227,55,216,6,137,165,127,253,137,40,72,17,21,97,201,179,145,135,163,245,71,93,178,86,7,46, +89,176,140,203,3,151,44,148,149,117,15,71,27,157,218,143,224,193,251,74,241,228,37,151,93,255,42,126,161,191, +67,225,158,197,213,97,123,95,55,247,128,45,104,233,195,69,218,91,223,38,74,244,189,70,154,123,248,32,101,102, +167,225,29,247,90,155,249,53,95,228,174,221,76,227,109,237,26,111,107,131,146,215,177,122,166,111,22,31,146,93, +167,222,232,111,162,91,16,229,140,213,115,253,12,30,191,244,200,235,213,45,196,43,40,244,163,126,6,57,81,178, +247,205,71,61,97,21,123,114,89,169,186,85,69,156,96,131,80,225,252,153,124,221,122,238,95,183,126,140,161,5, +5,137,104,214,254,26,135,142,61,152,189,135,63,79,47,212,37,108,200,103,206,20,207,45,100,131,13,203,150,84, +96,180,176,170,152,238,47,140,171,123,47,140,215,135,46,140,92,114,120,93,188,86,207,84,67,188,172,195,87,198, +149,106,212,58,254,248,151,110,105,32,85,5,127,81,160,152,239,63,135,36,121,195,37,240,138,100,245,138,6,158, +122,202,240,234,117,118,242,113,207,225,248,244,87,67,246,32,115,249,113,229,66,254,64,4,11,7,181,149,66,158, +231,125,18,66,115,63,3,60,165,159,217,211,255,59,194,143,146,136,13,156,30,56,63,131,152,109,204,135,141,109, +167,235,94,91,138,182,200,113,31,189,190,135,154,181,217,14,62,210,57,12,139,52,172,71,4,64,191,98,189,191, +65,64,231,144,20,78,239,29,237,118,232,148,163,239,86,130,175,160,184,230,185,179,86,200,89,3,50,229,203,50, +62,184,231,193,152,41,73,159,102,164,20,185,150,190,71,86,210,247,200,178,211,245,158,239,145,21,61,180,150,109, +224,234,190,106,97,55,115,189,89,12,23,113,101,22,101,2,177,173,42,212,8,18,201,98,204,20,171,19,85,34, +152,168,229,162,242,245,65,235,189,250,214,49,240,49,166,167,80,227,201,29,53,174,99,155,45,86,232,239,108,214, +36,80,225,172,233,122,238,143,26,62,250,165,198,30,156,234,249,192,44,105,171,110,131,12,43,126,144,234,80,227, +220,133,236,110,112,226,172,206,207,13,206,158,85,241,185,197,88,67,177,183,24,107,48,214,42,249,149,177,215,101, +148,21,106,52,206,41,212,143,176,94,160,234,169,102,80,231,62,198,202,49,140,21,97,248,214,166,27,151,142,205, +216,112,234,109,122,245,95,191,201,80,83,213,177,67,133,252,55,61,27,126,252,211,224,255,54,135,231,184,254,237, +60,158,143,23,114,9,166,216,237,160,162,88,125,140,228,139,44,115,216,205,64,192,78,56,110,193,165,247,59,199, +88,246,243,12,235,94,65,25,230,60,67,24,206,187,88,85,66,97,218,62,155,212,33,166,228,183,161,32,131,184, +168,30,193,149,243,225,126,66,114,138,101,83,29,85,248,52,211,30,239,113,17,88,110,124,190,183,198,213,44,125, +24,200,215,125,216,24,22,152,165,255,10,231,126,239,113,248,32,255,126,184,158,236,166,128,236,113,221,124,102,213, +112,150,240,125,203,223,91,180,189,197,244,56,221,253,225,128,155,29,255,233,225,79,115,171,203,180,85,87,188,23, +47,53,92,225,237,67,19,226,11,158,168,48,30,75,104,93,1,155,232,138,56,235,87,241,198,115,214,171,171,13, +76,133,173,31,189,23,95,186,204,6,51,3,69,118,21,24,189,23,64,145,93,157,51,11,127,3,52,196,102,122, +176,22,117,173,235,253,123,39,146,151,107,138,206,1,195,149,25,233,55,218,241,122,196,104,1,29,26,2,94,236, +124,163,223,147,156,79,255,61,3,34,174,237,237,64,178,104,48,250,70,191,215,91,47,50,243,1,57,253,43,117, +155,44,123,124,249,147,30,207,254,250,0,175,255,189,191,52,220,67,63,92,169,139,17,18,172,155,143,208,95,23, +234,74,125,32,157,163,177,133,186,199,225,193,0,128,60,131,222,51,238,104,226,188,235,79,54,8,178,115,204,110, +59,173,213,249,125,220,248,10,50,148,211,105,23,171,178,235,175,108,95,23,250,62,5,0,150,11,184,71,24,234, +56,197,170,209,24,8,250,141,125,102,229,146,54,85,145,214,136,132,38,243,103,82,52,233,95,229,216,157,116,255, +99,121,37,15,35,31,205,182,67,110,74,143,125,38,225,236,164,195,145,253,223,28,176,255,155,3,134,51,42,45, +91,112,69,130,200,56,203,131,29,214,243,233,40,79,12,57,93,202,145,239,181,3,104,100,234,166,197,43,22,149, +167,141,134,1,200,178,204,155,77,145,222,226,29,64,138,208,7,209,123,226,16,195,38,196,61,99,231,148,32,110, +80,84,2,116,224,181,189,41,61,221,86,152,93,247,166,180,123,58,135,123,12,124,135,221,204,220,233,147,223,192, +153,246,77,244,72,195,223,66,206,121,45,142,251,140,87,27,105,251,4,200,53,39,172,60,153,154,125,113,103,111, +78,62,254,24,74,14,239,200,254,190,220,240,183,37,37,189,182,159,108,29,55,117,224,180,169,22,57,233,214,167, +183,87,242,172,255,53,190,91,61,224,187,121,53,192,221,5,250,110,71,184,184,88,193,155,238,43,12,56,57,15, +225,255,195,31,149,117,55,191,139,39,151,34,37,58,206,125,59,81,205,199,203,105,143,156,143,245,65,2,43,149, +244,155,231,57,21,64,225,20,68,225,20,129,194,201,116,11,18,121,247,243,230,10,149,141,73,36,175,152,255,4, +43,5,127,172,139,246,192,79,98,163,199,208,0,50,239,210,69,237,200,190,100,117,124,163,182,20,115,235,99,110, +213,6,169,151,37,80,47,91,187,119,33,146,126,21,178,157,18,123,170,47,61,219,105,27,251,69,88,143,46,66, +166,10,181,33,218,163,123,67,199,44,226,7,212,49,130,208,191,194,92,24,156,113,130,213,96,108,110,60,198,48, +102,135,152,62,177,39,41,241,12,222,28,62,1,239,65,102,1,217,124,22,48,193,107,64,54,159,217,145,53,25, +226,70,24,219,103,114,108,161,111,167,116,234,32,14,195,28,162,19,57,206,126,154,17,54,66,167,18,126,152,191, +85,184,120,136,216,81,201,129,159,55,166,19,53,130,194,81,215,129,209,248,199,75,60,119,243,15,105,253,224,58, +27,152,196,221,93,92,144,161,187,139,11,107,237,11,212,12,63,243,71,113,242,62,87,159,146,224,183,136,251,58, +87,207,249,237,84,196,22,70,225,124,137,152,215,70,125,231,136,51,17,253,44,87,223,229,50,223,155,82,189,70, +232,146,81,185,122,67,203,34,35,63,203,187,120,30,60,26,91,107,128,117,117,253,0,173,98,126,94,215,176,89, +39,160,199,216,60,184,50,237,186,90,62,128,175,178,106,31,228,87,27,130,45,228,238,63,32,79,83,64,142,166, +128,176,30,32,193,74,110,34,17,244,31,64,23,54,208,32,22,131,9,249,0,107,188,60,158,196,204,95,48,101, +159,169,128,77,75,223,76,104,145,161,179,139,212,248,165,165,62,10,41,97,25,205,153,247,227,211,37,155,111,233, +71,47,243,213,106,44,158,118,200,183,163,73,0,232,54,161,31,13,241,30,138,181,155,206,40,31,122,80,55,101, +48,128,168,242,216,66,207,251,12,176,60,206,86,98,74,193,199,187,48,210,255,233,164,76,9,203,229,64,161,44, +0,52,209,154,213,45,252,189,237,146,95,43,202,216,249,146,95,103,67,111,162,222,35,181,191,194,11,199,212,67, +23,202,24,178,46,161,205,158,75,232,200,90,41,241,15,107,157,54,32,214,28,251,114,236,23,136,203,121,247,64, +149,38,199,208,21,49,236,129,49,30,85,170,84,133,116,35,251,108,104,45,14,254,122,32,109,108,107,149,98,126, +37,90,36,202,161,127,99,198,33,200,82,55,185,95,225,167,23,248,198,243,26,197,84,64,239,172,26,88,249,39, +53,113,182,145,101,181,137,23,89,1,92,67,58,242,201,182,195,208,42,181,80,6,38,191,70,154,141,63,100,56, +203,196,167,131,214,220,184,160,209,16,15,16,142,213,10,7,14,133,53,16,59,195,215,36,23,152,174,3,33,84, +37,43,100,141,174,243,100,9,188,74,196,78,164,168,203,144,199,5,226,46,100,63,193,188,149,231,167,139,9,254, +172,228,9,150,199,248,189,160,97,189,4,5,43,154,246,41,211,43,252,165,79,10,180,163,203,245,49,108,100,118, +181,214,157,70,243,186,138,70,65,67,232,52,172,50,197,181,170,150,106,132,91,189,2,109,42,189,236,123,56,94, +195,131,205,124,195,32,83,70,27,149,169,109,220,137,33,189,25,154,209,100,59,16,232,70,243,8,173,63,40,35, +99,111,57,214,221,166,252,54,237,19,54,193,154,10,48,84,103,13,104,216,37,100,229,65,198,223,66,252,45,196, +247,94,43,154,127,212,204,144,222,84,215,176,52,143,227,169,15,213,16,146,224,254,75,190,111,2,240,236,220,85, +247,55,68,97,185,96,5,2,27,24,88,127,223,1,26,131,119,113,96,15,208,98,26,168,192,143,193,90,84,219, +225,91,57,195,26,32,136,22,136,152,18,178,179,45,138,157,115,79,92,247,221,37,167,124,17,43,186,184,35,239, +79,141,0,155,161,125,58,236,166,79,173,184,93,94,127,113,29,201,2,31,103,221,49,85,6,215,250,38,58,59, +112,147,39,235,143,59,166,179,58,253,30,171,6,60,215,18,158,131,129,192,49,5,138,55,43,244,65,246,219,70, +196,115,234,167,172,234,141,239,187,76,108,11,86,12,241,102,179,107,253,134,252,85,90,200,63,244,40,27,166,163, +136,200,34,163,91,210,165,206,246,150,163,177,151,9,16,129,116,246,240,189,159,59,66,20,150,252,36,2,54,178, +121,143,0,26,182,119,66,195,176,170,43,141,108,249,109,12,12,196,116,17,193,120,252,52,101,253,105,90,243,52, +173,58,244,116,115,21,39,87,136,53,17,73,242,236,222,87,108,127,118,209,183,83,152,221,31,114,57,187,31,7, +221,11,163,25,93,31,149,11,15,126,77,156,244,150,42,57,59,15,205,92,143,24,212,188,48,216,74,172,112,57, +113,251,218,167,161,84,215,108,127,14,78,210,159,104,105,38,9,124,254,108,63,217,188,232,17,227,8,62,74,121, +85,145,215,130,150,214,155,179,26,129,245,183,76,147,10,128,0,8,69,53,188,47,163,2,237,160,67,178,129,228, +22,143,160,172,133,97,23,139,179,243,164,234,144,54,120,158,105,162,130,155,196,34,215,59,198,201,67,132,87,141, +27,100,146,139,42,23,132,109,26,85,65,185,228,7,247,141,222,220,99,149,10,180,83,59,103,146,209,125,199,130, +231,113,22,158,1,149,233,26,142,67,230,249,175,117,97,175,90,217,249,124,13,35,90,19,22,71,213,239,254,140, +173,251,51,86,112,113,62,71,58,156,154,148,150,218,229,251,184,241,223,218,133,252,216,73,192,93,230,198,254,212, +219,165,79,237,128,100,7,85,177,207,101,75,99,203,247,171,165,177,105,107,72,170,8,26,83,89,92,15,134,94, +192,220,140,35,173,44,108,171,154,121,63,31,61,108,46,39,198,218,41,188,170,152,230,55,215,225,231,72,76,34, +213,39,128,144,179,242,254,219,97,37,9,130,159,47,144,136,194,29,149,190,189,167,244,237,93,165,187,142,81,220, +23,176,173,217,148,43,25,105,85,108,206,85,57,235,173,226,144,90,73,66,26,25,124,112,245,132,74,35,163,159, +26,56,156,145,55,33,169,231,247,205,193,236,95,84,158,154,160,146,214,190,22,74,89,92,86,55,56,133,195,186, +150,131,166,217,82,56,142,222,111,156,6,72,142,50,49,170,34,163,164,165,155,254,134,165,120,161,198,138,63,157, +169,171,25,119,35,113,121,102,46,71,39,218,254,86,82,71,206,114,21,155,202,85,78,93,209,232,19,85,234,40, +71,230,87,236,192,213,60,65,57,32,19,55,58,63,51,64,192,109,152,147,146,84,193,44,19,9,89,37,181,162, +95,43,46,156,164,26,8,78,228,143,49,168,219,78,26,5,147,147,52,10,106,129,10,132,81,189,102,104,181,208, +142,193,143,74,113,27,128,104,167,131,134,186,216,179,196,195,128,95,245,6,188,235,104,128,54,104,30,0,129,238, +18,185,239,37,245,168,145,21,147,104,160,125,128,7,45,38,88,109,150,237,106,162,38,152,92,243,180,1,154,137, +0,214,29,254,232,157,149,19,56,81,192,154,205,204,18,62,120,48,39,214,194,205,9,246,216,62,14,78,167,170, +230,17,78,117,213,237,143,227,237,224,250,70,227,82,187,15,159,86,55,32,14,100,121,238,165,90,219,224,151,60, +103,157,182,106,7,126,105,217,56,89,62,180,75,182,75,97,85,43,166,112,118,200,83,124,99,77,240,232,20,161, +88,101,64,240,179,117,33,188,220,100,136,180,197,20,61,202,184,239,243,180,103,102,57,181,122,125,122,189,88,63, +44,147,2,223,60,210,15,105,94,160,84,26,117,89,65,126,11,208,77,156,248,236,101,136,198,146,205,160,164,29, +157,16,201,247,147,244,121,111,177,9,214,149,209,176,223,90,183,95,65,73,55,163,125,233,198,24,19,109,91,98, +166,21,98,62,139,63,108,108,19,98,217,46,116,172,42,31,135,40,134,51,214,62,146,49,141,117,199,255,190,97, +247,253,170,224,239,219,137,131,209,48,201,70,97,147,192,37,7,7,128,137,247,59,80,197,10,219,196,248,79,169, +202,164,113,73,69,236,190,106,255,149,198,42,168,166,218,158,248,48,180,255,1,95,206,224,34,28,234,111,226,80, +157,220,127,213,88,221,157,96,35,8,170,106,40,149,69,136,161,133,63,241,84,196,149,16,7,127,4,18,250,75, +197,110,28,80,56,65,135,172,24,84,173,149,88,200,73,144,65,38,98,88,181,244,131,201,118,162,69,6,142,81, +45,127,96,38,154,194,144,135,35,84,107,127,69,151,94,239,113,26,44,30,64,44,197,162,193,57,86,193,244,40, +161,131,95,73,253,198,144,52,27,90,200,6,85,169,153,182,65,199,170,95,161,75,91,222,55,192,184,178,187,158, +197,135,78,1,23,81,230,208,63,27,86,70,110,162,138,183,66,82,217,221,17,43,99,75,217,204,143,86,22,129, +40,108,126,202,205,119,228,95,26,251,74,62,106,96,190,107,37,163,34,97,82,208,55,126,162,90,43,128,65,59, +115,6,11,93,171,156,247,131,216,0,197,72,9,187,35,125,17,220,24,114,39,100,58,61,210,48,127,215,72,2, +210,215,58,152,101,191,214,72,235,67,66,127,216,187,38,189,194,11,99,133,238,112,225,182,152,216,136,53,71,72, +159,2,159,202,93,223,95,38,159,199,68,130,196,241,3,64,184,156,17,132,158,248,179,3,131,83,221,168,166,3, +62,26,76,104,196,251,28,120,107,24,114,248,193,68,60,37,248,233,199,26,58,245,114,128,175,219,209,110,149,145, +96,50,236,176,106,0,15,104,15,153,11,124,82,216,170,225,172,240,52,128,167,179,107,32,23,208,146,146,24,16, +132,20,49,223,186,88,249,99,164,140,242,197,89,111,33,209,193,95,127,153,228,254,188,49,163,124,8,127,146,40, +114,250,61,114,158,40,36,113,7,103,138,42,248,12,41,220,51,44,163,121,224,248,29,95,123,76,143,161,181,130, +89,147,39,136,106,29,160,218,213,95,241,234,47,59,253,218,218,201,75,85,9,57,254,169,87,120,251,231,78,172, +53,212,189,84,133,199,241,168,69,76,196,71,234,15,13,56,193,96,148,13,143,18,108,103,118,129,221,109,196,141, +146,113,148,113,248,168,245,72,165,157,150,1,11,153,105,163,114,127,122,229,254,80,19,172,204,102,220,241,5,1, +4,78,45,58,18,87,183,100,86,93,208,40,169,164,81,30,20,254,56,46,207,210,128,86,24,159,236,145,25,167, +29,204,197,248,65,13,234,89,136,195,228,100,7,6,36,172,203,195,53,185,126,108,104,246,10,30,207,252,178,141, +50,107,77,48,38,87,35,46,32,230,122,1,211,87,168,198,98,235,90,25,137,82,26,22,82,227,212,101,156,96, +102,198,233,211,236,216,246,31,74,173,32,77,113,229,186,86,46,101,170,87,170,214,5,79,58,113,61,69,159,215, +131,62,211,106,12,186,92,29,238,114,165,26,58,127,150,202,244,144,90,227,68,233,40,153,187,92,217,99,42,244, +121,169,86,161,199,85,175,199,149,46,236,184,187,14,248,61,26,139,222,234,186,251,242,24,111,174,19,120,180,175, +182,168,179,128,14,0,24,63,224,43,221,134,63,119,227,216,64,89,92,209,117,49,189,22,64,163,59,40,0,212, +31,159,172,8,38,6,61,40,218,47,220,191,177,106,253,144,117,248,4,154,246,20,82,28,57,175,253,39,80,187, +22,69,180,238,210,209,122,224,65,204,127,1,93,55,117,163,221,23,180,230,160,221,191,203,156,237,126,133,190,146, +240,180,65,7,133,252,213,117,112,121,231,158,57,191,97,157,178,158,173,120,12,1,115,114,190,5,255,250,203,86, +75,134,125,205,145,189,109,113,170,115,93,102,212,41,212,104,125,107,110,107,227,196,70,196,12,24,255,41,71,111, +252,103,24,182,225,143,78,49,2,227,141,204,126,168,250,108,182,70,23,109,148,251,215,124,187,188,110,159,247,4, +49,17,180,8,70,7,226,152,6,226,237,126,179,178,150,64,212,242,224,172,253,90,71,203,169,76,203,237,59,255, +193,101,83,87,112,46,240,251,195,213,241,165,1,108,98,94,81,63,250,111,16,253,180,40,238,28,81,176,70,136, +53,203,45,204,99,116,165,46,225,86,122,137,53,7,5,130,94,240,152,101,79,200,172,14,168,169,92,77,81,72, +20,16,140,90,13,31,64,3,46,72,90,37,54,89,98,60,188,55,170,127,67,72,170,16,193,153,107,213,187,245, +128,243,165,71,235,193,205,167,6,213,128,88,45,71,172,120,197,115,160,134,224,112,32,229,90,207,101,29,228,11, +71,117,178,84,215,120,65,133,70,111,18,198,91,183,9,161,2,172,76,109,52,92,204,10,71,41,103,228,117,21, +14,150,52,0,215,86,173,212,38,86,16,89,136,239,140,191,241,24,10,9,64,201,108,99,245,172,129,242,225,78, +224,18,183,72,128,108,143,175,225,227,22,63,214,54,99,255,150,192,153,37,39,149,233,138,173,237,60,162,147,45, +33,58,44,232,162,167,88,47,227,22,74,157,98,253,76,120,194,39,27,149,135,92,157,250,33,74,67,229,4,107, +78,222,245,10,193,98,222,159,203,75,217,149,88,93,58,34,0,27,196,138,239,33,122,0,40,189,80,193,79,213, +46,205,254,177,205,107,35,124,124,199,187,174,54,133,73,155,16,41,252,233,195,34,126,254,193,148,45,58,40,68, +25,61,247,120,203,46,75,199,211,144,151,103,62,228,153,125,250,35,209,135,240,208,124,202,10,56,249,213,246,10, +215,119,207,165,138,145,244,177,65,178,134,201,246,82,151,24,226,173,189,179,51,106,220,36,139,66,205,130,2,171, +162,66,94,204,163,38,78,144,201,149,55,207,218,150,222,53,197,8,79,58,175,214,2,136,14,19,156,117,175,23, +25,75,96,140,206,155,239,45,112,150,133,67,240,65,40,154,60,94,78,96,39,163,180,194,94,67,237,190,56,181, +134,25,239,152,14,207,245,228,19,90,247,95,154,137,250,53,211,112,142,109,179,181,85,160,152,92,85,219,198,44, +171,235,114,162,40,26,215,130,99,241,147,99,161,243,28,185,221,76,44,203,211,212,6,255,112,52,125,251,20,172, +79,84,237,227,7,149,251,248,237,102,191,118,0,36,159,25,240,147,143,135,111,17,219,169,55,141,70,229,105,111, +165,6,191,38,147,64,215,127,185,247,216,222,160,240,163,42,45,171,24,86,178,206,47,1,255,69,206,48,111,172, +154,97,18,219,238,37,70,116,126,214,230,231,218,105,200,38,59,6,154,146,247,101,163,168,250,32,6,104,28,82, +86,156,211,56,184,99,176,179,64,137,130,45,62,171,246,95,112,240,95,22,85,246,126,162,136,135,9,96,142,138, +183,226,27,51,144,100,218,12,162,38,234,13,178,194,194,61,38,69,82,215,247,190,18,142,192,34,71,35,87,49, +90,160,128,114,101,28,195,7,79,15,247,145,230,50,246,68,52,151,121,132,78,254,31,123,247,162,178,41,155,113, +216,150,175,160,10,158,23,25,58,191,170,244,107,184,98,110,16,71,125,32,9,100,16,58,10,171,247,163,87,150, +202,143,71,49,200,87,85,44,92,160,137,220,89,90,126,72,155,99,198,46,119,151,251,101,0,35,191,102,103,214, +201,35,144,244,246,3,159,58,75,60,110,58,109,197,57,60,243,8,83,19,99,89,62,73,171,172,24,72,146,43, +200,31,124,32,148,36,102,132,229,67,92,67,113,210,89,97,110,123,49,100,139,226,178,224,155,27,76,186,33,137, +225,52,39,97,197,216,99,158,240,158,183,167,91,230,38,66,53,228,71,247,235,173,149,36,253,246,178,49,53,58, +33,175,224,208,176,111,49,56,237,253,203,78,21,215,186,134,251,91,30,165,56,249,102,249,13,190,116,89,47,128, +53,62,107,66,2,79,176,79,154,67,130,65,2,38,220,145,43,219,86,180,172,50,244,82,74,166,219,242,98,137, +203,145,144,10,249,101,91,27,90,250,222,203,239,207,255,107,99,145,93,14,163,241,195,252,247,141,197,182,255,202, +216,238,166,27,186,183,127,134,134,130,252,40,255,81,121,165,134,92,95,231,37,224,203,227,229,224,200,155,163,105, +136,207,26,216,72,80,54,87,175,76,80,97,192,249,129,81,3,92,108,107,68,200,195,211,18,89,60,112,146,224, +32,98,113,175,102,120,135,154,236,21,141,27,222,219,97,78,138,86,253,163,138,177,93,126,225,19,85,253,21,170, +162,138,150,6,165,202,32,160,6,181,242,116,31,174,56,84,246,197,93,75,14,111,242,207,202,168,180,111,238,77, +159,212,175,244,77,21,69,40,233,32,222,59,129,75,91,228,208,40,209,166,115,67,201,42,123,210,139,182,11,172, +108,79,17,18,112,165,94,83,223,60,88,165,190,206,66,167,40,82,148,233,130,246,33,84,242,218,100,76,85,168, +245,32,150,47,234,153,38,39,19,107,252,1,237,111,124,171,22,16,85,123,136,106,98,69,203,130,220,102,177,9, +16,39,56,239,195,116,42,64,19,37,84,143,74,109,218,139,57,195,21,2,23,66,50,118,239,154,202,155,138,246, +10,196,180,55,222,24,149,137,0,13,86,42,143,97,62,160,27,66,115,1,229,168,184,195,103,181,170,189,77,48, +247,245,243,185,31,16,34,236,18,218,109,16,252,153,86,188,147,252,145,61,188,159,2,242,205,160,201,94,30,15, +121,11,140,190,204,216,207,119,76,104,117,72,137,74,246,31,151,35,56,50,120,154,199,142,72,245,182,162,40,218, +153,181,152,159,185,99,205,29,165,231,126,3,202,105,42,241,217,230,27,244,199,102,24,230,3,5,81,197,9,237, +30,17,163,234,112,205,3,232,182,231,46,138,84,14,52,63,193,214,41,30,234,189,38,141,205,13,13,234,6,254, +160,132,2,31,231,252,171,236,118,124,64,3,129,135,145,131,228,119,187,206,15,30,146,161,119,237,241,39,155,186, +186,201,137,143,17,2,122,215,225,158,217,165,68,14,39,63,100,208,44,125,253,156,41,11,152,201,23,89,119,134, +199,232,95,178,121,3,31,218,53,60,78,246,75,104,184,171,77,152,47,168,172,143,9,34,215,141,159,115,223,141, +156,187,1,95,182,27,223,100,49,182,130,240,137,53,240,89,124,247,61,227,0,86,190,239,246,241,121,225,163,228, +141,65,64,226,179,50,40,85,28,29,69,180,195,243,230,51,187,193,205,82,190,245,124,69,104,150,179,190,168,34, +184,10,48,91,1,141,64,101,181,49,229,103,4,213,61,15,87,192,139,121,144,151,86,145,112,63,235,226,69,150, +252,148,241,46,189,233,43,16,51,96,220,244,20,133,111,101,136,197,251,225,168,85,61,169,96,153,229,19,190,143, +132,248,142,101,195,157,240,127,112,57,132,15,209,183,168,145,141,37,133,56,26,43,152,157,7,146,203,103,237,214, +105,99,85,7,252,164,131,129,14,219,113,184,206,187,239,219,184,243,213,245,33,108,208,71,130,39,216,130,65,189, +27,115,248,77,16,118,102,219,219,140,184,3,105,243,91,127,37,60,49,81,188,160,208,69,91,37,80,13,111,211, +166,235,110,250,142,217,67,240,53,178,97,220,68,113,171,127,171,244,238,3,251,54,239,187,52,206,81,211,38,239, +84,9,116,72,157,123,108,143,36,60,30,53,60,130,201,201,100,196,0,222,64,247,218,217,195,211,57,206,128,113, +146,54,167,65,146,214,95,137,189,140,165,65,217,27,234,89,172,68,164,241,162,173,46,53,158,71,217,147,83,51, +251,227,63,255,153,61,61,53,167,127,130,149,33,215,188,25,30,194,249,42,207,38,200,139,251,7,142,192,196,157, +67,166,87,109,104,203,186,202,222,183,60,59,59,125,40,174,231,117,172,30,159,196,214,244,238,174,100,141,41,235, +153,10,247,232,11,214,90,120,158,191,203,219,38,73,21,84,53,22,223,141,43,222,23,61,40,103,15,234,86,156, +61,86,160,222,158,91,129,105,85,84,239,210,58,111,215,87,31,181,36,249,35,47,129,122,122,162,196,96,96,248, +121,28,135,227,14,202,131,159,56,252,121,108,127,254,180,248,91,117,204,107,143,22,88,11,130,117,101,155,4,208, +144,22,39,104,110,189,93,188,214,173,239,31,22,237,217,99,94,166,89,123,230,86,44,241,159,16,235,87,217,245, +36,172,117,252,84,35,179,23,232,7,209,239,28,87,215,232,124,214,139,139,149,33,105,185,239,114,189,115,74,29, +117,147,252,173,234,230,204,123,39,157,142,137,10,202,115,39,222,217,205,169,119,145,12,159,82,23,15,131,100,192, +34,153,208,114,76,212,59,88,75,147,176,138,158,172,43,40,67,158,18,11,220,90,58,196,20,12,125,91,126,230, +31,236,57,234,45,86,136,1,172,153,189,154,253,23,5,108,53,52,161,250,105,123,236,107,166,68,82,189,12,137, +25,6,197,64,236,77,253,121,218,172,65,102,78,132,190,181,57,78,56,138,123,218,41,82,154,145,26,133,10,137, +25,88,221,254,155,196,31,29,147,238,143,29,150,193,158,163,41,144,215,94,107,16,97,221,135,254,4,193,28,21, +71,92,125,111,80,135,148,167,231,68,196,208,96,100,99,127,80,98,74,241,145,4,125,66,203,239,239,124,70,210, +227,241,195,114,122,64,201,119,249,113,88,127,11,88,180,65,161,157,93,7,189,252,197,126,164,104,191,50,153,100, +204,85,202,234,170,105,158,217,184,210,164,78,137,10,155,248,20,106,93,2,102,167,217,229,111,238,119,253,238,50, +141,30,255,233,79,234,65,248,115,114,252,159,127,138,39,46,167,239,47,248,46,139,1,18,107,68,192,12,139,199, +12,82,19,90,67,248,245,159,123,57,17,218,250,25,89,15,247,206,236,50,207,253,133,8,18,14,244,134,45,14, +94,154,176,141,46,86,60,227,56,47,3,27,132,71,185,125,27,107,126,4,52,21,77,236,195,195,4,246,237,32, +33,93,225,228,199,118,139,79,220,18,78,56,76,10,62,245,168,13,195,0,215,46,51,206,229,167,33,182,27,233, +116,211,235,181,141,27,203,232,23,102,212,180,226,96,97,39,195,206,115,119,239,44,35,245,173,254,54,100,30,246, +15,0,85,90,91,45,132,45,94,229,87,57,176,104,62,33,81,167,6,19,16,156,217,112,222,114,97,240,101,13, +183,125,21,148,66,107,178,221,164,82,36,226,225,196,84,133,182,198,158,170,167,193,192,12,20,83,5,202,4,62, +42,99,85,8,43,114,244,100,94,198,54,255,137,197,238,107,181,226,28,75,93,61,61,181,220,107,82,23,135,219, +49,218,9,137,160,157,216,94,145,136,67,242,87,67,213,103,10,53,37,193,28,79,61,91,170,26,253,116,156,168, +149,134,188,243,245,147,213,124,61,157,198,62,99,115,182,62,199,63,211,211,115,127,60,249,196,212,214,227,142,152, +36,157,46,161,211,221,32,27,70,249,41,254,68,138,140,136,9,182,88,83,225,156,95,224,100,19,141,29,171,18, +195,182,246,71,102,26,181,208,103,180,16,132,177,176,16,175,56,193,117,76,156,66,193,100,191,106,36,47,164,93, +15,47,202,37,196,196,222,156,45,86,215,183,249,19,236,183,168,211,224,123,230,59,244,151,48,208,207,10,138,62, +168,161,37,221,111,21,26,165,69,176,222,226,105,227,42,46,186,177,22,66,87,205,122,92,98,84,72,138,230,82, +70,212,64,123,40,8,102,33,17,175,211,244,192,107,198,132,51,243,117,120,75,13,102,168,144,202,99,95,170,37, +59,107,49,121,1,3,85,214,179,88,46,61,139,161,228,62,154,34,225,102,114,148,125,81,205,116,74,245,52,15, +165,220,25,130,194,190,182,19,186,226,17,47,176,184,88,159,195,20,184,70,226,240,53,247,186,78,94,252,197,136, +254,25,104,151,164,247,155,89,169,140,46,30,9,56,40,30,153,24,1,188,2,208,62,153,199,233,116,10,1,177, +79,170,105,250,208,216,133,20,116,111,165,80,231,27,196,105,50,24,40,178,116,214,114,160,25,12,244,64,69,98, +204,165,92,62,191,82,126,17,201,131,206,99,207,139,224,73,207,113,5,140,62,133,5,109,73,232,23,178,225,154, +206,224,15,160,12,228,222,132,34,222,8,56,95,89,214,238,105,196,74,28,45,88,226,40,193,40,254,94,96,18, +70,169,239,26,109,215,4,73,23,204,128,18,7,112,205,12,197,115,208,52,155,154,4,127,102,38,32,201,231,77, +15,73,2,96,10,64,124,212,226,230,60,232,6,178,154,66,255,141,155,71,177,76,85,236,209,139,20,43,173,246, +118,107,110,81,49,183,32,13,49,181,100,138,12,175,53,61,199,187,53,134,189,219,221,84,195,221,228,207,172,87, +146,75,43,100,136,212,145,43,151,225,202,208,237,60,34,50,124,33,192,162,152,85,170,158,21,113,130,179,116,2, +41,209,126,21,167,49,100,120,244,56,201,52,100,31,105,1,17,50,250,255,156,234,230,73,187,200,146,89,166,138, +39,213,44,5,193,153,167,245,52,141,227,128,30,252,52,212,107,59,229,63,192,143,209,79,133,111,213,119,153,112, +187,249,232,177,157,113,28,65,243,20,242,179,156,152,247,192,233,184,68,86,25,4,239,171,231,243,210,9,110,156, +144,46,188,0,224,173,233,217,225,240,116,50,200,131,8,50,249,68,136,184,19,100,176,112,134,123,210,10,166,206, +61,71,174,141,144,46,68,115,165,136,224,11,12,50,205,233,96,32,250,2,226,144,52,141,23,244,235,79,152,211, +248,161,65,34,156,37,18,166,206,95,73,232,68,202,115,229,84,47,90,8,238,26,182,11,135,76,148,64,109,132, +66,5,195,217,160,24,14,51,49,86,15,196,151,198,200,94,225,108,221,247,148,181,45,163,220,15,196,160,190,171, +223,126,192,108,192,8,237,35,98,2,51,116,76,29,195,92,132,58,215,235,161,84,44,82,254,141,21,62,170,220, +5,160,230,103,251,148,223,163,138,78,231,210,83,78,230,148,231,215,157,46,8,54,200,197,50,250,81,14,186,105, +179,70,93,234,116,70,80,147,15,116,0,104,45,183,186,106,201,238,99,26,171,95,35,35,252,10,75,150,165,137, +17,117,221,0,238,191,0,53,81,189,134,159,125,35,127,55,241,244,106,214,90,173,89,124,227,114,52,255,98,163, +163,140,197,189,166,25,138,65,192,30,193,172,201,70,127,215,88,93,182,120,190,196,110,82,97,236,214,71,118,101, +123,176,43,179,203,233,88,87,182,208,21,150,139,243,158,69,48,107,178,21,93,217,216,57,169,173,77,90,19,144, +230,236,171,228,43,62,215,173,17,132,159,146,173,162,143,159,147,141,186,114,162,51,75,233,213,199,233,223,123,155, +204,67,246,92,235,172,251,57,211,126,75,221,194,31,27,64,168,132,32,254,28,100,205,33,231,95,183,248,215,6, +217,222,177,253,229,106,170,141,44,193,139,209,139,194,17,202,8,59,61,50,134,185,210,50,102,109,134,153,144,116, +171,209,116,132,151,187,197,122,130,40,138,23,65,177,185,113,206,68,173,62,242,203,253,138,25,139,128,232,206,88, +180,149,211,25,75,121,37,6,38,227,95,15,91,176,74,69,50,130,110,190,238,178,221,239,98,94,14,187,220,159, +133,26,21,16,101,148,61,218,240,64,181,25,240,234,136,246,38,94,182,230,170,161,103,27,78,160,70,199,99,145, +110,238,197,178,109,26,110,33,80,205,62,166,168,160,27,77,251,22,96,239,51,228,89,235,29,79,188,56,69,101, +47,195,105,42,99,7,90,251,158,79,124,177,109,76,253,181,31,186,140,12,243,195,205,109,223,97,71,204,114,152, +93,164,12,203,208,140,13,6,100,47,164,180,203,125,92,214,31,25,158,127,116,129,107,104,200,75,223,221,79,50, +251,130,228,248,215,248,38,180,111,9,227,184,9,239,84,204,191,246,225,56,22,144,210,210,207,96,208,20,98,139, +25,8,37,113,127,166,6,233,233,77,60,50,11,253,76,50,101,63,247,176,74,153,50,102,186,163,237,6,246,98, +241,92,219,185,238,39,173,114,61,77,140,234,117,42,41,69,24,211,27,203,222,247,247,15,189,193,39,192,67,138, +219,177,50,152,193,184,12,223,124,254,197,179,65,134,18,51,148,119,212,208,96,134,230,112,13,214,104,46,117,163, +140,201,116,46,181,216,196,210,128,238,39,81,75,105,33,108,98,235,117,38,47,97,88,236,115,132,106,50,84,71, +41,75,55,178,104,21,30,56,228,140,170,154,104,52,80,165,99,242,72,86,214,121,13,145,224,173,174,205,214,128, +143,88,249,151,181,126,7,86,62,210,161,149,143,90,163,109,37,105,116,46,12,1,107,70,242,11,31,219,140,48, +102,170,106,252,69,109,43,72,40,133,165,82,85,227,175,240,138,130,119,63,243,180,36,93,201,82,55,54,96,146, +210,207,176,81,180,86,38,246,243,92,42,138,134,234,187,78,170,196,48,224,217,211,96,136,151,81,228,25,207,133, +1,130,135,104,62,54,246,16,54,38,241,25,178,143,254,33,145,26,39,74,54,60,36,5,244,235,141,225,142,27, +133,101,237,98,95,202,99,5,111,1,151,230,182,79,71,33,139,228,198,86,138,182,19,236,23,190,233,9,179,185, +93,95,36,120,247,177,120,139,203,125,207,134,246,118,127,141,122,189,146,169,234,12,147,206,131,207,6,73,95,74, +86,123,195,172,245,138,217,203,245,192,50,62,122,54,106,82,52,44,132,199,205,156,210,250,221,24,28,221,237,240, +212,54,3,74,160,28,10,2,223,67,25,148,177,60,49,15,31,130,191,253,36,245,67,121,131,86,89,175,76,217, +224,144,221,136,154,177,72,98,157,14,179,15,143,219,81,168,240,36,211,180,36,234,10,126,4,80,175,221,29,7, +70,15,127,237,232,15,64,2,194,93,232,251,115,151,234,123,178,52,208,71,216,153,50,73,118,127,24,237,233,19, +247,212,84,169,38,62,8,133,39,177,156,185,79,183,121,177,228,13,38,151,201,102,17,137,8,247,162,15,178,220, +220,139,224,60,9,53,56,12,231,172,236,150,40,145,79,5,222,86,188,103,139,5,240,39,66,9,180,87,16,66, +220,155,160,156,208,235,246,103,206,149,218,43,73,211,185,60,217,193,84,223,255,131,21,212,238,50,140,186,59,199, +252,124,2,40,12,118,81,181,173,51,186,62,96,44,93,7,197,132,253,45,179,115,47,70,48,14,230,177,66,69, +221,195,179,34,43,16,35,126,145,183,110,0,43,252,20,99,121,49,8,187,141,221,249,201,243,94,107,123,248,134, +169,64,133,44,174,49,144,143,140,246,119,9,54,58,201,183,172,132,147,16,218,57,197,131,188,62,106,227,125,162, +212,12,233,209,114,148,20,109,251,148,112,57,115,229,232,221,233,109,229,178,201,97,244,146,58,158,129,113,12,43, +18,61,130,29,197,32,195,130,35,153,124,5,205,160,232,232,84,138,27,87,15,221,138,219,90,239,162,22,242,195, +100,203,203,89,31,57,135,251,96,255,42,40,74,196,251,215,167,253,43,216,216,93,106,244,70,118,210,141,97,208, +209,121,30,159,173,11,124,241,249,178,170,222,55,72,155,137,195,186,172,218,124,117,251,29,89,42,132,52,53,66, +172,247,91,1,94,167,175,117,15,149,114,213,162,181,201,48,207,36,238,70,49,237,206,14,241,238,202,124,22,95, +215,16,159,142,149,26,230,193,98,178,128,55,229,75,213,223,93,215,32,139,239,1,134,24,153,124,70,248,5,231, +127,127,137,238,200,236,103,213,89,108,198,92,140,157,4,51,121,95,234,98,96,13,111,196,245,96,57,157,198,21, +153,127,80,149,37,168,244,95,35,115,236,94,1,213,89,101,159,158,85,169,90,123,220,196,118,46,62,114,84,62, +239,225,65,221,125,128,12,43,188,43,183,175,114,252,188,145,52,169,164,200,96,230,252,27,229,222,121,73,207,147, +66,66,128,252,44,98,84,16,19,96,3,61,228,244,137,89,224,22,56,242,134,47,29,120,90,31,9,69,133,230, +41,186,14,42,159,160,160,138,205,59,100,29,142,240,71,248,42,232,94,44,214,193,247,226,43,127,160,69,200,81, +91,35,158,50,77,235,196,27,33,98,13,8,196,52,94,141,103,171,75,225,11,145,51,206,86,234,164,79,122,198, +243,84,183,252,186,184,232,165,60,42,147,237,163,136,222,12,86,211,63,63,77,201,51,24,197,68,190,192,241,159, +18,244,110,82,12,48,228,12,109,42,211,203,126,60,243,170,196,51,96,127,183,246,229,94,141,200,94,33,195,27, +21,242,131,109,186,213,195,213,116,249,112,137,15,96,155,210,63,88,218,143,180,129,47,24,96,52,28,247,244,207, +241,163,84,205,78,21,246,171,151,181,120,148,113,252,172,23,191,244,241,113,79,219,177,81,190,201,74,213,152,56, +178,96,117,119,39,101,51,186,85,238,1,108,65,122,12,139,251,36,159,25,201,146,0,242,172,186,117,226,116,107, +78,58,230,118,39,38,152,194,177,87,150,146,165,105,26,43,29,84,117,246,10,3,19,176,7,215,42,29,35,206, +201,10,85,120,191,133,197,109,148,233,175,38,102,73,225,24,30,63,131,221,225,186,53,232,99,190,192,35,247,192, +121,203,21,248,156,170,12,244,226,222,102,118,247,181,85,94,55,104,227,172,72,225,103,173,236,118,73,86,138,193, +37,89,118,227,155,11,55,142,3,217,135,143,213,70,95,180,209,254,194,199,138,13,124,100,85,19,109,98,117,105, +67,8,82,27,59,110,207,122,135,234,172,128,17,188,205,95,62,92,241,125,230,234,225,210,137,196,251,97,123,120, +27,140,223,195,246,197,116,27,123,229,242,97,221,87,190,238,75,89,55,69,201,170,229,34,112,178,173,216,78,135, +199,171,142,253,128,214,228,46,209,37,52,103,88,167,229,18,120,44,246,98,138,208,49,36,180,2,41,105,24,235, +200,187,172,37,188,6,81,108,252,68,146,92,252,17,39,189,218,237,175,204,57,108,145,98,7,245,35,177,54,136, +97,75,44,221,254,120,251,207,240,188,93,118,86,6,171,242,194,95,117,167,188,157,169,116,112,253,47,244,62,192, +28,105,203,132,58,226,151,100,134,92,100,127,90,11,127,8,52,163,199,132,60,10,134,207,165,232,226,37,92,23, +86,226,186,48,27,205,190,183,91,0,187,211,209,182,132,222,109,225,37,180,88,100,139,104,169,155,135,60,211,184, +27,30,154,176,22,75,8,138,35,166,129,52,38,152,43,28,6,93,62,240,137,136,163,109,172,41,151,147,197,82, +183,28,23,249,79,120,231,197,172,252,61,66,52,123,100,28,45,103,235,105,29,63,196,28,156,95,192,197,108,77, +130,174,67,114,90,150,223,206,86,119,148,95,65,121,222,89,56,27,1,142,200,105,126,235,3,115,57,202,8,89, +234,33,53,14,131,133,163,50,84,1,89,124,223,2,241,15,163,25,35,245,161,147,93,55,220,98,187,30,232,66, +229,195,253,36,103,106,56,135,106,63,115,60,136,235,217,58,26,116,116,63,235,160,244,208,146,209,222,34,140,101, +31,214,49,52,153,180,63,51,163,5,226,184,243,151,241,241,179,86,158,149,253,141,197,219,155,172,248,181,97,47, +155,254,94,246,236,220,32,1,66,159,182,125,8,181,118,247,66,229,47,88,175,126,192,52,245,39,34,39,119,7, +56,16,188,202,119,221,39,120,214,70,111,8,35,194,79,237,80,248,233,155,8,77,96,89,188,68,156,20,105,147, +66,149,179,153,50,179,25,59,125,184,235,2,208,13,207,76,201,232,16,172,23,34,147,239,188,188,72,46,41,214, +33,137,115,236,54,143,128,132,0,158,55,204,45,31,54,163,219,190,115,42,209,179,82,185,42,226,96,185,110,36, +95,16,161,217,101,45,190,87,236,189,250,185,183,26,54,208,10,127,130,208,21,252,35,147,171,86,114,64,93,169, +75,117,161,110,212,123,90,11,107,91,211,160,81,77,148,22,64,228,119,150,241,34,168,45,247,156,61,71,224,124, +191,168,130,219,136,44,86,37,145,82,122,163,183,199,77,139,110,134,213,149,110,208,47,41,254,1,245,40,228,53, +162,140,240,187,140,236,142,94,66,190,32,254,161,46,244,13,116,238,8,165,16,65,160,245,11,248,137,47,116,145, +195,188,92,89,31,252,87,40,33,115,129,70,109,110,244,229,156,30,251,161,143,148,17,58,207,24,110,201,115,72, +210,143,211,233,58,126,175,151,40,246,8,245,190,183,245,226,79,52,90,243,123,168,121,170,47,227,57,91,159,189, +136,85,109,191,110,122,90,12,23,42,237,89,240,186,65,245,129,122,29,53,65,239,234,90,7,123,150,144,249,131, +174,125,176,136,213,173,126,166,159,70,76,1,87,103,207,96,114,60,29,92,219,160,87,203,99,226,240,54,66,237, +136,20,191,200,191,44,19,137,16,186,142,153,78,196,192,135,88,81,173,77,82,113,133,200,241,239,246,156,186,180, +242,133,112,40,78,33,31,16,193,253,6,102,160,4,200,64,25,177,116,55,60,174,195,222,145,251,194,53,242,228, +4,208,207,211,160,98,178,64,78,103,66,25,247,154,55,103,173,211,64,145,173,60,39,207,204,212,248,8,103,16, +81,132,62,157,181,177,23,77,26,178,24,167,237,67,73,126,185,174,189,74,163,17,14,226,226,103,121,33,85,6, +142,220,196,118,135,187,33,230,194,143,59,106,103,195,70,227,71,99,109,142,244,127,113,58,67,7,85,125,215,189, +126,141,70,231,201,197,98,1,27,19,199,157,12,251,131,131,124,100,226,203,29,31,25,114,93,224,177,239,201,201, +194,36,237,83,252,124,122,178,192,171,88,96,171,141,175,171,115,109,219,62,213,80,170,125,98,196,133,198,171,97, +122,227,199,165,127,138,199,135,72,31,208,197,122,255,197,93,181,244,166,40,6,46,11,247,194,58,93,139,53,234, +215,130,241,80,141,23,63,30,231,181,240,104,148,57,116,113,42,181,87,154,241,55,40,68,235,77,136,247,119,41, +140,175,14,220,211,106,56,224,6,154,22,184,229,83,93,45,170,30,119,100,90,163,85,42,140,30,242,10,234,96, +18,127,140,129,92,60,44,159,166,15,155,69,10,172,144,226,81,147,20,15,155,39,233,195,114,81,64,56,125,212, +116,242,146,60,62,21,124,77,245,141,28,185,135,140,197,209,81,155,220,253,124,237,45,60,251,3,235,11,249,74, +39,129,200,95,37,134,206,71,101,103,212,206,222,244,3,173,83,119,186,193,249,114,194,229,197,216,61,95,173,247, +25,102,83,184,214,159,38,128,60,87,246,78,174,150,120,40,110,117,117,143,224,7,158,98,65,223,104,177,149,94, +164,80,45,71,111,128,222,190,12,14,73,222,250,221,10,168,163,84,111,213,38,238,232,216,165,227,85,129,47,107, +117,171,158,169,55,234,185,250,81,189,80,175,137,39,225,40,53,56,233,46,35,193,159,199,35,66,4,103,43,245, +70,95,204,174,212,143,152,141,8,219,233,149,122,173,91,78,247,135,97,45,232,189,80,39,230,135,162,84,16,74, +65,172,107,6,170,188,213,23,80,215,27,237,114,78,87,253,218,144,8,247,117,121,146,248,131,188,190,173,212,51, +234,221,115,170,26,11,96,247,94,232,214,166,247,235,195,40,89,33,101,135,162,182,32,148,130,120,91,14,123,247, +1,123,7,213,251,172,162,123,198,146,179,68,169,212,66,234,143,234,182,211,52,245,35,5,17,196,227,63,197,190, +236,175,81,237,175,171,111,123,2,135,53,9,28,190,212,245,217,219,243,57,214,36,80,140,149,192,132,132,125,100, +252,50,142,187,48,197,220,108,152,221,219,233,170,235,245,251,246,112,191,105,152,173,151,87,252,95,238,52,204,240, +12,103,248,195,108,37,87,129,219,103,230,238,47,32,223,207,219,170,175,52,163,208,146,170,184,81,157,170,32,202, +191,126,244,75,108,5,91,128,176,155,223,60,89,207,209,246,103,235,71,112,247,22,188,137,99,24,208,91,161,165, +247,23,8,89,181,188,219,22,202,6,93,37,122,122,254,208,66,242,80,47,79,173,49,214,235,254,169,55,46,104, +29,45,94,187,224,167,253,186,26,25,47,235,155,191,215,149,61,116,212,13,18,129,239,165,169,156,107,77,59,255, +189,122,25,171,98,241,1,104,188,231,250,133,190,78,110,245,27,253,163,126,173,175,213,146,173,119,183,55,167,201, +7,213,222,158,38,183,170,189,121,156,60,131,239,199,201,27,5,209,207,21,196,254,168,32,242,133,130,184,215,108, +106,232,165,162,145,39,127,145,138,136,183,237,190,34,226,135,86,168,58,174,91,161,218,248,6,3,114,72,201,181, +140,17,117,52,109,39,143,224,161,108,222,122,68,54,15,200,242,254,93,229,126,188,239,81,189,199,241,13,139,168, +84,157,46,85,61,138,224,83,129,224,21,51,186,10,169,101,152,121,174,215,218,233,74,174,58,93,169,37,98,255, +210,242,249,1,251,47,167,107,192,240,171,197,108,157,108,1,151,207,14,156,255,151,112,86,220,133,196,213,47,122, +114,149,47,151,133,33,158,88,227,241,121,31,125,111,212,179,64,24,252,132,254,228,104,142,168,199,228,191,46,236, +241,70,98,240,219,128,149,127,99,21,140,182,253,86,243,69,127,30,41,186,140,231,207,0,224,113,219,81,164,250, +0,161,155,206,215,231,209,246,191,169,66,137,184,27,137,0,111,245,8,218,222,10,252,215,28,192,127,13,227,191, +6,241,223,173,254,88,236,55,221,118,247,205,234,16,107,239,119,250,131,222,199,217,179,223,220,233,15,31,221,105, +236,242,221,147,31,38,190,227,94,163,70,149,224,6,254,98,161,52,41,4,19,240,151,0,118,254,14,213,142,147, +179,132,211,47,244,137,186,9,130,138,23,79,110,224,186,125,17,239,222,235,20,100,244,213,181,126,111,55,211,252, +163,176,253,5,52,250,102,148,109,12,73,83,126,10,102,156,254,252,110,70,196,5,210,58,207,37,103,225,133,254, +2,110,201,139,107,175,120,194,125,122,169,95,0,13,39,143,21,248,28,40,81,219,115,101,160,107,61,175,23,209, +7,253,70,249,253,190,200,240,11,21,156,129,205,118,133,252,243,197,107,61,123,241,240,199,233,143,168,62,36,192, +5,227,77,59,32,238,1,96,94,98,222,100,44,209,87,49,172,127,164,234,3,53,39,35,41,216,59,181,130,133, +127,253,80,147,98,105,4,231,20,144,46,209,233,236,69,252,16,234,182,108,186,53,185,107,123,123,188,167,182,237, +96,26,142,212,162,133,12,3,93,220,24,15,87,227,26,107,16,36,26,138,160,243,12,195,84,253,167,64,25,189, +158,189,177,116,211,63,244,7,252,196,189,52,111,174,115,184,104,68,191,64,51,105,99,28,146,77,62,157,233,107, +228,109,95,214,38,125,63,199,36,134,90,78,178,9,29,151,126,198,165,121,130,146,127,204,116,51,40,77,59,150, +83,184,240,186,101,101,134,127,144,168,226,167,206,60,96,59,125,195,221,119,188,154,107,138,178,1,62,160,195,68, +16,248,116,221,37,159,249,94,71,228,74,89,39,188,215,10,185,101,201,115,127,178,55,1,234,240,104,111,2,188, +225,169,142,32,200,231,243,107,229,55,120,242,12,191,233,202,79,126,72,127,81,109,157,150,77,97,155,58,131,227, +234,92,185,14,1,57,16,220,223,92,118,135,80,30,47,108,56,150,91,62,150,7,236,102,132,139,3,231,166,183, +193,194,251,195,171,42,242,116,91,171,37,30,112,61,227,218,138,106,9,68,213,104,46,232,147,60,190,106,252,153, +164,154,238,16,46,108,247,6,99,156,149,5,65,51,148,142,82,104,60,241,80,117,131,119,178,122,28,21,34,65, +50,173,84,161,235,222,53,158,6,152,41,207,240,17,74,69,13,190,197,248,203,19,22,46,253,222,94,100,110,188, +165,220,215,81,230,191,213,122,170,11,216,155,9,198,81,5,54,10,247,175,172,118,150,14,170,197,216,59,235,157, +249,122,109,23,20,87,135,1,168,221,120,34,64,14,129,142,189,234,95,111,106,230,71,224,107,77,255,229,121,145, +243,1,181,135,132,93,216,62,153,186,73,214,93,159,116,69,117,187,136,206,249,145,71,0,11,41,193,132,221,80, +166,92,13,158,14,28,224,225,110,241,80,0,72,92,204,39,87,198,54,202,131,224,60,126,121,225,119,198,50,82, +78,158,62,59,95,241,254,139,143,172,216,17,146,182,254,190,80,189,253,118,85,59,163,166,196,252,240,62,235,35, +249,230,208,6,9,141,129,87,123,64,18,182,133,82,89,37,71,230,113,123,238,54,243,31,141,125,210,73,63,144, +220,238,177,247,147,175,13,135,208,14,95,196,14,149,48,7,186,217,170,106,199,226,124,229,46,133,158,46,58,244, +116,131,4,255,253,34,80,67,197,214,70,242,145,86,192,185,39,55,98,104,50,138,229,207,208,114,93,176,45,249, +20,206,99,115,23,121,211,196,113,184,200,38,39,52,183,200,33,187,179,219,158,65,214,222,168,70,143,8,191,51, +67,116,16,203,57,15,240,225,98,231,128,194,43,166,68,164,136,143,214,23,143,214,206,167,4,124,209,169,132,252, +90,183,74,101,24,129,230,140,16,103,79,41,187,118,92,8,99,237,18,225,157,50,90,15,110,234,174,166,112,225, +212,62,139,143,130,60,164,196,240,93,218,174,169,109,180,56,247,182,138,138,99,116,129,113,203,117,96,76,6,49, +25,196,248,190,96,118,9,46,184,250,97,161,131,187,141,102,232,190,201,203,7,53,104,13,208,28,15,236,11,129, +183,166,8,236,151,65,15,78,213,45,252,220,158,118,202,134,31,219,240,227,14,173,84,154,160,92,237,75,180,92, +164,245,101,90,46,212,98,169,157,165,0,10,201,153,192,233,229,24,154,114,121,239,47,6,188,137,125,46,64,200, +33,163,209,10,120,96,105,134,29,205,246,111,1,206,164,236,21,113,96,75,62,7,81,51,253,30,134,105,165,75, +201,48,109,250,12,83,218,131,85,31,111,214,119,215,121,34,55,13,30,180,67,174,131,85,253,183,239,153,227,2, +234,5,178,100,90,21,208,105,21,207,42,160,249,51,17,95,91,188,26,79,107,72,88,235,149,78,241,36,18,25, +16,107,114,185,149,143,246,88,147,11,22,58,131,130,202,184,221,98,196,110,233,77,133,50,114,207,248,52,187,236, +166,7,245,198,67,61,177,217,60,196,171,85,236,107,193,108,1,218,105,121,195,43,61,206,249,200,25,38,145,158, +95,140,17,100,179,127,40,206,1,166,111,115,196,203,222,122,138,124,73,151,88,73,70,141,212,215,199,71,206,177, +154,180,189,34,125,99,75,203,43,100,195,21,73,103,52,219,202,119,204,194,83,221,248,252,46,142,147,144,224,113, +147,63,93,68,26,193,70,8,227,146,203,32,237,70,17,193,135,49,119,126,5,173,4,234,124,254,9,158,91,107, +117,162,86,42,195,135,97,152,178,15,48,101,118,109,222,162,32,227,161,179,84,18,169,152,47,41,189,253,180,134, +247,161,53,103,51,92,65,191,20,208,52,11,167,214,186,192,128,119,153,145,234,210,146,208,108,47,163,10,23,100, +188,17,245,5,69,132,14,61,132,64,57,31,183,210,84,215,14,230,191,136,74,26,49,249,89,157,246,234,122,200, +73,65,102,43,142,19,44,139,115,106,59,234,116,233,51,167,75,191,14,186,244,171,160,75,191,236,244,154,121,173, +133,50,42,141,113,102,91,101,171,135,233,61,81,149,195,156,37,47,238,104,53,74,80,123,235,40,37,11,162,253, +75,147,187,93,246,239,78,128,86,206,59,187,106,184,151,70,8,8,6,244,33,161,164,56,214,31,241,62,194,99, +94,142,241,0,33,34,252,238,141,59,246,8,115,159,156,56,0,24,127,28,255,138,175,138,165,254,156,37,154,49, +1,127,143,127,85,176,16,76,174,140,13,196,183,14,172,5,99,130,35,125,138,91,160,255,25,163,240,51,105,128, +76,240,153,163,6,125,208,36,152,92,202,228,251,231,164,217,159,129,174,83,88,207,244,212,214,20,33,65,50,156, +55,204,52,218,23,63,109,212,163,238,208,59,165,164,182,60,197,126,159,155,246,50,176,174,167,19,188,99,190,124, +62,81,210,149,151,112,225,101,14,208,20,169,54,72,83,164,232,207,82,219,250,112,117,162,35,120,212,78,173,213, +7,173,113,59,237,185,216,106,186,113,62,215,61,154,22,119,157,171,109,208,252,5,132,97,44,194,232,80,189,209, +26,210,28,194,219,161,14,156,196,98,223,115,141,209,221,170,145,82,23,18,68,35,156,181,140,47,140,52,145,33, +237,27,219,185,225,19,183,201,42,8,56,203,24,128,24,1,148,140,83,20,203,241,64,113,252,213,172,54,168,223, +69,234,116,93,222,32,43,17,234,17,82,47,156,47,0,122,222,124,231,190,191,93,121,171,156,182,253,144,77,137, +34,184,68,239,208,252,113,45,151,130,43,134,233,22,245,57,153,183,114,190,93,71,6,177,38,3,149,175,193,196, +194,98,179,31,142,170,172,109,144,90,135,241,79,39,199,147,105,197,228,84,187,174,171,235,7,104,248,252,115,188, +150,70,19,59,159,203,202,52,15,202,10,184,119,64,140,60,0,50,238,193,100,26,174,43,21,24,22,126,128,199, +52,89,191,109,213,10,237,212,213,48,227,189,137,133,27,218,151,254,59,162,110,180,62,220,160,250,1,110,51,49, +165,190,219,168,120,213,109,203,177,217,17,67,43,237,208,26,49,180,121,137,61,131,134,189,5,236,242,28,173,129, +81,244,151,224,153,17,38,142,147,48,132,169,131,30,115,234,63,90,72,147,54,152,87,123,182,167,62,51,209,8, +172,168,51,179,248,18,23,15,22,4,37,213,236,55,122,202,244,134,127,207,99,54,124,218,170,50,196,179,5,96, +232,1,78,230,32,214,102,179,54,15,171,26,50,9,203,136,109,63,77,58,56,102,227,71,242,201,160,13,182,196, +251,182,163,80,56,178,141,0,52,98,162,212,55,213,38,34,255,236,249,185,243,170,84,198,199,191,84,121,105,243, +212,26,165,43,101,169,84,215,92,170,128,47,159,211,27,179,172,20,30,195,105,220,51,42,181,14,70,165,38,249, +114,2,171,4,30,11,38,110,166,40,204,59,252,221,122,204,70,117,48,103,96,221,66,124,97,162,103,173,154,176, +235,238,102,162,188,50,50,187,246,246,217,110,32,155,139,155,112,158,13,169,6,250,44,118,222,212,132,163,57,151, +125,91,241,153,140,81,19,138,241,233,23,184,101,151,175,9,120,235,28,125,190,13,187,42,235,233,247,238,28,109, +181,71,199,199,199,94,70,205,224,82,77,220,86,152,168,214,89,79,31,201,181,45,101,62,172,234,179,208,232,221, +181,170,97,31,169,248,231,220,173,251,203,250,33,216,130,172,100,121,127,57,63,233,84,236,13,78,201,253,165,252, +228,17,43,39,140,113,136,75,46,112,239,237,15,77,77,66,96,66,85,240,56,239,40,239,135,231,161,198,150,180, +3,61,92,208,143,207,129,145,45,70,3,61,92,202,3,7,91,39,115,107,126,247,114,110,203,59,23,148,235,224, +177,222,95,129,28,182,47,237,23,246,222,194,126,232,190,172,95,221,251,139,250,245,181,57,248,60,63,131,162,38, +248,95,104,60,2,171,208,167,151,159,67,222,122,183,238,208,110,226,57,36,87,112,66,135,24,8,107,173,101,47, +145,116,202,151,11,238,215,141,201,162,150,204,28,36,63,68,141,170,125,83,233,157,77,213,241,188,87,65,138,142, +30,240,63,142,232,59,30,125,71,62,0,254,26,149,103,206,18,239,180,57,87,104,66,49,86,40,254,24,193,47, +166,178,57,94,159,216,141,53,205,22,254,172,157,239,19,144,39,31,69,70,94,74,126,58,13,71,217,104,78,246, +177,80,30,75,234,39,246,86,38,249,67,206,32,117,107,56,70,195,7,32,75,55,176,176,203,30,221,241,251,201, +239,167,237,244,247,147,7,185,165,59,210,7,14,36,204,242,193,239,167,229,148,206,18,79,210,146,69,241,183,45, +33,225,119,107,118,76,183,25,61,34,46,208,106,19,16,219,157,85,255,14,26,79,72,49,243,188,191,132,44,248, +36,36,10,216,79,123,180,63,15,199,107,212,134,35,229,130,107,12,165,84,171,38,228,115,161,40,194,35,120,165, +27,134,170,165,172,39,118,190,208,1,196,198,146,241,136,149,173,84,202,206,172,124,17,98,37,113,131,156,28,122, +163,223,43,49,105,144,171,127,103,135,183,101,232,178,170,187,139,225,60,53,26,72,189,158,199,254,10,157,42,181, +113,240,17,195,16,128,52,192,89,73,166,140,13,50,222,221,141,130,0,233,175,232,131,71,213,49,249,167,196,205, +150,161,103,10,114,40,25,123,71,129,252,129,166,182,128,75,158,179,141,133,111,34,97,255,38,246,236,161,170,88, +90,27,101,34,85,137,111,7,107,157,156,89,230,106,201,10,121,66,69,20,175,92,175,230,195,16,225,151,68,78, +242,155,22,221,244,173,209,106,10,173,166,233,198,138,14,61,224,176,53,18,213,160,57,220,224,232,211,127,250,211, +100,215,33,117,118,181,142,2,64,52,60,179,71,102,113,118,158,92,172,45,254,130,22,186,209,46,73,154,90,206, +38,201,175,149,189,57,109,52,41,5,227,219,5,195,108,10,166,196,209,94,201,149,137,10,253,52,229,110,161,41, +66,173,139,16,130,61,208,3,188,134,12,62,29,2,75,82,121,225,84,124,188,149,244,247,213,192,252,177,50,61, +73,152,183,45,55,202,151,131,96,14,11,29,110,53,79,252,181,186,1,188,199,166,123,161,76,56,192,13,92,7, +226,56,248,102,112,213,225,108,12,234,42,101,93,225,0,130,10,230,173,215,209,168,98,109,29,226,114,99,85,220, +237,219,93,190,236,89,39,61,2,0,200,113,13,89,183,129,190,79,22,187,46,201,67,145,139,245,152,79,112,180, +115,43,47,232,97,248,214,84,115,43,77,53,251,131,140,188,148,23,58,197,91,84,166,161,51,6,44,132,145,35, +111,239,23,170,97,217,3,59,27,73,234,89,141,55,208,15,7,172,169,202,20,140,79,112,28,130,11,243,253,238, +250,185,125,131,55,182,191,216,75,9,15,128,57,3,148,130,176,210,4,119,240,124,203,122,109,57,9,53,0,100, +169,206,38,147,115,181,11,166,224,209,224,61,45,128,11,0,82,195,6,208,37,155,180,197,92,246,109,22,127,121, +236,238,11,112,219,17,78,152,162,168,245,41,24,29,115,114,108,87,25,56,57,16,221,202,128,145,1,84,148,243, +109,222,174,165,27,142,220,15,11,145,248,5,21,186,152,44,140,110,19,27,67,79,132,23,136,209,141,102,165,187, +5,72,97,37,240,27,43,19,170,253,192,213,138,234,218,133,175,48,241,21,133,18,215,80,194,187,37,225,39,223, +124,255,201,151,117,116,115,241,224,28,12,71,251,76,183,162,226,166,28,116,5,123,205,197,32,227,34,135,135,225, +148,38,6,186,16,92,61,199,144,131,216,104,207,90,228,7,181,213,171,234,26,173,6,52,38,18,75,246,126,221, +95,178,127,180,193,73,227,142,205,218,238,186,142,120,2,54,136,11,133,55,217,50,178,25,21,1,217,216,141,189, +30,139,29,243,195,85,134,187,179,116,11,87,158,165,214,27,213,175,81,225,9,36,76,172,10,115,108,136,186,249, +251,75,123,156,61,160,190,61,112,86,147,82,26,27,212,105,227,147,7,159,236,210,238,239,68,42,21,199,23,232, +243,234,118,88,223,117,90,151,80,221,187,178,66,245,184,7,53,111,135,7,232,67,19,40,165,180,121,192,59,116, +164,90,239,27,175,180,238,240,214,26,192,39,99,163,181,98,218,230,21,232,236,105,252,3,188,69,85,195,240,244, +135,3,204,14,171,81,154,117,170,80,43,200,15,127,214,231,192,55,137,85,78,59,199,111,159,209,137,179,124,75, +239,101,51,211,169,220,62,37,249,221,193,190,69,176,218,5,239,60,209,75,185,52,171,176,52,75,223,194,86,223, +66,80,101,168,138,145,158,109,61,239,21,170,170,206,182,240,119,59,175,81,185,16,254,64,96,108,124,48,108,76, +116,227,220,118,170,196,224,234,108,137,163,196,113,246,101,232,71,135,89,35,124,64,77,133,58,251,210,9,102,22, +22,120,21,71,208,148,213,194,47,66,53,234,23,1,40,31,255,141,174,208,230,254,212,35,134,189,164,13,220,54, +208,188,115,66,221,109,45,13,121,235,156,54,74,238,87,74,135,79,162,4,114,182,74,168,249,131,99,67,125,95, +175,15,214,167,177,45,250,138,21,13,73,229,206,45,102,227,220,98,42,83,211,231,27,211,206,131,3,67,35,177, +229,171,134,238,16,121,32,125,201,78,37,106,105,65,82,131,73,72,55,152,26,125,88,146,22,149,225,102,222,153, +96,214,222,211,89,95,181,214,191,93,41,68,239,115,42,90,198,206,199,245,179,254,85,194,223,87,237,206,213,48, +230,96,130,12,15,43,162,156,252,144,122,76,239,186,151,134,151,255,7,240,242,214,162,43,28,169,159,23,106,63, +118,201,148,23,225,228,64,62,76,234,26,206,51,236,35,243,189,169,14,92,129,3,117,96,82,215,112,158,97,29, +126,17,219,152,234,97,208,59,80,21,167,118,141,200,57,172,144,227,185,95,12,176,7,234,227,84,103,159,50,236, +7,153,203,210,151,89,97,82,59,207,17,66,26,246,87,70,237,134,43,101,11,68,241,112,165,100,90,199,219,64, +146,41,220,79,132,207,86,225,3,211,217,217,223,61,138,251,100,215,118,127,87,64,146,156,251,178,207,156,123,57, +81,137,50,178,154,191,99,169,99,124,54,180,199,33,212,98,160,150,97,221,131,92,13,103,251,251,94,212,185,186, +187,71,204,253,185,187,63,179,59,58,193,12,33,223,3,153,138,97,153,238,27,223,35,248,164,71,206,32,167,132, +32,59,239,245,165,236,102,182,108,191,75,54,206,181,129,12,37,220,191,52,15,105,33,105,72,68,86,216,1,123, +145,89,82,228,240,218,53,128,13,22,45,45,153,111,225,104,193,35,168,139,30,94,2,230,42,153,139,143,111,25, +77,55,164,94,123,246,84,29,217,220,88,183,1,213,192,254,208,160,119,165,229,166,99,7,12,219,215,225,89,73, +189,37,76,135,51,131,63,224,53,190,102,226,123,58,206,5,226,167,181,79,90,233,167,239,12,30,167,106,21,199, +99,9,141,90,141,198,195,201,91,209,201,123,32,253,203,3,241,109,137,9,93,160,57,158,213,117,122,123,188,170, +171,171,40,16,87,25,95,75,172,151,220,204,222,53,70,206,97,194,237,235,180,161,151,175,154,102,221,224,177,158, +117,72,56,246,167,125,48,227,236,168,97,160,182,124,214,226,200,140,29,153,32,255,57,102,199,69,96,120,109,121, +222,49,122,248,38,189,50,75,255,118,106,185,35,26,175,32,225,18,184,251,164,129,14,153,37,94,56,212,206,161, +149,164,70,223,208,223,213,102,149,223,152,6,13,232,188,109,162,17,204,163,16,148,88,186,162,198,101,255,108,29, +213,10,253,26,84,199,92,49,154,38,47,245,11,124,140,89,148,81,156,148,126,130,177,190,193,29,9,33,9,165, +30,160,54,3,21,149,48,101,93,96,226,100,200,196,49,49,209,122,5,252,241,143,123,221,176,22,28,42,13,212, +219,9,10,67,171,14,15,70,240,170,72,0,4,58,81,65,85,246,200,69,198,106,37,238,247,80,73,207,109,70, +238,182,95,137,18,132,126,203,229,254,225,44,188,118,26,251,216,196,114,137,188,113,195,205,177,130,10,42,29,122, +124,93,210,254,239,45,137,145,124,141,180,127,9,1,154,52,43,182,75,0,173,201,26,223,9,39,0,215,188,245, +27,85,161,216,90,199,122,12,228,113,231,87,114,131,24,30,113,191,189,46,209,251,168,169,219,91,4,160,6,146, +143,97,33,183,153,113,14,188,225,5,31,22,20,253,250,196,234,232,84,56,32,251,76,94,122,118,121,243,38,92, +116,141,2,74,214,223,116,75,88,4,36,175,4,139,174,233,177,232,42,77,62,166,107,93,226,79,170,163,26,72, +96,244,238,118,102,121,115,232,185,9,250,144,194,101,236,205,26,126,224,23,92,70,125,129,95,158,35,231,121,116, +228,204,113,173,39,127,56,254,207,227,83,231,206,242,249,90,159,209,109,82,185,139,164,98,209,108,39,51,61,241, +158,72,38,231,97,140,159,55,131,91,227,216,165,20,66,207,215,158,181,146,123,214,10,230,144,151,236,215,253,202, +92,60,50,157,124,164,129,75,60,148,43,225,103,129,60,246,89,9,127,18,140,157,97,156,0,201,79,155,62,17, +238,165,179,131,209,29,119,188,207,135,198,69,45,99,246,181,41,151,8,48,234,175,214,191,111,85,126,86,161,5, +152,214,40,104,170,71,149,127,187,254,248,198,124,109,0,88,239,0,174,155,189,218,114,73,227,163,199,96,152,44, +242,24,140,19,60,177,166,85,224,70,174,157,39,251,227,240,6,246,233,237,203,37,20,78,114,128,142,96,144,38, +39,95,84,177,194,88,118,161,109,99,57,16,40,251,109,142,6,202,203,26,247,130,31,15,246,103,120,165,102,207, +182,219,220,51,194,141,126,106,132,95,239,216,62,39,11,95,166,175,134,143,240,146,7,56,2,255,101,128,255,105, +99,125,229,105,193,182,38,232,231,7,127,252,86,145,121,138,110,218,159,182,49,14,238,172,154,162,207,234,184,147, +104,74,240,179,120,67,248,11,44,66,227,85,181,109,76,181,109,39,150,133,87,46,218,196,63,96,231,253,43,133, +28,134,48,68,77,168,238,217,154,220,152,225,188,17,219,162,196,87,2,28,192,136,192,134,245,231,140,239,38,105, +1,155,107,121,139,178,14,208,139,227,7,36,120,12,30,172,219,245,131,151,207,31,252,126,50,173,128,214,154,78, +126,255,224,106,11,173,94,154,7,75,251,112,0,124,3,251,12,2,244,183,121,96,23,0,127,48,75,109,160,170, +37,190,189,120,137,219,225,41,3,17,251,167,240,136,113,93,34,213,253,229,6,199,25,149,62,248,207,127,130,151, +107,36,164,122,153,142,45,237,255,25,205,76,84,198,94,228,188,159,107,224,87,190,81,112,19,108,54,0,24,228, +171,27,5,18,80,246,154,65,75,101,26,24,155,133,83,135,88,83,136,149,110,188,85,186,165,190,73,163,56,120, +203,73,237,39,3,103,33,221,219,244,237,243,101,124,165,112,23,157,154,195,162,63,182,243,34,130,179,144,172,158, +112,248,114,101,248,70,238,99,154,22,4,226,154,129,99,158,27,19,114,100,219,186,54,229,158,59,243,94,9,143, +134,123,177,23,236,209,219,183,133,150,130,200,65,123,63,91,193,238,218,145,255,16,110,79,27,24,42,148,126,229, +19,123,101,26,146,141,251,122,56,28,102,80,248,122,164,244,197,134,103,213,59,126,247,185,214,32,105,105,74,56, +251,242,76,68,167,109,219,119,221,114,17,60,123,63,207,27,242,194,41,59,53,116,237,194,133,150,213,107,114,25, +175,47,83,36,101,41,146,175,159,43,52,229,84,83,234,115,67,54,111,79,164,5,124,126,86,33,203,32,249,25, +3,209,185,165,242,143,82,208,135,40,226,93,159,39,56,121,145,230,5,108,189,182,122,96,55,212,3,43,70,143, +59,239,247,237,3,134,233,7,220,207,7,72,58,211,254,124,7,19,93,62,192,87,14,255,32,218,125,215,30,219, +133,177,210,174,147,140,143,153,137,250,180,137,213,48,117,195,199,198,68,125,187,142,85,120,12,204,211,130,12,221, +244,167,20,142,14,49,13,150,17,32,128,119,143,224,222,137,68,178,25,148,151,45,252,123,38,98,77,199,234,9, +165,211,234,105,212,133,44,86,245,141,12,193,251,95,188,128,51,175,90,84,73,179,40,31,53,9,97,215,131,204, +141,59,121,27,61,214,198,93,108,141,187,248,25,123,236,12,49,129,131,122,6,132,129,120,112,142,251,110,197,195, +62,90,96,60,134,169,182,164,182,4,118,63,247,114,176,199,185,178,75,160,146,104,219,122,28,60,70,152,136,230, +45,163,36,244,57,111,34,129,234,60,6,228,204,248,108,23,242,2,96,81,4,38,113,6,238,52,157,110,144,92, +111,203,18,104,13,155,131,95,193,109,150,79,173,173,116,16,188,117,134,124,91,7,12,166,75,56,103,168,172,147, +1,127,110,202,25,81,141,150,29,199,139,192,8,236,49,52,75,236,91,15,78,19,148,253,181,238,225,201,238,83, +67,231,125,101,5,224,135,179,206,162,32,178,236,16,249,146,64,92,56,47,22,19,59,16,120,156,177,59,108,50, +15,137,186,30,49,249,90,243,199,199,156,36,14,84,82,144,82,64,34,102,12,2,184,125,181,195,159,164,238,72, +198,4,168,73,66,125,236,181,64,213,22,73,143,227,2,143,39,163,34,230,168,154,72,93,212,150,50,101,179,173, +89,214,231,75,144,30,125,249,188,137,198,5,141,37,51,255,135,200,40,212,10,65,70,45,189,35,55,29,91,174, +255,150,221,41,80,141,119,137,178,115,117,142,153,196,161,166,71,35,150,225,242,85,171,20,26,139,232,121,3,253, +204,199,200,55,231,59,228,217,57,233,23,86,186,114,194,143,125,143,130,0,89,155,72,202,4,25,251,148,218,148, +84,45,146,24,72,13,162,86,169,253,186,113,42,202,30,77,166,106,233,181,53,178,133,184,28,37,235,133,215,72, +231,43,212,18,201,75,204,85,167,75,192,49,168,24,151,214,148,17,186,102,222,85,245,45,102,181,177,93,135,28, +158,31,162,74,201,254,213,126,162,252,123,47,244,181,192,190,174,225,237,192,190,196,192,1,71,45,197,115,136,112, +157,115,34,59,64,24,127,222,136,120,149,197,192,63,135,40,40,228,226,98,4,57,81,84,38,41,120,244,128,137, +62,97,133,23,68,224,244,206,133,212,106,9,18,14,144,234,168,232,117,188,210,24,158,11,227,207,75,13,207,245, +94,112,110,13,164,62,209,9,203,104,151,47,147,194,242,120,214,164,128,230,149,144,104,74,41,216,197,170,60,91, +209,129,188,234,224,23,221,184,165,120,97,194,137,106,20,131,194,14,46,197,124,27,192,39,61,74,44,105,22,235, +86,120,137,177,219,67,249,25,141,85,77,220,199,79,171,27,78,35,121,47,123,94,50,209,51,4,219,64,220,177, +182,111,255,181,140,239,93,210,170,41,82,166,199,72,72,69,200,117,128,222,54,246,62,60,171,236,47,140,240,169, +9,18,96,141,54,40,169,0,26,4,77,108,27,100,90,95,168,38,224,149,66,24,69,45,103,38,238,70,9,54, +200,68,121,78,98,219,3,184,102,79,72,157,2,46,245,212,56,178,68,46,172,140,223,247,101,109,86,166,54,101, +22,42,240,36,130,31,117,210,42,107,202,211,141,56,49,78,61,201,141,216,155,122,244,50,217,146,6,86,173,103, +57,58,172,225,121,56,168,99,139,55,124,24,179,173,61,238,177,25,239,152,142,33,202,145,226,150,82,248,100,108, +209,188,107,10,186,132,82,242,157,83,162,172,239,10,51,244,93,225,111,172,40,187,78,117,214,222,170,137,236,173, +184,12,85,252,142,42,41,19,140,33,3,113,248,5,184,204,254,194,142,69,207,6,7,167,160,140,213,161,214,32, +197,238,207,84,213,225,153,86,87,131,39,219,180,71,165,96,33,130,20,93,217,95,160,154,93,113,180,208,196,14, +58,38,19,103,160,5,162,62,88,29,26,103,188,137,123,225,84,141,176,135,66,170,53,150,1,166,81,137,47,54, +204,136,138,38,239,221,17,34,17,75,193,136,37,172,53,154,91,221,245,159,75,222,18,254,85,24,41,99,214,157, +144,29,193,87,222,190,219,178,66,168,125,236,134,101,109,171,28,3,8,77,141,181,248,31,255,209,207,150,197,93, +127,88,132,4,217,39,23,140,152,133,141,122,51,212,245,237,112,13,241,146,106,113,235,250,134,49,10,60,75,239, +131,183,138,248,25,117,12,60,76,44,167,154,170,139,226,142,189,171,112,208,22,28,182,53,74,163,82,158,73,240, +76,55,32,31,248,229,207,248,59,137,84,64,21,119,111,51,228,84,152,143,228,84,168,70,31,188,68,30,149,130, +19,231,5,9,183,61,18,197,9,33,174,77,246,158,136,241,79,243,18,149,40,57,201,231,255,82,222,101,125,154, +23,100,11,34,144,227,211,36,156,236,33,57,119,85,45,209,171,117,16,171,196,87,8,43,110,57,84,243,164,218, +14,163,186,249,225,214,220,202,113,171,49,99,168,19,47,112,150,1,90,91,31,62,211,192,58,52,252,243,120,110, +23,192,38,89,117,163,168,39,139,21,204,58,122,199,244,28,224,149,229,0,207,87,253,49,120,168,90,246,92,155, +76,87,76,210,127,11,48,176,42,170,235,40,198,51,186,118,39,113,94,178,197,33,116,157,142,14,23,173,165,85, +142,92,160,61,85,185,104,236,147,177,38,119,153,72,103,101,72,103,121,160,239,175,48,15,4,47,142,135,111,99, +131,21,236,226,30,67,40,156,185,191,78,20,136,118,45,111,188,104,241,142,185,54,73,170,2,191,38,41,248,36, +45,156,44,59,198,126,73,70,246,97,137,240,98,144,164,254,108,237,129,35,62,111,144,214,115,19,165,246,10,161, +122,196,125,39,225,60,32,10,175,201,98,73,37,123,234,121,114,168,245,115,50,118,51,224,164,81,74,191,219,223, +65,135,233,127,126,16,237,83,234,3,158,21,122,85,247,249,218,99,154,152,6,168,221,163,239,237,27,1,176,105, +142,14,242,180,224,236,108,197,29,221,223,174,182,229,254,157,91,198,196,221,232,110,247,52,209,90,70,39,45,191, +12,155,96,45,232,251,50,199,187,229,243,192,101,138,98,47,150,106,43,1,186,106,93,45,147,82,145,224,108,210, +168,172,218,2,36,84,29,62,241,5,134,119,73,178,133,125,53,16,112,82,94,37,213,252,21,138,11,19,237,218, +141,182,57,156,120,201,245,98,211,247,48,123,142,120,115,248,102,140,63,198,72,232,110,186,23,104,56,191,78,76, +215,225,125,171,134,71,8,24,67,101,47,96,76,183,167,83,216,23,211,218,81,178,167,172,166,6,145,49,161,242, +50,58,9,242,176,149,62,5,213,90,51,175,0,5,229,43,90,249,70,149,81,21,199,174,207,124,90,138,7,235, +198,54,135,116,37,43,188,97,221,62,50,114,211,95,157,157,158,243,10,76,171,179,199,231,188,10,240,253,135,115, +180,135,217,71,32,94,14,125,28,211,114,54,192,10,119,33,244,186,229,67,48,112,136,246,57,8,106,104,211,220, +95,54,85,233,28,146,60,209,112,193,51,92,0,2,243,33,79,26,182,186,231,55,171,6,111,233,40,29,44,110, +137,226,10,139,154,161,225,198,132,249,124,32,26,32,55,164,87,80,35,200,199,192,134,97,202,161,159,211,211,251, +124,255,217,65,10,224,66,93,117,119,96,86,158,195,184,27,160,227,251,38,223,103,188,231,112,61,162,181,144,122, +55,170,188,227,236,51,116,35,51,241,189,36,148,156,175,249,255,168,246,254,128,35,163,94,32,196,45,218,200,81, +182,68,37,39,166,139,19,128,142,131,19,120,96,30,186,184,27,76,233,62,95,110,56,192,22,247,34,110,149,52, +41,173,148,54,204,39,85,103,6,211,58,191,119,93,92,119,26,187,2,168,14,45,103,143,187,134,111,104,82,205, +4,114,30,6,149,189,154,227,206,29,123,187,195,253,225,23,222,225,38,245,221,250,174,69,185,21,201,254,12,172, +180,163,1,127,20,78,20,136,33,252,97,195,73,20,12,37,196,10,158,163,119,146,165,17,179,101,9,246,71,130, +56,120,156,191,234,78,154,33,203,189,211,227,249,231,50,154,110,215,106,60,35,49,113,58,215,174,20,169,19,56, +69,224,33,31,30,159,75,172,242,30,116,23,176,152,71,12,124,223,70,140,245,36,176,12,200,3,196,175,136,199, +166,211,54,198,144,157,198,62,250,139,197,93,29,147,61,122,136,69,101,247,212,112,24,160,32,55,98,30,111,28, +130,107,31,55,36,49,224,185,168,210,153,135,112,174,46,27,141,110,26,125,167,154,39,21,50,119,194,217,110,240, +49,155,78,97,127,133,6,128,42,249,66,24,119,94,113,239,46,99,21,195,39,144,241,190,3,93,216,13,230,235, +227,176,233,71,175,48,211,24,119,119,85,32,198,96,44,102,110,158,194,162,207,102,38,222,91,86,242,7,116,215, +130,201,94,246,71,40,151,203,243,23,145,59,119,145,21,249,70,53,120,43,92,242,13,81,85,195,67,182,102,180, +215,58,180,103,47,50,191,25,235,217,233,171,61,114,177,166,148,140,178,182,70,173,207,112,171,63,116,146,84,20, +154,217,72,54,204,199,238,196,41,139,52,169,81,217,248,41,167,147,221,61,114,51,238,43,195,192,140,162,156,217, +63,231,128,92,84,198,120,165,226,148,169,203,130,7,180,196,206,140,207,172,85,35,19,171,250,183,161,104,63,13, +82,115,187,121,102,47,63,160,169,250,53,28,41,94,121,209,115,211,158,103,199,120,214,144,86,43,131,56,75,196, +84,90,79,156,124,199,100,81,49,33,69,165,209,201,208,222,41,182,59,76,191,162,191,224,114,192,224,245,226,164, +130,59,137,119,89,102,78,226,106,24,150,116,177,29,99,203,26,218,10,33,34,138,85,152,21,250,162,184,136,141, +12,51,106,195,246,30,97,191,111,172,166,131,13,220,202,0,113,225,18,148,31,242,12,57,127,24,187,46,37,70, +93,108,210,186,49,75,108,148,49,83,2,146,95,138,209,137,23,120,245,44,147,221,71,185,91,249,165,141,168,19, +226,28,179,156,123,75,59,78,58,187,166,114,183,99,27,219,82,182,112,63,98,96,100,208,13,153,135,247,46,30, +221,99,130,54,234,252,110,114,102,0,71,229,177,93,1,146,90,171,224,48,4,104,58,114,145,201,145,225,47,124, +78,150,221,202,139,188,189,253,24,234,105,238,27,56,50,93,91,189,123,103,135,45,43,137,119,251,50,18,56,46, +125,52,30,143,147,189,95,7,15,127,188,136,35,251,66,145,161,194,119,185,152,160,169,103,120,145,130,178,102,162, +170,67,212,96,173,43,129,22,188,248,168,151,153,111,34,47,42,58,191,108,201,132,88,69,171,5,123,216,79,69, +169,122,210,9,142,102,58,48,201,200,20,118,121,43,181,227,115,50,41,187,184,87,77,138,42,173,146,78,38,181, +186,38,177,253,1,32,197,145,225,184,121,194,199,231,4,14,181,14,103,226,254,140,120,158,238,115,227,247,105,132, +43,19,64,21,183,176,152,190,126,200,51,247,163,88,201,215,19,89,67,119,129,111,247,142,124,84,38,208,66,54, +94,125,231,88,57,238,125,95,159,220,245,90,133,36,19,209,74,148,99,124,60,29,199,222,73,83,59,117,118,199, +228,178,15,250,206,188,36,51,183,108,35,220,83,193,21,238,171,130,180,7,152,52,57,9,112,15,133,205,106,3, +101,27,47,70,230,210,109,251,68,236,6,129,48,12,141,95,60,151,220,125,63,243,66,30,232,142,179,205,149,130, +173,141,102,229,254,252,199,151,87,233,59,54,242,18,112,159,239,14,156,197,56,181,223,191,126,101,179,116,114,124, +59,207,132,250,190,49,53,199,222,35,109,130,185,95,251,56,87,38,25,72,87,157,116,195,90,37,87,72,242,218, +16,84,228,236,170,210,41,145,239,12,62,151,82,121,102,174,89,184,99,67,189,104,82,170,238,88,231,220,62,204, +58,159,77,63,233,90,185,239,159,117,170,70,248,155,85,140,178,4,114,164,204,230,83,200,82,138,42,186,216,141, +143,117,119,144,245,199,7,217,104,26,233,225,249,57,24,207,53,62,27,133,202,238,154,13,76,86,45,62,154,103, +48,27,62,59,198,160,90,48,239,205,241,130,30,246,48,119,220,169,202,23,199,28,94,128,87,136,25,97,58,187, +155,18,134,125,177,68,19,57,65,21,148,22,24,194,131,146,117,196,170,12,226,37,21,133,150,198,22,173,161,242, +218,86,184,39,176,215,244,10,13,119,118,184,138,158,168,19,172,83,116,167,83,104,119,237,25,215,37,5,151,226, +69,10,208,139,60,107,185,245,61,203,90,2,170,124,216,242,11,116,120,122,141,231,104,15,197,33,127,24,133,146, +127,189,145,195,2,150,221,144,113,63,118,24,27,52,155,111,91,154,56,139,131,232,217,159,157,167,106,237,204,101, +161,186,249,129,211,26,248,174,189,3,49,86,242,236,62,155,92,76,166,205,116,194,69,66,119,38,231,248,154,102, +221,134,122,81,134,244,73,1,231,68,26,239,208,134,88,122,238,53,88,224,12,27,107,187,238,55,44,154,157,195, +251,104,118,6,237,246,26,172,157,138,154,234,151,116,239,206,49,57,179,124,6,244,126,96,130,15,239,189,41,165, +34,147,189,107,134,121,135,199,178,204,141,212,63,177,133,7,108,182,138,105,236,186,139,133,80,206,216,104,171,152, +136,208,116,95,206,251,155,234,1,215,249,96,133,38,49,31,164,237,3,170,21,76,243,85,142,24,29,54,203,83, +145,216,211,26,132,148,124,71,0,107,29,53,104,136,215,191,100,120,25,224,114,40,2,236,207,190,209,167,34,172, +34,238,250,135,26,131,161,159,84,249,174,41,140,208,40,155,175,219,175,117,8,200,61,140,190,198,124,1,169,21, +238,110,181,6,237,157,140,172,146,160,244,238,122,72,194,173,122,17,255,241,31,172,82,130,41,12,26,68,148,70, +70,181,8,180,40,188,95,89,85,225,186,255,94,182,183,235,106,213,208,245,18,105,62,149,250,204,54,242,80,161, +212,23,66,250,79,158,98,253,235,192,142,146,146,86,213,6,77,246,14,217,165,42,15,22,198,147,191,5,123,103, +46,78,13,159,57,99,60,78,106,20,125,27,28,145,204,156,235,71,10,253,167,246,184,76,17,64,142,173,144,216, +157,44,31,130,155,9,222,162,199,95,160,165,119,125,202,107,23,252,88,12,198,95,176,202,143,101,16,136,70,85, +4,178,153,80,144,158,157,150,67,41,69,170,162,219,111,62,222,245,223,85,233,229,131,231,67,40,109,26,111,75, +124,31,147,224,43,90,137,132,76,161,159,209,53,39,211,160,52,210,14,183,20,228,41,224,74,231,183,222,96,175, +253,21,174,58,85,73,192,162,206,90,101,23,209,137,100,130,194,130,77,255,172,0,131,170,123,233,142,28,89,107, +220,224,105,48,162,18,129,245,252,253,221,158,30,218,216,88,20,183,246,30,54,200,212,186,27,29,57,243,92,16, +48,246,53,98,24,2,206,236,45,187,228,224,3,35,64,98,184,197,123,118,149,238,98,246,84,118,35,33,117,215, +117,236,154,169,33,106,227,135,232,93,126,156,67,4,66,80,163,64,41,41,31,151,176,0,6,123,11,244,140,19, +225,89,154,85,94,26,214,220,203,77,3,213,168,157,179,75,153,236,76,185,189,50,53,237,192,79,91,69,170,76, +201,151,157,242,13,141,231,216,230,157,242,6,80,199,179,252,163,237,84,205,150,221,198,115,188,133,28,236,156,124, +60,195,119,235,78,193,100,209,62,26,207,81,214,174,17,83,143,231,192,75,69,142,103,213,91,146,109,180,65,245, +178,33,3,202,219,242,227,11,243,13,82,150,239,132,138,99,83,247,52,186,118,77,139,155,191,124,71,166,203,55, +40,67,253,53,249,192,79,26,133,199,217,109,82,171,106,139,154,117,233,50,223,162,16,109,94,150,62,132,130,15, +214,111,142,110,30,165,243,188,103,10,63,63,78,235,204,94,41,84,57,203,148,153,102,177,42,158,54,139,8,115, +23,34,189,192,52,204,131,136,57,78,66,66,3,9,95,65,194,87,88,91,86,84,141,241,117,35,27,88,218,171, +249,92,26,197,184,69,203,56,103,19,234,248,27,178,108,165,108,224,243,114,9,159,56,4,142,231,0,198,159,139, +218,94,143,88,81,194,22,120,183,176,7,0,158,134,24,15,70,51,67,39,114,94,10,38,199,155,79,249,16,236, +180,227,193,86,120,26,36,195,156,34,75,17,199,15,203,71,143,61,226,109,163,66,157,40,145,33,139,227,206,145, +26,97,60,73,26,53,199,33,24,43,55,58,159,128,129,88,133,145,38,101,11,41,33,12,173,212,156,142,197,124, +42,134,48,77,154,2,203,205,80,7,15,28,98,152,105,254,208,59,88,110,99,128,148,210,197,52,208,243,86,86, +80,149,174,130,160,208,236,160,75,0,96,218,3,192,162,7,106,25,128,154,90,7,25,163,246,88,128,229,180,156, +154,89,161,200,99,112,6,254,176,51,140,152,22,137,149,125,94,106,231,35,102,171,155,89,138,216,208,47,235,75, +155,127,86,38,39,224,85,110,13,223,107,250,190,109,117,244,114,250,23,92,210,15,173,190,109,201,127,219,246,225, +109,251,40,186,109,167,101,156,108,231,75,29,109,103,31,112,213,25,1,110,66,239,142,79,78,78,213,246,225,122, +102,30,253,20,63,90,171,43,204,187,193,218,46,117,58,189,154,46,213,5,116,229,106,182,84,114,81,111,194,50, +190,151,75,119,29,214,233,67,167,95,227,225,182,82,107,117,49,187,68,167,195,235,217,141,122,6,127,223,171,55, +250,114,122,243,232,86,61,215,23,179,247,143,158,169,31,245,106,122,173,94,192,223,15,234,53,164,93,63,250,81, +253,2,105,31,30,189,192,73,56,180,93,215,232,44,51,86,239,159,158,132,89,2,16,120,166,158,19,195,96,110, +115,190,60,190,81,47,143,111,213,123,136,190,152,126,21,243,36,152,22,243,190,80,23,156,23,155,97,39,24,166, +133,34,240,231,54,86,31,100,221,148,255,151,177,186,63,96,205,234,151,41,205,235,119,47,227,14,171,243,253,92, +41,28,202,74,225,192,86,36,59,117,61,168,246,71,245,122,172,218,107,245,218,85,169,46,103,190,235,111,177,200, +173,186,220,239,249,91,40,248,22,251,125,35,27,160,220,111,198,26,184,193,106,213,155,184,235,97,45,161,232,60, +68,47,187,21,208,32,159,229,117,134,102,181,26,185,39,42,149,65,244,246,138,229,153,147,218,161,220,20,46,62, +64,92,81,46,236,44,108,44,177,205,170,233,247,225,185,174,208,176,17,158,52,112,7,44,226,156,156,105,68,208, +227,230,155,244,155,168,70,227,142,169,174,166,245,239,190,87,240,207,74,108,71,233,84,127,31,252,201,138,122,211, +88,185,26,84,26,6,244,146,7,20,54,119,169,110,135,3,145,155,187,86,114,196,41,110,238,226,192,230,158,213, +184,177,51,24,174,192,7,211,218,186,58,164,49,2,193,207,199,26,14,91,141,193,117,169,26,149,97,178,170,8, +82,214,48,37,107,114,85,178,142,115,239,151,133,106,59,84,188,80,21,86,48,94,86,174,238,139,245,62,182,147, +36,173,218,73,55,59,181,178,161,175,170,188,36,50,16,103,163,82,133,119,151,252,204,121,209,163,225,79,230,71, +232,226,169,88,68,185,112,87,83,63,124,172,108,24,171,209,41,216,194,35,47,14,147,56,233,231,27,230,186,132, +59,71,49,193,247,194,176,30,192,167,226,245,44,98,85,248,185,109,98,213,195,227,42,140,61,102,141,235,231,249, +3,224,227,2,84,54,15,110,218,161,61,167,102,11,148,221,128,41,218,215,147,149,128,222,75,9,96,36,163,61, +248,203,72,9,36,50,222,67,212,48,94,64,165,230,40,49,21,24,133,198,37,132,148,185,23,178,204,203,215,120, +215,25,187,56,179,51,129,77,19,157,129,38,146,2,107,124,100,98,120,151,242,86,88,230,48,36,222,206,239,171, +168,65,15,83,45,236,24,211,65,166,222,137,232,198,56,60,14,123,84,217,122,128,36,86,221,94,47,66,165,208, +29,172,148,63,69,165,76,36,133,80,175,82,59,130,229,64,159,108,147,102,112,193,133,115,109,3,74,77,43,85, +204,210,248,169,254,30,172,71,224,115,15,217,188,187,210,223,182,81,173,50,56,243,214,211,165,191,98,130,215,159, +43,122,208,36,151,53,223,85,185,112,253,136,212,5,210,19,18,125,52,97,38,42,57,19,67,250,180,59,48,255, +234,223,50,3,45,44,79,197,254,186,20,15,62,201,6,126,39,215,58,106,166,85,76,190,167,162,122,154,78,179, +105,1,161,185,32,157,60,225,180,142,31,174,136,116,242,132,19,198,116,93,91,85,69,155,111,190,99,105,191,161, +225,242,225,196,121,159,55,3,116,99,6,128,81,122,215,96,145,97,182,62,188,71,99,87,43,140,225,17,217,40, +207,110,144,59,196,140,35,165,197,241,31,254,144,140,236,158,242,233,247,11,26,217,170,168,0,11,148,143,190,143, +33,27,20,244,130,73,98,246,159,184,56,92,3,31,231,248,24,236,122,208,203,191,243,153,87,235,6,186,234,52, +75,162,1,186,152,246,240,4,14,170,61,118,30,131,76,228,87,161,136,31,214,202,175,0,134,226,17,148,244,84, +255,4,71,35,52,24,119,61,247,135,199,3,135,138,170,149,238,194,252,148,97,90,224,244,127,234,88,20,170,86, +85,60,127,33,131,42,237,185,79,236,186,231,57,42,104,78,224,36,154,204,159,7,191,22,122,39,22,35,241,126, +53,69,115,201,228,255,172,86,171,201,222,17,195,8,80,222,98,96,93,228,177,244,88,49,156,159,120,56,63,81, +128,190,124,225,142,123,18,124,102,232,61,207,146,147,65,196,68,152,22,169,248,34,138,246,65,236,161,244,89,138, +72,196,77,23,132,168,175,170,29,68,224,193,35,157,21,134,34,24,84,173,8,196,152,119,224,184,80,100,247,145, +170,221,139,138,229,73,25,202,96,80,116,75,198,196,74,158,180,161,8,133,85,43,67,152,85,130,72,200,204,0, +36,67,130,178,248,213,147,89,129,168,70,154,250,248,86,100,250,82,222,132,177,29,179,217,152,229,162,45,224,98, +221,154,178,129,60,100,1,118,123,153,103,47,17,98,54,149,245,159,5,172,29,102,32,149,85,91,149,102,178,48, +69,242,235,58,212,92,187,37,219,117,129,89,234,108,214,240,185,149,224,145,9,27,46,169,116,57,59,237,180,113, +241,53,197,14,41,189,6,111,161,153,188,52,147,130,108,243,4,222,38,170,39,64,233,52,79,83,248,122,154,58, +236,73,18,222,94,238,190,80,69,133,158,82,143,241,71,229,208,147,36,123,82,128,164,231,122,81,78,179,89,145, +192,63,113,15,253,113,143,238,222,32,254,68,146,187,71,160,113,43,53,183,146,218,86,10,110,160,211,48,17,13, +214,130,93,133,233,174,8,41,237,144,239,146,172,128,165,229,93,192,45,59,93,146,125,179,173,218,168,43,162,46, +183,128,185,182,79,116,6,212,227,54,222,232,230,12,104,237,104,9,55,208,109,178,141,227,223,213,231,234,104,115, +220,188,207,209,43,222,10,137,60,246,39,184,129,117,222,224,21,100,133,188,221,4,7,114,5,149,46,85,229,86, +24,143,218,141,63,95,11,40,47,170,79,78,108,229,99,229,98,117,116,84,132,73,250,102,132,247,209,30,219,137, +114,51,83,41,94,84,59,37,169,156,18,59,15,133,156,135,140,231,193,210,236,0,32,43,248,183,180,179,162,46, +213,5,99,197,27,125,141,76,242,105,148,45,210,217,117,114,13,93,174,212,123,251,134,137,30,212,47,97,76,30, +240,215,234,50,86,34,116,213,11,93,196,177,181,159,143,243,176,133,121,184,137,78,226,115,229,167,115,11,211,185, +133,233,140,21,114,7,150,79,52,146,243,75,228,164,114,238,37,228,222,210,66,196,248,254,149,151,91,199,55,189, +214,88,248,131,134,226,234,86,95,255,243,100,126,171,181,222,128,123,249,39,87,139,43,253,33,249,240,20,59,122, +169,63,32,120,68,171,135,235,233,117,252,104,58,93,197,73,244,62,18,189,188,86,144,99,163,111,105,54,174,52, +21,184,208,31,58,200,36,246,92,57,106,152,86,25,45,113,22,112,219,69,136,119,164,115,30,151,163,108,72,134, +50,62,40,4,141,65,4,103,248,106,29,62,192,226,227,248,224,72,226,3,44,226,160,134,172,222,127,179,78,126, +20,248,225,151,113,204,243,233,255,0,243,188,44,146,191,182,161,230,31,4,76,22,198,66,36,72,204,193,24,81, +84,192,135,72,213,8,47,110,143,159,3,14,197,24,6,74,184,196,200,155,120,172,236,233,195,179,41,239,50,141, +152,251,159,247,208,69,99,222,93,153,17,132,81,107,244,163,38,205,97,165,104,14,171,137,177,29,60,208,143,27, +68,244,195,187,169,197,167,169,67,145,134,80,164,153,34,226,132,46,15,89,158,254,166,105,91,248,126,173,89,16, +206,142,88,74,85,134,99,246,175,98,12,223,175,113,5,61,241,206,131,89,136,201,77,196,144,249,78,247,67,251, +145,119,58,214,210,244,66,10,227,151,188,11,122,38,147,81,22,28,251,49,72,67,190,26,198,210,18,15,98,8, +43,245,227,120,88,131,88,191,3,0,125,134,194,94,175,82,68,203,39,75,174,227,240,29,112,43,117,57,137,24, +31,24,59,29,186,223,143,202,176,21,202,143,217,10,49,172,89,41,118,221,72,223,133,244,1,146,240,229,23,233, +166,89,132,169,77,250,115,58,255,172,136,100,37,170,84,45,189,59,141,79,203,73,71,246,120,108,44,174,121,111, +230,219,32,237,38,231,222,199,250,117,59,48,231,221,187,80,183,71,29,50,43,101,112,213,14,179,112,60,226,128, +193,218,255,80,140,88,240,225,212,24,182,120,94,55,237,80,148,202,119,222,216,176,237,128,127,18,147,170,14,40, +157,65,123,246,188,43,210,143,173,73,88,151,240,252,126,91,27,108,248,115,188,165,156,119,185,135,133,187,13,239, +160,100,189,170,122,181,215,250,7,150,165,222,109,216,254,101,98,188,126,34,98,150,166,179,178,14,117,95,105,208, +223,73,206,208,162,10,32,241,210,94,178,50,101,217,107,86,193,184,150,250,196,211,105,214,123,64,74,86,216,0, +210,59,53,26,165,223,234,234,108,117,174,54,240,179,164,215,199,45,158,144,241,46,181,210,204,219,120,238,206,84, +198,101,87,150,2,76,47,155,40,106,102,91,84,27,120,20,109,224,199,126,199,234,82,23,145,37,23,252,94,0, +25,85,72,179,51,193,21,95,122,150,101,26,44,80,156,46,82,88,46,160,81,16,12,223,216,149,25,136,75,176, +19,204,216,221,193,48,141,178,143,242,118,252,234,86,218,21,228,75,105,216,115,115,163,141,117,236,10,251,92,172, +146,83,155,48,251,71,69,253,31,186,114,29,24,63,18,220,129,126,84,243,117,127,40,115,47,129,132,232,173,72, +52,77,142,220,61,16,87,242,62,34,61,251,255,213,223,68,177,106,121,15,29,160,121,44,116,23,30,21,216,58, +238,186,31,90,186,194,34,233,51,153,67,64,222,97,229,237,14,238,140,219,182,157,72,223,233,0,150,123,222,210, +79,134,87,90,244,8,28,110,191,124,137,253,131,202,210,205,167,230,215,156,25,37,13,74,111,140,226,92,40,207, +61,154,40,188,218,163,47,19,135,74,241,155,97,14,63,25,125,39,39,29,142,227,55,222,128,7,183,115,17,152, +96,117,210,175,164,222,93,216,111,39,118,114,17,60,173,224,171,58,146,102,97,94,128,56,163,24,236,188,188,102, +191,104,246,169,249,64,69,238,96,243,32,5,147,7,134,25,196,8,59,194,126,91,182,179,42,126,210,28,215,150, +251,222,128,56,120,107,249,6,76,38,124,251,47,177,126,173,242,131,140,34,210,187,31,209,86,28,241,63,228,201, +250,65,243,211,227,33,86,225,222,248,55,213,53,142,95,61,142,167,62,108,102,53,132,159,248,176,152,145,169,155, +38,200,0,29,250,201,247,40,24,136,109,184,195,10,155,52,152,235,231,59,115,221,98,174,251,216,165,135,199,227, +85,39,124,214,142,228,59,241,52,215,192,1,220,195,27,172,228,197,227,0,68,54,55,225,206,110,20,74,88,89, +129,145,215,46,67,48,9,130,137,18,185,64,34,183,30,1,30,139,31,62,102,228,117,144,74,242,203,15,20,18, +247,224,201,241,41,40,187,255,173,245,178,154,152,197,106,75,2,83,15,223,176,250,60,183,114,192,78,9,124,153, +82,118,77,73,62,94,185,199,199,203,114,212,96,176,141,221,216,159,91,171,48,67,139,37,79,253,225,4,122,162, +129,55,76,43,54,76,247,173,101,231,17,222,156,204,33,20,144,161,196,94,167,202,151,193,111,156,238,79,251,201, +97,5,146,63,42,170,142,113,33,242,45,145,185,93,219,196,63,4,223,234,39,29,54,248,239,196,90,30,213,164, +116,157,25,242,240,47,211,6,185,247,172,248,230,116,97,107,129,115,36,147,30,115,195,15,229,134,95,155,219,66, +48,61,122,162,172,181,90,171,85,240,82,181,246,110,171,23,209,74,215,192,202,78,3,59,9,29,90,73,118,19, +133,51,93,206,86,10,64,1,175,228,80,166,162,50,6,226,10,109,166,43,201,142,194,35,80,74,29,96,56,102, +245,191,148,212,246,50,86,245,43,156,158,222,186,19,207,162,109,64,190,174,195,160,221,231,233,143,144,243,139,161, +161,229,128,164,123,0,11,200,155,195,111,96,131,108,72,249,241,121,21,149,126,131,183,9,52,218,144,226,96,69, +127,79,144,178,175,109,44,117,85,85,252,123,130,207,25,151,54,197,118,94,85,252,97,75,21,54,13,71,171,42, +251,131,101,196,0,255,50,120,247,53,37,30,79,159,74,182,114,57,88,233,253,44,32,121,131,227,26,149,173,65, +171,146,108,73,221,47,10,182,167,210,189,137,40,144,212,250,53,106,194,84,84,155,87,102,69,19,114,84,160,211, +118,136,192,31,30,15,39,91,65,24,8,188,198,73,25,102,246,83,198,25,56,187,157,165,126,237,54,78,52,16, +50,201,66,131,102,108,164,108,73,100,27,10,226,252,212,179,89,142,251,45,198,43,134,45,58,107,109,195,165,110, +185,14,136,65,8,104,52,65,151,1,48,47,81,30,169,210,127,145,225,158,116,81,2,251,150,235,185,77,108,241, +235,196,168,117,82,58,92,82,117,248,252,38,115,78,1,68,92,110,248,110,177,196,12,226,224,95,141,37,225,183, +133,127,151,174,6,191,48,126,91,157,132,229,152,249,72,172,9,106,137,253,226,12,11,112,236,176,68,29,139,245, +25,148,9,241,178,212,37,182,227,75,141,52,21,18,134,229,176,181,78,218,43,255,62,31,225,158,106,235,221,15, +213,201,249,19,225,23,46,243,168,81,140,110,1,112,41,5,213,145,162,174,49,56,200,66,60,193,224,196,240,65, +6,31,43,74,50,138,128,84,57,24,138,5,58,249,170,207,137,227,185,5,230,155,159,53,252,246,179,33,66,148, +24,42,250,7,123,68,200,225,26,144,65,119,240,28,60,190,133,127,215,240,111,45,90,252,107,62,250,54,112,3, +52,41,60,81,44,102,244,144,4,17,183,20,113,107,35,42,29,65,150,105,126,124,109,179,77,193,54,202,2,18, +226,89,169,106,76,187,133,180,181,45,1,105,107,155,214,4,98,6,75,163,164,9,230,108,0,238,160,166,105,5, +48,7,165,166,53,195,27,4,236,71,199,164,234,171,127,137,84,13,231,77,47,26,15,46,31,17,204,203,202,24, +123,152,13,132,21,86,120,213,127,118,133,92,245,123,136,219,225,27,111,175,108,98,148,55,7,45,207,234,82,13, +207,246,134,237,62,170,157,221,198,149,125,63,199,19,25,208,11,166,32,114,5,0,170,121,218,226,197,58,79,254, +177,246,15,176,10,82,112,189,170,227,107,52,125,176,166,207,53,73,57,247,152,155,41,0,47,64,69,173,160,107, +184,137,173,200,166,143,175,212,204,160,78,121,159,6,227,80,52,65,89,248,106,185,156,96,134,177,106,43,101,6, +101,27,87,86,94,90,135,55,2,222,19,184,77,165,58,196,128,74,31,230,193,29,59,36,211,135,217,108,166,246, +126,58,221,147,68,1,146,156,164,251,29,20,81,200,60,160,231,171,5,144,213,13,80,193,88,57,132,202,36,42, +41,220,121,66,85,74,15,176,107,203,0,162,80,80,64,39,22,123,69,228,41,52,12,194,70,175,134,196,169,63, +111,19,43,80,209,191,112,239,61,41,247,129,116,130,102,222,38,158,82,21,111,201,175,254,189,52,41,122,76,249, +219,218,25,34,94,213,198,192,85,97,119,113,65,230,33,47,46,172,102,254,179,58,99,249,246,228,121,174,240,61, +217,5,127,104,21,45,157,11,127,155,171,79,211,218,133,94,229,82,160,250,147,129,180,23,59,160,134,205,130,22, +225,209,70,39,251,193,240,110,143,115,182,238,218,170,118,106,188,155,7,228,190,165,40,29,252,24,88,95,21,252, +157,179,232,158,87,215,130,220,179,83,126,66,67,9,208,165,120,86,172,207,138,233,244,92,231,224,13,82,173,160, +204,234,9,84,49,95,161,217,63,44,112,169,129,125,0,255,110,184,178,247,90,72,101,68,171,233,105,252,48,141, +167,167,83,56,187,3,157,213,207,242,216,102,81,38,134,92,31,244,245,236,61,53,124,163,223,207,111,158,92,207, +111,160,169,203,41,116,224,230,28,206,136,11,254,186,157,95,62,210,32,197,9,127,184,229,91,217,242,202,181,250, +236,80,171,167,178,85,128,246,55,0,227,207,59,26,39,63,167,110,244,236,84,221,232,91,232,197,51,234,197,70, +31,255,233,97,96,39,190,1,41,217,135,145,237,205,236,121,60,131,8,219,73,136,189,128,136,88,109,158,110,233, +145,112,163,150,212,107,117,165,111,226,57,207,232,82,173,245,149,227,42,250,89,206,206,165,243,200,118,53,124,157, +194,163,13,104,55,229,239,43,126,177,236,44,92,226,106,95,216,21,85,55,80,97,139,179,118,13,31,23,240,49, +187,177,107,170,91,244,36,60,69,37,239,26,152,167,58,183,54,175,163,20,179,196,143,174,31,150,228,194,243,150, +43,253,160,139,127,146,88,204,7,50,233,156,61,129,55,201,141,206,0,30,234,56,201,158,94,193,32,175,32,184, +132,32,41,251,62,108,166,80,19,62,77,86,210,62,235,173,174,103,167,196,39,254,38,90,1,90,135,159,101,236, +32,91,44,20,140,41,86,111,194,221,8,195,243,103,112,26,192,108,226,207,237,127,252,199,165,101,203,190,206,163, +207,243,8,157,84,158,61,59,143,113,25,155,46,134,178,54,239,155,67,121,223,132,188,93,253,20,196,82,111,177, +128,207,153,159,221,66,6,14,164,228,92,149,102,125,163,175,104,208,75,189,213,181,183,197,122,25,150,171,168,217, +33,175,124,24,149,111,172,244,4,228,253,212,136,76,42,196,97,22,53,166,186,114,11,176,48,193,212,137,218,161, +118,6,27,239,10,50,28,13,182,126,200,91,43,90,116,44,144,16,145,238,147,205,42,92,172,165,241,29,194,5, +100,213,120,206,36,1,25,114,36,134,30,60,195,147,152,45,172,76,146,42,248,126,78,157,92,38,5,70,185,64, +214,233,10,15,29,212,47,255,20,81,107,19,245,36,8,74,13,231,215,43,36,67,43,114,37,172,106,224,32,87, +120,59,156,157,198,177,202,22,205,48,67,26,3,167,3,54,44,234,40,206,202,164,209,102,86,58,102,118,201,246, +249,154,142,92,90,149,43,141,182,196,39,60,189,248,110,169,130,194,79,90,188,131,35,175,93,95,33,103,183,156, +65,167,39,138,238,142,108,25,101,204,58,106,18,124,143,34,252,154,99,46,16,239,112,214,189,243,14,79,158,178, +19,154,225,98,12,13,223,177,138,26,166,195,140,122,243,203,40,246,128,230,221,243,125,149,82,149,105,148,93,161, +42,113,43,93,154,232,44,85,225,138,235,235,56,143,201,89,51,24,240,43,216,94,52,243,200,251,111,51,107,157, +7,71,182,108,107,134,30,86,214,189,82,105,13,108,224,16,213,230,87,6,106,14,205,34,155,21,245,17,185,238, +194,248,231,27,94,153,101,167,205,138,148,60,189,198,132,57,110,215,181,105,214,85,177,252,231,63,255,248,144,78, +51,144,151,216,198,8,167,141,159,83,20,37,199,91,139,221,25,58,115,91,165,57,190,99,167,52,126,167,120,7, +205,204,235,14,90,78,24,130,249,77,220,110,24,190,1,250,205,217,169,70,102,187,178,47,149,220,159,171,174,139, +237,137,186,153,55,215,121,11,171,107,142,61,136,193,214,66,42,171,104,219,203,73,178,209,112,164,103,132,178,17, +136,231,151,181,73,223,207,49,131,7,68,200,211,174,92,30,151,131,65,55,25,234,17,255,253,251,18,46,21,27, +178,158,243,32,192,250,3,223,250,131,223,127,178,19,157,233,126,255,247,184,147,3,211,27,64,6,202,25,242,200, +25,152,59,193,121,107,6,8,34,63,108,59,199,160,92,193,136,157,158,133,57,150,198,142,132,238,206,234,176,0, +10,146,219,140,132,62,183,46,210,0,154,160,105,101,124,123,193,157,1,161,19,48,247,58,100,179,198,170,36,133, +85,160,148,248,114,188,56,58,77,40,238,100,49,129,57,1,218,127,146,148,66,36,102,53,228,149,81,31,17,48, +127,141,74,239,16,220,42,56,148,214,237,89,76,117,178,105,40,122,109,120,81,84,105,27,133,59,247,39,17,218, +3,20,212,7,233,209,54,139,40,42,173,13,212,201,12,246,146,251,158,78,172,103,210,118,218,224,97,170,145,139, +78,2,172,13,16,122,216,22,218,178,226,206,59,217,99,43,118,108,67,217,123,252,93,167,27,144,42,246,86,150, +203,248,41,106,95,136,161,166,97,242,173,211,245,164,133,11,182,125,161,50,128,229,121,242,137,156,109,252,18,67, +119,184,197,69,96,6,37,24,141,29,176,145,200,187,68,39,145,24,64,104,33,247,41,160,223,249,3,206,86,100, +120,214,18,74,67,243,40,148,78,75,216,143,194,103,193,79,208,57,38,78,6,0,214,151,254,158,18,217,211,123, +81,178,81,44,252,132,15,180,253,67,49,124,27,207,234,81,199,207,55,186,245,60,119,248,188,165,79,190,145,250, +135,137,145,39,93,127,213,106,240,54,228,88,0,117,223,189,17,61,206,50,234,179,210,138,223,119,170,37,77,15, +50,138,162,240,228,88,194,95,202,66,10,35,71,248,112,128,7,164,124,162,223,191,218,113,123,13,223,177,43,232, +42,9,205,142,10,127,87,241,195,166,47,252,77,49,172,36,32,185,74,197,62,28,4,32,64,142,159,20,13,192, +245,121,133,182,253,131,101,46,85,33,193,137,174,228,88,36,111,209,34,205,150,180,72,201,169,84,198,99,76,66, +169,4,92,68,191,226,214,146,80,181,214,181,7,39,12,165,4,74,240,193,112,147,172,3,148,88,128,138,21,120, +9,173,243,165,149,168,46,210,218,187,217,183,57,233,186,229,164,221,61,24,18,219,20,49,105,86,71,48,117,25, +64,196,45,252,189,117,211,76,69,159,179,46,197,139,186,186,178,215,110,95,126,29,119,172,5,37,52,160,42,75, +49,222,209,108,161,214,113,240,102,26,86,33,91,5,174,90,196,7,50,57,201,63,88,213,130,22,46,161,109,28, +234,249,174,116,8,12,251,54,111,159,230,243,118,54,11,232,204,56,203,104,14,133,221,0,178,241,129,219,56,166, +3,199,123,25,8,21,175,87,131,23,24,205,91,175,164,143,142,125,197,11,121,78,132,11,255,88,229,37,27,2, +5,52,16,222,69,106,40,213,208,127,188,218,52,177,151,187,111,240,102,146,105,114,201,80,30,49,22,143,120,166, +161,35,5,174,28,238,124,21,226,50,23,23,39,134,139,0,126,241,233,184,161,160,220,109,40,195,113,25,198,197, +24,29,6,190,26,217,31,136,85,177,4,18,70,244,10,161,172,113,1,93,250,113,226,142,112,51,145,234,229,202, +218,158,96,97,146,53,193,28,79,160,195,162,157,42,227,161,98,93,229,201,241,224,77,36,211,21,24,2,242,25, +215,58,179,184,4,20,199,224,19,38,115,190,134,204,219,21,10,64,227,157,61,245,146,43,8,238,63,180,209,190, +100,242,174,147,119,130,101,255,200,199,145,177,15,95,64,38,217,26,168,60,62,219,173,137,62,38,42,125,135,224, +138,138,238,106,188,113,83,233,7,165,116,62,106,189,181,142,54,182,36,78,197,86,224,208,112,245,182,108,214,249, +170,133,92,76,55,248,17,152,208,203,237,240,168,134,126,14,250,96,70,251,96,160,15,106,135,98,90,8,124,69, +10,63,169,229,27,161,210,249,59,232,156,125,60,183,2,77,244,150,82,3,27,61,166,123,93,29,151,190,123,133, +245,61,242,0,162,115,187,174,5,32,243,148,183,80,151,123,123,216,242,53,14,234,223,123,141,27,74,102,245,140, +42,192,139,175,23,193,179,146,89,185,4,178,156,129,204,190,97,162,136,76,1,127,164,7,135,121,22,192,40,11, +19,177,6,48,202,144,163,3,64,194,2,103,88,253,18,195,8,69,24,194,142,192,115,64,163,240,2,142,108,2, +36,71,86,170,160,223,229,156,7,202,253,180,51,154,218,25,45,120,70,75,113,210,108,196,78,34,81,77,127,210, +132,141,132,45,126,18,25,79,105,53,188,113,194,41,129,244,141,75,197,189,233,211,152,230,241,78,142,253,180,101, +148,203,85,248,192,217,117,168,86,128,251,23,77,178,70,241,114,185,68,107,126,108,46,140,135,126,152,81,46,255, +5,212,181,64,106,19,237,231,1,254,53,154,49,163,114,208,182,24,108,51,193,62,15,242,61,202,138,79,150,202, +203,78,6,226,197,119,228,202,195,138,133,105,228,229,88,194,216,193,50,68,176,153,47,186,148,250,89,163,245,159, +55,108,92,54,56,219,104,216,217,70,76,151,88,164,75,67,17,136,177,110,116,213,81,237,205,102,98,108,176,65, +236,243,50,218,36,2,181,166,46,117,174,132,208,22,105,184,251,67,9,231,82,57,12,228,52,142,43,18,233,199, +105,150,82,204,123,66,207,101,95,50,176,80,166,36,70,67,138,122,29,128,20,213,10,126,224,8,65,202,162,137, +207,231,149,208,114,88,195,153,176,70,45,135,10,154,241,186,171,156,224,53,108,68,198,88,213,116,167,144,132,96, +174,82,86,66,168,59,72,95,244,228,168,19,95,197,138,170,236,124,184,61,102,169,80,140,191,223,224,68,234,117, +97,105,157,28,38,224,43,70,123,150,35,22,48,240,51,151,158,182,137,182,155,208,213,97,213,226,210,84,248,91, +197,177,16,218,204,123,66,155,149,212,95,105,246,229,9,64,218,102,1,93,65,180,208,194,31,24,224,2,3,73, +187,192,96,114,18,74,95,14,177,154,68,82,141,71,82,170,10,228,1,114,165,71,214,183,48,251,171,59,207,180, +95,84,207,181,128,73,50,138,22,189,65,60,150,146,158,142,69,157,129,218,128,177,240,1,223,84,219,58,67,173, +87,123,141,76,214,110,38,176,6,108,8,107,233,246,164,71,87,36,249,170,214,18,20,151,216,213,149,27,234,214, +246,4,196,81,25,139,210,39,34,80,181,180,125,82,27,253,75,69,6,120,182,178,154,43,172,102,19,15,58,120, +229,58,184,228,14,146,28,29,172,206,90,109,253,101,207,49,70,227,14,187,222,207,2,17,46,67,94,198,120,176, +123,22,115,88,175,139,254,122,237,200,74,52,63,82,211,174,180,38,167,130,225,41,1,67,149,26,104,93,17,161, +58,175,232,249,7,225,111,168,229,78,207,187,53,106,248,207,106,213,204,202,0,242,177,80,13,106,198,84,131,228, +201,136,73,115,120,212,246,187,10,37,167,154,158,126,218,47,141,36,82,233,80,49,110,62,75,229,71,208,168,140, +94,118,42,101,9,58,188,194,169,84,3,16,211,83,139,88,36,183,42,133,171,37,227,193,175,105,240,43,244,201, +146,6,164,132,207,78,123,175,75,75,93,117,26,40,44,93,168,173,46,17,37,3,39,221,61,120,230,226,149,113, +169,104,89,106,5,140,104,128,169,6,153,251,241,64,175,131,193,127,3,152,201,12,48,83,97,153,80,87,36,20, +29,239,54,3,228,68,19,92,170,85,216,67,151,80,73,57,168,36,99,244,182,241,10,86,104,213,125,14,234,95, +192,28,87,32,76,194,213,192,246,25,152,160,224,161,68,87,11,255,192,154,76,202,170,252,213,212,213,4,83,195, +187,105,88,176,155,213,93,11,150,94,98,87,26,5,247,205,234,26,86,43,69,171,104,53,47,26,171,251,25,43, +20,189,96,244,151,180,196,43,14,19,92,56,176,172,96,230,209,207,253,151,118,0,53,178,41,98,69,16,51,108, +152,193,163,113,45,5,200,41,186,222,72,148,111,198,215,202,2,19,119,215,92,221,91,179,16,223,202,135,168,21, +232,168,54,182,85,251,62,86,10,135,237,32,185,145,30,66,105,89,232,53,103,40,132,184,179,19,188,214,25,79, +241,74,103,0,166,184,157,209,154,217,64,176,28,230,238,22,251,98,98,133,203,230,59,48,88,174,181,171,203,46, +151,25,118,48,86,31,160,150,56,38,62,253,45,243,233,177,139,40,97,61,226,167,100,56,250,104,192,82,151,178, +231,116,13,219,51,139,137,26,221,53,249,83,169,227,106,159,129,89,163,80,130,191,106,168,130,133,104,96,15,186, +89,132,79,73,53,254,128,150,136,11,237,45,78,143,48,61,235,216,217,102,180,20,110,189,138,82,232,81,25,179, +35,211,220,206,134,180,187,185,199,192,247,75,123,252,1,223,94,152,72,78,233,210,250,137,157,48,93,168,198,221, +60,246,134,202,87,103,123,131,41,8,10,172,245,62,232,61,7,129,192,108,136,37,133,96,152,190,131,217,134,117, +81,193,131,199,112,238,13,217,251,127,155,95,17,189,29,242,77,240,140,191,199,144,59,94,92,194,121,226,47,40, +181,110,130,191,135,218,250,123,168,93,139,41,13,193,141,118,142,190,215,163,148,208,255,241,152,118,84,165,82,218, +253,177,2,232,197,125,67,174,29,82,85,193,168,252,176,24,108,228,240,0,97,134,145,225,235,198,126,214,193,163, +73,121,239,112,197,85,180,12,3,108,236,0,155,254,141,216,15,176,18,221,174,148,152,174,189,254,143,173,14,112, +214,160,237,48,91,164,6,134,1,230,129,143,143,209,87,7,44,104,223,120,217,111,92,188,157,121,72,193,199,19, +87,95,50,82,91,231,238,173,63,52,26,123,138,252,29,36,238,192,233,212,151,236,26,92,183,10,66,86,188,3, +186,143,215,63,47,199,176,133,115,197,203,114,160,141,100,41,121,219,194,10,75,73,218,22,137,220,80,149,18,109, +40,116,174,207,129,96,4,72,25,156,209,15,43,215,179,220,115,137,90,255,149,239,89,141,239,69,96,134,220,167, +208,215,220,178,157,127,110,62,86,8,236,34,93,46,133,94,75,97,222,65,169,47,243,22,252,237,145,123,53,206, +69,194,216,102,249,18,70,34,172,183,46,171,237,187,117,185,109,81,197,196,87,65,139,230,200,183,190,164,89,235, +190,130,225,243,22,255,202,182,177,137,129,229,156,170,216,94,149,232,60,189,31,239,197,222,251,209,87,41,207,252, +48,150,242,202,72,60,127,101,152,79,206,126,35,102,213,175,168,150,85,11,217,162,123,196,227,46,174,200,196,136, +236,106,112,119,214,47,187,95,29,94,208,113,252,61,51,205,94,232,171,63,190,118,56,11,102,208,129,82,121,151, +6,249,149,229,6,244,253,38,58,238,58,183,157,163,27,202,65,110,106,115,240,30,178,136,196,208,123,125,18,51, +41,187,98,163,228,172,134,10,226,36,146,115,219,31,146,95,188,126,117,24,211,91,71,81,67,220,201,161,141,171, +54,88,151,193,82,69,228,175,196,71,47,77,13,179,109,139,170,179,0,227,118,111,88,223,137,206,171,32,161,9, +239,58,186,212,79,91,255,45,118,7,237,98,244,38,104,125,112,115,33,252,116,238,167,109,2,6,70,139,201,94, +51,149,140,239,147,252,25,197,251,27,202,116,184,144,125,11,58,3,39,8,124,113,93,230,13,218,5,142,119,114, +57,197,90,156,12,5,13,90,158,56,228,188,67,151,129,189,94,182,120,214,90,29,22,229,20,7,179,234,106,179, +109,205,219,188,45,140,93,199,72,162,204,84,162,201,162,211,63,160,37,232,42,232,106,26,170,23,43,109,107,224, +255,170,81,8,204,6,128,183,230,182,97,236,175,171,235,38,170,149,181,219,52,61,61,1,8,91,15,225,42,11, +217,129,60,237,103,151,27,219,227,253,76,181,190,49,171,104,228,131,177,68,14,161,4,108,134,208,32,23,241,225, +184,243,61,29,88,88,195,101,170,148,171,59,169,3,67,143,230,30,79,69,107,141,25,168,51,39,2,91,232,3, +40,61,211,67,252,9,239,190,200,191,154,166,236,78,191,5,246,26,58,173,176,134,144,38,184,79,39,202,70,225, +171,23,150,212,147,43,224,150,23,108,3,126,137,114,106,91,61,243,186,191,67,0,12,143,46,27,117,21,172,150, +95,234,114,10,162,250,211,10,8,135,20,221,185,190,133,38,162,13,181,20,179,0,75,116,101,45,42,101,103,153, +39,99,206,167,151,211,199,15,211,167,36,150,177,154,234,181,18,169,209,21,88,186,60,73,78,227,115,125,162,182, +152,184,156,78,99,85,156,93,157,107,171,221,114,66,218,45,91,85,195,45,97,201,218,59,151,193,147,157,234,55, +165,47,167,72,243,174,58,15,24,163,171,227,151,241,95,95,158,193,177,135,177,107,93,207,90,94,158,84,45,113, +100,40,14,70,198,53,78,238,159,245,75,117,17,102,253,102,116,214,47,123,179,126,129,50,105,219,105,67,211,188, +182,179,188,156,166,42,99,102,145,157,180,165,155,180,45,76,208,198,230,184,154,78,161,127,208,59,156,242,11,55, +229,27,158,114,24,86,114,197,83,126,35,166,124,25,100,237,150,234,38,198,117,107,236,180,67,173,119,181,186,234, +210,229,47,219,166,117,83,24,89,206,178,196,145,30,167,13,189,194,221,129,152,250,235,34,89,232,41,25,164,42, +213,96,105,113,16,117,139,210,206,222,176,119,99,162,42,28,126,2,131,16,147,112,4,131,89,54,187,117,112,80, +33,250,247,133,167,141,56,40,103,253,253,139,15,147,146,173,151,33,91,207,196,41,208,145,217,49,128,56,154,188, +180,95,191,189,218,88,101,164,249,98,231,10,191,176,76,102,79,243,26,127,208,110,246,171,182,6,30,253,77,84, +64,118,30,161,42,166,154,191,167,77,135,175,84,195,177,201,26,229,185,61,27,128,63,246,131,241,232,200,40,51, +204,9,35,77,121,148,16,250,87,26,224,241,234,130,199,200,3,199,207,59,198,109,227,7,131,231,42,97,244,157, +92,101,33,226,37,65,84,186,190,133,14,192,85,232,96,178,29,197,132,253,117,34,180,143,2,123,32,114,152,216, +158,3,243,164,237,249,194,181,53,32,59,4,21,223,56,184,79,35,248,153,194,125,16,224,35,41,145,124,240,226, +32,188,49,42,187,49,60,39,232,75,156,233,170,86,5,110,135,246,24,182,200,248,150,80,25,146,15,53,147,15, +59,203,166,90,43,183,187,86,157,174,213,82,103,150,168,216,234,229,163,199,44,241,230,189,154,210,14,134,225,52, +226,236,42,194,119,100,207,49,76,31,61,200,148,128,125,144,178,86,141,165,56,50,166,56,44,214,8,4,203,149, +184,227,93,74,226,229,130,136,23,232,108,172,110,180,151,216,123,70,118,135,113,177,172,168,195,21,16,142,87,228, +194,212,134,47,33,124,25,12,2,54,204,214,99,92,245,35,136,160,61,15,221,83,167,132,65,26,193,186,197,116, +31,82,41,142,50,152,157,123,238,190,149,53,64,64,169,67,203,113,207,7,81,234,4,179,9,51,113,207,125,64, +177,61,130,184,55,103,63,170,102,96,248,237,185,15,251,78,73,227,118,178,77,117,134,200,166,238,223,186,29,12, +191,208,59,22,138,185,178,194,239,111,254,250,250,237,227,71,143,165,178,197,243,227,16,240,26,193,16,235,62,123, +170,28,63,118,234,53,192,198,13,56,124,104,96,105,174,80,115,240,23,253,102,186,157,103,121,212,168,23,234,181, +250,37,182,88,139,123,0,137,254,128,138,150,32,118,15,173,159,196,88,139,64,6,88,21,86,84,26,24,89,207, +138,250,188,145,60,116,150,24,181,242,68,77,244,75,108,189,140,192,50,60,53,100,141,27,213,146,172,225,212,215, +234,54,121,161,174,1,220,144,68,225,89,248,165,139,147,198,190,106,188,134,206,2,40,226,212,178,126,208,143,88, +1,242,78,189,73,165,70,112,117,213,251,61,152,252,4,223,215,159,211,166,80,16,53,189,128,161,101,106,135,96, +255,222,180,235,26,111,249,201,115,150,142,80,126,63,37,114,111,61,15,223,40,221,173,174,245,200,241,166,62,220, +113,228,206,175,23,27,141,42,63,173,60,52,167,43,121,64,161,128,34,26,99,247,152,29,141,127,19,83,242,164, +75,168,180,44,120,43,234,194,204,31,32,78,30,3,6,106,115,120,223,213,162,190,172,34,239,146,181,165,65,61, +207,107,67,243,21,123,45,143,139,233,106,126,144,214,194,105,69,90,171,191,25,158,17,54,65,174,55,200,78,175, +121,181,198,146,184,145,231,186,233,81,103,207,36,117,166,126,236,33,182,103,254,27,24,110,50,164,107,177,44,177, +122,161,175,166,219,233,115,194,155,175,53,26,221,251,5,254,222,206,11,220,149,180,49,34,137,144,175,23,111,144, +8,124,61,125,49,93,61,245,171,128,215,85,42,54,213,183,106,67,251,23,8,62,170,78,223,189,118,54,51,108, +243,132,234,253,101,122,251,84,44,7,84,107,43,121,61,53,46,43,211,15,43,223,142,237,176,190,111,93,109,118, +191,184,222,95,125,139,123,62,122,29,207,111,96,175,169,95,212,51,220,192,23,41,154,82,159,194,212,192,136,97, +176,73,232,180,162,243,42,86,239,35,91,142,138,64,46,232,230,84,195,172,36,118,26,58,216,116,119,128,77,23, +14,167,193,161,44,197,141,91,204,160,74,60,4,13,31,130,141,46,48,192,71,160,125,89,54,227,196,108,117,223, +233,90,107,223,191,84,27,79,79,224,99,3,29,169,184,231,181,157,207,194,170,97,169,149,14,85,45,117,168,236, +16,241,26,72,120,20,57,26,16,147,177,90,7,26,50,131,186,43,84,184,37,146,65,245,224,100,25,75,125,157, +205,240,50,132,184,108,185,205,76,20,33,214,211,79,125,147,16,244,155,249,36,158,175,117,54,21,77,184,166,123, +128,178,153,57,222,133,155,227,217,97,252,228,44,211,109,177,231,41,244,121,69,6,159,197,102,171,196,150,220,150, +81,74,231,217,56,169,81,247,141,232,50,137,100,229,88,134,113,68,133,148,142,239,1,200,186,86,198,34,235,173, +90,163,216,208,104,119,71,1,141,129,204,32,144,181,12,100,37,2,89,235,129,204,95,37,29,156,45,12,174,34, +215,59,45,121,142,1,85,94,160,36,174,199,127,207,218,200,203,9,33,219,170,98,201,45,134,194,1,171,15,174, +237,144,102,70,151,37,182,146,163,213,216,253,88,149,112,217,45,131,52,217,116,90,198,72,8,233,234,172,60,87, +212,28,183,197,40,200,193,63,183,103,27,179,64,216,120,244,112,224,246,12,85,118,156,68,82,81,210,191,147,16, +78,28,26,193,59,186,118,14,139,148,113,117,247,125,255,14,102,206,107,158,199,243,125,95,71,228,103,79,8,36, +12,217,241,170,210,31,96,2,88,50,225,168,66,103,78,230,184,42,95,25,160,31,209,153,83,35,157,57,141,177, +243,85,233,203,73,39,81,165,44,215,145,152,97,201,153,130,167,40,153,73,190,170,95,243,171,186,179,233,21,229, +253,1,161,62,181,107,12,110,58,174,191,49,126,115,245,144,133,10,101,24,128,235,80,168,97,187,153,240,227,237, +123,126,188,181,139,54,81,23,206,65,221,207,236,157,97,95,38,200,102,37,91,154,63,55,17,177,110,248,181,203, +91,21,231,7,82,52,73,214,30,59,221,29,131,175,235,100,109,132,92,24,1,52,98,68,220,41,180,220,68,146, +211,206,59,145,77,114,45,121,223,153,46,194,61,163,141,191,45,187,92,163,77,151,254,37,197,116,246,161,218,85, +226,183,123,168,160,29,114,243,143,135,124,18,174,195,2,52,45,23,14,1,247,60,204,54,87,115,220,3,122,235, +196,173,247,26,199,88,2,223,226,220,153,146,208,5,86,165,125,195,221,238,17,3,115,122,201,142,83,117,109,209, +201,169,249,131,226,133,31,76,201,224,209,75,193,224,45,59,124,222,236,191,122,147,170,6,82,170,248,169,90,239, +215,25,216,189,16,143,238,147,251,241,167,48,22,6,67,43,12,205,128,104,3,124,145,229,187,40,154,37,179,45, +123,116,74,9,225,5,241,143,39,254,186,122,122,162,250,47,7,114,141,6,242,3,202,115,9,123,151,159,196,200, +235,141,36,188,189,244,80,215,249,245,118,125,10,239,151,23,254,121,184,255,46,76,30,37,107,225,56,178,150,50, +0,88,8,27,140,204,226,196,121,168,86,5,158,17,169,180,213,227,117,244,177,87,73,123,198,222,48,207,237,121, +170,252,57,150,236,203,124,120,98,55,169,148,115,103,238,5,60,21,95,84,147,116,104,41,221,93,20,125,10,6, +84,255,202,26,210,124,148,114,119,86,159,134,129,80,165,93,185,168,96,82,179,112,135,194,163,63,42,113,68,251, +194,118,8,114,93,208,186,205,216,205,51,245,55,79,185,114,152,219,7,135,246,4,36,156,39,60,163,93,231,144, +171,162,195,251,94,104,244,27,242,212,111,200,176,9,105,181,38,19,168,76,216,226,75,164,45,62,172,248,40,183, +130,132,205,143,57,92,9,38,85,57,137,253,94,24,102,61,155,244,225,124,162,38,246,197,11,62,240,13,107,114, +30,60,62,162,94,95,199,79,212,207,203,251,159,168,255,197,7,229,11,222,140,255,254,55,223,251,237,159,252,235, +207,187,119,91,218,13,29,13,227,130,207,163,242,222,247,59,49,34,57,1,254,89,79,22,19,89,253,40,135,5, +141,151,114,255,34,42,237,37,117,97,127,153,72,75,78,231,189,229,64,92,82,6,122,211,223,95,30,134,215,67, +73,113,246,202,114,7,230,99,175,127,178,131,85,34,134,81,117,50,231,56,105,28,86,203,19,192,130,19,43,185, +174,196,37,125,86,191,107,162,86,202,174,26,133,171,33,68,88,217,116,90,229,9,139,154,249,164,136,112,233,108, +116,54,48,250,70,223,70,31,54,215,246,210,129,47,162,106,165,205,20,223,42,171,89,9,167,90,45,89,195,150, +195,137,217,75,200,178,178,133,26,178,118,166,127,122,56,59,254,83,156,96,85,51,159,102,84,99,211,32,9,239, +127,51,200,186,35,60,243,83,178,86,244,241,115,178,10,15,143,89,192,111,69,231,24,208,67,62,179,26,210,196, +135,175,173,189,235,110,165,75,177,240,143,30,15,150,30,102,217,247,173,118,125,75,67,223,10,223,55,239,180,38, +172,85,21,207,225,230,212,186,155,211,9,252,87,58,38,51,95,181,198,107,18,200,27,238,116,198,174,92,172,228, +181,46,241,215,58,231,125,5,75,158,213,42,61,239,25,71,248,122,160,213,132,52,232,243,114,140,6,109,15,211, +160,70,181,125,26,148,164,249,105,54,62,45,170,236,189,54,68,25,63,99,202,152,18,4,97,252,188,236,19,198, +212,39,19,200,88,191,152,178,206,249,128,182,109,61,81,43,115,221,71,216,134,156,247,19,183,129,190,28,80,145, +226,116,67,160,73,118,76,63,194,246,44,150,147,78,82,153,129,22,27,144,166,254,24,84,92,248,177,249,67,167, +164,133,30,79,243,77,232,103,50,56,46,15,154,174,133,110,121,161,179,159,236,2,255,104,210,247,95,167,27,50, +218,243,134,23,165,217,94,242,186,140,94,82,24,46,254,229,187,137,250,201,32,99,239,206,123,10,230,121,135,121, +98,202,110,151,21,66,119,47,166,47,244,191,177,146,101,85,95,165,197,129,181,188,111,41,79,255,116,114,242,239, +95,203,103,70,239,224,86,80,167,239,232,174,69,218,171,125,51,227,71,214,118,17,9,6,0,70,103,35,57,244, +92,137,71,180,209,46,255,188,125,130,38,111,60,26,168,173,78,21,239,79,196,149,32,68,84,195,181,171,97,221, +226,88,208,233,67,199,89,241,188,156,234,20,149,16,240,231,86,77,167,85,215,121,101,236,242,81,133,138,233,143, +32,78,161,249,12,211,16,168,221,213,127,67,138,233,37,233,164,55,250,155,237,213,37,92,9,190,251,246,205,203, +183,47,127,248,252,226,229,55,47,94,126,243,242,237,207,138,100,155,105,112,100,9,40,12,174,122,82,195,224,42, +215,231,2,6,87,201,193,161,199,146,98,100,112,153,46,134,110,191,144,97,248,55,124,176,204,98,208,43,109,72, +171,104,141,238,102,1,165,66,69,105,104,35,29,153,23,163,11,26,8,234,216,14,172,3,75,219,18,239,121,70, +220,217,11,173,124,129,246,34,158,213,117,122,123,76,6,181,144,27,67,210,7,199,233,102,83,220,82,254,132,53, +43,241,189,56,15,8,254,121,43,84,168,217,125,69,14,167,178,101,221,33,15,67,10,113,191,161,88,116,133,225, +149,227,254,254,255,248,123,252,116,118,10,122,19,176,55,242,150,194,137,104,224,179,158,178,130,195,233,166,127,113, +40,21,86,103,223,101,213,136,180,121,25,139,251,30,223,61,147,154,157,45,163,123,70,175,98,255,172,92,210,58, +9,179,163,78,120,220,149,178,182,173,19,42,243,29,125,67,102,5,167,110,210,191,228,34,51,141,190,81,187,111, +133,59,188,69,185,101,219,166,235,127,82,137,190,70,49,69,219,49,53,195,33,250,177,75,235,148,205,208,80,8, +245,22,49,40,10,154,45,97,241,161,233,10,237,1,54,72,63,120,99,66,254,2,95,139,55,93,214,238,32,78, +41,22,126,65,52,74,102,35,168,180,141,89,219,24,91,177,141,90,105,199,160,84,75,47,92,142,170,55,238,115, +51,224,185,146,214,140,222,48,245,170,172,65,179,210,115,186,209,151,142,126,122,61,253,112,108,145,50,87,3,97, +36,63,154,16,36,126,142,107,228,132,216,137,104,172,140,139,125,10,163,112,121,115,155,87,68,169,21,154,208,154, +234,213,195,76,146,223,209,106,6,102,202,120,196,111,172,179,180,41,7,173,219,188,79,45,215,86,93,184,153,191, +214,158,121,76,215,229,102,225,153,244,169,42,68,229,113,34,67,115,104,124,251,240,122,10,134,203,182,241,67,153, +130,81,212,9,92,7,238,67,183,180,221,117,51,111,251,242,22,248,186,203,135,235,94,217,37,150,117,217,176,180, +159,242,27,111,127,78,60,138,94,199,187,75,237,59,124,169,140,124,134,131,84,102,20,220,196,222,14,182,113,42, +56,166,47,49,160,126,136,152,234,81,239,125,98,33,19,197,186,64,63,178,180,141,196,186,196,88,234,102,111,46, +235,233,227,41,206,196,205,119,238,72,132,170,74,117,13,12,157,31,162,107,174,18,139,98,136,224,131,3,182,102, +8,116,88,237,137,235,208,90,116,136,39,137,186,43,116,140,46,167,122,195,15,143,187,129,176,220,149,216,127,223, +245,176,211,109,98,92,38,52,222,238,39,235,9,152,229,93,208,217,157,152,167,57,131,252,12,35,221,21,203,211, +8,2,179,82,213,67,27,40,212,23,220,195,165,170,145,43,152,66,27,72,48,76,249,155,103,136,188,29,251,219, +17,188,140,79,171,105,253,180,181,35,98,198,50,93,214,48,109,86,205,106,47,134,113,36,20,76,191,189,187,11, +198,77,77,173,188,218,66,226,108,105,215,44,213,234,140,218,100,218,13,81,88,18,242,113,139,76,55,79,116,148, +146,99,205,133,237,117,194,61,76,32,165,130,216,76,115,124,243,84,215,51,136,129,221,144,185,97,196,10,230,43, +83,150,104,195,4,95,117,172,50,97,213,182,217,215,174,185,229,87,228,214,127,209,170,154,96,147,147,162,225,206, +116,227,114,250,47,49,67,202,150,238,41,223,191,90,121,165,118,60,139,121,234,74,161,115,209,134,149,88,152,153, +46,147,86,204,10,140,3,227,80,82,195,132,74,223,10,5,117,132,185,210,139,20,14,235,69,136,91,148,83,109, +18,113,157,135,152,153,110,166,38,193,31,52,25,45,180,222,154,225,122,123,248,130,117,151,240,133,110,172,171,90, +56,109,197,67,197,77,84,234,166,162,64,24,201,160,177,74,121,27,209,235,96,253,121,37,205,58,47,123,214,154, +183,29,10,148,212,108,225,75,195,60,182,42,117,140,147,43,253,22,195,133,202,130,109,59,9,75,105,96,11,108, +166,58,75,82,9,237,209,102,166,179,56,145,121,102,1,247,193,233,21,79,155,97,137,169,52,138,184,133,12,100, +9,169,108,163,13,222,169,237,166,154,241,230,2,56,192,148,43,76,113,59,189,229,15,121,51,254,106,120,195,24, +60,124,251,117,20,3,179,182,149,189,149,215,208,73,153,50,43,143,41,210,218,82,38,134,153,176,239,222,4,114, +13,41,65,144,178,37,10,46,14,89,62,247,224,197,217,126,129,12,106,199,244,102,210,42,254,162,247,194,196,40, +36,250,146,9,71,78,58,81,211,79,3,226,4,72,77,255,176,33,62,29,41,59,18,5,56,173,40,144,153,30, +44,61,193,80,241,233,162,198,247,14,131,148,162,229,173,22,31,205,91,173,240,56,108,111,181,211,82,73,161,179, +31,76,208,251,161,247,30,71,86,15,60,145,193,70,232,199,100,105,182,54,203,103,232,65,103,196,53,154,156,41, +223,192,39,233,120,238,79,144,66,133,49,132,200,33,35,24,17,207,5,125,245,92,175,209,247,253,156,98,156,218, +239,246,221,171,209,97,45,99,196,9,221,143,30,70,248,83,91,198,242,113,42,163,24,89,246,162,110,247,163,110, +250,25,126,187,190,17,225,167,159,246,163,126,238,69,17,21,111,233,138,253,248,240,18,53,146,136,196,80,175,96, +151,151,0,36,105,193,158,87,250,147,191,7,32,247,47,121,119,81,155,166,42,62,152,144,93,178,26,135,245,5, +198,184,127,205,119,66,63,18,38,202,129,135,111,184,114,218,86,35,182,80,237,195,49,138,223,148,206,198,38,104, +215,248,66,30,100,193,152,93,8,52,170,178,143,202,85,36,218,107,60,238,114,221,197,234,0,141,142,15,96,96, +211,185,138,209,14,148,236,148,191,168,202,25,99,7,113,97,6,63,95,137,62,136,65,241,174,31,217,144,214,241, +139,149,81,18,70,25,60,198,193,99,218,224,132,240,134,160,156,124,33,166,42,129,135,97,25,184,237,104,74,173, +75,187,69,70,11,178,229,32,111,172,11,190,1,29,167,136,141,161,103,42,132,170,94,168,198,16,246,251,83,191, +73,165,237,114,196,239,198,35,77,185,149,135,237,211,224,49,225,206,177,135,238,253,0,249,42,255,128,90,235,157, +173,26,125,121,17,193,141,31,52,90,248,232,160,195,63,177,46,19,116,188,230,110,96,255,83,254,166,187,54,117, +212,118,9,70,25,43,202,75,181,161,97,145,97,14,151,129,90,161,186,236,231,104,85,172,160,93,35,225,223,224, +80,159,97,214,251,166,139,234,59,56,91,47,8,179,125,20,172,216,172,99,192,194,248,241,48,180,112,209,127,39, +184,92,100,181,73,91,67,80,47,165,120,228,249,167,74,61,208,186,163,229,103,59,131,240,199,247,0,233,46,103, +131,79,101,218,120,99,105,79,50,50,199,199,214,214,62,147,27,82,25,48,248,18,76,240,9,213,193,84,167,28, +136,34,50,171,29,11,5,66,138,80,165,85,27,68,113,115,124,219,231,66,13,124,82,17,44,224,19,49,194,22, +248,1,102,96,237,1,118,133,0,217,134,165,70,219,50,12,35,43,113,36,8,48,90,35,34,146,57,196,225,48, +200,86,203,108,254,152,24,84,214,197,251,231,79,115,224,232,169,198,79,157,122,239,32,79,85,122,223,211,233,253, +24,95,130,129,51,227,128,11,142,44,83,199,104,137,37,233,100,133,173,163,74,239,56,34,57,233,122,18,140,169, +126,102,206,74,255,74,119,46,102,162,25,163,178,0,48,184,23,18,82,203,88,82,40,244,233,17,54,90,117,24, +82,43,62,75,192,141,50,159,204,49,72,243,251,222,103,8,232,194,231,226,157,203,89,60,58,192,116,111,54,81, +144,137,192,153,195,16,150,206,116,223,253,199,174,35,21,75,0,79,184,142,202,147,90,101,200,72,131,91,88,73, +239,163,33,41,158,75,66,106,205,31,61,82,106,205,31,74,44,203,169,186,73,86,104,149,17,254,222,242,229,147, +165,62,220,157,209,9,127,40,75,61,37,200,89,183,84,19,124,222,118,221,8,33,91,143,211,171,32,78,71,241, +99,68,12,27,142,112,8,90,181,72,71,32,189,94,151,105,33,191,5,164,48,223,85,204,143,191,130,96,178,149, +218,74,76,103,101,141,63,195,62,143,59,208,196,197,162,100,207,43,111,41,211,220,56,155,70,213,241,205,41,108, +246,219,211,88,201,184,199,24,247,184,31,247,7,140,251,67,220,141,212,41,217,36,78,104,201,174,137,115,7,162, +196,125,186,238,95,160,211,78,151,225,146,92,132,75,114,38,47,201,235,222,37,121,69,151,228,148,46,163,75,117, +139,151,230,214,177,100,54,129,91,165,237,174,6,117,72,117,163,222,43,224,172,122,83,168,242,114,25,93,235,237, +20,180,66,84,35,94,213,47,209,62,146,190,156,1,127,76,95,79,107,242,19,81,199,9,198,79,55,152,50,165, +148,25,164,96,58,114,217,46,33,89,84,113,1,57,253,237,25,13,177,78,235,164,17,55,87,76,223,204,124,142, +76,173,226,89,157,92,104,65,210,171,202,51,51,162,247,26,68,214,245,123,104,240,82,95,192,223,27,125,49,197, +14,189,199,206,99,202,148,82,166,148,2,93,133,126,189,199,9,58,77,46,213,205,227,4,166,224,15,9,108,136, +211,228,189,186,125,156,92,171,219,63,36,31,58,47,173,62,234,103,146,249,153,149,110,164,153,126,107,233,6,173, +106,134,7,157,198,68,101,144,71,191,145,194,232,246,89,12,46,57,95,241,46,96,178,209,238,217,50,86,70,72, +85,103,225,59,146,249,56,215,152,112,53,233,161,75,30,125,170,203,30,3,91,25,33,105,205,73,116,174,56,166, +104,237,152,162,108,4,149,14,115,91,136,216,192,100,179,13,108,188,226,40,240,60,190,157,214,61,209,5,27,167, +101,228,20,166,104,122,10,139,87,33,249,143,169,229,62,23,125,150,58,61,60,234,14,62,209,242,54,86,85,120, +49,28,158,157,40,251,156,234,209,227,19,147,132,193,147,34,8,43,102,74,240,144,215,157,174,212,10,231,173,18, +143,29,75,191,64,78,5,60,86,91,221,192,176,151,49,190,97,60,89,137,241,45,34,25,154,33,239,50,65,229, +100,3,67,221,16,108,140,235,118,93,122,221,46,161,86,143,165,165,148,221,189,34,118,61,215,146,29,108,198,70, +170,101,109,85,22,79,51,168,242,70,95,77,11,208,28,236,59,212,4,61,232,45,32,211,191,152,219,79,189,172, +162,244,190,52,158,129,28,104,18,34,137,85,191,190,186,39,44,40,43,170,135,210,144,162,22,214,55,147,254,60, +235,190,171,209,211,123,218,145,186,117,46,13,3,100,160,74,181,67,149,63,159,197,71,161,55,83,183,42,195,9, +156,173,227,225,180,54,172,64,183,85,167,49,102,152,1,220,223,16,175,114,160,252,214,87,119,187,97,117,183,247, +250,233,123,171,236,54,112,174,117,239,212,175,113,210,0,209,95,170,91,84,38,7,88,70,161,29,6,163,155,46, +22,206,178,188,22,220,61,235,208,235,129,111,224,2,27,152,158,98,19,179,199,216,8,252,29,105,6,112,238,125, +125,230,188,175,81,103,239,82,93,41,132,113,223,59,25,123,127,71,125,61,23,10,251,134,61,131,126,197,113,39, +11,14,137,102,198,19,132,221,249,234,39,206,104,220,243,65,145,87,60,179,37,149,194,144,61,185,107,213,123,138, +74,82,245,81,152,165,180,152,165,12,152,133,109,70,72,132,161,182,254,49,110,115,215,217,161,174,194,83,221,155, +120,39,112,242,27,181,177,8,121,186,181,232,119,233,241,240,114,90,117,112,18,110,196,81,194,236,116,79,6,168, +91,245,140,206,37,121,246,212,7,207,24,62,39,86,238,156,144,167,217,37,204,235,224,132,193,145,219,229,251,33, +26,80,234,168,49,186,69,245,245,203,163,64,8,212,189,231,32,16,133,131,89,157,226,223,19,56,214,79,212,109, +56,126,175,159,220,194,225,116,109,117,102,96,127,158,93,159,171,155,209,213,199,20,217,171,27,232,203,5,119,4, +59,241,94,95,48,195,1,58,243,158,235,247,172,170,222,153,100,84,11,115,182,193,113,10,165,175,222,114,22,49, +146,27,39,234,153,118,85,205,63,60,121,6,93,253,16,95,69,239,207,62,156,99,81,89,98,14,189,97,126,198, +85,220,1,52,12,210,121,230,108,22,158,56,92,221,153,174,8,168,61,71,98,64,179,248,203,202,8,209,210,35, +89,138,223,70,178,112,173,158,102,225,35,125,240,26,173,204,1,101,116,89,254,126,90,70,74,23,200,53,116,41, +227,196,75,10,91,42,37,226,37,237,19,47,41,48,46,126,27,241,82,246,223,207,45,149,24,176,155,188,108,8, +154,191,82,14,115,56,212,114,131,15,98,73,33,136,243,204,17,231,235,30,213,191,10,84,255,82,82,253,219,30, +213,191,33,170,191,57,150,215,135,120,46,103,168,217,67,160,166,119,134,54,189,51,212,136,195,183,239,155,216,244, +206,7,227,108,58,167,211,21,162,236,218,17,229,124,239,219,191,133,113,213,84,36,155,45,85,129,49,255,216,166, +144,179,205,179,207,182,53,87,151,169,66,209,223,233,18,171,149,239,159,149,191,41,124,92,51,88,201,122,182,25, +109,136,83,21,118,102,131,95,220,152,157,220,143,171,127,139,229,198,107,183,117,83,251,219,209,97,32,21,113,111, +43,92,211,234,80,27,138,103,223,244,172,192,26,119,246,247,22,16,53,142,77,80,137,191,176,151,112,127,47,127, +75,38,68,61,67,112,248,106,48,124,172,82,141,38,246,255,141,170,236,199,45,162,18,208,238,8,84,58,178,125, +218,81,182,143,228,49,141,115,128,144,219,84,247,37,165,211,49,126,10,234,173,239,243,83,106,21,178,198,42,67, +198,10,180,99,153,44,95,52,246,177,24,95,244,35,98,102,28,105,228,161,64,207,125,224,22,101,6,36,143,37, +27,227,177,100,252,33,95,160,82,251,219,123,167,74,249,163,247,46,5,70,83,228,163,20,132,111,213,189,12,19, +207,61,36,228,35,215,234,227,89,124,236,83,72,114,240,122,190,14,88,85,97,28,58,76,236,117,29,24,121,97, +238,62,15,73,12,189,83,149,55,73,128,124,39,59,123,221,188,212,222,129,99,25,63,57,62,57,57,69,183,233, +92,117,221,127,250,86,169,184,118,211,233,133,23,129,1,9,49,136,31,198,248,35,83,70,251,99,209,29,137,38, +188,117,161,45,29,239,16,23,30,144,138,234,50,45,158,21,155,117,170,121,185,7,216,191,82,173,85,51,64,235, +9,202,12,244,223,85,101,143,19,175,231,27,56,12,88,208,196,33,22,59,57,136,244,39,59,71,255,56,214,132, +116,138,27,211,19,7,238,46,239,64,79,62,156,133,189,135,215,162,174,25,230,29,50,143,67,126,100,15,147,242, +92,180,235,73,35,166,44,112,89,116,193,196,86,38,240,199,80,0,51,181,27,60,139,135,206,212,38,159,165,101, +89,1,131,30,234,3,115,244,220,200,131,180,125,64,13,60,152,76,83,247,108,48,232,129,151,135,204,172,144,101, +113,238,251,212,145,191,129,38,183,238,254,29,111,217,225,165,207,214,232,67,119,25,53,136,16,208,247,54,89,84, +147,163,214,163,92,106,111,86,18,48,0,204,250,107,98,64,146,150,104,3,218,150,54,141,183,45,40,95,198,66, +119,154,177,60,228,178,102,111,241,16,24,175,40,136,38,31,110,234,212,239,72,137,3,96,196,195,133,246,35,127, +55,178,224,149,21,90,74,15,76,78,109,17,45,212,131,243,88,171,42,6,53,62,47,109,51,156,175,90,69,141, +219,75,224,50,205,179,115,253,196,14,102,18,81,132,197,15,128,28,6,51,135,26,228,208,116,55,218,235,1,123, +87,78,192,136,34,121,181,109,157,9,97,247,158,193,193,7,198,227,158,62,208,186,182,158,181,56,221,120,235,71, +75,175,228,14,243,10,62,84,37,188,186,87,193,16,101,45,12,81,214,221,112,54,229,155,33,243,217,75,230,178, +7,127,64,158,73,76,199,104,53,122,140,74,71,124,53,251,25,137,74,248,168,241,56,107,232,227,54,238,186,162, +244,197,161,83,80,31,233,98,188,102,93,12,230,163,11,21,153,162,84,34,127,242,204,216,183,220,151,101,238,53, +53,12,249,24,224,162,244,236,95,148,145,151,141,246,218,26,93,124,64,143,34,15,98,62,254,243,88,8,80,24, +40,87,155,198,180,191,165,0,245,18,13,31,75,53,30,159,91,25,29,196,150,58,176,78,95,86,109,190,186,5, +254,13,218,78,141,38,252,174,111,115,88,139,218,38,118,179,106,37,148,32,210,26,103,142,213,94,113,106,124,80, +122,68,193,60,95,133,121,19,199,55,171,157,207,125,218,152,218,57,170,62,193,152,63,243,70,161,233,148,202,44, +84,33,58,233,164,182,137,247,166,122,162,220,6,36,165,110,161,63,194,26,29,19,181,231,141,187,126,119,153,70, +39,138,254,59,254,175,120,98,165,197,57,241,255,172,86,43,142,121,49,166,130,36,25,205,201,99,181,199,227,77, +254,172,2,23,59,97,206,42,30,218,178,126,138,8,149,96,200,54,214,209,119,175,172,191,2,250,210,28,21,202, +15,110,165,208,5,27,51,54,0,78,146,77,120,245,155,63,75,193,71,172,56,188,225,252,169,255,134,243,103,193, +27,98,19,215,65,142,158,44,206,120,134,209,161,244,125,38,26,15,175,207,134,194,37,238,137,67,251,11,221,112, +41,227,161,63,119,79,208,39,187,229,182,182,95,127,60,1,136,73,27,172,106,2,191,230,219,109,251,215,45,250, +60,233,66,118,128,175,146,116,99,224,195,138,252,217,224,196,121,83,200,77,147,4,47,247,68,32,194,175,37,11, +225,195,226,61,247,241,243,228,188,83,238,153,114,231,218,102,151,183,202,119,236,49,104,54,117,42,136,89,236,132, +40,78,242,89,107,129,202,59,127,182,84,221,211,19,129,10,208,202,43,110,182,32,214,224,44,247,150,218,44,12, +23,73,78,156,126,49,158,204,254,72,25,132,232,0,192,179,133,233,16,239,16,43,136,45,98,221,96,247,127,50, +193,250,56,232,115,217,32,166,148,120,51,179,165,136,152,121,82,250,99,233,76,68,59,83,44,147,9,99,21,63, +238,64,7,99,72,136,212,96,144,154,193,73,249,87,198,148,219,222,78,39,9,144,96,249,113,79,125,5,136,130, +97,140,213,4,211,249,200,76,180,136,179,166,26,43,138,189,124,218,94,121,110,246,155,8,13,162,96,126,164,134, +59,21,94,124,112,64,190,244,56,145,217,183,29,31,143,154,120,200,195,244,122,186,82,110,30,35,121,35,18,75, +114,234,32,166,183,185,76,143,123,226,31,26,66,2,6,212,240,1,34,164,250,168,129,145,132,174,83,125,190,230, +1,123,154,30,161,118,106,240,38,246,191,56,121,226,197,202,140,189,88,65,172,251,236,24,140,61,164,82,72,64, +177,151,156,194,176,69,201,62,155,79,232,246,116,29,253,73,49,89,85,165,63,28,66,140,63,185,124,196,221,182, +32,208,47,129,179,232,0,196,7,134,156,124,145,11,187,243,117,210,215,158,12,168,74,86,138,41,253,124,2,175, +66,198,21,23,234,39,52,50,101,226,163,209,144,5,162,125,252,78,139,111,105,233,223,100,213,134,176,47,121,110, +74,51,140,67,4,251,233,106,32,93,185,187,184,32,5,191,139,11,75,24,60,55,153,235,70,185,82,47,200,133, +69,114,187,82,214,82,83,242,126,165,222,108,47,45,214,121,179,82,246,227,25,124,48,81,245,122,213,185,45,253, +114,165,189,238,128,126,234,52,1,219,160,9,184,136,140,246,42,132,179,83,21,60,58,238,188,99,79,130,139,22, +29,136,90,3,161,45,210,59,214,231,105,172,76,28,116,24,95,172,246,253,86,5,149,194,214,90,10,37,167,123, +14,159,189,244,37,130,249,5,196,113,77,251,50,148,226,188,72,71,87,11,147,52,157,205,249,107,240,88,225,28, +90,227,31,20,243,39,174,134,101,8,228,177,58,129,76,108,235,99,147,123,121,116,99,198,229,209,91,103,154,138, +52,162,17,13,14,132,203,63,96,212,107,164,245,244,137,116,93,65,251,7,165,243,80,0,121,192,192,27,100,194, +137,112,167,220,224,134,31,156,10,75,215,91,172,71,201,107,81,117,100,112,24,221,165,216,87,252,146,180,52,51, +19,53,104,143,117,180,79,52,184,99,238,89,71,202,146,145,115,248,130,75,26,11,251,98,243,131,61,242,142,157, +243,230,5,214,133,244,62,52,127,102,176,35,45,44,14,128,64,169,90,245,185,117,76,178,215,147,88,253,186,130, +164,224,5,38,238,150,6,54,199,21,189,7,183,233,171,252,42,111,27,111,249,23,162,159,147,43,251,101,210,162, +81,4,23,48,157,239,218,247,13,224,42,92,105,236,94,97,168,76,82,98,230,164,9,185,190,206,203,175,211,27, +100,0,204,7,184,25,75,18,203,60,7,244,64,214,192,224,160,43,209,100,184,129,143,102,111,10,66,207,121,120, +208,158,46,189,171,11,221,88,255,14,111,177,58,41,38,206,89,149,209,46,235,80,4,188,162,67,70,184,109,218, +159,126,136,210,36,229,103,112,221,125,87,22,85,82,29,55,8,0,184,164,211,211,120,31,82,253,211,152,47,21, +149,100,153,94,157,238,195,187,239,45,102,58,254,83,114,18,11,23,69,45,56,85,210,64,25,76,167,113,195,182, +208,169,157,164,238,194,86,237,184,215,222,213,249,112,59,140,65,85,75,110,217,91,239,170,118,129,14,154,1,241, +4,181,126,222,163,199,34,102,204,253,130,19,67,191,160,219,63,123,80,7,22,205,72,100,220,237,57,101,23,74, +216,140,46,143,180,163,170,17,62,108,255,121,7,33,20,56,236,3,184,49,161,52,81,165,69,229,69,20,181,179, +225,44,199,143,134,171,212,235,12,66,208,222,172,17,148,250,249,66,71,248,237,83,19,224,0,187,33,186,32,71, +133,115,201,222,230,177,21,138,132,68,202,36,134,44,48,231,176,191,83,87,49,143,73,148,126,56,58,18,239,188, +126,64,24,177,139,231,110,3,103,195,82,195,205,163,53,239,170,250,118,50,135,8,119,109,214,59,26,106,226,133, +185,19,72,12,90,240,67,240,146,154,244,95,238,187,108,222,217,125,142,30,226,90,179,73,42,133,72,130,204,172, +144,219,57,24,79,67,134,100,84,6,249,218,36,195,20,218,193,201,154,208,78,254,14,176,82,178,82,108,63,234, +83,91,219,18,181,235,182,186,66,113,152,141,134,249,87,132,124,174,168,222,75,124,88,188,208,71,223,68,40,126, +135,191,105,172,222,227,111,22,171,107,29,93,206,174,226,71,209,10,54,43,109,246,15,250,139,50,178,145,155,71, +160,116,188,85,183,138,140,34,227,81,241,225,201,169,153,157,254,17,140,35,94,192,191,27,199,59,227,125,119,213, +41,254,186,236,206,231,207,237,86,207,76,94,68,151,143,62,196,51,10,174,138,10,54,240,21,132,213,243,167,27, +128,98,106,239,249,195,15,220,90,172,190,137,10,220,55,183,182,248,166,186,142,78,79,240,161,230,131,168,239,195, +195,219,248,209,109,172,4,194,92,68,207,244,160,133,135,31,212,27,217,9,138,138,19,200,120,5,9,151,177,130, +65,128,199,40,56,182,158,167,81,148,206,234,248,81,165,62,60,2,187,123,241,34,194,238,123,8,244,98,88,54, +215,7,181,142,177,67,54,244,92,61,211,53,84,152,198,201,123,236,197,197,162,78,158,65,248,102,145,38,48,111, +58,155,157,98,222,55,179,103,144,23,154,127,78,223,88,203,181,137,158,43,209,204,243,216,53,223,107,29,10,137, +201,132,96,48,129,238,177,233,95,202,8,166,20,254,62,131,212,254,220,225,132,46,126,76,96,10,159,201,90,159, +209,28,170,55,50,238,13,197,17,28,188,96,83,29,48,71,17,188,127,60,67,222,33,208,104,67,68,171,158,61, +1,49,253,23,211,41,14,70,212,20,61,155,190,128,201,182,109,212,234,43,180,254,126,173,242,56,166,204,113,226, +138,197,243,23,79,158,195,107,252,139,184,95,245,129,170,2,102,135,133,131,110,189,129,110,165,11,227,37,51,174, +17,191,4,60,196,136,70,165,216,129,212,118,96,49,146,65,167,73,191,249,180,131,133,58,2,158,233,27,13,13, +64,167,251,201,111,186,158,198,45,84,142,196,227,110,237,81,127,98,112,103,191,118,119,156,178,11,116,232,69,27, +225,251,143,142,140,181,1,208,0,88,53,177,29,112,86,53,240,13,224,143,47,96,170,214,199,255,249,167,135,237, +195,104,2,87,108,119,222,187,241,123,144,108,1,104,235,152,213,28,175,62,158,172,36,124,218,35,40,161,24,135, +239,33,60,33,227,61,228,168,164,235,252,213,189,133,129,5,202,223,31,101,112,116,72,27,32,214,194,74,12,248, +197,147,118,211,54,182,39,202,180,229,87,19,196,137,212,148,189,224,4,66,141,36,18,158,181,127,51,117,5,7, +118,255,5,68,82,113,70,82,113,229,61,84,92,131,153,29,255,61,60,74,234,167,141,54,139,38,41,84,138,129, +74,151,64,255,16,3,167,13,2,52,95,147,215,231,12,127,43,168,241,9,16,22,217,147,147,5,48,224,226,164, +120,138,33,252,83,67,176,227,75,73,133,138,219,88,244,116,30,85,79,157,197,153,175,159,253,116,241,230,217,11, +180,56,243,246,243,47,62,127,13,236,252,39,62,237,229,55,189,52,114,38,25,94,84,171,135,199,39,127,66,77, +159,168,154,162,44,29,188,104,69,205,172,136,59,79,54,54,129,108,36,229,58,156,94,164,128,15,27,77,70,156, +107,231,135,15,40,202,158,24,58,215,136,249,137,147,170,60,137,80,46,162,70,224,48,215,220,163,178,119,46,184, +14,65,244,244,84,53,79,1,23,194,80,176,7,85,97,142,175,211,186,140,254,78,254,55,65,242,128,29,174,45, +59,219,153,99,223,240,131,79,118,101,247,224,186,218,22,203,7,181,105,224,16,127,192,246,24,225,158,249,96,187, +121,208,86,144,165,233,30,216,114,15,176,231,152,132,241,167,39,39,39,199,127,199,131,5,209,112,156,56,186,219, +155,147,246,243,162,12,62,126,157,158,194,7,116,177,145,142,16,155,24,42,232,246,138,248,157,112,200,134,208,144, +96,31,183,138,238,231,94,222,139,68,43,243,50,156,10,143,85,41,222,231,61,45,81,42,38,67,90,190,119,16, +25,210,98,247,9,210,91,44,44,200,17,115,236,191,45,221,98,252,108,91,66,5,34,232,87,208,40,22,39,248, +48,76,152,192,142,35,164,178,196,151,80,157,8,129,4,110,159,222,129,228,94,152,158,100,58,255,192,89,3,94, +128,165,225,183,50,32,194,26,85,5,210,126,228,166,117,93,69,181,162,236,19,194,97,147,88,181,238,189,110,17, +201,167,59,129,56,29,4,7,212,233,128,23,160,38,228,243,177,62,159,47,25,171,58,220,40,134,11,30,22,217, +132,42,74,95,22,145,204,248,13,100,112,139,131,107,204,224,122,223,232,168,156,1,205,31,252,110,250,211,80,157, +198,32,8,110,102,186,81,229,84,55,221,222,41,96,134,7,64,185,143,251,161,242,209,75,23,47,192,231,16,16, +154,75,190,187,69,133,251,90,237,99,25,230,4,199,29,31,113,223,6,77,254,171,124,119,207,165,221,222,214,19, +51,122,1,247,232,239,19,180,111,213,38,193,47,38,196,24,32,20,146,83,27,115,232,204,25,219,227,114,21,135, +32,14,75,185,8,226,49,82,46,70,149,72,23,236,15,94,238,3,196,74,81,27,136,134,82,16,13,165,39,26, +42,45,133,134,168,107,200,216,116,125,62,241,59,33,32,99,243,200,35,47,48,215,92,73,193,199,38,190,251,94, +250,239,189,113,30,186,11,254,43,23,192,174,251,182,196,219,157,123,60,154,67,240,240,221,238,187,240,240,80,55, +199,64,160,152,58,207,228,157,238,31,210,64,70,254,72,210,217,242,246,65,54,51,98,152,157,211,64,37,254,56, +184,14,246,243,19,206,141,99,85,202,83,18,227,122,109,152,56,150,124,153,13,180,67,152,225,112,63,90,76,143, +99,40,87,15,90,36,85,96,25,87,245,154,170,33,185,208,53,208,42,50,214,19,21,144,12,86,108,151,213,131, +62,239,5,125,254,253,82,213,9,204,83,133,58,173,211,105,170,82,156,135,19,210,203,61,133,136,26,171,5,14, +203,226,20,239,36,149,188,127,164,15,251,61,120,8,74,47,197,252,122,157,163,74,231,19,64,234,53,84,5,231, +109,250,196,159,107,25,79,66,122,19,176,252,160,79,217,160,79,13,99,146,87,229,255,62,177,124,144,44,246,204, +214,43,201,78,192,244,129,45,0,101,206,137,95,93,34,195,45,222,217,74,209,127,191,62,242,6,138,121,216,159, +68,37,50,65,193,213,99,73,252,251,238,223,135,29,253,105,129,60,108,170,124,128,44,69,6,35,50,120,116,38, +8,115,128,132,222,40,226,123,176,236,120,194,191,192,155,197,105,247,135,106,227,15,85,207,248,7,98,30,50,44, +144,15,51,32,243,35,52,42,164,159,30,222,110,69,28,79,179,120,142,139,213,160,236,205,19,128,243,168,138,128, +76,172,33,51,236,25,8,164,81,169,136,109,91,195,103,163,224,11,190,49,39,240,34,48,198,38,54,20,81,83, +110,12,251,41,131,249,115,157,7,202,135,129,111,251,238,157,105,90,179,132,5,4,8,192,75,107,228,71,120,2, +64,225,91,253,237,204,226,64,123,18,204,176,52,104,3,178,19,76,46,138,152,244,166,83,165,6,76,103,79,242, +251,200,174,242,48,217,85,254,239,145,93,229,56,125,226,143,52,222,191,139,201,201,36,249,159,147,43,163,164,29, +247,106,62,70,187,13,144,8,226,238,125,254,57,70,251,145,204,48,112,215,1,29,133,209,88,147,221,8,10,81, +232,71,224,25,59,159,123,109,124,231,89,142,217,185,48,114,237,35,234,192,255,244,120,247,39,225,193,227,220,195, +143,220,112,123,212,128,25,225,250,118,175,248,212,175,222,165,117,222,174,175,242,108,50,127,245,241,71,127,40,199, +167,135,20,159,234,217,75,205,202,129,112,25,214,139,8,219,91,41,68,1,53,254,244,99,182,214,46,177,225,101, +93,109,190,27,88,245,250,156,93,17,33,245,190,242,194,63,95,134,239,120,106,156,68,53,23,17,102,249,190,25, +90,232,50,250,11,75,207,194,243,24,232,152,36,159,164,152,193,169,196,152,88,173,19,199,18,123,216,10,242,175, +11,117,254,205,155,126,83,141,175,151,158,59,173,181,192,102,177,163,21,73,218,25,250,205,194,215,225,118,10,95, +93,146,63,193,44,79,101,6,155,220,37,46,198,101,23,237,253,178,146,179,186,43,146,220,58,47,202,131,241,115, +12,171,58,201,217,73,150,72,97,71,101,144,212,86,27,81,4,131,234,18,162,45,227,95,20,225,152,78,153,125, +221,1,114,8,117,118,238,236,157,96,161,42,47,249,209,136,167,13,13,254,122,60,32,146,21,185,18,97,19,190, +62,118,241,211,163,42,57,241,79,89,78,161,186,152,78,131,174,120,45,133,246,201,96,109,168,128,163,241,176,153, +163,218,181,206,156,112,60,31,97,107,237,75,4,11,4,133,202,73,96,17,178,161,172,224,20,75,170,52,182,154, +136,25,27,156,95,106,132,30,178,15,187,82,189,161,98,118,56,216,176,185,37,55,179,213,43,238,27,61,146,63, +43,223,21,6,58,53,133,74,55,146,172,219,148,209,54,70,53,69,128,162,141,2,213,10,181,60,190,86,39,234, +244,191,224,236,191,116,177,183,16,187,86,255,125,162,30,255,39,144,31,63,208,25,162,182,228,201,177,203,105,54, +194,60,226,165,117,6,48,139,54,57,103,112,106,160,131,57,8,183,16,190,132,240,101,220,239,187,53,76,241,51, +140,140,224,55,128,217,15,24,181,39,54,236,137,92,127,215,50,158,126,246,241,120,239,194,248,185,247,225,139,126, +184,236,241,243,4,186,183,136,82,77,221,228,56,124,92,0,32,14,140,34,8,40,76,79,227,56,41,241,136,2, +105,63,107,242,38,162,32,142,203,22,170,3,71,39,167,177,214,83,40,164,26,223,88,187,136,10,29,225,28,52, +174,177,26,202,181,178,177,150,230,168,128,198,26,110,236,146,216,132,17,5,113,210,108,161,75,217,216,165,130,248, +105,33,237,255,209,44,10,10,150,247,198,248,182,168,194,182,128,249,3,84,89,161,22,123,170,123,144,136,206,137, +229,166,25,221,48,141,216,48,25,234,197,62,1,78,172,223,48,227,16,159,169,116,90,79,205,89,118,142,15,72, +171,1,72,2,236,194,106,34,204,78,191,138,99,4,253,22,115,110,245,23,171,200,65,227,10,33,249,251,85,180, +66,232,253,43,198,91,216,221,192,78,224,59,198,77,130,145,96,93,67,184,69,216,88,207,23,87,206,167,186,181, +180,49,197,146,236,11,99,11,129,117,23,123,103,106,194,100,243,74,222,48,173,159,119,252,129,205,178,112,26,99, +128,84,49,216,183,131,42,172,152,238,29,0,210,20,228,76,183,137,233,91,17,197,56,84,114,204,133,173,207,177, +58,254,27,250,130,191,176,67,23,182,76,18,153,167,16,130,232,39,255,125,18,115,85,189,138,254,66,21,9,191, +240,210,113,184,88,117,52,140,170,115,191,198,141,110,103,167,243,230,41,44,117,51,155,133,189,89,126,4,94,108, +226,216,106,137,86,22,171,121,29,75,177,68,153,42,188,185,209,100,197,75,180,244,203,211,233,125,4,130,118,186, +119,238,200,182,2,127,155,78,147,87,63,120,77,221,120,203,237,87,120,184,87,251,135,187,212,193,220,40,19,148, +214,215,179,43,123,154,173,224,3,207,168,229,108,61,189,98,37,170,237,108,5,223,206,64,40,120,236,48,3,188, +140,221,130,61,52,208,88,85,181,243,229,81,29,211,175,28,251,184,175,142,158,103,142,85,61,180,245,202,142,173, +115,28,175,137,155,227,180,206,96,1,110,44,86,134,62,221,242,23,186,18,249,62,158,123,255,230,213,216,214,36, +81,168,6,85,70,217,112,14,106,68,73,233,142,83,144,238,40,73,184,99,180,60,52,195,158,150,101,121,209,255, +159,70,68,192,216,115,4,202,251,230,117,182,45,82,63,71,181,112,124,149,162,105,102,244,254,119,84,130,118,75, +13,255,82,4,111,128,241,200,185,160,30,120,117,174,123,62,159,211,129,75,231,86,200,111,142,120,153,110,247,197, +59,251,142,145,105,37,172,102,137,106,164,90,167,239,5,126,74,101,175,240,48,57,110,171,213,10,111,25,214,135, +106,217,68,107,0,169,73,231,222,19,63,53,146,217,122,39,139,132,1,161,111,165,115,63,78,34,126,25,47,65, +218,27,67,221,63,195,173,122,218,243,252,202,148,205,208,0,230,208,163,82,86,246,56,170,49,217,73,214,67,103, +78,232,226,70,26,232,85,165,244,13,229,243,124,217,55,213,59,239,141,121,248,132,69,164,170,65,119,61,244,25, +251,217,24,205,141,68,106,137,153,241,43,222,159,37,145,95,188,50,149,56,158,255,57,135,231,116,192,225,1,112, +247,55,192,33,43,220,39,218,91,196,201,111,231,138,239,241,156,135,131,124,52,92,172,184,179,143,118,92,15,129, +5,2,93,143,93,182,159,69,42,67,197,35,128,53,20,249,178,202,139,86,42,213,227,137,191,250,174,12,233,121, +111,33,80,157,65,153,243,62,147,163,129,55,89,60,175,23,13,250,147,139,189,221,66,91,251,190,242,35,121,104, +204,139,188,189,141,74,220,181,7,159,91,231,226,42,217,202,238,184,232,5,92,151,168,43,9,254,29,208,202,94, +213,163,27,196,123,186,87,194,242,84,128,27,178,238,13,130,153,128,223,97,142,114,214,248,28,114,65,103,129,246, +28,38,193,225,228,72,76,223,135,184,235,221,34,36,119,224,251,71,209,222,58,122,173,221,211,120,40,189,72,244, +47,85,2,15,135,110,105,86,48,218,135,102,138,178,23,182,169,231,185,21,56,120,81,87,87,118,88,146,123,50, +16,69,5,200,15,150,115,135,131,241,236,152,153,231,170,204,199,164,239,61,103,43,228,143,31,154,36,106,125,57, +8,74,54,201,126,23,239,235,26,246,229,254,222,221,221,61,159,223,36,174,196,212,116,163,100,150,88,163,225,2, +193,86,176,26,149,67,81,202,112,107,64,57,64,238,9,158,83,123,122,233,170,69,31,77,221,222,201,207,254,132, +6,246,76,134,208,51,251,106,90,122,251,252,82,184,6,160,64,130,59,80,133,254,146,231,19,61,41,67,183,131, +164,217,239,133,7,22,121,184,82,209,145,254,114,252,93,48,103,226,32,168,232,11,222,93,113,232,2,192,185,111, +3,107,112,142,132,250,171,230,235,245,180,56,17,192,134,8,224,146,9,224,198,17,192,85,167,71,15,224,176,104, +247,20,31,90,67,113,141,14,85,102,90,245,174,206,151,201,206,81,100,232,190,102,95,111,215,131,141,247,168,55, +47,29,45,86,14,169,37,204,242,49,147,222,127,203,142,149,81,135,240,12,54,34,9,175,82,90,149,226,16,70, +7,34,204,142,255,11,24,218,253,222,0,213,142,224,12,9,198,38,41,237,124,52,104,48,184,218,91,131,113,19, +131,70,158,9,225,168,248,203,202,27,212,20,177,24,21,88,213,159,167,217,58,138,50,181,198,243,15,170,90,31, +209,43,83,170,63,98,254,50,22,227,157,59,27,186,119,26,154,32,73,245,249,79,220,167,149,74,161,95,168,137, +47,220,115,66,127,130,145,133,90,87,112,1,172,241,2,88,135,11,96,166,203,97,35,227,119,192,58,142,29,113, +191,22,196,253,170,211,217,252,104,5,68,253,26,133,209,4,217,190,26,88,146,91,15,172,199,101,146,140,223,183, +28,151,237,147,241,31,53,137,134,214,98,136,127,145,114,243,239,21,133,30,199,44,53,114,239,134,102,226,248,70, +213,195,113,18,167,113,215,49,15,186,3,3,103,96,210,34,92,220,181,67,24,254,148,134,5,80,76,33,166,155, +238,135,233,82,243,200,172,45,145,81,95,151,227,200,251,36,118,86,134,231,194,226,134,243,35,105,238,30,90,93, +181,152,167,193,239,96,101,42,56,251,61,96,83,106,108,83,192,230,226,77,81,88,93,135,35,227,86,201,143,226, +46,152,20,92,90,181,22,60,86,156,144,234,99,64,195,119,10,89,175,188,215,84,134,46,187,237,50,124,202,12, +6,32,224,6,126,139,106,221,246,92,52,177,86,43,59,106,146,214,244,16,110,37,71,195,239,101,188,63,237,179, +48,132,165,189,89,253,232,241,108,101,249,22,179,106,182,166,119,9,140,34,14,70,61,93,113,99,54,1,130,130, +133,209,42,238,145,58,129,178,106,237,118,106,102,89,21,93,220,245,204,151,4,251,171,8,134,221,167,134,158,118, +208,232,96,90,188,98,177,14,136,244,111,59,194,3,59,235,203,153,190,51,118,239,142,104,162,4,234,149,197,2, +198,56,149,154,154,103,231,251,250,153,39,221,240,8,67,105,52,79,134,226,125,137,223,154,246,86,14,91,186,71, +246,68,73,22,217,128,251,196,215,103,23,25,84,208,195,64,216,145,36,46,1,56,253,236,124,107,146,199,216,121, +93,246,63,169,61,214,43,14,166,227,217,13,74,149,122,55,9,19,103,23,13,248,144,66,53,118,162,4,63,33, +228,200,56,13,39,100,24,203,173,120,189,75,45,143,69,169,235,136,179,61,241,142,40,223,230,248,46,93,20,121, +99,32,2,151,161,186,186,170,74,28,187,29,53,137,44,54,232,78,191,83,227,121,48,141,115,253,25,230,8,176, +47,140,113,47,211,159,205,31,69,166,117,181,173,247,178,252,225,207,230,79,156,231,241,31,59,181,76,111,247,178, +252,215,159,255,232,243,252,1,234,185,54,230,125,200,116,202,77,157,252,241,191,124,46,168,8,210,218,245,94,85, +143,255,252,248,191,204,159,221,240,30,119,234,31,168,148,111,234,189,234,254,243,191,254,235,143,144,209,87,119,107, +210,145,190,159,254,233,143,230,63,81,157,190,117,111,96,239,205,109,19,189,205,99,33,254,100,153,185,30,116,102, +194,119,208,39,13,166,221,165,199,103,144,169,154,46,211,13,98,238,29,201,186,212,73,169,172,29,131,70,229,77, +245,35,204,6,206,90,213,97,86,204,129,124,133,102,238,53,190,122,250,80,165,214,19,215,60,178,177,107,93,210, +185,255,9,252,133,211,189,214,33,159,87,51,101,25,27,178,203,158,248,0,49,140,165,238,102,212,80,125,36,176, +128,75,132,213,127,103,162,10,234,173,180,70,161,21,168,137,246,248,183,43,168,107,194,125,71,123,192,137,76,64, +86,244,180,22,44,185,182,218,231,75,102,78,4,211,243,63,43,136,243,250,170,176,0,213,147,102,118,42,221,136, +214,250,109,126,150,181,224,74,244,156,222,24,105,109,23,252,155,28,144,25,39,87,170,199,184,238,232,169,37,48, +99,34,51,131,23,35,144,190,170,237,227,114,252,68,123,139,3,212,70,23,2,208,143,115,241,44,220,123,63,147, +189,247,66,165,243,234,169,28,140,129,193,204,102,97,24,84,63,118,12,198,83,159,251,206,5,64,1,202,97,133, +50,188,165,170,227,167,186,13,42,187,181,232,149,89,244,154,72,78,68,31,63,193,25,244,125,107,251,51,59,61, +85,6,99,6,158,104,109,127,160,226,246,156,251,36,39,164,21,181,27,94,79,235,66,214,196,57,164,2,120,16, +251,251,193,190,118,237,174,168,208,33,91,142,4,254,135,210,42,167,86,218,128,25,90,24,219,2,127,19,115,214, +192,132,156,85,88,77,39,0,103,185,7,56,114,59,85,122,218,120,184,107,207,78,156,234,75,137,144,13,93,30, +170,188,216,103,75,85,16,200,165,186,2,171,152,186,158,167,88,11,28,6,81,170,78,161,104,92,104,67,150,49, +233,22,31,181,72,135,144,52,4,110,0,191,25,67,31,243,106,244,109,112,71,194,217,131,171,11,53,92,35,117, +79,230,56,235,56,133,110,214,144,29,26,212,181,42,251,250,56,10,155,165,147,213,183,91,217,183,177,35,179,40, +19,156,28,132,65,101,28,231,250,165,249,88,225,62,246,57,164,201,126,24,158,240,214,2,8,126,193,145,3,63, +29,103,219,150,121,139,150,48,110,39,28,65,125,250,30,99,123,140,108,43,121,141,195,230,8,235,190,25,118,214, +18,4,213,57,206,227,54,233,44,106,96,104,13,136,191,43,84,254,181,31,80,95,112,199,193,203,78,134,150,222, +103,199,23,214,26,224,49,71,55,199,24,142,231,64,59,123,202,251,5,209,22,141,114,18,74,77,20,199,123,125, +241,72,249,216,126,88,220,12,65,250,21,24,26,162,66,160,83,61,165,238,253,81,155,227,16,144,50,144,251,50, +87,132,124,63,241,182,28,59,103,187,228,22,200,14,175,117,219,139,252,216,69,60,200,42,31,87,179,144,179,172, +220,90,28,3,8,0,187,133,96,96,168,29,164,132,28,98,45,229,16,211,3,114,136,126,215,20,81,22,239,142, +106,207,100,207,136,77,215,215,42,105,148,141,141,213,81,42,51,166,55,152,177,18,106,213,202,198,198,93,100,223, +174,48,189,96,94,135,99,124,187,62,196,42,114,146,121,71,94,50,15,221,200,177,74,13,208,56,153,193,20,59, +155,19,168,11,171,26,190,36,144,80,242,39,81,19,251,158,53,49,48,193,167,225,32,132,41,71,24,184,142,98, +68,72,21,100,174,124,102,248,92,84,152,25,54,235,48,235,244,212,139,43,202,185,168,224,20,240,47,20,97,236, +13,100,175,226,110,48,204,225,34,187,212,183,176,160,208,191,171,141,19,10,53,135,189,123,151,46,233,155,207,191, +120,214,75,242,88,48,88,135,54,26,241,175,42,251,104,55,182,74,185,134,160,165,236,62,90,217,231,202,88,240, +195,156,104,99,149,87,69,135,85,89,28,28,22,139,102,186,103,18,24,231,152,32,102,19,186,238,39,187,39,229, +9,239,26,56,32,63,225,61,129,79,76,108,196,48,189,20,173,171,75,213,65,106,51,213,235,2,215,79,213,30, +141,11,220,234,246,23,32,173,116,219,86,111,222,231,155,69,91,69,6,107,65,52,171,124,149,190,194,62,92,127, +150,90,147,81,40,219,158,252,141,89,77,41,119,78,221,81,79,28,239,161,116,224,89,80,32,216,45,12,61,197, +217,67,34,122,178,176,104,43,1,26,35,164,114,93,136,13,237,21,177,177,252,8,111,13,48,21,178,173,121,197, +189,28,118,32,238,68,5,120,104,89,16,61,177,206,244,85,53,191,83,219,7,177,199,80,174,210,51,22,16,60, +99,229,178,162,108,201,194,232,211,89,147,24,29,29,46,3,107,75,111,61,170,58,88,177,4,248,97,11,165,174, +146,82,71,213,236,35,10,63,62,143,241,81,201,171,90,186,132,39,127,64,43,16,199,143,255,52,55,186,68,73, +8,84,18,80,37,126,151,244,61,56,129,89,192,208,10,28,150,106,149,34,1,144,156,62,138,204,244,116,138,175, +11,126,107,12,247,160,71,253,163,234,87,106,207,182,102,99,119,106,173,43,134,97,128,219,202,195,155,81,229,33, +72,53,164,133,241,121,84,5,149,186,83,82,188,240,55,143,69,37,142,90,50,34,164,191,51,164,36,95,208,77, +68,173,129,50,32,4,182,210,70,45,213,22,169,233,12,0,96,165,167,173,199,192,43,113,71,65,142,212,32,49, +91,208,161,150,224,20,90,138,187,84,6,41,238,83,243,167,135,233,158,89,86,51,157,60,72,203,37,216,254,42, +241,171,54,112,175,169,30,172,210,250,1,140,171,6,127,231,121,187,126,224,6,244,160,90,65,198,20,50,78,128, +204,11,30,13,228,41,227,141,140,57,11,216,252,70,42,177,25,81,140,75,189,34,191,8,75,144,14,89,226,24, +144,90,93,170,20,23,127,59,157,198,166,66,39,189,106,227,240,11,20,32,33,85,97,98,197,159,115,91,4,76, +56,135,124,25,37,47,190,235,152,157,200,161,4,162,126,122,53,187,180,175,197,87,250,233,244,42,190,223,176,136, +164,30,228,86,69,64,241,106,177,206,186,163,165,206,22,134,137,179,168,85,131,164,56,145,105,3,162,142,200,61, +172,23,14,63,28,156,141,126,193,4,198,221,118,81,9,102,177,236,144,80,76,117,64,105,170,208,3,244,164,50, +116,208,80,227,237,96,173,11,252,42,206,1,168,208,2,143,90,98,4,160,160,21,252,111,145,168,218,246,231,36, +12,165,1,116,191,92,172,147,44,70,81,187,138,97,194,177,169,220,60,109,22,127,141,54,234,108,171,194,171,123, +178,29,149,16,96,60,137,227,37,120,177,24,211,95,62,12,128,205,116,106,226,70,183,216,83,246,8,193,157,27, +153,186,134,175,81,6,137,81,143,180,238,213,133,147,175,186,99,111,176,163,50,251,18,122,60,14,83,229,97,108, +43,207,208,113,213,59,166,192,166,248,182,124,108,209,223,61,162,248,247,181,237,139,60,114,21,206,136,114,11,93, +225,71,227,242,225,216,184,61,2,68,196,48,104,120,160,73,161,194,99,95,143,153,221,58,54,54,217,82,24,209, +179,92,24,108,116,92,153,24,137,79,241,26,172,106,45,158,127,85,122,159,242,36,241,71,220,3,232,117,82,62, +172,166,192,53,81,107,248,170,225,171,234,186,61,12,127,112,144,76,218,237,237,187,6,55,17,157,34,231,255,252, +103,121,44,120,155,238,228,29,223,227,39,158,150,0,22,196,30,53,33,44,93,247,215,160,26,104,1,142,77,104, +144,153,122,84,31,95,75,197,85,8,175,227,217,169,119,59,10,186,103,105,114,218,237,99,111,220,151,3,55,197, +100,45,16,5,5,112,87,227,161,229,118,233,208,95,113,160,100,160,159,217,26,248,120,36,58,83,24,180,76,8, +85,99,217,225,157,211,147,181,190,182,97,203,26,137,218,129,29,195,103,69,241,29,94,79,237,147,176,189,134,198, +2,143,52,67,60,2,3,194,42,178,20,125,136,152,143,170,46,158,31,234,16,69,248,33,176,106,207,128,168,31, +247,248,124,92,4,169,11,156,231,143,158,80,70,155,119,143,209,178,98,220,173,28,199,41,7,49,236,132,30,46, +197,162,77,246,71,38,2,30,133,190,2,248,181,103,238,63,86,232,211,224,165,125,232,193,141,50,153,191,148,15, +60,108,191,192,82,12,202,113,61,208,20,48,102,78,152,141,129,196,18,238,35,252,173,177,4,126,244,105,41,166, +208,146,137,216,102,19,213,219,145,88,109,231,159,112,136,88,73,38,120,63,153,236,105,6,157,118,61,93,97,111, +220,26,151,132,200,118,157,7,101,251,74,217,7,124,179,64,41,153,28,88,127,104,148,27,133,101,32,208,112,32, +146,12,195,87,45,84,56,129,248,137,106,241,121,27,190,146,202,142,56,237,168,6,27,87,219,184,162,163,138,226, +196,215,143,209,161,1,12,141,182,128,9,220,4,85,84,41,172,149,155,224,184,26,227,124,19,254,37,180,158,85, +14,50,178,69,58,141,80,189,1,14,130,25,200,253,103,73,202,188,185,183,65,241,246,229,125,188,185,22,167,54, +200,131,194,122,125,87,57,158,153,204,66,50,136,28,47,110,78,114,191,120,228,23,118,19,154,55,195,210,86,57, +223,215,198,223,116,69,127,85,85,239,183,27,202,133,39,110,175,27,176,194,70,249,195,109,191,59,34,29,213,245, +100,89,207,50,147,119,188,110,191,69,238,126,143,119,64,189,11,254,151,131,48,8,186,133,117,124,85,149,6,170, +167,126,146,18,139,53,179,44,214,236,169,54,104,134,6,253,180,176,170,116,102,109,98,186,123,214,99,111,218,139, +22,219,208,98,159,116,188,246,37,5,79,187,115,209,86,51,108,107,13,248,181,158,158,2,39,14,63,102,240,145, +225,199,185,146,166,157,214,83,116,107,136,70,213,225,194,82,217,174,216,54,50,106,163,134,87,9,224,245,8,166, +111,55,190,128,163,231,11,16,145,136,17,239,194,132,70,31,186,109,168,242,14,118,17,215,18,12,79,149,252,181, +104,135,88,220,184,243,161,140,227,164,213,198,101,196,105,220,235,174,110,85,123,39,153,25,1,72,9,48,83,109, +31,170,226,71,67,24,252,223,165,246,6,189,41,31,14,155,159,246,96,158,44,210,191,45,61,86,111,76,157,155, +102,50,135,40,143,219,5,158,39,175,4,102,121,143,145,218,207,216,108,223,27,212,200,77,54,185,178,34,1,54, +248,109,169,94,5,85,78,27,247,170,84,175,133,236,128,141,252,212,40,92,97,27,120,201,1,234,158,141,122,91, +122,91,182,208,251,179,235,76,253,109,13,6,116,149,89,158,7,140,143,102,94,25,16,21,32,173,28,120,38,200, +7,220,117,65,173,5,245,89,188,52,228,188,153,194,5,180,212,63,33,255,2,223,125,188,217,228,12,0,186,213, +175,162,73,150,150,31,210,102,18,171,151,101,68,18,145,255,79,226,174,187,185,113,28,217,255,191,159,130,167,189, +245,74,239,64,141,168,100,91,30,206,212,198,139,19,106,243,174,202,53,11,145,148,201,51,211,144,138,214,249,62, +251,251,53,26,132,72,90,158,240,226,4,17,177,209,104,52,186,27,13,16,20,73,87,125,249,244,21,30,43,4, +68,52,159,94,147,236,16,57,82,176,109,212,59,112,81,55,201,32,125,230,37,33,209,67,118,52,91,163,60,253, +248,176,117,15,80,46,175,81,141,171,19,33,49,52,199,157,156,210,175,116,215,77,212,47,130,155,168,164,243,167, +253,62,134,78,57,75,217,207,142,174,29,253,236,250,126,104,132,43,39,252,253,181,216,191,144,197,173,86,205,251, +239,0,7,20,164,28,200,48,193,247,196,151,124,53,71,71,165,152,111,93,184,7,46,146,243,215,20,102,5,228, +28,37,72,181,198,34,73,231,38,18,189,156,107,19,160,67,176,240,208,0,16,210,53,59,160,233,50,237,118,233, +40,143,84,219,37,55,81,55,174,218,22,220,13,97,218,21,85,131,124,8,229,171,130,107,210,43,32,93,201,86, +90,42,36,127,70,164,68,64,87,116,51,132,117,85,183,64,88,127,174,164,71,64,94,107,32,88,47,3,68,64, +103,173,246,221,158,238,202,125,109,35,32,236,46,123,135,191,71,243,229,243,142,190,252,24,135,35,72,38,118,174, +25,66,236,130,225,186,116,29,34,176,51,55,177,255,241,143,116,0,109,137,252,160,123,33,86,196,77,248,199,111, +174,6,17,96,66,145,51,153,162,212,194,154,60,160,123,161,220,165,234,77,143,41,103,114,70,162,68,14,37,245, +12,49,77,230,88,100,200,212,169,61,67,98,147,63,17,5,242,117,106,15,237,187,104,30,72,204,49,92,180,32, +87,38,79,120,173,173,128,204,55,86,192,203,213,105,43,0,159,54,90,85,95,54,41,125,145,250,226,110,37,152, +187,134,66,141,224,200,12,221,216,12,221,164,241,34,83,81,159,147,87,102,95,146,174,254,215,92,208,89,72,156, +62,97,53,90,214,39,107,134,201,154,61,77,171,201,154,209,100,45,137,188,144,149,52,209,140,13,172,248,42,243, +187,7,250,214,65,57,43,49,232,106,6,255,5,216,99,120,32,185,228,13,125,72,69,205,94,114,126,31,190,65, +142,160,144,8,104,155,87,228,72,134,114,172,68,179,116,139,51,231,57,38,112,42,230,223,20,221,76,57,108,83, +53,147,103,64,16,64,49,228,93,73,179,154,190,214,72,155,132,81,171,45,145,49,108,148,201,168,140,60,157,239, +220,11,159,178,191,83,8,53,100,128,52,50,160,197,106,169,98,181,193,67,86,131,138,171,15,251,170,26,234,248, +227,134,90,250,162,224,161,110,140,100,154,153,145,124,32,38,253,104,3,25,121,131,62,116,84,147,244,137,9,123, +104,133,248,191,184,177,23,208,127,246,104,48,176,148,217,17,248,246,18,51,207,146,139,50,139,215,43,242,93,230, +246,176,63,177,248,69,114,132,58,106,160,212,86,251,43,60,88,238,250,93,80,34,96,169,217,16,149,89,93,232, +19,95,245,83,153,192,67,218,17,233,3,60,203,92,166,29,178,45,126,235,6,45,124,233,176,160,125,83,200,189, +61,6,166,116,110,203,102,43,162,115,66,232,191,208,202,32,231,244,236,204,193,4,132,25,213,133,88,170,97,0, +231,166,175,116,11,138,26,169,223,194,223,171,79,16,61,77,141,53,41,150,228,81,22,185,72,132,249,64,188,75, +122,13,38,79,134,65,219,112,135,171,207,42,209,76,130,77,73,20,49,23,117,242,199,128,204,228,224,233,27,205, +135,215,102,234,130,191,202,28,129,104,163,206,233,37,50,74,87,248,255,69,153,67,237,127,39,81,8,75,43,51, +189,129,42,221,62,111,142,235,241,178,139,142,18,183,217,97,139,75,70,250,158,178,237,127,197,111,122,204,41,143, +193,204,253,10,195,178,185,65,176,160,32,5,36,5,114,216,169,8,199,20,46,128,8,194,94,45,28,214,202,44, +107,233,107,213,86,126,108,96,115,118,182,81,88,36,42,103,113,204,121,115,12,66,82,236,26,146,2,188,33,33, +181,193,23,47,6,214,32,28,142,55,195,241,95,6,119,138,201,99,209,161,99,152,200,243,214,69,129,210,124,228, +78,103,233,207,166,32,183,63,173,210,118,136,57,195,42,70,121,99,29,49,95,98,25,85,9,230,147,44,142,73, +42,84,253,254,68,197,189,199,91,247,78,180,238,113,235,231,85,132,178,46,57,210,110,220,171,53,126,193,41,173, +182,67,77,146,137,229,92,134,206,68,58,150,99,129,60,206,192,26,254,101,108,98,54,254,254,84,69,29,103,104, +13,54,206,228,142,1,60,142,252,242,29,89,39,250,181,212,84,61,175,98,123,138,57,28,107,247,108,89,235,153, +169,222,232,89,97,90,79,179,52,168,39,217,197,58,14,144,78,95,124,202,124,95,101,101,166,129,225,128,227,6, +190,73,216,68,1,14,111,83,27,160,137,53,28,227,95,85,119,151,196,202,70,10,87,171,124,246,228,201,118,187, +237,111,71,253,172,184,121,50,28,12,6,79,104,54,80,201,210,8,39,223,253,125,25,7,59,139,126,108,47,139, +173,136,94,6,177,249,180,168,245,207,117,73,95,190,170,162,91,27,227,17,210,207,31,15,209,124,116,125,111,196, +110,124,243,59,193,205,143,66,175,1,213,0,82,78,107,235,70,230,246,64,147,39,61,85,165,200,182,77,68,168, +6,119,242,205,177,124,168,164,189,74,93,188,175,97,200,21,139,203,91,69,16,75,250,186,92,91,173,160,184,77, +82,220,6,95,37,254,172,30,141,111,142,209,169,53,220,197,199,232,216,2,178,58,136,22,230,163,241,32,223,93, +91,185,61,177,26,152,112,23,72,103,109,67,116,205,226,35,181,250,193,218,193,129,118,200,54,65,177,140,1,49, +140,124,63,72,13,129,119,71,188,89,107,236,197,23,208,26,120,172,16,96,129,152,144,234,64,15,89,137,4,252, +72,233,1,217,79,143,146,21,12,180,0,61,10,33,249,17,243,195,227,71,200,143,37,215,91,243,35,103,105,135, +38,242,90,19,9,63,22,244,128,18,233,9,24,63,59,241,70,151,184,213,246,207,94,204,191,128,253,179,135,110, +121,190,133,61,179,117,149,130,97,1,206,88,7,88,65,163,11,221,109,223,239,58,72,116,25,66,247,15,183,255, +250,215,23,103,23,208,117,164,1,255,203,188,186,127,192,171,80,159,141,89,208,19,251,182,182,123,190,121,190,233, +231,138,206,179,238,198,133,234,219,247,4,203,251,35,29,128,247,6,120,111,24,239,13,227,173,109,209,239,201,226, +252,226,108,140,252,239,121,129,177,135,110,236,137,29,219,120,223,147,141,7,152,135,91,178,241,90,90,66,236,153, +126,100,227,81,25,121,50,159,109,60,100,239,89,249,51,27,248,93,61,88,20,248,14,21,27,70,65,104,108,191, +218,98,143,215,100,37,0,146,137,224,118,58,87,45,195,80,194,48,52,43,13,73,75,2,114,40,243,186,9,56, +82,49,189,50,114,187,250,221,10,149,230,71,197,106,127,6,227,26,251,198,94,216,77,121,1,114,240,100,25,40, +34,207,176,48,161,6,43,83,142,166,0,194,90,38,219,147,129,69,198,147,202,67,100,128,15,73,45,138,64,222, +94,105,71,194,177,58,207,160,225,233,234,148,199,213,239,235,88,97,96,104,197,148,153,181,110,218,223,189,145,187, +168,172,173,120,57,107,214,57,241,225,188,79,199,222,249,104,57,233,168,210,168,187,87,117,239,175,233,10,241,174, +67,212,132,192,46,217,167,69,231,217,20,97,116,142,3,162,161,175,61,60,230,218,48,171,76,234,229,199,153,212, +161,47,188,218,234,105,208,48,172,179,7,199,54,35,253,21,15,99,197,209,218,131,183,76,211,99,189,130,234,213, +119,92,81,2,231,75,111,97,120,86,54,36,167,108,144,66,214,100,101,14,99,127,125,22,137,101,84,148,43,246, +225,144,225,22,28,173,161,180,110,158,253,198,251,116,100,59,201,99,186,135,116,250,124,51,210,63,92,63,108,89, +180,183,70,27,213,229,71,84,87,21,130,199,42,112,153,199,228,182,145,235,70,96,107,108,202,196,34,185,159,239, +236,105,71,59,146,21,121,220,128,164,56,140,112,72,113,60,2,4,32,69,131,74,94,103,28,43,248,33,233,33, +133,199,177,144,150,7,92,117,229,226,65,156,92,146,128,52,195,84,173,15,50,200,125,206,143,41,223,12,90,149, +239,137,24,176,124,242,78,44,73,132,4,13,81,225,55,157,94,224,16,101,241,191,144,185,40,213,234,192,156,8, +43,220,103,133,106,217,172,176,249,252,239,209,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,228,66,176,5,63,104,192,167,250,36,159,175,26,106,253,161,22, +30,153,241,166,33,0,55,99,8,240,88,33,208,186,186,203,192,143,25,126,124,109,148,44,141,1,42,205,37,84, +155,36,10,131,250,5,127,117,240,109,68,67,134,137,226,136,2,29,77,1,249,7,58,112,171,234,137,44,107,251, +236,10,172,195,89,128,155,230,37,154,151,199,230,37,55,47,175,33,211,27,67,180,110,72,243,114,69,155,90,41, +123,212,218,34,188,36,17,174,74,144,12,47,43,25,94,246,85,154,18,68,149,8,186,249,56,17,180,246,133,207, +34,72,129,106,201,32,249,65,50,232,252,161,16,138,63,164,226,112,240,176,162,247,94,119,2,189,40,144,6,197, +95,126,120,241,15,247,247,167,176,138,45,58,181,135,186,46,204,246,97,199,82,102,180,251,46,43,218,210,70,184, +171,108,240,209,96,98,93,14,58,22,145,15,245,182,246,252,130,172,64,50,35,17,118,28,138,116,158,61,45,233, +197,188,103,79,159,232,39,173,49,45,56,204,19,124,119,213,114,250,151,225,104,242,214,57,239,95,146,81,127,73, +73,195,254,24,63,244,28,225,7,173,244,207,145,231,92,244,71,182,142,218,14,234,169,2,118,85,101,20,218,163, +201,93,50,28,246,167,214,116,220,191,8,9,204,91,2,234,140,81,97,130,242,147,254,212,158,170,95,103,130,74, +3,251,178,127,105,87,81,4,206,109,4,199,244,8,109,170,125,151,56,163,115,128,27,58,4,125,56,234,159,199, +54,126,40,111,130,248,133,138,35,159,227,200,191,140,145,56,182,47,166,125,39,28,78,251,23,119,137,61,118,0, +122,58,160,162,23,4,234,18,160,208,217,209,93,114,62,5,74,83,84,125,107,59,128,2,116,8,119,155,80,185, +164,95,52,195,168,77,99,116,253,18,93,126,11,220,44,244,28,139,69,170,203,161,65,159,22,133,244,176,185,83, +35,234,11,53,218,31,218,232,132,61,160,186,32,53,50,70,0,66,1,164,58,192,30,253,7,65,144,61,69,14, +61,9,49,64,0,145,47,250,19,219,1,174,8,76,237,243,254,152,104,48,86,1,2,133,6,145,57,180,38,200, +30,131,160,14,145,224,66,17,58,38,200,19,85,234,173,61,166,22,168,119,28,0,129,0,29,244,66,120,168,127, +199,10,125,170,61,66,24,191,4,76,117,14,145,49,32,1,55,11,97,238,40,32,225,255,5,213,69,211,84,227, +28,255,167,168,59,69,234,57,254,15,129,22,165,208,211,81,148,25,82,71,129,47,58,14,50,162,208,80,13,37, +5,238,146,11,112,6,70,107,99,143,136,17,135,248,217,168,209,179,41,136,212,145,10,143,251,163,141,14,14,55, +60,188,8,80,149,187,14,248,154,24,26,15,204,143,103,214,39,150,245,20,147,173,154,18,133,244,2,123,177,167, +82,72,125,246,123,115,233,213,112,216,13,44,122,245,20,15,242,218,209,83,95,234,56,168,84,112,200,143,15,95, +11,144,208,119,106,58,250,14,134,226,71,121,2,243,182,39,205,8,32,222,120,48,62,48,45,1,73,57,146,223, +234,93,114,231,243,26,121,222,111,155,176,219,112,14,179,6,130,228,123,52,18,161,135,94,201,212,252,156,253,96, +228,107,74,219,190,166,213,187,237,25,99,164,52,157,148,235,28,66,158,172,116,43,135,85,221,57,186,183,95,225, +177,66,64,80,32,160,192,55,104,148,29,222,165,113,120,23,198,221,77,107,160,66,169,71,201,138,198,213,46,239, +150,163,187,228,69,80,218,118,100,151,117,71,247,201,124,227,232,206,244,34,72,61,3,181,244,73,91,142,239,228, +29,22,13,245,129,146,164,73,162,189,168,104,62,190,54,119,111,174,221,103,107,210,56,253,200,55,154,154,142,172, +174,159,122,149,166,94,27,67,39,119,149,2,243,196,154,28,132,97,55,175,12,157,4,134,206,250,218,13,51,132, +114,216,2,84,120,137,134,38,85,67,62,55,228,156,159,104,104,121,170,33,73,13,45,185,33,159,26,146,186,161, +130,26,90,114,67,239,180,168,12,252,160,1,31,136,178,69,85,42,254,202,30,171,85,52,106,161,213,19,118,24, +189,220,107,209,15,77,215,146,100,43,29,181,33,86,44,237,68,238,44,73,47,244,215,28,68,236,30,202,62,24, +0,87,98,86,93,139,28,172,138,199,10,1,131,107,2,92,147,99,15,19,238,97,82,179,233,174,168,78,137,58, +130,2,217,195,202,69,163,114,193,149,51,93,57,102,254,231,214,243,51,103,76,75,127,207,93,131,133,68,20,129, +126,218,52,204,69,40,28,177,22,158,50,13,255,146,138,144,33,136,24,12,157,162,32,188,204,103,163,33,42,47, +81,121,162,43,23,84,185,64,101,159,42,243,61,27,84,121,169,43,203,170,50,205,168,53,191,181,24,31,95,143, +204,129,127,126,228,211,156,240,199,132,11,230,57,182,187,154,133,150,237,66,5,21,226,238,209,84,92,183,161,6, +141,10,242,52,212,162,85,200,64,229,9,12,160,107,109,4,191,11,56,65,38,35,248,138,10,211,250,81,61,179, +119,55,70,45,181,45,231,176,181,196,109,137,244,92,54,183,53,86,243,33,139,116,72,160,44,237,118,216,217,209, +17,171,249,57,164,217,99,235,222,69,116,82,42,183,87,129,70,178,190,75,160,174,220,236,170,41,84,167,36,85, +217,179,4,244,254,55,165,234,105,105,186,124,55,9,151,126,155,132,206,57,72,248,255,68,173,209,240,72,45,194, +228,255,156,90,139,199,119,227,220,63,240,198,155,151,129,177,153,120,191,20,134,120,161,44,191,215,86,197,244,90, +160,83,105,224,173,2,159,119,218,244,142,6,142,74,33,62,170,115,168,23,210,49,157,142,136,230,23,215,61,163, +194,230,137,47,114,159,14,244,207,107,199,91,124,150,89,122,248,214,0,68,94,80,242,69,211,50,167,131,11,225, +171,180,210,152,30,29,124,230,195,118,180,82,249,119,183,112,169,127,61,34,178,187,164,133,124,136,31,74,97,35, +200,67,239,148,239,116,165,52,137,114,9,209,118,96,231,212,136,183,181,141,160,3,22,168,205,10,225,244,202,158, +3,101,88,68,233,45,171,2,90,125,25,119,250,222,46,61,58,190,219,72,220,85,94,246,220,190,32,235,112,254, +233,242,130,254,94,191,219,33,196,13,18,36,99,76,209,151,108,67,106,34,8,210,150,153,138,98,208,78,199,220, +59,152,162,70,75,105,194,112,88,104,141,197,129,0,1,230,238,192,184,220,3,81,242,3,92,247,111,248,12,136, +208,71,5,36,142,10,136,68,235,97,77,30,119,143,116,16,187,167,61,118,79,207,60,240,185,231,18,179,9,30, +19,66,0,103,129,37,113,210,203,204,167,195,103,21,199,36,52,123,242,179,41,169,178,164,95,177,162,187,6,43, +146,150,226,100,195,147,72,31,170,244,11,78,55,204,137,140,209,113,198,37,252,90,230,194,45,174,136,109,214,124, +71,195,226,185,233,18,235,208,89,87,2,140,82,126,152,118,203,249,226,90,56,248,171,252,168,20,227,99,54,172, +245,136,30,207,53,235,9,249,92,26,16,117,110,68,67,96,35,42,140,105,46,133,67,81,67,189,222,76,42,128, +149,254,140,79,73,131,53,87,101,82,247,180,50,148,39,74,73,46,101,148,27,143,51,133,132,86,115,244,52,178, +226,56,156,109,61,245,198,111,156,53,214,78,122,250,41,106,51,84,72,50,93,99,250,241,232,39,228,211,84,75, +248,34,74,120,48,58,127,242,51,111,77,184,169,239,91,16,224,126,152,149,171,63,117,158,248,178,12,183,101,71, +157,200,243,143,18,97,141,57,203,206,113,242,85,155,228,28,201,45,151,246,209,215,61,12,70,61,193,149,40,234, +195,8,77,253,238,223,190,127,245,82,95,95,132,37,89,87,221,78,34,83,127,214,193,113,196,89,172,94,245,238, +220,195,106,145,229,62,245,172,163,169,222,221,66,138,23,251,3,33,182,185,82,33,151,96,233,155,61,182,122,239, +1,189,241,66,218,3,105,124,76,144,68,40,189,111,44,58,127,77,55,50,134,225,72,85,173,36,40,75,73,66, +145,107,139,125,229,73,186,223,244,53,94,36,228,88,185,107,228,102,234,26,46,250,140,125,119,172,40,76,125,156, +8,79,135,6,138,244,224,158,119,64,72,177,182,2,128,13,175,128,192,211,58,100,174,164,218,211,33,50,125,72, +54,154,237,251,145,184,197,239,173,216,224,119,163,223,62,234,116,40,2,215,154,110,21,92,140,38,101,225,51,64, +10,53,1,198,21,64,159,32,250,234,88,21,66,48,117,101,66,129,84,240,21,12,237,38,68,185,79,22,89,140, +96,89,79,231,47,27,83,0,15,157,225,232,40,157,182,71,80,127,102,16,33,252,50,166,68,51,198,52,148,5, +247,157,131,77,92,189,247,226,202,59,51,8,238,196,190,10,238,185,13,26,141,222,35,35,128,148,60,75,193,248, +101,125,0,78,208,223,152,180,95,192,128,252,226,169,172,12,200,47,240,234,33,76,105,137,61,204,126,4,192,64, +175,119,192,216,252,161,186,217,128,29,169,42,255,214,69,6,57,235,65,197,83,249,27,23,25,200,231,77,44,210, +213,102,4,57,240,46,140,226,22,70,177,194,200,103,148,124,224,212,106,115,44,84,9,53,198,170,221,152,240,42, +79,149,225,1,119,145,75,133,120,72,79,150,163,145,231,108,46,136,1,63,85,14,201,174,202,212,165,228,238,84, +41,36,187,42,83,196,117,138,24,78,225,208,59,71,201,107,209,196,123,64,147,102,203,19,161,74,48,47,185,200, +21,30,33,184,63,85,104,95,21,218,115,33,142,191,81,251,129,143,151,231,124,183,89,92,120,166,131,109,54,5, +115,222,208,21,68,71,177,25,66,81,196,129,44,42,25,27,34,201,109,111,35,250,124,107,37,84,133,152,66,234, +190,95,12,102,169,145,125,141,219,212,23,221,237,59,234,4,244,78,48,164,165,17,235,80,153,166,233,154,142,130, +86,240,93,170,248,115,176,248,62,243,110,3,168,107,42,153,165,170,172,187,86,225,44,15,82,55,71,208,32,227, +38,20,227,102,220,197,61,122,249,87,242,121,129,103,185,155,144,250,6,185,56,187,233,118,74,5,188,15,90,250, +123,178,68,208,29,191,22,235,53,98,160,175,193,167,255,213,63,94,125,255,205,215,144,226,166,35,192,186,55,123, +180,252,171,215,223,188,60,59,211,58,236,243,67,71,143,25,157,219,141,48,100,247,159,107,154,191,131,126,234,141, +106,43,74,143,84,177,182,84,37,80,36,197,82,122,18,140,42,59,107,231,110,209,223,211,42,19,202,42,88,201, +40,198,48,95,1,105,93,227,150,106,240,254,183,41,96,246,124,218,251,241,205,109,239,160,59,21,169,91,137,184, +103,100,200,204,165,94,144,240,137,94,28,7,172,54,131,118,31,183,25,244,198,23,139,227,17,79,34,198,14,235, +192,149,44,72,221,27,43,4,145,111,226,128,130,95,238,255,234,119,59,50,207,59,244,50,200,39,159,60,125,194, +247,196,61,251,196,194,31,222,50,177,212,185,12,117,142,242,137,7,75,252,217,39,255,33,102,124,87,140,152,201, +37,56,230,176,200,96,252,71,119,160,216,76,111,205,34,229,74,7,249,171,156,131,42,170,96,206,48,98,145,95, +37,241,45,133,159,6,147,224,60,88,220,183,96,219,246,106,139,18,233,10,232,206,44,220,0,30,174,146,248,64, +103,219,109,62,155,53,195,6,196,149,189,13,22,183,209,202,38,44,9,149,192,150,62,252,208,200,28,12,62,187, +178,147,236,206,94,201,133,202,153,141,175,236,172,30,171,5,149,159,119,41,147,40,222,207,214,145,93,202,180,180, +233,109,138,37,212,50,4,111,98,175,35,97,131,92,113,96,115,130,248,18,152,220,190,144,222,247,42,74,175,181, +138,239,131,155,44,176,126,252,171,248,46,131,243,28,110,162,32,222,4,88,180,73,235,101,176,14,196,23,69,36, +99,241,18,57,214,247,128,47,106,141,116,190,32,208,150,58,226,96,125,147,100,255,140,176,238,169,192,85,9,38, +254,189,82,28,162,67,160,26,117,238,23,153,191,63,36,24,245,40,5,225,235,180,138,210,16,45,173,238,195,226, +192,41,200,103,242,235,156,106,76,104,47,128,71,206,201,119,247,114,177,40,102,91,20,8,186,243,21,221,24,137, +5,78,131,228,126,224,101,5,191,24,188,78,81,159,218,180,252,108,133,101,201,213,251,10,220,135,142,8,135,34, +28,137,112,44,194,137,8,167,7,140,3,15,163,65,75,165,108,155,157,144,135,38,234,173,134,76,185,133,192,172, +129,168,63,212,129,128,116,64,227,222,163,21,215,45,38,77,41,147,156,62,206,124,104,241,64,2,209,89,230,210, +11,196,247,223,190,64,216,198,235,22,234,27,23,47,130,52,206,4,146,164,151,137,175,148,16,146,37,222,146,89, +4,220,188,133,44,202,88,23,81,80,96,232,183,194,128,186,58,246,207,9,146,251,50,145,113,92,235,243,197,224, +179,251,114,13,172,215,121,45,245,124,242,153,25,74,30,56,115,243,102,117,56,237,10,43,18,98,180,216,86,254, +220,217,66,223,200,74,208,14,250,14,105,187,63,156,80,155,128,141,49,70,148,98,234,205,162,131,162,95,148,250, +52,211,204,124,61,201,29,72,140,101,94,6,179,42,128,87,236,0,61,21,81,154,211,214,122,190,162,195,58,185, +64,243,88,154,10,2,44,33,234,235,180,53,32,77,15,121,174,158,224,214,22,135,26,198,174,238,215,28,84,173, +115,115,220,15,117,179,45,221,135,48,75,179,212,224,55,87,194,140,35,215,58,86,4,80,125,85,4,148,74,240, +138,184,225,110,76,246,64,2,148,23,204,184,214,213,241,40,146,166,141,106,137,215,239,245,204,40,129,142,229,198, +103,74,0,45,33,127,75,82,43,7,88,18,212,205,25,185,208,117,38,56,45,226,197,18,75,211,80,250,217,150, +107,231,69,118,3,28,203,195,99,163,59,155,85,216,170,13,46,187,132,146,180,117,135,143,121,104,180,153,167,37, +0,99,161,123,143,190,122,225,201,222,19,81,151,81,16,251,87,26,123,59,227,139,90,237,97,190,171,161,192,32, +106,211,240,20,48,166,138,169,179,140,32,80,215,121,156,73,95,227,246,14,250,19,191,152,169,93,174,19,176,195, +222,28,90,143,163,18,84,88,129,165,23,49,148,254,219,117,6,91,192,143,233,136,196,3,41,35,194,66,240,135, +34,5,207,252,138,177,238,85,63,209,183,195,9,86,227,99,242,135,99,2,68,240,58,22,208,166,235,3,181,174, +21,28,245,240,20,167,154,169,80,4,138,231,171,49,189,87,51,103,198,204,128,174,120,65,168,4,148,153,59,15, +179,14,250,164,221,204,185,210,26,244,210,147,35,185,100,80,40,94,218,42,116,26,156,201,254,8,144,179,211,136, +125,0,0,51,255,138,44,54,243,239,0,23,85,137,50,234,74,91,200,226,25,70,145,4,145,95,101,232,131,134, +247,81,114,35,176,175,45,54,145,31,100,130,223,221,19,114,237,71,153,136,150,133,76,2,17,36,139,192,23,153, +122,163,209,48,131,98,129,182,76,228,187,169,9,36,131,163,239,247,179,166,99,249,211,152,20,236,161,188,174,32, +50,223,178,249,211,54,81,204,77,218,246,110,102,13,174,90,105,123,147,198,87,105,155,104,121,27,192,21,218,140, +238,143,81,224,173,0,58,245,248,222,196,115,153,82,238,49,178,55,145,40,197,44,188,203,178,164,74,97,23,172, +93,166,50,183,201,188,245,86,41,132,202,204,130,120,217,69,9,198,140,139,101,5,56,85,198,166,22,148,90,24, +248,54,125,33,183,74,211,151,40,219,60,121,218,169,164,225,136,215,219,133,11,169,214,43,85,58,9,66,48,32, +102,89,35,133,133,138,30,16,11,71,119,30,230,49,99,89,159,46,151,203,99,102,149,90,220,44,186,19,28,180, +24,209,217,252,169,245,196,234,79,122,15,65,176,120,5,124,252,253,116,128,63,181,34,167,243,222,157,204,173,7, +126,59,155,206,206,2,41,29,166,35,21,154,232,87,198,172,45,100,105,40,64,39,0,212,16,87,9,225,58,48, +12,195,41,81,74,252,108,70,71,98,197,81,203,45,131,60,146,85,228,63,171,187,206,37,199,85,37,252,255,62, +133,110,94,85,217,28,129,130,131,110,126,137,155,188,63,38,120,66,213,120,147,119,54,169,244,238,135,15,26,104, +97,214,88,91,39,207,241,142,244,117,243,209,4,3,162,27,13,94,94,237,204,38,200,189,211,154,236,138,193,96, +96,44,138,45,245,130,216,100,47,137,109,231,34,42,68,12,211,216,17,227,161,148,177,128,138,59,10,23,194,50, +248,37,145,67,70,65,139,35,38,35,68,203,76,196,139,89,7,233,158,54,10,27,250,50,224,151,5,80,33,64, +240,155,32,23,15,67,235,41,11,130,71,237,68,107,168,68,167,218,119,251,131,75,13,156,8,72,66,250,86,121, +162,234,244,130,150,180,106,181,1,150,86,205,209,45,165,32,190,165,81,181,37,89,42,74,142,64,155,225,203,210, +254,57,50,85,1,144,1,144,26,176,139,185,39,221,174,131,185,220,110,244,127,163,96,135,47,6,231,242,127,62, +188,218,2,67,40,220,119,5,93,141,194,31,191,144,86,83,223,147,154,215,98,74,77,164,212,56,165,102,20,135, +101,235,166,91,95,172,195,39,15,46,209,40,78,210,19,102,171,129,171,99,216,158,164,0,48,85,7,162,117,81, +219,132,135,118,0,44,43,142,43,39,184,14,166,80,203,251,124,225,136,242,83,4,110,70,129,114,122,8,55,163, +176,115,201,116,42,17,214,77,229,86,98,152,128,128,237,254,47,43,221,175,118,47,189,0,183,144,40,66,200,94, +32,232,94,4,250,254,5,138,186,153,80,152,91,72,100,229,32,229,73,234,206,97,27,71,219,56,164,35,100,237, +147,17,224,211,72,226,225,126,184,1,55,193,250,15,15,163,248,104,203,26,230,90,13,105,59,215,198,76,130,113, +3,88,209,61,49,227,60,9,33,202,67,45,33,190,29,192,86,43,198,134,27,192,107,151,150,212,58,151,146,184, +152,247,114,96,215,91,57,10,123,181,172,38,120,69,73,238,117,79,94,8,252,59,248,123,36,242,207,30,131,191, +218,134,191,180,161,125,42,47,226,37,67,185,72,160,159,203,178,192,8,202,211,216,123,45,192,74,129,96,191,140, +32,248,191,17,14,30,51,70,255,135,4,97,101,225,36,255,141,36,72,51,10,187,12,91,210,250,44,94,174,9, +251,184,181,68,79,246,235,117,188,190,116,105,5,118,13,140,85,107,10,61,158,130,49,48,138,73,140,145,29,60, +60,228,158,30,32,114,27,68,125,164,194,68,163,224,209,75,150,75,63,39,188,65,77,211,0,119,212,51,195,155, +189,118,30,74,5,159,138,78,172,125,7,242,238,93,89,82,123,251,134,214,155,121,123,187,154,209,144,21,130,35, +22,90,218,81,240,144,194,193,134,90,25,104,107,161,81,76,78,232,13,116,231,119,189,192,106,69,65,19,207,32, +41,61,8,130,22,101,25,43,198,217,94,239,223,127,196,119,54,214,51,155,21,78,58,10,27,201,57,232,95,254, +91,8,168,53,136,20,42,64,141,133,252,125,181,19,86,75,72,166,5,42,154,121,113,187,182,204,94,172,108,138, +160,95,91,96,101,145,232,176,222,224,238,183,246,158,41,124,182,211,66,184,167,57,32,156,22,25,104,95,3,127, +252,228,249,232,179,224,231,218,35,21,204,148,48,60,156,107,139,57,166,20,234,36,19,57,145,191,127,55,132,157, +55,154,231,29,147,154,82,93,59,85,154,136,210,218,86,133,52,217,86,158,32,68,77,69,10,162,232,92,141,125, +176,33,208,47,206,228,116,103,8,11,110,213,212,133,106,26,90,113,211,72,18,167,44,75,159,1,219,49,25,38, +100,76,224,149,113,252,236,241,248,96,126,95,108,145,108,139,110,83,168,182,205,218,227,11,172,230,20,88,105,242, +90,234,79,46,3,138,122,38,230,123,198,26,109,36,17,51,76,166,207,132,249,62,98,221,253,127,71,241,50,187, +151,151,147,55,235,130,62,103,201,223,105,183,17,58,192,60,171,149,38,174,171,115,196,104,195,153,196,18,21,221, +232,234,104,206,18,223,99,205,49,143,89,85,154,85,161,25,207,215,197,254,118,22,47,44,85,170,195,231,44,239, +231,253,19,198,162,217,212,13,78,17,156,239,27,212,157,47,39,230,221,57,95,203,237,12,230,186,209,214,174,138, +77,147,53,120,22,173,172,112,4,69,115,171,117,190,191,213,115,136,155,149,49,88,181,245,5,173,55,203,102,133, +186,88,109,138,11,44,238,230,212,176,174,221,77,182,229,226,65,52,79,205,7,209,252,72,164,240,51,107,36,82, +5,125,50,118,235,212,23,179,86,5,126,50,223,230,153,141,182,41,186,53,254,63,199,234,143,60,39,136,69,59, +81,217,164,84,54,19,149,46,165,210,141,2,43,36,183,145,140,85,18,144,214,35,180,254,2,216,4,208,34,122, +52,112,80,120,216,237,9,113,143,179,130,146,127,10,233,233,161,155,43,211,35,183,231,197,194,110,66,221,169,36, +57,96,74,51,77,32,69,42,1,96,159,160,158,230,176,74,233,3,37,243,235,169,249,144,196,5,224,234,93,84, +90,17,171,179,39,92,227,89,162,69,53,174,105,79,217,34,36,62,30,152,211,78,172,109,238,83,127,181,98,108, +79,247,76,93,10,169,18,250,43,166,255,233,200,233,19,236,70,213,168,236,112,98,105,247,114,226,246,212,8,164, +254,45,72,19,97,227,133,199,253,225,17,30,210,137,184,131,216,31,78,138,221,108,94,128,231,95,253,93,210,181, +135,181,229,30,161,147,186,118,252,190,176,168,84,11,19,249,153,39,234,241,128,248,87,146,15,69,93,33,187,90, +15,160,126,48,138,19,148,37,113,154,213,75,155,231,196,138,85,83,110,154,44,35,70,205,60,35,223,120,206,82, +210,84,154,231,244,51,104,150,18,99,91,158,48,12,105,89,66,63,201,229,57,105,110,203,115,162,193,243,140,124, +134,207,114,210,10,59,67,200,23,214,57,66,76,105,221,53,126,118,47,47,49,148,62,231,120,237,62,60,30,53, +35,111,130,210,207,191,218,40,253,207,18,255,240,105,76,200,114,161,47,214,26,150,21,20,186,83,133,180,23,34, +166,37,187,34,205,4,123,90,175,103,46,105,210,72,249,83,22,193,251,81,46,38,122,231,20,72,230,235,40,174, +32,169,205,170,245,167,74,213,13,132,10,198,203,203,171,198,19,166,75,27,243,254,34,42,101,137,29,65,246,232, +68,168,119,133,201,187,246,110,51,117,84,17,85,92,3,37,109,79,26,87,136,5,143,3,67,112,238,101,255,78, +247,114,35,90,196,171,162,5,127,22,94,224,69,90,11,251,87,142,23,233,120,32,202,162,255,214,28,146,108,60, +223,31,158,121,126,137,244,75,119,209,158,46,36,115,123,243,124,173,61,158,215,251,47,143,120,187,107,179,168,22, +66,45,100,201,147,220,62,91,178,173,144,237,113,210,36,136,5,74,149,73,227,63,108,158,123,61,87,47,159,204, +203,122,135,51,196,86,99,20,116,136,92,247,191,231,71,10,152,43,36,134,153,158,144,55,123,124,191,164,104,142, +132,80,239,52,107,120,130,64,230,150,19,133,30,152,18,49,76,220,145,210,159,110,150,246,233,205,76,231,183,183, +189,62,202,170,236,237,38,23,9,201,252,178,159,110,200,233,125,239,155,23,233,244,122,64,81,101,31,237,48,246, +137,205,253,255,189,168,74,95,85,20,38,48,56,75,245,155,157,78,252,147,189,113,62,246,214,229,216,51,63,16, +153,108,46,227,142,204,10,66,99,145,155,154,132,244,185,83,112,2,203,60,205,62,183,22,220,183,194,254,9,104, +148,5,25,254,235,51,13,56,190,43,148,133,233,96,143,175,16,121,164,3,113,239,240,82,246,125,255,141,201,82, +142,148,37,204,47,243,181,51,254,195,217,172,207,62,154,120,145,99,97,51,31,170,63,14,95,103,30,223,191,78, +74,173,112,252,199,15,201,102,35,108,40,32,13,103,7,95,126,45,110,138,55,35,43,59,223,59,61,37,219,154, +224,51,31,120,6,158,132,82,8,226,122,122,52,251,180,15,207,135,235,172,29,122,221,64,221,9,151,81,119,162, +167,59,102,39,226,54,16,186,193,156,172,203,198,167,219,74,129,121,215,196,7,99,66,191,210,46,24,252,136,170, +157,204,184,152,151,205,7,218,102,242,172,42,121,213,71,46,170,124,17,223,61,191,50,127,102,98,105,158,89,200, +47,216,133,56,160,13,44,99,198,95,223,226,39,42,100,141,34,58,226,48,148,98,200,46,68,123,236,19,80,214, +208,7,140,54,57,115,153,101,166,174,218,205,66,214,213,66,53,221,66,108,202,100,241,15,199,184,168,145,45,188, +228,208,190,172,60,233,142,56,221,57,231,130,19,228,43,198,82,31,252,117,246,53,216,111,222,53,170,39,13,61, +20,95,212,143,190,246,221,77,179,205,108,123,34,192,163,250,15,103,14,177,205,55,7,158,97,115,243,43,111,100, +42,198,15,48,134,128,235,7,30,64,98,243,114,13,37,62,94,233,140,94,221,135,249,125,8,83,54,201,116,220, +126,161,142,126,114,246,67,95,78,145,79,153,65,3,211,166,15,27,29,91,118,39,106,76,154,65,22,77,228,223, +206,35,76,229,236,182,228,58,237,108,101,133,176,42,116,49,175,244,209,201,169,101,35,177,247,139,116,85,69,122, +151,251,93,204,166,214,234,140,119,132,103,114,245,249,27,242,88,181,197,186,133,139,224,210,44,218,217,165,104,224, +36,168,244,71,102,51,137,92,17,148,211,15,232,144,160,124,104,219,46,206,41,187,153,131,12,114,91,68,102,252, +219,109,93,24,60,214,66,209,242,42,12,56,108,130,139,226,230,161,52,254,227,176,191,125,188,42,94,32,88,138, +66,94,87,157,222,145,41,7,113,184,221,109,237,0,129,183,111,184,0,189,165,141,208,131,48,188,19,249,52,72, +15,251,143,246,130,116,47,143,232,131,122,34,160,13,112,50,130,13,2,254,138,133,1,55,211,192,47,168,32,54, +75,74,30,234,101,238,152,112,189,230,66,220,5,114,40,212,149,77,29,226,181,129,144,210,217,240,28,40,228,66, +116,160,115,97,212,12,84,79,222,98,49,4,132,234,137,20,227,119,96,240,48,20,139,140,169,110,32,43,213,152, +126,240,116,207,154,175,137,90,47,138,161,132,238,133,221,34,153,167,90,87,38,207,79,79,140,167,62,229,169,29, +79,157,230,105,235,206,240,236,106,53,161,74,116,62,223,247,212,72,234,151,150,118,196,113,69,251,50,71,188,238, +238,187,7,125,148,212,92,225,244,27,46,232,29,120,250,68,50,78,56,210,203,239,172,192,36,48,122,154,3,7, +10,255,246,155,239,1,247,22,16,137,182,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..803e84f --- /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[90104]; + +#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..e659a7c --- /dev/null +++ b/lib/MycilaAppInfo/MycilaAppInfo.cpp @@ -0,0 +1,83 @@ +// 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 "Pro (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_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), + debug(firmware.indexOf("-debug") >= 0), + trial(firmware.indexOf("-trial") >= 0) {} + +String Mycila::AppInfoClass::getDownloadableFirmware() const { + int i = firmware.indexOf("-debug"); + if (i >= 0) + return firmware.substring(0, i) + firmware.substring(i + 6); + i = firmware.indexOf("-trial"); + if (i >= 0) + return firmware.substring(0, i) + firmware.substring(i + 6); + return firmware; +} + +void Mycila::AppInfoClass::toJson(const JsonObject& root) const { + 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..ce1f7e0 --- /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(); + + String getDownloadableFirmware() const; + + 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 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..e62dc63 --- /dev/null +++ b/lib/MycilaButton/MycilaButton.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#define TAG "BUTTON" + +void Mycila::ButtonClass::begin() { + if (_enabled) + return; + + if (!ButtonConfig.isEnabled()) + return; + + int32_t pin = ButtonConfig.getPin(); + 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.info(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)); + _button->attachLongPressHoldEventCb( + +[](void* handle, void* data) { + Mycila::ButtonClass* self = (Mycila::ButtonClass*)data; + self->_lastEvent = ButtonEvent::BUTTON_LONG_PRESS_HOLD; + }, + this); + _button->attachSingleClickEventCb( + +[](void* handle, void* data) { + Mycila::ButtonClass* self = (Mycila::ButtonClass*)data; + self->_lastEvent = ButtonEvent::BUTTON_CLICKED; + }, + this); + + _enabled = true; +} + +void Mycila::ButtonClass::end() { + if (!_enabled) + return; + Logger.info(TAG, "Disable Push Button..."); + _enabled = false; + _button->del(); + delete _button; + _button = nullptr; + _pin = GPIO_NUM_NC; +} + +void Mycila::ButtonClass::loop() { + if (!_enabled) + return; + if (_lastEvent == ButtonEvent::BUTTON_CLICKED) { + _lastEvent = ButtonEvent::IDLE; + _callback(ButtonConfig.getPressAction()); + } else if (_lastEvent == ButtonEvent::BUTTON_LONG_PRESS_HOLD) { + _lastEvent = ButtonEvent::IDLE; + _callback(ButtonConfig.getLongPressAction()); + } +} + +void Mycila::ButtonClass::toJson(const JsonObject& root) const { + root["enabled"] = _enabled; +} + +namespace Mycila { + ButtonClass Button; + ButtonConfigClass ButtonConfig; +} // namespace Mycila diff --git a/lib/MycilaButton/MycilaButton.h b/lib/MycilaButton/MycilaButton.h new file mode 100644 index 0000000..249efed --- /dev/null +++ b/lib/MycilaButton/MycilaButton.h @@ -0,0 +1,53 @@ +// 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 ButtonConfigClass { + public: + int32_t getPin() const; + bool isEnabled() const; + String getPressAction() const; + String getLongPressAction() const; + }; + + class ButtonClass { + public: + ~ButtonClass() { end(); } + + void begin(); + void loop(); + void end(); + + 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; + volatile ButtonEvent _lastEvent = ButtonEvent::IDLE; + ButtonActionCallback _callback; + gpio_num_t _pin = GPIO_NUM_NC; + }; + + extern ButtonConfigClass ButtonConfig; + 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..11d2813 --- /dev/null +++ b/lib/MycilaBuzzer/MycilaBuzzer.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#define TAG "BUZZER" + +void Mycila::BuzzerClass::begin() { + if (_enabled) + return; + + if (!BuzzerConfig.isEnabled()) + return; + + int32_t pin = BuzzerConfig.getPin(); + 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.info(TAG, "Enable Buzzer..."); + Logger.debug(TAG, "- Pin: %u", _pin); + + _enabled = true; +} + +void Mycila::BuzzerClass::end() { + if (_enabled) { + Logger.info(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; + BuzzerConfigClass BuzzerConfig; +} // namespace Mycila diff --git a/lib/MycilaBuzzer/MycilaBuzzer.h b/lib/MycilaBuzzer/MycilaBuzzer.h new file mode 100644 index 0000000..1f1685a --- /dev/null +++ b/lib/MycilaBuzzer/MycilaBuzzer.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include + +namespace Mycila { + class BuzzerConfigClass { + public: + int32_t getPin() const; + bool isEnabled() const; + }; + + class BuzzerClass { + public: + void begin(); + void end(); + + void toJson(const JsonObject& root) const; + + void beep(uint8_t count = 1, uint32_t duration = 150) 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 BuzzerConfigClass BuzzerConfig; + 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..8ff1a8c --- /dev/null +++ b/lib/MycilaDimmer/MycilaDimmer.cpp @@ -0,0 +1,79 @@ +// 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() { + if (_enabled) + return; + + if (!DimmerConfig.isEnabled(name)) { + Logger.warn(TAG, "Disable Dimmer '%s'", name); + return; + } + + int32_t pin = DimmerConfig.getPin(name); + 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.info(TAG, "Enable Dimmer '%s'...", name); + Logger.debug(TAG, "- Pin: %u", _pin); + Logger.debug(TAG, "- Type: %s", DimmerTypeNames[static_cast(DimmerConfig.getType(name))]); + + _dimmer = new DimmableLightLinearized(_pin); + _level = 0; + _dimmer->setBrightness(0); + _enabled = true; +} + +void Mycila::Dimmer::end() { + if (_enabled) { + Logger.info(TAG, "Disable Dimmer '%s'", name); + _enabled = false; + _level = 0; + _dimmer->setBrightness(0); + delete _dimmer; + _dimmer = nullptr; + _pin = GPIO_NUM_NC; + } +} + +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"; +} + +namespace Mycila { + DimmerConfigClass DimmerConfig; +} // namespace Mycila diff --git a/lib/MycilaDimmer/MycilaDimmer.h b/lib/MycilaDimmer/MycilaDimmer.h new file mode 100644 index 0000000..6509e32 --- /dev/null +++ b/lib/MycilaDimmer/MycilaDimmer.h @@ -0,0 +1,67 @@ +// 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 DimmerConfigClass { + public: + bool isEnabled(const char* name) const; + int32_t getPin(const char* name) const; + DimmerType getType(const char* name) const; + }; + + class Dimmer { + public: + explicit Dimmer(const char* name) : name(name) {} + + void begin(); + 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; } + + gpio_num_t getPin() const { return _pin; } + bool isEnabled() const { return _enabled; } + + void toJson(const JsonObject& root) const; + + public: + const char* name; + + private: + bool _enabled = false; + uint8_t _level = 0; + gpio_num_t _pin = GPIO_NUM_NC; + DimmerLevelCallback _callback = nullptr; + DimmableLightLinearized* _dimmer = nullptr; + }; + + extern DimmerConfigClass DimmerConfig; +} // 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/MycilaDisplay/LICENSE b/lib/MycilaDisplay/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaDisplay/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/MycilaDisplay/MycilaDisplay.cpp b/lib/MycilaDisplay/MycilaDisplay.cpp new file mode 100644 index 0000000..4adc32d --- /dev/null +++ b/lib/MycilaDisplay/MycilaDisplay.cpp @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#include + +#define TAG "DISPLAY" + +#define DISPLAY_FONT u8g2_font_5x8_tr +#define DISPLAY_LINE_HEIGHT_PX 9 + +static const char* DisplayTypeNames[] = {"SH1106", "SH1107", "SSD1306"}; + +void Mycila::DisplayClass::begin() { + if (_enabled) + return; + + if (!DisplayConfig.isEnabled()) + return; + + int32_t clkPin = DisplayConfig.getClockPin(); + if (GPIO_IS_VALID_OUTPUT_GPIO(clkPin)) { + _clkPin = (gpio_num_t)clkPin; + } else { + Logger.error(TAG, "Invalid Clock pin: %u", _clkPin); + _clkPin = GPIO_NUM_NC; + return; + } + + int32_t dataPin = DisplayConfig.getDataPin(); + if (GPIO_IS_VALID_OUTPUT_GPIO(dataPin)) { + _dataPin = (gpio_num_t)dataPin; + } else { + Logger.error(TAG, "Invalid Data pin: %u", _dataPin); + _dataPin = GPIO_NUM_NC; + return; + } + + DisplayType type = DisplayConfig.getType(); + + Logger.info(TAG, "Enable Display..."); + Logger.debug(TAG, "- Type: %s", DisplayTypeNames[static_cast(type)]); + Logger.debug(TAG, "- Clock Pin: %u", _clkPin); + Logger.debug(TAG, "- Data Pin: %u", _dataPin); + Logger.debug(TAG, "- Rotation: %u°", DisplayConfig.getRotation()); + Logger.debug(TAG, "- Power Save Delay: %u seconds", DisplayConfig.getPowerSaveDelay()); + + switch (type) { + case DisplayType::SSD1306: + switch (DisplayConfig.getRotation()) { + case 90: + _display = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R1, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + case 180: + _display = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R2, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + case 270: + _display = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R3, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + default: + _display = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + } + break; + case DisplayType::SH1106: + switch (DisplayConfig.getRotation()) { + case 90: + _display = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R1, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + case 180: + _display = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R2, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + case 270: + _display = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R3, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + default: + _display = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + } + break; + case DisplayType::SH1107: + switch (DisplayConfig.getRotation()) { + case 90: + _display = new U8G2_SH1107_64X128_F_HW_I2C(U8G2_R2, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + case 180: + _display = new U8G2_SH1107_64X128_F_HW_I2C(U8G2_R3, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + case 270: + _display = new U8G2_SH1107_64X128_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + default: + _display = new U8G2_SH1107_64X128_F_HW_I2C(U8G2_R1, U8X8_PIN_NONE, _clkPin, _dataPin); + break; + } + break; + default: + assert(false); + break; + } + + _display->begin(); + _display->setPowerSave(false); + _display->setFont(DISPLAY_FONT); + + _enabled = true; + _active = true; +} + +void Mycila::DisplayClass::end() { + if (!_enabled) + return; + Logger.info(TAG, "Disable Display..."); + _enabled = false; + _active = false; + _display->clear(); + delete _display; + _dataPin = GPIO_NUM_NC; + _clkPin = GPIO_NUM_NC; +} + +void Mycila::DisplayClass::loop() { + if (!_enabled) + return; + + // readjust live power save state + const uint32_t delay = DisplayConfig.getPowerSaveDelay(); + _powerSave.setEnable(delay > 0, false); + + if (_active && delay > 0 && _powerSave.isAllowed(delay)) { + Logger.info(TAG, "Display power saving..."); + _display->setPowerSave(true); + _active = false; + + } else if (!_active && delay == 0) { + Logger.info(TAG, "Display power saving disabled"); + _display->setPowerSave(false); + _active = true; + } +} + +void Mycila::DisplayClass::setActive(bool active) { + if (!_enabled) + return; + + if (active == _active) + return; + + if (active && !_active) { + _display->setPowerSave(false); + _active = true; + } else if (!active && _active) { + _display->setPowerSave(true); + _active = false; + } +} + +void Mycila::DisplayClass::print(const char lines[MYCILA_DISPLAY_LINE_COUNT][MYCILA_DISPLAY_LINE_LENGTH]) { + if (!_enabled) + return; + + bool changed = false; + for (int i = 0; i < MYCILA_DISPLAY_LINE_COUNT; i++) { + if (strcmp(_lines[i], lines[i]) != 0) { + snprintf(_lines[i], MYCILA_DISPLAY_LINE_LENGTH, "%s", lines[i]); + changed = true; + } + } + + if (changed) { + _display->clearBuffer(); + _display->drawStr(1, DISPLAY_LINE_HEIGHT_PX, _lines[0]); + _display->drawStr(1, 2 * DISPLAY_LINE_HEIGHT_PX + 1, _lines[1]); + _display->drawStr(1, 3 * DISPLAY_LINE_HEIGHT_PX + 2, _lines[2]); + _display->drawStr(1, 4 * DISPLAY_LINE_HEIGHT_PX + 3, _lines[3]); + _display->drawStr(1, 5 * DISPLAY_LINE_HEIGHT_PX + 4, _lines[4]); + _display->drawStr(1, 6 * DISPLAY_LINE_HEIGHT_PX + 5, _lines[5]); + _display->sendBuffer(); + + if (!_active) + _display->setPowerSave(false); + + _active = true; + _powerSave.touch(); + } +} + +namespace Mycila { + DisplayClass Display; + DisplayConfigClass DisplayConfig; +} // namespace Mycila diff --git a/lib/MycilaDisplay/MycilaDisplay.h b/lib/MycilaDisplay/MycilaDisplay.h new file mode 100644 index 0000000..26b271c --- /dev/null +++ b/lib/MycilaDisplay/MycilaDisplay.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +#include +#include + +#define MYCILA_DISPLAY_LINE_COUNT 6 +#define MYCILA_DISPLAY_LINE_LENGTH 32 + +namespace Mycila { + enum DisplayType { + SH1106 = 0, + SH1107, + SSD1306 + }; + + class DisplayConfigClass { + public: + bool isEnabled() const; + DisplayType getType() const; + int32_t getClockPin() const; + int32_t getDataPin() const; + uint32_t getRotation() const; + uint32_t getPowerSaveDelay() const; + }; + + class DisplayClass { + public: + ~DisplayClass() { end(); } + + void begin(); + void end(); + void loop(); + + void setActive(bool active); + // Up to 6 lines supported, 25 chars long + void print(const char lines[MYCILA_DISPLAY_LINE_COUNT][MYCILA_DISPLAY_LINE_LENGTH]); + + bool isEnabled() const { return _enabled; } + // true if on, false if off or power saving + bool isActive() const { return _active; } + + gpio_num_t getClockPin() const { return _clkPin; } + gpio_num_t getDataPin() const { return _dataPin; } + + private: + bool _enabled = false; + bool _active = false; + DisplayType _display_type = DisplayType::SH1106; + U8G2* _display; + Throttle _powerSave = Throttle(true); + gpio_num_t _clkPin = GPIO_NUM_NC; + gpio_num_t _dataPin = GPIO_NUM_NC; + char _lines[MYCILA_DISPLAY_LINE_COUNT][MYCILA_DISPLAY_LINE_LENGTH]; + }; + + extern DisplayConfigClass DisplayConfig; + extern DisplayClass Display; +} // namespace Mycila 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..b2abe9d --- /dev/null +++ b/lib/MycilaGrid/MycilaGrid.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include +#include + +#define TAG "POWER" + +void Mycila::GridClass::begin() { + _topic = GridConfig.getGridPowerMQTTTopic(); + + Logger.info(TAG, "Enable Grid Data..."); + Logger.debug(TAG, "- MQTT Grid Power 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() { + Logger.info(TAG, "Disable Grid Data..."); + if (!_topic.isEmpty()) { + MQTT.unsubscribe(_topic); + _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() ? JSY.power2 : (_isMQTTGridPowerExpired() ? 0 : _mqttGridPower); +} + +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; + GridConfigClass GridConfig; +} // namespace Mycila diff --git a/lib/MycilaGrid/MycilaGrid.h b/lib/MycilaGrid/MycilaGrid.h new file mode 100644 index 0000000..247957c --- /dev/null +++ b/lib/MycilaGrid/MycilaGrid.h @@ -0,0 +1,43 @@ +// 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 GridConfigClass { + public: + String getGridPowerMQTTTopic() const; + }; + + class GridClass { + public: + void begin(); + void end(); + + void toJson(const JsonObject& root) const; + + float getFrequency() const; + float getPower() const; + float getVoltage() const; + inline bool isOnline() const { return getFrequency() > 0; } + + private: + volatile float _mqttGridPower = 0; + volatile uint32_t _mqttGridPowerUpdateTime = 0; + String _topic; + + private: + bool _isMQTTGridPowerExpired() const { return millis() - _mqttGridPowerUpdateTime >= YASOLR_MQTT_GRID_POWER_EXPIRATION * 1000; } + }; + + extern GridClass Grid; + extern GridConfigClass GridConfig; +} // 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..1b315eb --- /dev/null +++ b/lib/MycilaHTTPd/MycilaHTTPd.cpp @@ -0,0 +1,124 @@ +// 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::begin() { + if (_enabled) + return; + + Logger.info(TAG, "Enable HTTPd..."); + Logger.debug(TAG, "- Authentication: %s", (!HTTPdConfig.getUsername().isEmpty() && !HTTPdConfig.getPassword().isEmpty()) ? "true" : "false"); + Logger.debug(TAG, "- Username: %s", HTTPdConfig.getUsername().c_str()); + Logger.debug(TAG, "- Password: %s", HTTPdConfig.getPassword().isEmpty() ? "" : "********"); + + server.onNotFound([](AsyncWebServerRequest* request) { + request->send(404); + }); + server.begin(); + MDNS.addService("http", "tcp", 80); + _enabled = true; +} + +void Mycila::HTTPdClass::end() { + if (!_enabled) + return; + Logger.info(TAG, "Disable HTTPd..."); + server.end(); + mdns_service_remove("_http", "_tcp"); + _enabled = 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) { + const String _username = HTTPdConfig.getUsername(); + const String _password = HTTPdConfig.getPassword(); + const bool _auth = !_username.isEmpty() && !_password.isEmpty(); + return handler->setAuthentication(anonymous ? "" : (_auth ? _username.c_str() : ""), anonymous ? "" : (_auth ? _password.c_str() : "")); +} + +namespace Mycila { + HTTPdClass HTTPd; + HTTPdConfigClass HTTPdConfig; +} // namespace Mycila diff --git a/lib/MycilaHTTPd/MycilaHTTPd.h b/lib/MycilaHTTPd/MycilaHTTPd.h new file mode 100644 index 0000000..3e2ceba --- /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 HTTPdConfigClass { + public: + bool isEnabled() const; + String getUsername() const; + String getPassword() const; + }; + + class HTTPdClass { + public: + 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); + + public: + AsyncWebServer server = AsyncWebServer(80); + + private: + bool _enabled; + + private: + AsyncWebHandler& _authenticate(AsyncWebHandler* handler, bool anonymous); + }; + + extern HTTPdConfigClass HTTPdConfig; + 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..2033523 --- /dev/null +++ b/lib/MycilaLights/MycilaLights.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#define TAG "LIGHTS" + +void Mycila::LightsClass::begin(LightState green, LightState yellow, LightState red) { + if (LightsConfig.isEnabled()) { + Logger.info(TAG, "Enable Physical LEDs..."); + + int32_t greenPin = LightsConfig.getGreenPin(); + 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; + } + + int32_t yellowPin = LightsConfig.getYellowPin(); + 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; + } + + int32_t redPin = LightsConfig.getRedPin(); + 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; + } + } + + set(green, yellow, red); +} + +void Mycila::LightsClass::end() { + Logger.info(TAG, "Disable Physical LEDs..."); + set(LightState::OFF, LightState::OFF, LightState::OFF); + _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; + LightsConfigClass LightsConfig; +} // namespace Mycila diff --git a/lib/MycilaLights/MycilaLights.h b/lib/MycilaLights/MycilaLights.h new file mode 100644 index 0000000..b6c86d0 --- /dev/null +++ b/lib/MycilaLights/MycilaLights.h @@ -0,0 +1,71 @@ +// 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 LightsConfigClass { + public: + int32_t getGreenPin() const; + int32_t getYellowPin() const; + int32_t getRedPin() const; + bool isEnabled() const; + }; + + class LightsClass { + public: + void begin(LightState green = LightState::NONE, LightState yellow = LightState::NONE, LightState red = LightState::NONE); + 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 LightsConfigClass LightsConfig; + 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/MycilaOTA/LICENSE b/lib/MycilaOTA/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaOTA/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/MycilaOTA/MycilaOTA.cpp b/lib/MycilaOTA/MycilaOTA.cpp new file mode 100644 index 0000000..f1f7a87 --- /dev/null +++ b/lib/MycilaOTA/MycilaOTA.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include +#include + +#include +#include + +#ifdef APP_VERSION_PRO +#include +#else +#include +#endif + +#define TAG "OTA" + +void Mycila::OTAClass::begin() { + if (_enabled) + return; + + const String hostname = OTAConfig.getHostname(); + + if (hostname.isEmpty()) + return; + + Logger.info(TAG, "Enable OTA..."); + + const String password = OTAConfig.getPassword(); + + Logger.debug(TAG, "- ota://%s:%u", hostname.c_str(), MYCILA_OTA_PORT); + ArduinoOTA.setHostname(hostname.c_str()); + ArduinoOTA.setPort(MYCILA_OTA_PORT); + if (!password.isEmpty()) + ArduinoOTA.setPassword(password.c_str()); + ArduinoOTA.setRebootOnSuccess(false); + ArduinoOTA.onStart([this]() { + if (_prepareCallback) + _prepareCallback(); + }); + ArduinoOTA.onEnd([this]() { + if (_completeCallback) + _completeCallback(true); + }); + ArduinoOTA.onError([this](ota_error_t error) { + if (_completeCallback) + _completeCallback(false); + }); + ArduinoOTA.setMdnsEnabled(true); + ArduinoOTA.begin(); + ArduinoOTA.setMdnsEnabled(false); // so that end() does not end MDNS + + Logger.debug(TAG, "- http://%s/update", (hostname + (hostname.indexOf(".") >= 0 ? "" : ".local")).c_str()); + if (!_elegantOTAInitialized) { +#ifdef APP_VERSION_PRO + ElegantOTA.setID(OTAConfig.getID().c_str()); + ElegantOTA.setTitle(OTAConfig.getTitle().c_str()); + ElegantOTA.setFWVersion(OTAConfig.getFWVersion().c_str()); + ElegantOTA.setFirmwareMode(true); + ElegantOTA.setFilesystemMode(false); +#endif + ElegantOTA.setAutoReboot(false); + ElegantOTA.setAuth(OTAConfig.getUsername().c_str(), OTAConfig.getPassword().c_str()); + ElegantOTA.onStart([this]() { + if (_prepareCallback) + _prepareCallback(); + }); + ElegantOTA.onEnd([this](bool success) { + if (_completeCallback) + _completeCallback(success); + }); + ElegantOTA.begin(&HTTPd.server, OTAConfig.getUsername().c_str(), OTAConfig.getPassword().c_str()); + _elegantOTAInitialized = true; + } + + _enabled = true; +} + +void Mycila::OTAClass::loop() { + if (_enabled) { + ArduinoOTA.handle(); +#ifdef APP_VERSION_PRO + ElegantOTA.loop(); +#endif + } +} + +void Mycila::OTAClass::end() { + if (!_enabled) + return; + Logger.info(TAG, "Disable OTA..."); + MDNS.disableArduino(); + ArduinoOTA.end(); + _enabled = false; +} + +namespace Mycila { + OTAClass OTA; + OTAConfigClass OTAConfig; +} // namespace Mycila diff --git a/lib/MycilaOTA/MycilaOTA.h b/lib/MycilaOTA/MycilaOTA.h new file mode 100644 index 0000000..dd3eb77 --- /dev/null +++ b/lib/MycilaOTA/MycilaOTA.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +#ifndef MYCILA_OTA_PORT +#define MYCILA_OTA_PORT 3232 +#endif + +namespace Mycila { + typedef std::function OTACompleteCallback; + typedef std::function OTAPrepareCallback; + + class OTAConfigClass { + public: + String getHostname() const; + String getUsername() const; + String getPassword() const; + + String getID() const; + String getTitle() const; + String getFWVersion() const; + }; + + class OTAClass { + public: + void listen(OTACompleteCallback callback) { _completeCallback = callback; } + void listen(OTAPrepareCallback callback) { _prepareCallback = callback; } + + void begin(); + void loop(); + void end(); + + bool isEnabled() const { return _enabled; } + + private: + bool _enabled = false; + bool _elegantOTAInitialized = false; + OTAPrepareCallback _prepareCallback = nullptr; + OTACompleteCallback _completeCallback = nullptr; + }; + + extern OTAConfigClass OTAConfig; + extern OTAClass OTA; +} // namespace Mycila diff --git a/lib/MycilaOTA/library.properties b/lib/MycilaOTA/library.properties new file mode 100644 index 0000000..b51202e --- /dev/null +++ b/lib/MycilaOTA/library.properties @@ -0,0 +1,5 @@ +name=MycilaOTA +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..37e4727 --- /dev/null +++ b/lib/MycilaRelayManager/MycilaRelayManager.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include + +#define TAG "RELAY-M" + +void Mycila::RelayManagerClass::begin() { + for (auto& relay : relays) { + relay.begin(); + } +} + +void Mycila::RelayManagerClass::end() { + for (auto& relay : relays) { + relay.end(); + } +} + +void Mycila::RelayManagerClass::toJson(const JsonObject& root) const { + for (int i = 0; i < relays.size(); i++) { + auto& relay = relays[i]; + JsonObject outputJson = root[relay.name].to(); + relay.toJson(outputJson); + } +} + +void Mycila::RelayManagerClass::loop() { + if (millis() - _lastCheck >= MYCILA_RELAY_PAUSE_DURATION * 1000) { + 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.name); + if (threshold > 0 && grid <= -(threshold + round(threshold * MYCILA_RELAY_THRESHOLD_TOLERANCE_START / 100.0))) { + Logger.info(TAG, "Excess above %u W +%u%%: starting relay '%s'...", threshold, MYCILA_RELAY_THRESHOLD_TOLERANCE_START, relay.name); + 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.name); + if (threshold > 0 && grid - threshold >= -(threshold - round(threshold * MYCILA_RELAY_THRESHOLD_TOLERANCE_STOP / 100.0))) { + Logger.info(TAG, "Excess below %u W -%u%%: stopping relay '%s'...", threshold, MYCILA_RELAY_THRESHOLD_TOLERANCE_STOP, relay.name); + relay.setState(false); + // we stop the loop and let time for routing to adjust before checking again + return; + } + } + } + } + + _lastCheck = millis(); + } +} + +bool Mycila::RelayManagerClass::tryRelayState(int idx, bool switchOn, uint32_t duration) { + auto& relay = relays[idx]; + + if (!relay.isEnabled()) + return false; + + if (RelayManagerConfig.getPowerThreshold(relay.name) > 0) { + Logger.warn(TAG, "Auto Mode '%s' is activated: unable to switch Relay...", relay.name); + return false; + } + + relay.setState(switchOn, duration); + _lastCheck = millis(); + 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..afde583 --- /dev/null +++ b/lib/MycilaRelayManager/MycilaRelayManager.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#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 + +#ifndef MYCILA_RELAY_PAUSE_DURATION +#define MYCILA_RELAY_PAUSE_DURATION 60 +#endif + +namespace Mycila { + + class RelayManagerConfigClass { + public: + uint16_t getPowerThreshold(const char* name) const; + }; + + class RelayManagerClass { + public: + void begin(); + void loop(); + void end(); + + void toJson(const JsonObject& root) const; + + bool tryRelayState(int idx, bool state, uint32_t duration = 0); + + public: + std::vector relays; + + private: + uint32_t _lastCheck = 0; + }; + + 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..b4259a9 --- /dev/null +++ b/lib/MycilaRouter/MycilaRouter.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include + +#define TAG "ROUTER" + +void Mycila::RouterClass::begin() { + for (auto& output : outputs) { + output.begin(); + } +} + +void Mycila::RouterClass::end() { + for (auto& output : outputs) { + output.end(); + } +} + +void Mycila::RouterClass::loop() { + outputs[_loopIdx].loop(); + _loopIdx = (_loopIdx + 1) % outputs.size(); +} + +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 (int i = 0; i < outputs.size(); i++) { + auto& output = outputs[i]; + JsonObject outputJson = root[output.name].to(); + output.toJson(outputJson); + outputJson["power"] = getOutputPower(i); + } +} + +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; +} + +float Mycila::RouterClass::getOutputPower(int idx) const { + int routing = 0; + float dimmerLevel = 0; + + for (size_t i = 0, max = outputs.size(); i < max; i++) { + const uint8_t level = outputs[i].dimmer.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 = getOutputResistance(idx); + + return (u * u / r) * (dimmerLevel / 100.0); +} + +float Mycila::RouterClass::getOutputResistance(int idx) const { + // TODO: auto-calibration or config ? + return 227.34; +} + +namespace Mycila { + RouterClass Router; +} // namespace Mycila diff --git a/lib/MycilaRouter/MycilaRouter.h b/lib/MycilaRouter/MycilaRouter.h new file mode 100644 index 0000000..3986bec --- /dev/null +++ b/lib/MycilaRouter/MycilaRouter.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +#include +#include + +namespace Mycila { + class RouterClass { + public: + void begin(); + void loop(); + void end(); + + void toJson(const JsonObject& root) const; + + float getVirtualGridPower() const; + float getTotalRoutedPower() const; + float getTotalPowerFactor() const; + float getTotalTHDi() const; + float getTotalRoutedEnergy() const; + + float getOutputPower(int idx) const; + float getOutputResistance(int idx) const; + + inline bool isRouting() const { return outputs[0].getState() == RouterOutputState::OUTPUT_ROUTING || outputs[1].getState() == RouterOutputState::OUTPUT_ROUTING; } + + public: + std::vector outputs; + uint8_t _loopIdx = 0; + }; + + 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..846eaf6 --- /dev/null +++ b/lib/MycilaRouterOutput/MycilaRouterOutput.cpp @@ -0,0 +1,270 @@ +// 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"}; + +Mycila::RouterOutput::RouterOutput(const char* name) : name(name), + dimmer(name), + relay(name), + temperatureSensor(name) {} + +void Mycila::RouterOutput::begin() { + dimmer.begin(); + relay.begin(); + temperatureSensor.begin(); + + if (isEnabled()) { + Logger.debug(TAG, "Enable Router Output '%s'...", name); + Logger.debug(TAG, " - Dimmer: %s", RouterOutputConfig.isAutoDimmerEnabled(name) ? "auto" : "manual"); + Logger.debug(TAG, " - Dimmer Level Limit: %u %", RouterOutputConfig.getDimmerLevelLimit(name)); + Logger.debug(TAG, " - Bypass: %s", RouterOutputConfig.isAutoBypassEnabled(name) ? "auto" : "manual"); + Logger.debug(TAG, " - Auto Time: %s to %s %s", RouterOutputConfig.getAutoStartTime(name).c_str(), RouterOutputConfig.getAutoStopTime(name).c_str(), RouterOutputConfig.getWeekDays(name).c_str()); + Logger.debug(TAG, " - Auto Start Temperature: %u °C", RouterOutputConfig.getAutoStartTemperature(name)); + Logger.debug(TAG, " - Auto Stop Temperature: %u °C", RouterOutputConfig.getAutoStopTemperature(name)); + + } else { + Logger.warn(TAG, "Disable Router Output '%s'...", name); + } +} + +void Mycila::RouterOutput::end() { + _autoBypassEnabled = false; + dimmer.off(); + relay.off(); + dimmer.end(); + relay.end(); + temperatureSensor.end(); +} + +void Mycila::RouterOutput::loop() { + temperatureSensor.loop(); + + if (!dimmer.isEnabled()) + return; + + if (millis() - _lastCheck >= 500) { + // Auto BP rules + _autoBypassLoop(); + + // dimmer limit + if (!_autoBypassEnabled && dimmer.isOn()) { + uint8_t limit = RouterOutputConfig.getDimmerLevelLimit(dimmer.name); + if (dimmer.getLevel() > limit) { + Logger.warn(TAG, "Dimmer '%s' reached its limit at %u %", dimmer.name, limit); + dimmer.setLevel(limit); + } + } + + _lastCheck = millis(); + } +} + +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.name, 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); +} + +void Mycila::RouterOutput::_autoBypassLoop() { + 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 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); +} + +// _setBypassRelay +void Mycila::RouterOutput::_setBypassRelay(bool state) { + if (relay.isEnabled()) { + Logger.debug(TAG, "Turning %s Bypass Relay '%s'...", state ? "on" : "off", relay.name); + if (state) + dimmer.off(); + relay.setState(state); + + } else { + Logger.debug(TAG, "Turning %s Dimmer '%s'...", state ? "on" : "off", dimmer.name); + 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..921183a --- /dev/null +++ b/lib/MycilaRouterOutput/MycilaRouterOutput.h @@ -0,0 +1,81 @@ +// 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: + explicit RouterOutput(const char* name); + + void begin(); + void loop(); + void end(); + + RouterOutputState getState() const; + const char* getStateString() const; + bool isEnabled() const { return dimmer.isEnabled(); } + + 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); + + void toJson(const JsonObject& root) const; + + public: + const char* name; + Dimmer dimmer; + Relay relay; + TemperatureSensor temperatureSensor; + + private: + bool _autoBypassEnabled = false; + RouterOutputStateCallback _callback = nullptr; + u_int32_t _lastCheck = 0; + + private: + void _autoBypassLoop(); + 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..b01f514 --- /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..."); + if (_callback) + _callback(SystemEvent::BEFORE_RESET); + nvs_flash_erase(); + nvs_flash_init(); + esp_restart(); +} + +void Mycila::SystemClass::restart() { + Logger.debug(TAG, "Triggering System Restart..."); + if (_callback) + _callback(SystemEvent::BEFORE_RESTART); + 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..5d978dc --- /dev/null +++ b/lib/MycilaSystem/MycilaSystem.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include +#include + +namespace Mycila { + enum class SystemEvent { + BEFORE_RESET, + BEFORE_RESTART, + }; + + typedef std::function SystemEventCallback; + + typedef struct { + uint32_t total; + uint32_t used; + uint32_t free; + float usage; + } SystemMemory; + + class SystemClass { + public: + void begin(); + + void listen(SystemEventCallback callback) { _callback = callback; } + + void reset(); + void restart(); + + 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; + + private: + SystemEventCallback _callback = nullptr; + }; + + 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/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..4b8d304 --- /dev/null +++ b/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#define TAG "DS18B20" + +void Mycila::TemperatureSensor::begin() { + if (_enabled) + return; + + if (!TemperatureSensorConfig.isEnabled(name)) + return; + + int32_t pin = TemperatureSensorConfig.getPin(name); + 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.info(TAG, "Enable Temperature Sensor '%s'...", name); + Logger.debug(TAG, "- Pin: %u", _pin); + Logger.debug(TAG, "- Refresh Interval: %u seconds", MYCILA_TEMPERATURE_READ_INTERVAL); + + _enabled = true; +} + +void Mycila::TemperatureSensor::end() { + if (_enabled) { + Logger.info(TAG, "Disable Temperature Sensor '%s'...", name); + _enabled = false; + _temperature = 0; + _lastUpdate = 0; + _pin = GPIO_NUM_NC; + } +} + +void Mycila::TemperatureSensor::loop() { + if (!_enabled) + return; + if (millis() - _lastRead >= MYCILA_TEMPERATURE_READ_INTERVAL * 1000) { + _read(); + _lastRead = millis(); + } +} + +void Mycila::TemperatureSensor::_read() { + 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(); +} + +namespace Mycila { + TemperatureSensorConfigClass TemperatureSensorConfig; +} // namespace Mycila diff --git a/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.h b/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.h new file mode 100644 index 0000000..7f9dfc8 --- /dev/null +++ b/lib/MycilaTemperatureSensor/MycilaTemperatureSensor.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include +#include + +#ifndef MYCILA_TEMPERATURE_READ_INTERVAL +#define MYCILA_TEMPERATURE_READ_INTERVAL 10 +#endif + +namespace Mycila { + typedef std::function TemperatureChangeCallback; + + class TemperatureSensorConfigClass { + public: + bool isEnabled(const char* name) const; + int32_t getPin(const char* name) const; + }; + + class TemperatureSensor { + public: + explicit TemperatureSensor(const char* name) : name(name) {} + ~TemperatureSensor() { end(); } + + void begin(); + void loop(); + void end(); + + void listen(TemperatureChangeCallback callback) { _callback = callback; } + + float getTemperature() const { return _temperature; } + bool isEnabled() const { return _enabled; } + bool isValid() const { return millis() - _lastUpdate < 6 * MYCILA_TEMPERATURE_READ_INTERVAL * 1000; } + u_int32_t getLastUpdate() const { return _lastUpdate; } + String getTemperatureAsString() const; + gpio_num_t getPin() const { return _pin; }; + + void toJson(const JsonObject& root) const; + + public: + const char* name; + + private: + u_int32_t _lastRead = 0; + OneWire _oneWire; + DallasTemperature _dallas; + bool _enabled = false; + gpio_num_t _pin = GPIO_NUM_NC; + float _temperature = 0; + uint32_t _lastUpdate = 0; + TemperatureChangeCallback _callback = nullptr; + + private: + void _read(); + }; + + extern TemperatureSensorConfigClass TemperatureSensorConfig; +} // 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/MycilaThrottle/LICENSE b/lib/MycilaThrottle/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaThrottle/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/MycilaThrottle/MycilaThrottle.h b/lib/MycilaThrottle/MycilaThrottle.h new file mode 100644 index 0000000..382f6d2 --- /dev/null +++ b/lib/MycilaThrottle/MycilaThrottle.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +namespace Mycila { + class Throttle { + public: + explicit Throttle(bool enabled = false, bool touch = true) { setEnable(enabled, touch); }; + + void setEnable(bool enabled, bool touch = true) { + _enabled = enabled; + if (enabled && touch) + _last = millis(); + } + bool isEnabled() const { return _enabled; } + void touch() { _last = millis(); } + void touch(uint32_t time) { _last = time; } + + // interval in seconds + bool __attribute__((hot)) isAllowed(const uint32_t interval) { + if (_enabled) { + const uint32_t now = millis(); + if (now - _last >= interval * (uint32_t)1000) { + _last = now; + return true; + } + } + return false; + } + + bool __attribute__((hot)) isAllowedMillis(const uint32_t intervalMillis) { + if (_enabled) { + const uint32_t now = millis(); + if (now - _last >= intervalMillis) { + _last = now; + return true; + } + } + return false; + } + + private: + uint32_t _last = 0; + bool _enabled = false; + }; +} // namespace Mycila diff --git a/lib/MycilaThrottle/library.properties b/lib/MycilaThrottle/library.properties new file mode 100644 index 0000000..8ff9774 --- /dev/null +++ b/lib/MycilaThrottle/library.properties @@ -0,0 +1,5 @@ +name=MycilaThrottle +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/MycilaTrial/LICENSE b/lib/MycilaTrial/LICENSE new file mode 100644 index 0000000..2b5457c --- /dev/null +++ b/lib/MycilaTrial/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/MycilaTrial/MycilaTrial.cpp b/lib/MycilaTrial/MycilaTrial.cpp new file mode 100644 index 0000000..7db60d8 --- /dev/null +++ b/lib/MycilaTrial/MycilaTrial.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include + +#include +#include + +#define TAG "TRIAL" + +#ifndef MYCILA_TRIAL_DURATION +#define MYCILA_TRIAL_DURATION 86400 // 1 day in seconds +#endif + +#ifndef MYCILA_TRIAL_SAVE +#define MYCILA_TRIAL_SAVE 60 +#endif + +void Mycila::TrialClass::begin() { + Preferences prefs; + prefs.begin(TAG, true); + const uint32_t duration = prefs.getLong("duration", false); + prefs.end(); + if (duration > MYCILA_TRIAL_DURATION) { + Logger.error(TAG, "Trial expired"); + while (true) { + delay(1000); + } + } + _lastSave = millis(); + Logger.warn(TAG, "%lu seconds left for trial", duration > MYCILA_TRIAL_DURATION ? 0 : MYCILA_TRIAL_DURATION - duration); +} + +void Mycila::TrialClass::loop() { + const uint32_t uptime = millis(); + const uint32_t elapsed = uptime - _lastSave; + if (elapsed >= MYCILA_TRIAL_SAVE * 1000) { + Preferences prefs; + prefs.begin(TAG); + const uint32_t duration = prefs.getLong("duration", 0) + elapsed / 1000; + prefs.putLong("duration", duration); + prefs.end(); + Logger.warn(TAG, "%lu seconds left for trial", duration > MYCILA_TRIAL_DURATION ? 0 : MYCILA_TRIAL_DURATION - duration); + _lastSave = uptime; + if (duration >= MYCILA_TRIAL_DURATION) { + Logger.error(TAG, "Trial expired"); + while (true) { + delay(1000); + } + } + } +} + +namespace Mycila { + TrialClass Trial; +} // namespace Mycila diff --git a/lib/MycilaTrial/MycilaTrial.h b/lib/MycilaTrial/MycilaTrial.h new file mode 100644 index 0000000..df7f24f --- /dev/null +++ b/lib/MycilaTrial/MycilaTrial.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include + +namespace Mycila { + class TrialClass { + public: + void begin(); + void loop(); + + private: + uint32_t _lastSave = 0; + }; + + extern TrialClass Trial; +} // namespace Mycila diff --git a/lib/MycilaTrial/library.properties b/lib/MycilaTrial/library.properties new file mode 100644 index 0000000..227a85c --- /dev/null +++ b/lib/MycilaTrial/library.properties @@ -0,0 +1,5 @@ +name=MycilaTrial +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..ce5fd5b --- /dev/null +++ b/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.cpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include +#include + +#include +#include + +#define TAG "ZCD" + +void Mycila::ZeroCrossDetectionClass::begin() { + if (_enabled) + return; + + if (!ZCDConfig.isEnabled()) + return; + + int32_t pin = ZCDConfig.getPin(); + 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.info(TAG, "Enable Zero-Cross Detection..."); + Logger.debug(TAG, "- Pin: %u", _pin); + Logger.debug(TAG, "- Grid Frequency: %u Hz", ZCDConfig.getGridFrequency()); + +#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(ZCDConfig.getGridFrequency()); + DimmableLightLinearized::frequencyMonitorAlwaysOn(true); + DimmableLightLinearized::setSyncPin(_pin); + DimmableLightLinearized::begin(); + + _enabled = true; +} + +void Mycila::ZeroCrossDetectionClass::end() { + if (_enabled) { + Logger.info(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; + ZeroCrossDetectionConfigClass ZCDConfig; +} // namespace Mycila diff --git a/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.h b/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.h new file mode 100644 index 0000000..c61dacc --- /dev/null +++ b/lib/MycilaZeroCrossDetection/MycilaZeroCrossDetection.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +namespace Mycila { + class ZeroCrossDetectionConfigClass { + public: + bool isEnabled() const; + int32_t getPin() const; + uint8_t getGridFrequency() const; + }; + + class ZeroCrossDetectionClass { + public: + void begin(); + 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; + extern ZeroCrossDetectionConfigClass ZCDConfig; +} // 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..8ccf52c --- /dev/null +++ b/pio/version.py @@ -0,0 +1,39 @@ +import subprocess +import os + +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 + "\\\"" + + 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..1fa58a8 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,479 @@ +; 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 +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 + bblanchon/ArduinoJson @ 7.0.2 + bblanchon/StreamUtils @ 1.8.0 + esp-arduino-libs/ESP32_Button @ 0.0.1 + esphome/AsyncTCP-esphome @ 2.1.1 + mathieucarbou/ESP Async WebServer @ 2.6.1 + mathieucarbou/MycilaHADiscovery @ 1.0.1 + mathieucarbou/MycilaConfig @ 1.3.0 + mathieucarbou/MycilaESPConnect @ 2.1.2 + mathieucarbou/MycilaJSY @ 3.3.0 + mathieucarbou/MycilaLogger @ 2.0.2 + mathieucarbou/MycilaMQTT @ 1.1.0 + mathieucarbou/MycilaNTP @ 2.1.0 + mathieucarbou/MycilaRelay @ 1.1.0 + mathieucarbou/MycilaTaskMonitor @ 1.1.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 + ; https://github.com/arkhipenko/TaskScheduler#testing +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 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=2 + -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_JSON_SUPPORT + -D MYCILA_LOGGER_CUSTOM_LEVEL + -D MYCILA_NTP_JSON_SUPPORT + -D MYCILA_RELAY_JSON_SUPPORT + -D MYCILA_TASK_MONITOR_JSON_SUPPORT + -D NETWORK_FREQ_RUNTIME + -D WEBSERIAL_MAX_WS_CLIENTS=2 + -D WS_MAX_QUEUED_MESSAGES=32 +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 MYCILA_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 + MycilaTrial + WebSerialLite + +[pro] +build_flags = -D APP_VERSION_PRO +lib_deps = +lib_ignore = + ElegantOTA + ESPDASH + MycilaTrial + +[trial] +build_flags = + ${pro.build_flags} + -D APP_VERSION_TRIAL + -D MYCILA_TRIAL_DURATION=259200 +lib_deps = +lib_ignore = + ElegantOTA + ESPDASH + +; -------------------------------------------------------------------- +; 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:pro-esp32-trial] +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} + +; 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:pro-esp32s-trial] +extends = env:pro-esp32-trial +board = nodemcu-32s + +; esp32c3 + +[env:oss-esp32c3] +extends = env:oss-esp32 +board = esp32-c3-devkitc-02 +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + -D MYCILA_JSY_SERIAL=Serial1 + +[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} + -D MYCILA_JSY_SERIAL=Serial1 + +[env:pro-esp32c3] +extends = env:pro-esp32 +board = esp32-c3-devkitc-02 +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + -D MYCILA_JSY_SERIAL=Serial1 + +[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} + -D MYCILA_JSY_SERIAL=Serial1 + +[env:pro-esp32c3-trial] +extends = env:pro-esp32-trial +board = esp32-c3-devkitc-02 +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + -D MYCILA_JSY_SERIAL=Serial1 + +; esp32s3 + +[env:oss-esp32s3] +extends = env:oss-esp32 +board = esp32-s3-devkitc-1 + +[env:oss-esp32s3-debug] +extends = env:oss-esp32-debug +board = esp32-s3-devkitc-1 + +[env:pro-esp32s3] +extends = env:pro-esp32 +board = esp32-s3-devkitc-1 + +[env:pro-esp32s3-debug] +extends = env:pro-esp32-debug +board = esp32-s3-devkitc-1 + +[env:pro-esp32s3-trial] +extends = env:pro-esp32-trial +board = esp32-s3-devkitc-1 + +; lolin32_lite + +[env:oss-lolin32_lite] +extends = env:oss-esp32 +board = lolin32_lite + +[env:oss-lolin32_lite-debug] +extends = env:oss-esp32-debug +board = lolin32_lite + +[env:pro-lolin32_lite] +extends = env:pro-esp32 +board = lolin32_lite + +[env:pro-lolin32_lite-debug] +extends = env:pro-esp32-debug +board = lolin32_lite + +[env:pro-lolin32_lite-trial] +extends = env:pro-esp32-trial +board = lolin32_lite + +; lolin_s2_mini + +[env:oss-lolin_s2_mini] +extends = env:oss-esp32 +board = lolin_s2_mini +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + -D MYCILA_JSY_SERIAL=Serial1 + +[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} + -D MYCILA_JSY_SERIAL=Serial1 + +[env:pro-lolin_s2_mini] +extends = env:pro-esp32 +board = lolin_s2_mini +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + -D MYCILA_JSY_SERIAL=Serial1 + +[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} + -D MYCILA_JSY_SERIAL=Serial1 + +[env:pro-lolin_s2_mini-trial] +extends = env:pro-esp32-trial +board = lolin_s2_mini +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + -D MYCILA_JSY_SERIAL=Serial1 + +; esp32_poe + +[env:oss-esp32_poe] +extends = env:oss-esp32 +board = esp32-poe + +[env:oss-esp32_poe-debug] +extends = env:oss-esp32-debug +board = esp32-poe + +[env:pro-esp32_poe] +extends = env:pro-esp32 +board = esp32-poe + +[env:pro-esp32_poe-debug] +extends = env:pro-esp32-debug +board = esp32-poe + +[env:pro-esp32_poe-trial] +extends = env:pro-esp32-trial +board = esp32-poe + +; wt32_eth01 + +[env:oss-wt32_eth01] +extends = env:oss-esp32 +board = wt32-eth01 + +[env:oss-wt32_eth01-debug] +extends = env:oss-esp32-debug +board = wt32-eth01 + +[env:pro-wt32_eth01] +extends = env:pro-esp32 +board = wt32-eth01 + +[env:pro-wt32_eth01-debug] +extends = env:pro-esp32-debug +board = wt32-eth01 + +[env:pro-wt32_eth01-trial] +extends = env:pro-esp32-trial +board = wt32-eth01 + +; lilygo_eth_lite_s3 + +[env:oss-lilygo_eth_lite_s3] +extends = env:oss-esp32 +board = esp32s3box +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${release.build_flags} + -D BOARD_HAS_PSRAM + +[env:oss-lilygo_eth_lite_s3-debug] +extends = env:oss-esp32-debug +board = esp32s3box +build_flags = + ${env.build_flags} + ${oss.build_flags} + ${debug.build_flags} + -D BOARD_HAS_PSRAM + +monitor_speed = 460800 + +[env:pro-lilygo_eth_lite_s3] +extends = env:pro-esp32 +board = esp32s3box +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${release.build_flags} + -D BOARD_HAS_PSRAM + +[env:pro-lilygo_eth_lite_s3-debug] +extends = env:pro-esp32-debug +board = esp32s3box +build_flags = + ${env.build_flags} + ${pro.build_flags} + ${debug.build_flags} + -D BOARD_HAS_PSRAM + +[env:pro-lilygo_eth_lite_s3-trial] +extends = env:pro-esp32-trial +board = esp32s3box +build_flags = + ${env.build_flags} + ${trial.build_flags} + ${release.build_flags} + -D BOARD_HAS_PSRAM diff --git a/platformio_override.ini.template b/platformio_override.ini.template new file mode 100644 index 0000000..9d286e5 --- /dev/null +++ b/platformio_override.ini.template @@ -0,0 +1,43 @@ +; *** 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 +; default_envs = pro-esp32-trial +; default_envs = pro-esp32 +; default_envs = oss-esp32-debug +; default_envs = oss-esp32 + +[env] + +; --------------------------------- +; SERIAL MONITOR: CHOOSE ONE OPTION +; --------------------------------- + +monitor_port = /dev/cu.usbserial-0001 +; monitor_port = socket://arduino-playground.local:4444 + +; ------------------------- +; UPLOAD: CHOOSE ONE OPTION +; ------------------------- + +upload_protocol = esptool +upload_port = /dev/cu.usbserial-0001 + +; upload_protocol = espota +; upload_port = 192.168.125.139 + +; upload_protocol = custom +; extra_scripts = +; pre:pio/data.py +; pre:pio/version.py +; post:pio/factory.py +; upload_url = http://arduino.local/update diff --git a/src/YaSolRConfig.cpp b/src/YaSolRConfig.cpp new file mode 100644 index 0000000..a37aa75 --- /dev/null +++ b/src/YaSolRConfig.cpp @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#ifndef YASOLR_DEBUG_ENABLE +#define YASOLR_DEBUG_ENABLE false +#endif + +// network + +#ifndef YASOLR_AP_MODE_ENABLE +#define YASOLR_AP_MODE_ENABLE false +#endif + +#ifndef YASOLR_WIFI_CONNECTION_TIMEOUT +#define YASOLR_WIFI_CONNECTION_TIMEOUT 15 +#endif + +#ifndef YASOLR_CAPTURE_PORTAL_TIMEOUT +#define YASOLR_CAPTURE_PORTAL_TIMEOUT 180 +#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 + +void YaSolR::ConfigClass::begin() { + 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(YASOLR_WIFI_CONNECTION_TIMEOUT)); + // portal + Mycila::Config.configure(KEY_CAPTURE_PORTAL_TIMEOUT, String(YASOLR_CAPTURE_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); +} + +namespace YaSolR { + ConfigClass Config; +} // namespace YaSolR diff --git a/src/YaSolRConnector.cpp b/src/YaSolRConnector.cpp new file mode 100644 index 0000000..e6a367d --- /dev/null +++ b/src/YaSolRConnector.cpp @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define TAG "I/O" + +void YaSolR::Connector::begin() { + const String baseTopic = Mycila::Config.get(KEY_MQTT_TOPIC); + + // app + + Mycila::HTTPd.apiGET("/app", [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + Mycila::AppInfo.toJson(response->getRoot()); + response->setLength(); + request->send(response); + }); + + // 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); + }); + + 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) { +#if ARDUINOJSON_VERSION_MAJOR == 6 + AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096); +#else + AsyncJsonResponse* response = new AsyncJsonResponse(false); +#endif + 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::MQTT.subscribe(baseTopic + "/relays/" NAME_RELAY1 "/state/set", [this](const String& topic, const String& payload) { + if (Mycila::RelayManager.relays[0].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(0, true, duration); + else if (state == YASOLR_OFF) + Mycila::RelayManager.tryRelayState(0, false, duration); + } + }); + Mycila::MQTT.subscribe(baseTopic + "/relays/" NAME_RELAY2 "/state/set", [this](const String& topic, const String& payload) { + if (Mycila::RelayManager.relays[1].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(1, true, duration); + else if (state == YASOLR_OFF) + Mycila::RelayManager.tryRelayState(1, false, duration); + } + }); + + Mycila::HTTPd.apiPOST("/relays/" NAME_RELAY1, [this](AsyncWebServerRequest* request) { + if (Mycila::RelayManager.relays[0].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(0, true, duration); + else if (state == YASOLR_OFF) + Mycila::RelayManager.tryRelayState(0, false, duration); + } + request->send(200); + }); + Mycila::HTTPd.apiPOST("/relays/" NAME_RELAY2, [this](AsyncWebServerRequest* request) { + if (Mycila::RelayManager.relays[1].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(1, true, duration); + else if (state == YASOLR_OFF) + Mycila::RelayManager.tryRelayState(1, 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::MQTT.subscribe(baseTopic + "/router/" NAME_OUTPUT1 "/dimmer/level/set", [](const String& topic, const String& payload) { + Mycila::Router.outputs[0].tryDimmerLevel(payload.toInt()); + }); + Mycila::MQTT.subscribe(baseTopic + "/router/" NAME_OUTPUT2 "/dimmer/level/set", [](const String& topic, const String& payload) { + Mycila::Router.outputs[1].tryDimmerLevel(payload.toInt()); + }); + + Mycila::MQTT.subscribe(baseTopic + "/router/" NAME_OUTPUT1 "/bypass_relay/state/set", [](const String& topic, const String& payload) { + if (Mycila::Router.outputs[0].isBypassRelayEnabled()) { + if (payload == YASOLR_ON) + Mycila::Router.outputs[0].tryBypassRelayState(true); + else if (payload == YASOLR_OFF) + Mycila::Router.outputs[0].tryBypassRelayState(false); + } + }); + Mycila::MQTT.subscribe(baseTopic + "/router/" NAME_OUTPUT2 "/bypass_relay/state/set", [](const String& topic, const String& payload) { + if (Mycila::Router.outputs[1].isBypassRelayEnabled()) { + if (payload == YASOLR_ON) + Mycila::Router.outputs[1].tryBypassRelayState(true); + else if (payload == YASOLR_OFF) + Mycila::Router.outputs[1].tryBypassRelayState(false); + } + }); + + Mycila::HTTPd.apiPOST("/router/" NAME_OUTPUT1 "/dimmer", [](AsyncWebServerRequest* request) { + if (request->hasParam("level", true)) + Mycila::Router.outputs[0].tryDimmerLevel(request->getParam("level", true)->value().toInt()); + request->send(200); + }); + Mycila::HTTPd.apiPOST("/router/" NAME_OUTPUT2 "/dimmer", [](AsyncWebServerRequest* request) { + if (request->hasParam("level", true)) + Mycila::Router.outputs[1].tryDimmerLevel(request->getParam("level", true)->value().toInt()); + request->send(200); + }); + + Mycila::HTTPd.apiPOST("/router/" NAME_OUTPUT1 "/bypass_relay", [](AsyncWebServerRequest* request) { + if (Mycila::Router.outputs[0].isBypassRelayEnabled() && request->hasParam("state", true)) { + String state = request->getParam("state", true)->value(); + if (state == YASOLR_ON) + Mycila::Router.outputs[0].tryBypassRelayState(true); + else if (state == YASOLR_OFF) + Mycila::Router.outputs[0].tryBypassRelayState(false); + } + request->send(200); + }); + Mycila::HTTPd.apiPOST("/router/" NAME_OUTPUT2 "/bypass_relay", [](AsyncWebServerRequest* request) { + if (Mycila::Router.outputs[1].isBypassRelayEnabled() && request->hasParam("state", true)) { + String state = request->getParam("state", true)->value(); + if (state == YASOLR_ON) + Mycila::Router.outputs[1].tryBypassRelayState(true); + else if (state == YASOLR_OFF) + Mycila::Router.outputs[1].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::MQTT.subscribe(baseTopic + "/system/restart", [](const String& topic, const String& payload) { Mycila::System.restart(); }); + + Mycila::MQTT.subscribe(baseTopic + "/system/reset", [](const String& topic, const String& payload) { Mycila::System.reset(); }); + + Mycila::HTTPd.apiANY("/system/restart", [](AsyncWebServerRequest* request) { + request->send(200); + Mycila::System.restart(); + }); + + Mycila::HTTPd.apiANY("/system/reset", [](AsyncWebServerRequest* request) { + request->send(200); + Mycila::System.reset(); + }); + + 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(); + _systemTempSensor->toJson(temp_sensor); + response->setLength(); + request->send(response); + }); + + Mycila::MQTT.onConnect([this](void) { + Mycila::Logger.info(TAG, "MQTT connected!"); + _publishHADiscovery = true; + }); +} + +void YaSolR::Connector::publish() { + _lastPublish = 0; +} + +void YaSolR::Connector::loop() { + const uint32_t start = millis(); + + if (Mycila::MQTT.isConnected()) { + if (millis() - _lastPublish >= Mycila::Config.get(KEY_MQTT_PUBLISH_INTERVAL).toInt() * 1000) { + _publish(); + yield(); + } + + if (_publishHADiscovery) { + YaSolR::HADiscoveryController.publish(); + _publishHADiscovery = false; + yield(); + } + } + + const uint32_t elapsed = millis() - start; + if (elapsed > 2000) + Mycila::Logger.warn(TAG, "Abnormal loop duration: %u ms", elapsed); +} + +void YaSolR::Connector::_publish() { + if (Mycila::MQTT.isConnected()) { + const uint32_t start = millis(); + const String baseTopic = Mycila::Config.get(KEY_MQTT_TOPIC); + Mycila::Logger.debug(TAG, "Publishing..."); + + // 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 + { + Mycila::MQTT.publish(baseTopic + "/network/ip_address", ESPConnect.getIPAddress().toString()); + Mycila::MQTT.publish(baseTopic + "/network/mac_address", ESPConnect.getMacAddress()); + 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 + { + for (size_t i = 0; i < Mycila::RelayManager.relays.size(); i++) { + auto& relay = Mycila::RelayManager.relays[i]; + Mycila::MQTT.publish(baseTopic + "/relays/" + relay.name + "/enabled", YASOLR_BOOL(relay.isEnabled())); + Mycila::MQTT.publish(baseTopic + "/relays/" + relay.name + "/state", YASOLR_STATE(relay.isOn())); + Mycila::MQTT.publish(baseTopic + "/relays/" + relay.name + "/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())); + for (int i = 0; i < Mycila::Router.outputs.size(); i++) { + auto& output = Mycila::Router.outputs[i]; + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/enabled", YASOLR_BOOL(output.isEnabled())); + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/state", output.getStateString()); + + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/dimmer/enabled", YASOLR_BOOL(output.dimmer.isEnabled())); + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/dimmer/level", String(output.dimmer.getLevel())); + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/dimmer/state", YASOLR_STATE(output.dimmer.isOn())); + + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/temp_sensor/enabled", YASOLR_BOOL(output.temperatureSensor.isEnabled())); + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/temp_sensor/temperature", String(output.temperatureSensor.getTemperature())); + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/temp_sensor/valid", YASOLR_BOOL(output.temperatureSensor.isValid())); + + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/bypass_relay/enabled", YASOLR_BOOL(output.relay.isEnabled())); + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/bypass_relay/state", YASOLR_STATE(output.relay.isOn())); + Mycila::MQTT.publish(baseTopic + "/router/" + output.name + "/bypass_relay/switch_count", String(output.relay.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(_systemTempSensor->isEnabled())); + Mycila::MQTT.publish(baseTopic + "/system/temp_sensor/temperature", String(_systemTempSensor->getTemperature())); + Mycila::MQTT.publish(baseTopic + "/system/temp_sensor/valid", YASOLR_BOOL(_systemTempSensor->isValid())); + } + yield(); + + _lastPublish = millis(); + Mycila::Logger.debug(TAG, "Published in %u ms", (_lastPublish - start)); + } +} diff --git a/src/YaSolRController.cpp b/src/YaSolRController.cpp new file mode 100644 index 0000000..cefa0ac --- /dev/null +++ b/src/YaSolRController.cpp @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "CONTROL" + +void YaSolR::Controller::begin() { + Mycila::Logger.info(TAG, "Enable Event Manager..."); + + Mycila::Button.listen([this](const String& action) { + if (action == "restart") + _restartThrottle.setEnable(true); + else if (action == "reset") + _resetThrottle.setEnable(true); + else if (action == "bypass") { + for (auto& output : Mycila::Router.outputs) + output.tryBypassRelayToggle(); + _triggerRefresh(); + } + }); + + Mycila::System.listen([this](Mycila::SystemEvent event) { + if (event == Mycila::SystemEvent::BEFORE_RESET) { + 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::Display.end(); + Mycila::Buzzer.beep(3); + Mycila::Buzzer.end(); + Mycila::Router.end(); + Mycila::RelayManager.end(); + Mycila::JSY.end(); + Mycila::ZCD.end(); + Mycila::Config.end(); + Mycila::Lights.end(); + ESPConnect.end(); + } else if (event == Mycila::SystemEvent::BEFORE_RESTART) { + 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::Display.end(); + Mycila::Buzzer.beep(2); + Mycila::Buzzer.end(); + Mycila::Router.end(); + Mycila::RelayManager.end(); + Mycila::JSY.end(); + Mycila::ZCD.end(); + Mycila::Config.end(); + Mycila::Lights.end(); + ESPConnect.end(); + } + }); + + Mycila::OTA.listen([this]() { + Mycila::Logger.info(TAG, "Disabling services in preparation for OTA Update..."); + Mycila::RelayManager.end(); + Mycila::Router.end(); + Mycila::Lights.setAllOn(); + Mycila::JSY.end(); + Mycila::ZCD.end(); + Mycila::MQTT.end(); + }); + + Mycila::OTA.listen([this](bool success) { + if (success) { + Mycila::Logger.info(TAG, "OTA Update Success! Restarting..."); + } else { + Mycila::Logger.error(TAG, "OTA Failed!"); + } + _restartThrottle.setEnable(true); + }); + + ESPConnect.listen([this](ESPConnectState previous, ESPConnectState state) { + Mycila::Logger.debug(TAG, "NetworkState: %s => %s", ESPConnect.getStateName(previous), ESPConnect.getStateName(state)); + switch (state) { + case ESPConnectState::STA_CONNECTED: + Mycila::Lights.setRed(false); + Mycila::Logger.info(TAG, "Connected to WiFi %s with IP address %s", ESPConnect.getWiFiSSID().c_str(), ESPConnect.getIPAddress().toString().c_str()); + _events.push(Event::NETWORK_CONNECTED); + break; + case ESPConnectState::AP_CONNECTED: + 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()); + _events.push(Event::NETWORK_CONNECTED); + break; + case ESPConnectState::STA_DISCONNECTED: + Mycila::Lights.setRed(true); + Mycila::Logger.warn(TAG, "Disconnected from WiFi %s", ESPConnect.getWiFiSSIDConfigured().c_str()); + _events.push(Event::NETWORK_DISCONNECTED); + break; + case ESPConnectState::NETWORK_DISABLED: + Mycila::Lights.setRed(true); + Mycila::Logger.info(TAG, "Disable Network..."); + break; + case ESPConnectState::STA_CONNECTING: + Mycila::Lights.setRed(true); + Mycila::Logger.info(TAG, "Connecting to WiFi %s (timeout: %u seconds)...", ESPConnect.getWiFiSSIDConfigured().c_str(), ESPConnect.getWiFiConnectTimeout()); + break; + case ESPConnectState::AP_CONNECTING: + 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.isAPMode(); + 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.getWiFiSSIDConfigured()); + Mycila::Config.set(KEY_WIFI_PASSWORD, ESPConnect.getWiFiPassword()); + } + _restartThrottle.setEnable(true); + break; + } + case ESPConnectState::PORTAL_TIMEOUT: + Mycila::Logger.warn(TAG, "Captive Portal: timed out."); + _restartThrottle.setEnable(true); + break; + case ESPConnectState::STA_TIMEOUT: + Mycila::Logger.error(TAG, "Unable to connect to SSID: %s", ESPConnect.getWiFiSSIDConfigured().c_str()); + break; + case ESPConnectState::STA_RECONNECTING: + Mycila::Logger.info(TAG, "Trying to reconnect to WiFi %s", ESPConnect.getWiFiSSIDConfigured().c_str()); + break; + default: + break; + } + + _triggerRefresh(); + }); + + Mycila::Config.listen([this](const String& key, const String& oldValue, const String& newValue) { + if (key == KEY_MQTT_ENABLE || key == KEY_HA_DISCOVERY_ENABLE) + _events.push(Event::MQTT_RECONFIGURED); + else if (key == KEY_NTP_TIMEZONE || key == KEY_NTP_SERVER) + _events.push(Event::NTP_RECONFIGURED); + else if (key == KEY_SYSTEM_TEMP_ENABLE) + _events.push(Event::SYS_TEMP_RECONFIGURED); + else if (key == KEY_DISPLAY_ENABLE) + _events.push(Event::DISPLAY_RECONFIGURED); + else if (key == KEY_BUTTON_ENABLE) + _events.push(Event::BUTTON_RECONFIGURED); + else if (key == KEY_AP_MODE_ENABLE && (ESPConnect.getState() == ESPConnectState::AP_CONNECTED || + ESPConnect.getState() == ESPConnectState::STA_CONNECTING || + ESPConnect.getState() == ESPConnectState::STA_CONNECTED || + ESPConnect.getState() == ESPConnectState::STA_TIMEOUT || + ESPConnect.getState() == ESPConnectState::STA_DISCONNECTED || + ESPConnect.getState() == ESPConnectState::STA_RECONNECTING)) + _events.push(Event::NETWORK_RECONFIGURED); + else if (key == KEY_BUZZER_ENABLE) + _events.push(Event::BUZZER_RECONFIGURED); + else if (key == KEY_LIGHTS_ENABLE) + _events.push(Event::LIGHTS_RECONFIGURED); + else if (key == KEY_RELAY1_ENABLE) + _events.push(Event::RELAY1_RECONFIGURED); + else if (key == KEY_RELAY2_ENABLE) + _events.push(Event::RELAY2_RECONFIGURED); + else if (key == KEY_OUTPUT1_RELAY_ENABLE) + _events.push(Event::OUT1_RELAY_RECONFIGURED); + else if (key == KEY_OUTPUT1_TEMP_ENABLE) + _events.push(Event::OUT1_TEMP_RECONFIGURED); + else if (key == KEY_OUTPUT2_RELAY_ENABLE) + _events.push(Event::OUT2_RELAY_RECONFIGURED); + else if (key == KEY_OUTPUT2_TEMP_ENABLE) + _events.push(Event::OUT2_TEMP_RECONFIGURED); + else if (key == KEY_OUTPUT1_DIMMER_ENABLE) + _events.push(Event::OUT1_DIMMER_RECONFIGURED); + else if (key == KEY_OUTPUT2_DIMMER_ENABLE) + _events.push(Event::OUT2_DIMMER_RECONFIGURED); + else if (key == KEY_JSY_ENABLE) + _events.push(Event::JSY_RECONFIGURED); + else if (key == KEY_ZCD_ENABLE) + _events.push(Event::ZCD_RECONFIGURED); + else if (key == KEY_GRID_POWER_MQTT_TOPIC) + _events.push(Event::POWER_MANAGER_RECONFIGURED); + + _triggerRefresh(); + }); + + Mycila::Config.listen([this]() { + Mycila::Logger.info(TAG, "Configuration restored. Restarting..."); + _restartThrottle.setEnable(true); + }); + + for (auto& relay : Mycila::RelayManager.relays) + relay.listen([this](bool state) { _triggerRefresh(); }); + + for (auto& output : Mycila::Router.outputs) { + output.dimmer.listen([this](Mycila::DimmerLevel event) { _triggerRefresh(); }); + output.relay.listen([this](bool state) { _triggerRefresh(); }); + output.temperatureSensor.listen([this](const char* name, float temperature) { + Mycila::Logger.info(TAG, "Temperature '%s' changed to %.02f °C", name, temperature); + _triggerRefresh(); + }); + } + + _systemTemp->listen([this](const char* name, float temperature) { + Mycila::Logger.info(TAG, "Temperature '%s' changed to %.02f °C", name, temperature); + _triggerRefresh(); + }); +} + +void YaSolR::Controller::loop() { + if (!_events.empty()) { + // take last event + YaSolR::Event event = _events.front(); + _events.pop(); + + // discard same events + while (!_events.empty() && _events.front() == event) + _events.pop(); + + switch (event) { + case YaSolR::Event::MQTT_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring MQTT..."); + Mycila::MQTT.end(); + Mycila::MQTT.begin(); + break; + } + case YaSolR::Event::NTP_RECONFIGURED: { + Mycila::NTP.setTimeZone(Mycila::Config.get(KEY_NTP_TIMEZONE)); + if (!Mycila::Config.getBool(KEY_AP_MODE_ENABLE)) { + Mycila::NTP.sync(Mycila::Config.get(KEY_NTP_SERVER)); + _triggerRefresh(); + } + break; + } + case YaSolR::Event::RELAY1_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Relay 1..."); + Mycila::RelayManager.relays[0].end(); + Mycila::RelayManager.relays[0].begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::RELAY2_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Relay 2..."); + Mycila::RelayManager.relays[1].end(); + Mycila::RelayManager.relays[1].begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::BUZZER_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Buzzer..."); + Mycila::Buzzer.end(); + Mycila::Buzzer.begin(); + if (Mycila::Buzzer.isEnabled()) + Mycila::Buzzer.beep(); + _triggerRefresh(); + break; + } + + case YaSolR::Event::BUTTON_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Button..."); + Mycila::Button.end(); + Mycila::Button.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::LIGHTS_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring LEDs..."); + Mycila::LightState g = Mycila::Lights.getGreen(); + Mycila::LightState y = Mycila::Lights.getYellow(); + Mycila::LightState r = Mycila::Lights.getRed(); + Mycila::Lights.end(); + Mycila::Lights.begin(g, y, r); + _triggerRefresh(); + break; + } + case YaSolR::Event::SYS_TEMP_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring System Temperature Sensor..."); + _systemTemp->end(); + _systemTemp->begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::OUT1_RELAY_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Output 1 Bypass '%s'...", Mycila::Router.outputs[0].relay.name); + Mycila::Router.outputs[0].relay.end(); + Mycila::Router.outputs[0].relay.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::OUT1_TEMP_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Temperature Sensor '%s'...", Mycila::Router.outputs[0].temperatureSensor.name); + Mycila::Router.outputs[0].temperatureSensor.end(); + Mycila::Router.outputs[0].temperatureSensor.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::OUT1_DIMMER_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Dimmer '%s'...", Mycila::Router.outputs[0].dimmer.name); + Mycila::Router.outputs[0].dimmer.end(); + Mycila::Router.outputs[0].dimmer.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::OUT2_RELAY_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Output 1 Bypass '%s'...", Mycila::Router.outputs[1].relay.name); + Mycila::Router.outputs[1].relay.end(); + Mycila::Router.outputs[1].relay.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::OUT2_TEMP_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Temperature Sensor '%s'...", Mycila::Router.outputs[1].temperatureSensor.name); + Mycila::Router.outputs[1].temperatureSensor.end(); + Mycila::Router.outputs[1].temperatureSensor.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::OUT2_DIMMER_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Dimmer '%s'...", Mycila::Router.outputs[1].dimmer.name); + Mycila::Router.outputs[1].dimmer.end(); + Mycila::Router.outputs[1].dimmer.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::JSY_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring 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() && 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 not enabled"); + } + _triggerRefresh(); + break; + } + case YaSolR::Event::ZCD_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring ZCD..."); + Mycila::ZCD.end(); + Mycila::ZCD.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::POWER_MANAGER_RECONFIGURED: { + Mycila::Logger.info(TAG, "Reconfiguring Grid..."); + Mycila::Grid.end(); + Mycila::Grid.begin(); + _triggerRefresh(); + break; + } + case YaSolR::Event::NETWORK_RECONFIGURED: + Mycila::Logger.info(TAG, "Reconfiguring Network..."); + Mycila::MQTT.end(); + Mycila::HTTPd.end(); + Mycila::OTA.end(); + ESPConnect.end(); + + Mycila::Logger.info(TAG, "Enable Network..."); + ESPConnect.setAutoRestart(false); + ESPConnect.setBlocking(false); + ESPConnect.setCaptivePortalTimeout(Mycila::Config.get(KEY_CAPTURE_PORTAL_TIMEOUT).toInt()); + ESPConnect.setWiFiConnectTimeout(Mycila::Config.get(KEY_WIFI_CONNECTION_TIMEOUT).toInt()); + ESPConnect.begin(&Mycila::HTTPd.server, 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)}); + + _triggerRefresh(); + break; + + case YaSolR::Event::NETWORK_CONNECTED: + Mycila::OTA.begin(); + Mycila::HTTPd.begin(); + Mycila::MQTT.begin(); + if (!Mycila::Config.getBool(KEY_AP_MODE_ENABLE)) + Mycila::NTP.sync(Mycila::Config.get(KEY_NTP_SERVER)); + _triggerRefresh(); + break; + + case YaSolR::Event::NETWORK_DISCONNECTED: + Mycila::MQTT.end(); + Mycila::HTTPd.end(); + Mycila::OTA.end(); + Mycila::Buzzer.beep(2); + break; + + case YaSolR::Event::DISPLAY_RECONFIGURED: + Mycila::Display.end(); + Mycila::Display.begin(); + updateDisplay(); + break; + + default: + break; + } + } + + if (millis() - _lastUpdate >= 500) { + Mycila::Lights.setYellow(Mycila::RelayManager.relays[0].isOn() || + Mycila::RelayManager.relays[1].isOn() || + Mycila::Router.outputs[0].relay.isOn() || + Mycila::Router.outputs[1].relay.isOn() || + Mycila::Router.outputs[0].dimmer.isOn() || + Mycila::Router.outputs[1].dimmer.isOn()); + + updateDisplay(); + } + + if (_updateOnceThrottle.isAllowedMillis(500)) { + _updateOnceThrottle.setEnable(false); + _website->update(); + _connector->publish(); + } + + if (_restartThrottle.isAllowedMillis(500)) { + _restartThrottle.setEnable(false); + Mycila::System.restart(); + } + + if (_resetThrottle.isAllowedMillis(500)) { + _resetThrottle.setEnable(false); + Mycila::System.reset(); + } +} + +void YaSolR::Controller::_triggerRefresh() { + _updateOnceThrottle.setEnable(true); +} + +/* +| **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::Controller::updateDisplay() { + if (Mycila::Display.isEnabled()) { + // const uint32_t start = millis(); + + 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::STA_CONNECTED: + networkState = ESPConnect.getIPAddress().toString(); + break; + case ESPConnectState::STA_DISCONNECTED: + networkState = "Disconnected!"; + break; + case ESPConnectState::STA_CONNECTING: + networkState = "Connecting..."; + break; + case ESPConnectState::STA_RECONNECTING: + networkState = "Reconnecting..."; + break; + case ESPConnectState::STA_TIMEOUT: + networkState = "Connect Timeout!"; + break; + case ESPConnectState::AP_CONNECTED: + 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(), _systemTemp->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++; + + for (size_t i = 0; i < 2; i++) { + const auto& output = Mycila::Router.outputs[i]; + snprintf(lines[next], MYCILA_DISPLAY_LINE_LENGTH, "O%u: %-13.13s %-5.5s C", (i + 1), output.getStateString(), output.temperatureSensor.getTemperatureAsString().c_str()); + next++; + } + + snprintf(lines[next], MYCILA_DISPLAY_LINE_LENGTH, "Relay1: %-3.3s Relay2: %-3.3s", Mycila::RelayManager.relays[0].isOn() ? "ON" : "OFF", Mycila::RelayManager.relays[1].isOn() ? "ON" : "OFF"); + next++; + + for (; next < 6; next++) + lines[next][0] = '\0'; + + Mycila::Display.print(lines); + + // Mycila::Logger.debug("DISPLAY", "Updated in %u ms", millis() - start); + } +} diff --git a/src/YaSolRHADiscoveryController.cpp b/src/YaSolRHADiscoveryController.cpp new file mode 100644 index 0000000..34fafa4 --- /dev/null +++ b/src/YaSolRHADiscoveryController.cpp @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#define TAG "HA" + +void YaSolR::HADiscoveryControllerClass::publish() { + 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; + + const uint32_t start = millis(); + Mycila::Logger.debug(TAG, "Publishing..."); + + // 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); + + yield(); + + // 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)); + + yield(); + + // 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); + + Mycila::Logger.debug(TAG, "Published in %u ms", (millis() - start)); +} + +namespace YaSolR { + HADiscoveryControllerClass HADiscoveryController; +} // namespace YaSolR diff --git a/src/YaSolRMycilaConfig.cpp b/src/YaSolRMycilaConfig.cpp new file mode 100644 index 0000000..77d6e19 --- /dev/null +++ b/src/YaSolRMycilaConfig.cpp @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// logger + +uint8_t Mycila::LoggerClass::getLevel() const { return Mycila::Config.getBool(KEY_DEBUG_ENABLE) ? ARDUHAL_LOG_LEVEL_DEBUG : ARDUHAL_LOG_LEVEL_INFO; } + +// button + +bool Mycila::ButtonConfigClass::isEnabled() const { return Mycila::Config.getBool(KEY_BUTTON_ENABLE); } +int32_t Mycila::ButtonConfigClass::getPin() const { return Mycila::Config.get(KEY_BUTTON_PIN).toInt(); } +String Mycila::ButtonConfigClass::getPressAction() const { return Mycila::Config.get(KEY_BUTTON_ACTION); } +String Mycila::ButtonConfigClass::getLongPressAction() const { return "reset"; } + +// ota + +String Mycila::OTAConfigClass::getHostname() const { return Mycila::Config.get(KEY_HOSTNAME); } +String Mycila::OTAConfigClass::getUsername() const { return YASOLR_ADMIN_USERNAME; } +String Mycila::OTAConfigClass::getPassword() const { return Mycila::Config.get(KEY_ADMIN_PASSWORD); } +String Mycila::OTAConfigClass::getID() const { return AppInfo.firmware; } +String Mycila::OTAConfigClass::getTitle() const { return AppInfo.name + " Web Updater"; } +String Mycila::OTAConfigClass::getFWVersion() const { return AppInfo.version; } + +// 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}; +} + +// temperature sensors + +bool Mycila::TemperatureSensorConfigClass::isEnabled(const char* name) const { + if (strcmp(name, NAME_SYSTEM) == 0) + return Mycila::Config.getBool(KEY_SYSTEM_TEMP_ENABLE); + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.getBool(KEY_OUTPUT1_TEMP_ENABLE); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.getBool(KEY_OUTPUT2_TEMP_ENABLE); + return false; +} +int32_t Mycila::TemperatureSensorConfigClass::getPin(const char* name) const { + if (strcmp(name, NAME_SYSTEM) == 0) + return Mycila::Config.get(KEY_SYSTEM_TEMP_PIN).toInt(); + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.get(KEY_OUTPUT1_TEMP_PIN).toInt(); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.get(KEY_OUTPUT2_TEMP_PIN).toInt(); + return GPIO_NUM_NC; +} + +// buzzer + +bool Mycila::BuzzerConfigClass::isEnabled() const { return Mycila::Config.getBool(KEY_BUZZER_ENABLE); } +int32_t Mycila::BuzzerConfigClass::getPin() const { return Mycila::Config.get(KEY_BUZZER_PIN).toInt(); } + +// leds + +bool Mycila::LightsConfigClass::isEnabled() const { return Mycila::Config.getBool(KEY_LIGHTS_ENABLE); } +int32_t Mycila::LightsConfigClass::getGreenPin() const { return Mycila::Config.get(KEY_LIGHTS_GREEN_PIN).toInt(); } +int32_t Mycila::LightsConfigClass::getYellowPin() const { return Mycila::Config.get(KEY_LIGHTS_YELLOW_PIN).toInt(); } +int32_t Mycila::LightsConfigClass::getRedPin() const { return Mycila::Config.get(KEY_LIGHTS_RED_PIN).toInt(); } + +// bypass + +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"; +} + +// httpd + +String Mycila::HTTPdConfigClass::getUsername() const { return YASOLR_ADMIN_USERNAME; } +String Mycila::HTTPdConfigClass::getPassword() const { return Mycila::Config.get(KEY_ADMIN_PASSWORD); } + +// relays + +const Mycila::RelayConfig Mycila::Relay::getConfig(const char* name) { + if (strcmp(name, NAME_OUTPUT1) == 0) + return { + Mycila::Config.getBool(KEY_OUTPUT1_RELAY_ENABLE), + static_cast(Mycila::Config.get(KEY_OUTPUT1_RELAY_PIN).toInt()), + Mycila::Config.get(KEY_OUTPUT1_RELAY_TYPE) == "NC" ? Mycila::RelayType::NC : Mycila::RelayType::NO}; + + if (strcmp(name, NAME_OUTPUT2) == 0) + return { + Mycila::Config.getBool(KEY_OUTPUT2_RELAY_ENABLE), + static_cast(Mycila::Config.get(KEY_OUTPUT2_RELAY_PIN).toInt()), + Mycila::Config.get(KEY_OUTPUT2_RELAY_TYPE) == "NC" ? Mycila::RelayType::NC : Mycila::RelayType::NO}; + + if (strcmp(name, NAME_RELAY1) == 0) + return { + Mycila::Config.getBool(KEY_RELAY1_ENABLE), + static_cast(Mycila::Config.get(KEY_RELAY1_PIN).toInt()), + Mycila::Config.get(KEY_RELAY1_TYPE) == "NC" ? Mycila::RelayType::NC : Mycila::RelayType::NO}; + + if (strcmp(name, NAME_RELAY2) == 0) + return { + Mycila::Config.getBool(KEY_RELAY2_ENABLE), + static_cast(Mycila::Config.get(KEY_RELAY2_PIN).toInt()), + Mycila::Config.get(KEY_RELAY2_TYPE) == "NC" ? Mycila::RelayType::NC : Mycila::RelayType::NO}; + + return {false, static_cast(GPIO_NUM_NC), Mycila::RelayType::NO}; +} + +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; +} + +// display + +bool Mycila::DisplayConfigClass::isEnabled() const { return Mycila::Config.getBool(KEY_DISPLAY_ENABLE); } +Mycila::DisplayType Mycila::DisplayConfigClass::getType() const { + const String val = Mycila::Config.get(KEY_DISPLAY_TYPE); + if (val == "SSD1306") + return Mycila::DisplayType::SSD1306; + if (val == "SH1107") + return Mycila::DisplayType::SH1107; + return Mycila::DisplayType::SH1106; +} +int32_t Mycila::DisplayConfigClass::getClockPin() const { return Mycila::Config.get(KEY_DISPLAY_CLOCK_PIN).toInt(); } +int32_t Mycila::DisplayConfigClass::getDataPin() const { return Mycila::Config.get(KEY_DISPLAY_DATA_PIN).toInt(); } +uint32_t Mycila::DisplayConfigClass::getRotation() const { + const uint32_t val = Mycila::Config.get(KEY_DISPLAY_ROTATION).toInt(); + return val == 90 || val == 180 || val == 270 ? val : 0; +} +uint32_t Mycila::DisplayConfigClass::getPowerSaveDelay() const { return Mycila::Config.get(KEY_DISPLAY_POWER_SAVE_DELAY).toInt(); } + +// dimmer + +bool Mycila::DimmerConfigClass::isEnabled(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.getBool(KEY_OUTPUT1_DIMMER_ENABLE); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.getBool(KEY_OUTPUT2_DIMMER_ENABLE); + return false; +} +int32_t Mycila::DimmerConfigClass::getPin(const char* name) const { + if (strcmp(name, NAME_OUTPUT1) == 0) + return Mycila::Config.get(KEY_OUTPUT1_DIMMER_PIN).toInt(); + if (strcmp(name, NAME_OUTPUT2) == 0) + return Mycila::Config.get(KEY_OUTPUT2_DIMMER_PIN).toInt(); + return GPIO_NUM_NC; +} +Mycila::DimmerType Mycila::DimmerConfigClass::getType(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; +} + +// power manager + +String Mycila::GridConfigClass::getGridPowerMQTTTopic() const { return Mycila::Config.get(KEY_GRID_POWER_MQTT_TOPIC); } + +// zero cross detection + +bool Mycila::ZeroCrossDetectionConfigClass::isEnabled() const { return Mycila::Config.getBool(KEY_ZCD_ENABLE); } +int32_t Mycila::ZeroCrossDetectionConfigClass::getPin() const { return Mycila::Config.get(KEY_ZCD_PIN).toInt(); } +uint8_t Mycila::ZeroCrossDetectionConfigClass::getGridFrequency() const { return Mycila::Config.get(KEY_GRID_FREQ).toInt() == 60 ? 60 : 50; } diff --git a/src/YaSolRWebsite.cpp b/src/YaSolRWebsite.cpp new file mode 100644 index 0000000..fc87999 --- /dev/null +++ b/src/YaSolRWebsite.cpp @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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::Website::begin() { +// 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); + _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 + + Mycila::HTTPd.server.rewrite("/dash/assets/logo/mini", "/logo-icon"); + Mycila::HTTPd.server.rewrite("/dash/assets/logo/large", "/logo"); + Mycila::HTTPd.server.rewrite("/dash/assets/logo", "/logo"); + Mycila::HTTPd.server.rewrite("/ota/logo/dark", "/logo"); + Mycila::HTTPd.server.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(Mycila::HTTPdConfig.getUsername().c_str(), Mycila::HTTPdConfig.getPassword().c_str()); + Mycila::HTTPd.server.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); + }); + + // app stats + + _appName.set((Mycila::AppInfo.name + " " + Mycila::AppInfo.model).c_str()); + _appManufacturerStat.set(Mycila::AppInfo.manufacturer.c_str()); + _appVersionStat.set(Mycila::AppInfo.version.c_str()); + _appFirmwareStat.set(Mycila::AppInfo.firmware.c_str()); + _appIdStat.set(Mycila::AppInfo.id.c_str()); + _cpuCoresStat.set(String(ESP.getChipCores()).c_str()); + _cpuModelStat.set(ESP.getChipModel()); + _memoryTotalStat.set((String(ESP.getHeapSize()) + " bytes").c_str()); + _macAddressStat.set(ESPConnect.getMacAddress().c_str()); + _bootCountStat.set(String(Mycila::System.getBootCount()).c_str()); + + // home callbacks + + _outputRelaySwitch(&_output1BypassSwitch, 0); + _outputRelaySwitch(&_output2BypassSwitch, 1); + _outputDimmerSlider(&_output1DimmerSlider, 0); + _outputDimmerSlider(&_output2DimmerSlider, 1); + _relaySwitch(&_relay1Switch, 0); + _relaySwitch(&_relay2Switch, 1); + +#ifdef APP_VERSION_PRO + // management + + _boolConfig(&_debugMode, KEY_DEBUG_ENABLE); + _restart.attachCallback(PUSH_BUTTON_CARD_CB { Mycila::System.restart(); }); + _reset.attachCallback(PUSH_BUTTON_CARD_CB { Mycila::System.reset(); }); + _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); + _sliderConfig(&_output1DimmerPowerLimit, KEY_OUTPUT1_DIMMER_LEVEL_LIMIT); + _numConfig(&_output1TempPin, KEY_OUTPUT1_TEMP_PIN); + _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); + _sliderConfig(&_output2DimmerPowerLimit, KEY_OUTPUT2_DIMMER_LEVEL_LIMIT); + _numConfig(&_output2TempPin, KEY_OUTPUT2_TEMP_PIN); + _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::Website::loop() { + if (millis() - _lastUpdate >= YASOLR_WEBSITE_UPDATE_INTERVAL * 1000) { + update(); + } +} + +void YaSolR::Website::update(bool skipWebSocketPush) { + if (_dashboard.isAsyncAccessInProgress()) { + return; + } + + // const uint32_t start = millis(); + + // stats + + Mycila::SystemMemory memory = Mycila::System.getMemory(); + _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()); + _ipAddressStat.set(ESPConnect.getIPAddress().toString().c_str()); + _memoryUsageStat.set((String(memory.usage) + " %").c_str()); + _memoryUsedStat.set((String(memory.used) + " bytes").c_str()); + _output1SwitchCountStat.set(String(Mycila::Router.outputs[0].relay.getSwitchCount()).c_str()); + _output2SwitchCountStat.set(String(Mycila::Router.outputs[1].relay.getSwitchCount()).c_str()); + _relay1SwitchCountStat.set(String(Mycila::RelayManager.relays[0].getSwitchCount()).c_str()); + _relay2SwitchCountStat.set(String(Mycila::RelayManager.relays[1].getSwitchCount()).c_str()); + _systemTempStat.set(_systemTempSensor->isValid() ? (String(_systemTempSensor->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()); + + // home + + switch (Mycila::Router.outputs[0].getState()) { + case Mycila::RouterOutputState::OUTPUT_DISABLED: + _output1State.update(Mycila::Router.outputs[0].getStateString(), DASH_STATUS_DANGER); + break; + case Mycila::RouterOutputState::OUTPUT_IDLE: + _output1State.update(Mycila::Router.outputs[0].getStateString(), DASH_STATUS_IDLE); + break; + case Mycila::RouterOutputState::OUTPUT_BYPASS_AUTO: + case Mycila::RouterOutputState::OUTPUT_BYPASS_MANUAL: + _output1State.update(Mycila::Router.outputs[0].getStateString(), DASH_STATUS_WARNING); + break; + case Mycila::RouterOutputState::OUTPUT_ROUTING: + _output1State.update(Mycila::Router.outputs[0].getStateString(), DASH_STATUS_SUCCESS); + break; + default: + _output1State.update("Unknown", DASH_STATUS_DANGER); + break; + } + _temperature(&_output1TempState, &Mycila::Router.outputs[0].temperatureSensor); + _output1BypassSwitch.update(Mycila::Router.outputs[0].isBypassRelayOn()); + _output1DimmerSlider.update(static_cast(Mycila::Router.outputs[0].dimmer.getLevel())); + + switch (Mycila::Router.outputs[1].getState()) { + case Mycila::RouterOutputState::OUTPUT_DISABLED: + _output2State.update(Mycila::Router.outputs[1].getStateString(), DASH_STATUS_DANGER); + break; + case Mycila::RouterOutputState::OUTPUT_IDLE: + _output2State.update(Mycila::Router.outputs[1].getStateString(), DASH_STATUS_IDLE); + break; + case Mycila::RouterOutputState::OUTPUT_BYPASS_AUTO: + case Mycila::RouterOutputState::OUTPUT_BYPASS_MANUAL: + _output2State.update(Mycila::Router.outputs[1].getStateString(), DASH_STATUS_WARNING); + break; + case Mycila::RouterOutputState::OUTPUT_ROUTING: + _output2State.update(Mycila::Router.outputs[1].getStateString(), DASH_STATUS_SUCCESS); + break; + default: + _output2State.update("Unknown", DASH_STATUS_DANGER); + break; + } + _temperature(&_output2TempState, &Mycila::Router.outputs[1].temperatureSensor); + _output2BypassSwitch.update(Mycila::Router.outputs[1].isBypassRelayOn()); + _output2DimmerSlider.update(static_cast(Mycila::Router.outputs[1].dimmer.getLevel())); + + _relay1Switch.update(Mycila::RelayManager.relays[0].isOn()); + _relay2Switch.update(Mycila::RelayManager.relays[1].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::Display.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(), "Disconnected"); + _status(&_stateOutput1AutoBypass, KEY_OUTPUT1_AUTO_BYPASS_ENABLE, true, Mycila::NTP.isSynced(), "Waiting for time sync"); + _status(&_stateOutput1AutoDimmer, KEY_OUTPUT1_DIMMER_AUTO, true, Mycila::Router.outputs[0].dimmer.isEnabled(), "Dimmer disabled"); + _status(&_stateOutput1Bypass, KEY_OUTPUT1_RELAY_ENABLE, Mycila::Router.outputs[0].isBypassRelayEnabled()); + _status(&_stateOutput1Dimmer, KEY_OUTPUT1_DIMMER_ENABLE, Mycila::Router.outputs[0].dimmer.isEnabled(), gridOnline, "No grid voltage"); + _status(&_stateOutput1Temp, KEY_OUTPUT1_TEMP_ENABLE, Mycila::Router.outputs[0].temperatureSensor.isEnabled(), Mycila::Router.outputs[0].temperatureSensor.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, Mycila::Router.outputs[0].dimmer.isEnabled(), "Dimmer disabled"); + _status(&_stateOutput2Bypass, KEY_OUTPUT2_RELAY_ENABLE, Mycila::Router.outputs[1].isBypassRelayEnabled()); + _status(&_stateOutput2Dimmer, KEY_OUTPUT2_DIMMER_ENABLE, Mycila::Router.outputs[1].dimmer.isEnabled(), gridOnline, "No grid voltage"); + _status(&_stateOutput2Temp, KEY_OUTPUT2_TEMP_ENABLE, Mycila::Router.outputs[1].temperatureSensor.isEnabled(), Mycila::Router.outputs[1].temperatureSensor.isValid(), "Read error"); + _status(&_statePushButton, KEY_BUTTON_ENABLE, Mycila::Button.isEnabled()); + _status(&_stateRelay1, KEY_RELAY1_ENABLE, Mycila::RelayManager.relays[0].isEnabled()); + _status(&_stateRelay2, KEY_RELAY2_ENABLE, Mycila::RelayManager.relays[1].isEnabled()); + _status(&_stateSystemTemp, KEY_SYSTEM_TEMP_ENABLE, _systemTempSensor->isEnabled(), _systemTempSensor->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); + + // pinout live + + std::map pinout = {}; + _pinout(&_pinLiveBuzzer, Mycila::Buzzer.getPin(), &pinout); + _pinout(&_pinLiveDisplayClock, Mycila::Display.getClockPin(), &pinout); + _pinout(&_pinLiveDisplayData, Mycila::Display.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, Mycila::Router.outputs[0].relay.getPin(), &pinout); + _pinout(&_pinLiveOutput1Dimmer, Mycila::Router.outputs[0].dimmer.getPin(), &pinout); + _pinout(&_pinLiveOutput1Temp, Mycila::Router.outputs[0].temperatureSensor.getPin(), &pinout); + _pinout(&_pinLiveOutput2Bypass, Mycila::Router.outputs[1].relay.getPin(), &pinout); + _pinout(&_pinLiveOutput2Dimmer, Mycila::Router.outputs[1].dimmer.getPin(), &pinout); + _pinout(&_pinLiveOutput2Temp, Mycila::Router.outputs[1].temperatureSensor.getPin(), &pinout); + _pinout(&_pinLivePushButton, Mycila::Button.getPin(), &pinout); + _pinout(&_pinLiveRelay1, Mycila::RelayManager.relays[0].getPin(), &pinout); + _pinout(&_pinLiveRelay2, Mycila::RelayManager.relays[1].getPin(), &pinout); + _pinout(&_pinLiveSystemTemp, _systemTempSensor->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(); + } + + _lastUpdate = millis(); + // Mycila::Logger.debug(TAG, "Published in %u ms", (_lastUpdate - start)); +} + +void YaSolR::Website::_sliderConfig(Card* card, const char* key) { + card->attachCallback([key](int value) { + Mycila::Config.set(key, String(MAX(0, value))); + }); +} + +void YaSolR::Website::_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(MAX(0, strtol(value, nullptr, 10)))); + } + }); +#endif +} + +void YaSolR::Website::_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::Website::_textConfig(Card* card, const char* key) { +#ifdef APP_VERSION_PRO + card->attachCallback([key](const char* value) { Mycila::Config.set(key, value); }); +#endif +} + +void YaSolR::Website::_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::Website::_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::Website::_relaySwitch(Card* card, int idx) { + card->attachCallback([card, idx, this](int value) { + if (Mycila::RelayManager.relays[idx].isEnabled()) { + Mycila::RelayManager.tryRelayState(idx, value); + } + card->update(Mycila::RelayManager.relays[idx].isOn()); + _dashboard.refreshCard(card); + }); +} + +void YaSolR::Website::_outputRelaySwitch(Card* card, int idx) { + card->attachCallback([card, idx, this](int value) { + if (Mycila::Router.outputs[idx].isBypassRelayEnabled()) { + Mycila::Router.outputs[idx].tryBypassRelayState(value); + } + card->update(Mycila::Router.outputs[idx].isBypassRelayOn()); + _dashboard.refreshCard(card); + }); +} + +void YaSolR::Website::_outputDimmerSlider(Card* card, int idx) { + card->attachCallback([card, idx, this](int value) { // 0-100 + if (Mycila::Router.outputs[idx].dimmer.isEnabled()) { + Mycila::Router.outputs[idx].tryDimmerLevel(value); + } + card->update(static_cast(Mycila::Router.outputs[idx].dimmer.getLevel())); + _dashboard.refreshCard(card); + }); +} + +void YaSolR::Website::_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::Website::_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_DANGER); + else if (!enabled) + card->update("Unable to start", DASH_STATUS_WARNING); + else if (!active) + card->update(err, DASH_STATUS_WARNING); + else + card->update("Enabled", DASH_STATUS_SUCCESS); +} + +void YaSolR::Website::_pinout(Card* card, int32_t pin, std::map* pinout) { + if (pin == GPIO_NUM_NC) { + card->update("Disabled", DASH_STATUS_DANGER); + } 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..68f4969 --- /dev/null +++ b/src/main.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 +#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_PRO +#include +#endif + +#ifdef APP_VERSION_TRIAL +#include +#endif + +#define TAG "YASOLR" + +Mycila::Relay relay1("relay1"); +Mycila::Relay relay2("relay2"); + +Mycila::RouterOutput output1("output1"); +Mycila::RouterOutput output2("output2"); + +Mycila::TemperatureSensor systemTemp(NAME_SYSTEM); + +YaSolR::Connector connector(&systemTemp); +YaSolR::Website website(&systemTemp); +YaSolR::Controller appController(&systemTemp, &website, &connector); + +void loop() { +#ifdef APP_VERSION_TRIAL + Mycila::Trial.loop(); + yield(); +#endif + + const uint32_t start = millis(); + + Mycila::Button.loop(); + yield(); + + Mycila::Display.loop(); + yield(); + + systemTemp.loop(); + yield(); + + Mycila::Router.loop(); + yield(); + + Mycila::RelayManager.loop(); + yield(); + + appController.loop(); + yield(); + + ESPConnect.loop(); + yield(); + + Mycila::OTA.loop(); + yield(); + + Mycila::MQTT.loop(); + yield(); + + if (Mycila::Logger.isDebugEnabled()) { + Mycila::TaskMonitor.loop(); + yield(); + } + + website.loop(); + yield(); + + const uint32_t elapsed = millis() - start; + if (elapsed > 1000) + Mycila::Logger.warn(TAG, "Abnormal loop duration: %u ms", elapsed); +} + +void ioTask(void* params) { + if (Mycila::Logger.isDebugEnabled()) + Mycila::Logger.debug(TAG, "Task '%s' started on core: %u", pcTaskGetName(NULL), xPortGetCoreID()); + YaSolR::Connector* connector = reinterpret_cast(params); + while (true) { + connector->loop(); + delay(100); + } +} + +void setup() { + Serial.begin(YASOLR_SERIAL_BAUDRATE); +#if ARDUINO_USB_CDC_ON_BOOT + Serial.setTxTimeoutMs(0); + delay(100); +#else + while (!Serial) + yield(); +#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 + + // loading config + YaSolR::Config.begin(); + + // init logging + Mycila::Logger.info(TAG, "Starting %s %s %s...", Mycila::AppInfo.name.c_str(), Mycila::AppInfo.model.c_str(), Mycila::AppInfo.version.c_str()); + +// start components +#ifdef APP_VERSION_PRO + WebSerial.begin(&Mycila::HTTPd.server, "/console", Mycila::HTTPdConfig.getUsername(), Mycila::HTTPdConfig.getPassword()); + Mycila::Logger.forwardTo(&WebSerial); +#endif + + appController.begin(); + connector.begin(); + + Mycila::Button.begin(); + Mycila::Lights.begin(Mycila::LightState::ON, Mycila::LightState::OFF, Mycila::LightState::ON); + Mycila::Buzzer.begin(); + Mycila::Display.begin(); + + // JSY + 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() && Mycila::JSY.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { + if (Mycila::JSY.updateBaudRate(Mycila::JSYBaudRate::BAUD_38400)) { + Mycila::Logger.info(TAG, "JSY speed updated to 38400 bauds"); + } else { + Mycila::Logger.warn(TAG, "Failed to update JSY speed to 38400 bauds"); + } + } + } else { + Mycila::Logger.warn(TAG, "JSY not enabled"); + } + + Mycila::ZCD.begin(); + Mycila::Grid.begin(); + + // router + Mycila::Router.outputs.push_back(output1); + Mycila::Router.outputs.push_back(output2); + Mycila::Router.begin(); + + // relay manager + Mycila::RelayManager.relays.push_back(relay1); + Mycila::RelayManager.relays.push_back(relay2); + Mycila::RelayManager.begin(); + + systemTemp.begin(); + website.begin(); + + // ntp + Mycila::NTP.setTimeZone(Mycila::Config.get(KEY_NTP_TIMEZONE)); + + // network + Mycila::Logger.info(TAG, "Enable Network..."); + 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.setWiFiConnectTimeout(Mycila::Config.get(KEY_WIFI_CONNECTION_TIMEOUT).toInt()); + ESPConnect.begin(&Mycila::HTTPd.server, 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)}); + + // system monitoring + 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"); + + // STARTUP READY! + Mycila::Buzzer.beep(); + 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", 256 * 19, &connector, 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 +} diff --git a/upload.sh b/upload.sh new file mode 100755 index 0000000..5e76512 --- /dev/null +++ b/upload.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e + +pio run -t upload +pio run -t monitor