Created
October 25, 2019 21:07
-
-
Save daniel-frenkel/8cbd15f6a10863a20684b8b2b025832b to your computer and use it in GitHub Desktop.
MQTT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define USE_CUSTOM_BOARD // See "Custom board configuration" in Settings.h | |
#define APP_DEBUG // Comment this out to disable debug prints | |
#define MOVE_DISTANCE preferences.getFloat("move_distance", 0) | |
#define MOVE_PERCENT preferences.getFloat("move_percent", 389911.13) | |
#define BLYNK_PRINT Serial | |
#include "driver/ledc.h" | |
#include "BlynkProvisioning.h" | |
#include <PubSubClient.h> | |
#include <SPI.h> | |
#include <WiFi.h> | |
#include <HTTPClient.h> | |
#include <ArduinoJson.h> | |
#include <WiFiClientSecure.h> | |
#include <TimeLib.h> | |
#include <WidgetRTC.h> | |
#include "time_store.cpp" | |
#include "time.h" | |
#include "driver/periph_ctrl.h" | |
#include "driver/timer.h" | |
#include <Update.h> | |
//#define DEBUG_STREAM terminal | |
#define DEBUG_STREAM Serial | |
WidgetTerminal terminal(V2); | |
#include "motor_control.h" | |
#include "blynk_pins.h" | |
#include "GPS.h" | |
//#include "OTA_S3.h" | |
#include "pins.h" | |
#include <SimpleTimer.h> | |
SimpleTimer timer; | |
TaskHandle_t TaskA; | |
const char* ntpServer = "pool.ntp.org"; // where to get the current date and time | |
String sunsetsunrisebaseurl="https://api.sunrise-sunset.org/json?formatted=0&lng="; | |
int counter = 0; | |
long int last_UP_change = millis(); | |
// Wireless settings | |
const char AUTH[] = "YOUR_AUTH_TOKEN"; | |
const char SSID[] = "YOUR_WIFI_SSID"; | |
const char PASSWORD[] = "YOUT_WIFI_PASSWORD"; | |
IPAddress mqtt_server(192,168,50,178); | |
const char* mqtt_username = "MQTT_USERNAME"; | |
const char* mqtt_password = "MQTT_PASSWORD"; | |
char* InTopic = "room/curtain"; //subscribe to topic to be notified about | |
char* InTopic1 = "room"; | |
WiFiClient espClient; | |
PubSubClient client(espClient); | |
long lastMsg = 0; | |
char msg[50]; | |
int value = 0; | |
void callback(char* topic, byte* message, unsigned int length) { | |
Serial.print("Message arrived on topic: "); | |
Serial.print(topic); | |
Serial.print(". Message: "); | |
String messageTemp; | |
for (int i = 0; i < length; i++) { | |
Serial.print((char)message[i]); | |
messageTemp += (char)message[i]; | |
} | |
Serial.println(); | |
// Feel free to add more if statements to control more GPIOs with MQTT | |
// If a message is received on the topic esp32/output, you check if the message is either "on" or "off". | |
// Changes the output state according to the message | |
if (String(topic) == "room/curtain") { | |
Serial.print("Changing output to "); | |
if(messageTemp == "open"){ | |
Serial.println("open"); | |
move_close(); | |
//digitalWrite(ledPin, HIGH); | |
} | |
else if(messageTemp == "close"){ | |
Serial.println("close"); | |
move_open(); | |
//digitalWrite(ledPin, LOW); | |
} | |
} | |
} | |
void reconnect() { | |
// Loop until we're reconnected | |
while (!client.connected()) { | |
Serial.print("Attempting MQTT connection..."); | |
// Attempt to connect | |
if (client.connect("BlinkMQTTBridge",mqtt_username,mqtt_password)) { | |
Serial.println("connected"); | |
counter = 0; | |
// ... and resubscribe | |
client.subscribe(InTopic); | |
client.subscribe(InTopic1); | |
} else { | |
Serial.print("failed, rc="); | |
Serial.print(client.state()); | |
Serial.println(" try again in 0.3 second"); | |
++counter; | |
if (counter > 180) ESP.restart(); | |
// Wait .3 seconds before retrying | |
delay(300); | |
} | |
} | |
} | |
//SETUP | |
void setup() { | |
Serial.begin(115200); | |
pinMode(btn1,INPUT_PULLUP); | |
pinMode(btn2,INPUT_PULLUP); | |
// for storing the times over power cycle | |
preferences.begin("auto-curtain", false); | |
clockout_setup(); | |
setup_motors(); | |
xTaskCreatePinnedToCore( | |
IndependentTask, /* pvTaskCode */ | |
"NoBlynkSafe", /* pcName */ | |
8192, /* usStackDepth */ | |
NULL, /* pvParameters */ | |
1, /* uxPriority */ | |
&TaskA, /* pxCreatedTask */ | |
0); /* xCoreID */ | |
//Blynk Server - Use this one | |
//Blynk.begin(auth, ssid, pass); | |
//MorningRod Server - Only if on MorningRod server | |
//Blynk.begin(auth, ssid, pass, "morningrod.blynk.cc", 8080); | |
//Blynk | |
WiFi.mode(WIFI_STA); | |
//Use this for Blynk server | |
Blynk.begin(AUTH, SSID, PASSWORD); | |
//Use this for MorningRod server | |
//Blynk.begin(AUTH, SSID, PASSWORD, "morningrod.blynk.cc", 8080); | |
while (Blynk.connect() == false) { // Wait until connected | |
if ((millis() - last_UP_change) > 30000) { // 30s before reset | |
Serial.println(F("resetting")); | |
ESP.restart(); | |
} | |
} | |
delay(10); | |
client.setServer(mqtt_server, 1883); | |
client.setCallback(callback); | |
//Use For MorningRod App Provisioning Mode | |
/* | |
BlynkProvisioning.begin(); | |
Blynk.sendInternal("rtc", "sync"); | |
DEBUG_STREAM.println("Connected!"); | |
Blynk.syncAll(); | |
*/ | |
lat=preferences.getFloat("lat", -1); | |
lon=preferences.getFloat("lon", -1); | |
DEBUG_STREAM.println("Loading timetables..."); | |
for(int i=0;i<4;i++)load_time(i); // load time configuration | |
sun_delay = preferences.getInt("sun_delay",0); // load the delay time (in minutes) between sunset/sunrise and open/close | |
//reset_motors(1); | |
DEBUG_STREAM.println("Ready!"); | |
//sendData(0x21,0); Not sure what this does | |
check_timer=millis()+10000; | |
daylight_timer=millis()+9000; | |
for(int i=0;i<4;i++){ | |
if(times[i].active) | |
last_timezone_offset=times[i].offset; | |
} | |
//configTime(last_timezone_offset, 0, ntpServer, "time.nist.gov", "time.windows.com"); | |
} | |
time_store sunrise; | |
time_store sunset; | |
long update_timer = millis(); | |
int update_count = 0; | |
int last_dir; // last direction the motors moved | |
void IndependentTask( void * parameter ) { | |
// the internet should not be used AT ALL in this function!!!! | |
while(true) { | |
// buttons | |
// A press sets the command to open or close the track motor. | |
if(!digitalRead(btn1)){ | |
command = MOVE_CLOSE; | |
} | |
if(!digitalRead(btn2)){ | |
command = MOVE_OPEN; | |
} | |
// commands are sent from other threads so that blocking function calls | |
// (like trackClose(), shaftClose(), trackOpen(), and shaftOpen()) can be | |
// called without causing bizzare hickups in the other threads, namely the main thread | |
// which controls Blynk. | |
if(command!=-1){ | |
DEBUG_STREAM.print("Executing command "); | |
DEBUG_STREAM.println(command); | |
} | |
if(command==MOVE_CLOSE){ | |
move_close(); | |
last_dir=DIR_CLOSE; | |
//DEBUG_STREAM.println("Move Close Executed"); | |
}else if(command==MOVE_OPEN){ | |
move_open(); | |
last_dir=DIR_OPEN; | |
} | |
if(command!=-1)DEBUG_STREAM.println("[ready for next movement]"); | |
command = -1; | |
delay(16); | |
} | |
} | |
void loop() { | |
// This handles the network and cloud connection | |
//BlynkProvisioning.run(); | |
Blynk.run(); | |
if(!Blynk.connected()) { | |
Serial.println(F("Resetting in loop")); | |
ESP.restart(); | |
} | |
timer.run(); | |
if (!client.connected()) { | |
reconnect(); | |
} | |
client.loop(); | |
// check if the direction changed | |
if(preferences.getChar("last_dir",-1)!=last_dir){ | |
preferences.putChar("last_dir",last_dir); | |
Serial.printf("Saving direction %i\n", last_dir); | |
// check if Blynk is connected | |
// If Blynk is not connected then do NOT send the signals, this is because it may stall the rest of the program. | |
if(Blynk.connected()){ | |
Serial.printf("Writing direction %i\n", last_dir); | |
// write 1023 because Blynk writes accept values 0-1023 to control brightness | |
if(last_dir==DIR_OPEN){ | |
//lcd.clear(); //Use it to clear the LCD Widge | |
}else{ | |
//lcd.clear(); //Use it to clear the LCD Widget | |
//lcd.print(4, 0, "Status:"); // use: (position X: 0-15, position Y: 0-1, "Message you want to print") | |
//lcd.print(4, 1, "World"); | |
// Please use timed events when LCD printintg in void loop to avoid sending too many commands | |
// It will cause a FLOOD Error, and connection will be dropped | |
} | |
//Blynk.syncAll(); | |
} | |
} | |
if(check_timer<millis()){ | |
check_timer=millis()+55000; // check every 55 seconds. (avoid missing minutes) | |
// get the current time | |
struct tm ctime; | |
ctime.tm_hour=hour(); | |
ctime.tm_min=minute(); | |
DEBUG_STREAM.print(" ---- Time is "); | |
DEBUG_STREAM.print(ctime.tm_hour); | |
DEBUG_STREAM.print(":"); | |
DEBUG_STREAM.print(ctime.tm_min); | |
DEBUG_STREAM.println(" ----"); | |
if(ctime.tm_hour==2){ | |
daylight_timer=millis()+10; // trigger very soon to get daylight data back | |
} | |
DEBUG_STREAM.print("Timezone Offset: "); | |
DEBUG_STREAM.println(last_timezone_offset); | |
DEBUG_STREAM.print("GPS looks valid: "); | |
DEBUG_STREAM.println((lon==0||lat==0||lon==-1||lat==-1) ? "NO" : "YES"); | |
for(int i=0;i<4;i++){ | |
DEBUG_STREAM.print("Timer: "); | |
DEBUG_STREAM.print(i); | |
DEBUG_STREAM.print(" is "); | |
DEBUG_STREAM.println((bool)times[i].active); | |
// don't waste CPU time | |
if(!times[i].active)continue; | |
// check for different time zones | |
/*if(times[i].offset!=last_timezone_offset){ | |
// reconfigure clock | |
DEBUG_STREAM.print("Clock is being reconfigured...\nNew time zone="); | |
DEBUG_STREAM.println(times[i].offset); | |
configTime(times[i].offset, 0, ntpServer, "time.nist.gov", "time.windows.com"); | |
last_timezone_offset=times[i].offset; | |
// get the current time | |
getLocalTime(&ctime); | |
DEBUG_STREAM.print(" ---- Time is "); | |
DEBUG_STREAM.print(ctime.tm_hour); | |
DEBUG_STREAM.print(":"); | |
DEBUG_STREAM.print(ctime.tm_min); | |
DEBUG_STREAM.println(" ----"); | |
}*/ | |
if(times[i].type==1){ // sunrise | |
// change times[i] to target the sunrise | |
times[i].minute=sunrise.minute; | |
times[i].hour=sunrise.hour; | |
// add the wait time for sunrise/sunset | |
/*times[i].minute+=sun_delay; | |
times[i].hour+=times[i].minute/60; | |
times[i].minute=times[i].minute%60;*/ | |
}if(times[i].type==2){ // sunset | |
// change times[i] to target the sunset | |
times[i].minute=sunset.minute; | |
times[i].hour=sunset.hour; | |
// add the wait time for sunrise/sunset | |
times[i].minute+=sun_delay; | |
times[i].hour+=times[i].minute/60; | |
times[i].minute=times[i].minute%60; | |
} | |
DEBUG_STREAM.print(" Check is "); | |
DEBUG_STREAM.print(times[i].hour); | |
DEBUG_STREAM.print(":"); | |
DEBUG_STREAM.println(times[i].minute); | |
// check time first (lest likely to match first = fastest computation) | |
if(ctime.tm_min==times[i].minute&&ctime.tm_hour==times[i].hour){ | |
// check week day (day_sel is days since monday, tm_wday is days since sunday) | |
if(times[i].day_sel[(ctime.tm_wday+6)%7]){ | |
// odd indexes close and even indexes open | |
if(i%2==0){ | |
// even (open) | |
if(last_dir!=DIR_OPEN) | |
command = MOVE_OPEN; | |
}else{ | |
// odd (close) | |
if(last_dir!=DIR_CLOSE) | |
command = MOVE_CLOSE; | |
} | |
} | |
} | |
} | |
} | |
// find out what time the sunrise/sunset is once a day, super early in the morning | |
if(daylight_timer<millis()){ | |
daylight_timer=millis()+86400000; | |
HTTPClient http; | |
http.begin(sunsetsunrisebaseurl+String(lon)+"&lat="+String(lat)); | |
if(http.GET()<0)return; // error | |
const char* data=http.getString().c_str(); | |
StaticJsonDocument<800> doc; | |
DeserializationError error = deserializeJson(doc, data); | |
// Test if parsing succeeds. | |
if (error) { | |
DEBUG_STREAM.print(F("deserializeJson() failed: ")); | |
DEBUG_STREAM.println(error.c_str()); | |
return; | |
} | |
// Get the root object in the document | |
JsonObject root = doc.as<JsonObject>()["results"]; | |
String sunrise_str = root["sunrise"]; // "2018-09-09T05:55:31+00:00" | |
String sunset_str = root["sunset"]; // "2018-09-09T18:34:09+00:00" | |
sunrise_str=sunrise_str.substring(11,16); | |
sunset_str=sunset_str.substring(11,16); | |
int sunset_hr=(sunset_str.substring(0,2).toInt()+(last_timezone_offset/3600)+24)%24; | |
int sunrise_hr=(sunrise_str.substring(0,2).toInt()+(last_timezone_offset/3600)+24)%24; | |
int sunset_min=sunset_str.substring(3).toInt(); | |
int sunrise_min=sunrise_str.substring(3).toInt(); | |
sunrise.hour=sunrise_hr; | |
sunrise.minute=sunrise_min; | |
sunset.hour=sunset_hr; | |
sunset.minute=sunset_min; | |
//last_timezone_offset=-1;// force update of timezone and time data | |
} | |
} | |
void save_time(int i){ | |
DEBUG_STREAM.print("Saving time #"); | |
DEBUG_STREAM.println(i); | |
if(times[i].type==0){ | |
DEBUG_STREAM.print(" > Active at "); | |
DEBUG_STREAM.print(times[i].hour); | |
DEBUG_STREAM.print(":"); | |
DEBUG_STREAM.println(times[i].minute); | |
}else if(times[i].type==1){ | |
DEBUG_STREAM.println(" > Active at sunrise."); | |
}else | |
DEBUG_STREAM.println(" > Active at sunset."); | |
// make one string variable to save CPU power and memory | |
String num_=String(i); | |
preferences.putUInt(("hour_"+num_).c_str(), times[i].hour); | |
preferences.putUInt(("minute_"+num_).c_str(), times[i].minute); | |
preferences.putUInt(("type_"+num_).c_str(), times[i].type); | |
preferences.putLong(("offset_"+num_).c_str(), times[i].offset); | |
preferences.putUChar(("active_"+num_).c_str(), times[i].active); | |
for(int n=0;n<7;n++){ | |
preferences.putUChar(("day_sel_"+num_+"_"+n).c_str(), times[i].day_sel[n]); | |
} | |
} | |
void load_time(int i){ | |
// make one string variable to save CPU power and memory | |
String num_=String(i); | |
DEBUG_STREAM.print("Reloading timer "); | |
DEBUG_STREAM.println(i); | |
times[i].hour=preferences.getUInt(("hour_"+num_).c_str(),12); | |
times[i].minute=preferences.getUInt(("minute_"+num_).c_str(), 0); | |
times[i].type=preferences.getUInt(("type_"+num_).c_str(), 0); | |
times[i].offset=preferences.getLong(("offset_"+num_).c_str(), -25200); | |
// only set the first two as active by default | |
times[i].active=preferences.getUChar(("active_"+num_).c_str(), 0); | |
for(int n=0;n<7;n++){ | |
// by default select all days. | |
times[i].day_sel[n]=preferences.getUChar(("day_sel_"+num_+"_"+n).c_str(), 1); | |
} | |
} | |
void clockout_setup(){ | |
periph_module_enable(PERIPH_LEDC_MODULE); | |
uint32_t bit_width = 2; // 1 - 20 bits | |
uint32_t divider = 320; // Q10.8 fixed point number, 0x100 — 0x3FFFF | |
uint32_t duty_cycle = 1 << (bit_width - 1); | |
float freq_mhz = ((uint64_t) LEDC_APB_CLK_HZ << 8) / (double) divider / (1 << bit_width) / 1000000.0; | |
printf("\nfrequency: %f MHz\n", freq_mhz); | |
ledc_timer_set(LEDC_HIGH_SPEED_MODE, LEDC_TIMER_0, divider, bit_width, LEDC_APB_CLK); | |
ledc_timer_rst(LEDC_HIGH_SPEED_MODE, LEDC_TIMER_0); | |
ledc_timer_resume(LEDC_HIGH_SPEED_MODE, LEDC_TIMER_0); | |
ledc_channel_config_t pwm_pin_cfg = { | |
CLOCKOUT, // chosen GPIO output for clock | |
LEDC_HIGH_SPEED_MODE, // speed mode | |
LEDC_CHANNEL_0, // ledc channel | |
LEDC_INTR_DISABLE, // interrupt type | |
LEDC_TIMER_0, // timer select | |
duty_cycle // duty cycle | |
}; | |
ledc_channel_config(&pwm_pin_cfg); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment