From 5ed6161c7c5107254fb8a8afe92587ccdf0f71be Mon Sep 17 00:00:00 2001 From: igor Date: Mon, 3 May 2021 16:22:16 +0600 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=B8=D1=82=20=D0=B2=20=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=BC=20=D0=BF=D0=BE=D0=BA=D0=B0=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=87=D1=82=D0=B8=20=D0=BD=D0=B8=D1=87=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + .vscode/extensions.json | 7 + data/index.html | 353 ++++++++++++++++++ data/settings.txt | 8 + data/uuid.txt | 1 + include/Bounce2.h | 305 ++++++++++++++++ include/PubSubClient.h | 184 ++++++++++ include/README | 39 ++ include/tools.h | 14 + lib/README | 46 +++ platformio.ini | 20 ++ src/Bounce2.cpp | 155 ++++++++ src/PubSubClient.cpp | 769 ++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 593 +++++++++++++++++++++++++++++++ src/tools.cpp | 82 +++++ test/README | 11 + 16 files changed, 2592 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 data/index.html create mode 100644 data/settings.txt create mode 100644 data/uuid.txt create mode 100644 include/Bounce2.h create mode 100644 include/PubSubClient.h create mode 100644 include/README create mode 100644 include/tools.h create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/Bounce2.cpp create mode 100644 src/PubSubClient.cpp create mode 100644 src/main.cpp create mode 100644 src/tools.cpp create mode 100644 test/README diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..e80666b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/data/index.html b/data/index.html new file mode 100644 index 0000000..28d79df --- /dev/null +++ b/data/index.html @@ -0,0 +1,353 @@ + + + + + + Конфигурация + + + + +
+ +

Настройка

+

Устройство заснёт через: ___ секунд

+

Выберите свою Wi-Fi сеть

+ +
+
+ +

Точка доступа

+ +

Пароль

+ + +

+ Для проверки работоспособности спустите не меньше 4х литров воды +

+ +

Текущее показание холодной воды

+

___

+

Новое показание холодной воды

+ + + + + +
+ + + Литры +
+ + +

Текущее показание горячей воды

+

___

+ +

Новое показание горячей воды

+ + + + + +
+ + + Литры +
+ + + +

Протечка не обнаружена

+ +

+ + +

+ + + +
+ +

