Skip to content
This repository has been archived by the owner on Jan 29, 2019. It is now read-only.

Some additions #9

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
MIT License

Copyright (c) 2018 Peter Göthager

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.
MIT License
Copyright (c) 2018 Peter Göthager
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.
69 changes: 40 additions & 29 deletions NidayandHelper.cpp
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
#include "Arduino.h"
#include "NidayandHelper.h"

NidayandHelper::NidayandHelper(){
NidayandHelper::NidayandHelper() {
this->_configfile = "/config.json";
this->_mqttclientid = ("ESPClient-" + String(ESP.getChipId()));

}

boolean NidayandHelper::loadconfig(){
boolean NidayandHelper::loadconfig() {
File configFile = SPIFFS.open(this->_configfile, "r");
if (!configFile) {
Serial.println("Failed to open config file");
Serial.println(F("Failed to open config file"));
return false;
}

size_t size = configFile.size();
if (size > 1024) {
Serial.println("Config file size is too large");
Serial.println(F("Config file size is too large"));
return false;
}

// Allocate a buffer to store contents of the file.
std::unique_ptr<char[]> buf(new char[size]);
//std::unique_ptr<char[]> buf(new char[size]);

// We don't use String here because ArduinoJson library requires the input
// buffer to be mutable. If you don't use ArduinoJson, you may as well
// use configFile.readString instead.
configFile.readBytes(buf.get(), size);
//configFile.readBytes(buf.get(), size);

//No more memory leaks
DynamicJsonBuffer jsonBuffer(300);

//Reading from buffer breaks first key
//this->_config = jsonBuffer.parseObject(buf.get());

StaticJsonBuffer<200> jsonBuffer;
this->_config = jsonBuffer.parseObject(buf.get());
//Reading directly from file DOES NOT cause currentPosition to break
this->_config = jsonBuffer.parseObject(configFile);

//Avoid leaving opened files
configFile.close();

if (!this->_config.success()) {
Serial.println("Failed to parse config file");
Expand All @@ -38,19 +46,20 @@ boolean NidayandHelper::loadconfig(){
return true;
}

JsonVariant NidayandHelper::getconfig(){
JsonVariant NidayandHelper::getconfig() {
return this->_config;
}

boolean NidayandHelper::saveconfig(JsonVariant json){
boolean NidayandHelper::saveconfig(JsonVariant json) {
File configFile = SPIFFS.open(this->_configfile, "w");
if (!configFile) {
Serial.println("Failed to open config file for writing");
return false;
}

json.printTo(configFile);

configFile.flush(); //Making sure it's saved

Serial.println("Saved JSON to SPIFFS");
json.printTo(Serial);
Serial.println();
Expand All @@ -61,21 +70,23 @@ String NidayandHelper::mqtt_gettopic(String type) {
return "/raw/esp8266/" + String(ESP.getChipId()) + "/" + type;
}


void NidayandHelper::mqtt_reconnect(PubSubClient& psclient){
void NidayandHelper::mqtt_reconnect(PubSubClient& psclient) {
return mqtt_reconnect(psclient, String(NULL), String(NULL));
}
void NidayandHelper::mqtt_reconnect(PubSubClient& psclient, std::list<const char*> topics){

void NidayandHelper::mqtt_reconnect(PubSubClient& psclient, std::list<const char*> topics) {
return mqtt_reconnect(psclient, String(NULL), String(NULL), topics);
}
void NidayandHelper::mqtt_reconnect(PubSubClient& psclient, String uid, String pwd){

void NidayandHelper::mqtt_reconnect(PubSubClient& psclient, String uid, String pwd) {
std::list<const char*> mylist;
return mqtt_reconnect(psclient, uid, pwd, mylist);
}
void NidayandHelper::mqtt_reconnect(PubSubClient& psclient, String uid, String pwd, std::list<const char*> topics){

void NidayandHelper::mqtt_reconnect(PubSubClient& psclient, String uid, String pwd, std::list<const char*> topics) {
// Loop until we're reconnected
boolean mqttLogon = false;
if (uid!=NULL and pwd != NULL){
if (uid != NULL and pwd != NULL) {
mqttLogon = true;
}
while (!psclient.connected()) {
Expand All @@ -85,13 +96,13 @@ void NidayandHelper::mqtt_reconnect(PubSubClient& psclient, String uid, String p
Serial.println("connected");

//Send register MQTT message with JSON of chipid and ip-address
this->mqtt_publish(psclient, "/raw/esp8266/register", "{ \"id\": \"" + String(ESP.getChipId()) + "\", \"ip\":\"" + WiFi.localIP().toString() +"\"}");
this->mqtt_publish(psclient, "/raw/esp8266/register", "{ \"id\": \"" + String(ESP.getChipId()) + "\", \"ip\":\"" + WiFi.localIP().toString() + "\"}");

//Setup subscription
if (!topics.empty()){
for (const char* t : topics){
psclient.subscribe(t);
Serial.println("Subscribed to "+String(t));
if (!topics.empty()) {
for (const char* t : topics) {
psclient.subscribe(t);
Serial.println("Subscribed to " + String(t));
}
}

Expand All @@ -104,22 +115,22 @@ void NidayandHelper::mqtt_reconnect(PubSubClient& psclient, String uid, String p
delay(5000);
}
}
if (psclient.connected()){
if (psclient.connected()) {
psclient.loop();
}
}

void NidayandHelper::mqtt_publish(PubSubClient& psclient, String topic, String payload){
Serial.println("Trying to send msg..."+topic+":"+payload);
void NidayandHelper::mqtt_publish(PubSubClient& psclient, String topic, String payload) {
Serial.println("Trying to send msg..." + topic + ":" + payload);
//Send status to MQTT bus if connected
if (psclient.connected()) {
psclient.publish(topic.c_str(), payload.c_str());
} else {
Serial.println("PubSub client is not connected...");
Serial.println(F("PubSub client is not connected..."));
}
}

void NidayandHelper::resetsettings(WiFiManager& wifim){
void NidayandHelper::resetsettings(WiFiManager& wifim) {
SPIFFS.format();
wifim.resetSettings();
}
}
72 changes: 36 additions & 36 deletions NidayandHelper.h
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
#ifndef NidayandHelper_h
#define NidayandHelper_h

#include "Arduino.h"
#include <ArduinoJson.h>
#include "FS.h"
#include <PubSubClient.h>
#include <WiFiClient.h>
#include <WiFiManager.h>
#include <list>

class NidayandHelper {
public:
NidayandHelper();
boolean loadconfig();
JsonVariant getconfig();
boolean saveconfig(JsonVariant json);

String mqtt_gettopic(String type);

void mqtt_reconnect(PubSubClient& psclient);
void mqtt_reconnect(PubSubClient& psclient, std::list<const char*> topics);
void mqtt_reconnect(PubSubClient& psclient, String uid, String pwd);
void mqtt_reconnect(PubSubClient& psclient, String uid, String pwd, std::list<const char*> topics);

void mqtt_publish(PubSubClient& psclient, String topic, String payload);

void resetsettings(WiFiManager& wifim);

private:
JsonVariant _config;
String _configfile;
String _mqttclientid;
};

#endif
#ifndef NidayandHelper_h
#define NidayandHelper_h
#include "Arduino.h"
#include <ArduinoJson.h>
#include "FS.h"
#include <PubSubClient.h>
#include <WiFiClient.h>
#include <WiFiManager.h>
#include <list>
class NidayandHelper {
public:
NidayandHelper();
boolean loadconfig();
JsonVariant getconfig();
boolean saveconfig(JsonVariant json);
String mqtt_gettopic(String type);
void mqtt_reconnect(PubSubClient& psclient);
void mqtt_reconnect(PubSubClient& psclient, std::list<const char*> topics);
void mqtt_reconnect(PubSubClient& psclient, String uid, String pwd);
void mqtt_reconnect(PubSubClient& psclient, String uid, String pwd, std::list<const char*> topics);
void mqtt_publish(PubSubClient& psclient, String topic, String payload);
void resetsettings(WiFiManager& wifim);
private:
JsonVariant _config;
String _configfile;
String _mqttclientid;
};
#endif
106 changes: 62 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,62 @@
# motor-on-roller-blind-ws
WebSocket based version of [motor-on-roller-blind](https://github.com/nidayand/motor-on-roller-blind). I.e. there is no need of an MQTT server but MQTT is supported as well - you can control it with WebSockets and/or with MQTT messages.

3d parts for printing are available on Thingiverse.com: ["motor on a roller blind"](https://www.thingiverse.com/thing:2392856)

1. A tiny webserver is setup on the esp8266 that will serve one page to the client
2. Upon powering on the first time WIFI credentials, a hostname and - optional - MQTT server details is to be configured. You can specify if you want **clockwise (CW) rotation** to close the blind and you can also specify **MQTT authentication** if required. Connect your computer to a new WIFI hotspot named **BlindsConnectAP**. Password = **nidayand**
3. Connect to your normal WIFI with your client and go to the IP address of the device - or if you have an mDNS supported device (e.g. iOS, OSX or have Bonjour installed) you can go to http://{hostname}.local. If you don't know the IP-address of the device check your router for the leases (or check the serial console in the Arduino IDE or check the `/raw/esp8266/register` MQTT message if you are using an MQTT server)
4. As the webpage is loaded it will connect through a websocket directly to the device to progress updates and to control the device. If any other client connects the updates will be in sync.
5. Go to the Settings page to calibrate the motor with the start and end positions of the roller blind. Follow the instructions on the page

# MQTT
- When it connects to WIFI and MQTT it will send a "register" message to topic `/raw/esp8266/register` with a payload containing chip-id and IP-address
- A message to `/raw/esp8266/[chip-id]/in` will steer the blind according to the "payload actions" below
- Updates from the device will be sent to topic `/raw/esp8266/[chip-id]/out`

### If you don't want to use MQTT
Simply do not enter any string in the MQTT server form field upon WIFI configuration of the device (step 3 above)

## Payload options
- `(start)` - (calibrate) Sets the current position as top position
- `(max)` - (calibrate) Sets the current position as max position. Set `start` before you define `max` as `max` is a relative position to `start`
- `(0)` - (manual mode) Will stop the curtain
- `(-1)` - (manual mode) Will open the curtain. Requires `(0)` to stop the motor
- `(1)`- (manual mode) Will close the curtain. Requires `(0)` to stop the motor
- `0-100` - (auto mode) A number between 0-100 to set % of opened blind. Requires calibration before use. E.g. `50` will open it to 50%

# Required libraries (3rd party)
- Stepper_28BYJ_48: https://github.com/thomasfredericks/Stepper_28BYJ_48/
- PubSubClient: https://github.com/knolleary/pubsubclient/
- ArduinoJson: https://github.com/bblanchon/ArduinoJson
- WIFIManager: https://github.com/tzapu/WiFiManager
- WbSocketsServer: https://github.com/Links2004/arduinoWebSockets

# Screenshots

## Control
![Control](https://user-images.githubusercontent.com/2181965/31178217-a5351678-a918-11e7-9611-3e8256c873a4.png)

## Calibrate
![Settings](https://user-images.githubusercontent.com/2181965/31178216-a4f7194a-a918-11e7-85dd-8e189cfc031c.png)

## Communication settings
![WIFI Manager](https://user-images.githubusercontent.com/2181965/37288794-75244c84-2608-11e8-8c27-a17e1e854761.jpg)
# motor-on-roller-blind-ws
WebSocket based version of [motor-on-roller-blind](https://github.com/nidayand/motor-on-roller-blind). I.e. there is no need of an MQTT server but MQTT is supported as well - you can control it with WebSockets and/or with MQTT messages.

3d parts for printing are available on Thingiverse.com: ["motor on a roller blind"](https://www.thingiverse.com/thing:2392856)

1. A tiny webserver is setup on the esp8266 that will serve one page to the client
2. Upon powering on the first time WIFI credentials, a hostname and - optional - MQTT server details is to be configured. You can specify if you want **clockwise (CW) rotation** to close the blind and you can also specify **MQTT authentication** if required. Connect your computer to a new WIFI hotspot named **BlindsConnectAP**. Password = **nidayand**
3. Connect to your normal WIFI with your client and go to the IP address of the device - or if you have an mDNS supported device (e.g. iOS, OSX or have Bonjour installed) you can go to http://{hostname}.local. If you don't know the IP-address of the device check your router for the leases (or check the serial console in the Arduino IDE or check the `/raw/esp8266/register` MQTT message if you are using an MQTT server)
4. As the webpage is loaded it will connect through a websocket directly to the device to progress updates and to control the device. If any other client connects the updates will be in sync.
5. Go to the Settings page to calibrate the motor with the start and end positions of the roller blind. Follow the instructions on the page

# MQTT
- When it connects to WIFI and MQTT it will send a "register" message to topic `/raw/esp8266/register` with a payload containing chip-id and IP-address
- A message to `/raw/esp8266/[chip-id]/in` will steer the blind according to the "payload actions" below
- Updates from the device will be sent to topic `/raw/esp8266/[chip-id]/out`

### If you don't want to use MQTT
Simply do not enter any string in the MQTT server form field upon WIFI configuration of the device (step 3 above)

## Payload options
- `(start)` - (calibrate) Sets the current position as top position
- `(max)` - (calibrate) Sets the current position as max position. Set `start` before you define `max` as `max` is a relative position to `start`
- `(0)` - (manual mode) Will stop the curtain
- `(-1)` - (manual mode) Will open the curtain. Requires `(0)` to stop the motor
- `(1)`- (manual mode) Will close the curtain. Requires `(0)` to stop the motor
- `0-100` - (auto mode) A number between 0-100 to set % of opened blind. Requires calibration before use. E.g. `50` will open it to 50%

# Manual operation
For those who do not want to carry smartphone all the time, device supports manual operation using buttons - up and down.
Additional reset button is added to clean configuration, device will be just like freshly flashed.
Calibration using webpage is still needed though.

## Moving up and down
Buttons are active low using internal pullup resistors.
If you plan to use buttons _very_ far from device please use external pullup resistors and `INPUT` mode instead of `INPUT_PULLUP`.

## Resetting
1. Press and hold reset button
2. Press and hold up and down buttons
3. Hold for 5 seconds
4. Release buttons and wait for device to reboot

NOTE: After flashing - remove power source, otherwise device will hang after reset.
This is one-time-only issue so next resets will be done properly.

# Required libraries (3rd party)
- Stepper_28BYJ_48: https://github.com/thomasfredericks/Stepper_28BYJ_48/
- PubSubClient: https://github.com/knolleary/pubsubclient/
- ArduinoJson v5: https://github.com/bblanchon/ArduinoJson
- WIFIManager: https://github.com/tzapu/WiFiManager
- WbSocketsServer: https://github.com/Links2004/arduinoWebSockets

# Screenshots

## Control
![Control](https://user-images.githubusercontent.com/2181965/31178217-a5351678-a918-11e7-9611-3e8256c873a4.png)

## Calibrate
![Settings](https://user-images.githubusercontent.com/2181965/31178216-a4f7194a-a918-11e7-85dd-8e189cfc031c.png)

## Communication settings
![WIFI Manager](https://user-images.githubusercontent.com/2181965/37288794-75244c84-2608-11e8-8c27-a17e1e854761.jpg)
2 changes: 1 addition & 1 deletion index_html.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,4 @@ String INDEX_HTML = R"(<!DOCTYPE html>

</body>
</html>
)";
)";
Loading