Here is a copy of the code:
cpp<#define VERSION "Brew 4.18.18D"
// Virtual pin maps
// PUSH = data automatically sent to Blynk
// PULL = Blynk will request the data
// WRITE = Blynk will send the data to the device
// V0 - PUSH Temp C
// V1 - PUSH Relay (output) State
// V2 - PUSH Temperature Setpoint readback
// V3 - PULL RSSI
// V4 - PULL Version / Hostname / IP Address / SSID / BSSID / Auth Token
// V5 - PULL Sensor read fails
// V6 - PULL Sensor report fails
// V7 - WRITE Temperature Setpoint
// V8 - WRITE PID Mode AUTOMATIC/MANUAL
// V9 - WRITE PID Direction DIRECT/REVERSE
// V10 - WRITE PID Max Window Size
// V11 - WRITE PID Min Window Size
// V12 - WRITE PID Proportional on error or measurement P_ON_E/P_ON_M
// V13 - WRITE Kp PID Proportional
// V14 - WRITE Ki PID Integral
// V15 - WRITE Kd PID Derivative
// V16 - PUSH Output
#define MYDEBUG
// Blynk debug
#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG
//Blynk Colors
#define BLYNK_GREEN "#23C48E"
#define BLYNK_BLUE "#04C0F8"
#define BLYNK_YELLOW "#ED9D00"
#define BLYNK_RED "#D3435C"
#define BLYNK_DARK_BLUE "#5F7CD8"
// Indicate which board we are using
//#define NODEMCU
#define WEMOS
#include <BlynkSimpleEsp8266.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
#include <ArduinoOTA.h> // For OTA
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WidgetRTC.h>
#include <PID_v1.h>
#include <EEPROM.h>
#define EEPROM_SALT 12667
typedef struct {
char blynkToken[33] = "";
double tempSetpoint = 10;
double Kp = 2;
double Ki = 5;
double Kd = 1;
int WindowSizeMax = 5000;
int WindowSizeMin = 0;
int PIDMode = AUTOMATIC;
int PIDDirection = REVERSE;
int PIDProportional = P_ON_E;
int salt = EEPROM_SALT;
} DefaultSettings;
DefaultSettings settings;
#define OLED_RESET -1
Adafruit_SSD1306 display(OLED_RESET);
#ifdef MYDEBUG
#define MYDEBUG_PRINT(...) Serial.print(__VA_ARGS__)
#define MYDEBUG_PRINTLN(...) Serial.println(__VA_ARGS__)
#else
#define MYDEBUG_PRINT(...)
#define MYDEBUG_PRINTLN(...)
#endif
// This is the data pin we will use for the temperature sensor
#define ONE_WIRE_BUS D6
#include <DallasTemperature.h>
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// Pin on the NODEMCU we will use for controlling the relay
#define RELAY_BUS1 D7
#ifdef NODEMCU
#define LED_BUS D0
#endif
#ifdef WEMOS
#define LED_BUS 2
#endif
// select wich pin will trigger the configuraton portal when set to LOW
// ESP-01 users please note: the only pins available (0 and 2), are shared
// with the bootloader, so always set them HIGH at power-up. For the NodeMCU boards
// this is the flash button...very convenient
// NOTE: for the LinkSprite board this is D8 and there is no button on board
#define TRIGGER_PIN 0
// The extra parameters to be configured (can be either global or just in the setup)
// After connecting, parameter.getValue() will get you the configured value
// id/name placeholder/prompt default length
WiFiManagerParameter custom_token("token", "blynk token", settings.blynkToken, sizeof(settings.blynkToken));
int read_fails = 0; // will increment each time a bad read occurs
int report_fails = 0; // will increment if we didn't get a good reading in the report period
bool report_good = false; // When we report back to blynk we will reset this to false and each good read will set to true
int V4_Index = 0; // 0 = Version, 1 = Hostname, 2 = IP Address, 3 = SSID, 4 = BSSID, 5 = Auth Token
// Real time clock
WidgetRTC rtc;
// Timer functions
BlynkTimer timer;
// Timer IDs
int timerID_ReadSensor;
int timerID_ReportSensor;
int timerID_CheckConnection;
int timerID_ResetESP;
int timerID_CheckForReset;
int timerID_ResetSensor;
int timerID_runPID;
int timerID_oledDisplay;
int timerID_EEPROMCheck;
// Timer Durations
#define timer_ReadSensor 1000L // 1 Second
#define timer_ReportSensor 1000L // 1 Second
#define timer_CheckConnection 10000L // 10 Seconds
#define timer_ResetESP 60000L // 1 minute
#define timer_CheckForReset 1000L // 1 second
#define timer_ResetSensor 300000L // if the sensor hasn't had a good reading in 5 minutes reset
#define timer_runPID 100L // .1 seconds
#define timer_oledDisplay 2000L // refresh the display every 2 seconds
#define timer_EEPROMCheck 60000L // 1 minute
// PID Section
double tempC;
double Output;
PID myPID(&tempC, &Output, &settings.tempSetpoint, settings.Kp, settings.Ki, settings.Kd, settings.PIDDirection);
unsigned long windowStartTime;
//callback notifying us of the need to save config
void saveConfigCallback () {
MYDEBUG_PRINTLN("Config has changed, let's save it");
//read updated parameters
strcpy(settings.blynkToken, custom_token.getValue());
SaveFlash();
}
bool ReadFlash()
{
// Read the EEPROM and if salt is good return true
EEPROM.begin(512);
EEPROM.get(0, settings);
EEPROM.end();
if (settings.salt == EEPROM_SALT) return true;
else return false;
}
void SaveFlash()
{
EEPROM.begin(512);
EEPROM.put(0, settings);
EEPROM.end();
}
void InitFlash()
{
MYDEBUG_PRINTLN("Invalid settings in EEPROM, settings defaults");
DefaultSettings defaults;
settings = defaults;
EEPROM.begin(512);
EEPROM.put(0, settings);
EEPROM.end();
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
delay(1000);
MYDEBUG_PRINTLN();
// Print out version info
MYDEBUG_PRINT("Version: ");
MYDEBUG_PRINTLN(VERSION);
pinMode(LED_BUS, OUTPUT); //Declare Pin mode for LED flasher
pinMode(RELAY_BUS1, OUTPUT); //Declare Pin mode for Relay Module
digitalWrite(RELAY_BUS1,HIGH);
pinMode(ONE_WIRE_BUS, INPUT_PULLUP); // eliminates need for 4.7k resistor in DS18B20 CCT
sensors.begin();
// Set resolution to 11 bits
// Bits Res Time
// 9 0.5C 93.75ms
// 10 0.25C 197.5ms
// 11 0.125C 375ms
// 12 0.0625C 750ms
sensors.setResolution(11);
// Request temperature in sync mode first
sensors.requestTemperatures();
sensors.setWaitForConversion(false);
// by default, we'll generate the high voltage from the 3.3v line internally
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128x64)
// init done
// Show image buffer on the display hardware.
// Since the buffer is intialized with an Adafruit splashscreen
// internally, this will display the splashscreen.
display.display();
// Clear the buffer.
display.clearDisplay();
display.setTextColor(WHITE);
display.println("System Starting...");
display.println("");
display.println(VERSION);
display.println("");
// If json is not good the only thing we should do is recreate it.
if (!ReadFlash())
{
// Flash isn't setup
InitFlash();
}
//WiFiManager
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;
//set config save notify callback
wifiManager.setSaveConfigCallback(saveConfigCallback);
wifiManager.addParameter(&custom_token);
// If the blynkToken is not yet configured there is no point connecting to whatever
// was configured for wifi.
if (settings.blynkToken == "")
{
wifiManager.resetSettings();
display.println("Blynk not configured");
display.println("");
display.print("AP:");
display.println(" ESP" + String(ESP.getChipId()));
display.display();
}
else
{
wifiManager.setTimeout(120);
display.println("Connecting to WiFi...");
display.display();
}
//tries to connect to last known settings
//if it does not connect it starts an access point
//and goes into a blocking loop awaiting configuration
if (!wifiManager.autoConnect()) {
MYDEBUG_PRINTLN("failed to connect");
// If we timeout here it is because we have a configuration but it didn't connect
// We don't need to do anything as it should connect later when wifi is available
// or we didn't configure it correctly and will have to push the flash button
}
else
{
// We did connect
MYDEBUG_PRINT("local ip: ");
MYDEBUG_PRINTLN(WiFi.localIP());
}
MYDEBUG_PRINT("Blynk Token: ");
MYDEBUG_PRINTLN(settings.blynkToken);
// Configure Blynk with the valid stored auth token or the new one just configured
Blynk.config(settings.blynkToken);
// Other Time library functions can be used, like:
// timeStatus(), setSyncInterval(interval)...
// Read more: http://www.pjrc.com/teensy/td_libs_Time.html
setSyncInterval(10 * 60); // Sync interval in seconds (10 minutes)
// ArduinoOTA.setHostname("Wemos D1 Mini"); // For OTA - Use your own device identifying name
ArduinoOTA.begin(); // For OTA
// Start up timer routines
timerID_ReadSensor = timer.setInterval(timer_ReadSensor, ReadSensor);
timerID_ReportSensor = timer.setInterval(timer_ReportSensor, ReportSensor);
timerID_CheckConnection = timer.setInterval(timer_CheckConnection, CheckConnection);
timerID_ResetESP = timer.setInterval(timer_ResetESP, ResetESP);
timerID_CheckForReset = timer.setInterval(timer_CheckForReset, CheckForReset);
timerID_ResetSensor = timer.setInterval(timer_ResetSensor, ResetSensor);
timerID_runPID = timer.setInterval(timer_runPID, runPID);
timerID_oledDisplay = timer.setInterval(timer_oledDisplay, oledDisplay);
timerID_EEPROMCheck = timer.setInterval(timer_EEPROMCheck, EEPROMCheck);
// Initialize PID parameters
windowStartTime = millis();
//tell the PID to range between 0 and the full window size
myPID.SetOutputLimits(settings.WindowSizeMin, settings.WindowSizeMax);
//turn the PID on
myPID.SetMode(settings.PIDMode);
myPID.SetSampleTime(timer_runPID);
}
void loop() {
// put your main code here, to run repeatedly:
if (WiFi.status() == WL_CONNECTED)
{
Blynk.run();
ArduinoOTA.handle(); // For OTA
}
timer.run();
}
void ReadSensor() {
// Round the temp to one decimal point
tempC = sensors.getTempCByIndex(0);
// Round to one decimal
//tempC = round(tempC * 10.0)/10.0;
if (tempC != -127.0)
{
// Reset the ResetSensor timer
timer.restartTimer(timerID_ResetSensor);
report_good = true;
}
else
{
read_fails = read_fails + 1;
}
// Start the next async temperature read
sensors.requestTemperatures();
}
void ReportSensor() {
// Report the data back to Blynk
if (WiFi.status() == WL_CONNECTED)
{
if (tempC < 127)
{
// Only report good data
Blynk.virtualWrite(V0, tempC);
Blynk.virtualWrite(V2, settings.tempSetpoint);
Blynk.virtualWrite(V16, Output);
}
}
// Debug code to verify the sensor is working, output data to the serial port
MYDEBUG_PRINTLN("");
MYDEBUG_PRINT("wifi status: ");
MYDEBUG_PRINTLN(WiFi.status());
MYDEBUG_PRINT("blynk status: ");
MYDEBUG_PRINTLN(Blynk.connected());
MYDEBUG_PRINT("Temp: ");
MYDEBUG_PRINT(tempC);
MYDEBUG_PRINTLN(" *C");
//Strobe LED
digitalWrite(LED_BUS, LOW); //Turn the LED on
delay(50);
digitalWrite(LED_BUS, HIGH); //Turn the LED off
}
void CheckConnection() {
// Check to see if wifi is in a connected state and we are connected to Blynk
if (WiFi.status() == WL_CONNECTED)
{
if (Blynk.connected())
{
// Reset the ResetESP timer
timer.restartTimer(timerID_ResetESP);
}
else
{
MYDEBUG_PRINTLN("Blynk not connected, reconnect.");
bool result = Blynk.connect();
}
}
}
void ResetESP() {
// We have not been connected to WiFi for timer_ResetESP duration, restart the ESP
MYDEBUG_PRINTLN("wifi connect has failed, restarting...");
//reset and try again, or maybe put it to deep sleep
while(1) ESP.reset();
delay(5000);
}
void ResetSensor() {
// We have not had a good reading in 5 minutes from the DHT22, restart the ESP
MYDEBUG_PRINTLN("DS18B20 is not responding, reset ESP");
//reset and try again, or maybe put it to deep sleep
while(1) ESP.reset();
delay(5000);
}
void CheckForReset() {
// is configuration reset requested?
if ( digitalRead(TRIGGER_PIN) == LOW ) {
MYDEBUG_PRINTLN("Reset Settings, waiting for restart");
delay(3000);
//reset and try again, or maybe put it to deep sleep
while(1) ESP.reset();
delay(5000);
}
}
void runPID() {
// Check if the temp is above the setpoint, if so then call for cooling
// If we are in manual mode then just eject. Relay will be controlled by user.
if (myPID.GetMode() == MANUAL) return;
myPID.Compute();
MYDEBUG_PRINT(Output);
/************************************************
* turn the output pin on/off based on pid output
************************************************/
if (millis() - windowStartTime > settings.WindowSizeMax)
{ //time to shift the Relay Window
windowStartTime += settings.WindowSizeMax;
}
if (Output < millis() - windowStartTime)
{
// No output required. Relay is enabled on LOW so set to HIGH.
digitalWrite(RELAY_BUS1, HIGH);
MYDEBUG_PRINTLN(" Relay DISABLED");
if (WiFi.status() == WL_CONNECTED)
{
Blynk.virtualWrite(V1, LOW);
}
}
else
{
// Output required. Relay is enabled on LOW so set to LOW.
digitalWrite(RELAY_BUS1, LOW);
MYDEBUG_PRINTLN(" Relay ENABLED");
if (WiFi.status() == WL_CONNECTED)
{
Blynk.virtualWrite(V1, HIGH);
}
}
}
BLYNK_READ(V3)
{
if (WiFi.status() == WL_CONNECTED)
{
Blynk.virtualWrite(V3, WiFi.RSSI());
MYDEBUG_PRINTLN("Sending RSSI");
}
}
BLYNK_READ(V4)
{
if (WiFi.status() == WL_CONNECTED)
{
if (V4_Index == 0)
{
Blynk.setProperty(V4, "label", "Software Version");
Blynk.virtualWrite(V4, VERSION);
MYDEBUG_PRINTLN("Sending Version");
V4_Index = 1;
}
else if (V4_Index == 1)
{
Blynk.setProperty(V4, "label", "Hostname");
Blynk.virtualWrite(V4, WiFi.hostname());
MYDEBUG_PRINTLN("Sending Hostname");
V4_Index = 2;
}
else if (V4_Index ==2)
{
Blynk.setProperty(V4, "label", "IP Address");
Blynk.virtualWrite(V4, WiFi.localIP().toString());
MYDEBUG_PRINTLN("Sending IP Address");
V4_Index = 3;
}
else if (V4_Index ==3)
{
Blynk.setProperty(V4, "label", "SSID");
Blynk.virtualWrite(V4, WiFi.SSID());
MYDEBUG_PRINTLN("Sending SSID");
V4_Index = 4;
}
else if (V4_Index ==4)
{
Blynk.setProperty(V4, "label", "BSSID");
Blynk.virtualWrite(V4, WiFi.BSSIDstr());
MYDEBUG_PRINTLN("Sending BSSID");
V4_Index = 5;
}
else if (V4_Index ==5)
{
Blynk.setProperty(V4, "label", "AUTH TOKEN");
Blynk.virtualWrite(V4, settings.blynkToken);
MYDEBUG_PRINTLN("Sending Auth Token");
V4_Index = 0;
}
}
}
BLYNK_READ(V5)
{
if (WiFi.status() == WL_CONNECTED)
{
Blynk.virtualWrite(V5, read_fails);
MYDEBUG_PRINTLN("Sending Read Fails");
}
}
BLYNK_READ(V6)
{
if (WiFi.status() == WL_CONNECTED)
{
Blynk.virtualWrite(V6, report_fails);
MYDEBUG_PRINTLN("Sending Report Fails");
}
}
BLYNK_WRITE(V7)
{
settings.tempSetpoint = param.asDouble(); // Get value as double
MYDEBUG_PRINT("Received setpoint: ");
MYDEBUG_PRINT(settings.tempSetpoint);
MYDEBUG_PRINTLN(" *C");
}
BLYNK_WRITE(V8)
{
MYDEBUG_PRINT("Received PIDmode: ");
if (param.asInt() == 0)
{
MYDEBUG_PRINTLN("0 AUTOMATIC");
settings.PIDMode = 0;
myPID.SetMode(AUTOMATIC);
}
else
{
settings.PIDMode = 1;
MYDEBUG_PRINTLN("1 MANUAL");
myPID.SetMode(MANUAL);
// Turn the relay off if switching to manual mode, user can now control
digitalWrite(RELAY_BUS1, HIGH);
if (WiFi.status() == WL_CONNECTED)
{
Blynk.virtualWrite(V1, LOW);
}
}
}
BLYNK_WRITE(V9)
{
MYDEBUG_PRINT("Received PIDDirection: ");
if (param.asInt() == 0)
{
MYDEBUG_PRINTLN("0 DIRECT");
settings.PIDDirection = 0;
myPID.SetControllerDirection(DIRECT);
}
else
{
MYDEBUG_PRINTLN("1 REVERSE");
settings.PIDDirection = 1;
myPID.SetControllerDirection(REVERSE);
}
}
BLYNK_WRITE(V10)
{
settings.WindowSizeMax = param.asInt() * 1000; // Get value as integer
myPID.SetOutputLimits(settings.WindowSizeMin, settings.WindowSizeMax);
MYDEBUG_PRINT("Received WindowSizeMax: ");
MYDEBUG_PRINTLN(settings.WindowSizeMax);
}
BLYNK_WRITE(V11)
{
settings.WindowSizeMin = param.asInt() * 1000; // Get value as integer
myPID.SetOutputLimits(settings.WindowSizeMin, settings.WindowSizeMax);
MYDEBUG_PRINT("Received WindowSizeMin: ");
MYDEBUG_PRINTLN(settings.WindowSizeMin);
}
BLYNK_WRITE(V12)
{
MYDEBUG_PRINT("Received PIDProportionalMode: ");
if (param.asInt() == 0)
{
MYDEBUG_PRINTLN("0 P_ON_E");
settings.PIDProportional = P_ON_E;
}
else
{
MYDEBUG_PRINTLN("1 P_ON_M");
settings.PIDProportional = P_ON_M;
}
myPID.SetTunings(settings.Kp, settings.Ki, settings.Kd, settings.PIDProportional);
}
BLYNK_WRITE(V13)
{
settings.Kp = param.asDouble(); // Get value as Double
myPID.SetTunings(settings.Kp, settings.Ki, settings.Kd, settings.PIDProportional);
MYDEBUG_PRINT("Received Kp: ");
MYDEBUG_PRINTLN(settings.Kp);
}
BLYNK_WRITE(V14)
{
settings.Ki = param.asDouble(); // Get value as Double
myPID.SetTunings(settings.Kp, settings.Ki, settings.Kd, settings.PIDProportional);
MYDEBUG_PRINT("Received Ki: ");
MYDEBUG_PRINTLN(settings.Ki);
}
BLYNK_WRITE(V15)
{
settings.Kd = param.asDouble(); // Get value as Double
myPID.SetTunings(settings.Kp, settings.Ki, settings.Kd, settings.PIDProportional);
MYDEBUG_PRINT("Received Kd: ");
MYDEBUG_PRINTLN(settings.Kd);
}
BLYNK_CONNECTED() {
Blynk.syncAll();
// Synchronize time on connection
rtc.begin();
}
// Update info on the display
void oledDisplay()
{
int bars;
// int bars = map(RSSI,-80,-44,1,6); // this method doesn't refelct the Bars well
// simple if then to set the number of bars
int RSSI = WiFi.RSSI();
if (RSSI > -55) {
bars = 5;
} else if (RSSI < -55 & RSSI > -65) {
bars = 4;
} else if (RSSI < -65 & RSSI > -70) {
bars = 3;
} else if (RSSI < -70 & RSSI > -78) {
bars = 2;
} else if (RSSI < -78 & RSSI > -82) {
bars = 1;
} else {
bars = 0;
}
display.clearDisplay();
// Do some simple loop math to draw rectangles as the bars
// This will occupy the top left of screen (0,0) to (21,10)
for (int b=0; b <= bars; b++) {
display.fillRect(6+(b*3),10-(b*2),2,b*2,WHITE);
}
// Draw the antenna symbol
display.drawTriangle(0,0,10,0,5,5,WHITE);
display.fillRect(5,0,1,10,WHITE);
// Build up a 12H time string
String currentTime = String() + ((hour()%12) < 10 ? " " : "") + (hour()%12)+ ':' + (minute() < 10 ? "0" : "") + minute();
// right justify time on first line. Time hh:mm am
display.setCursor(80,0);
display.setTextSize(1);
display.setTextColor(WHITE);
display.print(currentTime);
display.print((hour() > 12 ? " PM" : " AM"));
// on the second line display the setpoint and whether HEAT is being called
display.setCursor(80,8);
display.print(String() + (settings.tempSetpoint < 10 ? " " : "") + String(settings.tempSetpoint,0) + "C");
// Move to a half character height below the yellow area of the OLED
display.setCursor(0,16);
display.setTextSize(1);
display.print("Temp");
display.setCursor(0,28);
display.setTextSize(3);
display.print(String(tempC,0));
display.setTextSize(2);
display.print("C");
display.setCursor(80,28);
display.setTextSize(3);
// Move to the bottom line of the display and print out hostname and last 4 bytes of auth token
display.setTextSize(1);
display.setCursor(0,56);
display.print(WiFi.hostname());
//auth token is right justified
display.setCursor(104,56);
display.print(String()+settings.blynkToken[28]+settings.blynkToken[29]+settings.blynkToken[30]+settings.blynkToken[31]);
display.display();
}
void EEPROMCheck()
{
// Check to see if the EEPROM copy needs to be updated
DefaultSettings EEPROMsettings;
EEPROM.begin(512);
EEPROM.get(0, EEPROMsettings);
EEPROM.end();
if ((settings.tempSetpoint == EEPROMsettings.tempSetpoint) && (settings.Kp == EEPROMsettings.Kp) && (settings.Ki == EEPROMsettings.Ki)
&& (settings.Kd == EEPROMsettings.Kd) && (settings.WindowSizeMax == EEPROMsettings.WindowSizeMax)
&& (settings.WindowSizeMin == EEPROMsettings.WindowSizeMin) && (settings.PIDMode == EEPROMsettings.PIDMode)
&& (settings.PIDDirection == EEPROMsettings.PIDDirection) && (settings.PIDProportional == EEPROMsettings.PIDProportional))
{
MYDEBUG_PRINTLN("EEPROM is up to date");
}
else
{
MYDEBUG_PRINTLN("EEPROM not current, updating...");
EEPROM.begin(512);
EEPROM.put(0, settings);
EEPROM.end();
}
}
>