+ + +
+ + diff --git a/data/settings.txt b/data/settings.txt new file mode 100644 index 0000000..39ead40 --- /dev/null +++ b/data/settings.txt @@ -0,0 +1,8 @@ +ssid=Node6 +pass=isecretk +mqtt_server=192.168.200.100 +mqtt_port=1883 +mqtt_fingerprint=32:95:81:45:9C:32:F3:BD:B3:09:63:58:D3:94:AC:19:78:45:3B:15 +mqtt_user=xxxxxx +mqtt_pass=xxxxxx +mqtt_topic=home/lights/main diff --git a/data/uuid.txt b/data/uuid.txt new file mode 100644 index 0000000..3b9aa0f --- /dev/null +++ b/data/uuid.txt @@ -0,0 +1 @@ +123456789012345 \ No newline at end of file diff --git a/include/Bounce2.h b/include/Bounce2.h new file mode 100644 index 0000000..150e77c --- /dev/null +++ b/include/Bounce2.h @@ -0,0 +1,305 @@ +/* + The MIT License (MIT) + + Copyright (c) 2013 thomasfredericks + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Main code by Thomas O Fredericks (tof@t-o-f.info) + Previous contributions by Eric Lowry, Jim Schimpf and Tom Harkaway + * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#ifndef Bounce2_h +#define Bounce2_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +// Uncomment the following line for "LOCK-OUT" debounce method +//#define BOUNCE_LOCK_OUT + +// Uncomment the following line for "BOUNCE_WITH_PROMPT_DETECTION" debounce method +//#define BOUNCE_WITH_PROMPT_DETECTION + +#include + +/** + @example bounce_basic.ino + Basic example of the Bounce class. +*/ + +/** + @example button_basic.ino + Basic example of the Button class. +*/ + + + +/** + @brief The Debouce class. Just the deboucing code separated from all harware. +*/ +class Debouncer +{ + // Note : this is private as it migh change in the futur +private: + static const uint8_t DEBOUNCED_STATE = 0b00000001; + static const uint8_t UNSTABLE_STATE = 0b00000010; + static const uint8_t CHANGED_STATE = 0b00000100; + +private: + inline void changeState(); + inline void setStateFlag(const uint8_t flag) {state |= flag;} + inline void unsetStateFlag(const uint8_t flag) {state &= ~flag;} + inline void toggleStateFlag(const uint8_t flag) {state ^= flag;} + inline bool getStateFlag(const uint8_t flag) {return((state & flag) != 0);} + +public: + /*! + @brief Create an instance of the Debounce class. + + @endcode +*/ + Debouncer(); + + /** + @brief Sets the debounce interval in milliseconds. + + @param interval_millis + The interval time in milliseconds. + + */ + void interval(uint16_t interval_millis); + + /*! + @brief Updates the pin's state. + + Because Bounce does not use interrupts, you have to "update" the object before reading its value and it has to be done as often as possible (that means to include it in your loop()). Only call update() once per loop(). + + @return True if the pin changed state. +*/ + + bool update(); + + /** + @brief Returns the pin's state (HIGH or LOW). + + @return HIGH or LOW. + */ + bool read(); + + /** + @brief Returns true if pin signal transitions from high to low. + */ + bool fell(); + + /** + @brief Returns true if pin signal transitions from low to high. + */ + bool rose(); + + + +public: + /** + @brief Returns true if the state changed on last update. + + @return True if the state changed on last update. Otherwise, returns false. +*/ + bool changed( ) { return getStateFlag(CHANGED_STATE); } + + /** + @brief Returns the duration in milliseconds of the current state. + + Is reset to 0 once the pin rises ( rose() ) or falls ( fell() ). + + @return The duration in milliseconds (unsigned long) of the current state. + */ + + unsigned long duration(); + + /** + @brief Returns the duration in milliseconds of the previous state. + + Takes the values of duration() once the pin changes state. + + @return The duration in milliseconds (unsigned long) of the previous state. + */ + unsigned long previousDuration(); + void setStateChangeLastTime(unsigned long time){ stateChangeLastTime=time; }; + +protected: + void begin(); + virtual bool readCurrentState() =0; + unsigned long previous_millis; + uint16_t interval_millis; + uint8_t state; + unsigned long stateChangeLastTime; + unsigned long durationOfPreviousState; + +}; + + +/** +@brief The Debouncer:Bounce class. Links the Deboucing class to a hardware pin. + + */ +class Bounce : public Debouncer +{ + + +public: + +/*! + @brief Create an instance of the Bounce class. + + @code + + // Create an instance of the Bounce class. + Bounce() button; + + @endcode +*/ + Bounce(); + + +/*! + @brief Attach to a pin and sets that pin's mode (INPUT, INPUT_PULLUP or OUTPUT). + + @param pin + The pin that is to be debounced. + @param mode + A valid Arduino pin mode (INPUT, INPUT_PULLUP or OUTPUT). +*/ + void attach(int pin, int mode); + + /** + Attach to a pin for advanced users. Only attach the pin this way once you have previously set it up. Otherwise use attach(int pin, int mode). + */ + void attach(int pin); + + Bounce(uint8_t pin, unsigned long interval_millis ) : Bounce() { + attach(pin); + interval(interval_millis); + } + + //////////////// + // Deprecated // + //////////////// + + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written with Bounce version 1 + */ + bool risingEdge() { return rose(); } + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written with Bounce version 1 + */ + bool fallingEdge() { return fell(); } + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written with Bounce version 1 + */ + + +protected: + + + uint8_t pin; + + virtual bool readCurrentState() { return digitalRead(pin); } + virtual void setPinMode(int pin, int mode) { +#if defined(ARDUINO_ARCH_STM32F1) + pinMode(pin, (WiringPinMode)mode); +#else + pinMode(pin, mode); +#endif + } + + + +}; + +/** + @brief The Debouncer:Bounce:Button class. The Button class matches an electrical state to a physical action. + */ +namespace Bounce2 { + // code declarations + +class Button : public Bounce{ +protected: + bool stateForPressed = 1; // + public: + /*! + @brief Create an instance of the Button class. By default, the pressed state is matched to a HIGH electrical level. + + @code + + // Create an instance of the Button class. + Button() button; + + @endcode +*/ + Button(){ } + + /*! + @brief Set the electrical state (HIGH/LOW) that corresponds to a physical press. By default, the pressed state is matched to a HIGH electrical level. + + @param state + The electrical state (HIGH/LOW) that corresponds to a physical press. + +*/ + void setPressedState(bool state){ + stateForPressed = state; + } + + /*! + @brief Get the electrical state (HIGH/LOW) that corresponds to a physical press. + */ + inline bool getPressedState() { + return stateForPressed; + }; + + /*! + @brief Returns true if the button is currently physically pressed. + */ + inline bool isPressed() { + return read() == getPressedState(); + }; + + /*! + @brief Returns true if the button was physically pressed +*/ + inline bool pressed() { + return changed() && isPressed(); + }; + + /*! + @brief Returns true if the button was physically released +*/ + inline bool released() { + return changed() && !isPressed(); + }; + +}; +}; + +#endif diff --git a/include/PubSubClient.h b/include/PubSubClient.h new file mode 100644 index 0000000..c70d9fd --- /dev/null +++ b/include/PubSubClient.h @@ -0,0 +1,184 @@ +/* + PubSubClient.h - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#ifndef PubSubClient_h +#define PubSubClient_h + +#include +#include "IPAddress.h" +#include "Client.h" +#include "Stream.h" + +#define MQTT_VERSION_3_1 3 +#define MQTT_VERSION_3_1_1 4 + +// MQTT_VERSION : Pick the version +//#define MQTT_VERSION MQTT_VERSION_3_1 +#ifndef MQTT_VERSION +#define MQTT_VERSION MQTT_VERSION_3_1_1 +#endif + +// MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize(). +#ifndef MQTT_MAX_PACKET_SIZE +#define MQTT_MAX_PACKET_SIZE 256 +#endif + +// MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive() +#ifndef MQTT_KEEPALIVE +#define MQTT_KEEPALIVE 15 +#endif + +// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout() +#ifndef MQTT_SOCKET_TIMEOUT +#define MQTT_SOCKET_TIMEOUT 15 +#endif + +// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client +// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to +// pass the entire MQTT packet in each write call. +//#define MQTT_MAX_TRANSFER_SIZE 80 + +// Possible values for client.state() +#define MQTT_CONNECTION_TIMEOUT -4 +#define MQTT_CONNECTION_LOST -3 +#define MQTT_CONNECT_FAILED -2 +#define MQTT_DISCONNECTED -1 +#define MQTT_CONNECTED 0 +#define MQTT_CONNECT_BAD_PROTOCOL 1 +#define MQTT_CONNECT_BAD_CLIENT_ID 2 +#define MQTT_CONNECT_UNAVAILABLE 3 +#define MQTT_CONNECT_BAD_CREDENTIALS 4 +#define MQTT_CONNECT_UNAUTHORIZED 5 + +#define MQTTCONNECT 1 << 4 // Client request to connect to Server +#define MQTTCONNACK 2 << 4 // Connect Acknowledgment +#define MQTTPUBLISH 3 << 4 // Publish message +#define MQTTPUBACK 4 << 4 // Publish Acknowledgment +#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1) +#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2) +#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3) +#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request +#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment +#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request +#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment +#define MQTTPINGREQ 12 << 4 // PING Request +#define MQTTPINGRESP 13 << 4 // PING Response +#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting +#define MQTTReserved 15 << 4 // Reserved + +#define MQTTQOS0 (0 << 1) +#define MQTTQOS1 (1 << 1) +#define MQTTQOS2 (2 << 1) + +// Maximum size of fixed header and variable length size header +#define MQTT_MAX_HEADER_SIZE 5 + +#if defined(ESP8266) || defined(ESP32) +#include +#define MQTT_CALLBACK_SIGNATURE std::function callback +#else +#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int) +#endif + +#define CHECK_STRING_LENGTH(l,s) if (l+2+strnlen(s, this->bufferSize) > this->bufferSize) {_client->stop();return false;} + +class PubSubClient : public Print { +private: + Client* _client; + uint8_t* buffer; + uint16_t bufferSize; + uint16_t keepAlive; + uint16_t socketTimeout; + uint16_t nextMsgId; + unsigned long lastOutActivity; + unsigned long lastInActivity; + bool pingOutstanding; + MQTT_CALLBACK_SIGNATURE; + uint32_t readPacket(uint8_t*); + boolean readByte(uint8_t * result); + boolean readByte(uint8_t * result, uint16_t * index); + boolean write(uint8_t header, uint8_t* buf, uint16_t length); + uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); + // Build up the header ready to send + // Returns the size of the header + // Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start + // (MQTT_MAX_HEADER_SIZE - ) bytes into the buffer + size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length); + IPAddress ip; + const char* domain; + uint16_t port; + Stream* stream; + int _state; +public: + PubSubClient(); + PubSubClient(Client& client); + PubSubClient(IPAddress, uint16_t, Client& client); + PubSubClient(IPAddress, uint16_t, Client& client, Stream&); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, Client& client); + PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(const char*, uint16_t, Client& client); + PubSubClient(const char*, uint16_t, Client& client, Stream&); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + + ~PubSubClient(); + + PubSubClient& setServer(IPAddress ip, uint16_t port); + PubSubClient& setServer(uint8_t * ip, uint16_t port); + PubSubClient& setServer(const char * domain, uint16_t port); + PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); + PubSubClient& setClient(Client& client); + PubSubClient& setStream(Stream& stream); + PubSubClient& setKeepAlive(uint16_t keepAlive); + PubSubClient& setSocketTimeout(uint16_t timeout); + + boolean setBufferSize(uint16_t size); + uint16_t getBufferSize(); + + boolean connect(const char* id); + boolean connect(const char* id, const char* user, const char* pass); + boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession); + void disconnect(); + boolean publish(const char* topic, const char* payload); + boolean publish(const char* topic, const char* payload, boolean retained); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + boolean publish_P(const char* topic, const char* payload, boolean retained); + boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + // Start to publish a message. + // This API: + // beginPublish(...) + // one or more calls to write(...) + // endPublish() + // Allows for arbitrarily large payloads to be sent without them having to be copied into + // a new buffer and held in memory at one time + // Returns 1 if the message was started successfully, 0 if there was an error + boolean beginPublish(const char* topic, unsigned int plength, boolean retained); + // Finish off this publish message (started with beginPublish) + // Returns 1 if the packet was sent successfully, 0 if there was an error + int endPublish(); + // Write a single byte of payload (only to be used with beginPublish/endPublish) + virtual size_t write(uint8_t); + // Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish) + // Returns the number of bytes written + virtual size_t write(const uint8_t *buffer, size_t size); + boolean subscribe(const char* topic); + boolean subscribe(const char* topic, uint8_t qos); + boolean unsubscribe(const char* topic); + boolean loop(); + boolean connected(); + int state(); + +}; + + +#endif diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/tools.h b/include/tools.h new file mode 100644 index 0000000..d09a211 --- /dev/null +++ b/include/tools.h @@ -0,0 +1,14 @@ +#ifndef TOOLS_H_ +#define TOOLS_H_ + +#include +#include + +String readUUID(); +String readLine(File& f); +String getIMEI(); +String BeforeFirst(String& str,const char ch); +String AfterFirst(String& str,const char ch); +String CutBeforeFirst(String& str,char ch,bool cutch); + +#endif /* TOOLS_H_ */ \ No newline at end of file diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..d549eec --- /dev/null +++ b/platformio.ini @@ -0,0 +1,20 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp01_1m] +platform = espressif8266 +board = esp01_1m +framework = arduino +board_build.filesystem = littlefs +board_build.ldscript = eagle.flash.1m64.ld +upload_port = COM6 +monitor_port = COM6 +monitor_speed = 115200 +lib_deps = bblanchon/ArduinoJson@^6.17.3 \ No newline at end of file diff --git a/src/Bounce2.cpp b/src/Bounce2.cpp new file mode 100644 index 0000000..18225c3 --- /dev/null +++ b/src/Bounce2.cpp @@ -0,0 +1,155 @@ +// Please read Bounce2.h for information about the liscence and authors + + +#include "Bounce2.h" + +////////////// +// DEBOUNCE // +////////////// + +Debouncer::Debouncer():previous_millis(0) + , interval_millis(10) + , state(0) {} + +void Debouncer::interval(uint16_t interval_millis) +{ + this->interval_millis = interval_millis; +} + +void Debouncer::begin() { + state = 0; + if (readCurrentState()) { + setStateFlag(DEBOUNCED_STATE | UNSTABLE_STATE); + } + + #ifdef BOUNCE_LOCK_OUT + previous_millis = 0; +#else + previous_millis = millis(); +#endif +} + +bool Debouncer::update() +{ + + unsetStateFlag(CHANGED_STATE); +#ifdef BOUNCE_LOCK_OUT + + // Ignore everything if we are locked out + if (millis() - previous_millis >= interval_millis) { + bool currentState = readCurrentState(); + if ( currentState != getStateFlag(DEBOUNCED_STATE) ) { + previous_millis = millis(); + changeState(); + } + } + + +#elif defined BOUNCE_WITH_PROMPT_DETECTION + // Read the state of the switch port into a temporary variable. + bool readState = readCurrentState(); + + + if ( readState != getStateFlag(DEBOUNCED_STATE) ) { + // We have seen a change from the current button state. + + if ( millis() - previous_millis >= interval_millis ) { + // We have passed the time threshold, so a new change of state is allowed. + // set the STATE_CHANGED flag and the new DEBOUNCED_STATE. + // This will be prompt as long as there has been greater than interval_misllis ms since last change of input. + // Otherwise debounced state will not change again until bouncing is stable for the timeout period. + changeState(); + } + } + + // If the readState is different from previous readState, reset the debounce timer - as input is still unstable + // and we want to prevent new button state changes until the previous one has remained stable for the timeout. + if ( readState != getStateFlag(UNSTABLE_STATE) ) { + // Update Unstable Bit to macth readState + toggleStateFlag(UNSTABLE_STATE); + previous_millis = millis(); + } + + +#else + // Read the state of the switch in a temporary variable. + bool currentState = readCurrentState(); + + + // If the reading is different from last reading, reset the debounce counter + if ( currentState != getStateFlag(UNSTABLE_STATE) ) { + previous_millis = millis(); + toggleStateFlag(UNSTABLE_STATE); + } else + if ( millis() - previous_millis >= interval_millis ) { + // We have passed the threshold time, so the input is now stable + // If it is different from last state, set the STATE_CHANGED flag + if (currentState != getStateFlag(DEBOUNCED_STATE) ) { + previous_millis = millis(); + + + changeState(); + } + } + + +#endif + + return changed(); + +} + +// WIP HELD +unsigned long Debouncer::previousDuration() { + return durationOfPreviousState; +} + +unsigned long Debouncer::duration() { + return (millis() - stateChangeLastTime); +} + +inline void Debouncer::changeState() { + toggleStateFlag(DEBOUNCED_STATE); + setStateFlag(CHANGED_STATE) ; + durationOfPreviousState = millis() - stateChangeLastTime; + stateChangeLastTime = millis(); +} + +bool Debouncer::read() +{ + return getStateFlag(DEBOUNCED_STATE); +} + +bool Debouncer::rose() +{ + return getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE); +} + +bool Debouncer::fell() +{ + return !getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE); +} + +//////////// +// BOUNCE // +//////////// + + +Bounce::Bounce() + : pin(0) +{} + +void Bounce::attach(int pin) { + this->pin = pin; + + // SET INITIAL STATE + begin(); +} + +void Bounce::attach(int pin, int mode){ + setPinMode(pin, mode); + this->attach(pin); +} + + + diff --git a/src/PubSubClient.cpp b/src/PubSubClient.cpp new file mode 100644 index 0000000..f9d921c --- /dev/null +++ b/src/PubSubClient.cpp @@ -0,0 +1,769 @@ +/* + + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#include "PubSubClient.h" +#include "Arduino.h" + +PubSubClient::PubSubClient() { + this->_state = MQTT_DISCONNECTED; + this->_client = NULL; + this->stream = NULL; + setCallback(NULL); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(Client& client) { + this->_state = MQTT_DISCONNECTED; + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::~PubSubClient() { + free(this->buffer); +} + +boolean PubSubClient::connect(const char *id) { + return connect(id,NULL,NULL,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { + return connect(id,user,pass,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { + if (!connected()) { + int result = 0; + + + if(_client->connected()) { + result = 1; + } else { + if (domain != NULL) { + result = _client->connect(this->domain, this->port); + } else { + result = _client->connect(this->ip, this->port); + } + } + + if (result == 1) { + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + unsigned int j; + +#if MQTT_VERSION == MQTT_VERSION_3_1 + uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 9 +#elif MQTT_VERSION == MQTT_VERSION_3_1_1 + uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 7 +#endif + for (j = 0;jbuffer[length++] = d[j]; + } + + uint8_t v; + if (willTopic) { + v = 0x04|(willQos<<3)|(willRetain<<5); + } else { + v = 0x00; + } + if (cleanSession) { + v = v|0x02; + } + + if(user != NULL) { + v = v|0x80; + + if(pass != NULL) { + v = v|(0x80>>1); + } + } + this->buffer[length++] = v; + + this->buffer[length++] = ((this->keepAlive) >> 8); + this->buffer[length++] = ((this->keepAlive) & 0xFF); + + CHECK_STRING_LENGTH(length,id) + length = writeString(id,this->buffer,length); + if (willTopic) { + CHECK_STRING_LENGTH(length,willTopic) + length = writeString(willTopic,this->buffer,length); + CHECK_STRING_LENGTH(length,willMessage) + length = writeString(willMessage,this->buffer,length); + } + + if(user != NULL) { + CHECK_STRING_LENGTH(length,user) + length = writeString(user,this->buffer,length); + if(pass != NULL) { + CHECK_STRING_LENGTH(length,pass) + length = writeString(pass,this->buffer,length); + } + } + + write(MQTTCONNECT,this->buffer,length-MQTT_MAX_HEADER_SIZE); + + lastInActivity = lastOutActivity = millis(); + + while (!_client->available()) { + unsigned long t = millis(); + if (t-lastInActivity >= ((int32_t) this->socketTimeout*1000UL)) { + _state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } + } + uint8_t llen; + uint32_t len = readPacket(&llen); + + if (len == 4) { + if (buffer[3] == 0) { + lastInActivity = millis(); + pingOutstanding = false; + _state = MQTT_CONNECTED; + return true; + } else { + _state = buffer[3]; + } + } + _client->stop(); + } else { + _state = MQTT_CONNECT_FAILED; + } + return false; + } + return true; +} + +// reads a byte into result +boolean PubSubClient::readByte(uint8_t * result) { + uint32_t previousMillis = millis(); + while(!_client->available()) { + yield(); + uint32_t currentMillis = millis(); + if(currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)){ + return false; + } + } + *result = _client->read(); + return true; +} + +// reads a byte into result[*index] and increments index +boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ + uint16_t current_index = *index; + uint8_t * write_address = &(result[current_index]); + if(readByte(write_address)){ + *index = current_index + 1; + return true; + } + return false; +} + +uint32_t PubSubClient::readPacket(uint8_t* lengthLength) { + uint16_t len = 0; + if(!readByte(this->buffer, &len)) return 0; + bool isPublish = (this->buffer[0]&0xF0) == MQTTPUBLISH; + uint32_t multiplier = 1; + uint32_t length = 0; + uint8_t digit = 0; + uint16_t skip = 0; + uint32_t start = 0; + + do { + if (len == 5) { + // Invalid remaining length encoding - kill the connection + _state = MQTT_DISCONNECTED; + _client->stop(); + return 0; + } + if(!readByte(&digit)) return 0; + this->buffer[len++] = digit; + length += (digit & 127) * multiplier; + multiplier <<=7; //multiplier *= 128 + } while ((digit & 128) != 0); + *lengthLength = len-1; + + if (isPublish) { + // Read in topic length to calculate bytes to skip over for Stream writing + if(!readByte(this->buffer, &len)) return 0; + if(!readByte(this->buffer, &len)) return 0; + skip = (this->buffer[*lengthLength+1]<<8)+this->buffer[*lengthLength+2]; + start = 2; + if (this->buffer[0]&MQTTQOS1) { + // skip message id + skip += 2; + } + } + uint32_t idx = len; + + for (uint32_t i = start;istream) { + if (isPublish && idx-*lengthLength-2>skip) { + this->stream->write(digit); + } + } + + if (len < this->bufferSize) { + this->buffer[len] = digit; + len++; + } + idx++; + } + + if (!this->stream && idx > this->bufferSize) { + len = 0; // This will cause the packet to be ignored. + } + return len; +} + +boolean PubSubClient::loop() { + if (connected()) { + unsigned long t = millis(); + if ((t - lastInActivity > this->keepAlive*1000UL) || (t - lastOutActivity > this->keepAlive*1000UL)) { + if (pingOutstanding) { + this->_state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } else { + this->buffer[0] = MQTTPINGREQ; + this->buffer[1] = 0; + _client->write(this->buffer,2); + lastOutActivity = t; + lastInActivity = t; + pingOutstanding = true; + } + } + if (_client->available()) { + uint8_t llen; + uint16_t len = readPacket(&llen); + uint16_t msgId = 0; + uint8_t *payload; + if (len > 0) { + lastInActivity = t; + uint8_t type = this->buffer[0]&0xF0; + if (type == MQTTPUBLISH) { + if (callback) { + uint16_t tl = (this->buffer[llen+1]<<8)+this->buffer[llen+2]; /* topic length in bytes */ + memmove(this->buffer+llen+2,this->buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */ + this->buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */ + char *topic = (char*) this->buffer+llen+2; + // msgId only present for QOS>0 + if ((this->buffer[0]&0x06) == MQTTQOS1) { + msgId = (this->buffer[llen+3+tl]<<8)+this->buffer[llen+3+tl+1]; + payload = this->buffer+llen+3+tl+2; + callback(topic,payload,len-llen-3-tl-2); + + this->buffer[0] = MQTTPUBACK; + this->buffer[1] = 2; + this->buffer[2] = (msgId >> 8); + this->buffer[3] = (msgId & 0xFF); + _client->write(this->buffer,4); + lastOutActivity = t; + + } else { + payload = this->buffer+llen+3+tl; + callback(topic,payload,len-llen-3-tl); + } + } + } else if (type == MQTTPINGREQ) { + this->buffer[0] = MQTTPINGRESP; + this->buffer[1] = 0; + _client->write(this->buffer,2); + } else if (type == MQTTPINGRESP) { + pingOutstanding = false; + } + } else if (!connected()) { + // readPacket has closed the connection + return false; + } + } + return true; + } + return false; +} + +boolean PubSubClient::publish(const char* topic, const char* payload) { + return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,false); +} + +boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { + return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,retained); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { + return publish(topic, payload, plength, false); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + if (connected()) { + if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) { + // Too long + return false; + } + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic,this->buffer,length); + + // Add payload + uint16_t i; + for (i=0;ibuffer[length++] = payload[i]; + } + + // Write the header + uint8_t header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + return write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) { + return publish_P(topic, (const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0, retained); +} + +boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + uint8_t llen = 0; + uint8_t digit; + unsigned int rc = 0; + uint16_t tlen; + unsigned int pos = 0; + unsigned int i; + uint8_t header; + unsigned int len; + int expectedLength; + + if (!connected()) { + return false; + } + + tlen = strnlen(topic, this->bufferSize); + + header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + this->buffer[pos++] = header; + len = plength + 2 + tlen; + do { + digit = len & 127; //digit = len %128 + len >>= 7; //len = len / 128 + if (len > 0) { + digit |= 0x80; + } + this->buffer[pos++] = digit; + llen++; + } while(len>0); + + pos = writeString(topic,this->buffer,pos); + + rc += _client->write(this->buffer,pos); + + for (i=0;iwrite((char)pgm_read_byte_near(payload + i)); + } + + lastOutActivity = millis(); + + expectedLength = 1 + llen + 2 + tlen + plength; + + return (rc == (unsigned int)expectedLength); +} + +boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) { + if (connected()) { + // Send the header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic,this->buffer,length); + uint8_t header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + size_t hlen = buildHeader(header, this->buffer, plength+length-MQTT_MAX_HEADER_SIZE); + uint16_t rc = _client->write(this->buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); + lastOutActivity = millis(); + return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); + } + return false; +} + +int PubSubClient::endPublish() { + return 1; +} + +size_t PubSubClient::write(uint8_t data) { + lastOutActivity = millis(); + return _client->write(data); +} + +size_t PubSubClient::write(const uint8_t *buffer, size_t size) { + lastOutActivity = millis(); + return _client->write(buffer,size); +} + +size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + uint16_t len = length; + do { + + digit = len & 127; //digit = len %128 + len >>= 7; //len = len / 128 + if (len > 0) { + digit |= 0x80; + } + lenBuf[pos++] = digit; + llen++; + } while(len>0); + + buf[4-llen] = header; + for (int i=0;i 0) && result) { + bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; + rc = _client->write(writeBuf,bytesToWrite); + result = (rc == bytesToWrite); + bytesRemaining -= rc; + writeBuf += rc; + } + return result; +#else + rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); + lastOutActivity = millis(); + return (rc == hlen+length); +#endif +} + +boolean PubSubClient::subscribe(const char* topic) { + return subscribe(topic, 0); +} + +boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { + size_t topicLength = strnlen(topic, this->bufferSize); + if (topic == 0) { + return false; + } + if (qos > 1) { + return false; + } + if (this->bufferSize < 9 + topicLength) { + // Too long + return false; + } + if (connected()) { + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + this->buffer[length++] = (nextMsgId >> 8); + this->buffer[length++] = (nextMsgId & 0xFF); + length = writeString((char*)topic, this->buffer,length); + this->buffer[length++] = qos; + return write(MQTTSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +boolean PubSubClient::unsubscribe(const char* topic) { + size_t topicLength = strnlen(topic, this->bufferSize); + if (topic == 0) { + return false; + } + if (this->bufferSize < 9 + topicLength) { + // Too long + return false; + } + if (connected()) { + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + this->buffer[length++] = (nextMsgId >> 8); + this->buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, this->buffer,length); + return write(MQTTUNSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +void PubSubClient::disconnect() { + this->buffer[0] = MQTTDISCONNECT; + this->buffer[1] = 0; + _client->write(this->buffer,2); + _state = MQTT_DISCONNECTED; + _client->flush(); + _client->stop(); + lastInActivity = lastOutActivity = millis(); +} + +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { + const char* idp = string; + uint16_t i = 0; + pos += 2; + while (*idp) { + buf[pos++] = *idp++; + i++; + } + buf[pos-i-2] = (i >> 8); + buf[pos-i-1] = (i & 0xFF); + return pos; +} + + +boolean PubSubClient::connected() { + boolean rc; + if (_client == NULL ) { + rc = false; + } else { + rc = (int)_client->connected(); + if (!rc) { + if (this->_state == MQTT_CONNECTED) { + this->_state = MQTT_CONNECTION_LOST; + _client->flush(); + _client->stop(); + } + } else { + return this->_state == MQTT_CONNECTED; + } + } + return rc; +} + +PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { + IPAddress addr(ip[0],ip[1],ip[2],ip[3]); + return setServer(addr,port); +} + +PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { + this->ip = ip; + this->port = port; + this->domain = NULL; + return *this; +} + +PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { + this->domain = domain; + this->port = port; + return *this; +} + +PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { + this->callback = callback; + return *this; +} + +PubSubClient& PubSubClient::setClient(Client& client){ + this->_client = &client; + return *this; +} + +PubSubClient& PubSubClient::setStream(Stream& stream){ + this->stream = &stream; + return *this; +} + +int PubSubClient::state() { + return this->_state; +} + +boolean PubSubClient::setBufferSize(uint16_t size) { + if (size == 0) { + // Cannot set it back to 0 + return false; + } + if (this->bufferSize == 0) { + this->buffer = (uint8_t*)malloc(size); + } else { + uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size); + if (newBuffer != NULL) { + this->buffer = newBuffer; + } else { + return false; + } + } + this->bufferSize = size; + return (this->buffer != NULL); +} + +uint16_t PubSubClient::getBufferSize() { + return this->bufferSize; +} +PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) { + this->keepAlive = keepAlive; + return *this; +} +PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) { + this->socketTimeout = timeout; + return *this; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e86f7bb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,593 @@ +/** + Created on: 01.01.2021 + Счётчик воды и протечки WEB морда для ESP8266 + 1) Установить: http://wiki.amperka.ru/%D0%BF%D1%80%D0%BE%D0%B4%D1%83%D0%BA%D1%82%D1%8B:esp8266:esptool + Для 1MB флешь памяти, файловая система: http://wikihandbk.com/wiki/ESP8266:%D0%9F%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B8/Arduino/%D0%A0%D0%B0%D0%B1%D0%BE%D1%82%D0%B0_%D1%81_%D1%84%D0%B0%D0%B9%D0%BB%D0%BE%D0%B2%D0%BE%D0%B9_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%BE%D0%B9_%D0%B2_%D0%B0%D0%B4%D0%B4%D0%BE%D0%BD%D0%B5_ESP8266_%D0%B4%D0%BB%D1%8F_IDE_Arduino + + На борту счётчика выход + 2 входа для счётчика горячей и холодной (Подключено к STM8L051 геркон) + 1 вход для датчика утечки (Подключено к STM8L051 200 ком резистор по моему) + 1 кнопка для включения ESP8266 и перехода в настройки при длительном нажатии (10 секунд) а при коротком просто отправить данные по WIFI (Подключено к STM8L051 кнопка) + 1 кнопочка для открытия кранов не смотря на утеку воды (для пожаробезопасности) (Подключено к STM8L051 кнопка) + + Подробнее схемотехника на: https://easyeda.com/ru + Другая документация на O:\MyDocuments\projects\_Doc\Water_meter_observer +*/ +//---------------------------------------------------------------------------------------------------- +#include +#include + + + +#include +#include // Include the WebServer library +#include +#include +#include //Ticker Library + +#include //Прмер: https://github.com/lorol/ESPAsyncWebServer/blob/master/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino +#include //Для отправки данных по MQTT см.: https://www.emqx.io/blog/esp8266-connects-to-the-public-mqtt-broker + +#include "Bounce2.h" +#include "tools.h" +//---------------------------------------------------------------------------------------------------- +#define PIN_LED 02 //Пин светодиода + +Bounce bounce = Bounce(); //Для избавления от дребезга контактов + +int blink=LOW; //Для мигания при настройке +int blinkMSec=0; //Для мигания при настройке + +ESP8266WebServer server(80); // Set web server and port number to 80 +WiFiClient wifiClient; +WiFiClientSecure wifiClientS; +IPAddress local_IP(192,168,1,1); //Для точки доступа +IPAddress gateway(192,168,1,1); //Для точки доступа +IPAddress subnet(255,255,255,0); //Для точки доступа +IPAddress DNS_IP(192,168,1,1); +int lastPS=0; //сколько приконекченных + +// DNS server +const byte DNS_PORT = 53; +DNSServer dnsServer; + +//Для входа и выхода из режима настройки WIFI +#define CNF_TIME 300; //Сколько секунд даётся для настройки оборудования (600=5минут) +int ticks = -1; //Переменная для отсчёта секунд, как дойдёт до нуля то засыпаем +Ticker blinker; //Для подсчёта времени до входа в спящий режим (предполагаю просыпание по кнопке ресет) + +String ssid="Node6"; +String pass="isecretk"; + +PubSubClient mqttClient; + +String mqtt_protocol = "tcp"; +String mqtt_host = "observer.kz"; // Имя сервера MQTT +uint16_t mqtt_port = 1883; // Порт для подключения к серверу MQTT +String mqtt_fingerprint = ""; //Отпечаток сертификата +String mqtt_user = ""; // Логи для подключения к серверу MQTT +String mqtt_pass = ""; // Пароль для подключения к серверу MQTT +String mqtt_topic = "home/water/main"; /* 1=on, 0=off else toggle*/ +#define LED_QOS 1 //0 - Без подтверждения 1 - С подтверждением 2 - С подтверждением и без возможности двойной отправки + +int time5s=millis(); //Для реконекта каждые 5 секунд +String uuid=""; //Уникальный ID клиента (загружается из файловой системы) + +String g_data; //Для накопления данных с последовательного порта +//---------------------------------------------------------------------------------------------------- +int g_cold = -1; //Показание холодной воды +int g_hot = -1; //Показание горячей воды +int g_leak = -1; //Протечка 0 нет 1 есть +int g_volt = -1; //Вольт на акамуляторе +int g_tmpr = -100; //Температура на микроконтроллере +//---------------------------------------------------------------------------------------------------- +bool configWebServer(); +void setLampLight(int mode); +//---------------------------------------------------------------------------------------------------- +//Load settings from file system to local variables +bool loadConfig() +{ + File f = LittleFS.open("settings.txt", "r"); + String line=""; + do { + line=readLine(f); + if(line!=""){ + if(BeforeFirst(line,'=')=="ssid") ssid=AfterFirst(line,'='); //Название сети + else if(BeforeFirst(line,'=')=="pass") pass=AfterFirst(line,'='); //Номер сети + else if(BeforeFirst(line,'=')=="mqtt_protocol") mqtt_protocol=AfterFirst(line,'='); + else if(BeforeFirst(line,'=')=="mqtt_host") mqtt_host=AfterFirst(line,'='); + else if(BeforeFirst(line,'=')=="mqtt_port") mqtt_port=AfterFirst(line,'=').toInt(); + else if(BeforeFirst(line,'=')=="mqtt_fingerprint") mqtt_fingerprint=AfterFirst(line,'='); + else if(BeforeFirst(line,'=')=="mqtt_user") mqtt_user=AfterFirst(line,'='); + else if(BeforeFirst(line,'=')=="mqtt_pass") mqtt_pass=AfterFirst(line,'='); + else if(BeforeFirst(line,'=')=="mqtt_topic") mqtt_topic=AfterFirst(line,'='); + } + } while (line!=""); + f.close(); + + Serial.println("SSID = "+ssid); + Serial.println("MQTT HOST = "+mqtt_host); + + return true; +} +//---------------------------------------------------------------------------------------------------- +//Save settings from local variables to file system +bool saveConfigs() +{ + File f = LittleFS.open("settings.txt", "w"); + if (!f) { + Serial.println("Count file open failed on update."); + } else { + f.println("ssid="+ssid); + f.println("pass="+pass); + + f.println("mqtt_protocol="+mqtt_protocol); + f.println("mqtt_host="+mqtt_host); + f.println("mqtt_port="+String(mqtt_port)); + f.println("mqtt_fingerprint="+mqtt_fingerprint); + f.println("mqtt_topic="+mqtt_topic); + f.println("mqtt_user="+mqtt_user); + f.println("mqtt_pass="+mqtt_pass); + + f.close(); + } + return true; +} +//======================================================================= +void handleNotFound() { + server.send(404, "text/plain", "FileNotFound"); +} +//======================================================================= +//Отправляю показания датчиков при настройке нептуна +void handleSensors() { + String json; + json.reserve(128); + + //Видимые сети + json = "{\"networks\":["; + int n = WiFi.scanNetworks(); + Serial.print(n); + Serial.println(" network(s) found"); + for (int i = 0; i < n; i++) + { + json += "{\"name\":\""; + json += WiFi.SSID(i); //Название сети + json += "\",\"lock\":"; + json += WiFi.encryptionType(i); //Шифрование + json += ",\"strength\":"; + json += WiFi.RSSI(i); //Уровень сигнала + json += "}"; + if(i!=n-1) json += ","; + } + json += "],"; + //Количество тиков до перехода в спящий режим + json += "\"ticks\":"; + json += ticks; + json += ",\"hot\":123"; + json += ",\"cold\":456"; + json += ",\"leak\":false"; + json += "}"; + server.send(200, "application/json", json); +} +//======================================================================= +void handleData(){ + String json; + json.reserve(128); + + json = "{"; + + json += "\"ssid\":\"ssid\""; + json += ",\"pass\":\"pass\""; + + json += ",\"mqtt\":\"mqtt\""; + json += ",\"uuid\":\"uuid\""; + json += ",\"topic\":\"topic\""; + json += ",\"login\":\"login\""; + json += ",\"password\":\"password\""; + + json += "}"; + + server.send(200, "application/json", json); +} +//======================================================================= +void handleSave(){ + + Serial.println("+++Save+++"); + + if(server.args()>0){ + for(int i=0;i0) { // Wait for the Wi-Fi to connect + delay(1000); + Serial.print(--i); Serial.print(' '); + }*/ + + return true; +} +//---------------------------------------------------------------------------------------------------- +//Отключиться от точки доступа +bool disconnectFromWIFIPoint(){ + WiFi.setAutoReconnect(false); + WiFi.disconnect(true); + return true; +} +//---------------------------------------------------------------------------------------------------- +//Инициилизировать WIFI точку доступа для настройки выключателя а также поднять HTML сервер +bool createAP(){ + + //WiFi.mode(WIFI_AP_STA); //WIFI_AP_STA - Режим точки доступа и клиента WIFI_AP - Режим только точки доступа + WiFi.mode(WIFI_AP); //WIFI_AP - Режим только точки доступа + + //Настройки точки доступа + if(WiFi.softAPConfig(local_IP, gateway, subnet)){ + Serial.println("Ready WiFi.softAPConfig"); + } + + Serial.print("Setting soft-AP ... "); + boolean result = WiFi.softAP("Observer_V03"); + if(result == true) + { + Serial.println("Ready"); + IPAddress IP = WiFi.softAPIP(); + Serial.println(IP); + //Serial.println(WiFi.localIP()); + + //Настройте DNS-сервер, перенаправляющий все домены на apIP + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(DNS_PORT, "*", DNS_IP); + + configWebServer(); + } + else + { + Serial.println("Failed!"); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +//Отключить программную точку доступа +bool deleteAP(){ + Serial.print("softAPdisconnect();"); + dnsServer.stop(); + bool result=WiFi.softAPdisconnect(true); + //Гашу светодиод + digitalWrite(LED_BUILTIN,HIGH); //LOW + + return result; +} +//---------------------------------------------------------------------------------------------------- +//Функция для подсчёта времени до перехода в спящий режим +void tick_1s() +{ + ticks--; + if(ticks==0){ //Переходим в спящий режим + Serial.print("Deep sleep!"); + Serial.print("#0;!*"); //Отсылаю команду на STM8 чтобы он на CHIP_PU подал низский сигнал для отключения питания + } + //Если в течении 2х минут не перешли в режим глубокого сна то переходим в обычный сон + if(ticks==-600){ + Serial.println("Sleep!"); + Serial.print("#0;!*"); //Повторно отсылаю команду на STM8 чтобы он на CHIP_PU подал низский сигнал для отключения питания + ESP.deepSleep(0); + } +} +//======================================================================= +//Запрос на получение данных с микроконтроллера +//Для дебага написал тестовую программу клторая эиетирует STM8L по последовательному порту: O:\MyDocuments\projects\Workspace_C++Builder\NeptuneW01 +bool getMode(){ + + Serial.println("#0;"); //Запрашиваю для какого режима работы включили WIFI +/*Serial.println("#1;"); //Запрашиваем '1'=холодная + Serial.println("#2;"); //Запрашиваем '2'=горячая + Serial.println("#3;"); //Запрашиваем '3'=протечки + Serial.println("#4;"); //Запрашиваем '4'=вольт на аккумуляторе + Serial.println("#5;"); //Запрашиваем '5'=температура на процессоре +*/ + return true; +} +//---------------------------------------------------------------------------------------------------- +bool configWebServer(){ + Serial.println("configWebServer()"); + + server.on("/sensors/", HTTP_GET, handleSensors); + server.on("/data/", HTTP_GET, handleData); + server.on("/save/", HTTP_POST, handleSave); + server.on("/", HTTP_GET, handleMain); + server.onNotFound(handleNotFound); + // Start server + server.begin(); + + return true; +} +//---------------------------------------------------------------------------------------------------- +// подключаемся к MQTT серверу +bool connectToMQTT(){ + Serial.println("mqtt_protocol = "+mqtt_protocol+" mqtt_host = "+mqtt_host+" mqtt_port = " + String(mqtt_port)); + + // Fingerprint of the broker CA + // openssl x509 -in m2mqtt_srv.crt -sha1 -noout -fingerprint + if(mqtt_protocol=="tcp"){ + mqttClient.setClient(wifiClient); + }else{ + mqttClient.setClient(wifiClientS); + wifiClientS.setFingerprint(mqtt_fingerprint.c_str()); + } + + //Присваиваю настройки + mqttClient.setServer(mqtt_host.c_str(),mqtt_port); + + if (WiFi.status() == WL_CONNECTED) { + if(!mqttClient.connected()) { + if (mqttClient.connect(uuid.c_str() )) { + Serial.println("connected"); + /*mqttClient.setCallback(callback); + if(mqttClient.subscribe(String(mqtt_topic+"/set").c_str(),LED_QOS)) //Подписываемся на топик + Serial.println(String("Subscribe on ")+mqtt_topic); + else + Serial.println("Error subscribe.");*/ + } else { + Serial.print("failed, status code ="); + Serial.print(mqttClient.state()); + } + } + } + return true; +} +//---------------------------------------------------------------------------------------------------- +void setup() +{ + Serial.begin(115200); + Serial.println(""); + Serial.println("Init led pin"); + //Настраиваем светодиод на плате + pinMode(PIN_LED, OUTPUT); + digitalWrite(PIN_LED, HIGH); // Выключаю светодиод + + //Инициализирую файловую систему + if(LittleFS.begin()) + Serial.println("SPIF FS ready"); + else + Serial.println("SPIF FS failed"); + + uuid=readUUID(); //Загружаю уникальный идентификатор клиента + + loadConfig(); //Загружаю настройки в локальные переменные +/* + blinker.attach(1, tick_1s); //Инициализирую таймер для подсчёта секунд работы в режиме настройки +*/ + connectToWIFIPoint(); //Пытаюсь подключиться к настроенной точке доступа +/* + connectToMQTT(); //Пытаюсь опубликовать топик +*/ + //Заранее инициализирую HTTP сервер чтобы не замарачиваться с логигой его работы... + configWebServer(); + + //Запрашиваю актуальную информацию по датчикам c STM8 микроконтролера по USART + getMode(); + +} +//---------------------------------------------------------------------------------------------------- +//Проверяю правильный ли CRC в строке +bool checkCRC(String str){ + + String data=CutBeforeFirst(str,'!',false); + String crcS=CutBeforeFirst(str,'*',true); + + //Подсчитываю CRC и проверяю с тем что передалось + unsigned char crcD=0; + for(unsigned int i=0;i30000){ + getMode(); + time5s=millis(); + } +*/ + +/* + mqttClient.loop(); + + dnsServer.processNextRequest(); //DNS + server.handleClient(); //Обрабатываем запрос клиента (без этого не берётся IP адресс) + + + + if(ticks>0){ + //В режиме конфигурации каждые 500 миллисекунд меняем цвет зелёного светодиода + if(millis()-blinkMSec>500){ + blink=!blink; + digitalWrite(LED_BUILTIN,blink); + blinkMSec=millis(); + } + }else{ + //Если в нормальном режиме функционирования + //Проверяю соединение с MQTT и если не соединён то пытаюсь подключиться каждые 10 секунд + if(millis()-time5s>10000){ + if(!mqttClient.connected()) { + connectToMQTT(); + } + time5s=millis(); + } + } + +*/ + + + //Блок кода для общения с энергоэфективным микроконтроллером + int inByte = 0; + if (Serial.available() > 0) { //если есть доступные данные то обрабатываем их + //Serial.println("available = "+String(Serial.available())); + + // считываем байты в строку до символа с символа '#' до символа '*' + for(int i=0;i-100) + data+=",\"tmpr\":"+String(g_tmpr); // Температура + data+="}"; + mqttClient.publish(mqtt_topic.c_str(), data.c_str(), true); //Отправляю данные в топик (С флагом RETAIN чтобы слушатели которые подключились поздно получили тек. состояние) + + Serial.println(data); + + //Как отправили данные устанавливаю время ожидания входа в сон на 5 секунд чтобы ESP8266 заснул + ticks=5; + //Очищаю данные + g_cold=-1; + g_hot=-1; + g_leak=-1; + g_volt=-1; + g_tmpr=-100; + } + +} diff --git a/src/tools.cpp b/src/tools.cpp new file mode 100644 index 0000000..4d0e652 --- /dev/null +++ b/src/tools.cpp @@ -0,0 +1,82 @@ +#include "tools.h" +#include +//---------------------------------------------------------------------------------------------------- +//Загрузить либо сгенерировать уникальный ID для идентификации устройства +String readUUID() +{ + File fr = LittleFS.open("uuid.txt", "r"); + String uuid=readLine(fr); + fr.close(); + if(uuid.length()<10){ + uuid=getIMEI(); + File fw = LittleFS.open("uuid.txt", "w"); + fw.write(uuid.c_str()); + fw.close(); + } + Serial.println("uuid = "+uuid); + return uuid; +} +//---------------------------------------------------------------------------------------------------- +//Прочитать строку из файла, до символа \n +String readLine(File& f) +{ + char str[255]; + memset(str, 0, sizeof(str)); + for(size_t i=0;i0) + return str.substring(0, pos); + else + return ""; +} +//--------------------------------------------------------------------------- +String AfterFirst(String& str,const char ch){ + int pos=str.indexOf(ch); + if(pos>0) + return str.substring(pos+1,str.length()); + else + return ""; +} +//--------------------------------------------------------------------------- +//Вырезать строку до заданного символа, если сивола нет то всю строку +String CutBeforeFirst(String& str,char ch,bool cutch=true) +{ + int pos=str.indexOf(ch); + String result; + result.reserve(20); + if(pos>0){ + if(cutch) + result=str.substring(0, pos); + else + result=str.substring(0, pos+1); + str=str.substring(pos+1); + }else{ + result=str; + str=""; + } + return result; +} +//---------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..b94d089 --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html