├── .circleci
├── assets
│ ├── circleci.ignore.yml
│ ├── configurator_v1.html
│ ├── docs_index_template.html
│ ├── generate_docs.py
│ ├── id_rsa.enc
│ └── mkdocs.default.yml
└── config.yml
├── .editorconfig
├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── banner.png
├── component.mk
├── data
└── homie
│ ├── README.md
│ └── example.config.json
├── docs
├── README.md
├── advanced-usage
│ ├── branding.md
│ ├── broadcast.md
│ ├── built-in-led.md
│ ├── compiler-flags.md
│ ├── custom-settings.md
│ ├── deep-sleep.md
│ ├── events.md
│ ├── input-handlers.md
│ ├── logging.md
│ ├── magic-bytes.md
│ ├── miscellaneous.md
│ ├── range-properties.md
│ ├── resetting.md
│ ├── standalone-mode.md
│ ├── streaming-operator.md
│ └── ui-bundle.md
├── assets
│ ├── github.png
│ ├── led_mqtt.gif
│ ├── led_solid.gif
│ ├── led_wifi.gif
│ ├── logo.png
│ └── youtube.png
├── configuration
│ ├── http-json-api.md
│ └── json-configuration-file.md
├── index.md
├── others
│ ├── community-projects.md
│ ├── cpp-api-reference.md
│ ├── homie-implementation-specifics.md
│ ├── limitations-and-known-issues.md
│ ├── ota-configuration-updates.md
│ ├── troubleshooting.md
│ ├── upgrade-guide-from-v1-to-v2.md
│ └── upgrade-guide-from-v2-to-v3.md
└── quickstart
│ ├── getting-started.md
│ └── what-is-it.md
├── examples
├── Broadcast
│ └── Broadcast.ino
├── CustomSettings
│ └── CustomSettings.ino
├── DoorSensor
│ └── DoorSensor.ino
├── GlobalInputHandler
│ └── GlobalInputHandler.ino
├── HookToEvents
│ └── HookToEvents.ino
├── IteadSonoff
│ └── IteadSonoff.ino
├── IteadSonoffButton
│ └── IteadSonoffButton.ino
├── LedStrip
│ └── LedStrip.ino
├── LightOnOff
│ └── LightOnOff.ino
├── Ping
│ ├── .gitignore
│ ├── README.md
│ ├── lib
│ │ └── README
│ ├── platformio.ini
│ └── src
│ │ ├── PingNode.cpp
│ │ ├── PingNode.hpp
│ │ └── main.cpp
├── SingleButton
│ └── SingleButton.ino
├── SonoffDualShutters
│ └── SonoffDualShutters.ino
├── TemperatureSensor
│ └── TemperatureSensor.ino
└── testAllInputHandlers
│ └── testAllInputHandlers.ino
├── homie-esp8266.cppcheck
├── keywords.txt
├── library.json
├── library.properties
├── mkdocs.yml
├── scripts
├── firmware_parser
│ ├── README.md
│ └── firmware_parser.py
└── ota_updater
│ ├── README.md
│ ├── ota_updater.py
│ └── requirements.txt
└── src
├── Homie.cpp
├── Homie.h
├── Homie.hpp
├── Homie
├── Blinker.cpp
├── Blinker.hpp
├── Boot
│ ├── Boot.cpp
│ ├── Boot.hpp
│ ├── BootConfig.cpp
│ ├── BootConfig.hpp
│ ├── BootNormal.cpp
│ ├── BootNormal.hpp
│ ├── BootStandalone.cpp
│ └── BootStandalone.hpp
├── Config.cpp
├── Config.hpp
├── Constants.hpp
├── Datatypes
│ ├── Callbacks.hpp
│ ├── ConfigStruct.hpp
│ ├── Interface.cpp
│ └── Interface.hpp
├── ExponentialBackoffTimer.cpp
├── ExponentialBackoffTimer.hpp
├── Limits.hpp
├── Logger.cpp
├── Logger.hpp
├── Strings.hpp
├── Timer.cpp
├── Timer.hpp
├── Uptime.cpp
├── Uptime.hpp
└── Utils
│ ├── DeviceId.cpp
│ ├── DeviceId.hpp
│ ├── Helpers.cpp
│ ├── Helpers.hpp
│ ├── ResetHandler.cpp
│ ├── ResetHandler.hpp
│ ├── Validation.cpp
│ └── Validation.hpp
├── HomieBootMode.hpp
├── HomieEvent.hpp
├── HomieNode.cpp
├── HomieNode.hpp
├── HomieRange.hpp
├── HomieSetting.cpp
├── HomieSetting.hpp
├── SendingPromise.cpp
├── SendingPromise.hpp
└── StreamingOperator.hpp
/.circleci/assets/circleci.ignore.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | working_directory: ~/code
5 | docker:
6 | - image: circleci/python:2.7
7 | branches:
8 | ignore:
9 | - gh-pages
10 | steps:
11 | - checkout
12 |
--------------------------------------------------------------------------------
/.circleci/assets/docs_index_template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Homie for ESP8266 docs
5 |
6 |
7 |
8 |
60 |
61 |
62 |
63 |
64 |
Homie for ESP8266 docs
65 |
66 | Configurators
67 |
68 | $configurators_html
69 |
70 | Documentation
71 |
72 | $documentation_html
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/.circleci/assets/generate_docs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import json
5 | import urllib
6 | import urllib.request
7 | import tempfile
8 | import zipfile
9 | import glob
10 | import subprocess
11 | import getopt
12 | import sys
13 | import shutil
14 | import os
15 | import string
16 |
17 | FIRST_RELEASE_ID=3084382
18 | DOCS_PATH = 'docs'
19 | DOCS_BRANCHES = [
20 | { 'tag': 'develop', 'description': 'develop branch (development)', 'path': 'develop' },
21 | { 'tag': 'master', 'description': 'master branch (stable)', 'path': 'stable' }
22 | ]
23 | CONFIGURATORS_PATH = 'configurators'
24 | CONFIGURATORS_VERSIONS = [
25 | { 'title': 'v2', 'description': 'For Homie v2.x.x', 'path': 'v2', 'url': 'https://github.com/homieiot/homie-esp8266-setup/raw/gh-pages/ui_bundle.html' },
26 | { 'title': 'v1', 'description': 'For Homie v1.x.x', 'path': 'v1', 'file': '/configurator_v1.html' }
27 | ]
28 |
29 | current_dir = os.path.dirname(__file__)
30 | output_dir = getopt.getopt(sys.argv[1:], 'o:')[0][0][1]
31 | github_releases = json.load(urllib.request.urlopen('https://api.github.com/repos/homieiot/homie-esp8266/releases'))
32 |
33 | def generate_docs(data):
34 | print('Generating docs for ' + data['tag'] + ' (' + data['description'] + ') at /' + data['path'] + '...')
35 | zip_url = 'https://github.com/homieiot/homie-esp8266/archive/' + data['tag'] + '.zip'
36 | zip_path = tempfile.mkstemp()[1]
37 | urllib.request.urlretrieve(zip_url, zip_path)
38 |
39 | zip_file = zipfile.ZipFile(zip_path, 'r')
40 | unzip_path = tempfile.mkdtemp()
41 | zip_file.extractall(unzip_path)
42 | src_path = glob.glob(unzip_path + '/*')[0]
43 |
44 | # now do it always
45 | # if not os.path.isfile(src_path + '/mkdocs.yml'):
46 | shutil.copy(current_dir + '/mkdocs.default.yml', src_path + '/mkdocs.yml')
47 |
48 | subprocess.call(['mkdocs', 'build'], cwd=src_path)
49 | shutil.copytree(src_path + '/site', output_dir + '/' + DOCS_PATH + '/' + data['path'])
50 | print('Done.')
51 |
52 | def generate_configurators(data):
53 | print('Generating configurator for ' + data['title'] + ' (' + data['description'] + ') at /' + data['path'] + '...')
54 | file_path = None
55 | if 'file' in data:
56 | file_path = current_dir + data['file']
57 | else: # url
58 | file_path = tempfile.mkstemp()[1]
59 | urllib.request.urlretrieve(data['url'], file_path)
60 |
61 | prefix_output = output_dir + '/' + CONFIGURATORS_PATH + '/' + data['path']
62 | try:
63 | os.makedirs(prefix_output)
64 | except:
65 | pass
66 |
67 | shutil.copy(file_path, prefix_output + '/index.html')
68 |
69 | print('Done.')
70 |
71 | shutil.rmtree(output_dir, ignore_errors=True)
72 |
73 | # Generate docs
74 |
75 | generated_docs = []
76 |
77 | # Generate docs for branches
78 |
79 | for branch in DOCS_BRANCHES:
80 | generated_docs.append(branch)
81 | generate_docs(branch)
82 |
83 | # Generate docs for releases
84 |
85 | for release in github_releases:
86 | if (release['id'] < FIRST_RELEASE_ID): continue
87 |
88 | tag_name = release['tag_name']
89 | version = tag_name[1:]
90 | description = 'release ' + version
91 |
92 | data = {
93 | 'tag': tag_name,
94 | 'description': description,
95 | 'path': version
96 | }
97 |
98 | generated_docs.append(data)
99 | generate_docs(data)
100 |
101 | # Generate documentation html
102 |
103 | documentation_html = ''
107 |
108 | # Generate configurators
109 |
110 | generated_configurators = []
111 |
112 | for version in CONFIGURATORS_VERSIONS:
113 | generated_configurators.append(version)
114 | generate_configurators(version)
115 |
116 | # Generate configurators html
117 |
118 | configurators_html = ''
122 |
123 | # Generate index
124 |
125 | docs_index_template_file = open(current_dir + '/docs_index_template.html')
126 | docs_index_template_html = docs_index_template_file.read()
127 | docs_index_template = string.Template(docs_index_template_html)
128 | docs_index = docs_index_template.substitute(documentation_html=documentation_html, configurators_html=configurators_html)
129 |
130 | docs_index_file = open(output_dir + '/index.html', 'w')
131 | docs_index_file.write(docs_index)
132 | docs_index_file.close()
133 |
--------------------------------------------------------------------------------
/.circleci/assets/id_rsa.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homieiot/homie-esp8266/ba452eae5e23d01463568756465be07f8dc3bfce/.circleci/assets/id_rsa.enc
--------------------------------------------------------------------------------
/.circleci/assets/mkdocs.default.yml:
--------------------------------------------------------------------------------
1 | site_name: Homie for ESP8266
2 | site_description: The Homie for ESP8266 documentation.
3 |
4 | repo_name: 'homieiot/homie-esp8266'
5 | repo_url: 'https://github.com/homieiot/homie-esp8266'
6 |
7 | nav:
8 | - Welcome: index.md
9 | - Quickstart:
10 | - What is it?: quickstart/what-is-it.md
11 | - Getting started: quickstart/getting-started.md
12 | - Advanced usage:
13 | - Built-in LED: advanced-usage/built-in-led.md
14 | - Branding: advanced-usage/branding.md
15 | - Events: advanced-usage/events.md
16 | - Logging: advanced-usage/logging.md
17 | - Streaming operator: advanced-usage/streaming-operator.md
18 | - Input handlers: advanced-usage/input-handlers.md
19 | - Broadcast: advanced-usage/broadcast.md
20 | - Custom settings: advanced-usage/custom-settings.md
21 | - Resetting: advanced-usage/resetting.md
22 | - Compiler-Flags: advanced-usage/compiler-flags.md
23 | - Standalone mode: advanced-usage/standalone-mode.md
24 | - Magic bytes: advanced-usage/magic-bytes.md
25 | - Range properties: advanced-usage/range-properties.md
26 | - Deep sleep: advanced-usage/deep-sleep.md
27 | - Miscellaneous: advanced-usage/miscellaneous.md
28 | - UI Bundle: advanced-usage/ui-bundle.md
29 | - Configuration:
30 | - JSON configuration file: configuration/json-configuration-file.md
31 | - HTTP JSON API: configuration/http-json-api.md
32 | - Others:
33 | - OTA/configuration updates: others/ota-configuration-updates.md
34 | - Homie implementation specifics: others/homie-implementation-specifics.md
35 | - Limitations and known issues: others/limitations-and-known-issues.md
36 | - Troubleshooting: others/troubleshooting.md
37 | - C++ API reference: others/cpp-api-reference.md
38 | - Upgrade guide from v1 to v2: others/upgrade-guide-from-v1-to-v2.md
39 | - Upgrade guide from v2 to v3: others/upgrade-guide-from-v2-to-v3.md
40 | - Community projects: others/community-projects.md
41 |
42 | theme:
43 | name: material
44 | palette:
45 | primary: red
46 | accent: red
47 | logo: assets/logo.png
48 | feature:
49 | tabs: true
50 |
51 | extra:
52 | social:
53 | - type: cog
54 | link: http://homieiot.github.io/homie-esp8266/configurators/v2/
55 |
56 | markdown_extensions:
57 | - meta
58 | - footnotes
59 | - codehilite
60 | - admonition
61 | - toc:
62 | permalink: true
63 | - pymdownx.arithmatex
64 | - pymdownx.betterem:
65 | smart_enable: all
66 | - pymdownx.caret
67 | - pymdownx.critic
68 | - pymdownx.details
69 | - pymdownx.emoji:
70 | emoji_generator: !!python/name:pymdownx.emoji.to_svg
71 | - pymdownx.inlinehilite
72 | - pymdownx.magiclink
73 | - pymdownx.mark
74 | - pymdownx.smartsymbols
75 | - pymdownx.superfences
76 | - pymdownx.tasklist:
77 | custom_checkbox: true
78 | - pymdownx.tilde
79 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | working_directory: ~/code
5 | docker:
6 | - image: circleci/python:3.8.1
7 | steps:
8 | - checkout
9 | - run:
10 | name: install PlatformIO
11 | command: sudo pip install -U platformio
12 | - run:
13 | name: install current code as a PlatformIO library with all dependencies
14 | command: platformio lib -g install file://.
15 | - run:
16 | name: install Arduino Core for ESP8266
17 | command: platformio platform install espressif8266
18 | - run:
19 | name: install Arduino Core for ESP32
20 | command: platformio platform install espressif32
21 | - run:
22 | name: install examples dependencies
23 | command: platformio lib -g install Shutters@2.1.1 SonoffDual@1.1.0
24 | - run: platformio ci ./examples/Broadcast --board=esp01_1m --board=nodemcuv2 --board=esp32dev
25 | - run: platformio ci ./examples/CustomSettings --board=esp01_1m --board=nodemcuv2 --board=esp32dev
26 | - run: platformio ci ./examples/DoorSensor --board=esp01_1m --board=nodemcuv2 --board=esp32dev
27 | - run: platformio ci ./examples/GlobalInputHandler --board=esp01_1m --board=nodemcuv2 --board=esp32dev
28 | - run: platformio ci ./examples/HookToEvents --board=esp01_1m --board=nodemcuv2 --board=esp32dev
29 | - run: platformio ci ./examples/IteadSonoff --board=esp01_1m --board=nodemcuv2 --board=esp32dev
30 | - run: platformio ci ./examples/IteadSonoffButton --board=esp01_1m --board=nodemcuv2 --board=esp32dev
31 | - run: platformio ci ./examples/LedStrip --board=esp01_1m --board=nodemcuv2 --board=esp32dev
32 | - run: platformio ci ./examples/LightOnOff --board=esp01_1m --board=nodemcuv2 --board=esp32dev
33 | - run: platformio ci ./examples/Ping --board=esp01_1m --board=nodemcuv2 --board=esp32dev
34 | - run: platformio ci ./examples/SingleButton --board=esp01_1m --board=nodemcuv2 --board=esp32dev
35 | - run: platformio ci ./examples/SonoffDualShutters --board=esp01_1m --board=nodemcuv2 --board=esp32dev
36 | - run: platformio ci ./examples/TemperatureSensor --board=esp01_1m --board=nodemcuv2 --board=esp32dev
37 |
38 | lint:
39 | working_directory: ~/code
40 | docker:
41 | - image: circleci/python:3.8.1
42 | steps:
43 | - checkout
44 | - run:
45 | name: install cpplint
46 | command: sudo pip install cpplint
47 | - run: make cpplint
48 |
49 | generate_docs:
50 | working_directory: ~/code
51 | docker:
52 | - image: circleci/python:3.8.1
53 | steps:
54 | - checkout
55 | - run:
56 | name: install dependencies
57 | command: sudo pip install mkdocs==1.0.4 mkdocs-material==4.6.0 pygments==2.5.2 pymdown-extensions==6.2.1 markdown==3.1.1
58 | - run:
59 | name: generate and publish docs
60 | command: |
61 | if [ -z ${PRIVATE_KEY_ENCRYPT_KEY+x} ]
62 | then
63 | echo "Fork detected. Ignoring..."
64 | exit 0
65 | fi
66 |
67 | openssl aes-256-cbc -d -md sha256 -in ./.circleci/assets/id_rsa.enc -k "${PRIVATE_KEY_ENCRYPT_KEY}" >> /tmp/deploy_rsa
68 | eval "$(ssh-agent -s)"
69 | chmod 600 /tmp/deploy_rsa
70 | ssh-add /tmp/deploy_rsa
71 |
72 | chmod +x ./.circleci/assets/generate_docs.py
73 | ./.circleci/assets/generate_docs.py -o /tmp/site
74 |
75 | # make sure we ignore the gh-pages branch
76 | mkdir /tmp/site/.circleci
77 | cp ./.circleci/assets/circleci.ignore.yml /tmp/site/.circleci/config.yml
78 |
79 | pushd /tmp/site
80 | git init
81 | git config --global user.name "circleci"
82 | git config --global user.email "sayhi@circleci.com"
83 | git remote add origin git@github.com:homieiot/homie-esp8266.git
84 | git add .
85 | git commit -m ":package: Result of CircleCI build ${CIRCLE_BUILD_URL}"
86 | git push -f origin master:gh-pages
87 | popd
88 |
89 | workflows:
90 | version: 2
91 | lint_build_generatedocs:
92 | jobs:
93 | - lint:
94 | filters:
95 | branches:
96 | ignore:
97 | - gh-pages
98 | - build:
99 | filters:
100 | branches:
101 | ignore:
102 | - gh-pages
103 | - generate_docs:
104 | filters:
105 | branches:
106 | ignore:
107 | - gh-pages
108 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
11 | [keywords.txt]
12 | indent_style = tab
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | The issue tracker is a great place to ask for enhancements and to report bugs.
2 | If you have some questions or if you need help, some people might help you on the [Gitter room](https://gitter.im/homie-iot/ESP8266).
3 |
4 | Before submitting your issue, make sure:
5 |
6 | - [ ] You've read the documentation for *your* release (in the `docs/` folder or at https://homieiot.github.io/homie-esp8266/) which contains some answsers to the most common problems (notably the `Limitations and know issues` and `Troubleshooting` pages)
7 | - [ ] You're using the examples bundled in *your* release, which are in the `examples/` folder of the `.zip` of the release you're using. Examples might not be backward-compatible
8 |
9 | Thanks!
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Output of mkdocs
2 | /site/
3 |
4 | /config.json
5 | *.filters
6 | *.vcxitems
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Marvin Roger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | cpplint:
2 | cpplint --repository=. --recursive --filter=-whitespace/line_length,-legal/copyright,-runtime/printf,-build/include,-build/namespace,-runtime/int,-whitespace/comments,-runtime/threadsafe_fn ./src
3 | .PHONY: cpplint
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://circleci.com/gh/homieiot/homie-esp8266) [](https://github.com/homieiot/homie-esp8266/releases) [](https://gitter.im/homie-iot/ESP8266) [](https://platformio.org/lib/show/555/Homie)
2 |
3 | # Homie for ESP8266 / ESP32
4 |
5 | 
6 |
7 | An Arduino for ESP8266 / ESP32 implementation of [Homie](https://github.com/homieiot/convention), an MQTT convention for the IoT.
8 |
9 | This branch of Homie for ESP8266 implements [Homie 3.0.1](https://github.com/homieiot/convention/releases/tag/v3.0.1) and adds support for ESP32.
10 |
11 | [](https://homieiot.github.io/)
12 |
13 | ## Download
14 |
15 | The Git repository contains the development version of Homie for ESP8266.
16 | Stable releases are available [on the releases page](https://github.com/homieiot/homie-esp8266/releases).
17 |
18 |
19 | ## Using with PlatformIO
20 |
21 | [PlatformIO](http://platformio.org) is an open source ecosystem for IoT development with cross platform build system, library manager and full support for Espressif ESP8266 development. It works on the popular host OS: Mac OS X, Windows, Linux 32/64, Linux ARM (like Raspberry Pi, BeagleBone, CubieBoard).
22 |
23 | 1. Install [PlatformIO IDE](http://platformio.org/platformio-ide)
24 | 2. Create new project using "PlatformIO Home > New Project"
25 | 3. Open [Project Configuration File `platformio.ini`](http://docs.platformio.org/page/projectconf.html)
26 |
27 | ### Stable version
28 |
29 | 4. Add "Homie" to project using `platformio.ini` and [lib_deps](http://docs.platformio.org/page/projectconf/section_env_library.html#lib-deps) option:
30 | ```ini
31 | [env:myboard]
32 | platform = espressif8266
33 | board = ...
34 | framework = arduino
35 | build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
36 | lib_deps = Homie
37 | ```
38 |
39 | Add the `PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY` build flag to ensure reliable OTA updates.
40 |
41 | ### Development version
42 |
43 | 4. Update dev/platform to staging version:
44 | - [Instruction for Espressif 8266](http://docs.platformio.org/en/latest/platforms/espressif8266.html#using-arduino-framework-with-staging-version)
45 |
46 | 5. Before editing platformio.ini as shown below, you must install "git" if you don't already have it. For Windows, just go to http://git-scm.com/download/win and the download will start automatically. Note, this is only a requirement for the development versions.
47 |
48 | 6. Add development version of "Homie" to project using `platformio.ini` and [lib_deps](http://docs.platformio.org/page/projectconf/section_env_library.html#lib-deps) option:
49 | ```ini
50 | [env:myboard]
51 | platform = ...
52 | board = ...
53 | framework = arduino
54 | build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
55 |
56 | ; the latest development branch (convention V3.0.x)
57 | lib_deps = https://github.com/homieiot/homie-esp8266.git#develop
58 |
59 | ```
60 |
61 | -----
62 | Happy coding with PlatformIO!
63 |
64 | ## Features
65 |
66 | * Automatic connection/reconnection to Wi-Fi/MQTT
67 | * [JSON configuration file](https://homieiot.github.io/homie-esp8266/docs/stable/configuration/json-configuration-file) to configure the device
68 | * [Cute HTTP API / Web UI / App](https://homieiot.github.io/homie-esp8266/docs/stable/configuration/http-json-api) to remotely send the configuration to the device and get information about it
69 | * [Custom settings](https://homieiot.github.io/homie-esp8266/docs/stable/advanced-usage/custom-settings)
70 | * [OTA over MQTT](https://homieiot.github.io/homie-esp8266/docs/stable/others/ota-configuration-updates)
71 | * [Magic bytes](https://homieiot.github.io/homie-esp8266/docs/stable/advanced-usage/magic-bytes)
72 | * Available in the [PlatformIO registry](http://platformio.org/#!/lib/show/555/Homie)
73 | * Pretty [straightforward sketches](./examples), a simple light for example: (**TODO**: adapt to V3)
74 |
75 | ```c++
76 | #include
77 |
78 | const int PIN_RELAY = 5;
79 |
80 | HomieNode lightNode("light", "Light", "switch");
81 |
82 | bool lightOnHandler(const HomieRange& range, const String& value) {
83 | if (value != "true" && value != "false") return false;
84 |
85 | bool on = (value == "true");
86 | digitalWrite(PIN_RELAY, on ? HIGH : LOW);
87 | lightNode.setProperty("on").send(value);
88 | Homie.getLogger() << "Light is " << (on ? "on" : "off") << endl;
89 |
90 | return true;
91 | }
92 |
93 | void setup() {
94 | Serial.begin(115200);
95 | Serial << endl << endl;
96 | pinMode(PIN_RELAY, OUTPUT);
97 | digitalWrite(PIN_RELAY, LOW);
98 |
99 | Homie_setFirmware("awesome-relay", "1.0.0");
100 |
101 | lightNode.advertise("on", "On", "boolean").settable(lightOnHandler);
102 |
103 | Homie.setup();
104 | }
105 |
106 | void loop() {
107 | Homie.loop();
108 | }
109 | ```
110 |
111 | ## Requirements, installation and usage
112 |
113 | The project is documented on https://homieiot.github.io/homie-esp8266/ with a *Getting started* guide and every piece of information you will need.
114 |
115 | ## Donate
116 |
117 | I am a student and maintaining Homie for ESP8266 takes time. **I am not in need and I will continue to maintain this project as much as I can even without donations**. Consider this as a way to tip the project if you like it. :wink:
118 |
119 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JSGTYJPMNRC74)
120 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homieiot/homie-esp8266/ba452eae5e23d01463568756465be07f8dc3bfce/banner.png
--------------------------------------------------------------------------------
/component.mk:
--------------------------------------------------------------------------------
1 | COMPONENT_SRCDIRS := src src/Homie src/Homie/Boot src/Homie/Datatypes src/Homie/Utils
2 | COMPONENT_ADD_INCLUDEDIRS := src src/Homie src/Homie/Boot src/Homie/Datatypes src/Homie/Utils
3 |
--------------------------------------------------------------------------------
/data/homie/README.md:
--------------------------------------------------------------------------------
1 | `data/homie` folder
2 | ===================
3 |
4 | This folder contains the data you can upload to the SPIFFS of your ESP8266.
5 | This is optional.
6 |
7 | To upload files to the SPIFFS of your device, first create a folder named `data` in your sketch directory. In this `data` folder, create an `homie` directory. You can put two files in it:
8 |
9 | 1. The `config.json` file, if you want to bypass the `configuration` mode.
10 | 2. The `ui_bundle.gz` file, that you can download [here](http://setup.homie-esp8266.marvinroger.fr/ui_bundle.gz). If present, the configuration UI will be served directly from the ESP8266.
11 |
12 | Finally initiate the [SPIFFS upload process](http://docs.platformio.org/en/stable/platforms/espressif8266.html?highlight=spiffs#uploading-files-to-file-system-spiffs) via PlatformIO, or via the [Arduino IDE](http://esp8266.github.io/Arduino/versions/2.3.0/doc/filesystem.html#uploading-files-to-file-system)
13 |
--------------------------------------------------------------------------------
/data/homie/example.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Kitchen light",
3 | "wifi": {
4 | "ssid": "Network_1",
5 | "password": "I'm a Wi-Fi password!"
6 | },
7 | "mqtt": {
8 | "host": "192.168.1.20",
9 | "port": 1883
10 | },
11 | "ota": {
12 | "enabled": false
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | Docs
2 | ====
3 |
4 | Docs are available:
5 |
6 | * Locally at [index.md](index.md)
7 | * Online at https://homieiot.github.io/homie-esp8266
8 |
--------------------------------------------------------------------------------
/docs/advanced-usage/branding.md:
--------------------------------------------------------------------------------
1 | By default, Homie for ESP8266 will spawn an `Homie-xxxxxxxxxxxx` AP and will connect to the MQTT broker with the `Homie-xxxxxxxxxxxx` client ID. You might want to change the `Homie` text:
2 |
3 | ```c++
4 | void setup() {
5 | Homie_setBrand("MyIoTSystem"); // before Homie.setup()
6 | // ...
7 | }
8 | ```
9 |
--------------------------------------------------------------------------------
/docs/advanced-usage/broadcast.md:
--------------------------------------------------------------------------------
1 | Your device can react to Homie broadcasts. To do that, you can use a broadcast handler:
2 |
3 | ```c++
4 | bool broadcastHandler(const String& level, const String& value) {
5 | Serial << "Received broadcast level " << level << ": " << value << endl;
6 | return true;
7 | }
8 |
9 | void setup() {
10 | Homie.setBroadcastHandler(broadcastHandler); // before Homie.setup()
11 | // ...
12 | }
13 | ```
14 |
--------------------------------------------------------------------------------
/docs/advanced-usage/built-in-led.md:
--------------------------------------------------------------------------------
1 | By default, Homie for ESP8266 will blink the built-in LED to indicate its status. Note it does not indicate activity, only the status of the device (in `configuration` mode, connecting to Wi-Fi or connecting to MQTT), see [Getting started](../quickstart/getting-started.md) for more information.
2 |
3 | However, on some boards like the ESP-01, the built-in LED is actually the TX port, so it is fine if Serial is not enabled, but if you enable Serial, this is a problem. You can easily disable the built-in LED blinking.
4 |
5 | ```c++
6 | void setup() {
7 | Homie.disableLedFeedback(); // before Homie.setup()
8 | // ...
9 | }
10 | ```
11 |
12 | You may, instead of completely disable the LED control, set a new LED to control:
13 |
14 | ```c++
15 | void setup() {
16 | Homie.setLedPin(16, HIGH); // before Homie.setup() -- 2nd param is the state of the pin when the LED is o
17 | // ...
18 | }
19 | ```
20 |
--------------------------------------------------------------------------------
/docs/advanced-usage/compiler-flags.md:
--------------------------------------------------------------------------------
1 | Compiler flags can be used to add or remove certain functionality from homie-esp8266.
2 |
3 | Removing functionality can become useful if your firmware gets too large in size and is not upgradable any more over OTA. On the other hand these compiler flags allow to remove features from homie-esp8266, that you do not require or do not use.
4 |
5 | Adding functionality or features is useful to enable only partly implemented features or unstable or experimental features.
6 |
7 | **HOMIE_CONFIG**
8 |
9 | This compiler flag allows to disable the configuration mode completely. To configure your homie-esp8266, you need to upload the configuration to the SPIFFS before starting the device. Without a proper configuration the device will just restart after writing the error message about the missing configuration to the logger. Add the following to your platformio.ini file:
10 |
11 | ```
12 | build_flags = -D HOMIE_CONFIG=0
13 | ```
14 |
15 | This reduces the firmware size by about 50000 bytes.
16 |
17 | **HOMIE_MDNS**
18 |
19 | This compiler flag allows to disable the publishing of the device identifier via mDNS protocol. Add the following to your platformio.ini file:
20 |
21 | ```
22 | build_flags = -D HOMIE_MDNS=0
23 | ```
24 |
25 | This reduces the firmware size by about 6400 bytes.
26 |
27 | **ASYNC_TCP_SSL_ENABLED**
28 |
29 | This compiler flag allows to use SSL encryption for MQTT connections. All other network connections still can not be encrypted like HTTP or OTA.
30 |
31 | ```
32 | build_flags =
33 | -D ASYNC_TCP_SSL_ENABLED=1
34 | -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
35 | ```
36 |
37 | The additional flag `PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWITH` is necessary for SSL encryptions to work properly.
38 |
--------------------------------------------------------------------------------
/docs/advanced-usage/custom-settings.md:
--------------------------------------------------------------------------------
1 | # Typical use case
2 |
3 | Homie for ESP8266 lets you implement custom settings that can be set from the JSON configuration file and the Configuration API. Below is an example of how to use this feature:
4 |
5 | ```c++
6 | HomieSetting percentageSetting("percentage", "A simple percentage"); // id, description
7 |
8 | void setup() {
9 | percentageSetting.setDefaultValue(50).setValidator([] (long candidate) {
10 | return (candidate >= 0) && (candidate <= 100);
11 | });
12 |
13 | Homie.setup();
14 | }
15 | ```
16 |
17 | !!! tip "setDefaultValue() before Homie.setup()"
18 |
19 | As shown in the example above, the **default value** has to be set **before** `Homie.setup()` is called.
20 | Otherwise you get an error on startup if there is also no value configured in JSON configuration file.
21 |
22 | An `HomieSetting` instance can be of the following types:
23 |
24 | Type | Value
25 | ---- | -----
26 | `bool` | `true` or `false`
27 | `long` | An integer from `-2,147,483,648` to `2,147,483,647`
28 | `double` | A floating number that can fit into a `real64_t`
29 | `const char*` | Any string
30 |
31 | By default, a setting is mandatory (you have to set it in the configuration file). If you give it a default value with `setDefaultValue()`, the setting becomes optional. You can validate a setting by giving a validator function to `setValidator()`. To get the setting from your code, use `get()`. To get whether the value returned is the optional one or the one provided, use `wasProvided()`.
32 |
33 | For this example, if you want to provide the `percentage` setting, you will have to put in your configuration file:
34 |
35 | ```json
36 | {
37 | "settings": {
38 | "percentage": 75
39 | }
40 | }
41 | ```
42 |
43 | # Updating custom settings
44 |
45 | In order to change custom settings send via MQTT the JSON Object to: `homie//$implementation/config/set`. The JSON object might include all settings you need to update at once. As well, partial/incremental update is supported.
46 | JSON object should be flat string. From the example above it becomes:
47 | `{"settings":{"percentage":75}}`
48 |
49 | !!! tip Updated custom settings are saved to SPIFFS automatically.
50 |
51 | Once you updated the custom settings Homie will save it to the Flash and reboot itself.
52 | The reboot causes the updated and stored setting be read into RAM and be used in actual code.
53 |
54 | !!! tip If function of your Homie device is sensitive to reboots, then consider other method of custom settings.
55 |
56 | Sometimes, the device is designed for non-interrupted service. So the reset by setting update is not acceptable and can affect the device function/behavior.
57 |
58 | # Example
59 | See the following example for a concrete use case:
60 |
61 | [ CustomSettings.ino](https://github.com/homieiot/homie-esp8266/blob/develop/examples/CustomSettings/CustomSettings.ino)
62 |
--------------------------------------------------------------------------------
/docs/advanced-usage/deep-sleep.md:
--------------------------------------------------------------------------------
1 | Before deep sleeping, you will want to ensure that all messages are sent, including property values of your `HomieNode` and the `$online → false`. To do that, you can call `Homie.prepareToSleep()`. But the event `MQTT_READY` is emitted before your `HomieNode`s `loop()` method is called. Therefore you need to postpone the call to `Homie.prepareToSleep()` until the `loop()` method of all `HomieNode`s is called and their properties are submitted over MQTT. For that the additional Timer is necessary. Then `Homie.prepareToSleep()` will disconnect everything cleanly, so that you can call `Homie.doDeepSleep()`.
2 |
3 | ```c++
4 | #include
5 | #include "Timer.h"
6 |
7 | Timer t;
8 |
9 | void prepareSleep() {
10 | Homie.prepareToSleep();
11 | }
12 |
13 | void onHomieEvent(const HomieEvent& event) {
14 | switch(event.type) {
15 | case HomieEventType::MQTT_READY:
16 | Homie.getLogger() << "MQTT connected, preparing for deep sleep after 100ms..." << endl;
17 | t.after(100, prepareSleep);
18 | break;
19 | case HomieEventType::READY_TO_SLEEP:
20 | Homie.getLogger() << "Ready to sleep" << endl;
21 | Homie.doDeepSleep();
22 | break;
23 | }
24 | }
25 |
26 | void setup() {
27 | Serial.begin(115200);
28 | Serial << endl << endl;
29 | Homie.onEvent(onHomieEvent);
30 | Homie.setup();
31 | }
32 |
33 | void loop() {
34 | Homie.loop();
35 | t.update();
36 | }
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/advanced-usage/events.md:
--------------------------------------------------------------------------------
1 | You may want to hook to Homie events. Maybe you will want to control an RGB LED if the Wi-Fi connection is lost, or execute some code prior to a device reset, for example to clear some EEPROM you're using:
2 |
3 | ```c++
4 | void onHomieEvent(const HomieEvent& event) {
5 | switch(event.type) {
6 | case HomieEventType::STANDALONE_MODE:
7 | // Do whatever you want when standalone mode is started
8 | break;
9 | case HomieEventType::CONFIGURATION_MODE:
10 | // Do whatever you want when configuration mode is started
11 | break;
12 | case HomieEventType::NORMAL_MODE:
13 | // Do whatever you want when normal mode is started
14 | break;
15 | case HomieEventType::OTA_STARTED:
16 | // Do whatever you want when OTA is started
17 | break;
18 | case HomieEventType::OTA_PROGRESS:
19 | // Do whatever you want when OTA is in progress
20 |
21 | // You can use event.sizeDone and event.sizeTotal
22 | break;
23 | case HomieEventType::OTA_FAILED:
24 | // Do whatever you want when OTA is failed
25 | break;
26 | case HomieEventType::OTA_SUCCESSFUL:
27 | // Do whatever you want when OTA is successful
28 | break;
29 | case HomieEventType::ABOUT_TO_RESET:
30 | // Do whatever you want when the device is about to reset
31 | break;
32 | case HomieEventType::WIFI_CONNECTED:
33 | // Do whatever you want when Wi-Fi is connected in normal mode
34 |
35 | // You can use event.ip, event.gateway, event.mask
36 | break;
37 | case HomieEventType::WIFI_DISCONNECTED:
38 | // Do whatever you want when Wi-Fi is disconnected in normal mode
39 |
40 | // You can use event.wifiReason
41 | /*
42 | Wi-Fi Reason (souce: https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WiFiClientEvents/WiFiClientEvents.ino)
43 | 0 SYSTEM_EVENT_WIFI_READY < ESP32 WiFi ready
44 | 1 SYSTEM_EVENT_SCAN_DONE < ESP32 finish scanning AP
45 | 2 SYSTEM_EVENT_STA_START < ESP32 station start
46 | 3 SYSTEM_EVENT_STA_STOP < ESP32 station stop
47 | 4 SYSTEM_EVENT_STA_CONNECTED < ESP32 station connected to AP
48 | 5 SYSTEM_EVENT_STA_DISCONNECTED < ESP32 station disconnected from AP
49 | 6 SYSTEM_EVENT_STA_AUTHMODE_CHANGE < the auth mode of AP connected by ESP32 station changed
50 | 7 SYSTEM_EVENT_STA_GOT_IP < ESP32 station got IP from connected AP
51 | 8 SYSTEM_EVENT_STA_LOST_IP < ESP32 station lost IP and the IP is reset to 0
52 | 9 SYSTEM_EVENT_STA_WPS_ER_SUCCESS < ESP32 station wps succeeds in enrollee mode
53 | 10 SYSTEM_EVENT_STA_WPS_ER_FAILED < ESP32 station wps fails in enrollee mode
54 | 11 SYSTEM_EVENT_STA_WPS_ER_TIMEOUT < ESP32 station wps timeout in enrollee mode
55 | 12 SYSTEM_EVENT_STA_WPS_ER_PIN < ESP32 station wps pin code in enrollee mode
56 | 13 SYSTEM_EVENT_AP_START < ESP32 soft-AP start
57 | 14 SYSTEM_EVENT_AP_STOP < ESP32 soft-AP stop
58 | 15 SYSTEM_EVENT_AP_STACONNECTED < a station connected to ESP32 soft-AP
59 | 16 SYSTEM_EVENT_AP_STADISCONNECTED < a station disconnected from ESP32 soft-AP
60 | 17 SYSTEM_EVENT_AP_STAIPASSIGNED < ESP32 soft-AP assign an IP to a connected station
61 | 18 SYSTEM_EVENT_AP_PROBEREQRECVED < Receive probe request packet in soft-AP interface
62 | 19 SYSTEM_EVENT_GOT_IP6 < ESP32 station or ap or ethernet interface v6IP addr is preferred
63 | 20 SYSTEM_EVENT_ETH_START < ESP32 ethernet start
64 | 21 SYSTEM_EVENT_ETH_STOP < ESP32 ethernet stop
65 | 22 SYSTEM_EVENT_ETH_CONNECTED < ESP32 ethernet phy link up
66 | 23 SYSTEM_EVENT_ETH_DISCONNECTED < ESP32 ethernet phy link down
67 | 24 SYSTEM_EVENT_ETH_GOT_IP < ESP32 ethernet got IP from connected AP
68 | 25 SYSTEM_EVENT_MAX
69 | */
70 | break;
71 | case HomieEventType::MQTT_READY:
72 | // Do whatever you want when MQTT is connected in normal mode
73 | break;
74 | case HomieEventType::MQTT_DISCONNECTED:
75 | // Do whatever you want when MQTT is disconnected in normal mode
76 |
77 | // You can use event.mqttReason
78 | /*
79 | MQTT Reason (source: https://github.com/marvinroger/async-mqtt-client/blob/master/src/AsyncMqttClient/DisconnectReasons.hpp)
80 | 0 TCP_DISCONNECTED
81 | 1 MQTT_UNACCEPTABLE_PROTOCOL_VERSION
82 | 2 MQTT_IDENTIFIER_REJECTED
83 | 3 MQTT_SERVER_UNAVAILABLE
84 | 4 MQTT_MALFORMED_CREDENTIALS
85 | 5 MQTT_NOT_AUTHORIZED
86 | 6 ESP8266_NOT_ENOUGH_SPACE
87 | 7 TLS_BAD_FINGERPRINT
88 | */
89 | break;
90 | case HomieEventType::MQTT_PACKET_ACKNOWLEDGED:
91 | // Do whatever you want when an MQTT packet with QoS > 0 is acknowledged by the broker
92 |
93 | // You can use event.packetId
94 | break;
95 | case HomieEventType::READY_TO_SLEEP:
96 | // After you've called `prepareToSleep()`, the event is triggered when MQTT is disconnected
97 | break;
98 | case HomieEventType::SENDING_STATISTICS:
99 | // Do whatever you want when statistics are sent in normal mode
100 | break;
101 | }
102 | }
103 |
104 | void setup() {
105 | Homie.onEvent(onHomieEvent); // before Homie.setup()
106 | // ...
107 | }
108 | ```
109 |
110 | See the following example for a concrete use case:
111 |
112 | [ HookToEvents.ino](https://github.com/homieiot/homie-esp8266/blob/develop/examples/HookToEvents/HookToEvents.ino)
113 |
--------------------------------------------------------------------------------
/docs/advanced-usage/input-handlers.md:
--------------------------------------------------------------------------------
1 | There are four types of input handlers:
2 |
3 | * Global input handler. This unique handler will handle every changed settable properties for all nodes
4 |
5 | ```c++
6 | bool globalInputHandler(const HomieNode& node, const HomieRange& range, const String& property, const String& value) {
7 |
8 | }
9 |
10 | void setup() {
11 | Homie.setGlobalInputHandler(globalInputHandler); // before Homie.setup()
12 | // ...
13 | }
14 | ```
15 |
16 | * Node input handlers. This handler will handle every changed settable properties of a specific node
17 |
18 | ```c++
19 | bool nodeInputHandler(const HomieRange& range, const String& property, const String& value) {
20 |
21 | }
22 |
23 | HomieNode node("id", "type", false, 0, 0, nodeInputHandler);
24 | ```
25 |
26 | * Virtual callback from node input handler
27 |
28 | You can create your own class derived from HomieNode that implements the virtual method `bool HomieNode::handleInput(const HomieRange& range, const String& property, const String& value)`. The default node input handler then automatically calls your callback.
29 |
30 | ```c++
31 | class RelaisNode : public HomieNode {
32 | public:
33 | RelaisNode(): HomieNode("Relais", "switch8");
34 |
35 | protected:
36 | virtual bool handleInput(const HomieRange& range, const String& property, const String& value) {
37 |
38 | }
39 | };
40 | ```
41 |
42 | * Property input handlers. This handler will handle changes for a specific settable property of a specific node
43 |
44 | ```c++
45 | bool propertyInputHandler(const HomieRange& range, const String& value) {
46 |
47 | }
48 |
49 | HomieNode node("id", "type");
50 |
51 | void setup() {
52 | node.advertise("property").settable(propertyInputHandler); // before Homie.setup()
53 | // ...
54 | }
55 | ```
56 |
57 | You can see that input handlers return a boolean. An input handler can decide whether or not it handled the message and want to propagate it down to other input handlers. If an input handler returns `true`, the propagation is stopped, if it returns `false`, the propagation continues. The order of propagation is global handler → node handler → property handler.
58 |
59 | For example, imagine you defined three input handlers: the global one, the node one, and the property one. If the global input handler returns `false`, the node input handler will be called. If the node input handler returns `true`, the propagation is stopped and the property input handler won't be called. You can think of it as middlewares.
60 |
61 |
62 | !!! warning
63 | Homie uses [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) for network communication that make uses of asynchronous callback from the ESP8266 framework for incoming network packets. Thus the input handler runs in a different task than the `loopHandler()`. So keep in mind that the network task may interrupt your loop at any time.
64 |
--------------------------------------------------------------------------------
/docs/advanced-usage/logging.md:
--------------------------------------------------------------------------------
1 | By default, Homie for ESP8266 will output a lot of useful debug messages on the Serial. You may want to disable this behavior if you want to use the Serial line for anything else.
2 |
3 | ```c++
4 | void setup() {
5 | Homie.disableLogging(); // before Homie.setup()
6 | // ...
7 | }
8 | ```
9 |
10 | !!! warning
11 | It's up to you to call `Serial.begin();`, whether logging is enabled or not.
12 |
13 | You can also change the `Print` instance to log to:
14 |
15 | ```c++
16 | void setup() {
17 | Homie.setLoggingPrinter(&Serial2); // before Homie.setup()
18 | // ...
19 | }
20 | ```
21 |
22 | You can use the logger from your code with the `getLogger()` client:
23 |
24 | ```c++
25 | Homie.getLogger() << "Hey!" << endl;
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/advanced-usage/magic-bytes.md:
--------------------------------------------------------------------------------
1 | Homie for ESP8266 firmwares contain magic bytes allowing you to check if a firmware is actually an Homie for ESP8266 firmware, and if so, to get the name, the version and the brand of the firmware.
2 |
3 | You might be wondering why `Homie_setFirmware()` instead of `Homie.setFirmware()`, this is because we use [special macros](https://github.com/homieiot/homie-esp8266/blob/8935639bc649a6c71ce817ea4f732988506d020e/src/Homie.hpp#L23-L24) to embed the magic bytes.
4 |
5 | Values are encoded as such within the firmware binary:
6 |
7 | Type | Left boundary | Value | Right boundary
8 | ---- | ------------- | ----- | --------------
9 | Homie magic bytes | *None* | `0x25 0x48 0x4F 0x4D 0x49 0x45 0x5F 0x45 0x53 0x50 0x38 0x32 0x36 0x36 0x5F 0x46 0x57 0x25` | *None*
10 | Firmware name | `0xBF 0x84 0xE4 0x13 0x54` | **actual firmware name** | `0x93 0x44 0x6B 0xA7 0x75`
11 | Firmware version | `0x6A 0x3F 0x3E 0x0E 0xE1` | **actual firmware version** | `0xB0 0x30 0x48 0xD4 0x1A`
12 | Firmware brand (only present if `Homie_setBrand()` called, Homie otherwise) | `0xFB 0x2A 0xF5 0x68 0xC0` | **actual firmware brand** | `0x6E 0x2F 0x0F 0xEB 0x2D`
13 |
14 | See the following script for a concrete use case:
15 |
16 | [ firmware_parser.py](https://github.com/homieiot/homie-esp8266/blob/develop/scripts/firmware_parser)
17 |
--------------------------------------------------------------------------------
/docs/advanced-usage/miscellaneous.md:
--------------------------------------------------------------------------------
1 | # Know if the device is configured / connected
2 |
3 | If, for some reason, you want to run some code in the Arduino `loop()` function, it might be useful for you to know if the device is in configured (so in `normal` mode) and if the network connection is up.
4 |
5 | ```c++
6 | void loop() {
7 | if (Homie.isConfigured()) {
8 | // The device is configured, in normal mode
9 | if (Homie.isConnected()) {
10 | // The device is connected
11 | } else {
12 | // The device is not connected
13 | }
14 | } else {
15 | // The device is not configured, in either configuration or standalone mode
16 | }
17 | }
18 | ```
19 |
20 | # Organizing the sketch in good and safe order
21 |
22 | The example above is good to demonstrate usage of `Homie.isConnected()`. However, consider to not add any code into `loop()` function. ESP8266 is relatively weak MCU and Homie manages a few important environmental tasks. Interferring with Homie might boomerang by sudden crashes. Advised sketch of Homie is:
23 | ```c++
24 | #include
25 |
26 | void setupHandler() {
27 | // Code which should run AFTER the WiFi is connected.
28 | }
29 |
30 | void loopHandler() {
31 | // Code which should run in normal loop(), after setupHandler() finished.
32 | }
33 |
34 | void setup() {
35 | Serial.begin(115200);
36 | Serial << endl << endl;
37 |
38 | Homie_setFirmware("bare-minimum", "1.0.0"); // The underscore is not a typo! See Magic bytes
39 | Homie.setSetupFunction(setupHandler).setLoopFunction(loopHandler);
40 |
41 | // Code which should run BEFORE the WiFi is connected.
42 |
43 | Homie.setup();
44 | }
45 |
46 | void loop() {
47 | Homie.loop();
48 | }
49 | ```
50 |
51 | This way you can be sure the run is safe enough (unless you use blocking delay, then it is not) and the sketch keeps 'regular' structure of `setup()`/`loop()` just in terms of Homie.
52 |
53 | ## Why is `setupHandler()` needed in addition to `setup()` in Homie?
54 | `setup()` starts execution immediately after power on/wake up. `setupHandler()` starts to run after WiFi established the connection.
55 |
56 | ## Why it's needed `loopHandler()` in addition to `loop()` in Homie?
57 | `loop()` starts to run then `setup()` is completed. This somehow controls a run of `setupHandler()`. The `loopHandler()` serves safe way to run the loop.
58 |
59 | ## Why is this understanding important?
60 | 1. Once your code in `setupHandler()` and `loopHandler()` engaged, you can be pretty sure the `Homie.isConnected()` is true without checking it. Unless, you intend your device is mobile and might reach out of WiFi coverage in the middle of the run.
61 | 1. The `loop()` often starts before Wifi is connected. "Wifi connected" event causes significant load on the MCU with Wifi related tasks, also with sending initial MQTT reports. Therefore, running complex commands around the moment of "Wifi connected" might cause malfunction/crash.
62 |
63 | As solution, heavy commands (massive initializations, long calculations, SPIFFS reading/writing, etc.) should be initiated in one of 2 cases:
64 | - very early in setup() before "Wifi connected".
65 | - way after "Wifi connected" (non-blocking wait 3-5 seconds and then do the heavy commands).
66 |
67 | # Get access to the configuration
68 |
69 | You can get access to the configuration of the device. The representation of the configuration is:
70 |
71 | ```c++
72 | struct ConfigStruct {
73 | char* name;
74 | char* deviceId;
75 |
76 | struct WiFi {
77 | char* ssid;
78 | char* password;
79 | } wifi;
80 |
81 | struct MQTT {
82 | struct Server {
83 | char* host;
84 | uint16_t port;
85 | } server;
86 | char* baseTopic;
87 | bool auth;
88 | char* username;
89 | char* password;
90 | } mqtt;
91 |
92 | struct OTA {
93 | bool enabled;
94 | } ota;
95 | };
96 | ```
97 |
98 | For example, to access the Wi-Fi SSID, you would do:
99 |
100 | ```c++
101 | Homie.getConfiguration().wifi.ssid;
102 | ```
103 |
104 | # Get access to the MQTT client
105 |
106 | You can get access to the underlying MQTT client. For example, to disconnect from the broker:
107 |
108 | ```c++
109 | Homie.getMqttClient().disconnect();
110 | ```
111 |
112 | # Keep and get MQTT reports while WiFi is not connected
113 |
114 | ```c++
115 | #include
116 |
117 | HomieNode myNode("q_test", "test");
118 |
119 | // Struct for Queueing of postponed MQTT messages. Needed to gather messages while Wifi isn't connected yet.
120 | typedef struct strRec {
121 | char topic[10];
122 | char msg[90];
123 | } Rec;
124 | Queue msg_q(sizeof(Rec), 7, FIFO, true); // RAM is limited. Keep reasonable Q length (5-10 messages).
125 |
126 | // Here comes all other stuff, inclusing setup() and loop().
127 |
128 | ```
129 |
130 | In functions, where WiFi is not expected, keep the report as example:
131 | ```c++
132 | Rec r = {"alert", "Low storage space is free on SPIFF."};
133 | msg_q.push(&r);
134 | ```
135 |
136 | To release (AKA send) all kept messages from the queue, add in `loopHandler()`:
137 | ```c++
138 | while (!msg_q.isEmpty() && Homie.isConnected()) {
139 | Rec r;
140 | msg_q.pop(&r);
141 | myNode.setProperty(r.topic).setRetained(false).send(r.msg);
142 | }
143 | ```
144 |
145 | It might be good practice to use the queue always, not only when WiFi is down. This might make Homie job easier, and you get stability as benefit.
146 |
--------------------------------------------------------------------------------
/docs/advanced-usage/range-properties.md:
--------------------------------------------------------------------------------
1 | In all the previous examples you have seen, node properties were advertised one-by-one (e.g. `temperature`, `unit`...). But what if you have a LED strip with, say, 100 properties, one for each LED? You won't advertise these 100 LEDs one-by-one. This is what range properties are meant for.
2 |
3 | ```c++
4 | HomieNode stripNode("strip", "strip");
5 |
6 | bool ledHandler(const HomieRange& range, const String& value) {
7 | Homie.getLogger() << "LED " << range.index << " set to " << value << endl;
8 |
9 | // Now, let's update the actual state of the given led
10 | stripNode.setProperty("led").setRange(range).send(value);
11 | }
12 |
13 | void setup() {
14 | stripNode.advertiseRange("led", 1, 100).settable(ledHandler);
15 | // before Homie.setup()
16 | }
17 | ```
18 |
19 | On the mqtt broker you will see the following message show up:
20 | ```
21 | topic message
22 | --------------------------------------------------------
23 | homie//strip/$type strip
24 | homie//strip/$properties led[1-100]:settable
25 | ```
26 |
27 | You can then publish the value `on` to topic `homie//strip/led_1/set` to turn on led number 1.
28 |
29 | See the following example for a concrete use case:
30 |
31 | [ LedStrip](https://github.com/homieiot/homie-esp8266/blob/develop/examples/LedStrip/LedStrip.ino)
32 |
--------------------------------------------------------------------------------
/docs/advanced-usage/resetting.md:
--------------------------------------------------------------------------------
1 | Resetting the device means erasing the stored configuration and rebooting from `normal` mode to `configuration` mode. By default, you can do it by pressing for 5 seconds the `FLASH` button of your ESP8266 board.
2 |
3 | This behavior is configurable:
4 |
5 | ```c++
6 | void setup() {
7 | Homie.setResetTrigger(1, LOW, 2000); // before Homie.setup()
8 | // ...
9 | }
10 | ```
11 |
12 | The device will now reset if pin `1` is `LOW` for `2000`ms. You can also disable completely this reset trigger:
13 |
14 | ```c++
15 | void setup() {
16 | Homie.disableResetTrigger(); // before Homie.setup()
17 | // ...
18 | }
19 | ```
20 |
21 | In addition, you can also trigger a device reset from your sketch:
22 |
23 | ```c++
24 | void loop() {
25 | Homie.reset();
26 | }
27 | ```
28 |
29 | This will reset the device as soon as it is idle. Indeed, sometimes, you might want to disable temporarily the ability to reset the device. For example, if your device is doing some background work like moving shutters, you will want to disable the ability to reset until the shutters are not moving anymore.
30 |
31 | ```c++
32 | Homie.setIdle(false);
33 | ```
34 |
35 | Note that if a reset is asked while the device is not idle, the device will be flagged. In other words, when you will call `Homie.setIdle(true);` back, the device will immediately reset.
36 |
--------------------------------------------------------------------------------
/docs/advanced-usage/standalone-mode.md:
--------------------------------------------------------------------------------
1 | Homie for ESP8266 has a special mode named `standalone`. It was a [requested feature](https://github.com/homieiot/homie-esp8266/issues/125) to implement a way not to boot into `configuration` mode on initial boot, so that a device can work without being configured first. It was already possible in `configuration` mode, but the device would spawn an AP which would make it insecure.
2 |
3 | To enable this mode, call `Homie.setStandalone()`:
4 |
5 | ```c++
6 | void setup() {
7 | Homie.setStandalone(); // before Homie.setup()
8 | // ...
9 | }
10 | ```
11 |
12 | To actually configure the device, you have to reset it, the same way you would to go from `normal` mode to `configuration` mode.
13 |
--------------------------------------------------------------------------------
/docs/advanced-usage/streaming-operator.md:
--------------------------------------------------------------------------------
1 | Homie for ESP8266 includes a nice streaming operator to interact with `Print` objects.
2 |
3 | Imagine the following code:
4 |
5 | ```c++
6 | int temperature = 32;
7 | Homie.getLogger().print("The current temperature is ");
8 | Homie.getLogger().print(temperature);
9 | Homie.getLogger().println(" °C.");
10 | ```
11 |
12 | With the streaming operator, the following code will do exactly the same thing, without performance penalties:
13 |
14 | ```c++
15 | int temperature = 32;
16 | Homie.getLogger() << "The current temperature is " << temperature << " °C." << endl;
17 | ```
18 |
--------------------------------------------------------------------------------
/docs/advanced-usage/ui-bundle.md:
--------------------------------------------------------------------------------
1 | The Homie for ESP8266 configuration AP implements a captive portal. When connecting to it, you will be prompted to connect, and your Web browser will open. By default, it will show an empty page with a text saying to install an `ui_bundle.gz` file.
2 |
3 | Indeed, you can serve the [configuration UI](https://homieiot.github.io/homie-esp8266/configurators/v2/) directly from your ESP8266. See [the data/homie folder](https://github.com/homieiot/homie-esp8266/tree/develop/data/homie).
4 |
--------------------------------------------------------------------------------
/docs/assets/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homieiot/homie-esp8266/ba452eae5e23d01463568756465be07f8dc3bfce/docs/assets/github.png
--------------------------------------------------------------------------------
/docs/assets/led_mqtt.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homieiot/homie-esp8266/ba452eae5e23d01463568756465be07f8dc3bfce/docs/assets/led_mqtt.gif
--------------------------------------------------------------------------------
/docs/assets/led_solid.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homieiot/homie-esp8266/ba452eae5e23d01463568756465be07f8dc3bfce/docs/assets/led_solid.gif
--------------------------------------------------------------------------------
/docs/assets/led_wifi.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homieiot/homie-esp8266/ba452eae5e23d01463568756465be07f8dc3bfce/docs/assets/led_wifi.gif
--------------------------------------------------------------------------------
/docs/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homieiot/homie-esp8266/ba452eae5e23d01463568756465be07f8dc3bfce/docs/assets/logo.png
--------------------------------------------------------------------------------
/docs/assets/youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homieiot/homie-esp8266/ba452eae5e23d01463568756465be07f8dc3bfce/docs/assets/youtube.png
--------------------------------------------------------------------------------
/docs/configuration/http-json-api.md:
--------------------------------------------------------------------------------
1 | When in `configuration` mode, the device exposes a HTTP JSON API to send the configuration to it. When you send a valid configuration to the `/config` endpoint, the configuration file is stored in the filesystem at `/homie/config.json`.
2 |
3 | If you don't want to mess with JSON, you have a Web UI / app available:
4 |
5 | - At http://homieiot.github.io/homie-esp8266/configurators/v2/
6 | - As an [Android app](https://build.phonegap.com/apps/1906578/share)
7 |
8 | **Quick instructions to use the Web UI / app**:
9 |
10 | 1. Open the Web UI / app
11 | 2. Disconnect from your current Wi-Fi AP, and connect to the `Homie-xxxxxxxxxxxx` AP spawned in `configuration` mode
12 | 3. Follow the instructions
13 |
14 | You can see the sources of the Web UI [here](https://github.com/homieiot/homie-esp8266-setup).
15 |
16 | Alternatively, you can use this `curl` command to send the configuration to the device. You must connect to the device in `configuration` mode (i.e. the device is an Access Point). This method will not work if not in `configuration` mode:
17 |
18 | ```shell
19 | curl -X PUT http://192.168.123.1/config --header "Content-Type: application/json" -d @config.json
20 | ```
21 |
22 | This will send the `./config.json` file to the device.
23 |
24 | # Error handling
25 |
26 | When everything went fine, a `2xx` HTTP code is returned, such as `200 OK`, `202 Accepted`, `204 No Content` and so on.
27 | If anything goes wrong, a return code != 2xx will be returned, with a JSON `error` field indicating the error, such as `500 Internal Server error`, `400 Bad request` and so on.
28 |
29 | # Endpoints
30 |
31 | **API base address:** `http://192.168.123.1`
32 |
33 | ??? summary "GET `/heart`"
34 | This is useful to ensure we are connected to the device AP.
35 |
36 | ## Response
37 |
38 | `204 No Content`
39 |
40 | ---
41 |
42 | ??? summary "GET `/device-info`"
43 | Get some information on the device.
44 |
45 | ## Response
46 |
47 | `200 OK (application/json)`
48 |
49 | ```json
50 | {
51 | "hardware_device_id": "52a8fa5d",
52 | "homie_esp8266_version": "2.0.0",
53 | "firmware": {
54 | "name": "awesome-device",
55 | "version": "1.0.0"
56 | },
57 | "nodes": [
58 | {
59 | "id": "light",
60 | "type": "light"
61 | }
62 | ],
63 | "settings": [
64 | {
65 | "name": "timeout",
66 | "description": "Timeout in seconds",
67 | "type": "ulong",
68 | "required": false,
69 | "default": 10
70 | }
71 | ]
72 | }
73 | ```
74 |
75 | `type` can be one of the following:
76 |
77 | * `bool`: a boolean
78 | * `ulong`: an unsigned long
79 | * `long`: a long
80 | * `double`: a double
81 | * `string`: a string
82 |
83 | !!! note "Note about settings"
84 | If a setting is not required, the `default` field will always be set.
85 |
86 | ---
87 |
88 | ??? summary "GET `/networks`"
89 | Retrieve the Wi-Fi networks the device can see.
90 |
91 | ## Response
92 |
93 | !!! success "In case of success"
94 | `200 OK (application/json)`
95 |
96 | ```json
97 | {
98 | "networks": [
99 | { "ssid": "Network_2", "rssi": -82, "encryption": "wep" },
100 | { "ssid": "Network_1", "rssi": -57, "encryption": "wpa" },
101 | { "ssid": "Network_3", "rssi": -65, "encryption": "wpa2" },
102 | { "ssid": "Network_5", "rssi": -94, "encryption": "none" },
103 | { "ssid": "Network_4", "rssi": -89, "encryption": "auto" }
104 | ]
105 | }
106 | ```
107 |
108 | !!! failure "In case the initial Wi-Fi scan is not finished on the device"
109 | `503 Service Unavailable (application/json)`
110 |
111 | ```json
112 | {
113 | "error": "Initial Wi-Fi scan not finished yet"
114 | }
115 | ```
116 |
117 | ---
118 |
119 | ??? summary "PUT `/config`"
120 | Save the config to the device.
121 |
122 | ## Request body
123 |
124 | `(application/json)`
125 |
126 | See [JSON configuration file](json-configuration-file.md).
127 |
128 | ## Response
129 |
130 | !!! success "In case of success"
131 | `200 OK (application/json)`
132 |
133 | ```json
134 | {
135 | "success": true
136 | }
137 | ```
138 |
139 | !!! failure "In case of error in the payload"
140 | `400 Bad Request (application/json)`
141 |
142 | ```json
143 | {
144 | "success": false,
145 | "error": "Reason why the payload is invalid"
146 | }
147 | ```
148 |
149 | !!! failure "In case the device already received a valid configuration and is waiting for reboot"
150 | `403 Forbidden (application/json)`
151 |
152 | ```json
153 | {
154 | "success": false,
155 | "error": "Device already configured"
156 | }
157 | ```
158 |
159 | ---
160 |
161 | ??? summary "PUT `/wifi/connect`"
162 | Initiates the connection of the device to the Wi-Fi network while in configuation mode. This request is not synchronous and the result (Wi-Fi connected or not) must be obtained by with `GET /wifi/status`.
163 |
164 | ## Request body
165 |
166 | `(application/json)`
167 |
168 | ```json
169 | {
170 | "ssid": "My_SSID",
171 | "password": "my-passw0rd"
172 | }
173 | ```
174 |
175 | ## Response
176 |
177 | !!! success "In case of success"
178 | `202 Accepted (application/json)`
179 |
180 | ```json
181 | {
182 | "success": true
183 | }
184 | ```
185 |
186 | !!! failure "In case of error in the payload"
187 | `400 Bad Request (application/json)`
188 |
189 | ```json
190 | {
191 | "success": false,
192 | "error": "Reason why the payload is invalid"
193 | }
194 | ```
195 |
196 | ---
197 |
198 | ??? summary "GET `/wifi/status`"
199 | Returns the current Wi-Fi connection status.
200 |
201 | Helpful when monitoring Wi-Fi connectivity after `PUT /wifi/connect`.
202 |
203 | ## Response
204 |
205 | `200 OK (application/json)`
206 |
207 | ```json
208 | {
209 | "status": "connected"
210 | }
211 | ```
212 |
213 | `status` might be one of the following:
214 |
215 | * `idle`
216 | * `connect_failed`
217 | * `connection_lost`
218 | * `no_ssid_available`
219 | * `connected` along with a `local_ip` field
220 | * `disconnected`
221 |
222 | ---
223 |
224 | ??? summary "PUT `/proxy/control`"
225 | Enable/disable the device to act as a transparent proxy between AP and Station networks.
226 |
227 | All requests that don't collide with existing API paths will be bridged to the destination according to the `Host` HTTP header. The destination host is called using the existing Wi-Fi connection (established after a `PUT /wifi/connect`) and all contents are bridged back to the connection made to the AP side.
228 |
229 | This feature can be used to help captive portals to perform cloud API calls during device enrollment using the ESP8266 Wi-Fi AP connection without having to patch the Homie firmware. By using the transparent proxy, all operations can be performed by the custom JavaScript running on the browser (in SPIFFS location `/data/homie/ui_bundle.gz`).
230 |
231 | HTTPS is not supported.
232 |
233 | **Important**: The HTTP requests and responses must be kept as small as possible because all contents are transported using RAM memory, which is very limited.
234 |
235 | ## Request body
236 |
237 | `(application/json)`
238 |
239 | ```json
240 | {
241 | "enable": true
242 | }
243 | ```
244 |
245 | ## Response
246 |
247 | ??? success "In case of success"
248 | `200 OK (application/json)`
249 |
250 | ```json
251 | {
252 | "success": true
253 | }
254 | ```
255 |
256 | ??? failure "In case of error in the payload"
257 | `400 Bad Request (application/json)`
258 |
259 | ```json
260 | {
261 | "success": false,
262 | "error": "Reason why the payload is invalid"
263 | }
264 | ```
265 |
--------------------------------------------------------------------------------
/docs/configuration/json-configuration-file.md:
--------------------------------------------------------------------------------
1 | To configure your device, you have two choices: manually flashing the configuration file to the SPIFFS at the `/homie/config.json` (see [Uploading files to file system](http://esp8266.github.io/Arduino/versions/2.3.0/doc/filesystem.html#uploading-files-to-file-system)), so you can bypass the `configuration` mode, or send it through the [HTTP JSON API](http-json-api.md).
2 |
3 | Below is the format of the JSON configuration you will have to provide:
4 |
5 | ```json
6 | {
7 | "name": "The kitchen light",
8 | "device_id": "kitchen-light",
9 | "device_stats_interval": 60,
10 | "wifi": {
11 | "ssid": "Network_1",
12 | "password": "I'm a Wi-Fi password!",
13 | "bssid": "DE:AD:BE:EF:BA:BE",
14 | "channel": 1,
15 | "ip": "192.168.1.5",
16 | "mask": "255.255.255.0",
17 | "gw": "192.168.1.1",
18 | "dns1": "8.8.8.8",
19 | "dns2": "8.8.4.4"
20 | },
21 | "mqtt": {
22 | "host": "192.168.1.10",
23 | "port": 1883,
24 | "base_topic": "devices/",
25 | "auth": true,
26 | "username": "user",
27 | "password": "pass",
28 | "ssl": true,
29 | "ssl_fingerprint": "a27992d3420c89f293d351378ba5f5675f74fe3c"
30 | },
31 | "ota": {
32 | "enabled": true
33 | },
34 | "settings": {
35 | "percentage": 55
36 | }
37 | }
38 | ```
39 |
40 | The above JSON contains every field that can be customized.
41 |
42 | Here are the rules:
43 |
44 | * `name`, `wifi.ssid`, `wifi.password`, `mqtt.host` and `ota.enabled` are mandatory
45 | * `wifi.password` can be `null` if connecting to an open network
46 | * If `mqtt.auth` is `true`, `mqtt.username` and `mqtt.password` must be provided
47 | * `bssid`, `channel`, `ip`, `mask`, `gw`, `dns1`, `dns2` are not mandatory and are only needed to if there is a requirement to specify particular AP or set Static IP address. There are some rules which needs to be satisfied:
48 | - `bssid` and `channel` have to be defined together and these settings are independand of settings related to static IP
49 | - to define static IP, `ip` (IP address), `mask` (netmask) and `gw` (gateway) settings have to be defined at the same time
50 | - to define second DNS `dns2` the first one `dns1` has to be defined. Set DNS without `ip`, `mask` and `gw` does not affect the configuration (dns server will be provided by DHCP). It is not required to set DNS servers.
51 | * `ssl_fingerprint` can optionally be defined if `ssl` is enabled. The public key of the MQTT server is then verified against the fingerprint.
52 | * Homie's limits for MQTT broker username and password are ``32`` symbols. To increase this limit change ``MAX_MQTT_CREDS_LENGHT`` in the ``Limit.hpp`` file
53 |
54 | Default values if not provided:
55 |
56 | * `device_id`: the hardware device ID (eg. `1a2b3c4d5e6f`)
57 | * `device_stats_interval`: 60 seconds
58 | * `mqtt.port`: `1883`
59 | * `mqtt.base_topic`: `homie/`
60 | * `mqtt.auth`: `false`
61 |
62 | The `mqtt.host` field can be either an IP or an hostname.
63 |
64 | !!! tip "OTA"
65 | Homie-esp8266 supports `Over the Air` update if you enabled this in configuration (`ota.enabled: true`).
66 | For more details see: [OTA updates](../others/ota-configuration-updates.md)
67 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | Welcome on the Homie for ESP8266 docs.
2 |
--------------------------------------------------------------------------------
/docs/others/community-projects.md:
--------------------------------------------------------------------------------
1 | This page lists the projects made by the community to work with Homie.
2 |
3 | [](https://homieiot.github.io/)
4 |
5 | # [jpmens/homie-ota](https://github.com/jpmens/homie-ota)
6 |
7 | homie-ota is written in Python. It provides an OTA server for Homie devices as well as a simple inventory which can be useful to keep track of Homie devices. homie-ota also enables you to trigger an OTA update (over MQTT, using the Homie convention) from within its inventory. New firmware can be uploaded to homie-ota which detects firmware name (fwname) and version (fwversion) from the uploaded binary blob, thanks to an idea and code contributed by Marvin.
8 |
9 | # [stufisher/homie-control](https://github.com/stufisher/homie-control)
10 |
11 | homie-control provides a web UI to manage Homie devices as well as a series of virtual python devices to allow extended functionality.
12 |
13 | Its lets you do useful things like:
14 |
15 | * Historically log device properties
16 | * Schedule changes in event properties (i.e. water your garden once a day)
17 | * Execute profiles of property values (i.e. turn a series of lights on and off simultaneously)
18 | * Trigger property changes based on:
19 | * When a network device is dis/connected (i.e. your phone joins your wifi, turn the lights on)
20 | * Sunset / rise
21 | * When another property changes
22 |
--------------------------------------------------------------------------------
/docs/others/cpp-api-reference.md:
--------------------------------------------------------------------------------
1 | # Homie
2 |
3 | You don't have to instantiate an `Homie` instance, it is done internally.
4 |
5 | ```c++
6 | void setup();
7 | ```
8 |
9 | Setup Homie.
10 |
11 | !!! warning "Mandatory!"
12 | Must be called once in `setup()`.
13 |
14 | ```c++
15 | void loop();
16 | ```
17 |
18 | Handle Homie work.
19 |
20 | !!! warning "Mandatory!"
21 | Must be called once in `loop()`.
22 |
23 | ## Functions to call *before* `Homie.setup()`
24 |
25 | ```c++
26 | void Homie_setFirmware(const char* name, const char* version);
27 | // This is not a typo
28 | ```
29 |
30 | Set the name and version of the firmware.
31 | This is useful for OTA, as Homie will check against the server if there is a newer version.
32 | Be aware, that the function is implemented as a `define` macro.
33 | If you want to define the name or version outside the function call, you need to do so in the form of a `define` as well.
34 |
35 | !!! warning "Mandatory!"
36 | You need to set the firmware for your sketch to work.
37 |
38 |
39 | * **`name`**: Name of the firmware. Default value is `undefined`
40 | * **`version`**: Version of the firmware. Default value is `undefined`
41 |
42 | ```c++
43 | void Homie_setBrand(const char* name);
44 | // This is not a typo
45 | ```
46 |
47 | Set the brand of the device, used in the configuration AP, the device hostname and the MQTT client ID.
48 |
49 | * **`name`**: Name of the brand. Default value is `Homie`
50 |
51 | ```c++
52 | Homie& disableLogging();
53 | ```
54 |
55 | Disable Homie logging.
56 |
57 | ```c++
58 | Homie& setLoggingPrinter(Print* printer);
59 | ```
60 |
61 | Set the Print instance used for logging.
62 |
63 | * **`printer`**: Print instance to log to. By default, `Serial` is used
64 |
65 | !!! warning
66 | It's up to you to call `Serial.begin()`
67 |
68 | ```c++
69 | Homie& disableLedFeedback();
70 | ```
71 |
72 | Disable the built-in LED feedback indicating the Homie for ESP8266 state.
73 |
74 | ```c++
75 | Homie& setLedPin(uint8_t pin, uint8_t on);
76 | ```
77 |
78 | Set pin of the LED to control.
79 |
80 | * **`pin`**: LED to control
81 | * **`on`**: state when the light is on (HIGH or LOW)
82 |
83 | ```c++
84 | Homie& setConfigurationApPassword(const char* password);
85 | ```
86 |
87 | Set the configuration AP password.
88 |
89 | * **`password`**: the configuration AP password
90 |
91 | ```c++
92 | Homie& setGlobalInputHandler(std::function handler);
93 | ```
94 |
95 | Set input handler for subscribed properties.
96 |
97 | * **`handler`**: Global input handler
98 | * **`node`**: Name of the node getting updated
99 | * **`property`**: Property of the node getting updated
100 | * **`range`**: Range of the property of the node getting updated
101 | * **`value`**: Value of the new property
102 |
103 | ```c++
104 | Homie& setBroadcastHandler(std::function handler);
105 | ```
106 |
107 | Set broadcast handler.
108 |
109 | * **`handler`**: Broadcast handler
110 | * **`level`**: Level of the broadcast
111 | * **`value`**: Value of the broadcast
112 |
113 | ```c++
114 | Homie& onEvent(std::function callback);
115 | ```
116 |
117 | Set the event handler. Useful if you want to hook to Homie events.
118 |
119 | * **`callback`**: Event handler
120 |
121 | ```c++
122 | Homie& setResetTrigger(uint8_t pin, uint8_t state, uint16_t time);
123 | ```
124 |
125 | Set the reset trigger. By default, the device will reset when pin `0` is `LOW` for `5000`ms.
126 |
127 | * **`pin`**: Pin of the reset trigger
128 | * **`state`**: Reset when the pin reaches this state for the given time
129 | * **`time`**: Time necessary to reset
130 |
131 | ```c++
132 | Homie& disableResetTrigger();
133 | ```
134 |
135 | Disable the reset trigger.
136 |
137 | ```c++
138 | Homie& setSetupFunction(std::function callback);
139 | ```
140 |
141 | You can provide the function that will be called when operating in `normal` mode.
142 |
143 | * **`callback`**: Setup function
144 |
145 | ```c++
146 | Homie& setLoopFunction(std::function callback);
147 | ```
148 |
149 | You can provide the function that will be looped in normal mode.
150 |
151 | * **`callback`**: Loop function
152 |
153 | ```c++
154 | Homie& setStandalone();
155 | ```
156 |
157 | This will mark the Homie firmware as standalone, meaning it will first boot in `standalone` mode. To configure it and boot to `configuration` mode, the device has to be resetted.
158 |
159 | ## Functions to call *after* `Homie.setup()`
160 |
161 | ```c++
162 | void reset();
163 | ```
164 |
165 | Flag the device for reset.
166 |
167 | ```c++
168 | void setIdle(bool idle);
169 | ```
170 |
171 | Set the device as idle or not. This is useful at runtime, because you might want the device not to be resettable when you have another library that is doing some unfinished work, like moving shutters for example.
172 |
173 | * **`idle`**: Device in an idle state or not
174 |
175 | ```c++
176 | void prepareToSleep();
177 | ```
178 |
179 | Prepare the device for deep sleep. It ensures messages are sent and disconnects cleanly from the MQTT broker, triggering a `READY_TO_SLEEP` event when done.
180 |
181 | ```c++
182 | void doDeepSleep(uint64_t time_us = 0, RFMode mode = RF_DEFAULT);
183 | ```
184 |
185 | Puth the device into deep sleep. It ensures the Serial is flushed.
186 |
187 | ```c++
188 | bool isConfigured() const;
189 | ```
190 |
191 | Is the device in `normal` mode, configured?
192 |
193 | ```c++
194 | bool isConnected() const;
195 | ```
196 |
197 | Is the device in `normal` mode, configured and connected?
198 |
199 | ```c++
200 | const ConfigStruct& getConfiguration() const;
201 | ```
202 |
203 | Get the configuration struct.
204 |
205 | !!! danger
206 | Be careful with this struct, never attempt to change it.
207 |
208 | ```c++
209 | AsyncMqttClient& getMqttClient();
210 | ```
211 |
212 | Get the underlying `AsyncMqttClient` object.
213 |
214 | ```c++
215 | Logger& getLogger();
216 | ```
217 |
218 | Get the underlying `Logger` object, which is only a wrapper around `Serial` by default.
219 |
220 | -------
221 |
222 | # HomieNode
223 |
224 | ```c++
225 | HomieNode(const char* id, const char* name, const char* type, bool range, uint16_t lower, uint16_t upper, std::function handler);
226 | ```
227 |
228 | Constructor of an HomieNode object.
229 |
230 | * **`id`**: ID of the node
231 | * **`type`**: Type of the node
232 | * **`handler`**: Optional. Input handler of the node
233 |
234 | ```c++
235 | const char* getId() const;
236 | ```
237 |
238 | Return the ID of the node.
239 |
240 | ```c++
241 | const char* getType() const;
242 | ```
243 |
244 | Return the type of the node.
245 |
246 | ```c++
247 | PropertyInterface& advertise(const char* property);
248 | ```
249 |
250 | Advertise a property / range property on the node.
251 |
252 | * **`property`**: Property to advertise
253 |
254 | This returns a reference to `PropertyInterface` on which you can call:
255 |
256 | ```c++
257 | void settable(std::function handler) = );
258 | ```
259 |
260 | Make the property settable.
261 |
262 | * **`handler`**: Optional. Input handler of the property
263 |
264 | ```c++
265 | SendingPromise& setProperty(const String& property);
266 | ```
267 |
268 | Using this function, you can set the value of a node property, like a temperature for example.
269 |
270 | * **`property`**: Property to send
271 |
272 | This returns a reference to `SendingPromise`, on which you can call:
273 |
274 | ```c++
275 | SendingPromise& setQos(uint8_t qos); // defaults to 1
276 | SendingPromise& setRetained(bool retained); // defaults to true
277 | SendingPromise& overwriteSetter(bool overwrite); // defaults to false
278 | SendingPromise& setRange(const HomieRange& range); // defaults to not a range
279 | SendingPromise& setRange(uint16_t rangeIndex); // defaults to not a range
280 | uint16_t send(const String& value); // finally send the property, return the packetId (or 0 if failure)
281 | ```
282 |
283 | Method names should be self-explanatory.
284 |
285 | # HomieSetting
286 |
287 | ```c++
288 | HomieSetting(const char* name, const char* description);
289 | ```
290 |
291 | Constructor of an HomieSetting object.
292 |
293 | * **`T`**: Type of the setting. Either `bool`, `unsigned long`, `long`, `double` or `const char*`
294 | * **`name`**: Name of the setting
295 | * **`description`**: Description of the setting
296 |
297 | ```c++
298 | T get() const;
299 | ```
300 |
301 | Get the default value if the setting is optional and not provided, or the provided value if the setting is required or optional but provided.
302 |
303 | ```c++
304 | bool wasProvided() const;
305 | ```
306 |
307 | Return whether the setting was provided or not (otherwise `get()` would return the default value).
308 |
309 | Set the default value and make the setting optional.
310 |
311 | ```c++
312 | HomieSetting& setDefaultValue(T defaultValue);
313 | ```
314 |
315 | * **`defaultValue`**: The default value
316 |
317 | ```c++
318 | HomieSetting& setValidator(std::function validator);
319 | ```
320 |
321 | Set a validation function for the setting. The validator must return `true` if the candidate is correct, `false` otherwise.
322 |
323 | * **`validator`**: The validation function
324 |
--------------------------------------------------------------------------------
/docs/others/homie-implementation-specifics.md:
--------------------------------------------------------------------------------
1 | The Homie `$implementation` identifier is `esp8266`.
2 |
3 | # Version
4 |
5 | * `$implementation/version`: Homie for ESP8266 version
6 |
7 | # Reset
8 |
9 | * `$implementation/reset`: You can publish a `true` to this topic to reset the device
10 |
11 | # Configuration
12 |
13 | * `$implementation/config`: The `configuration.json` is published there, with `wifi.password`, `mqtt.username` and `mqtt.password` fields stripped
14 | * `$implementation/config/set`: You can update the `configuration.json` by sending incremental JSON on this topic
15 |
16 | # OTA
17 |
18 | * `$implementation/ota/enabled`: `true` if OTA is enabled, `false` otherwise
19 | * `$implementation/ota/firmware`: If the update request is accepted, you must send the firmware payload to this topic
20 | * `$implementation/ota/status`: HTTP-like status code indicating the status of the OTA. Might be:
21 |
22 | Code|Description
23 | ----|-----------
24 | `200`|OTA successfully flashed
25 | `202`|OTA request / checksum accepted
26 | `206 465/349680`|OTA in progress. The data after the status code corresponds to `/`
27 | `304`|The current firmware is already up-to-date
28 | `400 BAD_FIRMWARE`|OTA error from your side. The identifier might be `BAD_FIRMWARE`, `BAD_CHECKSUM`, `NOT_ENOUGH_SPACE`, `NOT_REQUESTED`
29 | `403`|OTA not enabled
30 | `500 FLASH_ERROR`|OTA error on the ESP8266. The identifier might be `FLASH_ERROR`
31 |
--------------------------------------------------------------------------------
/docs/others/limitations-and-known-issues.md:
--------------------------------------------------------------------------------
1 | # SSL support
2 |
3 | In Homie for ESP8266 v1.x, SSL was possible but it was not reliable. Due to the asynchronous nature of the v2.x, SSL is not completely available anymore. Only MQTT connections can be encrypted with SSL.
4 |
5 | # ADC readings
6 |
7 | [This is a known esp8266/Arduino issue](https://github.com/esp8266/Arduino/issues/1634) that polling `analogRead()` too frequently forces the Wi-Fi to disconnect. As a workaround, don't poll the ADC more than one time every 3ms.
8 |
9 | # Wi-Fi connection
10 |
11 | If you encouter any issues with the Wi-Fi, try changing the flash size build parameter, or try to erase the flash. See [#158](https://github.com/homieiot/homie-esp8266/issues/158) for more information.
12 |
--------------------------------------------------------------------------------
/docs/others/ota-configuration-updates.md:
--------------------------------------------------------------------------------
1 | # OTA updates
2 |
3 | Homie for ESP8266 supports OTA (Over the Air), if enabled in the configuration, and if a compatible OTA entity is set up.
4 |
5 | There's a script that does just that:
6 |
7 | [ ota_updater.py](https://github.com/homieiot/homie-esp8266/blob/develop/scripts/ota_updater)
8 |
9 | It works this way:
10 |
11 | 1. During startup of the Homie for ESP8266 device, it reports the current firmware's MD5 to `$fw/checksum` (in addition to `$fw/name` and `$fw/version`). The OTA entity may or may not use this information to automatically schedule OTA updates
12 | 2. The OTA entity publishes the latest available firmware payload to `$implementation/ota/firmware/`, either as binary or as a Base64 encoded string
13 | * If OTA is disabled, Homie for ESP8266 reports `403` to `$implementation/ota/status` and aborts the OTA
14 | * If OTA is enabled and the latest available checksum is the same as what is currently running, Homie for ESP8266 reports `304` and aborts the OTA
15 | * If the checksum is not a valid MD5, Homie for ESP8266 reports `400 BAD_CHECKSUM` to `$implementation/ota/status` and aborts the OTA
16 | 3. Homie starts to flash the firmware
17 | * The firmware is updating. Homie for ESP8266 reports progress with `206 /`
18 | * When all bytes are flashed, the firmware is verified (including the MD5 if one was set)
19 | * Homie for ESP8266 either reports `200` on success, `400` if the firmware in invalid or `500` if there's an internal error
20 | 5. Homie for ESP8266 reboots on success as soon as the device is idle
21 |
22 | See [Homie implementation specifics](homie-implementation-specifics.md) for more details on status codes.
23 |
24 | ## OTA entities projects
25 |
26 | See [Community projects](community-projects.md).
27 |
28 | # Configuration updates
29 |
30 | In `normal` mode, you can get the current `config.json`, published on `$implementation/config` with `wifi.password`, `mqtt.username` and `mqtt.password` stripped. You can update the configuration on-the-fly by publishing incremental JSON updates to `$implementation/config/set`. For example, given the following `config.json`:
31 |
32 | ```json
33 | {
34 | "name": "Kitchen light",
35 | "wifi": {
36 | "ssid": "Network_1",
37 | "password": "I'm a Wi-Fi password!"
38 | },
39 | "mqtt": {
40 | "host": "192.168.1.20",
41 | "port": 1883
42 | },
43 | "ota": {
44 | "enabled": false
45 | },
46 | "settings": {
47 |
48 | }
49 | }
50 | ```
51 |
52 | You can update the name and Wi-Fi password by sending the following incremental JSON:
53 |
54 | ```json
55 | {
56 | "name": "Living room light",
57 | "wifi": {
58 | "password": "I'am a new Wi-Fi password!"
59 | }
60 | }
61 | ```
62 |
--------------------------------------------------------------------------------
/docs/others/troubleshooting.md:
--------------------------------------------------------------------------------
1 | ## 1. I see some garbage on the Serial monitor?
2 |
3 | You are probably using a generic ESP8266. The problem with these modules is the built-in LED is tied to the serial line. You can do two things:
4 |
5 | * Disable the serial logging, to have the LED working:
6 |
7 | ```c++
8 | void setup() {
9 | Homie.enableLogging(false); // before Homie.setup()
10 | // ...
11 | }
12 | ```
13 |
14 | * Disable the LED blinking, to have the serial line working:
15 |
16 | ```c++
17 | void setup() {
18 | Homie.enableBuiltInLedIndicator(false); // before Homie.setup()
19 | // ...
20 | }
21 | ```
22 |
23 | ## 2. I see an `abort` message on the Serial monitor?
24 |
25 | `abort()` is called by Homie for ESP8266 when the framework is used in a bad way. The possible causes are:
26 |
27 | * You are calling a function that is meant to be called before `Homie.setup()`, after `Homie.setup()`
28 |
29 | * One of the string you've used (in `setFirmware()`, `subscribe()`, etc.) is too long. Check the `Limits.hpp` file to see the max length possible for each string.
30 |
31 | ## 3. The network is completely unstable... What's going on?
32 |
33 | The framework needs to work continuously (ie. `Homie.loop()` needs to be called very frequently). In other words, don't use `delay()` (see [avoid delay](http://playground.arduino.cc/Code/AvoidDelay)) or anything that might block the code for more than 50ms or so. There is also a known Arduino for ESP8266 issue with `analogRead()`, see [Limitations and known issues](limitations-and-known-issues.md#adc-readings).
34 |
35 | ## 4. My device resets itself without me doing anything?
36 |
37 | You have probably connected a sensor to the default reset pin of the framework (D3 on NodeMCU, GPIO0 on other boards). See [Resetting](../advanced-usage/resetting.md).
38 |
--------------------------------------------------------------------------------
/docs/others/upgrade-guide-from-v1-to-v2.md:
--------------------------------------------------------------------------------
1 | This is an upgrade guide to upgrade your Homie devices from v1 to v2.
2 |
3 | ## New convention
4 |
5 | The Homie convention has been revised to v2 to be more extensible and introspectable. Be sure to [check it out](https://github.com/homieiot/convention/tree/v2.0.0).
6 |
7 | ## API changes in the sketch
8 |
9 | 1. `Homie.setFirmware(name, version)` must be replaced by `Homie_setFirmware(name, version)`
10 | 2. `Homie.setBrand(brand)` must be replaced by `Homie_setBrand(brand)`
11 | 3. `Homie.registerNode()` must be removed, nodes are now automagically registered
12 | 4. If you've enabled Serial logging, `Serial.begin()` must be called explicitely in your sketch
13 | 5. Remove the `HOMIE_OTA_MODE` in your event handler, if you have one
14 | 6. The `Homie.setNodeProperty()` signature changed completely. If you had `Homie.setNodeProperty(node, "property", "value", true)`, the new equivalent syntax is `Homie.setNodeProperty(node, "property").setRetained(true).send("value")`. Note the `setRetained()` is not even required as messages are retained by default.
15 | 7. TODO
16 |
--------------------------------------------------------------------------------
/docs/others/upgrade-guide-from-v2-to-v3.md:
--------------------------------------------------------------------------------
1 | This is an upgrade guide to upgrade your Homie devices from v2 to v3.
2 |
3 | ## New convention
4 |
5 | The Homie convention has been revised to v3 to be more extensible and introspectable. Be sure to [check it out](https://github.com/homieiot/convention/tree/v3.0.1).
6 |
7 | ## API changes in the sketch
8 |
9 | 1. Constructor of `HomieNode` needs third mandatory param `const char* type`:
10 | E.g. `HomieNode lightNode("light", "Light");` -> `HomieNode lightNode("light", "Light", "switch");`.
11 | 2. Signature of handleInput has changed to: `handleInput(const HomieRange& range, const String& property, const String& value)`
12 | TODO: see Ping example.
13 | 4. TODO
14 |
--------------------------------------------------------------------------------
/docs/quickstart/getting-started.md:
--------------------------------------------------------------------------------
1 | This *Getting Started* guide assumes you have an ESP8266 board with an user-configurable LED, and an user programmable button, like a NodeMCU DevKit 1.0, for example. These restrictions can be lifted (see next pages).
2 |
3 | To use Homie for ESP8266, you will need:
4 |
5 | * An ESP8266
6 | * The Arduino IDE for ESP8266 (version 2.3.0 minimum)
7 | * Basic knowledge of the Arduino environment (upload a sketch, import libraries, ...)
8 | * To understand [the Homie convention](https://github.com/homieiot/convention)
9 |
10 | ## Installing Homie for ESP8266
11 |
12 | There are two ways to install Homie for ESP8266.
13 |
14 | ### 1a. For the Arduino IDE
15 |
16 | There is a YouTube video with instructions:
17 |
18 | [ How to install Homie libraries on Arduino IDE](https://www.youtube.com/watch?v=bH3KfFfYUvg)
19 |
20 | 1. Download the [release corresponding to this documentation version](https://github.com/homieiot/homie-esp8266/releases)
21 |
22 | 2. Load the `.zip` with **Sketch → Include Library → Add .ZIP Library**
23 |
24 | Homie for ESP8266 has 5 dependencies:
25 |
26 | * [ArduinoJson](https://github.com/bblanchon/ArduinoJson) >= 5.0.8
27 | * [Bounce2](https://github.com/thomasfredericks/Bounce2)
28 | * [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) >= [c8ed544](https://github.com/me-no-dev/ESPAsyncTCP)
29 | * [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client)
30 | * [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
31 |
32 | Some of them are available through the Arduino IDE, with **Sketch → Include Library → Manage Libraries**. For the others, install it by downloading the `.zip` on GitHub.
33 |
34 | ### 1b. With [PlatformIO](http://platformio.org)
35 |
36 | In a terminal, run `platformio lib install 555`.
37 |
38 | !!! warning "Not yet released as stable"
39 | The above command is for when the v2 is stable and released. Currently, the latest stable version is 1.5. In the meantime, use the develop branch to get started with the v2, add this in your **platformio.ini**:
40 |
41 | ```
42 | lib_deps = git+https://github.com/homieiot/homie-esp8266.git#develop
43 | ```
44 |
45 | Dependencies are installed automatically.
46 |
47 | ## Bare minimum sketch
48 |
49 | ```c++
50 | #include
51 |
52 | void setup() {
53 | Serial.begin(115200);
54 | Serial << endl << endl;
55 |
56 | Homie_setFirmware("bare-minimum", "1.0.0"); // The underscore is not a typo! See Magic bytes
57 | Homie.setup();
58 | }
59 |
60 | void loop() {
61 | Homie.loop();
62 | }
63 | ```
64 |
65 |
66 | This is the bare minimum needed for Homie for ESP8266 to work correctly.
67 |
68 | !!! tip "LED"
69 | 
70 | If you upload this sketch, you will notice the LED of the ESP8266 will light on. This is because you are in `configuration` mode.
71 |
72 | Homie for ESP8266 has 3 modes of operation:
73 |
74 | 1. By default, the `configuration` mode is the initial one. It spawns an AP and an HTTP webserver exposing a JSON API. To interact with it, you have to connect to the AP. Then, an HTTP client can get the list of available Wi-Fi networks and send the configuration (like the Wi-Fi SSID, the Wi-Fi password, some settings...). Once the device receives the credentials, it boots into `normal` mode.
75 |
76 | 2. The `normal` mode is the mode the device will be most of the time. It connects to the Wi-Fi, to the MQTT, it sends initial informations to the Homie server (like the local IP, the version of the firmware currently running...) and it subscribes to the needed MQTT topics. It automatically reconnects to the Wi-Fi and the MQTT when the connection is lost. It also handle the OTA. The device can return to `configuration` mode in different ways (press of a button or custom function, see [Resetting](../advanced-usage/resetting.md)).
77 |
78 | 3. The `standalone` mode. See [Standalone mode](../advanced-usage/standalone-mode.md).
79 |
80 | !!! warning
81 | **As a rule of thumb, never block the device with blocking code for more than 50ms or so.** Otherwise, you may very probably experience unexpected behaviors.
82 |
83 | ## Connecting to the AP and configuring the device
84 |
85 | Homie for ESP8266 has spawned a secure AP named `Homie-xxxxxxxxxxxx`, like `Homie-c631f278df44`. Connect to it.
86 |
87 | !!! tip "Hardware device ID"
88 | This `c631f278df44` ID is unique to each device, and you cannot change it (this is actually the MAC address of the station mode). If you flash a new sketch, this ID won't change.
89 |
90 | Once connected, the webserver is available at `http://192.168.123.1`. Every domain name is resolved by the built-in DNS server to this address. You can then configure the device using the [HTTP JSON API](../configuration/http-json-api.md). When the device receives its configuration, it will reboot into `normal` mode.
91 |
92 | ## Understanding what happens in `normal` mode
93 |
94 | ### Visual codes
95 |
96 | When the device boots in `normal` mode, it will start blinking:
97 |
98 | !!! tip "LED"
99 | 
100 | Slowly when connecting to the Wi-Fi
101 |
102 | !!! tip "LED"
103 | 
104 | Faster when connecting to the MQTT broker
105 |
106 | This way, you can have a quick feedback on what's going on. If both connections are established, the LED will stay off. Note the device will also blink during the automatic reconnection, if the connection to the Wi-Fi or the MQTT broker is lost.
107 |
108 | ### Under the hood
109 |
110 | Although the sketch looks like it does not do anything, it actually does quite a lot:
111 |
112 | * It automatically connects to the Wi-Fi and MQTT broker. No more network boilerplate code
113 | * It exposes the Homie device on MQTT (as `/`, e.g. `homie/c631f278df44`)
114 | * It subscribes to the special OTA and configuration topics, automatically flashing a sketch if available or updating the configuration
115 | * It checks for a button press on the ESP8266, to return to `configuration` mode
116 |
117 | ## Creating an useful sketch
118 |
119 | Now that we understand how Homie for ESP8266 works, let's create an useful sketch. We want to create a smart light.
120 |
121 | [ LightOnOff.ino](https://github.com/homieiot/homie-esp8266/blob/develop/examples/LightOnOff/LightOnOff.ino)
122 |
123 | Alright, step by step:
124 |
125 | 1. We create a node with an ID of `light`, and name `Light` and a type of `switch` with `HomieNode lightNode("light", "Light", "switch")`
126 | 2. We set the name and the version of the firmware with `Homie_setFirmware("awesome-light" ,"1.0.0");`
127 | 3. We want our `light` node to advertise an `on` property, which is settable. We do that with `lightNode.advertise("on").settable(lightOnHandler);`. The `lightOnHandler` function will be called when the value of this property is changed
128 | 4. In the `lightOnHandler` function, we want to update the state of the `light` node. We do this with `lightNode.setProperty("on").send("true");`
129 |
130 | In about thirty SLOC, we have achieved to create a smart light, without any hard-coded credentials, with automatic reconnection in case of network failure, and with OTA support. Not bad, right?
131 |
132 | ## Creating a sensor node
133 |
134 | In the previous example sketch, we were reacting to property changes. But what if we want, for example, to send a temperature every 5 minutes? We could do this in the Arduino `loop()` function. But then, we would have to check if we are in `normal` mode, and we would have to ensure the network connection is up before being able to send anything. Boring.
135 |
136 | Fortunately, Homie for ESP8266 provides an easy way to do that.
137 |
138 | [ TemperatureSensor.ino](https://github.com/homieiot/homie-esp8266/blob/develop/examples/TemperatureSensor/TemperatureSensor.ino)
139 |
140 | The only new things here are the `Homie.setSetupFunction(setupHandler);` and `Homie.setLoopFunction(loopHandler);` calls. The setup function will be called once, when the device is in `normal` mode and the network connection is up. The loop function will be called everytime, when the device is in `normal` mode and the network connection is up. This provides a nice level of abstraction.
141 |
142 | Now that you understand the basic usage of Homie for ESP8266, you can head on to the next pages to learn about more powerful features like input handlers, the event system and custom settings.
143 |
--------------------------------------------------------------------------------
/docs/quickstart/what-is-it.md:
--------------------------------------------------------------------------------
1 | Homie for ESP8266 is an ESP8266 for Arduino implementation of [Homie](https://github.com/homieiot/convention), a thin and simple MQTT convention for the IoT. More than that, it's also a full-featured framework to get started with your IoT project very quickly. Simply put, you don't have to manage yourself the connection/reconnection to the Wi-Fi/MQTT. You don't even have to hard-code credentials in your sketch: this can be done using a simple JSON API. Everything is handled internally, by Homie for ESP8266.
2 |
3 | You guessed it, the purpose of Homie for ESP8266 is to simplify the development of connected objects.
4 |
--------------------------------------------------------------------------------
/examples/Broadcast/Broadcast.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | bool broadcastHandler(const String& level, const String& value) {
4 | Homie.getLogger() << "Received broadcast level " << level << ": " << value << endl;
5 | return true;
6 | }
7 |
8 | void setup() {
9 | Serial.begin(115200);
10 | Serial << endl << endl;
11 | Homie_setFirmware("broadcast-test", "1.0.0");
12 | Homie.setBroadcastHandler(broadcastHandler);
13 |
14 | Homie.setup();
15 | }
16 |
17 | void loop() {
18 | Homie.loop();
19 | }
20 |
--------------------------------------------------------------------------------
/examples/CustomSettings/CustomSettings.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | const int DEFAULT_TEMPERATURE_INTERVAL = 300;
4 |
5 | unsigned long lastTemperatureSent = 0;
6 |
7 | HomieNode temperatureNode("temperature", "Temperature", "temperature");
8 |
9 | HomieSetting temperatureIntervalSetting("temperatureInterval", "The temperature interval in seconds");
10 |
11 | void loopHandler() {
12 | if (millis() - lastTemperatureSent >= temperatureIntervalSetting.get() * 1000UL || lastTemperatureSent == 0) {
13 | float temperature = 22; // Fake temperature here, for the example
14 | Homie.getLogger() << "Temperature: " << temperature << " °C" << endl;
15 | temperatureNode.setProperty("degrees").send(String(temperature));
16 | lastTemperatureSent = millis();
17 | }
18 | }
19 |
20 | void setup() {
21 | Serial.begin(115200);
22 | Serial << endl << endl;
23 | Homie_setFirmware("temperature-setting", "1.0.0");
24 | Homie.setLoopFunction(loopHandler);
25 |
26 | temperatureNode.advertise("degrees").setName("Degrees")
27 | .setDatatype("float")
28 | .setUnit("ºC");
29 |
30 | temperatureIntervalSetting.setDefaultValue(DEFAULT_TEMPERATURE_INTERVAL).setValidator([] (long candidate) {
31 | return candidate > 0;
32 | });
33 |
34 | Homie.setup();
35 | }
36 |
37 | void loop() {
38 | Homie.loop();
39 | }
40 |
--------------------------------------------------------------------------------
/examples/DoorSensor/DoorSensor.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | const int PIN_DOOR = 16;
4 |
5 | Bounce debouncer = Bounce(); // Bounce is built into Homie, so you can use it without including it first
6 | int lastDoorValue = -1;
7 |
8 | HomieNode doorNode("door", "Door", "door");
9 |
10 | void loopHandler() {
11 | int doorValue = debouncer.read();
12 |
13 | if (doorValue != lastDoorValue) {
14 | Homie.getLogger() << "Door is now " << (doorValue ? "open" : "close") << endl;
15 |
16 | doorNode.setProperty("open").send(doorValue ? "true" : "false");
17 | lastDoorValue = doorValue;
18 | }
19 | }
20 |
21 | void setup() {
22 | Serial.begin(115200);
23 | Serial << endl << endl;
24 | pinMode(PIN_DOOR, INPUT);
25 | digitalWrite(PIN_DOOR, HIGH);
26 | debouncer.attach(PIN_DOOR);
27 | debouncer.interval(50);
28 |
29 | Homie_setFirmware("awesome-door", "1.0.0");
30 | Homie.setLoopFunction(loopHandler);
31 |
32 | doorNode.advertise("open").setName("Open").setDatatype("boolean");
33 |
34 | Homie.setup();
35 | }
36 |
37 | void loop() {
38 | Homie.loop();
39 | debouncer.update();
40 | }
41 |
--------------------------------------------------------------------------------
/examples/GlobalInputHandler/GlobalInputHandler.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | HomieNode lightNode("light", "Light", "switch");
4 |
5 | bool globalInputHandler(const HomieNode& node, const HomieRange& range, const String& property, const String& value) {
6 | Homie.getLogger() << "Received on node " << node.getId() << ": " << property << " = " << value << endl;
7 | return true;
8 | }
9 |
10 | void setup() {
11 | Serial.begin(115200);
12 | Serial << endl << endl;
13 | Homie_setFirmware("global-input-handler", "1.0.0");
14 | Homie.setGlobalInputHandler(globalInputHandler);
15 |
16 | lightNode.advertise("on").setName("On").setDatatype("boolean").settable();
17 |
18 | Homie.setup();
19 | }
20 |
21 | void loop() {
22 | Homie.loop();
23 | }
24 |
--------------------------------------------------------------------------------
/examples/HookToEvents/HookToEvents.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | void onHomieEvent(const HomieEvent& event) {
4 | switch (event.type) {
5 | case HomieEventType::STANDALONE_MODE:
6 | Serial << "Standalone mode started" << endl;
7 | break;
8 | case HomieEventType::CONFIGURATION_MODE:
9 | Serial << "Configuration mode started" << endl;
10 | break;
11 | case HomieEventType::NORMAL_MODE:
12 | Serial << "Normal mode started" << endl;
13 | break;
14 | case HomieEventType::OTA_STARTED:
15 | Serial << "OTA started" << endl;
16 | break;
17 | case HomieEventType::OTA_PROGRESS:
18 | Serial << "OTA progress, " << event.sizeDone << "/" << event.sizeTotal << endl;
19 | break;
20 | case HomieEventType::OTA_FAILED:
21 | Serial << "OTA failed" << endl;
22 | break;
23 | case HomieEventType::OTA_SUCCESSFUL:
24 | Serial << "OTA successful" << endl;
25 | break;
26 | case HomieEventType::ABOUT_TO_RESET:
27 | Serial << "About to reset" << endl;
28 | break;
29 | case HomieEventType::WIFI_CONNECTED:
30 | Serial << "Wi-Fi connected, IP: " << event.ip << ", gateway: " << event.gateway << ", mask: " << event.mask << endl;
31 | break;
32 | case HomieEventType::WIFI_DISCONNECTED:
33 | Serial << "Wi-Fi disconnected, reason: " << (int8_t)event.wifiReason << endl;
34 | break;
35 | case HomieEventType::MQTT_READY:
36 | Serial << "MQTT connected" << endl;
37 | break;
38 | case HomieEventType::MQTT_DISCONNECTED:
39 | Serial << "MQTT disconnected, reason: " << (int8_t)event.mqttReason << endl;
40 | break;
41 | case HomieEventType::MQTT_PACKET_ACKNOWLEDGED:
42 | Serial << "MQTT packet acknowledged, packetId: " << event.packetId << endl;
43 | break;
44 | case HomieEventType::READY_TO_SLEEP:
45 | Serial << "Ready to sleep" << endl;
46 | break;
47 | case HomieEventType::SENDING_STATISTICS:
48 | Serial << "Sending statistics" << endl;
49 | break;
50 | }
51 | }
52 |
53 | void setup() {
54 | Serial.begin(115200);
55 | Serial << endl << endl;
56 | Homie.disableLogging();
57 | Homie_setFirmware("events-test", "1.0.0");
58 | Homie.onEvent(onHomieEvent);
59 | Homie.setup();
60 | }
61 |
62 | void loop() {
63 | Homie.loop();
64 | }
65 |
--------------------------------------------------------------------------------
/examples/IteadSonoff/IteadSonoff.ino:
--------------------------------------------------------------------------------
1 | /* WARNING: untested */
2 |
3 | #include
4 |
5 | const int PIN_RELAY = 12;
6 | const int PIN_LED = 13;
7 | const int PIN_BUTTON = 0;
8 |
9 | HomieNode switchNode("switch", "Switch", "switch");
10 |
11 | bool switchOnHandler(const HomieRange& range, const String& value) {
12 | if (value != "true" && value != "false") return false;
13 |
14 | bool on = (value == "true");
15 | digitalWrite(PIN_RELAY, on ? HIGH : LOW);
16 | switchNode.setProperty("on").send(value);
17 | Homie.getLogger() << "Switch is " << (on ? "on" : "off") << endl;
18 |
19 | return true;
20 | }
21 |
22 | void setup() {
23 | Serial.begin(115200);
24 | Serial << endl << endl;
25 | pinMode(PIN_RELAY, OUTPUT);
26 | digitalWrite(PIN_RELAY, LOW);
27 |
28 | Homie_setFirmware("itead-sonoff", "1.0.0");
29 | Homie.setLedPin(PIN_LED, LOW).setResetTrigger(PIN_BUTTON, LOW, 5000);
30 |
31 | switchNode.advertise("on").setName("On").setDatatype("boolean").settable(switchOnHandler);
32 |
33 | Homie.setup();
34 | }
35 |
36 | void loop() {
37 | Homie.loop();
38 | }
39 |
--------------------------------------------------------------------------------
/examples/IteadSonoffButton/IteadSonoffButton.ino:
--------------------------------------------------------------------------------
1 | /*
2 | * Tested with "WiFi Smart Socket ESP8266 MQTT"
3 | * and "Sonoff - WiFi Wireless Smart Switch ESP8266 MQTT"
4 | *
5 | * The Relay could be toggeled with the physical pushbutton
6 | */
7 |
8 | #include
9 |
10 | const int PIN_RELAY = 12;
11 | const int PIN_LED = 13;
12 | const int PIN_BUTTON = 0;
13 |
14 | unsigned long buttonDownTime = 0;
15 | byte lastButtonState = 1;
16 | byte buttonPressHandled = 0;
17 |
18 | HomieNode switchNode("switch", "Switch", "switch");
19 |
20 | bool switchOnHandler(HomieRange range, String value) {
21 | if (value != "true" && value != "false") return false;
22 |
23 | bool on = (value == "true");
24 | digitalWrite(PIN_RELAY, on ? HIGH : LOW);
25 | switchNode.setProperty("on").send(value);
26 | Homie.getLogger() << "Switch is " << (on ? "on" : "off") << endl;
27 |
28 | return true;
29 | }
30 |
31 | void toggleRelay() {
32 | bool on = digitalRead(PIN_RELAY) == HIGH;
33 | digitalWrite(PIN_RELAY, on ? LOW : HIGH);
34 | switchNode.setProperty("on").send(on ? "false" : "true");
35 | Homie.getLogger() << "Switch is " << (on ? "off" : "on") << endl;
36 | }
37 |
38 | void loopHandler() {
39 | byte buttonState = digitalRead(PIN_BUTTON);
40 | if ( buttonState != lastButtonState ) {
41 | if (buttonState == LOW) {
42 | buttonDownTime = millis();
43 | buttonPressHandled = 0;
44 | }
45 | else {
46 | unsigned long dt = millis() - buttonDownTime;
47 | if ( dt >= 90 && dt <= 900 && buttonPressHandled == 0 ) {
48 | toggleRelay();
49 | buttonPressHandled = 1;
50 | }
51 | }
52 | lastButtonState = buttonState;
53 | }
54 | }
55 |
56 | void setup() {
57 | Serial.begin(115200);
58 | Serial.println();
59 | Serial.println();
60 | pinMode(PIN_RELAY, OUTPUT);
61 | pinMode(PIN_BUTTON, INPUT);
62 | digitalWrite(PIN_RELAY, LOW);
63 |
64 | Homie_setFirmware("itead-sonoff-buton", "1.0.0");
65 | Homie.setLedPin(PIN_LED, LOW).setResetTrigger(PIN_BUTTON, LOW, 5000);
66 |
67 | switchNode.advertise("on").setName("On").setDatatype("boolean").settable(switchOnHandler);
68 |
69 | Homie.setLoopFunction(loopHandler);
70 | Homie.setup();
71 | }
72 |
73 | void loop() {
74 | Homie.loop();
75 | }
76 |
--------------------------------------------------------------------------------
/examples/LedStrip/LedStrip.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | const unsigned int NUMBER_OF_LED = 4;
4 | const unsigned char LED_PINS[NUMBER_OF_LED] = { 16, 5, 4, 0 };
5 |
6 | HomieNode stripNode("strip", "Strip", "strip", true, 1, NUMBER_OF_LED);
7 |
8 | bool stripLedHandler(const HomieRange& range, const String& value) {
9 | if (!range.isRange) return false; // if it's not a range
10 |
11 | if (range.index < 1 || range.index > NUMBER_OF_LED) return false; // if it's not a valid range
12 |
13 | if (value != "on" && value != "off") return false; // if the value is not valid
14 |
15 | bool on = (value == "on");
16 |
17 | digitalWrite(LED_PINS[range.index - 1], on ? HIGH : LOW);
18 | stripNode.setProperty("led").setRange(range).send(value); // Update the state of the led
19 | Homie.getLogger() << "Led " << range.index << " is " << value << endl;
20 |
21 | return true;
22 | }
23 |
24 | void setup() {
25 | for (int i = 0; i < NUMBER_OF_LED; i++) {
26 | pinMode(LED_PINS[i], OUTPUT);
27 | digitalWrite(LED_PINS[i], LOW);
28 | }
29 |
30 | Serial.begin(115200);
31 | Serial << endl << endl;
32 |
33 | Homie_setFirmware("awesome-ledstrip", "1.0.0");
34 |
35 | stripNode.advertise("led").setName("Led").setDatatype("boolean").settable(stripLedHandler);
36 |
37 | Homie.setup();
38 | }
39 |
40 | void loop() {
41 | Homie.loop();
42 | }
43 |
--------------------------------------------------------------------------------
/examples/LightOnOff/LightOnOff.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #define firmwareVersion "1.0.0"
4 | const int PIN_RELAY = 5;
5 |
6 | HomieNode lightNode("light", "Light", "switch");
7 |
8 | bool lightOnHandler(const HomieRange& range, const String& value) {
9 | if (value != "true" && value != "false") return false;
10 |
11 | bool on = (value == "true");
12 | digitalWrite(PIN_RELAY, on ? HIGH : LOW);
13 | lightNode.setProperty("on").send(value);
14 | Homie.getLogger() << "Light is " << (on ? "on" : "off") << endl;
15 |
16 | return true;
17 | }
18 |
19 | void setup() {
20 | Serial.begin(115200);
21 | Serial << endl << endl;
22 | pinMode(PIN_RELAY, OUTPUT);
23 | digitalWrite(PIN_RELAY, LOW);
24 |
25 | Homie_setFirmware("awesome-relay", firmwareVersion);
26 |
27 | lightNode.advertise("on").setName("On").setDatatype("boolean").settable(lightOnHandler);
28 |
29 | Homie.setup();
30 | }
31 |
32 | void loop() {
33 | Homie.loop();
34 | }
35 |
--------------------------------------------------------------------------------
/examples/Ping/.gitignore:
--------------------------------------------------------------------------------
1 | ### PlatformIO ###
2 | .pioenvs
3 | .piolibdeps
4 | .clang_complete
5 | .gcc-flags.json
6 |
7 | ### VisualStudioCode ###
8 | .vscode/*
9 | !.vscode/settings.json
10 | !.vscode/tasks.json
11 | !.vscode/launch.json
12 | !.vscode/extensions.json
13 |
--------------------------------------------------------------------------------
/examples/Ping/README.md:
--------------------------------------------------------------------------------
1 | # Homie Ping Example
2 |
3 | 📣 Homie Ping example.
4 |
5 | This is a simple example for Ping: You can send a Homie 3.0 compatible ping-request to the controller and it
6 | will answer with message 'pong'.
7 | For seeing it is alive, the controller sends in loop 'hello' messages.
8 |
9 | Use PlatformIO to build and deploy the example.
10 |
11 | Merged from: https://github.com/stritti/Homie8266-Ping
12 |
--------------------------------------------------------------------------------
/examples/Ping/lib/README:
--------------------------------------------------------------------------------
1 |
2 | This directory is intended for project specific (private) libraries.
3 | PlatformIO will compile them to static libraries and link into executable file.
4 |
5 | The source code of each library should be placed in a an own separate directory
6 | ("lib/your_library_name/[here are source files]").
7 |
8 | For example, see a structure of the following two libraries `Foo` and `Bar`:
9 |
10 | |--lib
11 | | |
12 | | |--Bar
13 | | | |--docs
14 | | | |--examples
15 | | | |--src
16 | | | |- Bar.c
17 | | | |- Bar.h
18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
19 | | |
20 | | |--Foo
21 | | | |- Foo.c
22 | | | |- Foo.h
23 | | |
24 | | |- README --> THIS FILE
25 | |
26 | |- platformio.ini
27 | |--src
28 | |- main.c
29 |
30 | and a contents of `src/main.c`:
31 | ```
32 | #include
33 | #include
34 |
35 | int main (void)
36 | {
37 | ...
38 | }
39 |
40 | ```
41 |
42 | PlatformIO Library Dependency Finder will find automatically dependent
43 | libraries scanning project source files.
44 |
45 | More information about PlatformIO Library Dependency Finder
46 | - https://docs.platformio.org/page/librarymanager/ldf.html
47 |
--------------------------------------------------------------------------------
/examples/Ping/platformio.ini:
--------------------------------------------------------------------------------
1 | ; PlatformIO Project Configuration File
2 | ;
3 | ; Build options: build flags, source filter
4 | ; Upload options: custom upload port, speed and extra flags
5 | ; Library options: dependencies, extra library storages
6 | ; Advanced options: extra scripting
7 | ;
8 | ; Please visit documentation for the other options and examples
9 | ; https://docs.platformio.org/page/projectconf.html
10 |
11 | [env:nodemcuv2]
12 | platform = espressif8266
13 | board = nodemcuv2
14 | framework = arduino
15 |
16 | ;Add the PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY build flag to ensure reliable OTA updates.
17 | build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
18 |
19 | lib_deps =
20 | https://github.com/homieiot/homie-esp8266.git#develop
21 |
22 | ; Serial Monitor options
23 | monitor_speed = 115200
24 |
25 |
26 | [env:esp32dev]
27 | platform = espressif32
28 | board = esp32dev
29 | framework = arduino
30 |
31 | lib_deps =
32 | https://github.com/homieiot/homie-esp8266.git#develop
33 |
34 | ; Serial Monitor options
35 | monitor_speed = 115200
36 |
37 | ; Unit Testing options
38 | test_ignore = test_desktop
39 |
--------------------------------------------------------------------------------
/examples/Ping/src/PingNode.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * Homie Node for Pings.
3 | *
4 | */
5 | #include "PingNode.hpp"
6 |
7 | PingNode::PingNode(const char* id, const char* name, const int measurementInterval) : HomieNode(id, name, "ping") {
8 | _measurementInterval = (measurementInterval > MIN_INTERVAL) ? measurementInterval : MIN_INTERVAL;
9 | _lastMeasurement = 0;
10 | }
11 |
12 | /**
13 | *
14 | */
15 | void PingNode::printCaption() {
16 | Homie.getLogger() << cCaption << endl;
17 | }
18 |
19 | /**
20 | * Handles the received MQTT messages from Homie.
21 | *
22 | */
23 | bool PingNode::handleInput(const HomieRange& range, const String& property, const String& value) {
24 |
25 | printCaption();
26 | Homie.getLogger() << cIndent << "〽 handleInput -> property '" << property << "' value=" << value << endl;
27 |
28 | setProperty(cPing).send(cPong);
29 |
30 | return true;
31 | }
32 |
33 | /**
34 | *
35 | */
36 | void PingNode::loop() {
37 | if (millis() - _lastMeasurement >= _measurementInterval * 1000UL || _lastMeasurement == 0) {
38 | _lastMeasurement = millis();
39 |
40 | Homie.getLogger() << "〽 Sending ping 'hello': " << getId() << endl;
41 |
42 | setProperty(cPing).send(cHello);
43 | }
44 | }
45 |
46 | /**
47 | *
48 | */
49 | void PingNode::onReadyToOperate() {}
50 |
51 | /**
52 | *
53 | */
54 | void PingNode::setup() {
55 | printCaption();
56 |
57 | advertise(cPing).setName("ping").setRetained(true).setDatatype("string").settable();
58 | }
59 |
--------------------------------------------------------------------------------
/examples/Ping/src/PingNode.hpp:
--------------------------------------------------------------------------------
1 | /**
2 | * Homie Node for Pings.
3 | *
4 | */
5 |
6 | #pragma once
7 |
8 | #include
9 |
10 | class PingNode : public HomieNode {
11 |
12 | public:
13 | PingNode(const char* id, const char* name, const int measurementInterval = MEASUREMENT_INTERVAL);
14 |
15 | protected:
16 | virtual void setup() override;
17 | virtual void onReadyToOperate() override;
18 | virtual bool handleInput(const HomieRange& range, const String& property, const String& value);
19 |
20 | virtual void loop() override;
21 |
22 | private:
23 | static const int MIN_INTERVAL = 60; // in seconds
24 | static const int MEASUREMENT_INTERVAL = 300;
25 |
26 | const char* cCaption = "• Ping Module:";
27 | const char* cIndent = " ◦ ";
28 |
29 | const char* cPing = "ping";
30 | const char* cPong = "pong";
31 | const char* cHello = "hello";
32 |
33 | unsigned long _measurementInterval;
34 | unsigned long _lastMeasurement;
35 |
36 | void printCaption();
37 | };
38 |
--------------------------------------------------------------------------------
/examples/Ping/src/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "PingNode.hpp"
3 |
4 | PingNode pingNode("ping", "Ping");
5 |
6 | void setup() {
7 | Serial.begin(115200);
8 |
9 | while (!Serial) {
10 | ; // wait for serial port to connect. Needed for native USB port only
11 | }
12 |
13 | Homie_setFirmware("ping-demo", "1.0.0"); // The underscore is not a typo! See Magic bytes
14 |
15 | Homie.setup();
16 | Homie.getLogger() << F("✔ main: Setup ready") << endl;
17 | Homie.getLogger() << F(" send now ping request via MQTT to /homie/") << Homie.getConfiguration().deviceId << F("/ping/ping/set") << endl;
18 | }
19 |
20 | void loop() {
21 | Homie.loop();
22 | }
23 |
--------------------------------------------------------------------------------
/examples/SingleButton/SingleButton.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | const int PIN_BUTTON = 5; // D1
4 |
5 | int buttonState = 0;
6 | bool buttonPressed = false;
7 |
8 | HomieNode buttonNode("button", "Button", "button");
9 |
10 | void loopHandler() {
11 | buttonState = !digitalRead(PIN_BUTTON);
12 |
13 | if (buttonState == HIGH && !buttonPressed) {
14 | buttonNode.setProperty("button").send("PRESSED");
15 | Homie.getLogger() << "Button pressed" << endl;
16 | buttonPressed = true;
17 | } else if (buttonState == LOW && buttonPressed) {
18 | buttonNode.setProperty("button").send("RELEASED");
19 | Homie.getLogger() << "Button released" << endl;
20 | buttonPressed = false;
21 | }
22 | }
23 |
24 | void setup() {
25 | Serial.begin(115200);
26 | Serial << endl << endl;
27 | Homie_setFirmware("awesome-button", "1.0.0");
28 | Homie.setLoopFunction(loopHandler);
29 |
30 | pinMode(PIN_BUTTON, INPUT_PULLUP);
31 |
32 | buttonNode.advertise("button").setName("Button")
33 | .setDatatype("enum")
34 | .setFormat("PRESSED,RELEASED")
35 | .setRetained(false);
36 |
37 | Homie.setup();
38 | }
39 |
40 | void loop() {
41 | Homie.loop();
42 | }
43 |
--------------------------------------------------------------------------------
/examples/SonoffDualShutters/SonoffDualShutters.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | # Homie enabled Sonoff Dual shutters
4 |
5 | Requires the Shutters library:
6 | https://github.com/marvinroger/arduino-shutters
7 | and the SonoffDual library:
8 | https://github.com/marvinroger/arduino-sonoff-dual
9 |
10 | ## Features
11 |
12 | * Do a short press to close shutters
13 | if level != 0 or open shutters if level == 0
14 | * Do a long press to reset
15 |
16 | */
17 |
18 | #include
19 |
20 | #include
21 | #include
22 | #include
23 |
24 | const unsigned long COURSE_TIME = 30 * 1000;
25 | const float CALIBRATION_RATIO = 0.1;
26 |
27 | const bool RELAY1_MOVE = true;
28 | const bool RELAY1_STOP = false;
29 |
30 | const bool RELAY2_UP = true;
31 | const bool RELAY2_DOWN = false;
32 |
33 | const byte SHUTTERS_EEPROM_POSITION = 0;
34 |
35 | HomieNode shuttersNode("shutters", "Shutters", "shutters");
36 |
37 | // Shutters
38 |
39 | void shuttersUp() {
40 | SonoffDual.setRelays(RELAY1_MOVE, RELAY2_UP);
41 | }
42 |
43 | void shuttersDown() {
44 | SonoffDual.setRelays(RELAY1_MOVE, RELAY2_DOWN);
45 | }
46 |
47 | void shuttersHalt() {
48 | SonoffDual.setRelays(RELAY1_STOP, false);
49 | }
50 |
51 | uint8_t shuttersGetState() {
52 | return EEPROM.read(SHUTTERS_EEPROM_POSITION);
53 | }
54 |
55 | void shuttersSetState(uint8_t state) {
56 | EEPROM.write(SHUTTERS_EEPROM_POSITION, state);
57 | EEPROM.commit();
58 | }
59 |
60 | Shutters shutters(COURSE_TIME, shuttersUp, shuttersDown, shuttersHalt, shuttersGetState, shuttersSetState, CALIBRATION_RATIO, onShuttersLevelReached);
61 |
62 | void onShuttersLevelReached(uint8_t level) {
63 | if (shutters.isIdle()) Homie.setIdle(true); // if idle, we've reached our target
64 | if (Homie.isConnected()) shuttersNode.setProperty("level").send(String(level));
65 | }
66 |
67 | // Homie
68 |
69 | void onHomieEvent(const HomieEvent& event) {
70 | switch (event.type) {
71 | case HomieEventType::ABOUT_TO_RESET:
72 | shutters.reset();
73 | break;
74 | }
75 | }
76 |
77 | bool shuttersLevelHandler(const HomieRange& range, const String& value) {
78 | for (byte i = 0; i < value.length(); i++) {
79 | if (isDigit(value.charAt(i)) == false) return false;
80 | }
81 |
82 | const unsigned long numericValue = value.toInt();
83 | if (numericValue > 100) return false;
84 |
85 | // wanted value is valid
86 |
87 | if (shutters.isIdle() && numericValue == shutters.getCurrentLevel()) return true; // nothing to do
88 |
89 | Homie.setIdle(false);
90 | shutters.setLevel(numericValue);
91 |
92 | return true;
93 | }
94 |
95 | // Logic
96 |
97 | void setup() {
98 | SonoffDual.setup();
99 | EEPROM.begin(4);
100 | shutters.begin();
101 |
102 | Homie_setFirmware("sonoff-dual-shutters", "1.0.0");
103 | Homie.disableLogging();
104 | Homie.disableResetTrigger();
105 | Homie.setLedPin(SonoffDual.LED_PIN, SonoffDual.LED_ON);
106 | Homie.onEvent(onHomieEvent);
107 |
108 | shuttersNode.advertise("level").setName("Level").setDatatype("integer")
109 | .setFormat("0:100").settable(shuttersLevelHandler);
110 |
111 | Homie.setup();
112 | }
113 |
114 | void loop() {
115 | shutters.loop();
116 | Homie.loop();
117 | SonoffDualButton buttonState = SonoffDual.handleButton();
118 | if (buttonState == SonoffDualButton::LONG) {
119 | Homie.reset();
120 | } else if (buttonState == SonoffDualButton::SHORT && shutters.isIdle()) {
121 | Homie.setIdle(false);
122 |
123 | if (shutters.getCurrentLevel() == 100) {
124 | shutters.setLevel(0);
125 | } else {
126 | shutters.setLevel(100);
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/examples/TemperatureSensor/TemperatureSensor.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | const int TEMPERATURE_INTERVAL = 300;
4 |
5 | unsigned long lastTemperatureSent = 0;
6 |
7 | HomieNode temperatureNode("temperature", "Temperature", "temperature");
8 |
9 | void loopHandler() {
10 | if (millis() - lastTemperatureSent >= TEMPERATURE_INTERVAL * 1000UL || lastTemperatureSent == 0) {
11 | float temperature = 22; // Fake temperature here, for the example
12 | Homie.getLogger() << "Temperature: " << temperature << " °C" << endl;
13 | temperatureNode.setProperty("degrees").send(String(temperature));
14 | lastTemperatureSent = millis();
15 | }
16 | }
17 |
18 | void setup() {
19 | Serial.begin(115200);
20 | Serial << endl << endl;
21 | Homie_setFirmware("awesome-temperature", "1.0.0");
22 | Homie.setLoopFunction(loopHandler);
23 |
24 | temperatureNode.advertise("degrees").setName("Degrees")
25 | .setDatatype("float")
26 | .setUnit("ºC");
27 |
28 | Homie.setup();
29 | }
30 |
31 | void loop() {
32 | Homie.loop();
33 | }
34 |
--------------------------------------------------------------------------------
/examples/testAllInputHandlers/testAllInputHandlers.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This is a sketch testing all the different types of handlers
4 | The global input handler will always be triggered.
5 | Setting lightnode1/property1/ will trigger the node input handler.
6 | Setting lightnode2/property1/ will trigger the property input handler.
7 |
8 | */
9 |
10 | #include
11 | #include
12 |
13 | bool globalInputHandler(const HomieNode& node, const HomieRange& range, const String& property, const String& value) {
14 | Homie.getLogger() << "Global input handler. Received on node " << node.getId() << ": " << property << " = " << value << endl;
15 | return false;
16 | }
17 |
18 | bool nodeInputHandler(const HomieRange & range, const String & property, const String & value) {
19 | Homie.getLogger() << "Node input handler. Received on property " << property << " value: " << value;
20 | return true;
21 | }
22 |
23 | bool propertyInputHandler(const HomieRange& range, const String& value) {
24 | Homie.getLogger() << "Property input handler. Receveived value: " << value;
25 | return true;
26 | }
27 |
28 | HomieNode lightNode1("lightnode1", "Light Node One Name","switch", false, 0 , 0, &nodeInputHandler);
29 | HomieNode lightNode2("lightnode2", "Light Two One Name","switch");
30 |
31 | void setup() {
32 | Serial.begin(115200);
33 | Serial << endl << endl;
34 | Homie_setFirmware("Test all input handlers", "0.0.1");
35 | lightNode1.advertise("property1").setName("ln1 First property").setDatatype("boolean").settable();
36 | lightNode2.advertise("property1").setName("ln2 First property").setDatatype("boolean").settable(propertyInputHandler);
37 | Homie.setGlobalInputHandler(globalInputHandler);
38 | Homie.setup();
39 | }
40 |
41 | void loop() {
42 | Homie.loop();
43 | }
44 |
--------------------------------------------------------------------------------
/homie-esp8266.cppcheck:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/keywords.txt:
--------------------------------------------------------------------------------
1 | #######################################
2 | # Datatypes (KEYWORD1)
3 | #######################################
4 |
5 | Homie KEYWORD1
6 | HomieNode KEYWORD1
7 | HomieSetting KEYWORD1
8 | HomieEvent KEYWORD1
9 | HomieEventType KEYWORD1
10 | HomieRange KEYWORD1
11 |
12 | #######################################
13 | # Methods and Functions (KEYWORD2)
14 | #######################################
15 |
16 | Homie_setBrand KEYWORD2
17 | Homie_setFirmware KEYWORD2
18 |
19 | # Homie
20 |
21 | setup KEYWORD2
22 | loop KEYWORD2
23 | disableLogging KEYWORD2
24 | setLoggingPrinter KEYWORD2
25 | disableLedFeedback KEYWORD2
26 | setLedPin KEYWORD2
27 | setConfigurationApPassword KEYWORD2
28 | setGlobalInputHandler KEYWORD2
29 | setBroadcastHandler KEYWORD2
30 | onEvent KEYWORD2
31 | setResetTrigger KEYWORD2
32 | disableResetTrigger KEYWORD2
33 | setSetupFunction KEYWORD2
34 | setLoopFunction KEYWORD2
35 | setStandalone KEYWORD2
36 | reset KEYWORD2
37 | setIdle KEYWORD2
38 | isConfigured KEYWORD2
39 | isConnected KEYWORD2
40 | getConfiguration KEYWORD2
41 | getMqttClient KEYWORD2
42 | getLogger KEYWORD2
43 | prepareToSleep KEYWORD2
44 | doDeepSleep KEYWORD2
45 |
46 | # HomieNode
47 |
48 | getId KEYWORD2
49 | getType KEYWORD2
50 | advertise KEYWORD2
51 | advertiseRange KEYWORD2
52 | settable KEYWORD2
53 | setProperty KEYWORD2
54 |
55 | # HomieSetting
56 |
57 | get KEYWORD2
58 | wasProvided KEYWORD2
59 | setDefaultValue KEYWORD2
60 | setValidator KEYWORD2
61 |
62 | # HomieRange
63 |
64 | isRange KEYWORD2
65 | index KEYWORD2
66 |
67 | # SendingPromise
68 |
69 | setQos KEYWORD2
70 | setRetained KEYWORD2
71 | setRange KEYWORD2
72 | send KEYWORD2
73 |
74 | #######################################
75 | # Constants (LITERAL1)
76 | #######################################
77 |
78 | # HomieEventType
79 |
80 | STANDALONE_MODE LITERAL1
81 | CONFIGURATION_MODE LITERAL1
82 | NORMAL_MODE LITERAL1
83 | OTA_STARTED LITERAL1
84 | OTA_PROGRESS LITERAL1
85 | OTA_FAILED LITERAL1
86 | OTA_SUCCESSFUL LITERAL1
87 | ABOUT_TO_RESET LITERAL1
88 | WIFI_CONNECTED LITERAL1
89 | WIFI_DISCONNECTED LITERAL1
90 | MQTT_READY LITERAL1
91 | MQTT_DISCONNECTED LITERAL1
92 | MQTT_PACKET_ACKNOWLEDGED LITERAL1
93 | READY_TO_SLEEP LITERAL1
94 |
95 | # StreamingOperator
96 |
97 | endl LITERAL1
98 |
--------------------------------------------------------------------------------
/library.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Homie",
3 | "version": "3.0.1",
4 | "keywords": "iot, home, automation, mqtt, esp8266, async, sensor",
5 | "description": "ESP8266 framework for Homie, a lightweight MQTT convention for the IoT",
6 | "homepage": "https://homieiot.github.io/homie-esp8266/",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Marvin Roger",
11 | "url": "https://www.marvinroger.fr",
12 | "maintainer": true
13 | },
14 | {
15 | "name": "Ian Hubbertz",
16 | "url": "https://github.com/euphi",
17 | "maintainer": true
18 | }
19 | ],
20 | "repository":
21 | {
22 | "type": "git",
23 | "url": "https://github.com/homieiot/homie-esp8266.git"
24 | },
25 | "frameworks": "arduino",
26 | "platforms": [
27 | "espressif8266",
28 | "espressif32"
29 | ],
30 | "dependencies": [
31 | {
32 | "name": "ArduinoJson",
33 | "version": "~6.11.4"
34 | },
35 | {
36 | "name": "AsyncMqttClient",
37 | "version": "^0.8.0"
38 | },
39 | {
40 | "name": "Bounce2",
41 | "version": "^2.1.0"
42 | },
43 | {
44 | "name": "ESP Async WebServer"
45 | }
46 | ],
47 | "export": {
48 | "include": [
49 | "LICENSE",
50 | "keywords.txt",
51 | "src/*",
52 | "examples/*"
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=Homie
2 | version=3.0.0
3 | author=Marvin Roger
4 | maintainer=Marvin Roger
5 | sentence=ESP32/ESP8266 framework for Homie, a lightweight MQTT convention for the IoT
6 | paragraph=Like this project? Please star it on GitHub!
7 | category=Device Control
8 | url=https://github.com/homieiot/homie-esp8266
9 | architectures=esp32,esp8266
10 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Homie for ESP8266
2 | site_description: The Homie for ESP8266 documentation.
3 | site_author: Marvin ROGER
4 |
5 | repo_name: "homieiot/homie-esp8266"
6 | repo_url: "https://github.com/homieiot/homie-esp8266"
7 |
8 | edit_uri: edit/develop/docs
9 |
10 | nav:
11 | - Welcome: index.md
12 | - Quickstart:
13 | - What is it?: quickstart/what-is-it.md
14 | - Getting started: quickstart/getting-started.md
15 | - Advanced usage:
16 | - Built-in LED: advanced-usage/built-in-led.md
17 | - Branding: advanced-usage/branding.md
18 | - Events: advanced-usage/events.md
19 | - Logging: advanced-usage/logging.md
20 | - Streaming operator: advanced-usage/streaming-operator.md
21 | - Input handlers: advanced-usage/input-handlers.md
22 | - Broadcast: advanced-usage/broadcast.md
23 | - Custom settings: advanced-usage/custom-settings.md
24 | - Resetting: advanced-usage/resetting.md
25 | - Compiler-Flags: advanced-usage/compiler-flags.md
26 | - Standalone mode: advanced-usage/standalone-mode.md
27 | - Magic bytes: advanced-usage/magic-bytes.md
28 | - Range properties: advanced-usage/range-properties.md
29 | - Deep sleep: advanced-usage/deep-sleep.md
30 | - Miscellaneous: advanced-usage/miscellaneous.md
31 | - UI Bundle: advanced-usage/ui-bundle.md
32 | - Configuration:
33 | - JSON configuration file: configuration/json-configuration-file.md
34 | - HTTP JSON API: configuration/http-json-api.md
35 | - Others:
36 | - OTA/configuration updates: others/ota-configuration-updates.md
37 | - Homie implementation specifics: others/homie-implementation-specifics.md
38 | - Limitations and known issues: others/limitations-and-known-issues.md
39 | - Troubleshooting: others/troubleshooting.md
40 | - C++ API reference: others/cpp-api-reference.md
41 | - Upgrade guide from v1 to v2: others/upgrade-guide-from-v1-to-v2.md
42 | - Upgrade guide from v2 to v3: others/upgrade-guide-from-v2-to-v3.md
43 | - Community projects: others/community-projects.md
44 |
45 | theme:
46 | name: material
47 | palette:
48 | primary: red
49 | accent: red
50 | logo: assets/logo.png
51 | feature:
52 | tabs: true
53 |
54 | extra:
55 | social:
56 | - type: cog
57 | link: http://homieiot.github.io/homie-esp8266/configurators/v2/
58 |
59 | markdown_extensions:
60 | - meta
61 | - footnotes
62 | - codehilite
63 | - admonition
64 | - toc:
65 | permalink: true
66 | - pymdownx.arithmatex
67 | - pymdownx.betterem:
68 | smart_enable: all
69 | - pymdownx.caret
70 | - pymdownx.critic
71 | - pymdownx.details
72 | - pymdownx.emoji:
73 | emoji_generator: !!python/name:pymdownx.emoji.to_svg
74 | - pymdownx.inlinehilite
75 | - pymdownx.magiclink
76 | - pymdownx.mark
77 | - pymdownx.smartsymbols
78 | - pymdownx.superfences
79 | - pymdownx.tasklist:
80 | custom_checkbox: true
81 | - pymdownx.tilde
82 |
--------------------------------------------------------------------------------
/scripts/firmware_parser/README.md:
--------------------------------------------------------------------------------
1 | Script: Firmware parser
2 | =======================
3 |
4 | This will allow you to get information about the binary firmware file.
5 |
6 | ## Usage
7 |
8 | `python ./firmware_parser.py ~/firmware.bin`
9 |
--------------------------------------------------------------------------------
/scripts/firmware_parser/firmware_parser.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import re
4 | import sys
5 |
6 | if len(sys.argv) != 2:
7 | print("Please specify a file")
8 | sys.exit(1)
9 |
10 | regex_homie = re.compile(b"\x25\x48\x4f\x4d\x49\x45\x5f\x45\x53\x50\x38\x32\x36\x36\x5f\x46\x57\x25")
11 | regex_name = re.compile(b"\xbf\x84\xe4\x13\x54(.+)\x93\x44\x6b\xa7\x75")
12 | regex_version = re.compile(b"\x6a\x3f\x3e\x0e\xe1(.+)\xb0\x30\x48\xd4\x1a")
13 | regex_brand = re.compile(b"\xfb\x2a\xf5\x68\xc0(.+)\x6e\x2f\x0f\xeb\x2d")
14 |
15 | try:
16 | firmware_file = open(sys.argv[1], "rb")
17 | except Exception as err:
18 | print("Error: {0}".format(err.strerror))
19 | sys.exit(2)
20 |
21 | firmware_binary = firmware_file.read()
22 | firmware_file.close()
23 |
24 | regex_name_result = regex_name.search(firmware_binary)
25 | regex_version_result = regex_version.search(firmware_binary)
26 |
27 | if not regex_homie.search(firmware_binary) or not regex_name_result or not regex_version_result:
28 | print("Not a valid Homie firmware")
29 | sys.exit(3)
30 |
31 |
32 | regex_brand_result = regex_brand.search(firmware_binary)
33 |
34 | name = regex_name_result.group(1).decode()
35 | version = regex_version_result.group(1).decode()
36 | brand = regex_brand_result.group(1).decode() if regex_brand_result else "unset (default is Homie)"
37 |
38 | print("Name: {0}".format(name))
39 | print("Version: {0}".format(version))
40 | print("Brand: {0}".format(brand))
41 |
--------------------------------------------------------------------------------
/scripts/ota_updater/README.md:
--------------------------------------------------------------------------------
1 | Script: OTA updater
2 | ===================
3 |
4 | This script will allow you to send an OTA update to your device.
5 |
6 | ## Installation
7 |
8 | `pip install -r requirements.txt`
9 |
10 | ## Usage
11 |
12 | ```text
13 | usage: ota_updater.py [-h] -l BROKER_HOST -p BROKER_PORT [-u BROKER_USERNAME]
14 | [-d BROKER_PASSWORD] [-t BASE_TOPIC] -i DEVICE_ID
15 | firmware
16 |
17 | ota firmware update scirpt for ESP8226 implemenation of the Homie mqtt IoT
18 | convention.
19 |
20 | positional arguments:
21 | firmware path to the firmware to be sent to the device
22 |
23 | arguments:
24 | -h, --help show this help message and exit
25 | -l BROKER_HOST, --broker-host BROKER_HOST
26 | host name or ip address of the mqtt broker
27 | -p BROKER_PORT, --broker-port BROKER_PORT
28 | port of the mqtt broker
29 | -u BROKER_USERNAME, --broker-username BROKER_USERNAME
30 | username used to authenticate with the mqtt broker
31 | -d BROKER_PASSWORD, --broker-password BROKER_PASSWORD
32 | password used to authenticate with the mqtt broker
33 | -t BASE_TOPIC, --base-topic BASE_TOPIC
34 | base topic of the homie devices on the broker
35 | -i DEVICE_ID, --device-id DEVICE_ID
36 | homie device id
37 | ```
38 |
39 | * `BROKER_HOST` and `BROKER_PORT` defaults to 127.0.0.1 and 1883 respectively if not set.
40 | * `BROKER_USERNAME` and `BROKER_PASSWORD` are optional.
41 | * `BASE_TOPIC` has to end with a slash, defaults to `homie/` if not set.
42 |
43 | ### Example:
44 |
45 | ```bash
46 | python ota_updater.py -l localhost -u admin -d secure -t "homie/" -i "device-id" /path/to/firmware.bin
47 | ```
48 |
49 |
--------------------------------------------------------------------------------
/scripts/ota_updater/ota_updater.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from __future__ import division, print_function
4 | import paho.mqtt.client as mqtt
5 | import base64, sys, math
6 | from hashlib import md5
7 |
8 | # Global variable for total bytes to transfer
9 | total = 0
10 |
11 | # The callback for when the client receives a CONNACK response from the server.
12 | def on_connect(client, userdata, flags, rc):
13 | if rc != 0:
14 | print("Connection Failed with result code {}".format(rc))
15 | client.disconnect()
16 | else:
17 | print("Connected with result code {}".format(rc))
18 |
19 | client.subscribe("{base_topic}{device_id}/$state".format(**userdata)) # v3 / v4 devices
20 | client.subscribe("{base_topic}{device_id}/$online".format(**userdata)) # v2 devices
21 |
22 |
23 | print("Waiting for device to come online...")
24 |
25 | # Called from on_message to print a progress bar
26 | def on_progress(progress, total):
27 | g_total = total
28 | bar_width = 30
29 | bar = int(bar_width*(progress/total))
30 | print("\r[", '+'*bar, ' '*(bar_width-bar), "] ", progress, end='', sep='')
31 | if (progress == total):
32 | print()
33 | sys.stdout.flush()
34 |
35 | # The callback for when a PUBLISH message is received from the server.
36 | def on_message(client, userdata, msg):
37 | global total
38 | # decode string for python2/3 compatiblity
39 | msg.payload = msg.payload.decode()
40 |
41 | if msg.topic.endswith('$implementation/ota/status'):
42 | status = int(msg.payload.split()[0])
43 |
44 | if userdata.get("published"):
45 | if status == 200:
46 | on_progress(total, total)
47 | print("Firmware uploaded successfully. Waiting for device to come back online.")
48 | sys.stdout.flush()
49 | elif status == 202:
50 | print("Checksum accepted")
51 | elif status == 206: # in progress
52 | # state in progress, print progress bar
53 | progress, total = [int(x) for x in msg.payload.split()[1].split('/')]
54 | on_progress(progress, total)
55 | elif status == 304: # not modified
56 | print("Device firmware already up to date with md5 checksum: {}".format(userdata.get('md5')))
57 | client.disconnect()
58 | elif status == 403: # forbidden
59 | print("Device ota disabled, aborting...")
60 | client.disconnect()
61 | elif (status > 300) and (status < 500):
62 | print("Other error '" + msg.payload + "', aborting...")
63 | client.disconnect()
64 | else:
65 | print("Other error '" + msg.payload + "'")
66 |
67 | elif msg.topic.endswith('$fw/checksum'):
68 | checksum = msg.payload
69 |
70 | if userdata.get("published"):
71 | if checksum == userdata.get('md5'):
72 | print("Device back online. Update Successful!")
73 | else:
74 | print("Expecting checksum {}, got {}, update failed!".format(userdata.get('md5'), checksum))
75 | client.disconnect()
76 | else:
77 | if checksum != userdata.get('md5'): # save old md5 for comparison with new firmware
78 | userdata.update({'old_md5': checksum})
79 | else:
80 | print("Device firmware already up to date with md5 checksum: {}".format(checksum))
81 | client.disconnect()
82 |
83 | elif msg.topic.endswith('ota/enabled'):
84 | if msg.payload == 'true':
85 | userdata.update({'ota_enabled': True})
86 | else:
87 | print("Device ota disabled, aborting...")
88 | client.disconnect()
89 |
90 | elif msg.topic.endswith('$state') or msg.topic.endswith('$online'):
91 | if (msg.topic.endswith('$state') and msg.payload != 'ready') or (msg.topic.endswith('$online') and msg.payload == 'false'):
92 | return
93 |
94 | # calcluate firmware md5
95 | firmware_md5 = md5(userdata['firmware']).hexdigest()
96 | userdata.update({'md5': firmware_md5})
97 |
98 | # Subscribing in on_connect() means that if we lose the connection and
99 | # reconnect then subscriptions will be renewed.
100 | client.subscribe("{base_topic}{device_id}/$implementation/ota/status".format(**userdata))
101 | client.subscribe("{base_topic}{device_id}/$implementation/ota/enabled".format(**userdata))
102 | client.subscribe("{base_topic}{device_id}/$fw/#".format(**userdata))
103 |
104 | # Wait for device info to come in and invoke the on_message callback where update will continue
105 | print("Waiting for device info...")
106 |
107 | if ( not userdata.get("published") ) and ( userdata.get('ota_enabled') ) and \
108 | ( 'old_md5' in userdata.keys() ) and ( userdata.get('md5') != userdata.get('old_md5') ):
109 | # push the firmware binary
110 | userdata.update({"published": True})
111 | topic = "{base_topic}{device_id}/$implementation/ota/firmware/{md5}".format(**userdata)
112 | print("Publishing new firmware with checksum {}".format(userdata.get('md5')))
113 | client.publish(topic, userdata['firmware'])
114 |
115 |
116 | def main(broker_host, broker_port, broker_username, broker_password, broker_ca_cert, base_topic, device_id, firmware):
117 | # initialise mqtt client and register callbacks
118 | client = mqtt.Client()
119 | client.on_connect = on_connect
120 | client.on_message = on_message
121 |
122 | # set username and password if given
123 | if broker_username and broker_password:
124 | client.username_pw_set(broker_username, broker_password)
125 |
126 | if broker_ca_cert is not None:
127 | client.tls_set(
128 | ca_certs=broker_ca_cert
129 | )
130 |
131 | # save data to be used in the callbacks
132 | client.user_data_set({
133 | "base_topic": base_topic,
134 | "device_id": device_id,
135 | "firmware": firmware
136 | })
137 |
138 | # start connection
139 | print("Connecting to mqtt broker {} on port {}".format(broker_host, broker_port))
140 | client.connect(broker_host, broker_port, 60)
141 |
142 | # Blocking call that processes network traffic, dispatches callbacks and handles reconnecting.
143 | client.loop_forever()
144 |
145 |
146 | if __name__ == '__main__':
147 | import argparse
148 |
149 | print (sys.argv[1:])
150 |
151 | parser = argparse.ArgumentParser(
152 | description='ota firmware update scirpt for ESP8226 implemenation of the Homie mqtt IoT convention.')
153 |
154 | # ensure base topic always ends with a '/'
155 | def base_topic_arg(s):
156 | s = str(s)
157 | if not s.endswith('/'):
158 | s = s + '/'
159 | return s
160 |
161 | # specify arguments
162 | parser.add_argument('-l', '--broker-host', type=str, required=False,
163 | help='host name or ip address of the mqtt broker', default="127.0.0.1")
164 | parser.add_argument('-p', '--broker-port', type=int, required=False,
165 | help='port of the mqtt broker', default=1883)
166 | parser.add_argument('-u', '--broker-username', type=str, required=False,
167 | help='username used to authenticate with the mqtt broker')
168 | parser.add_argument('-d', '--broker-password', type=str, required=False,
169 | help='password used to authenticate with the mqtt broker')
170 | parser.add_argument('-t', '--base-topic', type=base_topic_arg, required=False,
171 | help='base topic of the homie devices on the broker', default="homie/")
172 | parser.add_argument('-i', '--device-id', type=str, required=True,
173 | help='homie device id')
174 | parser.add_argument('firmware', type=argparse.FileType('rb'),
175 | help='path to the firmware to be sent to the device')
176 |
177 | parser.add_argument("--broker-tls-cacert", default=None, required=False,
178 | help="CA certificate bundle used to validate TLS connections. If set, TLS will be enabled on the broker conncetion"
179 | )
180 |
181 | # workaround for http://bugs.python.org/issue9694
182 | parser._optionals.title = "arguments"
183 |
184 | # get and validate arguments
185 | args = parser.parse_args()
186 |
187 | # read the contents of firmware into buffer
188 | fw_buffer = args.firmware.read()
189 | args.firmware.close()
190 | firmware = bytearray()
191 | firmware.extend(fw_buffer)
192 |
193 | # Invoke the business logic
194 | main(args.broker_host, args.broker_port, args.broker_username,
195 | args.broker_password, args.broker_tls_cacert, args.base_topic, args.device_id, firmware)
196 |
--------------------------------------------------------------------------------
/scripts/ota_updater/requirements.txt:
--------------------------------------------------------------------------------
1 | paho-mqtt >1.2.3,<=1.3.0
2 |
--------------------------------------------------------------------------------
/src/Homie.cpp:
--------------------------------------------------------------------------------
1 | #include "Homie.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | HomieClass::HomieClass()
6 | : _setupCalled(false)
7 | , _firmwareSet(false)
8 | , __HOMIE_SIGNATURE("\x25\x48\x4f\x4d\x49\x45\x5f\x45\x53\x50\x38\x32\x36\x36\x5f\x46\x57\x25") {
9 | strlcpy(Interface::get().brand, DEFAULT_BRAND, MAX_BRAND_LENGTH);
10 | Interface::get().bootMode = HomieBootMode::UNDEFINED;
11 | Interface::get().configurationAp.secured = false;
12 | #ifdef ESP32
13 | Interface::get().led.enabled = false;
14 | #ifdef LED_BUILTIN
15 | Interface::get().led.pin = LED_BUILTIN;
16 | #endif // LED_BUILTIN
17 | Interface::get().led.on = LOW;
18 | #elif defined(ESP8266)
19 | Interface::get().led.enabled = true;
20 | Interface::get().led.pin = LED_BUILTIN;
21 | Interface::get().led.on = LOW;
22 | #endif // ESP32
23 | Interface::get().reset.idle = true;
24 | Interface::get().reset.enabled = true;
25 | Interface::get().reset.triggerPin = DEFAULT_RESET_PIN;
26 | Interface::get().reset.triggerState = DEFAULT_RESET_STATE;
27 | Interface::get().reset.triggerTime = DEFAULT_RESET_TIME;
28 | Interface::get().reset.resetFlag = false;
29 | Interface::get().disable = false;
30 | Interface::get().flaggedForSleep = false;
31 | Interface::get().globalInputHandler = [](const HomieNode& node, const HomieRange& range, const String& property, const String& value) { return false; };
32 | Interface::get().broadcastHandler = [](const String& level, const String& value) { return false; };
33 | Interface::get().setupFunction = []() {};
34 | Interface::get().loopFunction = []() {};
35 | Interface::get().eventHandler = [](const HomieEvent& event) {};
36 | Interface::get().ready = false;
37 | Interface::get()._mqttClient = &_mqttClient;
38 | Interface::get()._sendingPromise = &_sendingPromise;
39 | Interface::get()._blinker = &_blinker;
40 | Interface::get()._logger = &_logger;
41 | Interface::get()._config = &_config;
42 |
43 | DeviceId::generate();
44 | }
45 |
46 | HomieClass::~HomieClass() {
47 | }
48 |
49 | void HomieClass::_checkBeforeSetup(const __FlashStringHelper* functionName) const {
50 | if (_setupCalled) {
51 | String message;
52 | message.concat(F("✖ "));
53 | message.concat(functionName);
54 | message.concat(F("(): has to be called before setup()"));
55 | Helpers::abort(message);
56 | }
57 | }
58 |
59 | void HomieClass::setup() {
60 | _setupCalled = true;
61 |
62 | // Check if firmware is set
63 | if (!_firmwareSet) {
64 | Helpers::abort(F("✖ Firmware name must be set before calling setup()"));
65 | return; // never reached, here for clarity
66 | }
67 |
68 | // Check the max allowed setting elements
69 | if (IHomieSetting::settings.size() > MAX_CONFIG_SETTING_SIZE) {
70 | Helpers::abort(F("✖ Settings exceed set limit of elelement."));
71 | return; // never reached, here for clarity
72 | }
73 |
74 | // Check if default settings values are valid
75 | bool defaultSettingsValuesValid = true;
76 | for (IHomieSetting* iSetting : IHomieSetting::settings) {
77 | if (iSetting->isBool()) {
78 | HomieSetting* setting = static_cast*>(iSetting);
79 | if (!setting->isRequired() && !setting->validate(setting->get())) {
80 | defaultSettingsValuesValid = false;
81 | break;
82 | }
83 | } else if (iSetting->isLong()) {
84 | HomieSetting* setting = static_cast*>(iSetting);
85 | if (!setting->isRequired() && !setting->validate(setting->get())) {
86 | defaultSettingsValuesValid = false;
87 | break;
88 | }
89 | } else if (iSetting->isDouble()) {
90 | HomieSetting* setting = static_cast*>(iSetting);
91 | if (!setting->isRequired() && !setting->validate(setting->get())) {
92 | defaultSettingsValuesValid = false;
93 | break;
94 | }
95 | } else if (iSetting->isConstChar()) {
96 | HomieSetting* setting = static_cast*>(iSetting);
97 | if (!setting->isRequired() && !setting->validate(setting->get())) {
98 | defaultSettingsValuesValid = false;
99 | break;
100 | }
101 | }
102 | }
103 |
104 | if (!defaultSettingsValuesValid) {
105 | Helpers::abort(F("✖ Default setting value does not pass validator test"));
106 | return; // never reached, here for clarity
107 | }
108 |
109 | // boot mode set during this boot by application before Homie.setup()
110 | HomieBootMode _applicationHomieBootMode = Interface::get().bootMode;
111 |
112 | // boot mode set before resetting the device. If application has defined a boot mode, this will be ignored
113 | HomieBootMode _nextHomieBootMode = Interface::get().getConfig().getHomieBootModeOnNextBoot();
114 | if (_nextHomieBootMode != HomieBootMode::UNDEFINED) {
115 | Interface::get().getConfig().setHomieBootModeOnNextBoot(HomieBootMode::UNDEFINED);
116 | }
117 |
118 | #if HOMIE_CONFIG
119 | HomieBootMode _selectedHomieBootMode = HomieBootMode::CONFIGURATION;
120 | #else
121 | HomieBootMode _selectedHomieBootMode = HomieBootMode::NORMAL;
122 | #endif
123 |
124 | // select boot mode source
125 | if (_applicationHomieBootMode != HomieBootMode::UNDEFINED) {
126 | _selectedHomieBootMode = _applicationHomieBootMode;
127 | } else if (_nextHomieBootMode != HomieBootMode::UNDEFINED) {
128 | _selectedHomieBootMode = _nextHomieBootMode;
129 | } else {
130 | _selectedHomieBootMode = HomieBootMode::NORMAL;
131 | }
132 |
133 | // load and check config file
134 | bool isConfigured = Interface::get().getConfig().load();
135 |
136 | // validate selected mode and fallback as needed
137 | if (_selectedHomieBootMode == HomieBootMode::NORMAL && !isConfigured) {
138 | #if HOMIE_CONFIG
139 | Interface::get().getLogger() << F("Configuration invalid. Using CONFIG MODE") << endl;
140 | _selectedHomieBootMode = HomieBootMode::CONFIGURATION;
141 | #else
142 | Interface::get().getLogger() << F("Configuration invalid. CONFIG MODE is disabled.") << endl;
143 | ESP.restart();
144 | #endif
145 | }
146 |
147 | // run selected mode
148 | if (_selectedHomieBootMode == HomieBootMode::NORMAL) {
149 | _boot = &_bootNormal;
150 | Interface::get().event.type = HomieEventType::NORMAL_MODE;
151 | Interface::get().eventHandler(Interface::get().event);
152 | #if HOMIE_CONFIG
153 | } else if (_selectedHomieBootMode == HomieBootMode::CONFIGURATION) {
154 | _boot = &_bootConfig;
155 | Interface::get().event.type = HomieEventType::CONFIGURATION_MODE;
156 | Interface::get().eventHandler(Interface::get().event);
157 | #endif
158 | } else if (_selectedHomieBootMode == HomieBootMode::STANDALONE) {
159 | _boot = &_bootStandalone;
160 | Interface::get().event.type = HomieEventType::STANDALONE_MODE;
161 | Interface::get().eventHandler(Interface::get().event);
162 | } else {
163 | Helpers::abort(F("✖ Boot mode invalid"));
164 | return; // never reached, here for clarity
165 | }
166 |
167 | WiFi.disconnect(); // workaround for issue #351
168 |
169 | _boot->setup();
170 | }
171 |
172 | void HomieClass::loop() {
173 | _boot->loop();
174 |
175 | if (_flaggedForReboot && Interface::get().reset.idle) {
176 | Interface::get().getLogger() << F("Device is idle") << endl;
177 | Interface::get().getLogger() << F("Triggering ABOUT_TO_RESET event...") << endl;
178 | Interface::get().event.type = HomieEventType::ABOUT_TO_RESET;
179 | Interface::get().eventHandler(Interface::get().event);
180 |
181 | Interface::get().getLogger() << F("↻ Rebooting device...") << endl;
182 | Serial.flush();
183 | ESP.restart();
184 | }
185 | }
186 |
187 | HomieClass& HomieClass::disableLogging() {
188 | _checkBeforeSetup(F("disableLogging"));
189 |
190 | Interface::get().getLogger().setLogging(false);
191 |
192 | return *this;
193 | }
194 |
195 | HomieClass& HomieClass::setLoggingPrinter(Print* printer) {
196 | _checkBeforeSetup(F("setLoggingPrinter"));
197 |
198 | Interface::get().getLogger().setPrinter(printer);
199 |
200 | return *this;
201 | }
202 |
203 | HomieClass& HomieClass::disableLedFeedback() {
204 | _checkBeforeSetup(F("disableLedFeedback"));
205 |
206 | Interface::get().led.enabled = false;
207 |
208 | return *this;
209 | }
210 |
211 | HomieClass& HomieClass::setLedPin(uint8_t pin, uint8_t on) {
212 | _checkBeforeSetup(F("setLedPin"));
213 |
214 | Interface::get().led.pin = pin;
215 | Interface::get().led.on = on;
216 | Interface::get().led.enabled = true;
217 |
218 | return *this;
219 | }
220 |
221 | HomieClass& HomieClass::setConfigurationApPassword(const char* password) {
222 | _checkBeforeSetup(F("setConfigurationApPassword"));
223 |
224 | Interface::get().configurationAp.secured = true;
225 | strlcpy(Interface::get().configurationAp.password, password, MAX_WIFI_PASSWORD_LENGTH);
226 | return *this;
227 | }
228 |
229 | void HomieClass::__setFirmware(const char* name, const char* version) {
230 | _checkBeforeSetup(F("setFirmware"));
231 | if (strlen(name) + 1 - 10 > MAX_FIRMWARE_NAME_LENGTH || strlen(version) + 1 - 10 > MAX_FIRMWARE_VERSION_LENGTH) {
232 | Helpers::abort(F("✖ setFirmware(): either the name or version string is too long"));
233 | return; // never reached, here for clarity
234 | }
235 |
236 | strncpy(Interface::get().firmware.name, name + 5, strlen(name) - 10);
237 | Interface::get().firmware.name[strlen(name) - 10] = '\0';
238 | strncpy(Interface::get().firmware.version, version + 5, strlen(version) - 10);
239 | Interface::get().firmware.version[strlen(version) - 10] = '\0';
240 | _firmwareSet = true;
241 | }
242 |
243 | void HomieClass::__setBrand(const char* brand) const {
244 | _checkBeforeSetup(F("setBrand"));
245 | if (strlen(brand) + 1 - 10 > MAX_BRAND_LENGTH) {
246 | Helpers::abort(F("✖ setBrand(): the brand string is too long"));
247 | return; // never reached, here for clarity
248 | }
249 |
250 | strncpy(Interface::get().brand, brand + 5, strlen(brand) - 10);
251 | Interface::get().brand[strlen(brand) - 10] = '\0';
252 | }
253 |
254 | void HomieClass::reset() {
255 | Interface::get().getLogger() << F("Flagged for reset by sketch") << endl;
256 | Interface::get().disable = true;
257 | Interface::get().reset.resetFlag = true;
258 | }
259 |
260 | void HomieClass::reboot() {
261 | Interface::get().getLogger() << F("Flagged for reboot by sketch") << endl;
262 | Interface::get().disable = true;
263 | _flaggedForReboot = true;
264 | }
265 |
266 | void HomieClass::setIdle(bool idle) {
267 | Interface::get().reset.idle = idle;
268 | }
269 |
270 | HomieClass& HomieClass::setGlobalInputHandler(const GlobalInputHandler& globalInputHandler) {
271 | _checkBeforeSetup(F("setGlobalInputHandler"));
272 |
273 | Interface::get().globalInputHandler = globalInputHandler;
274 |
275 | return *this;
276 | }
277 |
278 | HomieClass& HomieClass::setBroadcastHandler(const BroadcastHandler& broadcastHandler) {
279 | _checkBeforeSetup(F("setBroadcastHandler"));
280 |
281 | Interface::get().broadcastHandler = broadcastHandler;
282 |
283 | return *this;
284 | }
285 |
286 | HomieClass& HomieClass::setSetupFunction(const OperationFunction& function) {
287 | _checkBeforeSetup(F("setSetupFunction"));
288 |
289 | Interface::get().setupFunction = function;
290 |
291 | return *this;
292 | }
293 |
294 | HomieClass& HomieClass::setLoopFunction(const OperationFunction& function) {
295 | _checkBeforeSetup(F("setLoopFunction"));
296 |
297 | Interface::get().loopFunction = function;
298 |
299 | return *this;
300 | }
301 |
302 | HomieClass& HomieClass::setHomieBootMode(HomieBootMode bootMode) {
303 | _checkBeforeSetup(F("setHomieBootMode"));
304 | Interface::get().bootMode = bootMode;
305 | return *this;
306 | }
307 |
308 | HomieClass& HomieClass::setHomieBootModeOnNextBoot(HomieBootMode bootMode) {
309 | Interface::get().getConfig().setHomieBootModeOnNextBoot(bootMode);
310 | return *this;
311 | }
312 |
313 | bool HomieClass::isConfigured() {
314 | return Interface::get().getConfig().isValid();
315 | }
316 |
317 | bool HomieClass::isConnected() {
318 | return Interface::get().ready;
319 | }
320 |
321 | HomieClass& HomieClass::onEvent(const EventHandler& handler) {
322 | _checkBeforeSetup(F("onEvent"));
323 |
324 | Interface::get().eventHandler = handler;
325 |
326 | return *this;
327 | }
328 |
329 | HomieClass& HomieClass::setResetTrigger(uint8_t pin, uint8_t state, uint16_t time) {
330 | _checkBeforeSetup(F("setResetTrigger"));
331 |
332 | Interface::get().reset.enabled = true;
333 | Interface::get().reset.triggerPin = pin;
334 | Interface::get().reset.triggerState = state;
335 | Interface::get().reset.triggerTime = time;
336 |
337 | return *this;
338 | }
339 |
340 | HomieClass& HomieClass::disableResetTrigger() {
341 | _checkBeforeSetup(F("disableResetTrigger"));
342 |
343 | Interface::get().reset.enabled = false;
344 |
345 | return *this;
346 | }
347 |
348 | const ConfigStruct& HomieClass::getConfiguration() {
349 | return Interface::get().getConfig().get();
350 | }
351 |
352 | AsyncMqttClient& HomieClass::getMqttClient() {
353 | return _mqttClient;
354 | }
355 |
356 | Logger& HomieClass::getLogger() {
357 | return _logger;
358 | }
359 |
360 | void HomieClass::prepareToSleep() {
361 | Interface::get().getLogger() << F("Flagged for sleep by sketch") << endl;
362 | if (Interface::get().ready) {
363 | Interface::get().disable = true;
364 | Interface::get().flaggedForSleep = true;
365 | } else {
366 | Interface::get().disable = true;
367 | Interface::get().getLogger() << F("Triggering READY_TO_SLEEP event...") << endl;
368 | Interface::get().event.type = HomieEventType::READY_TO_SLEEP;
369 | Interface::get().eventHandler(Interface::get().event);
370 | }
371 | }
372 |
373 | #ifdef ESP32
374 | void HomieClass::doDeepSleep(uint64_t time_us) {
375 | Interface::get().getLogger() << F("💤 Device is deep sleeping...") << endl;
376 | Serial.flush();
377 |
378 | esp_sleep_enable_timer_wakeup(time_us);
379 |
380 | esp_deep_sleep_start();
381 | }
382 | void HomieClass::doDeepSleep(gpio_num_t wakeupPin, int logicLevel) {
383 | Interface::get().getLogger() << F("💤 Device is deep sleeping...") << endl;
384 | Serial.flush();
385 |
386 | esp_sleep_enable_ext0_wakeup(wakeupPin, logicLevel);
387 |
388 | esp_deep_sleep_start();
389 | }
390 | void HomieClass::doDeepSleep(uint64_t pinMask, esp_sleep_ext1_wakeup_mode_t mode) {
391 | Interface::get().getLogger() << F("💤 Device is deep sleeping...") << endl;
392 | Serial.flush();
393 |
394 | esp_sleep_enable_ext1_wakeup(pinMask, mode);
395 |
396 | esp_deep_sleep_start();
397 | }
398 | #elif defined(ESP8266)
399 | void HomieClass::doDeepSleep(uint64_t time_us, RFMode mode) {
400 | Interface::get().getLogger() << F("💤 Device is deep sleeping...") << endl;
401 | Serial.flush();
402 | ESP.deepSleep(time_us, mode);
403 | }
404 | #endif // ESP32
405 |
406 |
407 | HomieClass Homie;
408 |
--------------------------------------------------------------------------------
/src/Homie.h:
--------------------------------------------------------------------------------
1 | #ifndef SRC_HOMIE_H_
2 | #define SRC_HOMIE_H_
3 |
4 | #include "Homie.hpp"
5 |
6 | #endif // SRC_HOMIE_H_
7 |
--------------------------------------------------------------------------------
/src/Homie.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | #include "AsyncMqttClient.h"
6 | #include "Homie/Datatypes/Interface.hpp"
7 | #include "Homie/Constants.hpp"
8 | #include "Homie/Limits.hpp"
9 | #include "Homie/Utils/DeviceId.hpp"
10 | #include "Homie/Boot/Boot.hpp"
11 | #include "Homie/Boot/BootStandalone.hpp"
12 | #include "Homie/Boot/BootNormal.hpp"
13 | #include "Homie/Boot/BootConfig.hpp"
14 | #include "Homie/Logger.hpp"
15 | #include "Homie/Config.hpp"
16 | #include "Homie/Blinker.hpp"
17 |
18 | #include "SendingPromise.hpp"
19 | #include "HomieBootMode.hpp"
20 | #include "HomieEvent.hpp"
21 | #include "HomieNode.hpp"
22 | #include "HomieSetting.hpp"
23 | #include "StreamingOperator.hpp"
24 |
25 | // Define DEBUG for debug
26 |
27 | #define Homie_setFirmware(name, version) const char* __FLAGGED_FW_NAME = "\xbf\x84\xe4\x13\x54" name "\x93\x44\x6b\xa7\x75"; const char* __FLAGGED_FW_VERSION = "\x6a\x3f\x3e\x0e\xe1" version "\xb0\x30\x48\xd4\x1a"; Homie.__setFirmware(__FLAGGED_FW_NAME, __FLAGGED_FW_VERSION);
28 | #define Homie_setBrand(brand) const char* __FLAGGED_BRAND = "\xfb\x2a\xf5\x68\xc0" brand "\x6e\x2f\x0f\xeb\x2d"; Homie.__setBrand(__FLAGGED_BRAND);
29 |
30 | namespace HomieInternals {
31 | class HomieClass {
32 | friend class ::HomieNode;
33 | friend SendingPromise;
34 |
35 | public:
36 | HomieClass();
37 | ~HomieClass();
38 | void setup();
39 | void loop();
40 |
41 | void __setFirmware(const char* name, const char* version);
42 | void __setBrand(const char* brand) const;
43 |
44 | HomieClass& disableLogging();
45 | HomieClass& setLoggingPrinter(Print* printer);
46 | HomieClass& disableLedFeedback();
47 | HomieClass& setLedPin(uint8_t pin, uint8_t on);
48 | HomieClass& setConfigurationApPassword(const char* password);
49 | HomieClass& setGlobalInputHandler(const GlobalInputHandler& globalInputHandler);
50 | HomieClass& setBroadcastHandler(const BroadcastHandler& broadcastHandler);
51 | HomieClass& onEvent(const EventHandler& handler);
52 | HomieClass& setResetTrigger(uint8_t pin, uint8_t state, uint16_t time);
53 | HomieClass& disableResetTrigger();
54 | HomieClass& setSetupFunction(const OperationFunction& function);
55 | HomieClass& setLoopFunction(const OperationFunction& function);
56 | HomieClass& setHomieBootMode(HomieBootMode bootMode);
57 | HomieClass& setHomieBootModeOnNextBoot(HomieBootMode bootMode);
58 |
59 | static void reset();
60 | void reboot();
61 | static void setIdle(bool idle);
62 | static bool isConfigured();
63 | static bool isConnected();
64 | static const ConfigStruct& getConfiguration();
65 | AsyncMqttClient& getMqttClient();
66 | Logger& getLogger();
67 | static void prepareToSleep();
68 | #ifdef ESP32
69 | static void doDeepSleep(uint64_t time_us = 0);
70 | static void doDeepSleep(gpio_num_t wakeupPin, int logicLevel);
71 | static void doDeepSleep(uint64_t pinMask, esp_sleep_ext1_wakeup_mode_t mode);
72 | #elif defined(ESP8266)
73 | static void doDeepSleep(uint64_t time_us = 0, RFMode mode = RF_DEFAULT);
74 | #endif // ESP32
75 |
76 | private:
77 | bool _setupCalled;
78 | bool _firmwareSet;
79 | Boot* _boot;
80 | BootStandalone _bootStandalone;
81 | BootNormal _bootNormal;
82 | #if HOMIE_CONFIG
83 | BootConfig _bootConfig;
84 | #endif
85 | bool _flaggedForReboot;
86 | SendingPromise _sendingPromise;
87 | Logger _logger;
88 | Blinker _blinker;
89 | Config _config;
90 | AsyncMqttClient _mqttClient;
91 |
92 | void _checkBeforeSetup(const __FlashStringHelper* functionName) const;
93 |
94 | const char* __HOMIE_SIGNATURE;
95 | };
96 | } // namespace HomieInternals
97 |
98 | extern HomieInternals::HomieClass Homie;
99 |
--------------------------------------------------------------------------------
/src/Homie/Blinker.cpp:
--------------------------------------------------------------------------------
1 | #include "Blinker.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | Blinker::Blinker()
6 | : _lastBlinkPace(0) {
7 | }
8 |
9 | void Blinker::start(float blinkPace) {
10 | if (_lastBlinkPace != blinkPace) {
11 | _ticker.attach(blinkPace, _tick, Interface::get().led.pin);
12 | _lastBlinkPace = blinkPace;
13 | }
14 | }
15 |
16 | void Blinker::stop() {
17 | if (_lastBlinkPace != 0) {
18 | _ticker.detach();
19 | _lastBlinkPace = 0;
20 | digitalWrite(Interface::get().led.pin, !Interface::get().led.on);
21 | }
22 | }
23 |
24 | void Blinker::_tick(uint8_t pin) {
25 | digitalWrite(pin, !digitalRead(pin));
26 | }
27 |
--------------------------------------------------------------------------------
/src/Homie/Blinker.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "Datatypes/Interface.hpp"
5 |
6 | namespace HomieInternals {
7 | class Blinker {
8 | public:
9 | Blinker();
10 | void start(float blinkPace);
11 | void stop();
12 |
13 | private:
14 | Ticker _ticker;
15 | float _lastBlinkPace;
16 |
17 | static void _tick(uint8_t pin);
18 | };
19 | } // namespace HomieInternals
20 |
--------------------------------------------------------------------------------
/src/Homie/Boot/Boot.cpp:
--------------------------------------------------------------------------------
1 | #include "Boot.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | Boot::Boot(const char* name)
6 | : _name(name) {
7 | }
8 |
9 |
10 | void Boot::setup() {
11 | if (Interface::get().led.enabled) {
12 | pinMode(Interface::get().led.pin, OUTPUT);
13 | digitalWrite(Interface::get().led.pin, !Interface::get().led.on);
14 | }
15 |
16 | #ifdef ESP32
17 | WiFi.persistent(false); // Workaround for ESP32: Disable Wi-Fi persistence to prevent crashes on AP connect
18 | #else
19 | WiFi.persistent(true); // Persist data on SDK as it seems Wi-Fi connection is faster
20 | #endif //ESP32
21 |
22 | Interface::get().getLogger() << F("💡 Firmware ") << Interface::get().firmware.name << F(" (") << Interface::get().firmware.version << F(")") << endl;
23 | Interface::get().getLogger() << F("🔌 Booting into ") << _name << F(" mode 🔌") << endl;
24 | }
25 |
26 | void Boot::loop() {
27 | }
28 |
--------------------------------------------------------------------------------
/src/Homie/Boot/Boot.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | #ifdef ESP32
6 | #include
7 | #elif defined(ESP8266)
8 | #include
9 | #endif // ESP32
10 | #include "../../StreamingOperator.hpp"
11 | #include "../Datatypes/Interface.hpp"
12 | #include "../Constants.hpp"
13 | #include "../Limits.hpp"
14 | #include "../Utils/Helpers.hpp"
15 |
16 | namespace HomieInternals {
17 | class Boot {
18 | public:
19 | explicit Boot(const char* name);
20 | virtual void setup();
21 | virtual void loop();
22 |
23 | protected:
24 | const char* _name;
25 | };
26 | } // namespace HomieInternals
27 |
--------------------------------------------------------------------------------
/src/Homie/Boot/BootConfig.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | #include "../Constants.hpp"
6 | #if HOMIE_CONFIG
7 |
8 | #include
9 | #ifdef ESP32
10 | #include
11 | #include
12 | #include
13 | #include
14 | #elif defined(ESP8266)
15 | #include
16 | #include
17 | #include
18 | #endif // ESP32
19 | #include
20 | #include
21 | #include
22 | #include "Boot.hpp"
23 | #include "../Limits.hpp"
24 | #include "../Datatypes/Interface.hpp"
25 | #include "../Timer.hpp"
26 | #include "../Utils/DeviceId.hpp"
27 | #include "../Utils/Validation.hpp"
28 | #include "../Utils/Helpers.hpp"
29 | #include "../Logger.hpp"
30 | #include "../Strings.hpp"
31 | #include "../../HomieSetting.hpp"
32 | #include "../../StreamingOperator.hpp"
33 |
34 | namespace HomieInternals {
35 | class BootConfig : public Boot {
36 | public:
37 | BootConfig();
38 | ~BootConfig();
39 | void setup();
40 | void loop();
41 |
42 | private:
43 | AsyncWebServer _http;
44 | HTTPClient _httpClient;
45 | WiFiClient _wifiClient;
46 | DNSServer _dns;
47 | uint8_t _ssidCount;
48 | bool _wifiScanAvailable;
49 | Timer _wifiScanTimer;
50 | bool _lastWifiScanEnded;
51 | String _jsonWifiNetworks;
52 | bool _flaggedForReboot;
53 | uint32_t _flaggedForRebootAt;
54 | bool _proxyEnabled;
55 | char _apIpStr[MAX_IP_STRING_LENGTH];
56 |
57 | void _onCaptivePortal(AsyncWebServerRequest *request);
58 | void _onDeviceInfoRequest(AsyncWebServerRequest *request);
59 | void _onNetworksRequest(AsyncWebServerRequest *request);
60 | void _onConfigRequest(AsyncWebServerRequest *request);
61 | void _generateNetworksJson();
62 | void _onWifiConnectRequest(AsyncWebServerRequest *request);
63 | void _onProxyControlRequest(AsyncWebServerRequest *request);
64 | void _proxyHttpRequest(AsyncWebServerRequest *request);
65 | void _onWifiStatusRequest(AsyncWebServerRequest *request);
66 |
67 | // Helpers
68 | static void __setCORS();
69 | static const int MAX_POST_SIZE = 1500;
70 | static void __parsePost(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
71 | static void __SendJSONError(AsyncWebServerRequest *request, String msg, int16_t code = 400);
72 | };
73 | } // namespace HomieInternals
74 |
75 | #endif
76 |
--------------------------------------------------------------------------------
/src/Homie/Boot/BootNormal.hpp:
--------------------------------------------------------------------------------
1 |
2 | #pragma once
3 |
4 | #include "Arduino.h"
5 |
6 | #include
7 | #include
8 |
9 | #ifndef HOMIE_MDNS
10 | #define HOMIE_MDNS 1
11 | #endif
12 |
13 |
14 | #ifdef ESP32
15 | #include
16 | #include
17 | #if HOMIE_MDNS
18 | #include
19 | #endif
20 | #elif defined(ESP8266)
21 | #include
22 | #if HOMIE_MDNS
23 | #include
24 | #endif
25 | #endif // ESP32
26 |
27 |
28 | #include
29 | #include "../../HomieNode.hpp"
30 | #include "../../HomieRange.hpp"
31 | #include "../../StreamingOperator.hpp"
32 | #include "../Constants.hpp"
33 | #include "../Limits.hpp"
34 | #include "../Datatypes/Interface.hpp"
35 | #include "../Utils/Helpers.hpp"
36 | #include "../Uptime.hpp"
37 | #include "../Timer.hpp"
38 | #include "../ExponentialBackoffTimer.hpp"
39 | #include "Boot.hpp"
40 | #include "../Utils/ResetHandler.hpp"
41 |
42 | namespace HomieInternals {
43 | class BootNormal : public Boot {
44 | public:
45 | BootNormal();
46 | ~BootNormal();
47 | void setup();
48 | void loop();
49 |
50 | private:
51 | struct AdvertisementProgress {
52 | bool done = false;
53 | enum class GlobalStep {
54 | PUB_INIT,
55 | PUB_HOMIE,
56 | PUB_NAME,
57 | PUB_MAC,
58 | PUB_LOCALIP,
59 | PUB_NODES_ATTR,
60 | PUB_STATS,
61 | PUB_STATS_INTERVAL,
62 | PUB_FW_NAME,
63 | PUB_FW_VERSION,
64 | PUB_FW_CHECKSUM,
65 | PUB_IMPLEMENTATION,
66 | PUB_IMPLEMENTATION_CONFIG,
67 | PUB_IMPLEMENTATION_VERSION,
68 | PUB_IMPLEMENTATION_OTA_ENABLED,
69 | PUB_NODES,
70 | SUB_IMPLEMENTATION_OTA,
71 | SUB_IMPLEMENTATION_RESET,
72 | SUB_IMPLEMENTATION_CONFIG_SET,
73 | SUB_SET,
74 | SUB_BROADCAST,
75 | PUB_READY
76 | } globalStep;
77 |
78 | enum class NodeStep {
79 | PUB_NAME,
80 | PUB_TYPE,
81 | PUB_ARRAY,
82 | PUB_ARRAY_NODES,
83 | PUB_PROPERTIES,
84 | PUB_PROPERTIES_ATTRIBUTES
85 | } nodeStep;
86 |
87 | enum class PropertyStep {
88 | PUB_NAME,
89 | PUB_SETTABLE,
90 | PUB_RETAINED,
91 | PUB_DATATYPE,
92 | PUB_UNIT,
93 | PUB_FORMAT
94 | } propertyStep;
95 |
96 | size_t currentNodeIndex;
97 | size_t currentArrayNodeIndex;
98 | size_t currentPropertyIndex;
99 | } _advertisementProgress;
100 | Uptime _uptime;
101 | Timer _statsTimer;
102 | ExponentialBackoffTimer _mqttReconnectTimer;
103 | bool _setupFunctionCalled;
104 | #ifdef ESP32
105 | WiFiEventId_t _wifiGotIpHandler;
106 | WiFiEventId_t _wifiDisconnectedHandler;
107 | #elif defined(ESP8266)
108 | WiFiEventHandler _wifiGotIpHandler;
109 | WiFiEventHandler _wifiDisconnectedHandler;
110 | #endif // ESP32
111 | bool _mqttConnectNotified;
112 | bool _mqttDisconnectNotified;
113 | bool _otaOngoing;
114 | bool _flaggedForReboot;
115 | uint16_t _mqttOfflineMessageId;
116 | char _fwChecksum[32 + 1];
117 | bool _otaIsBase64;
118 | base64_decodestate _otaBase64State;
119 | size_t _otaBase64Pads;
120 | size_t _otaSizeTotal;
121 | size_t _otaSizeDone;
122 |
123 | std::unique_ptr _mqttTopic;
124 |
125 | std::unique_ptr _mqttClientId;
126 | std::unique_ptr _mqttWillTopic;
127 | std::unique_ptr _mqttPayloadBuffer;
128 | std::unique_ptr _mqttTopicLevels;
129 | uint8_t _mqttTopicLevelsCount;
130 | std::unique_ptr _mqttTopicCopy;
131 |
132 | void _wifiConnect();
133 | #ifdef ESP32
134 | void _onWifiGotIp(WiFiEvent_t event, WiFiEventInfo_t info);
135 | void _onWifiDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
136 | #elif defined(ESP8266)
137 | void _onWifiGotIp(const WiFiEventStationModeGotIP& event);
138 | void _onWifiDisconnected(const WiFiEventStationModeDisconnected& event);
139 | #endif // ESP32
140 | void _mqttConnect();
141 | void _advertise();
142 | void _onMqttConnected();
143 | void _onMqttDisconnected(AsyncMqttClientDisconnectReason reason);
144 | void _onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total);
145 | void _onMqttPublish(uint16_t id);
146 | void _prefixMqttTopic();
147 | char* _prefixMqttTopic(PGM_P topic);
148 | bool _publishOtaStatus(int status, const char* info = nullptr);
149 | void _endOtaUpdate(bool success, uint8_t update_error = UPDATE_ERROR_OK);
150 |
151 | // _onMqttMessage Helpers
152 | void __splitTopic(char* topic);
153 | bool __fillPayloadBuffer(char* topic, char* payload, const AsyncMqttClientMessageProperties& properties, size_t len, size_t index, size_t total);
154 | bool __handleOTAUpdates(char* topic, char* payload, const AsyncMqttClientMessageProperties& properties, size_t len, size_t index, size_t total);
155 | bool __handleBroadcasts(char* topic, char* payload, const AsyncMqttClientMessageProperties& properties, size_t len, size_t index, size_t total);
156 | bool __handleResets(char* topic, char* payload, const AsyncMqttClientMessageProperties& properties, size_t len, size_t index, size_t total);
157 | bool __handleConfig(char* topic, char* payload, const AsyncMqttClientMessageProperties& properties, size_t len, size_t index, size_t total);
158 | bool __handleNodeProperty(char* topic, char* payload, const AsyncMqttClientMessageProperties& properties, size_t len, size_t index, size_t total);
159 | };
160 | } // namespace HomieInternals
161 |
--------------------------------------------------------------------------------
/src/Homie/Boot/BootStandalone.cpp:
--------------------------------------------------------------------------------
1 | #include "BootStandalone.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | BootStandalone::BootStandalone()
6 | : Boot("standalone") {
7 | }
8 |
9 | BootStandalone::~BootStandalone() {
10 | }
11 |
12 | void BootStandalone::setup() {
13 | Boot::setup();
14 |
15 | WiFi.mode(WIFI_OFF);
16 |
17 | #if HOMIE_CONFIG
18 | ResetHandler::Attach();
19 | #endif
20 | }
21 |
22 | void BootStandalone::loop() {
23 | Boot::loop();
24 | }
25 |
--------------------------------------------------------------------------------
/src/Homie/Boot/BootStandalone.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | #include "Boot.hpp"
6 | #include "../../StreamingOperator.hpp"
7 | #include "../Utils/ResetHandler.hpp"
8 |
9 | namespace HomieInternals {
10 | class BootStandalone : public Boot {
11 | public:
12 | BootStandalone();
13 | ~BootStandalone();
14 | void setup();
15 | void loop();
16 | };
17 | } // namespace HomieInternals
18 |
--------------------------------------------------------------------------------
/src/Homie/Config.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | #include
6 | #ifdef ESP32
7 | #include
8 | #endif // ESP32
9 | #include "FS.h"
10 | #include "Datatypes/Interface.hpp"
11 | #include "Datatypes/ConfigStruct.hpp"
12 | #include "Utils/DeviceId.hpp"
13 | #include "Utils/Validation.hpp"
14 | #include "Constants.hpp"
15 | #include "Limits.hpp"
16 | #include "../HomieBootMode.hpp"
17 | #include "../HomieSetting.hpp"
18 | #include "../StreamingOperator.hpp"
19 |
20 | namespace HomieInternals {
21 | class Config {
22 | public:
23 | Config();
24 | bool load();
25 | inline const ConfigStruct& get() const;
26 | char* getSafeConfigFile() const;
27 | void erase();
28 | void setHomieBootModeOnNextBoot(HomieBootMode bootMode);
29 | HomieBootMode getHomieBootModeOnNextBoot();
30 | void write(const JsonObject config);
31 | bool patch(const char* patch);
32 | void log() const; // print the current config to log output
33 | bool isValid() const;
34 |
35 | private:
36 | ConfigStruct _configStruct;
37 | bool _spiffsBegan;
38 | bool _valid;
39 |
40 | bool _spiffsBegin();
41 | void _patchJsonObject(JsonObject object, JsonObject patch);
42 | };
43 |
44 | const ConfigStruct& Config::get() const {
45 | return _configStruct;
46 | }
47 | } // namespace HomieInternals
48 |
--------------------------------------------------------------------------------
/src/Homie/Constants.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifdef ESP32
4 | #include
5 | #elif defined(ESP8266)
6 | #include
7 | #endif // ESP32
8 |
9 | #ifndef HOMIE_CONFIG
10 | #define HOMIE_CONFIG 1
11 | #endif
12 |
13 | namespace HomieInternals {
14 | const char HOMIE_VERSION[] = "3.0.1";
15 | const char HOMIE_ESP8266_VERSION[] = "3.0.0";
16 |
17 | const IPAddress ACCESS_POINT_IP(192, 168, 123, 1);
18 |
19 | const uint16_t DEFAULT_MQTT_PORT = 1883;
20 | const char DEFAULT_MQTT_BASE_TOPIC[] = "homie/";
21 |
22 | const uint8_t DEFAULT_RESET_PIN = 0; // == D3 on nodeMCU
23 | const uint8_t DEFAULT_RESET_STATE = LOW;
24 | const uint16_t DEFAULT_RESET_TIME = 5 * 1000;
25 |
26 | const char DEFAULT_BRAND[] = "Homie";
27 |
28 | const uint16_t CONFIG_SCAN_INTERVAL = 20 * 1000;
29 | const uint32_t STATS_SEND_INTERVAL_SEC = 1 * 60;
30 | const uint16_t MQTT_RECONNECT_INITIAL_INTERVAL = 1000;
31 | const uint8_t MQTT_RECONNECT_MAX_BACKOFF = 6;
32 |
33 | const float LED_WIFI_DELAY = 1;
34 | const float LED_MQTT_DELAY = 0.2;
35 |
36 | const char CONFIG_UI_BUNDLE_PATH[] = "/homie/ui_bundle.gz";
37 | const char CONFIG_NEXT_BOOT_MODE_FILE_PATH[] = "/homie/NEXTMODE";
38 | const char CONFIG_FILE_PATH[] = "/homie/config.json";
39 | } // namespace HomieInternals
40 |
--------------------------------------------------------------------------------
/src/Homie/Datatypes/Callbacks.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "../../HomieEvent.hpp"
5 | #include "../../HomieRange.hpp"
6 |
7 | class HomieNode;
8 |
9 | namespace HomieInternals {
10 | typedef std::function OperationFunction;
11 |
12 | typedef std::function GlobalInputHandler;
13 | typedef std::function NodeInputHandler;
14 | typedef std::function PropertyInputHandler;
15 |
16 | typedef std::function EventHandler;
17 |
18 | typedef std::function BroadcastHandler;
19 | } // namespace HomieInternals
20 |
--------------------------------------------------------------------------------
/src/Homie/Datatypes/ConfigStruct.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../Constants.hpp"
4 | #include "../Limits.hpp"
5 |
6 | namespace HomieInternals {
7 | struct ConfigStruct {
8 | char name[MAX_FRIENDLY_NAME_LENGTH];
9 | char deviceId[MAX_DEVICE_ID_LENGTH];
10 | uint16_t deviceStatsInterval;
11 |
12 | struct WiFi {
13 | char ssid[MAX_WIFI_SSID_LENGTH];
14 | char password[MAX_WIFI_PASSWORD_LENGTH];
15 | char bssid[MAX_MAC_STRING_LENGTH + 6];
16 | uint16_t channel;
17 | char ip[MAX_IP_STRING_LENGTH];
18 | char mask[MAX_IP_STRING_LENGTH];
19 | char gw[MAX_IP_STRING_LENGTH];
20 | char dns1[MAX_IP_STRING_LENGTH];
21 | char dns2[MAX_IP_STRING_LENGTH];
22 | } wifi;
23 |
24 | struct MQTT {
25 | struct Server {
26 | char host[MAX_HOSTNAME_LENGTH];
27 | uint16_t port;
28 | #if ASYNC_TCP_SSL_ENABLED
29 | struct {
30 | bool enabled;
31 | bool hasFingerprint;
32 | uint8_t fingerprint[MAX_FINGERPRINT_SIZE];
33 | } ssl;
34 | #endif
35 | } server;
36 | char baseTopic[MAX_MQTT_BASE_TOPIC_LENGTH];
37 | bool auth;
38 | char username[MAX_MQTT_CREDS_LENGTH];
39 | char password[MAX_MQTT_CREDS_LENGTH];
40 | } mqtt;
41 |
42 | struct OTA {
43 | bool enabled;
44 | } ota;
45 | };
46 | } // namespace HomieInternals
47 |
--------------------------------------------------------------------------------
/src/Homie/Datatypes/Interface.cpp:
--------------------------------------------------------------------------------
1 | #include "Interface.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | InterfaceData Interface::_interface; // need to define the static variable
6 |
7 | InterfaceData::InterfaceData()
8 | : brand{ '\0' }
9 | , bootMode{ HomieBootMode::UNDEFINED }
10 | , configurationAp{ .secured = false, .password = {'\0'} }
11 | , firmware{ .name = {'\0'}, .version = {'\0'} }
12 | , led{ .enabled = false, .pin = 0, .on = 0 }
13 | , reset{ .enabled = false, .idle = false, .triggerPin = 0, .triggerState = 0, .triggerTime = 0, .resetFlag = false }
14 | , disable{ false }
15 | , flaggedForSleep{ false }
16 | , event{}
17 | , ready{ false }
18 | , _logger{ nullptr }
19 | , _blinker{ nullptr }
20 | , _config{ nullptr }
21 | , _mqttClient{ nullptr }
22 | , _sendingPromise{ nullptr } {
23 | }
24 |
25 | InterfaceData& Interface::get() {
26 | return _interface;
27 | }
28 |
--------------------------------------------------------------------------------
/src/Homie/Datatypes/Interface.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "../Logger.hpp"
5 | #include "../Blinker.hpp"
6 | #include "../Constants.hpp"
7 | #include "../Config.hpp"
8 | #include "../Limits.hpp"
9 | #include "./Callbacks.hpp"
10 | #include "../../HomieBootMode.hpp"
11 | #include "../../HomieNode.hpp"
12 | #include "../../SendingPromise.hpp"
13 | #include "../../HomieEvent.hpp"
14 |
15 | namespace HomieInternals {
16 | class Logger;
17 | class Blinker;
18 | class Config;
19 | class SendingPromise;
20 | class HomieClass;
21 |
22 | class InterfaceData {
23 | friend HomieClass;
24 |
25 | public:
26 | InterfaceData();
27 |
28 | /***** User configurable data *****/
29 | char brand[MAX_BRAND_LENGTH];
30 |
31 | HomieBootMode bootMode;
32 |
33 | struct ConfigurationAP {
34 | bool secured;
35 | char password[MAX_WIFI_PASSWORD_LENGTH];
36 | } configurationAp;
37 |
38 | struct Firmware {
39 | char name[MAX_FIRMWARE_NAME_LENGTH];
40 | char version[MAX_FIRMWARE_VERSION_LENGTH];
41 | } firmware;
42 |
43 | struct LED {
44 | bool enabled;
45 | uint8_t pin;
46 | uint8_t on;
47 | } led;
48 |
49 | struct Reset {
50 | bool enabled;
51 | bool idle;
52 | uint8_t triggerPin;
53 | uint8_t triggerState;
54 | uint16_t triggerTime;
55 | bool resetFlag;
56 | } reset;
57 |
58 | bool disable;
59 | bool flaggedForSleep;
60 |
61 | GlobalInputHandler globalInputHandler;
62 | BroadcastHandler broadcastHandler;
63 | OperationFunction setupFunction;
64 | OperationFunction loopFunction;
65 | EventHandler eventHandler;
66 |
67 | /***** Runtime data *****/
68 | HomieEvent event;
69 | bool ready;
70 | Logger& getLogger() { return *_logger; }
71 | Blinker& getBlinker() { return *_blinker; }
72 | Config& getConfig() { return *_config; }
73 | AsyncMqttClient& getMqttClient() { return *_mqttClient; }
74 | SendingPromise& getSendingPromise() { return *_sendingPromise; }
75 |
76 | private:
77 | Logger* _logger;
78 | Blinker* _blinker;
79 | Config* _config;
80 | AsyncMqttClient* _mqttClient;
81 | SendingPromise* _sendingPromise;
82 | };
83 |
84 | class Interface {
85 | public:
86 | static InterfaceData& get();
87 |
88 | private:
89 | static InterfaceData _interface;
90 | };
91 | } // namespace HomieInternals
92 |
--------------------------------------------------------------------------------
/src/Homie/ExponentialBackoffTimer.cpp:
--------------------------------------------------------------------------------
1 | #include "ExponentialBackoffTimer.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | ExponentialBackoffTimer::ExponentialBackoffTimer(uint16_t initialInterval, uint8_t maxBackoff)
6 | : _timer(Timer())
7 | , _initialInterval(initialInterval)
8 | , _maxBackoff(maxBackoff)
9 | , _retryCount(0) {
10 | _timer.deactivate();
11 | }
12 |
13 | bool ExponentialBackoffTimer::check() {
14 | if (_timer.check()) {
15 | if (_retryCount != _maxBackoff) _retryCount++;
16 |
17 | uint32_t fixedDelay = pow(_retryCount, 2) * _initialInterval;
18 | uint32_t randomDifference = random(0, (fixedDelay / 10) + 1);
19 | uint32_t nextInterval = fixedDelay - randomDifference;
20 |
21 | _timer.setInterval(nextInterval, false);
22 | return true;
23 | } else {
24 | return false;
25 | }
26 | }
27 |
28 | void ExponentialBackoffTimer::activate() {
29 | if (_timer.isActive()) return;
30 |
31 | _timer.setInterval(_initialInterval, false);
32 | _timer.activate();
33 | _retryCount = 1;
34 | }
35 |
36 | void ExponentialBackoffTimer::deactivate() {
37 | _timer.deactivate();
38 | }
39 |
40 | bool ExponentialBackoffTimer::isActive() const {
41 | return _timer.isActive();
42 | }
43 |
--------------------------------------------------------------------------------
/src/Homie/ExponentialBackoffTimer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Timer.hpp"
4 | #include "Datatypes/Interface.hpp"
5 |
6 | namespace HomieInternals {
7 | class ExponentialBackoffTimer {
8 | public:
9 | ExponentialBackoffTimer(uint16_t initialInterval, uint8_t maxBackoff);
10 | void activate();
11 | bool check();
12 | void deactivate();
13 | bool isActive() const;
14 |
15 | private:
16 | Timer _timer;
17 |
18 | uint16_t _initialInterval;
19 | uint8_t _maxBackoff;
20 | uint8_t _retryCount;
21 | };
22 | } // namespace HomieInternals
23 |
--------------------------------------------------------------------------------
/src/Homie/Limits.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace HomieInternals {
6 | const uint16_t MAX_JSON_CONFIG_FILE_SIZE = 1000;
7 |
8 | // max setting elements
9 | const uint8_t MAX_CONFIG_SETTING_SIZE = 10;
10 | // 6 elements at root, 9 elements at wifi, 6 elements at mqtt, 1 element at ota, max settings elements
11 | const uint16_t MAX_JSON_CONFIG_ARDUINOJSON_BUFFER_SIZE = JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(9) + JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(MAX_CONFIG_SETTING_SIZE);
12 |
13 | const uint8_t MAX_MAC_LENGTH = 6;
14 | const uint8_t MAX_MAC_STRING_LENGTH = (MAX_MAC_LENGTH * 2) + 1;
15 |
16 | const uint8_t MAX_WIFI_SSID_LENGTH = 32 + 1;
17 | const uint8_t MAX_WIFI_PASSWORD_LENGTH = 64 + 1;
18 | const uint16_t MAX_HOSTNAME_LENGTH = 255 + 1;
19 | const uint8_t MAX_FINGERPRINT_SIZE = 20;
20 | const uint8_t MAX_FINGERPRINT_STRING_LENGTH = (MAX_FINGERPRINT_SIZE *2) + 1;
21 |
22 | const uint8_t MAX_MQTT_CREDS_LENGTH = 32 + 1;
23 | const uint8_t MAX_MQTT_BASE_TOPIC_LENGTH = 48 + 1;
24 | const uint8_t MAX_MQTT_TOPIC_LENGTH = 128 + 1;
25 |
26 | const uint8_t MAX_FRIENDLY_NAME_LENGTH = 64 + 1;
27 | const uint8_t MAX_DEVICE_ID_LENGTH = 32 + 1;
28 |
29 | const uint8_t MAX_BRAND_LENGTH = MAX_WIFI_SSID_LENGTH - 10 - 1;
30 | const uint8_t MAX_FIRMWARE_NAME_LENGTH = 32 + 1;
31 | const uint8_t MAX_FIRMWARE_VERSION_LENGTH = 16 + 1;
32 |
33 | const uint8_t MAX_NODE_ID_LENGTH = 24 + 1;
34 | const uint8_t MAX_NODE_TYPE_LENGTH = 24 + 1;
35 | const uint8_t MAX_NODE_PROPERTY_LENGTH = 24 + 1;
36 |
37 | const uint8_t MAX_IP_STRING_LENGTH = 16 + 1;
38 |
39 | } // namespace HomieInternals
40 |
--------------------------------------------------------------------------------
/src/Homie/Logger.cpp:
--------------------------------------------------------------------------------
1 | #include "Logger.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | Logger::Logger()
6 | : _loggingEnabled(true)
7 | , _printer(&Serial) {
8 | }
9 |
10 | void Logger::setLogging(bool enable) {
11 | _loggingEnabled = enable;
12 | }
13 |
14 | void Logger::setPrinter(Print* printer) {
15 | _printer = printer;
16 | }
17 |
18 | size_t Logger::write(uint8_t character) {
19 | if (_loggingEnabled) return _printer->write(character);
20 | return 0;
21 | }
22 |
23 | size_t Logger::write(const uint8_t* buffer, size_t size) {
24 | if (_loggingEnabled) return _printer->write(buffer, size);
25 | return 0;
26 | }
27 |
--------------------------------------------------------------------------------
/src/Homie/Logger.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | namespace HomieInternals {
6 | class HomieClass;
7 | class Logger : public Print {
8 | friend HomieClass;
9 |
10 | public:
11 | Logger();
12 | virtual size_t write(uint8_t character);
13 | virtual size_t write(const uint8_t* buffer, size_t size);
14 |
15 | private:
16 | void setPrinter(Print* printer);
17 | void setLogging(bool enable);
18 |
19 | bool _loggingEnabled;
20 | Print* _printer;
21 | };
22 | } // namespace HomieInternals
23 |
--------------------------------------------------------------------------------
/src/Homie/Strings.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | namespace HomieInternals {
4 | // config mode
5 |
6 | const char PROGMEM_CONFIG_APPLICATION_JSON[] PROGMEM = "application/json";
7 | const char PROGMEM_CONFIG_JSON_SUCCESS[] PROGMEM = "{\"success\":true}";
8 | const char PROGMEM_CONFIG_JSON_FAILURE_BEGINNING[] PROGMEM = "{\"success\":false,\"error\":\"";
9 | const char PROGMEM_CONFIG_JSON_FAILURE_END[] PROGMEM = "\"}";
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/Homie/Timer.cpp:
--------------------------------------------------------------------------------
1 | #include "Timer.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | Timer::Timer()
6 | : _initialTime(0)
7 | , _interval(0)
8 | , _tickAtBeginning(false)
9 | , _active(true) {
10 | }
11 |
12 | void Timer::setInterval(uint32_t interval, bool tickAtBeginning) {
13 | _interval = interval;
14 | _tickAtBeginning = tickAtBeginning;
15 |
16 | this->reset();
17 | }
18 |
19 | uint32_t HomieInternals::Timer::getInterval() {
20 | return _interval;
21 | }
22 |
23 | bool Timer::check() const {
24 | if (!_active) return false;
25 |
26 | if (_tickAtBeginning && _initialTime == 0) return true;
27 | if (millis() - _initialTime >= _interval) return true;
28 |
29 | return false;
30 | }
31 |
32 | void Timer::reset() {
33 | if (_tickAtBeginning) {
34 | _initialTime = 0;
35 | } else {
36 | this->tick();
37 | }
38 | }
39 |
40 | void Timer::tick() {
41 | _initialTime = millis();
42 | }
43 |
44 | void Timer::activate() {
45 | _active = true;
46 | }
47 |
48 | void Timer::deactivate() {
49 | _active = false;
50 | reset();
51 | }
52 |
53 | bool Timer::isActive() const {
54 | return _active;
55 | }
56 |
--------------------------------------------------------------------------------
/src/Homie/Timer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | namespace HomieInternals {
6 | class Timer {
7 | public:
8 | Timer();
9 | void setInterval(uint32_t interval, bool tickAtBeginning = true);
10 | uint32_t getInterval();
11 | bool check() const;
12 | void tick();
13 | void reset();
14 | void activate();
15 | void deactivate();
16 | bool isActive() const;
17 |
18 | private:
19 | uint32_t _initialTime;
20 | uint32_t _interval;
21 | bool _tickAtBeginning;
22 | bool _active;
23 | };
24 | } // namespace HomieInternals
25 |
--------------------------------------------------------------------------------
/src/Homie/Uptime.cpp:
--------------------------------------------------------------------------------
1 | #include "Uptime.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | Uptime::Uptime()
6 | : _milliseconds(0)
7 | , _lastTick(0) {
8 | }
9 |
10 | void Uptime::update() {
11 | uint32_t now = millis();
12 | _milliseconds += (now - _lastTick);
13 | _lastTick = now;
14 | }
15 |
16 | uint64_t Uptime::getSeconds() const {
17 | return (_milliseconds / 1000ULL);
18 | }
19 |
--------------------------------------------------------------------------------
/src/Homie/Uptime.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | namespace HomieInternals {
6 | class Uptime {
7 | public:
8 | Uptime();
9 | void update();
10 | uint64_t getSeconds() const;
11 |
12 | private:
13 | uint64_t _milliseconds;
14 | uint32_t _lastTick;
15 | };
16 | } // namespace HomieInternals
17 |
--------------------------------------------------------------------------------
/src/Homie/Utils/DeviceId.cpp:
--------------------------------------------------------------------------------
1 | #include "DeviceId.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | char DeviceId::_deviceId[]; // need to define the static variable
6 |
7 | #ifdef ESP32
8 | void DeviceId::generate() {
9 | uint8_t mac[6];
10 | esp_read_mac(mac, ESP_MAC_WIFI_STA);
11 | snprintf(DeviceId::_deviceId, MAX_MAC_STRING_LENGTH+1 , "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
12 | }
13 | #elif defined(ESP8266)
14 | void DeviceId::generate() {
15 | uint8_t mac[6];
16 | WiFi.macAddress(mac);
17 | snprintf(DeviceId::_deviceId, MAX_MAC_STRING_LENGTH, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
18 | }
19 | #endif // ESP32
20 |
21 | const char* DeviceId::get() {
22 | return DeviceId::_deviceId;
23 | }
24 |
--------------------------------------------------------------------------------
/src/Homie/Utils/DeviceId.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | #ifdef ESP32
6 | #include
7 | #include "esp_system.h"
8 | #elif defined(ESP8266)
9 | #include
10 | #endif // ESP32
11 |
12 | #include "../Limits.hpp"
13 |
14 | namespace HomieInternals {
15 | class DeviceId {
16 | public:
17 | static void generate();
18 | static const char* get();
19 |
20 | private:
21 | static char _deviceId[MAX_MAC_STRING_LENGTH];
22 | };
23 | } // namespace HomieInternals
24 |
--------------------------------------------------------------------------------
/src/Homie/Utils/Helpers.cpp:
--------------------------------------------------------------------------------
1 | #include "Helpers.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | void Helpers::abort(const String& message) {
6 | Serial.begin(115200);
7 | Serial << message << endl;
8 | Serial.flush();
9 | ::abort();
10 | }
11 |
12 | uint8_t Helpers::rssiToPercentage(int32_t rssi) {
13 | uint8_t quality;
14 | if (rssi <= -100) {
15 | quality = 0;
16 | } else if (rssi >= -50) {
17 | quality = 100;
18 | } else {
19 | quality = 2 * (rssi + 100);
20 | }
21 |
22 | return quality;
23 | }
24 |
25 | void Helpers::stringToBytes(const char* str, char sep, byte* bytes, int maxBytes, int base) {
26 | // taken from http://stackoverflow.com/a/35236734
27 | for (int i = 0; i < maxBytes; i++) {
28 | bytes[i] = strtoul(str, NULL, base);
29 | str = strchr(str, sep);
30 | if (str == NULL || *str == '\0') {
31 | break;
32 | }
33 | str++;
34 | }
35 | }
36 |
37 | bool Helpers::validateIP(const char* ip) {
38 | IPAddress test;
39 | return test.fromString(ip);
40 | }
41 |
42 | bool Helpers::validateMacAddress(const char* mac) {
43 | // taken from http://stackoverflow.com/a/4792211
44 | int i = 0;
45 | int s = 0;
46 | while (*mac) {
47 | if (isxdigit(*mac)) {
48 | i++;
49 | } else if (*mac == ':' || *mac == '-') {
50 | if (i == 0 || i / 2 - 1 != s)
51 | break;
52 | ++s;
53 | } else {
54 | s = -1;
55 | }
56 | ++mac;
57 | }
58 | return (i == MAX_MAC_LENGTH * 2 && s == 5);
59 | }
60 |
61 | bool Helpers::validateMd5(const char* md5) {
62 | if (strlen(md5) != 32) return false;
63 |
64 | for (uint8_t i = 0; i < 32; i++) {
65 | char c = md5[i];
66 | bool valid = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
67 | if (!valid) return false;
68 | }
69 |
70 | return true;
71 | }
72 |
73 | std::unique_ptr Helpers::cloneString(const String& string) {
74 | size_t length = string.length();
75 | std::unique_ptr copy(new char[length + 1]);
76 | memcpy(copy.get(), string.c_str(), length);
77 | copy.get()[length] = '\0';
78 |
79 | return copy;
80 | }
81 |
82 | void Helpers::ipToString(const IPAddress& ip, char * str) {
83 | snprintf(str, MAX_IP_STRING_LENGTH, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
84 | }
85 |
86 | void Helpers::hexStringToByteArray(const char* hexStr, uint8_t* hexArray, uint8_t size) {
87 | for (uint8_t i = 0; i < size; i++) {
88 | char hex[3];
89 | strncpy(hex, (hexStr + (i * 2)), 2);
90 | hex[2] = '\0';
91 | hexArray[i] = (uint8_t)strtol((const char*)&hex, nullptr, 16);
92 | }
93 | }
94 |
95 | void Helpers::byteArrayToHexString(const uint8_t * hexArray, char* hexStr, uint8_t size) {
96 | for (uint8_t i = 0; i < size; i++) {
97 | snprintf((hexStr + (i * 2)), 3, "%02x", hexArray[i]);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Homie/Utils/Helpers.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 | #include
5 | #include "../../StreamingOperator.hpp"
6 | #include "../Limits.hpp"
7 | #include
8 |
9 | namespace HomieInternals {
10 | class Helpers {
11 | public:
12 | static void abort(const String& message);
13 | static uint8_t rssiToPercentage(int32_t rssi);
14 | static void stringToBytes(const char* str, char sep, byte* bytes, int maxBytes, int base);
15 | static bool validateIP(const char* ip);
16 | static bool validateMacAddress(const char* mac);
17 | static bool validateMd5(const char* md5);
18 | static std::unique_ptr cloneString(const String& string);
19 | static void ipToString(const IPAddress& ip, char* str);
20 | static void hexStringToByteArray(const char* hexStr, uint8_t* hexArray, uint8_t size);
21 | static void byteArrayToHexString(const uint8_t* hexArray, char* hexStr, uint8_t size);
22 | };
23 | } // namespace HomieInternals
24 |
--------------------------------------------------------------------------------
/src/Homie/Utils/ResetHandler.cpp:
--------------------------------------------------------------------------------
1 | #include "ResetHandler.hpp"
2 |
3 | #if HOMIE_CONFIG
4 | using namespace HomieInternals;
5 |
6 | Ticker ResetHandler::_resetBTNTicker;
7 | Bounce ResetHandler::_resetBTNDebouncer;
8 | Ticker ResetHandler::_resetTicker;
9 | bool ResetHandler::_sentReset = false;
10 |
11 | void ResetHandler::Attach() {
12 | if (Interface::get().reset.enabled) {
13 | pinMode(Interface::get().reset.triggerPin, INPUT_PULLUP);
14 | _resetBTNDebouncer.attach(Interface::get().reset.triggerPin);
15 | _resetBTNDebouncer.interval(Interface::get().reset.triggerTime);
16 |
17 | _resetBTNTicker.attach_ms(10, _tick);
18 | _resetTicker.attach_ms(100, _handleReset);
19 | }
20 | }
21 |
22 | void ResetHandler::_tick() {
23 | if (!Interface::get().reset.resetFlag && Interface::get().reset.enabled) {
24 | _resetBTNDebouncer.update();
25 | if (_resetBTNDebouncer.read() == Interface::get().reset.triggerState) {
26 | Interface::get().getLogger() << F("Flagged for reset by pin") << endl;
27 | Interface::get().disable = true;
28 | Interface::get().reset.resetFlag = true;
29 | }
30 | }
31 | }
32 |
33 | void ResetHandler::_handleReset() {
34 | if (Interface::get().reset.resetFlag && !_sentReset && Interface::get().reset.idle) {
35 | Interface::get().getLogger() << F("Device is idle") << endl;
36 |
37 | Interface::get().getConfig().erase();
38 | Interface::get().getLogger() << F("Configuration erased") << endl;
39 |
40 | // Set boot mode
41 | Interface::get().getConfig().setHomieBootModeOnNextBoot(HomieBootMode::CONFIGURATION);
42 |
43 | Interface::get().getLogger() << F("Triggering ABOUT_TO_RESET event...") << endl;
44 | Interface::get().event.type = HomieEventType::ABOUT_TO_RESET;
45 | Interface::get().eventHandler(Interface::get().event);
46 |
47 | Interface::get().getLogger() << F("↻ Rebooting into config mode...") << endl;
48 | Serial.flush();
49 | ESP.restart();
50 | _sentReset = true;
51 | }
52 | }
53 | #endif
54 |
--------------------------------------------------------------------------------
/src/Homie/Utils/ResetHandler.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | #include
6 | #include
7 | #include "../../StreamingOperator.hpp"
8 | #include "../Datatypes/Interface.hpp"
9 |
10 | #if HOMIE_CONFIG
11 | namespace HomieInternals {
12 | class ResetHandler {
13 | public:
14 | static void Attach();
15 |
16 | private:
17 | // Disallow creating an instance of this object
18 | ResetHandler() {}
19 | static Ticker _resetBTNTicker;
20 | static Bounce _resetBTNDebouncer;
21 | static void _tick();
22 | static Ticker _resetTicker;
23 | static bool _sentReset;
24 | static void _handleReset();
25 | };
26 | } // namespace HomieInternals
27 | #endif
28 |
--------------------------------------------------------------------------------
/src/Homie/Utils/Validation.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 |
5 | #include
6 | #include "Helpers.hpp"
7 | #include "../Limits.hpp"
8 | #include "../../HomieSetting.hpp"
9 |
10 | namespace HomieInternals {
11 | struct ConfigValidationResult {
12 | bool valid;
13 | String reason;
14 | };
15 |
16 | class Validation {
17 | public:
18 | static ConfigValidationResult validateConfig(const JsonObject object);
19 |
20 | private:
21 | static ConfigValidationResult _validateConfigRoot(const JsonObject object);
22 | static ConfigValidationResult _validateConfigWifi(const JsonObject object);
23 | static ConfigValidationResult _validateConfigMqtt(const JsonObject object);
24 | static ConfigValidationResult _validateConfigOta(const JsonObject object);
25 | static ConfigValidationResult _validateConfigSettings(const JsonObject object);
26 | };
27 | } // namespace HomieInternals
28 |
--------------------------------------------------------------------------------
/src/HomieBootMode.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | enum class HomieBootMode : uint8_t {
4 | UNDEFINED = 0,
5 | STANDALONE = 1,
6 | #if HOMIE_CONFIG
7 | CONFIGURATION = 2,
8 | #endif
9 | NORMAL = 3
10 | };
11 |
--------------------------------------------------------------------------------
/src/HomieEvent.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifdef ESP32
4 | #include
5 | #elif defined(ESP8266)
6 | #include
7 | #endif // ESP32
8 | #include
9 |
10 | enum class HomieEventType : uint8_t {
11 | STANDALONE_MODE = 1,
12 | CONFIGURATION_MODE,
13 | NORMAL_MODE,
14 | OTA_STARTED,
15 | OTA_PROGRESS,
16 | OTA_SUCCESSFUL,
17 | OTA_FAILED,
18 | ABOUT_TO_RESET,
19 | WIFI_CONNECTED,
20 | WIFI_DISCONNECTED,
21 | MQTT_READY,
22 | MQTT_DISCONNECTED,
23 | MQTT_PACKET_ACKNOWLEDGED,
24 | READY_TO_SLEEP,
25 | SENDING_STATISTICS
26 | };
27 |
28 | struct HomieEvent {
29 | HomieEventType type;
30 | /* WIFI_CONNECTED */
31 | IPAddress ip;
32 | IPAddress mask;
33 | IPAddress gateway;
34 | /* WIFI_DISCONNECTED */
35 | #ifdef ESP32
36 | uint8_t wifiReason;
37 | #elif defined(ESP8266)
38 | WiFiDisconnectReason wifiReason;
39 | #endif // ESP32
40 | /* MQTT_DISCONNECTED */
41 | AsyncMqttClientDisconnectReason mqttReason;
42 | /* MQTT_PACKET_ACKNOWLEDGED */
43 | uint16_t packetId;
44 | /* OTA_PROGRESS */
45 | size_t sizeDone;
46 | size_t sizeTotal;
47 | };
48 |
--------------------------------------------------------------------------------
/src/HomieNode.cpp:
--------------------------------------------------------------------------------
1 | #include "HomieNode.hpp"
2 | #include "Homie.hpp"
3 |
4 | using namespace HomieInternals;
5 |
6 | std::vector HomieNode::nodes;
7 |
8 | PropertyInterface::PropertyInterface()
9 | : _property(nullptr) {
10 | }
11 |
12 | PropertyInterface& PropertyInterface::settable(const PropertyInputHandler& inputHandler) {
13 | _property->settable(inputHandler);
14 | return *this;
15 | }
16 |
17 | PropertyInterface& PropertyInterface::setName(const char* name) {
18 | _property->setName(name);
19 | return *this;
20 | }
21 |
22 | PropertyInterface& PropertyInterface::setUnit(const char* unit) {
23 | _property->setUnit(unit);
24 | return *this;
25 | }
26 |
27 | PropertyInterface& PropertyInterface::setDatatype(const char* datatype) {
28 | _property->setDatatype(datatype);
29 | return *this;
30 | }
31 |
32 | PropertyInterface& PropertyInterface::setFormat(const char* format) {
33 | _property->setFormat(format);
34 | return *this;
35 | }
36 |
37 | PropertyInterface& PropertyInterface::setRetained(const bool retained) {
38 | _property->setRetained(retained);
39 | return *this;
40 | }
41 |
42 | PropertyInterface& PropertyInterface::setProperty(Property* property) {
43 | _property = property;
44 | return *this;
45 | }
46 |
47 | HomieNode::HomieNode(const char* id, const char* name, const char* type, bool range, uint16_t lower, uint16_t upper, const NodeInputHandler& inputHandler)
48 | : _id(id)
49 | , _name(name)
50 | , _type(type)
51 | , _range(range)
52 | , _lower(lower)
53 | , _upper(upper)
54 | , runLoopDisconnected(false)
55 | , _properties()
56 | , _inputHandler(inputHandler) {
57 | if (strlen(id) + 1 > MAX_NODE_ID_LENGTH || strlen(type) + 1 > MAX_NODE_TYPE_LENGTH) {
58 | Helpers::abort(F("✖ HomieNode(): either the id or type string is too long"));
59 | return; // never reached, here for clarity
60 | }
61 | Homie._checkBeforeSetup(F("HomieNode::HomieNode"));
62 |
63 | HomieNode::nodes.push_back(this);
64 | }
65 |
66 | HomieNode::~HomieNode() {
67 | Helpers::abort(F("✖✖ ~HomieNode(): Destruction of HomieNode object not possible\n Hint: Don't create HomieNode objects as a local variable (e.g. in setup())"));
68 | return; // never reached, here for clarity
69 | }
70 |
71 | PropertyInterface& HomieNode::advertise(const char* id) {
72 | Property* propertyObject = new Property(id);
73 |
74 | _properties.push_back(propertyObject);
75 |
76 | return _propertyInterface.setProperty(propertyObject);
77 | }
78 |
79 | SendingPromise& HomieNode::setProperty(const String& property) const {
80 | Property* iProperty = this->getProperty(property);
81 | if (iProperty && iProperty->isRetained()) {
82 | return Interface::get().getSendingPromise().setNode(*this).setProperty(property).setQos(1).setRetained(true);
83 | } else {
84 | return Interface::get().getSendingPromise().setNode(*this).setProperty(property).setQos(1);
85 | }
86 | }
87 |
88 | Property* HomieNode::getProperty(const String& property) const {
89 | for (Property* iProperty : getProperties()) {
90 | if (strcmp(iProperty->getId(), property.c_str()) == 0)
91 | return iProperty;
92 | }
93 | return NULL;
94 | }
95 |
96 | bool HomieNode::handleInput(const HomieRange& range, const String& property, const String& value) {
97 | return _inputHandler(range, property, value);
98 | }
99 |
100 | const std::vector& HomieNode::getProperties() const {
101 | return _properties;
102 | }
103 |
--------------------------------------------------------------------------------
/src/HomieNode.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include "Arduino.h"
6 | #include "StreamingOperator.hpp"
7 | #include "Homie/Datatypes/Interface.hpp"
8 | #include "Homie/Datatypes/Callbacks.hpp"
9 | #include "Homie/Limits.hpp"
10 | #include "HomieRange.hpp"
11 |
12 | class HomieNode;
13 |
14 | namespace HomieInternals {
15 | class HomieClass;
16 | class Property;
17 | class BootNormal;
18 | class BootConfig;
19 | class SendingPromise;
20 |
21 | class PropertyInterface {
22 | friend ::HomieNode;
23 |
24 | public:
25 | PropertyInterface();
26 |
27 | PropertyInterface& settable(const PropertyInputHandler& inputHandler = [](const HomieRange& range, const String& value) { return false; });
28 | PropertyInterface& setName(const char* name);
29 | PropertyInterface& setUnit(const char* unit);
30 | PropertyInterface& setDatatype(const char* datatype);
31 | PropertyInterface& setFormat(const char* format);
32 | PropertyInterface& setRetained(const bool retained = true);
33 |
34 | private:
35 | PropertyInterface& setProperty(Property* property);
36 |
37 | Property* _property;
38 | };
39 |
40 | class Property {
41 | friend HomieNode;
42 | friend BootNormal;
43 |
44 | public:
45 | explicit Property(const char* id) {
46 | _id = strdup(id); _name = ""; _unit = ""; _datatype = ""; _format = ""; _retained = true; _settable = false; }
47 | void settable(const PropertyInputHandler& inputHandler) { _settable = true; _inputHandler = inputHandler; }
48 | void setName(const char* name) { _name = name; }
49 | void setUnit(const char* unit) { _unit = unit; }
50 | void setDatatype(const char* datatype) { _datatype = datatype; }
51 | void setFormat(const char* format) { _format = format; }
52 | void setRetained(const bool retained = true) { _retained = retained; }
53 |
54 |
55 | private:
56 | const char* getId() const { return _id; }
57 | const char* getName() const { return _name; }
58 | const char* getUnit() const { return _unit; }
59 | const char* getDatatype() const { return _datatype; }
60 | const char* getFormat() const { return _format; }
61 | bool isRetained() const { return _retained; }
62 | bool isSettable() const { return _settable; }
63 | PropertyInputHandler getInputHandler() const { return _inputHandler; }
64 | const char* _id;
65 | const char* _name;
66 | const char* _unit;
67 | const char* _datatype;
68 | const char* _format;
69 | bool _retained;
70 | bool _settable;
71 | PropertyInputHandler _inputHandler;
72 | };
73 | } // namespace HomieInternals
74 |
75 | class HomieNode {
76 | friend HomieInternals::HomieClass;
77 | friend HomieInternals::BootNormal;
78 | friend HomieInternals::BootConfig;
79 |
80 | public:
81 | HomieNode(const char* id, const char* name, const char* type, bool range = false, uint16_t lower = 0, uint16_t upper = 0, const HomieInternals::NodeInputHandler& nodeInputHandler = [](const HomieRange& range, const String& property, const String& value) { return false; });
82 | virtual ~HomieNode();
83 |
84 | const char* getId() const { return _id; }
85 | const char* getType() const { return _type; }
86 | const char* getName() const {return _name; }
87 | bool isRange() const { return _range; }
88 | uint16_t getLower() const { return _lower; }
89 | uint16_t getUpper() const { return _upper; }
90 |
91 | HomieInternals::PropertyInterface& advertise(const char* id);
92 | HomieInternals::SendingPromise& setProperty(const String& property) const;
93 | HomieInternals::Property* getProperty(const String& property) const;
94 |
95 | void setRunLoopDisconnected(bool runLoopDisconnected) {
96 | this->runLoopDisconnected = runLoopDisconnected;
97 | }
98 |
99 | protected:
100 | virtual void setup() {}
101 | virtual void loop() {}
102 | virtual void onReadyToOperate() {}
103 | virtual bool handleInput(const HomieRange& range, const String& property, const String& value);
104 |
105 | private:
106 | const std::vector& getProperties() const;
107 |
108 | static HomieNode* find(const char* id) {
109 | for (HomieNode* iNode : HomieNode::nodes) {
110 | if (strcmp(id, iNode->getId()) == 0) return iNode;
111 | }
112 |
113 | return 0;
114 | }
115 |
116 |
117 | const char* _id;
118 | const char* _name;
119 | const char* _type;
120 | bool _range;
121 | uint16_t _lower;
122 | uint16_t _upper;
123 |
124 | bool runLoopDisconnected;
125 |
126 | std::vector _properties;
127 | HomieInternals::NodeInputHandler _inputHandler;
128 |
129 | HomieInternals::PropertyInterface _propertyInterface;
130 |
131 | static std::vector nodes;
132 | };
133 |
--------------------------------------------------------------------------------
/src/HomieRange.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | struct HomieRange {
4 | bool isRange;
5 | uint16_t index;
6 | };
7 |
--------------------------------------------------------------------------------
/src/HomieSetting.cpp:
--------------------------------------------------------------------------------
1 | #include "HomieSetting.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | std::vector __attribute__((init_priority(101))) IHomieSetting::settings;
6 |
7 | HomieInternals::IHomieSetting::IHomieSetting(const char * name, const char * description)
8 | : _name(name)
9 | , _description(description)
10 | , _required(true)
11 | , _provided(false) {
12 | }
13 |
14 | bool IHomieSetting::isRequired() const {
15 | return _required;
16 | }
17 |
18 | const char* IHomieSetting::getName() const {
19 | return _name;
20 | }
21 |
22 | const char* IHomieSetting::getDescription() const {
23 | return _description;
24 | }
25 |
26 |
27 |
28 | template
29 | HomieSetting::HomieSetting(const char* name, const char* description)
30 | : IHomieSetting(name, description)
31 | , _value()
32 | , _validator([](T candidate) { return true; }) {
33 | IHomieSetting::settings.push_back(this);
34 | }
35 |
36 | template
37 | T HomieSetting::get() const {
38 | return _value;
39 | }
40 |
41 | template
42 | bool HomieSetting::wasProvided() const {
43 | return _provided;
44 | }
45 |
46 | template
47 | HomieSetting& HomieSetting::setDefaultValue(T defaultValue) {
48 | _value = defaultValue;
49 | _required = false;
50 | return *this;
51 | }
52 |
53 | template
54 | HomieSetting& HomieSetting::setValidator(const std::function& validator) {
55 | _validator = validator;
56 | return *this;
57 | }
58 |
59 | template
60 | bool HomieSetting::validate(T candidate) const {
61 | return _validator(candidate);
62 | }
63 |
64 | template
65 | void HomieSetting::set(T value) {
66 | _value = value;
67 | _provided = true;
68 | }
69 |
70 | template
71 | bool HomieSetting::isBool() const { return false; }
72 |
73 | template
74 | bool HomieSetting::isLong() const { return false; }
75 |
76 | template
77 | bool HomieSetting::isDouble() const { return false; }
78 |
79 | template
80 | bool HomieSetting::isConstChar() const { return false; }
81 |
82 | template<>
83 | bool HomieSetting::isBool() const { return true; }
84 | template<>
85 | const char* HomieSetting::getType() const { return "bool"; }
86 |
87 | template<>
88 | bool HomieSetting::isLong() const { return true; }
89 | template<>
90 | const char* HomieSetting::getType() const { return "long"; }
91 |
92 | template<>
93 | bool HomieSetting::isDouble() const { return true; }
94 | template<>
95 | const char* HomieSetting::getType() const { return "double"; }
96 |
97 | template<>
98 | bool HomieSetting::isConstChar() const { return true; }
99 | template<>
100 | const char* HomieSetting::getType() const { return "string"; }
101 | template<>
102 | HomieSetting& HomieSetting::setDefaultValue(const char* defaultValue) {
103 | //free any potentially prior set value
104 | free(const_cast(_value));
105 | //duplicate the value, so we own it, and might free when required
106 | _value = strdup(defaultValue);;
107 | _required = false;
108 | return *this;
109 | }
110 | template<>
111 | void HomieSetting::set(const char* value) {
112 | //free any potentially prior set value (that was copied)
113 | free(const_cast(_value));
114 | //duplicate the value, so we own it, and might free when required
115 | _value = strdup(value);
116 | _required = false;
117 | }
118 |
119 | // Needed because otherwise undefined reference to
120 | template class HomieSetting;
121 | template class HomieSetting;
122 | template class HomieSetting;
123 | template class HomieSetting;
124 |
--------------------------------------------------------------------------------
/src/HomieSetting.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include "Arduino.h"
6 |
7 | #include "./Homie/Datatypes/Callbacks.hpp"
8 |
9 | namespace HomieInternals {
10 | class HomieClass;
11 | class Config;
12 | class Validation;
13 | class BootConfig;
14 |
15 | class IHomieSetting {
16 | public:
17 | static std::vector settings;
18 |
19 | bool isRequired() const;
20 | const char* getName() const;
21 | const char* getDescription() const;
22 |
23 | virtual bool isBool() const { return false; }
24 | virtual bool isLong() const { return false; }
25 | virtual bool isDouble() const { return false; }
26 | virtual bool isConstChar() const { return false; }
27 |
28 | virtual const char* getType() const { return "unknown"; }
29 |
30 | protected:
31 | explicit IHomieSetting(const char* name, const char* description);
32 | const char* _name;
33 | const char* _description;
34 | bool _required;
35 | bool _provided;
36 | };
37 | } // namespace HomieInternals
38 |
39 | template
40 | class HomieSetting : public HomieInternals::IHomieSetting {
41 | friend HomieInternals::HomieClass;
42 | friend HomieInternals::Config;
43 | friend HomieInternals::Validation;
44 | friend HomieInternals::BootConfig;
45 |
46 | public:
47 | HomieSetting(const char* name, const char* description);
48 | T get() const;
49 | bool wasProvided() const;
50 | HomieSetting& setDefaultValue(T defaultValue);
51 | HomieSetting& setValidator(const std::function& validator);
52 |
53 | private:
54 | T _value;
55 | std::function _validator;
56 |
57 | bool validate(T candidate) const;
58 | void set(T value);
59 |
60 | bool isBool() const;
61 | bool isLong() const;
62 | bool isDouble() const;
63 | bool isConstChar() const;
64 | void ffree() const;
65 |
66 | const char* getType() const;
67 | };
68 |
--------------------------------------------------------------------------------
/src/SendingPromise.cpp:
--------------------------------------------------------------------------------
1 | #include "SendingPromise.hpp"
2 |
3 | using namespace HomieInternals;
4 |
5 | SendingPromise::SendingPromise()
6 | : _node(nullptr)
7 | , _property(nullptr)
8 | , _qos(0)
9 | , _retained(false)
10 | , _overwriteSetter(false)
11 | , _range { .isRange = false, .index = 0 } {
12 | }
13 |
14 | SendingPromise& SendingPromise::setQos(uint8_t qos) {
15 | _qos = qos;
16 | return *this;
17 | }
18 |
19 | SendingPromise& SendingPromise::setRetained(bool retained) {
20 | _retained = retained;
21 | return *this;
22 | }
23 |
24 | SendingPromise& SendingPromise::overwriteSetter(bool overwrite) {
25 | _overwriteSetter = overwrite;
26 | return *this;
27 | }
28 |
29 | SendingPromise& SendingPromise::setRange(const HomieRange& range) {
30 | _range = range;
31 | return *this;
32 | }
33 |
34 | SendingPromise& SendingPromise::setRange(uint16_t rangeIndex) {
35 | HomieRange range;
36 | range.isRange = true;
37 | range.index = rangeIndex;
38 | _range = range;
39 | return *this;
40 | }
41 |
42 | uint16_t SendingPromise::send(const String& value) {
43 | if (!Interface::get().ready) {
44 | Interface::get().getLogger() << F("✖ setNodeProperty(): impossible now") << endl;
45 | return 0;
46 | }
47 |
48 | char* topic = new char[strlen(Interface::get().getConfig().get().mqtt.baseTopic) + strlen(Interface::get().getConfig().get().deviceId) + 1 + strlen(_node->getId()) + 1 + strlen(_property->c_str()) + 6 + 4 + 1]; // last + 6 for range _65536, last + 4 for /set
49 | strcpy(topic, Interface::get().getConfig().get().mqtt.baseTopic);
50 | strcat(topic, Interface::get().getConfig().get().deviceId);
51 | strcat_P(topic, PSTR("/"));
52 | strcat(topic, _node->getId());
53 | if (_range.isRange) {
54 | char rangeStr[5 + 1]; // max 65536
55 | itoa(_range.index, rangeStr, 10);
56 | strcat_P(topic, PSTR("_"));
57 | strcat(topic, rangeStr);
58 | _range.isRange = false; //FIXME: This is a workaround. Problem is that Range is loaded from the property into SendingPromise, but the SendingPromise is global. (one SendingPromise for the HomieClass instance
59 | _range.index = 0;
60 | }
61 |
62 | strcat_P(topic, PSTR("/"));
63 | strcat(topic, _property->c_str());
64 |
65 | uint16_t packetId = Interface::get().getMqttClient().publish(topic, _qos, _retained, value.c_str());
66 |
67 | if (_overwriteSetter) {
68 | strcat_P(topic, PSTR("/set"));
69 | Interface::get().getMqttClient().publish(topic, 1, true, value.c_str());
70 | }
71 |
72 | delete[] topic;
73 |
74 | return packetId;
75 | }
76 |
77 | SendingPromise& SendingPromise::setNode(const HomieNode& node) {
78 | _node = &node;
79 | return *this;
80 | }
81 |
82 | SendingPromise& SendingPromise::setProperty(const String& property) {
83 | _property = &property;
84 | return *this;
85 | }
86 |
87 | const HomieNode* SendingPromise::getNode() const {
88 | return _node;
89 | }
90 |
91 | const String* SendingPromise::getProperty() const {
92 | return _property;
93 | }
94 |
95 | uint8_t SendingPromise::getQos() const {
96 | return _qos;
97 | }
98 |
99 | HomieRange SendingPromise::getRange() const {
100 | return _range;
101 | }
102 |
103 | bool SendingPromise::isRetained() const {
104 | return _retained;
105 | }
106 |
107 | bool SendingPromise::doesOverwriteSetter() const {
108 | return _overwriteSetter;
109 | }
110 |
--------------------------------------------------------------------------------
/src/SendingPromise.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Arduino.h"
4 | #include "StreamingOperator.hpp"
5 | #include "Homie/Datatypes/Interface.hpp"
6 | #include "HomieRange.hpp"
7 |
8 | class HomieNode;
9 |
10 | namespace HomieInternals {
11 | class SendingPromise {
12 | friend ::HomieNode;
13 |
14 | public:
15 | SendingPromise();
16 | SendingPromise& setQos(uint8_t qos);
17 | SendingPromise& setRetained(bool retained);
18 | SendingPromise& overwriteSetter(bool overwrite);
19 | SendingPromise& setRange(const HomieRange& range);
20 | SendingPromise& setRange(uint16_t rangeIndex);
21 | uint16_t send(const String& value);
22 |
23 | private:
24 | SendingPromise& setNode(const HomieNode& node);
25 | SendingPromise& setProperty(const String& property);
26 | const HomieNode* getNode() const;
27 | const String* getProperty() const;
28 | uint8_t getQos() const;
29 | HomieRange getRange() const;
30 | bool isRetained() const;
31 | bool doesOverwriteSetter() const;
32 |
33 | const HomieNode* _node;
34 | const String* _property;
35 | uint8_t _qos;
36 | bool _retained;
37 | bool _overwriteSetter;
38 | HomieRange _range;
39 | };
40 | } // namespace HomieInternals
41 |
--------------------------------------------------------------------------------
/src/StreamingOperator.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | template
4 | inline Print &operator <<(Print &stream, T arg)
5 | { stream.print(arg); return stream; }
6 |
7 | enum _EndLineCode { endl };
8 |
9 | inline Print &operator <<(Print &stream, _EndLineCode arg)
10 | { stream.println(); return stream; }
11 |
--------------------------------------------------------------------------------