diff --git a/.travis.yml b/.travis.yml index 4198ed5a..d9a05021 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ +sudo: required language: python -python: - - "2.7" +services: + - docker env: - PLATFORMIO_CI_SRC=examples/CustomSettings @@ -13,12 +14,36 @@ env: - PLATFORMIO_CI_SRC=examples/Broadcast - PLATFORMIO_CI_SRC=examples/GlobalInputHandler - CPPLINT=true + - DOCS=true install: - - pip install -U platformio - - pip install cpplint - # install current build as a library with all dependencies - - platformio lib -g install file://. + - chmod +x ./ci/generate_docs.py script: - - if [[ "$CPPLINT" ]]; then make cpplint; else platformio ci --board=esp01 --board=nodemcuv2; fi + - set -eo pipefail + - | + if [[ "${CPPLINT}" ]]; then + pip install cpplint + make cpplint + elif [[ "${DOCS}" ]]; then + openssl aes-256-cbc -d -in ./ci/id_rsa.enc -k "${PRIVATE_KEY_ENCRYPT_KEY}" >> /tmp/deploy_rsa + eval "$(ssh-agent -s)" + chmod 600 /tmp/deploy_rsa + ssh-add /tmp/deploy_rsa + + ./ci/generate_docs.py -o /tmp/docs_site + pushd /tmp/docs_site + git init + git config --global user.name "travis-ci" + git config --global user.email "contact@travis-ci.com" + git remote add origin git@github.com:marvinroger/homie-esp8266.git + git add . + git commit -m ":package: Result of Travis build ${TRAVIS_BUILD_NUMBER}" + git push -f origin master:gh-pages + popd + else + pip install -U platformio + # install current build as a library with all dependencies + platformio lib -g install file://. + platformio ci --board=esp01 --board=nodemcuv2 + fi diff --git a/ci/docs_index_template.html b/ci/docs_index_template.html new file mode 100644 index 00000000..26de8499 --- /dev/null +++ b/ci/docs_index_template.html @@ -0,0 +1,69 @@ + + + + Homie for ESP8266 docs + + + + + + + +
+

Homie for ESP8266 docs

