Blog

  • easyeda-ibom-extension

    Hero

    iBom for EasyEDA is a native extension for EasyEDA and LCEDA: Standard Edition. It is based on the awesome project called InteractiveHtmlBom designed and built by qu1ck.

    Although InteractiveHtmlBom already supports generation of interactive BOMs from EasyEDA projects, but it involves messing with Python scripts and requires to host the generated html files somewhere. Since my knowledge of Python === null, and my favorite tool for designing electronics is EasyEDA – I decided to give it a shot and port it as a native extension.

    Comparing to original solution, iBom for EasyEDA is available right within EasyEDA itself:

    • There is no need to run any Python scripts.
    • No need to host the produced html files.
    • iBom is always accessible by clicking a single menu button from your PCB document!
    • It also provides an ability to generate a stand-alone HTML representation the same way InteractiveHtmlBom does out of the box.

    NOTE: This extension is still in ALPHA/BETA stage – a lot of things should be done in order to make it fully compatible with EasyEDA.

    Installation

    1. Download easyeda-ibom-v0.1.2.zip
    2. Un-zip the downloaded archive on your hard drive.
    3. Go to Extension Settings dialog by using main menu Advanced -> Extensions -> Extensions Settings....
    4. Click Load Extension... button and add all the files in from the extracted folder using Select Files... button.
    5. Click Load Extension and close the Extension Settings dialog.

    iBom Installation

    Usage

    Within EasyEDA

    In order to launch iBom within the editor itself – just click on iBom -> Launch... menu item:

    iBom Demo

    You can always dismiss the iBom view by clicking on the little close button located in the right-top corner of the panel.

    Generating stand-alone HTML representation

    Just click iBom -> Generate & Download HTML menu item in order to get the stand-alone representation of your PCB document:

    iBom Demo

    Note: In case the downloaded file doesn’t have .html extension – just add it manually. It is some sort of weird browser behaviour for the case to be investigated.

    Feedback

    If you discover any issue or have any suggestions for improvement of the plugin, please open an issue or find me on twitter @turbobabr.

    Credits

    This solution uses:

    • InteractiveHtmlBom as a core component.
    • A "hackable" nature of EasyEDA/LCEDA – thank you guys for allowing us messing with the product! 👏
    • svg-path-bbox for calculating various bounding boxes during EasyEDA -> InteractiveHTMLBOM data conversions.

    License

    MIT License

    Copyright (c) 2021 Andrey Shakhmin

    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.

    Visit original content creator repository https://github.com/turbobabr/easyeda-ibom-extension
  • football-league

    GitHub

    Football league

    The webpage is a result of XSLT transformation to both HTML applied on an XML file with football league data. It has been developed as a semestral work as a part of the course 4IZ238 XML – Theory and Practice of Markup Languages.

    This is also my very first XSLT transformation to both HTML and PDF from 2016 and published now. The webpage is hosted on GitHub Pages.

    Glossary

    For those who haven’t encountered with the following elementary terms yet:

    How it works?

    The entire semestral work could be divided into 4 separated processes.

    Input

    The only input is an XML file. All the names and numbers are fictional or randomly generated in the past and exclusively created by myself only.

    It’s the input XML file with a root element league and two its descendants detail describing the league detail and teams which lists the detailed description of the team itself and all its players. A brief form appears below:

    XML input example
    league> <detail> <!--/* League details */ --> </detail> <teams> <team> <description> <!--/* Team description */ --> </description> <players> <player> <!--/* Player description */ --> </player> <!--/* Another player */ --> </team> <!--/* Another team */ --> </teams> </league>

    Validation

    The only input XML file validated against both an XSD file and a Schematron file.

    This XSD file is a validation file for the input XML which validates its entire structure, the allowed count and order of the particular elements and the data type and format of their values. The following list provides a selection of the most important rules (not ordered by the importance):

    • All the names, shortcuts and contacts have to remain distinct
    • Each team is allowed to recruit at least 11 and up to 20 players and each player is between 15 and 99 years, both inclusive
    • Some of them are talented and their skill has to match the enumeration: free kicks, quick, powerful, technical or head
    • All the contacts, licenses and ID’s must match the format usually defined with a regular expression (Regex)
    • A player can play on one of 4 available positions: goalkeeper, defender, midfielder or forward
    • Their overall skill is represented by a double number between 1 and 40, both inclusive round on the halves using:
      <xsd:assertion test="$value = (for $d in 1 to 40 return 0.5 * $d)"/>

    I have used the Venetian Blind design.

    The Schematron validation is a minor and a supplementary validation file using XPath. Its only job is to assure that each team will have only and exactly one player market as a captain. A part of the overall score was the usage of a Schematron file and the captain validation is a job which suits Schematron a lot:

    Schematron (7 lines)
    pattern id="check"> <rule context="//teams/team"> <assert test="count(players/player[@status='captain']) = 1"> <!-- Error message --> </assert> </rule> </pattern>

    Transformation

    The crucial part of the semestral work is the transformation into HTML and PDF output with mutually linked pages and aggregation functions. Each transformation has defined a set of functions applicable to the statistics of the players.

    The first transformation produces the website consisted of mutually linked HTML pages deployed on the project’s GitHub Pages with the similar design but variable linked content.

    All the transformations are applied to the root element / and define immediately self as a template rendered to index.html with to generated partial templates under the div containers. The sample code is followed with a brief sample of the en:teams template responsible that each team have own page generated into chunks folder:

    The root element template (31 lines)
    </tr> <xsl:for-each select=”en:team”> <xsl:sort select=”@id”/> <tr id=”teams”> <td><a href=”chunks/{translate(en:description/en:name, $uppercase, $lowercase)}.html”> <xsl:value-of select=”@id”/> </a></td> <td><xsl:value-of select=”en:description/en:short”/></td> <td><xsl:value-of select=”en:description/en:name”/></td> <td><xsl:value-of select=”en:description/en:trainer/en:name”/></td> <td><xsl:value-of select=”en:calculatePower(en:players)”/></td> </tr> <xsl:result-document href=”chunks/{translate(en:description/en:name, $uppercase, $lowercase)}.html” format=”html”> <html> <head> <title><xsl:value-of select=”en:description/en:name”/></title> <link rel=”stylesheet” type=”text/css” href=”../index.css”/> </head> <body> <div id=”league”> <xsl:apply-templates select=”en:description”/> </div> <div id=”teams”> <xsl:apply-templates select=”en:players”/> </div> <div id=”footer”/> </body> </html> </xsl:result-document> </xsl:for-each> </table> </xsl:template>’>
    <xsl:template match="en:teams">
        <h2>Teams</h2>
        <table id="teams">
            <tr id="teams-label">
                <!-- /* SKIPPED: Table header labels */-->
            </tr>
            <xsl:for-each select="en:team">
        
                <!-- /* Sorted table of teams with links to the newly generated pages below */-->
                <xsl:sort select="@id"/>
                    <tr id="teams">
                        <td><a href="chunks/{translate(en:description/en:name, $uppercase, $lowercase)}.html">
    	                <xsl:value-of select="@id"/>
    	            </a></td>
                        <td><xsl:value-of select="en:description/en:short"/></td>
                        <td><xsl:value-of select="en:description/en:name"/></td>
                        <td><xsl:value-of select="en:description/en:trainer/en:name"/></td>
                        <td><xsl:value-of select="en:calculatePower(en:players)"/></td>
                    </tr>
         
                <!-- /* Generated page for each team */-->
                <xsl:result-document 
    		    href="chunks/{translate(en:description/en:name, $uppercase, $lowercase)}.html" 
    		    format="html">
                    <html>
                        <head>
                            <title><xsl:value-of select="en:description/en:name"/></title>
                            <link rel="stylesheet" type="text/css" href="../index.css"/>
                        </head>
                        <body>
                            <!-- /* SKIPPED: Header and  watermark */-->	
                            <div id="league">
    	                    <!-- /* Generated team description using another template */-->
                                <xsl:apply-templates select="en:description"/>    
                            </div>
                            <div id="teams">
    	                    <!-- /* Generated list of players using another template */-->
                                <xsl:apply-templates select="en:players"/>        
                            </div>
                            <div id="footer"/>
                        </body>
                    </html>
                </xsl:result-document>
            </xsl:for-each>
        </table>
    </xsl:template>

    On the similar principle works the transformation to PDF with a watermark. In the beginning, there is generated a table of contents with links to particular pages. Each team is rendered to the separated page. Last few pages have generated tables with the best players.

    This transformation is three times more verbose than the previous one because of the design redefinition since CSS can not be used to this kind of transformation. However, unlike the used elemenets, the principle is pretty identical. A really shortened example of a block summary of the best goalkeepers follows:

    The best goalkeepers template (49 lines)
    xsl:template name="bestPlayers"> <fo:block id="bestPlayers" break-before="page"></fo:block> <xsl:call-template name="watermark"/> <fo:block font-size="9mm" text-align="center" padding-top="5mm"> <xsl:text>Best players</xsl:text> </fo:block> <!-- /* Block for the best goalkeepers */--> <fo:block padding-before="1cm" width="75%"> <fo:block margin-left="2cm"> <fo:block font-size="6mm" padding-after="5mm"> <xsl:text>Best goalkeepers</xsl:text> </fo:block> <fo:table text-align="center" white-space="nowrap" width="75%" font-size="3mm"> <fo:table-column width="20%"/> <!-- /* SKIPPED: 4 identical lines */--> <fo:table-column width="20%"/> <!-- /* Table header definition */--> <fo:table-header> <fo:table-row font-size="3mm" height="8mm"> <fo:table-cell><fo:block>NAME</fo:block></fo:table-cell> <!-- /* SKIPPED: Age, Nationality and Skill */--> <fo:table-cell><fo:block>TEAM</fo:block></fo:table-cell> <fo:table-cell><fo:block></fo:block></fo:table-cell> </fo:table-row> </fo:table-header> <!-- /* Table body definition */--> <fo:table-body padding-top="5mm"> <xsl:for-each select= "en:league/en:teams/en:team/en:players/en:player[@position='goalkeeper']"> <xsl:sort select="en:skill" data-type="number" order="descending"/> <xsl:if test="position() &lt;= 6"> <!-- /* Call of the template rendering the player characteristics */--> <xsl:call-template name="six-characteristics"> <xsl:with-param name="node" select="."/> </xsl:call-template> </xsl:if> </xsl:for-each> </fo:table-body> </fo:table> </fo:block> </fo:block> <!-- /* SKIPPED: 3 blocks for the defenders, midfielders and forwards */--> </fo:block> </xsl:template>

    Ouptut

    This section offers an overall view to the generated content structure.

    HTML

    PDF

    • index.pdf

      The GitHub PDF viewer doesn’t suppor the links to the pages. There is need to download the file for the full functionality.

    Quality check

    I have integrated Codebeat and Codacy cloud static analysis services to check the overall code quality out of curiosity.

    Licence

    MIT License

    Copyright (c) 2018 Nikolas Charalambidis

    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.

    Visit original content creator repository https://github.com/Nikolas-Charalambidis/football-league
  • kong-plugin-url-rewrite

    Build Status

    Kong-plugin-url-rewrite

    Kong API Gateway plugin for url-rewrite purposes. This plugin has been tested to work along with kong >= 2.6.x, for a legacy version of this plugin, please check this link.

    The Problem

    When using Kong, you can create routes that proxy to an upstream. The problem lies when the upstream has an url that is not very friendly to your clients, or restful, or even pretty. When you add a Route in Kong, you have a somewhat limited url rewrite capability. This plugin simply throws away the url set in Kong route and uses the url set in it’s configuration to proxy to the upstream. This gives you full freedom as to how to write your url’s in Kong and inner services as well.

    Project Structure

    The plugin folder should contain at least a schema.lua and a handler.lua, alongside with a spec folder and a .rockspec file specifying the current version of the package.

    Rockspec Format

    The .rockspec file should follow LuaRocks’ conventions

    Configuration

    Enabling the plugin on a Route

    Configure this plugin on a Route with:

    curl -X POST http://kong:8001/routes/{route_id}/plugins \
        --data "name=kong-plugin-url-rewrite"  \
        --data "config.url=http://new-url.com"
    • route_id: the id of the Route that this plugin configuration will target.
    • config.url: the url where you want kong to execute the request.

    Developing

    In docker

    docker build . -t kong-plugin-url-rewrite-dev
    docker run -it -v ${PWD}/url-rewrite:/url-rewrite kong-plugin-url-rewrite-dev bash

    Credits

    made with ❤️ by Stone Payments

    Visit original content creator repository https://github.com/stone-payments/kong-plugin-url-rewrite
  • rooks



    CI and Semantic Release GitHub GitHub release (latest by date) GitHub contributors npm npm bundle size Codecov branch node-lts Website Module


    Collection of awesome react hooks



    List of all hooks

    🎬 Animation & Timing – 5 hooks

    • useIntervalWhen – Sets an interval immediately when a condition is true
    • useLockBodyScroll – This hook locks the scroll of the body element when isLocked is set to true.
    • useRaf – A continuously running requestAnimationFrame hook for React
    • useResizeObserverRef – Resize Observer hook for React.
    • useTimeoutWhen – Takes a callback and fires it when a condition is true

    🌐 Browser APIs – 9 hooks

    🛠️ Development & Debugging – 1 hook

    🚀 Events – 15 hooks

    📝 Form & File Handling – 1 hook

    ⌨️ Keyboard & Input – 5 hooks

    • useInput – Input hook for React.
    • useKey – keypress, keyup and keydown event handlers as hooks for react.
    • useKeyBindings – useKeyBindings can bind multiple keys to multiple callbacks and fire the callbacks on key press.
    • useKeyRef – Very similar useKey but it returns a ref
    • useKeys – A hook which allows to setup callbacks when a combination of keys are pressed at the same time.

    🔥 Lifecycle & Effects – 9 hooks

    • useAsyncEffect – A version of useEffect that accepts an async function
    • useDeepCompareEffect – Deep compare dependencies instead of shallow for useEffect
    • useDidMount – componentDidMount hook for React
    • useDidUpdate – componentDidUpdate hook for react
    • useDocumentTitle – A hook to easily update document title with React
    • useEffectOnceWhen – Runs a callback effect atmost one time when a condition becomes true
    • useIsomorphicEffect – A hook that resolves to useEffect on the server and useLayoutEffect on the client.
    • useLifecycleLogger – A react hook that console logs parameters as component transitions through lifecycles.
    • useWillUnmount – componentWillUnmount lifecycle as hook for React.

    🖱️ Mouse & Touch – 3 hooks

    ⚡ Performance & Optimization – 4 hooks

    ❇️ State – 18 hooks

    • useArrayState – Array state manager hook for React
    • useCountdown – Count down to a target timestamp and call callbacks every second (or provided peried)
    • useCounter – Counter hook for React.
    • useGetIsMounted – Checks if a component is mounted or not at the time. Useful for async effects
    • useLocalstorageState – UseState but auto updates values to localStorage
    • useMapState – A react hook to manage state in a key value pair map.
    • useMultiSelectableList – A custom hook to easily select multiple values from a list
    • useNativeMapState – Manage Map() object state in React
    • usePreviousDifferent – usePreviousDifferent returns the last different value of a variable
    • usePreviousImmediate – usePreviousImmediate returns the previous value of a variable even if it was the same or different
    • usePromise – Promise management hook for react
    • useQueueState – A React hook that manages state in the form of a queue
    • useSafeSetState – set state but ignores if component has already unmounted
    • useSelect – Select values from a list easily. List selection hook for react.
    • useSelectableList – Easily select a single value from a list of values. very useful for radio buttons, select inputs etc.
    • useSessionstorageState – useState but syncs with sessionstorage
    • useSetState – Manage the state of a Set in React.
    • useStackState – A React hook that manages state in the form of a stack

    🔄 State History & Time Travel – 4 hooks

    • useTimeTravelState – A hook that manages state which can undo and redo. A more powerful version of useUndoState hook.
    • useToggle – Toggle (between booleans or custom data)hook for React.
    • useUndoRedoState – Setstate but can also undo and redo
    • useUndoState – Drop in replacement for useState hook but with undo functionality.

    ⚛️ UI – 12 hooks

    🔧 Utilities & Refs – 7 hooks

    • useEventListenerRef – A react hook to add an event listener to a ref
    • useForkRef – A hook that can combine two refs(mutable or callbackRefs) into a single callbackRef
    • useFreshCallback – Avoid stale closures and keep your callback fresh
    • useFreshRef – Avoid stale state in callbacks with this hook. Auto updates values using a ref.
    • useFreshTick – Like use-fresh-ref but specifically for functions
    • useMergeRefs – Merges any number of refs into a single ref
    • useRefElement – Helps bridge gap between callback ref and state

    📱 Window & Viewport – 2 hooks

    🧪 Experimental Hooks – 4 hooks

    ⚠️ Experimental hooks may be removed or significantly changed in any release without notice. Use with caution in production.


    Features

    ✅ Collection of 99 hooks as standalone modules.

    ✅ Standalone package with all the hooks at one place

    ✅ Built for ESM

    Installation

    npm i -s rooks
    

    Import any hook from “rooks” and start using them!

    import { useDidMount } from "rooks";

    Usage

    function App() {
      useDidMount(() => {
        alert("mounted");
      });
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <h2>Start editing to see some magic happen!</h2>
        </div>
      );
    }

    Standalone Package

    Package containing all the hooks is over here. – Docs and Npm Install


    License

    MIT

    Contributors ✨

    All Contributors

    Thanks goes to these wonderful people (emoji key):

    Bhargav Ponnapalli
    Bhargav Ponnapalli

    💻 🚧
    anil kumar chaudhary
    anil kumar chaudhary

    💻
    Qiwei Yang
    Qiwei Yang

    💻 🚧
    maciek_grzybek
    maciek_grzybek

    💻
    Harsh Zalavadiya
    Harsh Zalavadiya

    💻
    B V K MAHIJENDRA
    B V K MAHIJENDRA

    💻
    Braxton Christensen
    Braxton Christensen

    💻
    Hansel
    Hansel

    💻
    Harshil Parmar
    Harshil Parmar

    💻
    Lionel
    Lionel

    💻
    Max Stoiber
    Max Stoiber

    💻
    Michael Moore
    Michael Moore

    💻
    Rajas Paranjpe
    Rajas Paranjpe

    💻
    Mahendra Choudhary
    Mahendra Choudhary

    💻
    Nghia Pham
    Nghia Pham

    💻
    Akshay Kadam (A2K)
    Akshay Kadam (A2K)

    💻
    Alex Golubtsov
    Alex Golubtsov

    💻
    Arman
    Arman

    💻
    Branden Visser
    Branden Visser

    💻
    Brian Steere
    Brian Steere

    💻
    Cal Courtney
    Cal Courtney

    💻
    Chris Milson
    Chris Milson

    💻
    Cong Zhang
    Cong Zhang

    💻
    Daniel Holmes
    Daniel Holmes

    💻
    Fernando Beck
    Fernando Beck

    💻
    Josh Davenport
    Josh Davenport

    💻
    MARCEL
    MARCEL

    💻
    Neilor Caldeira
    Neilor Caldeira

    💻
    Tobias Lins
    Tobias Lins

    💻
    Tsvetan
    Tsvetan

    💻
    Wei Zhu
    Wei Zhu

    💻
    Yakko Majuri
    Yakko Majuri

    💻
    Frank Hellwig
    Frank Hellwig

    💻
    Austin Peterson
    Austin Peterson

    💻
    thodubois
    thodubois

    💻
    wes christiansen
    wes christiansen

    💻
    CJ Patoilo
    CJ Patoilo

    💻
    mar1u50
    mar1u50

    💻
    Ayushman Gupta
    Ayushman Gupta

    💻
    Rafael Ferreira
    Rafael Ferreira

    💻
    Kristinn Thor Johannsson
    Kristinn Thor Johannsson

    💻
    Michael Moore
    Michael Moore

    💻
    Trevor Blades
    Trevor Blades

    💻
    official_dulin
    official_dulin

    💻
    Billy Mosis Priambodo
    Billy Mosis Priambodo

    💻
    Stafford Williams
    Stafford Williams

    💻
    Chanhee Kim
    Chanhee Kim

    💻
    Hooriza
    Hooriza

    💻
    Nils Wittler
    Nils Wittler

    💻
    Sebastian Szczepański
    Sebastian Szczepański

    💻
    Mahendra Choudhary
    Mahendra Choudhary

    💻
    Som Shekhar Mukherjee
    Som Shekhar Mukherjee

    💻
    Qiushi Pan
    Qiushi Pan

    💻
    Jishnu Viswanath
    Jishnu Viswanath

    💻
    brahambence
    brahambence

    💻
    dependabot[bot]
    dependabot[bot]

    💻
    renovate[bot]
    renovate[bot]

    💻
    dependabot-preview[bot]
    dependabot-preview[bot]

    💻
    github-actions[bot]
    github-actions[bot]

    💻
    allcontributors[bot]
    allcontributors[bot]

    💻
    zhangenming
    zhangenming

    💻
    Antoni Kiszka
    Antoni Kiszka

    💻
    Greg Poole
    Greg Poole

    💻
    mergify[bot]
    mergify[bot]

    💻
    Chaitanya J
    Chaitanya J

    💻
    G H Mahimaanvita
    G H Mahimaanvita

    💻
    Danilo Woznica
    Danilo Woznica

    💻
    dan-klasson
    dan-klasson

    💻
    Sébastien Vanvelthem
    Sébastien Vanvelthem

    💻
    Aleksandr Soldatov
    Aleksandr Soldatov

    💻
    Eli Yukelzon
    Eli Yukelzon

    💻
    Mahendra Choudhary
    Mahendra Choudhary

    💻
    Chaitanya J
    Chaitanya J

    💻
    Dominik Dorfmeister
    Dominik Dorfmeister

    💻
    Nghiệp
    Nghiệp

    💻
    Seongmin Park
    Seongmin Park

    💻
    Nate Higgins
    Nate Higgins

    💻
    Michael Moore
    Michael Moore

    💻
    Moritz Brandes
    Moritz Brandes

    💻
    Som Shekhar Mukherjee
    Som Shekhar Mukherjee

    💻
    cursor[bot]
    cursor[bot]

    💻
    JulianWielga
    JulianWielga

    💻

    Other hooks libraries

    These are some libraries that I constantly take inspiration and ideas from

    1. React-use
    2. React-aria
    3. Valtio
    4. Jotai
    5. Recoil
    6. Downshiftjs
    7. React hook form
    Visit original content creator repository https://github.com/imbhargav5/rooks
  • weapp-gulp-service

    简介

    weapp-gulp-service 是一款基于 gulp 实现的微信小程序预编译开发工具,可以有效提升原生小程序的开发效率。主要功能如下所示:

    语法增强:

    1. 支持 less;
    2. 支持 px 自动转 rpx;
    3. 支持本地图片转 base64;
    4. 支持路径别名 alias;
    5. 支持设置环境变量(类似 vue-cli 的 .env 文件);
    6. 支持 json5 语法;
    7. 扩展 app.json,支持表达力更好的路由写法;
    8. 支持.vue 单文件开发(兼容大部分 v-指令);

    工具特性:

    1. 0 配置使用;
    2. 支持增量编译;
    3. 支持自定义 task;
    4. 支持插件扩展(类似 Vue.use);
    5. 支持自动构建 npm(需开启 wxDevtool cli 服务);
    6. 支持命令行上传代码(需集成 miniprogram-ci);

    安装/运行

    全局安装并使用:

    npm i weapp-gulp-service -g
    # 项目根目录执行
    wgs --config /user/workspace/xx/weapp.config.js

    也可以局部安装并使用:

    npm i weapp-gulp-service -D
    # 项目根目录执行
    npx wgs

    项目 Demo 参见 weapp-project (可直接作为开发模版)。

    详细说明

    docs 目录。

    Command API

    开发模式:

    # 编译 + watch
    wgs [options]
    # 或者
    wgs serve [options]

    打包模式:

    # 仅编译
    wgs build [options]

    构建 npm

    # 生成 miniprogram_npm 目录
    wgs build:npm [options]

    代码上传:

    # 先编译再上传
    wgs upload -v 1.0.1 [desc]

    ps: 各命令选项可通过 --help 查看

    小程序 ci/cli 集成说明

    自动构建 npm上传代码两个功能前置依赖小程序官方服务——wxdevtool-cli 或者 miniprogram-ci

    如果使用 wxdevtool-cli(以下简称 cli 模式),需设置环境变量:

    # macOS
    WX_CLI=<安装路径>/Contents/MacOS/cli
    # Windows
    WX_CLI=<安装路径>/cli.bat

    如果使用 miniprogram-ci(以下简称 ci 模式),需设置环境变量:

    WE_APP_PRIVATE_KEY_PATH=<私匙路径>/private.xxxx.key

    并安装全局依赖 miniprogram-ci;

    npm i miniprogram-ci -g

    两种方式二选一即可。如果二者同时设置,则 ci 模式优先。

    Visit original content creator repository
    https://github.com/lixl39505/weapp-gulp-service

  • ESP8266_W5500_Manager

    ESP8266_W5500_Manager

    arduino-library-badge GitHub release GitHub contributions welcome GitHub issues

    Donate to my libraries using BuyMeACoffee



    Table of Contents



    Why do we need this ESP8266_W5500_Manager library

    Features

    This is an ESP8266 + LwIP W5500 Credentials and Connection Manager with fallback Web ConfigPortal. This Library is used for configuring ESP8266 Ethernet Static / DHCP and Credentials at runtime. You can specify static DNS servers, personalized HostName and CORS feature.

    This library is based on, modified and improved from:

    1. Khoi Hoang's ESP_WiFiManager

    Currently supported Boards

    This ESP8266_W5500_Manager library currently supports these following boards:

    1. ESP8266 boards using LwIP W5500 Ethernet



    Prerequisites

    1. Arduino IDE 1.8.19+ for Arduino. GitHub release
    2. ESP8266 Core 3.0.2+ for ESP8266-based boards. Latest release
    3. ESP_DoubleResetDetector v1.3.2+ if using DRD feature. To install, check arduino-library-badge. Use v1.1.0+ if using LittleFS for ESP8266 v1.0.6+.


    Installation

    Use Arduino Library Manager

    The best and easiest way is to use Arduino Library Manager. Search for ESP8266_W5500_Manager, then select / install the latest version. You can also use this link arduino-library-badge for more detailed instructions.

    Manual Install

    1. Navigate to ESP8266_W5500_Manager page.
    2. Download the latest release ESP8266_W5500_Manager-main.zip.
    3. Extract the zip file to ESP8266_W5500_Manager-main directory
    4. Copy the whole ESP8266_W5500_Manager-main folder to Arduino libraries’ directory such as ~/Arduino/libraries/.

    VS Code & PlatformIO:

    1. Install VS Code
    2. Install PlatformIO
    3. Install ESP8266_W5500_Manager library by using Library Manager. Search for ESP8266_W5500_Manager in Platform.io Author’s Libraries
    4. Use included platformio.ini file from examples to ensure that all dependent libraries will installed automatically. Please visit documentation for the other options and examples at Project Configuration File


    HOWTO Fix Multiple Definitions Linker Error

    The current library implementation, using xyz-Impl.h instead of standard xyz.cpp, possibly creates certain Multiple Definitions Linker error in certain use cases.

    You can use

    #include <ESP8266_W5500_Manager.hpp>               //https://github.com/khoih-prog/ESP8266_W5500_Manager

    in many files. But be sure to use the following #include <ESP8266_W5500_Manager.h> in just 1 .h, .cpp or .ino file, which must not be included in any other file, to avoid Multiple Definitions Linker Error

    // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error
    #include <ESP8266_W5500_Manager.h>          //https://github.com/khoih-prog/ESP8266_W5500_Manager

    Have a look at the discussion in Different behaviour using the src_cpp or src_h lib #80



    How It Works

    • The ConfigOnSwitch example shows how it works and should be used as the basis for a sketch that uses this library.
    • The concept of ConfigOnSwitch is that a new ESP8266 will start a ConfigPortal when powered up and save the configuration data in non volatile memory. Thereafter, the ConfigPortal will only be started again if a button is pushed on the ESP8266 module.
    • Using any network-enabled device with a browser (computer, phone, tablet) connect to the newly created Access Point (AP) (Dynamic or Static IP specified in sketch)

    then connect WebBrowser to configurable ConfigPortal IP address, e.g. 192.168.186



    HOWTO Basic configurations

    1. Using default for every configurable parameter

    • Include in your sketch
    #include <FS.h>
    
    #include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
    //needed for library
    #include <DNSServer.h>
    
    #define USE_LITTLEFS      true
    
    #if USE_LITTLEFS
      #include <LittleFS.h>
      FS* filesystem =      &LittleFS;
      #define FileFS        LittleFS
      #define FS_Name       "LittleFS"
    #else
      FS* filesystem =      &SPIFFS;
      #define FileFS        SPIFFS
      #define FS_Name       "SPIFFS"
    #endif
    
    //////////////////////////////////////////////////////////
    
    #define ESP_getChipId()   (ESP.getChipId())
    
    #define LED_ON      LOW
    #define LED_OFF     HIGH
    
    //////////////////////////////////////////////////////////
    
    // You only need to format the filesystem once
    //#define FORMAT_FILESYSTEM       true
    #define FORMAT_FILESYSTEM         false
    
    ////////////////////////////////////////////////////
    
    #include <ESP8266_W5500_Manager.h>               //https://github.com/khoih-prog/ESP8266_W5500_Manager
    
    #define HTTP_PORT     80
    

    2. Using many configurable parameters

    • Include in your sketch
    #include <FS.h>
    
    #include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
    //needed for library
    #include <DNSServer.h>
    
    #define USE_LITTLEFS      true
    
    #if USE_LITTLEFS
      #include <LittleFS.h>
      FS* filesystem =      &LittleFS;
      #define FileFS        LittleFS
      #define FS_Name       "LittleFS"
    #else
      FS* filesystem =      &SPIFFS;
      #define FileFS        SPIFFS
      #define FS_Name       "SPIFFS"
    #endif
    
    //////////////////////////////////////////////////////////
    
    #define ESP_getChipId()   (ESP.getChipId())
    
    #define LED_ON      LOW
    #define LED_OFF     HIGH
    
    //////////////////////////////////////////////////////////////
    
    // You only need to format the filesystem once
    //#define FORMAT_FILESYSTEM       true
    #define FORMAT_FILESYSTEM         false
    
    //////////////////////////////////////////////////////////////
    
    // Assuming max 49 chars
    #define TZNAME_MAX_LEN            50
    #define TIMEZONE_MAX_LEN          50
    
    typedef struct
    {
      char TZ_Name[TZNAME_MAX_LEN];     // "America/Toronto"
      char TZ[TIMEZONE_MAX_LEN];        // "EST5EDT,M3.2.0,M11.1.0"
      uint16_t checksum;
    } EthConfig;
    
    EthConfig         Ethconfig;
    
    //////////////////////////////////////////////////////////////
    
    #define  CONFIG_FILENAME              F("/eth_cred.dat")
    
    //////////////////////////////////////////////////////////////
    
    // Indicates whether ESP has credentials saved from previous session, or double reset detected
    bool initialConfig = false;
    
    // Use false if you don't like to display Available Pages in Information Page of Config Portal
    // Comment out or use true to display Available Pages in Information Page of Config Portal
    // Must be placed before #include <ESP8266_W5500_Manager.h>
    #define USE_AVAILABLE_PAGES     true
    
    // From v1.0.10 to permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used.
    // You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa
    // You have to explicitly specify false to disable the feature.
    //#define USE_STATIC_IP_CONFIG_IN_CP          false
    
    // Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal.
    // See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23)
    #define USE_ESP_ETH_MANAGER_NTP     true
    
    // Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen
    // if using too much memory
    #define USING_AFRICA        false
    #define USING_AMERICA       true
    #define USING_ANTARCTICA    false
    #define USING_ASIA          false
    #define USING_ATLANTIC      false
    #define USING_AUSTRALIA     false
    #define USING_EUROPE        false
    #define USING_INDIAN        false
    #define USING_PACIFIC       false
    #define USING_ETC_GMT       false
    
    // Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare
    // See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21)
    #define USE_CLOUDFLARE_NTP          false
    
    // New in v1.0.11
    #define USING_CORS_FEATURE          true
    
    //////////////////////////////////////////////////////////////
    
    // Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network
    #if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP)
      // Force DHCP to be true
      #if defined(USE_DHCP_IP)
        #undef USE_DHCP_IP
      #endif
      #define USE_DHCP_IP     true
    #else
      // You can select DHCP or Static IP here
      //#define USE_DHCP_IP     true
      #define USE_DHCP_IP     false
    #endif
    
    #if ( USE_DHCP_IP )
      // Use DHCP
    
      #if (_ESP8266_ETH_MGR_LOGLEVEL_ > 3)
        #warning Using DHCP IP
      #endif
    
      IPAddress stationIP   = IPAddress(0, 0, 0, 0);
      IPAddress gatewayIP   = IPAddress(192, 168, 2, 1);
      IPAddress netMask     = IPAddress(255, 255, 255, 0);
    
    #else
      // Use static IP
    
      #if (_ESP8266_ETH_MGR_LOGLEVEL_ > 3)
        #warning Using static IP
      #endif
    
      IPAddress stationIP   = IPAddress(192, 168, 2, 186);
      IPAddress gatewayIP   = IPAddress(192, 168, 2, 1);
      IPAddress netMask     = IPAddress(255, 255, 255, 0);
    #endif
    
    //////////////////////////////////////////////////////////////
    
    #define USE_CONFIGURABLE_DNS      true
    
    IPAddress dns1IP      = gatewayIP;
    IPAddress dns2IP      = IPAddress(8, 8, 8, 8);
    
    #include <ESP8266_W5500_Manager.h>               //https://github.com/khoih-prog/ESP8266_W5500_Manager
    
    #define HTTP_PORT     80
    
    //////////////////////////////////////////////////////////////
    
    /******************************************
       // Defined in ESP8266_W5500_Manager.hpp
      typedef struct
      {
        IPAddress _sta_static_ip;
        IPAddress _sta_static_gw;
        IPAddress _sta_static_sn;
        #if USE_CONFIGURABLE_DNS
        IPAddress _sta_static_dns1;
        IPAddress _sta_static_dns2;
        #endif
      }  ETH_STA_IPConfig;
    ******************************************/
    
    ETH_STA_IPConfig EthSTA_IPconfig;

    3. Using STA-mode DHCP, but don’t like to change to static IP or display in Config Portal

    // You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa
    // You have to explicitly specify false to disable the feature.
    #define USE_STATIC_IP_CONFIG_IN_CP          false

    4. Using STA-mode DHCP, but permit to change to static IP and display in Config Portal

    // You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa
    // You have to explicitly specify false to disable the feature.
    //#define USE_STATIC_IP_CONFIG_IN_CP          false
    
    // Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network
    #if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP)
      // Force DHCP to be true
      #if defined(USE_DHCP_IP)
        #undef USE_DHCP_IP
      #endif
      #define USE_DHCP_IP     true
    #else
      // You can select DHCP or Static IP here
      #define USE_DHCP_IP     true
    #endif

    5. Using STA-mode StaticIP, and be able to change to DHCP IP and display in Config Portal

    // You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa
    // You have to explicitly specify false to disable the feature.
    //#define USE_STATIC_IP_CONFIG_IN_CP          false
    
    // Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network
    #if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP)
    // Force DHCP to be true
      #if defined(USE_DHCP_IP)
        #undef USE_DHCP_IP
      #endif
      #define USE_DHCP_IP     true
    #else
      // You can select DHCP or Static IP here
      #define USE_DHCP_IP     false
    #endif

    6. Using STA-mode StaticIP and configurable DNS, and be able to change to DHCP IP and display in Config Portal

    // You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa
    // You have to explicitly specify false to disable the feature.
    //#define USE_STATIC_IP_CONFIG_IN_CP          false
    
    // Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network
    #if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP)
      // Force DHCP to be true
      #if defined(USE_DHCP_IP)
        #undef USE_DHCP_IP
      #endif
      #define USE_DHCP_IP     true
    #else
      // You can select DHCP or Static IP here
      #define USE_DHCP_IP     false
    #endif
    
    #define USE_CONFIGURABLE_DNS      true
    
    IPAddress dns1IP      = gatewayIP;
    IPAddress dns2IP      = IPAddress(8, 8, 8, 8);

    7. Using STA-mode StaticIP and auto DNS, and be able to change to DHCP IP and display in Config Portal

    // You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa
    // You have to explicitly specify false to disable the feature.
    //#define USE_STATIC_IP_CONFIG_IN_CP          false
    
    // Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network
    #if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP)
      // Force DHCP to be true
      #if defined(USE_DHCP_IP)
        #undef USE_DHCP_IP
      #endif
      #define USE_DHCP_IP     true
    #else
      // You can select DHCP or Static IP here
      #define USE_DHCP_IP     false
    #endif
    
    #define USE_CONFIGURABLE_DNS      false

    8. Not using NTP to avoid issue with some WebBrowsers, especially in CellPhone or Tablets.

    // Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal.
    // See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23)
    #define USE_ESP_ETH_MANAGER_NTP     false

    9. Using NTP feature with CloudFlare. System can hang until you have Internet access for CloudFlare.

    // Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal.
    // See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23)
    #define USE_ESP_ETH_MANAGER_NTP     true
    
    // Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare
    // See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21)
    #define USE_CLOUDFLARE_NTP          true

    10. Using NTP feature without CloudFlare to avoid system hang if no Internet access for CloudFlare.

    // Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal.
    // See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23)
    #define USE_ESP_ETH_MANAGER_NTP     true
    
    // Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare
    // See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21)
    #define USE_CLOUDFLARE_NTP          false

    11. Setting STA-mode static IP

    //ESP8266_W5500_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask, dns1IP, dns2IP);
    ESP8266_W5500_manager.setSTAStaticIPConfig(WM_STA_IPconfig);

    12. Using CORS (Cross-Origin Resource Sharing) feature

    1. To use CORS feature with default CORS Header ““. Some WebBrowsers won’t accept this allowing-all “” CORS Header.
    // Default false for using only whenever necessary to avoid security issue
    #define USING_CORS_FEATURE     true
    1. To use CORS feature with specific CORS Header “Your Access-Control-Allow-Origin”. To be modified according to your specific Allowed-Origin.
    // Default false for using only whenever necessary to avoid security issue
    #define USING_CORS_FEATURE     true
    
    ...
    
    #if USING_CORS_FEATURE
      ESP_wifiManager.setCORSHeader("Your Access-Control-Allow-Origin");
    #endif
    1. Not use CORS feature (default)
    // Default false for using only whenever necessary to avoid security issue
    #define USING_CORS_FEATURE     false

    13. How to auto getting _timezoneName

    1. Turn on auto NTP configuration by
    // Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal.
    // See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23)
    #define USE_ESP_ETH_MANAGER_NTP     true
    1. The _timezoneName, in the format similar to America/New_York, America/Toronto, Europe/London, etc., can be retrieved by using
    String tempTZ = ESP8266_W5500_manager.getTimezoneName();

    14. How to get TZ variable to configure Timezone

    1. ESP8266 TZ can be configured, using the similar to EST5EDT,M3.2.0,M11.1.0 (for America/New_York) , as follows:
    // EST5EDT,M3.2.0,M11.1.0 (for America/New_York)
    // EST5EDT is the name of the time zone
    // EST is the abbreviation used when DST is off
    // 6 hours is the time difference from GMT
    // EDT is the abbreviation used when DST is on
    // ,M3 is the third month
    // .2 is the second occurrence of the day in the month
    // .0 is Sunday
    // ,M11 is the eleventh month
    // .1 is the first occurrence of the day in the month
    // .0 is Sunday
    
    configTime(Ethconfig.TZ, "pool.ntp.org");
    1. To convert from _timezoneName to TZ, use the function getTZ() as follows:
    const char * TZ_Result = ESP8266_W5500_manager.getTZ(_timezoneName);

    The conversion depends on the stored TZs, which is using some memory, and can cause issue for ESP8266 in certain cases. Therefore, enable just the region you’re interested.

    For example, your application is used in America continent, you need just

    #define USING_AMERICA       true

    Hereafter is the regions’ list

    // Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen
    // if using too much memory
    #define USING_AFRICA        false
    #define USING_AMERICA       true
    #define USING_ANTARCTICA    false
    #define USING_ASIA          false
    #define USING_ATLANTIC      false
    #define USING_AUSTRALIA     false
    #define USING_EUROPE        false
    #define USING_INDIAN        false
    #define USING_PACIFIC       false
    #define USING_ETC_GMT       false

    15. How to use the TZ variable to configure Timezone

    configTime(Ethconfig.TZ, "pool.ntp.org");

    then to print local time

    void printLocalTime()
    {
      static time_t now;
    
      now = time(nullptr);
    
      if ( now > 1451602800 )
      {
        Serial.print("Local Date/Time: ");
        Serial.print(ctime(&now));
      }
    }


    HOWTO Open Config Portal

    • When you want to open a config portal, with default DHCP hostname ESP8266-XXXXXX, just add
    #include <ESP8266_W5500_Manager.h>              //https://github.com/khoih-prog/ESP8266_W5500_Manager
    
    #define HTTP_PORT           80
    
    WebServer webServer(HTTP_PORT);
    
    ESP8266_W5500_Manager ESP8266_W5500_manager();

    If you’d like to have a personalized hostname (RFC952-conformed,- 24 chars max,- only a..z A..Z 0..9 '-' and no '-' as last char)

    add

    ESP8266_W5500_Manager ESP8266_W5500_manager("Personalized-HostName");

    then later call

    ESP8266_W5500_manager.startConfigPortal()

    While in Config Portal, connect to it using its AP IP, e.g. 192.168.2.186, configure Credentials, then save. The settings will be saved in non volatile memory. It will then reboot and autoconnect.



    HOWTO Add Dynamic Parameters

    These illustrating steps is based on the example ConfigOnSwitchFS

    1. Determine the variables to be configured via Config Portal (CP)

    The application will:

    • use DHT sensor (either DHT11 or DHT22) and
    • need to connect to ThingSpeak with unique user’s API Key.

    The DHT sensor is connected to the ESP boards using SDA/SCL pins which also need to be configurable.

    So this is the list of variables to be dynamically configured using CP

    1. `thingspeakApiKey`,  type `char array`, max length 17 chars, and just arbitrarily selected default value to be "" or "ThingSpeak-APIKey"
    2. `sensorDht22`,       type `bool`, default to be `true` (DHT22)
    3. `pinSda`,            type `int`,  default to be `PIN_D2`
    4. `pinScl`,            type `int`,  default to be `PIN_D1`
    

    The Label can be any arbitrary string that help you identify the variable, but must be unique in your application

    The initial code will be

    #define API_KEY_LEN                 17
    
    // Default configuration values
    char thingspeakApiKey[API_KEY_LEN]  = "";
    bool sensorDht22                    = true;
    int pinSda                          = PIN_D2;     // Pin D2 mapped to pin GPIO4 of ESP8266
    int pinScl                          = PIN_D1;     // Pin D1 mapped to pin GPIO5 of ESP8266
    
    // Any unique string helping you identify the vars
    #define ThingSpeakAPI_Label         "thingspeakApiKey"
    #define SensorDht22_Label           "SensorDHT22"
    #define PinSDA_Label                "PinSda"
    #define PinSCL_Label                "PinScl"

    2. Initialize the variables to prepare for Config Portal (CP)

    The example ConfigOnSwitchFS will open the CP whenever a SW press is detected in loop(). So the code to add dynamic variables will be there, just after the CP ESP8266_W5500_Manager class initialization to create ESP8266_W5500_Manager object.

    void loop()
    {
    // is configuration portal requested?
      if ((digitalRead(TRIGGER_PIN) == LOW) || (digitalRead(TRIGGER_PIN2) == LOW))
      {
        Serial.println("\nConfiguration portal requested.");
        digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode.
    
        //Local initialization. Once its business is done, there is no need to keep it around
        ESP8266_W5500_Manager ESP8266_W5500_manager("ConfigOnSwitchFS");
    
        //Check if there is stored WiFi router/password credentials.
        //If not found, device will remain in configuration mode until switched off via webserver.
        Serial.print("Opening configuration portal. ");
        
        ...
        
        // The addition of dynamic vars will be somewhere here
        
    }    

    The ESP8266_EMParameter class constructor will be used to initialize each newly-added parameter object.

    2.1 Use the following simple constructor for simple variables such as thingspeakApiKey, pinSda and pinScl :

    ESP8266_EMParameter(const char *id, const char *placeholder, const char *defaultValue, int length);

    2.2 For example, to create a new ESP8266_EMParameter object p_thingspeakApiKey for thingspeakApiKey,

    The command to use will be

    ESP8266_EMParameter p_thingspeakApiKey(ThingSpeakAPI_Label, "Thingspeak API Key", thingspeakApiKey, API_KEY_LEN);

    where

    - p_thingspeakApiKey                  : ESP8266_EMParameter class object reference that stores the new Custom Parameter
    - id => ThingSpeakAPI_Label           : var ref to Json associative name and HTML element ID for the new Custom Paramerter you just defined in step 1
    - placeholder => "Thingspeak API Key" : HTML input placeholder and/or label element text the user sees in the configuration interface for this Custom Parameter
    - defaultValue => thingspeakApiKey    : variable for storing the value of your Custom Parameter in the file system or default value when no data is entered
    - length  => API_KEY_LEN              : max allowed length you want for this Custom Parameter to have
    

    For pinSda and pinScl, the command will be similar

    // I2C SCL and SDA parameters are integers so we need to convert them to char array but
    // no other special considerations
    char convertedValue[3];
    sprintf(convertedValue, "%d", pinSda);
    ESP8266_EMParameter p_pinSda(PinSDA_Label, "I2C SDA pin", convertedValue, 3);
    
    sprintf(convertedValue, "%d", pinScl);
    ESP8266_EMParameter p_pinScl(PinSCL_Label, "I2C SCL pin", convertedValue, 3);

    where

    - p_pinSda / p_pinScl                         : ESP8266_EMParameter class object reference that stores the new Custom Parameter
    - id => PinSDA_Label/PinSCL_Label             : var ref to Json associative name and HTML element ID for the new Custom Paramerter you just defined in step 1
    - placeholder => "I2C SDA pin"https://github.com/"I2C SCL pin"  : HTML input placeholder and/or label element text the user sees in the configuration interface for this Custom Parameter
    - defaultValue => convertedValue              : variable for storing the value of your Custom Parameter in the file system or default value when no data is entered
    - length  => 3                                : max allowed length you want for this Custom Parameter to have
    

    2.3 Use the more complex following constructor for variables such as sensorDht22:

    ESP8266_EMParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom, int labelPlacement);

    2.4 For example, to create a new ESP8266_EMParameter object p_sensorDht22 for sensorDht22,

    The command to use will be

    ESP8266_EMParameter p_sensorDht22(SensorDht22_Label, "DHT-22 Sensor", "T", 2, customhtml, WFM_LABEL_AFTER);

    where

    - p_sensorDht22                       : ESP8266_EMParameter class object reference that stores the new Custom Parameter
    - id => SensorDht22_Label             : var ref to Json associative name and HTML element ID for the new Custom Paramerter you just defined in step 1
    - placeholder => "DHT-22 Sensor"      : HTML input placeholder and/or label element text the user sees in the configuration interface for this Custom Parameter
    - defaultValue => "T"                 : variable for storing the value of your Custom Parameter in the file system or default value when no data is entered ("T" means `true`)
    - length  => 2                        : max allowed length you want for this Custom Parameter to have
    - custom => customhtml                : custom HTML code to add element type, e.g. `checkbox`, and `checked` when `sensorDht22 == true`
    - labelPlacement => WFM_LABEL_AFTER   : to place label after
    

    and customhtml Code is:

    char customhtml[24] = "type=\"checkbox\"";
    
    if (sensorDht22)
    {
      strcat(customhtml, " checked");
    }

    3. Add the variables to Config Portal (CP)

    Adding those ESP8266_EMParameter objects created in Step 2 using the function addParameter() of object ESP8266_W5500_Manager

    3.1 addParameter() function Prototype:

    //adds a custom parameter
    bool addParameter(ESP8266_EMParameter *p);

    3.2 Code to add variables to CP

    Add parameter objects, previously created in Step 2, such as : p_thingspeakApiKey, p_sensorDht22, p_pinSda and p_pinScl

    //add all parameters here
    
    ESP8266_W5500_manager.addParameter(&p_thingspeakApiKey);
    ESP8266_W5500_manager.addParameter(&p_sensorDht22);
    ESP8266_W5500_manager.addParameter(&p_pinSda);
    ESP8266_W5500_manager.addParameter(&p_pinScl);

    4. Save the variables configured in Config Portal (CP)

    When the CP exits, we have to store the parameters’ values that users input via CP to use later.

    For ESP8266, that can be EEPROM or SPIFFS.

    We can write directly to a well-defined structure of our choice, but the current example is using JSON to be portable but much more complicated and not advised for new users.

    4.1 Getting variables’ data from CP

    After users select Save, the CP ESP8266_W5500_Manager object will save the user input data into related ESP8266_EMParameter objects.

    We can now retrieve the data, using getValue() function, for each ESP8266_EMParameter object. Then we can utilize the data for our purpose, such as thingspeakApiKey to log in, sensorDht22 type to know how to handle the sensor, pinSda and pinSda to know which pins to use to communicate with the DHT sensor.

    The code is as follows:

    // Getting posted form values and overriding local variables parameters
    // Config file is written regardless the connection state
    strcpy(thingspeakApiKey, p_thingspeakApiKey.getValue());
    sensorDht22 = (strncmp(p_sensorDht22.getValue(), "T", 1) == 0);
    pinSda = atoi(p_pinSda.getValue());
    pinScl = atoi(p_pinScl.getValue());

    We can also save to FS file to use later in next boot.

    // Writing JSON config file to flash for next boot
    writeConfigFile();

    5. Write to FS (SPIFFS, LittleFS, etc.) using JSON format

    First, you have to familiarize yourself with ArduinoJson library, its functions, the disruptive differences between ArduinoJson version 5.x.x- and v6.0.0+. The best documentation can be found at The best JSON library for embedded C++.

    This documentation will discuss only ArduinoJson v6.x.x+ (ARDUINOJSON_VERSION_MAJOR >= 6)

    Then have a look at the code snippet of writeConfigFile() function and the following step-by-step explanations.

    bool writeConfigFile()
    {
      Serial.println("Saving config file");
    
    #if (ARDUINOJSON_VERSION_MAJOR >= 6)
      DynamicJsonDocument json(1024);
    #else
      DynamicJsonBuffer jsonBuffer;
      JsonObject& json = jsonBuffer.createObject();
    #endif
    
      // JSONify local configuration parameters
      json[ThingSpeakAPI_Label] = thingspeakApiKey;
      json[SensorDht22_Label] = sensorDht22;
      json[PinSDA_Label] = pinSda;
      json[PinSCL_Label] = pinScl;
    
      // Open file for writing
      File f = FileFS.open(CONFIG_FILE, "w");
    
      if (!f)
      {
        Serial.println("Failed to open config file for writing");
        return false;
      }
    
    #if (ARDUINOJSON_VERSION_MAJOR >= 6)
      serializeJsonPretty(json, Serial);
      // Write data to file and close it
      serializeJson(json, f);
    #else
      json.prettyPrintTo(Serial);
      // Write data to file and close it
      json.printTo(f);
    #endif
    
      f.close();
    
      Serial.println("\nConfig file was successfully saved");
      return true;
    }

    5.1 Create a DynamicJsonDocument Object

    We’ll create an object with size 1024 bytes, enough to hold our data:

    DynamicJsonDocument json(1024);

    5.2 Fill the DynamicJsonDocument Object with data got from Config Portal

    Then JSONify all local parameters we’ve just received from CP and wish to store into FS by using the function prototype:

    json[Unique_Label] = Value_For_Unique_Label;

    as follows:

    // JSONify local configuration parameters
    json[ThingSpeakAPI_Label] = thingspeakApiKey;
    json[SensorDht22_Label]   = sensorDht22;
    json[PinSDA_Label]        = pinSda;
    json[PinSCL_Label]        = pinScl;

    5.3 Open file to write the Jsonified data

    This is the CONFIG_FILE file name we already declared at the beginning of the sketch (for ESP8266):

    #include <SPIFFS.h>
    FS* filesystem =      &SPIFFS;
    #define FileFS        SPIFFS
        
    const char* CONFIG_FILE = "/ConfigSW.json";

    Now just open the file for writing, and abort if open-for-writing error:

    // Open file for writing
    File f = FileFS.open(CONFIG_FILE, "w");
    
    if (!f)
    {
      Serial.println("Failed to open config file for writing");
      return false;
    }

    5.4 Write the Jsonified data to CONFIG_FILE

    As simple as this single command to write the whole json object we declared then filled with data in steps 5.1 and 5.2

    // Write data to file and close it
    serializeJson(json, f);

    5.5 Close CONFIG_FILE to flush and save the data

    Soooo simple !!! Now everybody can do it.

    f.close();

    But HOWTO use the saved data in the next startup ???? That’s in next step 6.

    6. Read from FS using JSON format

    Now, you have familiarized yourself with ArduinoJson library, its functions. We’ll discuss HOWTO read data from the CONFIG_FILE in Jsonified format, then HOWTO parse the to use.

    The documentation will discuss only ArduinoJson v6.x.x+ (ARDUINOJSON_VERSION_MAJOR >= 6)

    First, have a look at the code snippet of readConfigFile() function.

    bool readConfigFile()
    {
      // this opens the config file in read-mode
      File f = FileFS.open(CONFIG_FILE, "r");
    
      if (!f)
      {
        Serial.println("Configuration file not found");
        return false;
      }
      else
      {
        // we could open the file
        size_t size = f.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size + 1]);
    
        // Read and store file contents in buf
        f.readBytes(buf.get(), size);
        // Closing file
        f.close();
        // Using dynamic JSON buffer which is not the recommended memory model, but anyway
        // See https://github.com/bblanchon/ArduinoJson/wiki/Memory%20model
    
    #if (ARDUINOJSON_VERSION_MAJOR >= 6)
        DynamicJsonDocument json(1024);
        auto deserializeError = deserializeJson(json, buf.get());
        if ( deserializeError )
        {
          Serial.println("JSON parseObject() failed");
          return false;
        }
        serializeJson(json, Serial);
    #else
        DynamicJsonBuffer jsonBuffer;
        // Parse JSON string
        JsonObject& json = jsonBuffer.parseObject(buf.get());
        // Test if parsing succeeds.
        if (!json.success())
        {
          Serial.println("JSON parseObject() failed");
          return false;
        }
        json.printTo(Serial);
    #endif
    
        // Parse all config file parameters, override
        // local config variables with parsed values
        if (json.containsKey(ThingSpeakAPI_Label))
        {
          strcpy(thingspeakApiKey, json[ThingSpeakAPI_Label]);
        }
    
        if (json.containsKey(SensorDht22_Label))
        {
          sensorDht22 = json[SensorDht22_Label];
        }
    
        if (json.containsKey(PinSDA_Label))
        {
          pinSda = json[PinSDA_Label];
        }
    
        if (json.containsKey(PinSCL_Label))
        {
          pinScl = json[PinSCL_Label];
        }
      }
      Serial.println("\nConfig file was successfully parsed");
      return true;
    }
    

    and the following step-by-step explanations.

    6.1 Open CONFIG_FILE to read

    As simple as this

    // this opens the config file in read-mode
    File f = FileFS.open(CONFIG_FILE, "r");

    We’ll inform and abort if the CONFIG_FILE can’t be opened (file not found, can’t be opened, etc.)

    if (!f)
    {
      Serial.println("Configuration file not found");
      return false;
    }

    6.2 Open CONFIG_FILE to read

    Now we have to determine the file size to create a buffer large enough to store the to-be-read data

    // we could open the file
    size_t size = f.size();
    // Allocate a buffer to store contents of the file.
    std::unique_ptr<char[]> buf(new char[size + 1]);

    Remember always add 1 to the buffer length to store the terminating 0.

    Then just read the file into the buffer, and close the file to be safe

    // Read and store file contents in buf
    f.readBytes(buf.get(), size);
    // Closing file
    f.close();

    6.3 Populate the just-read Jsonified data into the DynamicJsonDocument json object

    We again use the same DynamicJsonDocument json object to store the data we’ve just read from CONFIG_FILE.

    Why the same complicated DynamicJsonDocument json object ?? Because in steps 5, we did store Jsonified data using the same DynamicJsonDocument json object. It’s much easier we now use it again to facilitate the parsing of Jsonified data back to the data we can use easily.

    We first create the object with enough size

    DynamicJsonDocument json(1024);

    then populate it with data from buffer we read from CONFIG_FILE in step 6.2, pre-parse and check for error. All is done just by one command deserializeJson()

    auto deserializeError = deserializeJson(json, buf.get());

    Abort if there is any data error in the process of writing, storing, reading back. If OK, just nicely print out to the Debug Terminal

    if ( deserializeError )
    {
      Serial.println("JSON parseObject() failed");
      return false;
    }
    
    serializeJson(json, Serial);

    6.4 Parse the Jsonified data from the DynamicJsonDocument json object to store into corresponding parameters

    This is as simple as in the step 5.2, but in reverse direction.

    To be sure there is good corresponding data, not garbage, for each variable, we have to perform sanity checks by verifying the DynamicJsonDocument json object still contains the correct keys we passed to it when we wrote into CONFIG_FILE.

    For example:

    if (json.containsKey(ThingSpeakAPI_Label))

    Then proceed to get every parameter we know we stored there from last CP Save.

    // Parse all config file parameters, override
    // local config variables with parsed values
    if (json.containsKey(ThingSpeakAPI_Label))
    {
      strcpy(thingspeakApiKey, json[ThingSpeakAPI_Label]);
    }
    
    if (json.containsKey(SensorDht22_Label))
    {
      sensorDht22 = json[SensorDht22_Label];
    }
    
    if (json.containsKey(PinSDA_Label))
    {
      pinSda = json[PinSDA_Label];
    }
    
    if (json.containsKey(PinSCL_Label))
    {
      pinScl = json[PinSCL_Label];
    }

    6.5 Then what to do now

    Just use those parameters for whatever purpose you designed them for in step 1:

    The application will use DHT sensor (either DHT11 or DHT22) and need to connect to ThingSpeak with unique user's API Key. The DHT sensor is connected to the ESP boards using SDA/SCL pins which also need to be configurable.


    So, how it works?

    In ConfigPortal Mode, it starts an access point @ the current Static or DHCP IP.

    Connect to it by going to, e.g. http://192.168.2.186, you’ll see this Main page:

    Select Information to enter the Info page where the board info will be shown (long page)

    Select Configuration to enter this page where you can modify its Credentials

    Enter your credentials, then click Save. The Credentials will be saved and the board continues, or reboots if necessary, to connect to the selected Static IP or DHCP.

    If you’re already satisfied with the current settings and don’t want to change anything, just select Exit Portal from the Main page to continue or reboot the board and connect using the previously-stored Credentials.



    Documentation

    Password protect the configuration Access Point

    You can password protect the ConfigPortal AP. Check ESP8266_FSWebServer example


    Callbacks

    Save settings

    This gets called when custom parameters have been set AND a connection has been established. Use it to set a flag, so when all the configuration finishes, you can save the extra parameters somewhere.

    See ConfigOnSwitchFS Example.

    ESP8266_W5500_manager.setSaveConfigCallback(saveConfigCallback);

    saveConfigCallback declaration and example

    //flag for saving data
    bool shouldSaveConfig = false;
    
    //callback notifying us of the need to save config
    void saveConfigCallback () 
    {
      Serial.println("Should save config");
      shouldSaveConfig = true;
    }

    ConfigPortal Timeout

    If you need to set a timeout so the ESP8266 doesn’t hang waiting to be configured for ever.

    ESP8266_W5500_manager.setConfigPortalTimeout(120);

    which will wait 2 minutes (120 seconds). When the time passes, the startConfigPortal() function will return and continue the sketch, unless you’re accessing the Config Portal. In this case, the startConfigPortal() function will stay until you save config data or exit the Config Portal.


    On Demand ConfigPortal

    Example usage

    void loop()
    {
      // is configuration portal requested?
      if ((digitalRead(TRIGGER_PIN) == LOW) || (digitalRead(TRIGGER_PIN2) == LOW))
      {
        Serial.println(F("\nConfiguration portal requested."));
        digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode.
    
        //Local initialization. Once its business is done, there is no need to keep it around
        // Use this to default DHCP hostname to ESP8266-XXXXXX
        //ESP8266_W5500_Manager ESP8266_W5500_manager;
        // Use this to personalize DHCP hostname (RFC952 conformed)
        ESP8266_W5500_Manager ESP8266_W5500_manager("ConfigOnSwitch");
    
    #if !USE_DHCP_IP
    #if USE_CONFIGURABLE_DNS
        // Set static IP, Gateway, Subnetmask, DNS1 and DNS2
        ESP8266_W5500_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask, dns1IP, dns2IP);
    #else
        // Set static IP, Gateway, Subnetmask, Use auto DNS1 and DNS2.
        ESP8266_W5500_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask);
    #endif
    #endif
    
    #if USING_CORS_FEATURE
        ESP8266_W5500_manager.setCORSHeader("Your Access-Control-Allow-Origin");
    #endif
    
        //Check if there is stored credentials.
        //If not found, device will remain in configuration mode until switched off via webserver.
        Serial.println(F("Opening configuration portal. "));
    
        if (loadConfigData())
        {
          //If no access point name has been previously entered disable timeout.
          ESP8266_W5500_manager.setConfigPortalTimeout(120);
    
          Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal"));
        }
        else
        {
          // Enter CP only if no stored SSID on flash and file
          ESP8266_W5500_manager.setConfigPortalTimeout(0);
          Serial.println(F("Open Config Portal without Timeout: No stored Credentials."));
          initialConfig = true;
        }
    
        //Starts an access point
        //and goes into a blocking loop awaiting configuration
        if (!ESP8266_W5500_manager.startConfigPortal())
          Serial.println(F("Not connected to ETH network but continuing anyway."));
        else
        {
          Serial.println(F("ETH network connected...yeey :)"));
          Serial.print(F("Local IP: "));
          Serial.println(eth.localIP());
        }
    
    #if USE_ESP_ETH_MANAGER_NTP
        String tempTZ = ESP8266_W5500_manager.getTimezoneName();
    
        if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1)
          strcpy(Ethconfig.TZ_Name, tempTZ.c_str());
        else
          strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1);
    
        const char * TZ_Result = ESP8266_W5500_manager.getTZ(Ethconfig.TZ_Name);
    
        if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1)
          strcpy(Ethconfig.TZ, TZ_Result);
        else
          strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1);
    
        if ( strlen(Ethconfig.TZ_Name) > 0 )
        {
          LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ);
    
          configTime(Ethconfig.TZ, "pool.ntp.org");
        }
        else
        {
          LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set."));
        }
    
    #endif
    
        ESP8266_W5500_manager.getSTAStaticIPConfig(EthSTA_IPconfig);
    
        saveConfigData();
    
    #if !USE_DHCP_IP
    
        // Reset to use new Static IP, if different from current eth.localIP()
        if (eth.localIP() != EthSTA_IPconfig._sta_static_ip)
        {
          Serial.print(F("Current IP = "));
          Serial.print(eth.localIP());
          Serial.print(F(". Reset to take new IP = "));
          Serial.println(EthSTA_IPconfig._sta_static_ip);
    
          ESP.reset();
          delay(2000);
        }
    
    #endif
    
        digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode.
      }
    
      // put your main code here, to run repeatedly
      check_status();
    }

    See ConfigOnSwitch example for a more complex version.



    Custom Parameters

    Many applications need configuration parameters like MQTT host and port, Blynk or emoncms tokens, etc. While it is possible to use ESP8266_W5500_Manager to collect additional parameters, it is better to read these parameters from a web service once ESP8266_W5500_Manager has been used to connect to the Internet.

    To capture other parameters with ESP8266_W5500_Manager is a little bit more complicated than all the other features. This requires adding custom HTML to your form.

    If you want to do it with ESP8266_W5500_Manager see the example ConfigOnSwitchFS


    Custom IP Configuration

    You can set a custom IP for STA (station mode, client mode, normal project state)

    Custom Station (client) Static IP Configuration

    This will use the specified IP configuration instead of using DHCP in station mode.

    ESP8266_W5500_manager.setSTAStaticIPConfig(IPAddress(192,168,2,186), IPAddress(192,168,2,1), IPAddress(255,255,255,0));

    Custom HTML, CSS, Javascript

    There are various ways in which you can inject custom HTML, CSS or Javascript into the ConfigPortal.

    The options are:

    • inject custom head element

    You can use this to any html bit to the head of the ConfigPortal. If you add a <style> element, keep in mind it overwrites the included css, not replaces.

    ESP8266_W5500_manager.setCustomHeadElement("<style>html{filter: invert(100%); -webkit-filter: invert(100%);}</style>");
    • inject a custom bit of html in the configuration form
    ESP8266_EMParameter custom_text("<p>This is just a text paragraph</p>");
    ESP8266_W5500_manager.addParameter(&custom_text);
    • inject a custom bit of html in a configuration form element

    Just add the bit you want added as the last parameter to the custom parameter constructor.

    ESP8266_EMParameter custom_mqtt_server("server", "mqtt server", "iot.eclipse", 40, " readonly");


    How to connect W5x00 to ESP8266

    You can change the CS/SS pin to another one. Default is GPIO16

    Connecting CS/SS to TX0/GPIO15 interferes with uploading firmware to ESP8266. If absolutely necessary to use TX0/GPIO15, remove the wire to TX0/GPIO15 before uploading firmware. Then reconnect after done uploading.

    // Using GPIO4, GPIO16, or GPIO5
    #define CSPIN             16

    W5x00 <—> ESP8266
    MOSI <—> GPIO13
    MISO <—> GPIO12
    SCK <—> GPIO14
    SS <—> GPIO16
    GND <—> GND
    3.3V <—> 3.3V


    Examples

    1. ConfigOnSwitch
    2. ConfigOnSwitchFS
    3. ConfigOnDoubleReset_TZ (now support ArduinoJson 6.0.0+ as well as 5.13.5-)
    4. ConfigOnDoubleReset (now support ArduinoJson 6.0.0+ as well as 5.13.5-)
    5. ConfigPortalParamsOnSwitch (now support ArduinoJson 6.0.0+ as well as 5.13.5-)
    6. ESP8266_FSWebServer
    7. ESP8266_FSWebServer_DRD


    #if !( defined(ESP8266) )
    #error This code is intended to run on the (ESP8266 + LwIP W5500) platform! Please check your Tools->Board setting.
    #endif
    //////////////////////////////////////////////////////////////
    // Use from 0 to 4. Higher number, more debugging messages and memory usage.
    #define _ESP8266_ETH_MGR_LOGLEVEL_ 4
    // To not display stored SSIDs and PWDs on Config Portal, select false. Default is true
    // Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials
    //#define DISPLAY_STORED_CREDENTIALS_IN_CP false
    //////////////////////////////////////////////////////////////
    // Using GPIO4, GPIO16, or GPIO5
    #define CSPIN 16
    //////////////////////////////////////////////////////////
    #include <FS.h>
    #include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
    //needed for library
    #include <DNSServer.h>
    #define USE_LITTLEFS true
    #if USE_LITTLEFS
    #include <LittleFS.h>
    FS* filesystem = &LittleFS;
    #define FileFS LittleFS
    #define FS_Name LittleFS
    #else
    FS* filesystem = &SPIFFS;
    #define FileFS SPIFFS
    #define FS_Name SPIFFS
    #endif
    //////////////////////////////////////////////////////////
    #define ESP_getChipId() (ESP.getChipId())
    #define LED_ON LOW
    #define LED_OFF HIGH
    //////////////////////////////////////////////////////////
    // Onboard LED I/O pin on NodeMCU board
    #define LED_BUILTIN 2 // Pin D4 mapped to pin GPIO2/TXD1 of ESP8266, NodeMCU and WeMoS, control on-board LED
    //PIN_D0 can’t be used for PWM/I2C
    #define PIN_D0 16 // Pin D0 mapped to pin GPIO16/USER/WAKE of ESP8266. This pin is also used for Onboard-Blue LED. PIN_D0 = 0 => LED ON
    #define PIN_D1 5 // Pin D1 mapped to pin GPIO5 of ESP8266
    #define PIN_D2 4 // Pin D2 mapped to pin GPIO4 of ESP8266
    #define PIN_D3 0 // Pin D3 mapped to pin GPIO0/FLASH of ESP8266
    #define PIN_D4 2 // Pin D4 mapped to pin GPIO2/TXD1 of ESP8266
    #define PIN_D5 14 // Pin D5 mapped to pin GPIO14/HSCLK of ESP8266
    #define PIN_D6 12 // Pin D6 mapped to pin GPIO12/HMISO of ESP8266
    #define PIN_D7 13 // Pin D7 mapped to pin GPIO13/RXD2/HMOSI of ESP8266
    #define PIN_D8 15 // Pin D8 mapped to pin GPIO15/TXD2/HCS of ESP8266
    //Don’t use pins GPIO6 to GPIO11 as already connected to flash, etc. Use them can crash the program
    //GPIO9(D11/SD2) and GPIO11 can be used only if flash in DIO mode ( not the default QIO mode)
    #define PIN_D11 9 // Pin D11/SD2 mapped to pin GPIO9/SDD2 of ESP8266
    #define PIN_D12 10 // Pin D12/SD3 mapped to pin GPIO10/SDD3 of ESP8266
    #define PIN_SD2 9 // Pin SD2 mapped to pin GPIO9/SDD2 of ESP8266
    #define PIN_SD3 10 // Pin SD3 mapped to pin GPIO10/SDD3 of ESP8266
    #define PIN_D9 3 // Pin D9 /RX mapped to pin GPIO3/RXD0 of ESP8266
    #define PIN_D10 1 // Pin D10/TX mapped to pin GPIO1/TXD0 of ESP8266
    #define PIN_RX 3 // Pin RX mapped to pin GPIO3/RXD0 of ESP8266
    #define PIN_TX 1 // Pin RX mapped to pin GPIO1/TXD0 of ESP8266
    #define LED_PIN 16 // Pin D0 mapped to pin GPIO16 of ESP8266. This pin is also used for Onboard-Blue LED. PIN_D0 = 0 => LED ON
    /* Trigger for inititating config mode is Pin D1 and also flash button on NodeMCU
    Flash button is convenient to use but if it is pressed it will stuff up the serial port device driver
    until the computer is rebooted on windows machines.
    */
    const int TRIGGER_PIN = PIN_D1; // D1 on NodeMCU and WeMos.
    /*
    Alternative trigger pin. Needs to be connected to a button to use this pin. It must be a momentary connection
    not connected permanently to ground. Either trigger pin will work.
    */
    const int TRIGGER_PIN2 = PIN_D2; // D2 on NodeMCU and WeMos.
    //////////////////////////////////////////////////////////////
    // You only need to format the filesystem once
    //#define FORMAT_FILESYSTEM true
    #define FORMAT_FILESYSTEM false
    //////////////////////////////////////////////////////////////
    // Assuming max 49 chars
    #define TZNAME_MAX_LEN 50
    #define TIMEZONE_MAX_LEN 50
    typedef struct
    {
    char TZ_Name[TZNAME_MAX_LEN]; // “America/Toronto”
    char TZ[TIMEZONE_MAX_LEN]; // “EST5EDT,M3.2.0,M11.1.0”
    uint16_t checksum;
    } EthConfig;
    EthConfig Ethconfig;
    //////////////////////////////////////////////////////////////
    #define CONFIG_FILENAME F(/eth_cred.dat)
    //////////////////////////////////////////////////////////////
    // Indicates whether ESP has credentials saved from previous session, or double reset detected
    bool initialConfig = false;
    // Use false if you don’t like to display Available Pages in Information Page of Config Portal
    // Comment out or use true to display Available Pages in Information Page of Config Portal
    // Must be placed before #include <ESP8266_W5500_Manager.h>
    #define USE_AVAILABLE_PAGES true
    // From v1.0.10 to permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used.
    // You’ll loose the feature of dynamically changing from DHCP to static IP, or vice versa
    // You have to explicitly specify false to disable the feature.
    //#define USE_STATIC_IP_CONFIG_IN_CP false
    // Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal.
    // See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23)
    #define USE_ESP_ETH_MANAGER_NTP true
    // Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen
    // if using too much memory
    #define USING_AFRICA false
    #define USING_AMERICA true
    #define USING_ANTARCTICA false
    #define USING_ASIA false
    #define USING_ATLANTIC false
    #define USING_AUSTRALIA false
    #define USING_EUROPE false
    #define USING_INDIAN false
    #define USING_PACIFIC false
    #define USING_ETC_GMT false
    // Use true to enable CloudFlare NTP service. System can hang if you don’t have Internet access while accessing CloudFlare
    // See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21)
    #define USE_CLOUDFLARE_NTP false
    // New in v1.0.11
    #define USING_CORS_FEATURE true
    //////////////////////////////////////////////////////////////
    // Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network
    #if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP)
    // Force DHCP to be true
    #if defined(USE_DHCP_IP)
    #undef USE_DHCP_IP
    #endif
    #define USE_DHCP_IP true
    #else
    // You can select DHCP or Static IP here
    //#define USE_DHCP_IP true
    #define USE_DHCP_IP false
    #endif
    #if ( USE_DHCP_IP )
    // Use DHCP
    #if (_ESP8266_ETH_MGR_LOGLEVEL_ > 3)
    #warning Using DHCP IP
    #endif
    IPAddress stationIP = IPAddress(0, 0, 0, 0);
    IPAddress gatewayIP = IPAddress(192, 168, 2, 1);
    IPAddress netMask = IPAddress(255, 255, 255, 0);
    #else
    // Use static IP
    #if (_ESP8266_ETH_MGR_LOGLEVEL_ > 3)
    #warning Using static IP
    #endif
    IPAddress stationIP = IPAddress(192, 168, 2, 186);
    IPAddress gatewayIP = IPAddress(192, 168, 2, 1);
    IPAddress netMask = IPAddress(255, 255, 255, 0);
    #endif
    //////////////////////////////////////////////////////////////
    #define USE_CONFIGURABLE_DNS true
    IPAddress dns1IP = gatewayIP;
    IPAddress dns2IP = IPAddress(8, 8, 8, 8);
    #include <ESP8266_W5500_Manager.h> //https://github.com/khoih-prog/ESP8266_W5500_Manager
    #define HTTP_PORT 80
    //////////////////////////////////////////////////////////////
    /******************************************
    // Defined in ESP8266_W5500_Manager.hpp
    typedef struct
    {
    IPAddress _sta_static_ip;
    IPAddress _sta_static_gw;
    IPAddress _sta_static_sn;
    #if USE_CONFIGURABLE_DNS
    IPAddress _sta_static_dns1;
    IPAddress _sta_static_dns2;
    #endif
    } ETH_STA_IPConfig;
    ******************************************/
    ETH_STA_IPConfig EthSTA_IPconfig;
    //////////////////////////////////////////////////////////////
    void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig)
    {
    in_EthSTA_IPconfig._sta_static_ip = stationIP;
    in_EthSTA_IPconfig._sta_static_gw = gatewayIP;
    in_EthSTA_IPconfig._sta_static_sn = netMask;
    #if USE_CONFIGURABLE_DNS
    in_EthSTA_IPconfig._sta_static_dns1 = dns1IP;
    in_EthSTA_IPconfig._sta_static_dns2 = dns2IP;
    #endif
    }
    //////////////////////////////////////////////////////////////
    void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig)
    {
    LOGERROR3(F(stationIP =), in_EthSTA_IPconfig._sta_static_ip, , gatewayIP =, in_EthSTA_IPconfig._sta_static_gw);
    LOGERROR1(F(netMask =), in_EthSTA_IPconfig._sta_static_sn);
    #if USE_CONFIGURABLE_DNS
    LOGERROR3(F(dns1IP =), in_EthSTA_IPconfig._sta_static_dns1, , dns2IP =, in_EthSTA_IPconfig._sta_static_dns2);
    #endif
    }
    //////////////////////////////////////////////////////////////
    void toggleLED()
    {
    //toggle state
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    }
    //////////////////////////////////////////////////////////////
    #if USE_ESP_ETH_MANAGER_NTP
    void printLocalTime()
    {
    static time_t now;
    now = time(nullptr);
    if ( now > 1451602800 )
    {
    Serial.print(Local Date/Time: );
    Serial.print(ctime(&now));
    }
    }
    #endif
    //////////////////////////////////////////////////////////////
    void heartBeatPrint()
    {
    #if USE_ESP_ETH_MANAGER_NTP
    printLocalTime();
    #else
    static int num = 1;
    if (eth.connected())
    Serial.print(F(H)); // H means connected to Ethernet
    else
    Serial.print(F(F)); // F means not connected to Ethernet
    if (num == 80)
    {
    Serial.println();
    num = 1;
    }
    else if (num++ % 10 == 0)
    {
    Serial.print(F( ));
    }
    #endif
    }
    //////////////////////////////////////////////////////////////
    void check_status()
    {
    static ulong checkstatus_timeout = 0;
    static ulong LEDstatus_timeout = 0;
    static ulong current_millis;
    #if USE_ESP_ETH_MANAGER_NTP
    #define HEARTBEAT_INTERVAL 60000L
    #else
    #define HEARTBEAT_INTERVAL 10000L
    #endif
    #define LED_INTERVAL 2000L
    current_millis = millis();
    if ((current_millis > LEDstatus_timeout) || (LEDstatus_timeout == 0))
    {
    // Toggle LED at LED_INTERVAL = 2s
    toggleLED();
    LEDstatus_timeout = current_millis + LED_INTERVAL;
    }
    // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds.
    if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0))
    {
    heartBeatPrint();
    checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL;
    }
    }
    //////////////////////////////////////////////////////////////
    int calcChecksum(uint8_t* address, uint16_t sizeToCalc)
    {
    uint16_t checkSum = 0;
    for (uint16_t index = 0; index < sizeToCalc; index++)
    {
    checkSum += * ( ( (byte*) address ) + index);
    }
    return checkSum;
    }
    //////////////////////////////////////////////////////////////
    bool loadConfigData()
    {
    File file = FileFS.open(CONFIG_FILENAME, r);
    LOGERROR(F(LoadCfgFile ));
    memset((void *) &Ethconfig, 0, sizeof(Ethconfig));
    memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig));
    if (file)
    {
    file.readBytes((char *) &Ethconfig, sizeof(Ethconfig));
    file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig));
    file.close();
    LOGERROR(F(OK));
    if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) – sizeof(Ethconfig.checksum) ) )
    {
    LOGERROR(F(Ethconfig checksum wrong));
    return false;
    }
    displayIPConfigStruct(EthSTA_IPconfig);
    return true;
    }
    else
    {
    LOGERROR(F(failed));
    return false;
    }
    }
    //////////////////////////////////////////////////////////////
    void saveConfigData()
    {
    File file = FileFS.open(CONFIG_FILENAME, w);
    LOGERROR(F(SaveCfgFile ));
    if (file)
    {
    Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) – sizeof(Ethconfig.checksum) );
    file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig));
    displayIPConfigStruct(EthSTA_IPconfig);
    file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig));
    file.close();
    LOGERROR(F(OK));
    }
    else
    {
    LOGERROR(F(failed));
    }
    }
    //////////////////////////////////////////////////////////////
    void initEthernet()
    {
    SPI.begin();
    SPI.setClockDivider(SPI_CLOCK_DIV4);
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);
    LOGWARN(F(Default SPI pinout:));
    LOGWARN1(F(MOSI:), MOSI);
    LOGWARN1(F(MISO:), MISO);
    LOGWARN1(F(SCK:), SCK);
    LOGWARN1(F(CS:), CSPIN);
    LOGWARN(F(=========================));
    #if !USING_DHCP
    //eth.config(localIP, gateway, netMask, gateway);
    eth.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn,
    EthSTA_IPconfig._sta_static_dns1);
    #endif
    eth.setDefault();
    if (!eth.begin())
    {
    Serial.println(No Ethernet hardware … Stop here);
    while (true)
    {
    delay(1000);
    }
    }
    else
    {
    Serial.print(Connecting to network : );
    while (!eth.connected())
    {
    Serial.print(.);
    delay(1000);
    }
    }
    Serial.println();
    #if USING_DHCP
    Serial.print(Ethernet DHCP IP address: );
    #else
    Serial.print(Ethernet Static IP address: );
    #endif
    Serial.println(eth.localIP());
    }
    //////////////////////////////////////////////////////////////
    void setup()
    {
    //set led pin as output
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(TRIGGER_PIN, INPUT_PULLUP);
    pinMode(TRIGGER_PIN2, INPUT_PULLUP);
    Serial.begin(115200);
    while (!Serial && millis() < 5000);
    delay(200);
    Serial.print(F(\nStarting ConfigOnSwitch using ));
    Serial.print(FS_Name);
    Serial.print(F( on ));
    Serial.print(ARDUINO_BOARD);
    Serial.print(F( with ));
    Serial.println(SHIELD_TYPE);
    Serial.println(ESP8266_W5500_MANAGER_VERSION);
    Serial.setDebugOutput(false);
    #if FORMAT_FILESYSTEM
    Serial.println(F(Forced Formatting.));
    FileFS.format();
    #endif
    // Format FileFS if not yet
    if (!FileFS.begin())
    {
    FileFS.format();
    Serial.println(F(SPIFFS/LittleFS failed! Already tried formatting.));
    if (!FileFS.begin())
    {
    // prevents debug info from the library to hide err message.
    delay(100);
    #if USE_LITTLEFS
    Serial.println(F(LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever));
    #else
    Serial.println(F(SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever));
    #endif
    while (true)
    {
    delay(1);
    }
    }
    }
    unsigned long startedAt = millis();
    initSTAIPConfigStruct(EthSTA_IPconfig);
    digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode.
    //Local intialization. Once its business is done, there is no need to keep it around
    // Use this to default DHCP hostname to ESP8266-XXXXXX
    //ESP8266_W5500_Manager ESP8266_W5500_manager;
    // Use this to personalize DHCP hostname (RFC952 conformed)
    ESP8266_W5500_Manager ESP8266_W5500_manager(ConfigOnSwitch);
    ESP8266_W5500_manager.setDebugOutput(true);
    #if !USE_DHCP_IP
    // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask)
    ESP8266_W5500_manager.setSTAStaticIPConfig(EthSTA_IPconfig);
    #endif
    #if USING_CORS_FEATURE
    ESP8266_W5500_manager.setCORSHeader(Your Access-Control-Allow-Origin);
    #endif
    bool configDataLoaded = false;
    if (loadConfigData())
    {
    configDataLoaded = true;
    //If no access point name has been previously entered disable timeout.
    ESP8266_W5500_manager.setConfigPortalTimeout(120);
    Serial.println(F(Got stored Credentials. Timeout 120s for Config Portal));
    #if USE_ESP_ETH_MANAGER_NTP
    if ( strlen(Ethconfig.TZ_Name) > 0 )
    {
    LOGERROR3(F(Current TZ_Name =), Ethconfig.TZ_Name, F(, TZ = ), Ethconfig.TZ);
    configTime(Ethconfig.TZ, pool.ntp.org);
    }
    else
    {
    Serial.println(F(Current Timezone is not set. Enter Config Portal to set.));
    }
    #endif
    }
    else
    {
    // Enter CP only if no stored SSID on flash and file
    Serial.println(F(Open Config Portal without Timeout: No stored Credentials.));
    initialConfig = true;
    }
    //////////////////////////////////
    // Connect ETH now if using STA
    initEthernet();
    //////////////////////////////////
    if (initialConfig)
    {
    Serial.print(F(Starting configuration portal @ ));
    Serial.println(eth.localIP());
    digitalWrite(LED_BUILTIN, LED_ON); // Turn led on as we are in configuration mode.
    //sets timeout in seconds until configuration portal gets turned off.
    //If not specified device will remain in configuration mode until
    //switched off via webserver or device is restarted.
    //ESP8266_W5500_manager.setConfigPortalTimeout(600);
    // Starts an access point
    if (!ESP8266_W5500_manager.startConfigPortal())
    Serial.println(F(Not connected to ETH network but continuing anyway.));
    else
    {
    Serial.println(F(ETH network connected…yeey 🙂));
    }
    #if USE_ESP_ETH_MANAGER_NTP
    String tempTZ = ESP8266_W5500_manager.getTimezoneName();
    if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) – 1)
    strcpy(Ethconfig.TZ_Name, tempTZ.c_str());
    else
    strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) – 1);
    const char * TZ_Result = ESP8266_W5500_manager.getTZ(Ethconfig.TZ_Name);
    if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) – 1)
    strcpy(Ethconfig.TZ, TZ_Result);
    else
    strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) – 1);
    if ( strlen(Ethconfig.TZ_Name) > 0 )
    {
    LOGERROR3(F(Saving current TZ_Name =), Ethconfig.TZ_Name, F(, TZ = ), Ethconfig.TZ);
    configTime(Ethconfig.TZ, pool.ntp.org);
    }
    else
    {
    LOGERROR(F(Current Timezone Name is not set. Enter Config Portal to set.));
    }
    #endif
    ESP8266_W5500_manager.getSTAStaticIPConfig(EthSTA_IPconfig);
    saveConfigData();
    }
    digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode.
    startedAt = millis();
    Serial.print(F(After waiting ));
    Serial.print((float) (millis() – startedAt) / 1000);
    Serial.print(F( secs more in setup(), connection result is ));
    if (eth.connected())
    {
    Serial.print(F(connected. Local IP: ));
    Serial.println(eth.localIP());
    }
    }
    //////////////////////////////////////////////////////////////
    void loop()
    {
    // is configuration portal requested?
    if ((digitalRead(TRIGGER_PIN) == LOW) || (digitalRead(TRIGGER_PIN2) == LOW))
    {
    Serial.println(F(\nConfiguration portal requested.));
    digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode.
    //Local intialization. Once its business is done, there is no need to keep it around
    // Use this to default DHCP hostname to ESP8266-XXXXXX
    //ESP8266_W5500_Manager ESP8266_W5500_manager;
    // Use this to personalize DHCP hostname (RFC952 conformed)
    ESP8266_W5500_Manager ESP8266_W5500_manager(ConfigOnSwitch);
    #if !USE_DHCP_IP
    #if USE_CONFIGURABLE_DNS
    // Set static IP, Gateway, Subnetmask, DNS1 and DNS2
    ESP8266_W5500_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask, dns1IP, dns2IP);
    #else
    // Set static IP, Gateway, Subnetmask, Use auto DNS1 and DNS2.
    ESP8266_W5500_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask);
    #endif
    #endif
    #if USING_CORS_FEATURE
    ESP8266_W5500_manager.setCORSHeader(Your Access-Control-Allow-Origin);
    #endif
    //Check if there is stored credentials.
    //If not found, device will remain in configuration mode until switched off via webserver.
    Serial.println(F(Opening configuration portal. ));
    if (loadConfigData())
    {
    //If no access point name has been previously entered disable timeout.
    ESP8266_W5500_manager.setConfigPortalTimeout(120);
    Serial.println(F(Got stored Credentials. Timeout 120s for Config Portal));
    }
    else
    {
    // Enter CP only if no stored SSID on flash and file
    ESP8266_W5500_manager.setConfigPortalTimeout(0);
    Serial.println(F(Open Config Portal without Timeout: No stored Credentials.));
    initialConfig = true;
    }
    //Starts an access point
    //and goes into a blocking loop awaiting configuration
    if (!ESP8266_W5500_manager.startConfigPortal())
    Serial.println(F(Not connected to ETH network but continuing anyway.));
    else
    {
    Serial.println(F(ETH network connected…yeey 🙂));
    Serial.print(F(Local IP: ));
    Serial.println(eth.localIP());
    }
    #if USE_ESP_ETH_MANAGER_NTP
    String tempTZ = ESP8266_W5500_manager.getTimezoneName();
    if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) – 1)
    strcpy(Ethconfig.TZ_Name, tempTZ.c_str());
    else
    strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) – 1);
    const char * TZ_Result = ESP8266_W5500_manager.getTZ(Ethconfig.TZ_Name);
    if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) – 1)
    strcpy(Ethconfig.TZ, TZ_Result);
    else
    strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) – 1);
    if ( strlen(Ethconfig.TZ_Name) > 0 )
    {
    LOGERROR3(F(Saving current TZ_Name =), Ethconfig.TZ_Name, F(, TZ = ), Ethconfig.TZ);
    configTime(Ethconfig.TZ, pool.ntp.org);
    }
    else
    {
    LOGERROR(F(Current Timezone Name is not set. Enter Config Portal to set.));
    }
    #endif
    ESP8266_W5500_manager.getSTAStaticIPConfig(EthSTA_IPconfig);
    saveConfigData();
    #if !USE_DHCP_IP
    // Reset to use new Static IP, if different from current eth.localIP()
    if (eth.localIP() != EthSTA_IPconfig._sta_static_ip)
    {
    Serial.print(F(Current IP = ));
    Serial.print(eth.localIP());
    Serial.print(F(. Reset to take new IP = ));
    Serial.println(EthSTA_IPconfig._sta_static_ip);
    ESP.reset();
    delay(2000);
    }
    #endif
    digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode.
    }
    // put your main code here, to run repeatedly
    check_status();
    }



    Debug Terminal Output Samples

    1. ConfigOnDoubleReset_TZ using LittleFS on ESP8266_DEV with ESP8266_W5500

    1.1 DRD => Config Portal

    This is terminal debug output when running ConfigOnDoubleReset on ESP8266_W5500. Config Portal was requested by DRD to input and save Credentials, such as Static IP address.

    Starting ConfigOnDoubleReset_TZ using LittleFS on ESP8266_NODEMCU_ESP12E with ESP8266_W5500 Ethernet
    ESP8266_W5500_Manager v1.0.0
    ESP_DoubleResetDetector v1.3.2
    [EM] RFC925 Hostname = ConfigOnDoubleReset_TZ
    [EM] setSTAStaticIPConfig
    [EM] Set CORS Header to :  Your Access-Control-Allow-Origin
    [EM] LoadCfgFile 
    [EM] OK
    [EM] stationIP = 192.168.2.186 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    Got stored Credentials. Timeout 120s for Config Portal
    [EM] Current TZ_Name = America/New_York , TZ =  EST5EDT,M3.2.0,M11.1.0
    [EM] Default SPI pinout:
    [EM] MOSI: 13
    [EM] MISO: 12
    [EM] SCK: 14
    [EM] CS: 16
    [EM] =========================
    Connecting to network : 
    Ethernet Static IP address: 192.168.2.186
    LittleFS Flag read = 0xD0D01234
    doubleResetDetected
    Saving config file...
    Saving config file OK
    Open Config Portal without Timeout: Double Reset Detected
    Starting configuration portal @ 192.168.2.186
    _domainName = *
    _resolvedIP = 192.168.2.186
    _port = 53
    [EM] _configPortalStart millis() = 354
    [EM] Config Portal IP address = 192.168.2.186
    [EM] HTTP server started
    [EM] startConfigPortal : Enter loop
    [EM] handleRoot
    [EM] captivePortal: hostHeader =  192.168.2.186
    [EM] Handle ETH
    [EM] Static IP = 192.168.2.186
    [EM] Sent config page
    [EM] ETH save
    [EM] TZ name = America/New_York
    [EM] New Static IP = 192.168.2.188
    [EM] New Static Gateway = 192.168.2.1
    [EM] New Static Netmask = 255.255.255.0
    [EM] New Static DNS1 = 192.168.2.1
    [EM] New Static DNS2 = 8.8.8.8
    [EM] Sent eth save page
    [EM] stopConfigPortal
    ETH network connected...yeey :)
    [EM] Saving current TZ_Name = America/New_York , TZ =  EST5EDT,M3.2.0,M11.1.0
    [EM] getSTAStaticIPConfig
    [EM] SaveCfgFile 
    [EM] stationIP = 192.168.2.188 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    [EM] OK
    Current IP = 192.168.2.186. Reset to take new IP = 192.168.2.188
    ets Jan  8 2013,rst cause:2, boot mode:(3,6)
    
    1.2. Get new Static IP after reset
    Starting ConfigOnDoubleReset_TZ using LittleFS on ESP8266_NODEMCU_ESP12E with ESP8266_W5500 Ethernet
    ESP8266_W5500_Manager v1.0.0
    ESP_DoubleResetDetector v1.3.2
    [EM] RFC925 Hostname = ConfigOnDoubleReset_TZ
    [EM] setSTAStaticIPConfig
    [EM] Set CORS Header to :  Your Access-Control-Allow-Origin
    [EM] LoadCfgFile 
    [EM] OK
    [EM] stationIP = 192.168.2.188 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    Got stored Credentials. Timeout 120s for Config Portal
    [EM] Current TZ_Name = America/New_York , TZ =  EST5EDT,M3.2.0,M11.1.0
    [EM] Default SPI pinout:
    [EM] MOSI: 13
    [EM] MISO: 12
    [EM] SCK: 14
    [EM] CS: 16
    [EM] =========================
    Connecting to network : 
    Ethernet Static IP address: 192.168.2.188
    LittleFS Flag read = 0xD0D04321
    No doubleResetDetected
    Saving config file...
    Saving config file OK
    After waiting 0.00 secs more in setup(), connection result is connected. Local IP: 192.168.2.188
    [EM] freeing allocated params!
    Stop doubleResetDetecting
    Saving config file...
    Saving config file OK
    Local Date/Time: Sun Dec 11 18:23:34 2022
    Local Date/Time: Sun Dec 11 18:24:34 2022
    Local Date/Time: Sun Dec 11 18:25:34 2022
    Local Date/Time: Sun Dec 11 18:26:34 2022
    Local Date/Time: Sun Dec 11 18:27:34 2022
    Local Date/Time: Sun Dec 11 18:28:34 2022
    

    2. ConfigOnSwichFS using LittleFS on ESP8266_DEV with ESP8266_W5500

    This is terminal debug output when running ConfigOnSwichFS on ESP8266_W5500. Config Portal was requested to input and save Credentials.

    Starting ConfigOnSwichFS using LittleFS on ESP8266_NODEMCU_ESP12E with ESP8266_W5500 Ethernet
    ESP8266_W5500_Manager v1.0.0
    {"thingspeakApiKey":"","SensorDHT22":true,"PinSda":4,"PinScl":5}
    Config file was successfully parsed
    [EM] RFC925 Hostname = ConfigOnSwitchFS
    [EM] setSTAStaticIPConfig
    [EM] Set CORS Header to :  Your Access-Control-Allow-Origin
    [EM] LoadCfgFile 
    [EM] OK
    [EM] stationIP = 192.168.2.186 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    Got stored Credentials. Timeout 120s for Config Portal
    Current Timezone is not set. Enter Config Portal to set.
    [EM] Default SPI pinout:
    [EM] MOSI: 13
    [EM] MISO: 12
    [EM] SCK: 14
    [EM] CS: 16
    [EM] =========================
    Connecting to network : 
    Ethernet Static IP address: 192.168.2.186
    After waiting 0.00 secs more in setup(), connection result is connected. Local IP: 192.168.2.186
    [EM] freeing allocated params!
    
    Configuration portal requested.
    [EM] RFC925 Hostname = ConfigOnSwitchFS
    Opening configuration portal. 
    [EM] LoadCfgFile 
    [EM] OK
    [EM] stationIP = 192.168.2.186 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    Got stored Credentials. Timeout 120s for Config Portal
    [EM] Adding parameter thingspeakApiKey
    [EM] Adding parameter SensorDHT22
    [EM] Adding parameter PinSda
    [EM] Adding parameter PinScl
    [EM] setSTAStaticIPConfig for USE_CONFIGURABLE_DNS
    [EM] Set CORS Header to :  Your Access-Control-Allow-Origin
    [EM] _configPortalStart millis() = 26788
    [EM] Config Portal IP address = 192.168.2.186
    [EM] HTTP server started
    [EM] startConfigPortal : Enter loop
    [EM] handleRoot
    [EM] request host IP = 192.168.2.186
    [EM] Info
    [EM] Info page sent
    [EM] handleRoot
    [EM] request host IP = 192.168.2.186
    [EM] Handle ETH
    [EM] Static IP = 192.168.2.186
    [EM] Sent config page
    [EM] ETH save
    [EM] TZ = America/Toronto
    [EM] Parameter and value : thingspeakApiKey 
    [EM] Parameter and value : SensorDHT22 T
    [EM] Parameter and value : PinSda 4
    [EM] Parameter and value : PinScl 5
    [EM] New Static IP = 192.168.2.186
    [EM] New Static Gateway = 192.168.2.1
    [EM] New Static Netmask = 255.255.255.0
    [EM] New Static DNS1 = 192.168.2.1
    [EM] New Static DNS2 = 8.8.8.8
    [EM] Sent eth save page
    [EM] stopConfigPortal
    ETH network connected...yeey :)
    Local IP: 192.168.2.186
    [EM] Saving current TZ_Name = America/Toronto , TZ =  EST5EDT,M3.2.0,M11.1.0
    [EM] getSTAStaticIPConfig
    [EM] SaveCfgFile 
    [EM] stationIP = 192.168.2.186 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    [EM] OK
    Saving config file
    {
      "thingspeakApiKey": "",
      "SensorDHT22": true,
      "PinSda": 4,
      "PinScl": 5
    }
    Config file was successfully saved
    Current IP = 192.168.2.186. Reset to take new IP = 192.168.2.186
    ets Jan  8 2013,rst cause:2, boot mode:(3,6)
    
    ...
    
    Starting ConfigOnSwichFS using LittleFS on ESP8266_NODEMCU_ESP12E with ESP8266_W5500 Ethernet
    ESP8266_W5500_Manager v1.0.0
    {"thingspeakApiKey":"","SensorDHT22":true,"PinSda":4,"PinScl":5}
    Config file was successfully parsed
    [EM] RFC925 Hostname = ConfigOnSwitchFS
    [EM] setSTAStaticIPConfig
    [EM] Set CORS Header to :  Your Access-Control-Allow-Origin
    [EM] LoadCfgFile 
    [EM] OK
    [EM] stationIP = 192.168.2.186 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    Got stored Credentials. Timeout 120s for Config Portal
    [EM] Current TZ_Name = America/Toronto , TZ =  EST5EDT,M3.2.0,M11.1.0
    [EM] Default SPI pinout:
    [EM] MOSI: 13
    [EM] MISO: 12
    [EM] SCK: 14
    [EM] CS: 16
    [EM] =========================
    Connecting to network : 
    Ethernet Static IP address: 192.168.2.186
    After waiting 0.00 secs more in setup(), connection result is connected. Local IP: 192.168.2.186
    [EM] freeing allocated params!
    Local Date/Time: Fri Dec  9 23:05:29 2022
    Local Date/Time: Fri Dec  9 23:06:29 2022
    

    3. ESP8266_FSWebServer_DRD using LittleFS on ESP8266_DEV with ESP8266_W5500

    This is terminal debug output when running ESP8266_FSWebServer_DRD on ESP8266_W5500.

    The terminal debug output demonstrate the upload the contents of the data folder to ESP8266 LittleFS using the following command

    for file in `\ls -A1`; do curl -F "file=@$PWD/$file" 192.168.2.186/edit; done
    
    Starting ESP_FSWebServer_DRD using LittleFS on ESP8266_NODEMCU_ESP12E with ESP8266_W5500 Ethernet
    ESP8266_W5500_Manager v1.0.0
    ESP_DoubleResetDetector v1.3.2
    Opening / directory
    FS File: PM2_5_Log.csv, size: 40B
    FS File: drd.dat, size: 4B
    FS File: eth_cred.dat, size: 142B
    FS File: timezone.dat, size: 32B
    
    [EM] RFC925 Hostname = ESP-FSWebServerDRD
    [EM] setSTAStaticIPConfig
    [EM] Set CORS Header to :  Your Access-Control-Allow-Origin
    [EM] LoadCfgFile 
    [EM] OK
    [EM] stationIP = 192.168.2.186 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    [EM] Default SPI pinout:
    [EM] MOSI: 13
    [EM] MISO: 12
    [EM] SCK: 14
    [EM] CS: 16
    [EM] =========================
    Connecting to network : 
    Ethernet Static IP address: 192.168.2.186
    LittleFS Flag read = 0xD0D04321
    No doubleResetDetected
    Saving config file...
    Saving config file OK
    After waiting 0.00 secs more in setup(), connection result is connected. Local IP: 192.168.2.186
    HTTP server started @ 192.168.2.186
    ===============================================================
    Open http://192.168.2.186/edit.htm to see the file browser
    Using username = admin and password = admin
    ===============================================================
    [EM] freeing allocated params!
    Stop doubleResetDetecting
    Saving config file...
    Saving config file OK
    handleFileRead: /
    handleFileRead: /edit.htm
    handleFileRead: /edit.htm
    handleFileRead: /
    handleFileUpload Name: /CanadaFlag_1.png
    handleFileUpload Size: 41214
    handleFileUpload Name: /CanadaFlag_2.png
    handleFileUpload Size: 8311
    handleFileUpload Name: /CanadaFlag_3.jpg
    handleFileUpload Size: 11156
    handleFileUpload Name: /edit.htm.gz
    handleFileUpload Size: 4116
    handleFileUpload Name: /favicon.ico
    handleFileUpload Size: 1150
    handleFileUpload Name: /graphs.js.gz
    handleFileUpload Size: 1971
    handleFileUpload Name: /index.htm
    handleFileUpload Size: 3721
    handleFileRead: /
    handleFileRead: /graphs.js
    handleFileRead: /edit.htm
    handleFileRead: /index.htm
    handleFileList: /
    handleFileRead: /CanadaFlag_1.png

    Then after restart, the uploaded files are shown in / directory

    Starting ESP_FSWebServer_DRD using LittleFS on ESP8266_NODEMCU_ESP12E with ESP8266_W5500 Ethernet
    ESP8266_W5500_Manager v1.0.0
    ESP_DoubleResetDetector v1.3.2
    Opening / directory
    FS File: CanadaFlag_1.png, size: 40.25KB
    FS File: CanadaFlag_2.png, size: 8.12KB
    FS File: CanadaFlag_3.jpg, size: 10.89KB
    FS File: PM2_5_Log.csv, size: 40B
    FS File: drd.dat, size: 4B
    FS File: edit.htm.gz, size: 4.02KB
    FS File: eth_cred.dat, size: 142B
    FS File: favicon.ico, size: 1.12KB
    FS File: graphs.js.gz, size: 1.92KB
    FS File: index.htm, size: 3.63KB
    FS File: timezone.dat, size: 32B
    
    [EM] RFC925 Hostname = ESP-FSWebServerDRD
    [EM] setSTAStaticIPConfig
    [EM] Set CORS Header to :  Your Access-Control-Allow-Origin
    [EM] LoadCfgFile 
    [EM] OK
    [EM] stationIP = 192.168.2.186 , gatewayIP = 192.168.2.1
    [EM] netMask = 255.255.255.0
    [EM] dns1IP = 192.168.2.1 , dns2IP = 8.8.8.8
    [EM] Default SPI pinout:
    [EM] MOSI: 13
    [EM] MISO: 12
    [EM] SCK: 14
    [EM] CS: 16
    [EM] =========================
    Connecting to network : 
    Ethernet Static IP address: 192.168.2.186
    LittleFS Flag read = 0xD0D04321
    No doubleResetDetected
    Saving config file...
    Saving config file OK
    After waiting 0.00 secs more in setup(), connection result is connected. Local IP: 192.168.2.186
    HTTP server started @ 192.168.2.186
    ===============================================================
    Open http://192.168.2.186/edit.htm to see the file browser
    Using username = admin and password = admin
    ===============================================================
    [EM] freeing allocated params!
    


    Debug

    Debug is enabled by default on Serial. To disable, add before startConfigPortal()

    ESP8266_W5500_manager.setDebugOutput(false);

    You can also change the debugging level from 0 to 4

    // Use from 0 to 4. Higher number, more debugging messages and memory usage.
    #define _ESP8266_ETH_MGR_LOGLEVEL_    3

    Troubleshooting

    If you get compilation errors, more often than not, you may need to install a newer version of the ESP8266 core for Arduino.

    Sometimes, the library will only work if you update the ESP8266 core to the latest version because I am using some newly added function.

    If you connect to the created configuration Access Point but the ConfigPortal does not show up, just open a browser and type in the IP of the web portal, by default 192.168.2.186.


    Issues

    Submit issues to: ESP8266_W5500_Manager issues



    Contributions and Thanks

    1. Based on and modified from Tzapu, KenTaylor’s version, and Khoi Hoang's ESP_WiFiManager.
    Tzapu
    ⭐️ Tzapu

    kentaylor
    ⭐️ Ken Taylor


    Contributing

    If you want to contribute to this project:

    • Report bugs and errors
    • Ask for enhancements
    • Create issues and pull requests
    • Tell other people about this library

    License and credits

    • The library is licensed under MIT

    Copyright

    Copyright 2022- Khoi Hoang

    Visit original content creator repository https://github.com/khoih-prog/ESP8266_W5500_Manager
  • hazel

    Hazel 🌳

    This proof of concept (POC) investigates how Datomic principles can be adapted for the frontend environment.
    It introduces a peer library, written in JavaScript, that is capable of navigating a DataScript database and storing its segments in the browser’s cache.

    This approach is useful for productivity tools like Asana, Jira, Slack, and Notion,
    especially for applications that work with relatively large databases in the browser.
    It is necessary to make fast queries on that data without access to the backend.

    The project is built upon the React TodoMVC framework.

    The project’s capabilities include:

    • Range queries.
    • Lazy iteration over databases by fetching database segments on-demand from the backend.
    • Long-term storage of these segments within the browser cache.
    • Getting a consistent snapshot of a database.

    For those unfamiliar with Datomic-like databases, here’s an illustrative example of its core concept:

    Consider a traditional database such as PostgreSQL or MySQL,
    where data resides on the server’s disk and your application’s queries are processed on the server.
    If you decide to cache a slow query’s result within your application,
    you essentially forego the query engine, reducing it to key-value (KV) storage.
    In contrast, a Datomic-like system enables you to execute queries within your application utilizing its cache.

    More info you can find in Datomic Introduction.

    How does it work?

    Hazel is designed to read indexes built by DataScript.
    However, unlike DataScript, it provides an asynchronous API for data querying and loads storage segments on-demand.

    You should first be familiar with the DataScript or Datomic data model. If not, please refer to the following resources:

    Datoms and Indexes

    The DataScript database operates on datoms, which are atomic units of data. Each Datom is represented as a tuple (E, A, V), where:

    • E stands for the entity ID,
    • A for the attribute, and
    • V for the value.

    These elements form the core structure of a Datom. To efficiently organize datoms, DataScript uses three indexes: EAV, AEV, and AVE. The name of each index reflects the order in which datoms are sorted:

    • EAV: Sorted by entity ID, then attribute, then value.
    • AEV and AVE: Follow analogous patterns.

    While datoms in Datomic and DataScript also include an additional element T (Transaction ID), Hazel simplifies the model by excluding this component.

    Index Implementation

    The indexes in DataScript are implemented as Persistent Sorted Sets, a type of immutable data structure based on B+ trees. These structures are optimized for storing elements in sorted order and enable efficient operations such as lookups, insertions, and deletions, with a time complexity of $$O(\log n)$$. Functional immutability is achieved through structural sharing, ensuring that updates reuse existing data whenever possible. A detailed explanation of B-trees, including their variation B+ trees, can be found in the paper “The Ubiquitous B-Tree” by Douglas Comer.

    Each node of the tree corresponds to a storage segment, serialized and stored persistently. Branch nodes contain keys and addresses for navigation, while leaf nodes store ordered sequences of keys (datoms).

    Database Implementation

    In DataScript, changes are made using transactions, which are represented as structured data. While a comprehensive understanding of the entire transaction process is not required, it’s important to note that transactions are represented as a collections of datoms. Each Datom in transaction includes a flag that indicates whether it will be added to or removed from the database.

    Since persistent data structures can lead to high overhead when updating the entire tree for every transaction, DataScript employs an optimization mechanism that relies on an append-only “tail” for managing updates:

    1. Changes are stored in the “tail”.
    2. Once the size of the tail becomes comparable to a tree node, the “tail” is “flushed” into the tree.
      For implementation details, see the source code.

    Hazel’s Peer

    In Datomic and DataScript, separate APIs are used for querying and mutating data. The Peer library is responsible for querying data. Moreover, it executes queries using a local cache.

    Ultimately, Datomic and DataScript provide low-level API for querying data:

    Hazel implements a similar low-level API.

    First, let’s consider the Datomic and DataScript implementations for the JVM.
    When querying data, they access storage segments stored remotely or in a local cache.
    This access uses blocking I/O, and the result of the queries is a lazy sequence.

    Here are the advantages of this approach:

    1. It allows processing data that exceeds the size of RAM.
    2. It allows stopping lazy sequence consumption, which prevents further loading of the next segments.

    Second, let’s examine the DataScript implementation for ClojureScript (JS).
    It shares the same codebase as the JVM implementation and, as a result, has the same API. However, in JavaScript, blocking I/O cannot be used for retrieving segments, unlike in the JVM. This limitation means that DataScript in JavaScript can operate only with data stored in RAM.

    Hazel is designed to overcome this limitation.
    In JavaScript, the equivalent of lazy sequences is a Generator function (function*/yield). However, since segments are requested asynchronously over the network, Hazel uses AsyncGenerator to manage this process.

    Here are some examples:

    A range query:

    for async (const [e, _a, _v] of db.ave.datoms('task/completed', true)) {
      // Retrieve datoms with the attribute `task/completed` and value `true`.
      // ...
    }

    Retrieving all atriibutes of an Entity:

    const todo = {
      id: e,
    }
    for async (const [_e, a, v] of db.eav.datoms(e)) {
      todo[a] = v;
    }

    NOTE: The index name matters. In the first example, the AVE index is used, while in the second example, the EAV index is used.

    Finally, the Cache API is used to cache segments.
    For more details, see the documentation here.

    Learning by Example

    A motivated reader can easily grasp these concepts by reviewing the provided tests, which offer clear examples and practical insights. Additional details on persistent B+ trees and storage mechanisms in DataScript can be found in the following references:

    Limitations

    I’ve only implemented the methods (r)seek and datoms over indexes.

    The main drawback I’ve found is access control.
    For instance, consider an Asana project; it has tasks and collaborators, and each collaborator can read all tasks of the project.
    I think we should store data per project database.
    However, the problem is that we cannot just add an external collaborator to a task of this project
    because our database is cached in the browser, and this collaborator will have access to all the data in the database.

    I have two solutions for this problem:

    • Having an additional API for data access that runs queries on the backend, where we can easily handle access control, although we would lose caching.
    • Spreading (replicate) copies of a task across many databases (projects).

    If you have any thoughts, feel free to open an issue in this repository.

    Future work

    It is possible to implement datalog
    and other Datomic/DataScript APIs.

    It may be possible to adopt this approach for the local-first paradigm.
    For example, we can implement Conflict-free Replicated Data Types (CRDT)
    by writing database functions in JavaScript and optimistically transacting them on the frontend side.

    How to run

    Dev

    bun run test
    

    Bun builder is currently in beta and lacks some features:

    • When using build.js for configuration, Bun reloads only the build.js file, not the other project files.
    • Automatic generation of manifest.json is not yet implemented.

    Visit original content creator repository
    https://github.com/darkleaf/hazel

  • ITT

    Intermediate Triple Table

    DOI

    ITT is a virtual knowledge graph system implementing the Intermediate Triple Table architecture. It allows to access heterogeneous data as a knowledge graph using the R2RML and RML mapping languages.

    Installation ⚙️

    To install ITT execute the following:

    pip install git+https://github.com/arenas-guerrero-julian/itt.git

    Execution 🚀

    You can run ITT via command line:

    python3 -m itt path/to/config.ini path/to/query.rq

    The query result set is written to itt_result.csv.

    The configuration file is similar to that of Morph-KGC:

    [DataSource1]
    mappings: /path/to/mapping/mapping_file.rml.ttl
    db_url: mysql://username:password@host:port/database

    ITT uses ConnectorX to access relational databases and the connection URLs must be formatted according to this engine. For Postgres the format is postgresql://username:password@host:port/database and for MySQL the format is mysql://username:password@host:port/database. See the details here.

    For MongoDB the connection URL format is mongodb://localhost:27017/database. Example config file for MongoDB:

    [DataSource1]
    mappings: /path/to/mapping/mapping_file.rml.ttl
    mongo_url: mongodb://localhost:27017/database

    License 🔓

    ITT is available under the Apache License 2.0.

    Author & Contact 📬

    Universidad Politécnica de Madrid.

    Citing 💬

    If you used ITT in your work, please cite the Knowledge-Based Systems paper:

    @article{arenas2025itt,
      title = {Intermediate triple table: A general architecture for virtual knowledge graphs},
      author = {Julián Arenas-Guerrero and Oscar Corcho and María S. Pérez},
      journal = {Knowledge-Based Systems},
      volume = {314},
      pages = {113179},
      year = {2025},
      publisher = {Elsevier},
      issn = {0950-7051},
      doi = {10.1016/j.knosys.2025.113179},
    }
    Visit original content creator repository https://github.com/arenas-guerrero-julian/ITT
  • DigitalSteering

    Open an Issue · Download

    DigitalSteering

    What is DigitalSteering?

    At its core, DigitalSteering employs sophisticated algorithms to interpret user inputs and translate them into virtual steering commands. The program mimics the functionality of a physical steering wheel, allowing users to navigate and control in-game vehicles with precision.

    Why DigitalSteering?

    One of the primary goals of DigitalSteering is to break down barriers in gaming accessibility. By offering diverse and customizable control methods, it caters to a broader audience, including individuals with different physical abilities and disabilities.

    DigitalSteering takes a step further by addressing the specific needs of individuals with limited mobility, particularly those without the use of arms or hands. The project incorporates adaptive technologies to empower gamers facing physical challenges, providing them with a means to enjoy gaming on equal terms.

    image-2023-12-30-111624263

    Guide

    We have a vscode and pycharm guide. Please ask if you need help for a different application

    For offsetting the degrees please insert the integer to the range of 0 (Normal) to 365 (Full Offset). Please change accordingly

    Sensitivity is for changing how many degrees to steer. The less it is, more steering is required to turn. The more it is, less steering is required to turn. Please change accordingly

    Download the Code

    Download the provided code and un-zip the file.

    Guide For Pycharm

    Please note that this assumes you already have Python installed on your system.

    ONLY tested 3.10 Python so it might not work in newer and older versions

    Install Required Packages

    First, you need to install the necessary Python packages. Open the integrated command prompt in Pycharm: image-2023-12-30-111932555

    Copy and paste the following into the terminal:

    pip install opencv-python

    pip install mediapipe

    pip install ctypes

    Run the Code

    Go into pycharm and click on the three bars on top left. Click open… and select the un-zipped file. Run steering.py by doing F10 + Shift or on the top right of the screen

    Exit the Application

    To exit the application, press the ‘q’ key while the application window is active.

    Make sure your system has a webcam or an external camera connected. The script uses the default camera (index 0). If you have multiple cameras or a different camera index, you may need to modify the code by changing the number from (0)

    Feel free to ask if you have any questions or encounter any issues!

    Common Errors

    DO NOT be tabbed into the window with the camera output. Please click a different window to use it if you want to see the steering wheel. Else it shouldn’t matter.

    If the tracker is not tracking your hands consider to close your hands while using. You can also lower the confidence bar on Line 65 and 66 (decimal = % for example 0.3 = %30 if you didn’t know)

    Same goes for the tracker falsely identifying a hand. You may raise the confidence bar on 65 and 66 (decimal = % for example 0.3 = %30 if you didn’t know)

    Steering Digitally 😀

    Guide For Visual Studio Code

    Please note that this assumes you already have Python installed on your system.

    ONLY tested 3.10 Python so it might not work in newer and older versions

    Install Required Packages

    First, you need to install the necessary Python packages. To open the integrated command prompt in VScode go to the top and click on Terminal: image-2023-12-30-112635556

    Copy and paste the following into the terminal:

    pip install opencv-python

    pip install mediapipe

    pip install ctypes

    Run the Code

    Go into VScode and click on the File button on the top left. Click Open Folder and select the un-zipped file. After opening the file click on Steering.py. You should be able to run it from there

    Exit the Application

    To exit the application, press the ‘q’ key while the application window is active.

    Make sure your system has a webcam or an external camera connected. The script uses the default camera (index 0). If you have multiple cameras or a different camera index, you may need to modify the code by changing the number from (0) (Line 61)

    Feel free to ask if you have any questions or encounter any issues!

    Common Errors

    MAKE SURE you have your python interpreter to the one you installed your packages in.

    DO NOT be tabbed into the window with the camera output. Please click a different window to use it if you want to see the steering wheel. Else it shouldn’t matter.

    If the tracker is not tracking your hands consider to close your hands while using. You can also lower the confidence bar on Line 65 and 66 (decimal = % for example 0.3 = %30 if you didn’t know)

    Same goes for the tracker falsely identifying a hand. You may raise the confidence bar on 65 and 66 (decimal = % for example 0.3 = %30 if you didn’t know)

    Visit original content creator repository https://github.com/Mrflops/DigitalSteering
  • ebook-for-education

    Youtube KongRuksiam Official Youtube KongRuksiam Tutorial


    📄 “แจกเอกสารประกอบการสอนเพื่อการศึกษา (PDF)”

    Course หัวข้อ ดาวน์โหลด
    image เขียนโปรแกรมภาษา C เบื้องต้น ดาวน์โหลด
    image Docker เบื้องต้น ดาวน์โหลด
    image Git & GitHub ดาวน์โหลด
    image JSON ดาวน์โหลด
    image Flask Framework ดาวน์โหลด
    image OpenCV & Python ดาวน์โหลด
    image PHP เบื้องต้น ดาวน์โหลด
    image Nodejs เบื้องต้น ดาวน์โหลด
    image เขียนโปรแกรมเชิงวัตถุด้วย Java ดาวน์โหลด
    image เขียนโปรแกรมเชิงวัตถุด้วย Python ดาวน์โหลด
    image HTML5 เบื้องต้น ดาวน์โหลด
    image CSS3 เบื้องต้น ดาวน์โหลด
    image JavaScript เบื้องต้น ดาวน์โหลด
    image React 17.x เบื้องต้น ดาวน์โหลด
    image Python เบื้องต้น ดาวน์โหลด
    image Visual Studio Code เบื้องต้น ดาวน์โหลด
    image Prompt Engineering เบื้องต้น ดาวน์โหลด

    Creative Commons licenses | CC BY-NC

    ©︎ ลิขสิทธิ์โดย “ก้องรักสยาม” เจ้าของเพจ KongRuksiam Studio สอนเขียนโปรแกรมในช่องยูทูป KongRuksiam Official , KongRuksiam Tutorial

    image

    • BY : สามารถนำไปใช้ในสื่อการเรียนการสอนได้ โดยระบุแหล่งที่มาของเอกสาร
    • NC : ห้ามนำไปใช้ในเชิงพาณิชย์
    • กรณีนำไปใช้ในงานด้านวิชาการ : ให้ตรวจสอบการสะกดคำและไวยากรณ์ในเอกสารให้ถูกต้องตามหลักราชบัณฑิตยสถาน

    🔥 หลักสูตรฟรียอดนิยมบน YouTube

    • หลักสูตรก้าวแรกสู่การเขียนโปรแกรมภาษา Python เข้าเรียน
    • หลักสูตรการพัฒนาเว็บแอพพลิเคชั่น (Frontend Development) เข้าเรียน
    • หลักสูตรการพัฒนาเกมด้วยโปรแกรม Unity (Game Development) เข้าเรียน
    • หลักสูตรการเขียนโปรแกรมด้วยภาษา Java (Basic & OOP) เข้าเรียน
    • หลักสูตรก้าวแรกสู่ Data Science & Image Processing เข้าเรียน
    • หลักสูตรการเขียนโปรแกรมเชิงวัตถุ (Object Oriented Programming) เข้าเรียน
    • หลักสูตรการพัฒนาเว็บแอพพลิเคชั่นด้วยภาษา PHP & Framework เข้าเรียน

    🎓 สนับสนุนคอร์สเรียนของเราได้ที่ Udemy

    “กดใช้งานคูปอง (Coupon)” เพื่อรับส่วนลดคอร์สเรียนของเราในราคาพิเศษได้ที่ ขั้นตอนการใช้คูปอง

    ลำดับที่ ชื่อคอร์สเรียน ดูรายละเอียด
    1 พัฒนา REST API ด้วย Django REST Framework ดูรายละเอียด
    2 พัฒนาระบบร้านค้าออนไลน์ด้วย Django Framework 4.x (E-Commerce) ดูรายละเอียด
    3 พัฒนาเว็บแอพพลิเคชั่นด้วย React (Real-World Projects) ดูรายละเอียด
    4 พัฒนาเว็บด้วย JavaScript 40 Workshop (Building 40 Projects) ดูรายละเอียด
    5 พัฒนาเว็บด้วย React 15 Workshop (Building 15 Projects) ดูรายละเอียด
    6 สร้างเว็บแอพพลิเคชั่นด้วย JavaScript (Building 20 Projects) ดูรายละเอียด
    7 พัฒนาเว็บด้วย PHP PDO & MySQL (CRUD & Authentication) ดูรายละเอียด
    8 เจาะลึก TypeScript ตั้งแต่เริ่มต้นจนใช้งานจริง ดูรายละเอียด
    9 สร้างแอพพลิเคชั่นด้วยภาษา Python (Real-World Projects) ดูรายละเอียด
    10 สร้าง GUI Application ด้วย Python (Real-World Project) ดูรายละเอียด
    11 พัฒนาเว็บด้วย Django Framework 4.x (Real-World Projects) ดูรายละเอียด
    12 Blockchain & Smart Contract (Solidity) ดูรายละเอียด
    13 สร้างเกม 3D ด้วยโปรแกรม Unity (ตั้งแต่เริ่มต้นจนเล่นได้จริง) ดูรายละเอียด
    14 สร้างเกม 2D ด้วยโปรแกรม Unity (ตั้งแต่เริ่มต้นจนเล่นได้จริง) ดูรายละเอียด
    15 Generative AI & Prompt Engineering (Techniques & Frameworks) ดูรายละเอียด

    🏆 สิทธิพิเศษที่จะได้รับ

    • เรียนได้ตลอดชีพ ไม่จำกัดเวลา
    • ใบรับรองการจบหลักสูตร (Certificate)
    • รับประกันยินดีคืนเงินใน 30 วัน

    ❓ คอร์สที่สอนฟรีใน Youtube กับคอร์สใน Udemy ต่างกันอย่างไร

    • คอร์สเรียนใน Udemy เป็นเนื้อหา Workshop ส่วนคอร์สใน Youtube เป็นเนื้อหาเบื้องต้น
    • ผู้เรียนต้องเรียนเนื้อหาเบื้องต้นใน Youtube ก่อนจึงจะสามารถเข้าเรียนคอร์ส Workshop ใน Udemy ได้

    👨‍💻 ขั้นตอนการใช้คูปองส่วนลด Udemy

    • กดที่เมนู “ใช้คูปอง (Apply Coupon)”
    • คัดลอกรหัสคูปองด้านล่าง 👇
      CP082025
    
    • วางคูปองที่คัดลอกลงในช่อง “ป้อนคูปอง”
    • กดปุ่ม “ปรับใช้ (Apply)”
    • เมื่อคูปองถูกใช้งานจะแจ้งสถานะ “คูปองวิทยากร (Instructor coupon)

    ภาพตัวอย่างการใช้งาน

    *หมายเหตุ : หากคูปองไม่สามารถใช้งานได้ กรุณาติดต่อเราได้ที่ เพจ Facebook

    Visit original content creator repository https://github.com/kongruksiamza/ebook-for-education