+ + $versions_html +
+ + diff --git a/ci/generate_docs.py b/ci/generate_docs.py new file mode 100644 index 00000000..4770598b --- /dev/null +++ b/ci/generate_docs.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import json +import urllib +import urllib2 +import tempfile +import zipfile +import glob +import subprocess +import getopt +import sys +import shutil +import os +import string + +FIRST_RELEASE_ID=3084382 +VERSIONS = [ + ('master', 'branch master (stable)', 'stable') +] + +current_dir = os.path.dirname(__file__) +output_dir = getopt.getopt(sys.argv[1:], 'o:')[0][0][1] +github_releases = json.load(urllib2.urlopen('https://api.github.com/repos/marvinroger/homie-esp8266/releases')) + +def generate_docs(tag_name, description, destination_folder): + print('Generating docs for ' + tag_name + ' (' + description + ') at /' + destination_folder + '...') + zip_url = 'https://github.com/marvinroger/homie-esp8266/archive/' + tag_name + '.zip' + zip_path = tempfile.mkstemp()[1] + urllib.urlretrieve(zip_url, zip_path) + + zip_file = zipfile.ZipFile(zip_path, 'r') + unzip_path = tempfile.mkdtemp() + zip_file.extractall(unzip_path) + src_path = glob.glob(unzip_path + '/*')[0] + + if not os.path.isfile(src_path + '/mkdocs.yml'): shutil.copy(current_dir + '/mkdocs.default.yml', src_path + '/mkdocs.yml') + + subprocess.call(['docker', 'run', '--rm', '-it', '-p', '8000:8000', '-v', src_path + ':/docs', 'squidfunk/mkdocs-material', 'build']) + shutil.copytree(src_path + '/site', output_dir + '/' + destination_folder) + print('Done.') + +# Generate docs for branches + +for version in VERSIONS: + generate_docs(version[0], version[1], version[2]) + +# Generate docs for releases + +for release in github_releases: + if (release['id'] < FIRST_RELEASE_ID): continue + + tag_name = release['tag_name'] + version = tag_name[1:] + description = 'release ' + version + + VERSIONS.append((tag_name, description, version)) + generate_docs(tag_name, description, version) + +# Generate index + +versions_html = '' + +docs_index_template_file = open(current_dir + '/docs_index_template.html') +docs_index_template_html = docs_index_template_file.read() +docs_index_template = string.Template(docs_index_template_html) +docs_index = docs_index_template.substitute(versions_html=versions_html) + +docs_index_file = open(output_dir + '/index.html', 'w') +docs_index_file.write(docs_index) +docs_index_file.close() diff --git a/ci/id_rsa.enc b/ci/id_rsa.enc new file mode 100644 index 00000000..a2b5335a Binary files /dev/null and b/ci/id_rsa.enc differ diff --git a/ci/id_rsa.pub b/ci/id_rsa.pub new file mode 100644 index 00000000..b6e42727 --- /dev/null +++ b/ci/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCkK7S8axcIlUN9CF3TUyL24VGxKeXnfZ0iSTcGI7qyyiAF5DEbgOLp0HavmA2gEg0SMnWfFiMOzl9+Kh8ejML2vhDD4VkDkMXepWN2f9v1TgkxFl/S6tyL1IIPy8TEOEdg74o6gYZJdIghP9Xd6Wuu1M4n7bh5kEWRshuSFD6r24YW1/ptiV9WkgTJyesjeS+6UwIWvHVFsjHZEC9lwXGu0knWLrYi0AMW0EUr6V5OHX1AdgipshmaOLDjdN25svG9XAQXxQVeM0jHMCYfP9p/yziJPdrIH5A6tQLHV3Qxc8QNNE8dKPq6zMj3KCrq5H3WGCsjkoQzT6/Hce8j5KWza4iDu0H8HgFgMGY0+/gDISYDNEDHHHO31LO0euW2E55WfkRqsVuhmWbjbnoYBSvy94u8NEKDSWZLRnuzjGJwh9YduirE8AycFdd1pbo6U/oMVB9Q1iTqb5yfSkiwodO6YG61K38d1Chwec/AEohA7kn2wL5rtIjYTuRevJgg4Ud8Lp8DIEUc82aK8/+bXUMPTZm44bJuH1267NMW/A8xyBoDrUtKtuwKI/eizfzC0sTVwYBBGddQBIqIpZePzkOMuunKg6+5r6QaLHMiCBtyNtwA7Hp8mpNXBh/KyavcJJDJQl/v9MQeP2Tw8qLolEjCnwH2vSJHfYzF6o6/JqPedw== marvin@DESKTOP-FASPR5U diff --git a/ci/mkdocs.default.yml b/ci/mkdocs.default.yml new file mode 100644 index 00000000..20f92f2c --- /dev/null +++ b/ci/mkdocs.default.yml @@ -0,0 +1,29 @@ +site_name: Homie for ESP8266 +repo_name: 'marvinroger/homie-esp8266' +repo_url: 'https://github.com/marvinroger/homie-esp8266' + +theme: material +extra: + palette: + primary: red + accent: red + +markdown_extensions: + - meta + - footnotes + - codehilite + - admonition + - toc(permalink=true) + - pymdownx.arithmatex + - pymdownx.betterem(smart_enable=all) + - pymdownx.caret + - pymdownx.critic + - pymdownx.emoji: + emoji_generator: !!python/name:pymdownx.emoji.to_svg + - pymdownx.inlinehilite + - pymdownx.magiclink + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences + - pymdownx.tasklist(custom_checkbox=true) + - pymdownx.tilde diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..7c6e3550 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,7 @@ +Welcome to the homie-esp8266 docs. This is also available at http://homie-esp8266.readthedocs.io/en/:version + +Replace `:version` by: + +* `latest` to access the latest development branch docs +* `stable` to access the latest stable docs +* `vx.x.x` to access specific version docs diff --git a/docs/advanced-usage/branding.md b/docs/advanced-usage/branding.md new file mode 100644 index 00000000..a2a1e1ee --- /dev/null +++ b/docs/advanced-usage/branding.md @@ -0,0 +1,8 @@ +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: + +```c++ +void setup() { + Homie_setBrand("MyIoTSystem"); // before Homie.setup() + // ... +} +``` diff --git a/docs/advanced-usage/broadcast.md b/docs/advanced-usage/broadcast.md new file mode 100644 index 00000000..f77e452b --- /dev/null +++ b/docs/advanced-usage/broadcast.md @@ -0,0 +1,13 @@ +Your device can react to Homie broadcasts. To do that, you can use a broadcast handler: + +```c++ +bool broadcastHandler(const String& level, const String& value) { + Serial << "Received broadcast level " << level << ": " << value << endl; + return true; +} + +void setup() { + Homie.setBroadcastHandler(broadcastHandler); // before Homie.setup() + // ... +} +``` diff --git a/docs/advanced-usage/built-in-led.md b/docs/advanced-usage/built-in-led.md new file mode 100644 index 00000000..d5fc54a0 --- /dev/null +++ b/docs/advanced-usage/built-in-led.md @@ -0,0 +1,19 @@ +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. + +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. + +```c++ +void setup() { + Homie.disableLedFeedback(); // before Homie.setup() + // ... +} +``` + +You may, instead of completely disable the LED control, set a new LED to control: + +```c++ +void setup() { + Homie.setLedPin(16, HIGH); // before Homie.setup() -- 2nd param is the state of the pin when the LED is o + // ... +} +``` diff --git a/docs/advanced-usage/custom-settings.md b/docs/advanced-usage/custom-settings.md new file mode 100644 index 00000000..03b29083 --- /dev/null +++ b/docs/advanced-usage/custom-settings.md @@ -0,0 +1,38 @@ +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: + +```c++ +HomieSetting percentageSetting("percentage", "A simple percentage"); // id, description + +void setup() { + percentageSetting.setDefaultValue(50).setValidator([] (long candidate) { + return (candidate >= 0) && (candidate <= 100); + }); + + Homie.setup(); +} +``` + +An `HomieSetting` instance can be of the following types: + +Type | Value +---- | ----- +`bool` | `true` or `false` +`long` | An integer from `-2,147,483,648` to `2,147,483,647` +`double` | A floating number that can fit into a `real64_t` +`const char*` | Any string + +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()`. + +For this example, if you want to provide the `percentage` setting, you will have to put in your configuration file: + +```json +{ + "settings": { + "percentage": 75 + } +} +``` + +See the following example for a concrete use case: + +[![GitHub logo](../assets/github.png) CustomSettings.ino](https://github.com/marvinroger/homie-esp8266/blob/develop/examples/CustomSettings/CustomSettings.ino) diff --git a/docs/advanced-usage/deep-sleep.md b/docs/advanced-usage/deep-sleep.md new file mode 100644 index 00000000..da80538a --- /dev/null +++ b/docs/advanced-usage/deep-sleep.md @@ -0,0 +1,29 @@ +Before deep sleeping, you will want to ensure that all messages are sent, including the `$online → false`. To do that, you can call `Homie.prepareToSleep()`. This will disconnect everything cleanly, so that you can call `ESP.deepSleep()`. + +```c++ +#include + +void onHomieEvent(const HomieEvent& event) { + switch(event.type) { + case HomieEventType::MQTT_CONNECTED: + Homie.getLogger() << "MQTT connected, preparing for deep sleep..." << endl; + Homie.prepareToSleep(); + break; + case HomieEventType::READY_TO_SLEEP: + Homie.getLogger() << "Ready to sleep" << endl; + ESP.deepSleep(); + break; + } +} + +void setup() { + Serial.begin(115200); + Serial << endl << endl; + Homie.onEvent(onHomieEvent); + Homie.setup(); +} + +void loop() { + Homie.loop(); +} +``` diff --git a/docs/advanced-usage/events.md b/docs/advanced-usage/events.md new file mode 100644 index 00000000..ff6e8d45 --- /dev/null +++ b/docs/advanced-usage/events.md @@ -0,0 +1,64 @@ +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: + +```c++ +void onHomieEvent(const HomieEvent& event) { + switch(event.type) { + case HomieEventType::STANDALONE_MODE: + // Do whatever you want when standalone mode is started + break; + case HomieEventType::CONFIGURATION_MODE: + // Do whatever you want when configuration mode is started + break; + case HomieEventType::NORMAL_MODE: + // Do whatever you want when normal mode is started + break; + case HomieEventType::OTA_STARTED: + // Do whatever you want when OTA is started + break; + case HomieEventType::OTA_FAILED: + // Do whatever you want when OTA is failed + break; + case HomieEventType::OTA_SUCCESSFUL: + // Do whatever you want when OTA is successful + break; + case HomieEventType::ABOUT_TO_RESET: + // Do whatever you want when the device is about to reset + break; + case HomieEventType::WIFI_CONNECTED: + // Do whatever you want when Wi-Fi is connected in normal mode + + // You can use event.ip, event.gateway, event.mask + break; + case HomieEventType::WIFI_DISCONNECTED: + // Do whatever you want when Wi-Fi is disconnected in normal mode + + // You can use event.wifiReason + break; + case HomieEventType::MQTT_CONNECTED: + // Do whatever you want when MQTT is connected in normal mode + break; + case HomieEventType::MQTT_DISCONNECTED: + // Do whatever you want when MQTT is disconnected in normal mode + + // You can use event.mqttReason + break; + case HomieEventType::MQTT_PACKET_ACKNOWLEDGED: + // Do whatever you want when an MQTT packet with QoS > 0 is acknowledged by the broker + + // You can use event.packetId + break; + case HomieEventType::READY_TO_SLEEP: + // After you've called `prepareToSleep()`, the event is triggered when MQTT is disconnected + break; + } +} + +void setup() { + Homie.onEvent(onHomieEvent); // before Homie.setup() + // ... +} +``` + +See the following example for a concrete use case: + +[![GitHub logo](../assets/github.png) HookToEvents.ino](https://github.com/marvinroger/homie-esp8266/blob/develop/examples/HookToEvents/HookToEvents.ino) diff --git a/docs/advanced-usage/input-handlers.md b/docs/advanced-usage/input-handlers.md new file mode 100644 index 00000000..b31b6ac4 --- /dev/null +++ b/docs/advanced-usage/input-handlers.md @@ -0,0 +1,59 @@ +There are four types of input handlers: + +* Global input handler. This unique handler will handle every changed settable properties for all nodes + +```c++ +bool globalInputHandler(const HomieNode& node, const String& property, const HomieRange& range, const String& value) { + +} + +void setup() { + Homie.setGlobalInputHandler(globalInputHandler); // before Homie.setup() + // ... +} +``` + +* Node input handlers. This handler will handle every changed settable properties of a specific node + +```c++ +bool nodeInputHandler(const String& property, const HomieRange& range, const String& value) { + +} + +HomieNode node("id", "type", nodeInputHandler); +``` + +* Virtual callback from node input handler + +You can create your own class derived from HomieNode that implements the virtual method `bool HomieNode::handleInput(const String& property, const String& value)`. The default node input handler then automatically calls your callback. + +```c++ +class RelaisNode : public HomieNode { + public: + RelaisNode(): HomieNode("Relais", "switch8"); + + protected: + virtual bool handleInput(const String& property, const HomieRange& range, const String& value) { + + } +}; +``` + +* Property input handlers. This handler will handle changes for a specific settable property of a specific node + +```c++ +bool propertyInputHandler(const HomieRange& range, const String& value) { + +} + +HomieNode node("id", "type"); + +void setup() { + node.advertise("property").settable(propertyInputHandler); // before Homie.setup() + // ... +} +``` + +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. + +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. diff --git a/docs/advanced-usage/logging.md b/docs/advanced-usage/logging.md new file mode 100644 index 00000000..e1b48ece --- /dev/null +++ b/docs/advanced-usage/logging.md @@ -0,0 +1,26 @@ +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. + +```c++ +void setup() { + Homie.disableLogging(); // before Homie.setup() + // ... +} +``` + +!!! warning + It's up to you to call `Serial.begin();`, whether logging is enabled or not. + +You can also change the `Print` instance to log to: + +```c++ +void setup() { + Homie.setLoggingPrinter(&Serial2); // before Homie.setup() + // ... +} +``` + +You can use the logger from your code with the `getLogger()` client: + +```c++ +Homie.getLogger() << "Hey!" << endl; +``` diff --git a/docs/advanced-usage/magic-bytes.md b/docs/advanced-usage/magic-bytes.md new file mode 100644 index 00000000..a13b2a5a --- /dev/null +++ b/docs/advanced-usage/magic-bytes.md @@ -0,0 +1,16 @@ +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. + +You might be wondering why `Homie_setFirmware()` instead of `Homie.setFirmware()`, this is because we use [special macros](https://github.com/marvinroger/homie-esp8266/blob/8935639bc649a6c71ce817ea4f732988506d020e/src/Homie.hpp#L23-L24) to embed the magic bytes. + +Values are encoded as such within the firmware binary: + +Type|Left boundary|Value|Right boundary +----|-------------|-----|-------------- +Homie magic bytes||`0x25 0x48 0x4F 0x4D 0x49 0x45 0x5F 0x45 0x53 0x50 0x38 0x32 0x36 0x36 0x5F 0x46 0x57 0x25`| +Firmware name|`0xBF 0x84 0xE4 0x13 0x54`||`0x93 0x44 0x6B 0xA7 0x75` +Firmware version|`0x6A 0x3F 0x3E 0x0E 0xE1`||`0xB0 0x30 0x48 0xD4 0x1A` +Firmware brand (only present if `Homie_setBrand()` called, Homie otherwise)|`0xFB 0x2A 0xF5 0x68 0xC0`||`0x6E 0x2F 0x0F 0xEB 0x2D` + +See the following example for a concrete use case: + +[![GitHub logo](../assets/github.png) firmware_parser.py](https://github.com/marvinroger/homie-esp8266/blob/develop/firmware_parser.py) diff --git a/docs/advanced-usage/miscellaneous.md b/docs/advanced-usage/miscellaneous.md new file mode 100644 index 00000000..ae698c92 --- /dev/null +++ b/docs/advanced-usage/miscellaneous.md @@ -0,0 +1,63 @@ +# Know if the device is configured / connected + +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. + +```c++ +void loop() { + if (Homie.isConfigured()) { + // The device is configured, in normal mode + if (Homie.isConnected()) { + // The device is connected + } else { + // The device is not connected + } + } else { + // The device is not configured, in either configuration or standalone mode + } +} +``` + +# Get access to the configuration + +You can get access to the configuration of the device. The representation of the configuration is: + +```c++ +struct ConfigStruct { + char* name; + char* deviceId; + + struct WiFi { + char* ssid; + char* password; + } wifi; + + struct MQTT { + struct Server { + char* host; + uint16_t port; + } server; + char* baseTopic; + bool auth; + char* username; + char* password; + } mqtt; + + struct OTA { + bool enabled; + } ota; +}; +``` + +For example, to access the Wi-Fi SSID, you would do: + +```c++ +Homie.getConfiguration().wifi.ssid; +``` + +# Get access to the MQTT client + +You can get access to the underlying MQTT client. For example, to disconnect from the broker: + +```c++ +Homie.getMqttClient().disconnect(); +``` diff --git a/docs/advanced-usage/range-properties.md b/docs/advanced-usage/range-properties.md new file mode 100644 index 00000000..1c9a6e24 --- /dev/null +++ b/docs/advanced-usage/range-properties.md @@ -0,0 +1,21 @@ +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. + +```c++ +HomieNode stripNode("strip", "strip"); + +bool ledHandler(const HomieRange& range, const String& value) { + Homie.getLogger() << "LED " << range.index << " set to " << value << endl; + + // Now, let's update the actual state of the given led + stripNode.setProperty("led").setRange(range).send(value); +} + +void setup() { + stripNode.advertiseRange("led", 1, 100).settable(ledHandler); + // before Homie.setup() +} +``` + +See the following example for a concrete use case: + +[![GitHub logo](../assets/github.png) LedStrip](https://github.com/marvinroger/homie-esp8266/blob/develop/examples/LedStrip/LedStrip.ino) diff --git a/docs/advanced-usage/resetting.md b/docs/advanced-usage/resetting.md new file mode 100644 index 00000000..d7bdf81b --- /dev/null +++ b/docs/advanced-usage/resetting.md @@ -0,0 +1,35 @@ +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. + +This behavior is configurable: + +```c++ +void setup() { + Homie.setResetTrigger(1, LOW, 2000); // before Homie.setup() + // ... +} +``` + +The device will now reset if pin `1` is `LOW` for `2000`ms. You can also disable completely this reset trigger: + +```c++ +void setup() { + Homie.disableResetTrigger(); // before Homie.setup() + // ... +} +``` + +In addition, you can also trigger a device reset from your sketch: + +```c++ +void loop() { + Homie.reset(); +} +``` + +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. + +```c++ +Homie.setIdle(false); +``` + +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. diff --git a/docs/advanced-usage/standalone-mode.md b/docs/advanced-usage/standalone-mode.md new file mode 100644 index 00000000..8f584025 --- /dev/null +++ b/docs/advanced-usage/standalone-mode.md @@ -0,0 +1,12 @@ +Homie for ESP8266 has a special mode named `standalone`. It was a [requested feature](https://github.com/marvinroger/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. + +To enable this mode, call `Homie.setStandalone()`: + +```c++ +void setup() { + Homie.setStandalone(); // before Homie.setup() + // ... +} +``` + +To actually configure the device, you have to reset it, the same way you would to go from `normal` mode to `configuration` mode. diff --git a/docs/advanced-usage/streaming-operator.md b/docs/advanced-usage/streaming-operator.md new file mode 100644 index 00000000..f420cac6 --- /dev/null +++ b/docs/advanced-usage/streaming-operator.md @@ -0,0 +1,17 @@ +Homie for ESP8266 includes a nice streaming operator to interact with `Print` objects. + +Imagine the following code: + +```c++ +int temperature = 32; +Homie.getLogger().print("The current temperature is "); +Homie.getLogger().print(temperature); +Homie.getLogger().println(" °C."); +``` + +With the streaming operator, the following code will do exactly the same thing, without performance penalties: + +```c++ +int temperature = 32; +Homie.getLogger() << "The current temperature is " << temperature << " °C." << endl; +``` diff --git a/docs/advanced-usage/ui-bundle.md b/docs/advanced-usage/ui-bundle.md new file mode 100644 index 00000000..d589ce0a --- /dev/null +++ b/docs/advanced-usage/ui-bundle.md @@ -0,0 +1,3 @@ +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. + +Indeed, you can serve the [configuration UI](http://setup.homie-esp8266.marvinroger.fr/) directly from your ESP8266. See [the data/homie folder](https://github.com/marvinroger/homie-esp8266/tree/develop/data/homie). diff --git a/docs/assets/github.png b/docs/assets/github.png new file mode 100644 index 00000000..fd168a46 Binary files /dev/null and b/docs/assets/github.png differ diff --git a/docs/assets/led_mqtt.gif b/docs/assets/led_mqtt.gif new file mode 100644 index 00000000..baf07ca0 Binary files /dev/null and b/docs/assets/led_mqtt.gif differ diff --git a/docs/assets/led_solid.gif b/docs/assets/led_solid.gif new file mode 100644 index 00000000..57aae4ee Binary files /dev/null and b/docs/assets/led_solid.gif differ diff --git a/docs/assets/led_wifi.gif b/docs/assets/led_wifi.gif new file mode 100644 index 00000000..d7f21605 Binary files /dev/null and b/docs/assets/led_wifi.gif differ diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 00000000..ccfc5e63 Binary files /dev/null and b/docs/assets/logo.png differ diff --git a/docs/assets/youtube.png b/docs/assets/youtube.png new file mode 100644 index 00000000..ffb6613d Binary files /dev/null and b/docs/assets/youtube.png differ diff --git a/docs/configuration/http-json-api.md b/docs/configuration/http-json-api.md new file mode 100644 index 00000000..5055a220 --- /dev/null +++ b/docs/configuration/http-json-api.md @@ -0,0 +1,277 @@ +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`. + +If you don't want to mess with JSON, you have a Web UI / app available: +* At http://setup.homie-esp8266.marvinroger.fr/ +* As an [Android app](https://build.phonegap.com/apps/1906578/share) + +**Quick instructions to use the Web UI / app**: + +1. Open the Web UI / app +2. Disconnect from your current Wi-Fi AP, and connect to the `Homie-xxxxxxxxxxxx` AP spawned in `configuration` mode +3. Follow the instructions + +You can see the sources of the Web UI [here](https://github.com/marvinroger/homie-esp8266-setup). + +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: + +```shell +curl -X PUT http://192.168.123.1/config --header "Content-Type: application/json" -d @config.json +``` + +This will send the `./config.json` file to the device. + +# Error handling + +When everything went fine, a `2xx` HTTP code is returned, such as `200 OK`, `202 Accepted`, `204 No Content` and so on. +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. + +# Endpoints + +**API base address:** `http://192.168.123.1` + +!!! summary "GET `/heart`" + +This is useful to ensure we are connected to the device AP. + +## Response + +204 No Content + +-------------- + +!!! summary "GET `/device-info`" + + +Get some information on the device. + +## Response + +200 OK (application/json) + +```json +{ + "hardware_device_id": "52a8fa5d", + "homie_esp8266_version": "2.0.0", + "firmware": { + "name": "awesome-device", + "version": "1.0.0" + }, + "nodes": [ + { + "id": "light", + "type": "light" + } + ], + "settings": [ + { + "name": "timeout", + "description": "Timeout in seconds", + "type": "ulong", + "required": false, + "default": 10 + } + ] +} +``` + +**Note about settings:** If a setting is no required, the `default` field is always present. `type` can be one of the following: + +* `bool`: a boolean +* `ulong`: an unsigned long +* `long`: a long +* `double`: a double +* `string`: a string + +-------------- + +!!! summary "GET `/networks`" + +Retrieve the Wi-Fi networks the device can see. + +## Response + +!!! success "In case of success" + +200 OK (application/json) + +```json +{ + "networks": [ + { "ssid": "Network_2", "rssi": -82, "encryption": "wep" }, + { "ssid": "Network_1", "rssi": -57, "encryption": "wpa" }, + { "ssid": "Network_3", "rssi": -65, "encryption": "wpa2" }, + { "ssid": "Network_5", "rssi": -94, "encryption": "none" }, + { "ssid": "Network_4", "rssi": -89, "encryption": "auto" } + ] +} +``` + +!!! failure "In case the initial Wi-Fi scan is not finished on the device" + +503 Service Unavailable (application/json) + +```json +{ + "error": "Initial Wi-Fi scan not finished yet" +} +``` + +-------------- + +!!! summary "PUT `/config`" + +Save the config to the device. + +## Request body + +(application/json) + +See [JSON configuration file](https://homie-esp8266.readme.io/docs/json-configuration-file). + +## Response + +!!! success "In case of success" + +200 OK (application/json) + +```json +{ + "success": true +} +``` + +!!! failure "In case of error in the payload" + +400 Bad Request (application/json) + +```json +{ + "success": false, + "error": "Reason why the payload is invalid" +} +``` + +!!! failure "In case the device already received a valid configuration and is waiting for reboot" + +403 Forbidden (application/json) + +```json +{ + "success": false, + "error": "Device already configured" +} +``` + +-------------- + +!!! summary "GET `/wifi/connect`" + +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`. + +## Request body + +(application/json) + +```json +{ + "ssid": "My_SSID", + "password": "my-passw0rd" +} +``` + +## Response + +!!! success "In case of success" + +202 Accepted (application/json) + +```json +{ + "success": true +} +``` + +!!! failure "In case of error in the payload" + +400 Bad Request (application/json) + +```json +{ + "success": false, + "error": "Reason why the payload is invalid" +} +``` + +-------------- + +!!! summary "GET `/wifi/status`" + +Returns the current Wi-Fi connection status. + +Helpful when monitoring Wi-Fi connectivity after `PUT /wifi/connect`. + +## Response + +200 OK (application/json) + +```json +{ + "status": "connected" +} +``` + +`status` might be one of the following: + +* `idle` +* `connect_failed` +* `connection_lost` +* `no_ssid_available` +* `connected` along with a `local_ip` field +* `disconnected` + +-------------- + +!!! summary "PUT `/proxy/control`" + +Enable/disable the device to act as a transparent proxy between AP and Station networks. + +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. + +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`). + +HTTPS is not supported. + +**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. + +## Request body + +(application/json) + +```json +{ + "enable": true +} +``` + +## Response + +!!! success "In case of success" + +200 OK (application/json) + +```json +{ + "success": true +} +``` + +!!! failure "In case of error in the payload" + +400 Bad Request (application/json) + +```json +{ + "success": false, + "error": "Reason why the payload is invalid" +} +``` diff --git a/docs/configuration/json-configuration-file.md b/docs/configuration/json-configuration-file.md new file mode 100644 index 00000000..87361caa --- /dev/null +++ b/docs/configuration/json-configuration-file.md @@ -0,0 +1,57 @@ +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). + +Below is the format of the JSON configuration you will have to provide: + +```json +{ + "name": "The kitchen light", + "device_id": "kitchen-light", + "wifi": { + "ssid": "Network_1", + "password": "I'm a Wi-Fi password!", + "bssid": "DE:AD:BE:EF:BA:BE", + "channel": 1, + "ip": "192.168.1.5", + "mask": "255.255.255.0", + "gw": "192.168.1.1", + "dns1": "8.8.8.8", + "dns2": "8.8.4.4" + }, + "mqtt": { + "host": "192.168.1.10", + "port": 1883, + "base_topic": "devices/", + "auth": true, + "username": "user", + "password": "pass" + }, + "ota": { + "enabled": true + }, + "settings": { + "percentage": 55 + } +} +``` + +The above JSON contains every field that can be customized. + +Here are the rules: + +* `name`, `wifi.ssid`, `wifi.password`, `mqtt.host` and `ota.enabled` are mandatory +* `wifi.password` can be `null` if connecting to an open network +* If `mqtt.auth` is `true`, `mqtt.username` and `mqtt.password` must be provided +* `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: + - `bssid` and `channel` have to be defined together and these settings are independand of settings related to static IP + - to define static IP, `ip` (IP address), `mask` (netmask) and `gw` (gateway) settings have to be defined at the same time + - 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. + + +Default values if not provided: + +* `device_id`: the hardware device ID (eg. `1a2b3c4d5e6f`) +* `mqtt.port`: `1883` +* `mqtt.base_topic`: `homie/` +* `mqtt.auth`: `false` + +The `mqtt.host` field can be either an IP or an hostname. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..74a4a83e --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +Welcome on the Homie for ESP8266 docs. diff --git a/docs/others/community-projects.md b/docs/others/community-projects.md new file mode 100644 index 00000000..faceb1a5 --- /dev/null +++ b/docs/others/community-projects.md @@ -0,0 +1,19 @@ +This page lists the projects made by the community to work with Homie. + +# [jpmens/homie-ota](https://github.com/jpmens/homie-ota) + +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. + +# [stufisher/homie-control](https://github.com/stufisher/homie-control) + +homie-control provides a web UI to manage Homie devices as well as a series of virtual python devices to allow extended functionality. + +Its lets you do useful things like: + +* Historically log device properties +* Schedule changes in event properties (i.e. water your garden once a day) +* Execute profiles of property values (i.e. turn a series of lights on and off simultaneously) +* Trigger property changes based on: + * When a network device is dis/connected (i.e. your phone joins your wifi, turn the lights on) + * Sunset / rise + * When another property changes diff --git a/docs/others/cpp-api-reference.md b/docs/others/cpp-api-reference.md new file mode 100644 index 00000000..dd02ccf5 --- /dev/null +++ b/docs/others/cpp-api-reference.md @@ -0,0 +1,317 @@ +# Homie + +You don't have to instantiate an `Homie` instance, it is done internally. + +```c++ +void setup(); +``` + +Setup Homie. + +!!! warning "Mandatory!" + Must be called once in `setup()`. + +```c++ +void loop(); +``` + +Handle Homie work. + +!!! warning "Mandatory!" + Must be called once in `loop()`. + +## Functions to call *before* `Homie.setup()` + +```c++ +void Homie_setFirmware(const char* name, const char* version); +// This is not a typo +``` + +Set the name and version of the firmware. This is useful for OTA, as Homie will check against the server if there is a newer version. + +!!! warning "Mandatory!" + You need to set the firmware for your sketch to work. + + +* **`name`**: Name of the firmware. Default value is `undefined` +* **`version`**: Version of the firmware. Default value is `undefined` + +```c++ +void Homie_setBrand(const char* name); +// This is not a typo +``` + +Set the brand of the device, used in the configuration AP, the device hostname and the MQTT client ID. + +* **`name`**: Name of the brand. Default value is `Homie` + +```c++ +Homie& disableLogging(); +``` + +Disable Homie logging. + +```c++ +Homie& setLoggingPrinter(Print* printer); +``` + +Set the Print instance used for logging. + +* **`printer`**: Print instance to log to. By default, `Serial` is used + +!!! warning + It's up to you to call `Serial.begin()` + +```c++ +Homie& disableLedFeedback(); +``` + +Disable the built-in LED feedback indicating the Homie for ESP8266 state. + +```c++ +Homie& setLedPin(uint8_t pin, uint8_t on); +``` + +Set pin of the LED to control. + +* **`pin`**: LED to control +* **`on`**: state when the light is on (HIGH or LOW) + +```c++ +Homie& setConfigurationApPassword(const char* password); +``` + +Set the configuration AP password. + +* **`password`**: the configuration AP password + +```c++ +Homie& setGlobalInputHandler(std::function handler); +``` + +Set input handler for subscribed properties. + +* **`handler`**: Global input handler +* **`node`**: Name of the node getting updated +* **`property`**: Property of the node getting updated +* **`range`**: Range of the property of the node getting updated +* **`value`**: Value of the new property + +```c++ +Homie& setBroadcastHandler(std::function handler); +``` + +Set broadcast handler. + +* **`handler`**: Broadcast handler +* **`level`**: Level of the broadcast +* **`value`**: Value of the broadcast + +```c++ +Homie& onEvent(std::function callback); +``` + +Set the event handler. Useful if you want to hook to Homie events. + +* **`callback`**: Event handler + +```c++ +Homie& setResetTrigger(uint8_t pin, uint8_t state, uint16_t time); +``` + +Set the reset trigger. By default, the device will reset when pin `0` is `LOW` for `5000`ms. + +* **`pin`**: Pin of the reset trigger +* **`state`**: Reset when the pin reaches this state for the given time +* **`time`**: Time necessary to reset + +```c++ +Homie& disableResetTrigger(); +``` + +Disable the reset trigger. + +```c++ +Homie& setSetupFunction(std::function callback); +``` + +You can provide the function that will be called when operating in `normal` mode. + +* **`callback`**: Setup function + +```c++ +Homie& setLoopFunction(std::function callback); +``` + +You can provide the function that will be looped in normal mode. + +* **`callback`**: Loop function + +```c++ +Homie& setStandalone(); +``` + +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. + +## Functions to call *after* `Homie.setup()` + +```c++ +void reset(); +``` + +Flag the device for reset. + +```c++ +void setIdle(bool idle); +``` + +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. + +* **`idle`**: Device in an idle state or not + +```c++ +void prepareToSleep(); +``` + +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. + +```c++ +bool isConfigured() const; +``` + +Is the device in `normal` mode, configured? + +```c++ +bool isConnected() const; +``` + +Is the device in `normal` mode, configured and connected? + +```c++ +const ConfigStruct& getConfiguration() const; +``` + +Get the configuration struct. + +!!! danger + Be careful with this struct, never attempt to change it. + +```c++ +AsyncMqttClient& getMqttClient(); +``` + +Get the underlying `AsyncMqttClient` object. + +```c++ +Logger& getLogger(); +``` + +Get the underlying `Logger` object, which is only a wrapper around `Serial` by default. + +------- + +# HomieNode + +```c++ +HomieNode(const char* id, const char* type, std::function handler = ); +``` + +Constructor of an HomieNode object. + +* **`id`**: ID of the node +* **`type`**: Type of the node +* **`handler`**: Optional. Input handler of the node + +```c++ +const char* getId() const; +``` + +Return the ID of the node. + +```c++ +const char* getType() const; +``` + +Return the type of the node. + +```c++ +PropertyInterface& advertise(const char* property); +PropertyInterface& advertiseRange(const char* property, uint16_t lower, uint16_t upper); +``` + +Advertise a property / range property on the node. + +* **`property`**: Property to advertise +* **`lower`**: Lower bound of the range +* **`upper`**: Upper bound of the range + +This returns a reference to `PropertyInterface` on which you can call: + +```c++ +void settable(std::function handler) = ); +``` + +Make the property settable. + +* **`handler`**: Optional. Input handler of the property + +```c++ +SendingPromise& setProperty(const String& property); +``` + +Using this function, you can set the value of a node property, like a temperature for example. + +* **`property`**: Property to send + +This returns a reference to `SendingPromise`, on which you can call: + +```c++ +SendingPromise& setQos(uint8_t qos); // defaults to 1 +SendingPromise& setRetained(bool retained); // defaults to true +SendingPromise& overwriteSetter(bool overwrite); // defaults to false +SendingPromise& setRange(const HomieRange& range); // defaults to not a range +SendingPromise& setRange(uint16_t rangeIndex); // defaults to not a range +uint16_t send(const String& value); // finally send the property, return the packetId (or 0 if failure) +``` + +Method names should be self-explanatory. + +# HomieSetting + +```c++ +HomieSetting(const char* name, const char* description); +``` + +Constructor of an HomieSetting object. + +* **`T`**: Type of the setting. Either `bool`, `unsigned long`, `long`, `double` or `const char*` +* **`name`**: Name of the setting +* **`description`**: Description of the setting + +```c++ +T get() const; +``` + +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. + +```c++ +bool wasProvided() const; +``` + +Return whether the setting was provided or not (otherwise `get()` would return the default value). + +Set the default value and make the setting optional. + +```c++ +HomieSetting& setDefaultValue(T defaultValue); +``` + +* **`defaultValue`**: The default value + +```c++ +HomieSetting& setValidator(std::function validator); +``` + +Set a validation function for the setting. The validator must return `true` if the candidate is correct, `false` otherwise. + +* **`validator`**: The validation function diff --git a/docs/others/homie-implementation-specifics.md b/docs/others/homie-implementation-specifics.md new file mode 100644 index 00000000..0f1fbc67 --- /dev/null +++ b/docs/others/homie-implementation-specifics.md @@ -0,0 +1,30 @@ +The Homie `$implementation` identifier is `esp8266`. + +# Version + +* `$implementation/version`: Homie for ESP8266 version + +# Reset + +* `$implementation/reset`: You can publish a `true` to this topic to reset the device + +# Configuration + +* `$implementation/config`: The `configuration.json` is published there, with `wifi.password`, `mqtt.username` and `mqtt.password` fields stripped +* `$implementation/config/set`: You can update the `configuration.json` by sending incremental JSON on this topic + +# OTA + +* `$implementation/ota/enabled`: `true` if OTA is enabled, `false` otherwise +* `$implementation/ota/firmware`: If the update request is accepted, you must send the firmware payload to this topic +* `$implementation/ota/status`: HTTP-like status code indicating the status of the OTA. Might be: + +Code|Description +----|----------- +`200`|OTA successfully flashed +`202`|OTA request / checksum accepted +`206 465/349680`|OTA in progress. The data after the status code corresponds to `/` +`304`|The current firmware is already up-to-date +`400 BAD_FIRMWARE`|OTA error from your side. The identifier might be `BAD_FIRMWARE`, `BAD_CHECKSUM`, `NOT_ENOUGH_SPACE`, `NOT_REQUESTED` +`403`|OTA not enabled +`500 FLASH_ERROR`|OTA error on the ESP8266. The identifier might be `FLASH_ERROR` diff --git a/docs/others/limitations-and-known-issues.md b/docs/others/limitations-and-known-issues.md new file mode 100644 index 00000000..4849a012 --- /dev/null +++ b/docs/others/limitations-and-known-issues.md @@ -0,0 +1,11 @@ +# SSL support + +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 available anymore. + +# ADC readings + +[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. + +# Wi-Fi connection + +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/marvinroger/homie-esp8266/issues/158) for more information. diff --git a/docs/others/ota-configuration-updates.md b/docs/others/ota-configuration-updates.md new file mode 100644 index 00000000..97e087fd --- /dev/null +++ b/docs/others/ota-configuration-updates.md @@ -0,0 +1,58 @@ +# OTA updates + +Homie for ESP8266 supports OTA, if enabled in the configuration, and if a compatible OTA entity is set up. + +It works this way: + +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 +2. The OTA entity publishes the latest available firmware MD5 to `$implementation/ota/checksum` + * If OTA is disabled, Homie for ESP8266 reports `403` to `$implementation/ota/status` and aborts the OTA + * 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 + * If the checksum is not a valid MD5, Homie for ESP8266 reports `400 BAD_CHECKSUM` to `$implementation/ota/status` and aborts the OTA + * Otherwise, Homie for ESP8266 reports `202` and subscribes to `$implementation/ota/firmware` +3. The OTA entity publishes the firmware to `$implementation/ota/firmware`. It can do so as a binary or as a Base64 encoded string + * The firmware is updating. Homie for ESP8266 reports progress with `206 /` + * When all bytes are flashed, the firmware is verified (including the MD5 if one was set) + * Homie for ESP8266 either reports `200` on success, `400` if the firmware in invalid or `500` if there's an internal error +5. Homie for ESP8266 reboots on success as soon as the device is idle + +See [Homie implementation specifics](homie-implementation-specifics.md) for more details on status codes. + +## OTA entities projects + +See [Community projects](community-projects.md). + +# Configuration updates + +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`: + +```json +{ + "name": "Kitchen light", + "wifi": { + "ssid": "Network_1", + "password": "I'm a Wi-Fi password!" + }, + "mqtt": { + "host": "192.168.1.20", + "port": 1883 + }, + "ota": { + "enabled": false + }, + "settings": { + + } +} +``` + +You can update the name and Wi-Fi password by sending the following incremental JSON: + +```json +{ + "name": "Living room light", + "wifi": { + "password": "I'am a new Wi-Fi password!" + } +} +``` diff --git a/docs/others/troubleshooting.md b/docs/others/troubleshooting.md new file mode 100644 index 00000000..0dc71b79 --- /dev/null +++ b/docs/others/troubleshooting.md @@ -0,0 +1,37 @@ +## 1. I see some garbage on the Serial monitor? + +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: + +* Disable the serial logging, to have the LED working: + +```c++ +void setup() { + Homie.enableLogging(false); // before Homie.setup() + // ... +} +``` + +* Disable the LED blinking, to have the serial line working: + +```c++ +void setup() { + Homie.enableBuiltInLedIndicator(false); // before Homie.setup() + // ... +} +``` + +## 2. I see an `abort` message on the Serial monitor? + +`abort()` is called by Homie for ESP8266 when the framework is used in a bad way. The possible causes are: + +* You are calling a function that is meant to be called before `Homie.setup()`, after `Homie.setup()` + +* 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. + +## 3. The network is completely unstable... What's going on? + +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). + +## 4. My device resets itself without me doing anything? + +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). diff --git a/docs/others/upgrade-guide-from-v1-to-v2.md b/docs/others/upgrade-guide-from-v1-to-v2.md new file mode 100644 index 00000000..e7a69526 --- /dev/null +++ b/docs/others/upgrade-guide-from-v1-to-v2.md @@ -0,0 +1,15 @@ +This is an upgrade guide to upgrade your Homie devices from v1 to v2. + +## New convention + +The Homie convention has been revised to v2 to be more extensible and introspectable. Be sure to [check it out](https://github.com/marvinroger/homie/tree/v2). + +## API changes in the sketch + +1. `Homie.setFirmware(name, version)` must be replaced by `Homie_setFirmware(name, version)` +2. `Homie.setBrand(brand)` must be replaced by `Homie_setBrand(brand)` +3. `Homie.registerNode()` must be removed, nodes are now automagically registered +4. If you've enabled Serial logging, `Serial.begin()` must be called explicitely in your sketch +5. Remove the `HOMIE_OTA_MODE` in your event handler, if you have one +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. +7. TODO diff --git a/docs/quickstart/getting-started.md b/docs/quickstart/getting-started.md new file mode 100644 index 00000000..63826322 --- /dev/null +++ b/docs/quickstart/getting-started.md @@ -0,0 +1,141 @@ +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). + +To use Homie for ESP8266, you will need: + +* An ESP8266 +* The Arduino IDE for ESP8266 (version 2.3.0 minimum) +* Basic knowledge of the Arduino environment (upload a sketch, import libraries, ...) +* To understand [the Homie convention](https://github.com/marvinroger/homie) + +## Installing Homie for ESP8266 + +There are two ways to install Homie for ESP8266. + +### 1a. For the Arduino IDE + +There is a YouTube video with instructions: + +[![YouTube logo](../assets/youtube.png) How to install Homie libraries on Arduino IDE](https://www.youtube.com/watch?v=bH3KfFfYUvg) + +1. Download the [release corresponding to this documentation version](https://github.com/marvinroger/homie-esp8266/releases) + +2. Load the `.zip` with **Sketch → Include Library → Add .ZIP Library** + +Homie for ESP8266 has 4 dependencies: + +* [ArduinoJson](https://github.com/bblanchon/ArduinoJson) >= 5.0.8 +* [Bounce2](https://github.com/thomasfredericks/Bounce2) +* [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) >= [c8ed544](https://github.com/me-no-dev/ESPAsyncTCP) +* [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) + +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. + +### 1b. With [PlatformIO](http://platformio.org) + +In a terminal, run `platformio lib install 555`. + +!!! warning "Not yet released as stable" + 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**: + + ``` + lib_deps = git+https://github.com/marvinroger/homie-esp8266.git#develop + ``` + +Dependencies are installed automatically. + +## Bare minimum sketch + +```c++ +#include + +void setup() { + Serial.begin(115200); + Serial << endl << endl; + + Homie_setFirmware("bare-minimum", "1.0.0"); // The underscore is not a typo! See Magic bytes + Homie.setup(); +} + +void loop() { + Homie.loop(); +} +``` + + +This is the bare minimum needed for Homie for ESP8266 to work correctly. + +!!! tip "LED" + ![Solid LED](../assets/led_solid.gif) + If you upload this sketch, you will notice the LED of the ESP8266 will light on. This is because you are in `configuration` mode. + +Homie for ESP8266 has 3 modes of operation: + +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. + +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)). + +3. The `standalone` mode. See [Standalone mode](../advanced-usage/standalone-mode.md). + +!!! warning + **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. + +## Connecting to the AP and configuring the device + +Homie for ESP8266 has spawned a secure AP named `Homie-xxxxxxxxxxxx`, like `Homie-c631f278df44`. Connect to it. + +!!! tip "Hardware device ID" + 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. + +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. + +## Understanding what happens in `normal` mode + +### Visual codes + +When the device boots in `normal` mode, it will start blinking: + +!!! tip "LED" + ![Slowly blinking LED](../assets/led_wifi.gif) + Slowly when connecting to the Wi-Fi + +!!! tip "LED" + ![Fast blinking LED](../assets/led_mqtt.gif) + Faster when connecting to the MQTT broker + +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. + +### Under the hood + +Although the sketch looks like it does not do anything, it actually does quite a lot: + +* It automatically connects to the Wi-Fi and MQTT broker. No more network boilerplate code +* It exposes the Homie device on MQTT (as `/`, e.g. `homie/c631f278df44`) +* It subscribes to the special OTA and configuration topics, automatically flashing a sketch if available or updating the configuration +* It checks for a button press on the ESP8266, to return to `configuration` mode + +## Creating an useful sketch + +Now that we understand how Homie for ESP8266 works, let's create an useful sketch. We want to create a smart light. + +[![GitHub logo](../assets/github.png) LightOnOff.ino](https://github.com/marvinroger/homie-esp8266/blob/develop/examples/LightOnOff/LightOnOff.ino) + +Alright, step by step: + +1. We create a node with an ID of `light` and a type of `switch` with `HomieNode lightNode("light", "switch")` +2. We set the name and the version of the firmware with `Homie_setFirmware("awesome-light" ,"1.0.0");` +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 +4. In the `lightOnHandler` function, we want to update the state of the `light` node. We do this with `lightNode.setProperty("on").send("true");` + +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? + +## Creating a sensor node + +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. + +Fortunately, Homie for ESP8266 provides an easy way to do that. + +[![GitHub logo](../assets/github.png) TemperatureSensor.ino](https://github.com/marvinroger/homie-esp8266/blob/develop/examples/TemperatureSensor/TemperatureSensor.ino) + +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. + +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. diff --git a/docs/quickstart/what-is-it.md b/docs/quickstart/what-is-it.md new file mode 100644 index 00000000..87303ee2 --- /dev/null +++ b/docs/quickstart/what-is-it.md @@ -0,0 +1,3 @@ +Homie for ESP8266 is an ESP8266 for Arduino implementation of [Homie](https://github.com/marvinroger/homie), 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. + +You guessed it, the purpose of Homie for ESP8266 is to simplify the development of connected objects. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..fc92e477 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,68 @@ +site_name: Homie for ESP8266 +repo_name: 'marvinroger/homie-esp8266' +repo_url: 'https://github.com/marvinroger/homie-esp8266' + +pages: + - Welcome: index.md + - Quickstart: + - What is it?: quickstart/what-is-it.md + - Getting started: quickstart/getting-started.md + - Advanced usage: + - Built-in LED: advanced-usage/built-in-led.md + - Branding: advanced-usage/branding.md + - Events: advanced-usage/events.md + - Logging: advanced-usage/logging.md + - Streaming operator: advanced-usage/streaming-operator.md + - Input handlers: advanced-usage/input-handlers.md + - Broadcast: advanced-usage/broadcast.md + - Custom settings: advanced-usage/custom-settings.md + - Resetting: advanced-usage/resetting.md + - Standalone mode: advanced-usage/standalone-mode.md + - Magic bytes: advanced-usage/magic-bytes.md + - Range properties: advanced-usage/range-properties.md + - Deep sleep: advanced-usage/deep-sleep.md + - Miscellaneous: advanced-usage/miscellaneous.md + - UI Bundle: advanced-usage/ui-bundle.md + - Configuration: + - JSON configuration file: configuration/json-configuration-file.md + - HTTP JSON API: configuration/http-json-api.md + - Others: + - OTA/configuration updates: others/ota-configuration-updates.md + - Homie implementation specifics: others/homie-implementation-specifics.md + - Limitations and known issues: others/limitations-and-known-issues.md + - Troubleshooting: others/troubleshooting.md + - C++ API reference: others/cpp-api-reference.md + - Upgrade guide from v1 to v2: others/upgrade-guide-from-v1-to-v2.md + - Community projects: others/community-projects.md + +theme: material +extra: + logo: assets/logo.png + palette: + primary: red + accent: red + feature: + tabs: true + social: + - type: cog + link: http://setup.homie-esp8266.marvinroger.fr + +markdown_extensions: + - meta + - footnotes + - codehilite + - admonition + - toc(permalink=true) + - pymdownx.arithmatex + - pymdownx.betterem(smart_enable=all) + - pymdownx.caret + - pymdownx.critic + - pymdownx.emoji: + emoji_generator: !!python/name:pymdownx.emoji.to_svg + - pymdownx.inlinehilite + - pymdownx.magiclink + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences + - pymdownx.tasklist(custom_checkbox=true) + - pymdownx.tilde