smart teapot with ESP8266


carbonfiber heating wire.


there are 3x DS18B20 i2c temperature sensors. one for each heatingloop.


driver board with 3 channels, IRF1404 and Rds(on) = 0.004 Ohm


the ESP8266


PS3 Fat PSU as power supply (12V, 282W). the teapots maximum power usage is 202W.


teapot and controlbox. added bimetal switch as overtemperature protection at 105°C.


isolated finished 1.7l smart teapot.


control interface running openhab2 and mosquitto as mqtt broker. accessible over http.

[code language=”cpp”]
/*
Author: Dejan Lauber
Date: 7.2017
File: mqtt_esp8266_418_i_am_a_teapot.ino
Version: V2.1

Description:
Smart Teapot

pinout:
D0 PSU EN
D5 HCt
D6 HCm
D7 HCb
D8

D1 DS18b20
D2 OverTemp
D3 led&&fan
D4 button

TC[]:
0 bottom
1 middle
2 top

Tset[]:
0 warm
1 brew

MQTT topics sub:
teapot/lef_cmd

MQTT topics pub&sub:
teapot/pottingphase
teapot/Twarm
teapot/Tbrew
teapot/BrewTime

MQTT topics pub:
teapot/lef_fb
teapot/updateID
teapot/TCt
teapot/TCm
teapot/TCb
teapot/power

valid range:
BrewTime = 0-240 min
Twarm = 20-80 °C; Tbrew = 50-95 °C
when using Tbrew>80°C, water temp will be less than 80°C when reaching pottingphase 4

pottingphase:
0 off
1 Pmax to Tbrew-x
2 Pmax3/4 to Tbrew+x
3 Pmax1/2 to Tbrew+x
4 stay at Tbrew for BrewTime minutes
5 cooling to Twarm
6 stay at Twarm
7 stay at Tbrew
8 heat with 25% (no fan) to Tbrew+x

*/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Pushbutton.h>
#include <EEPROM.h>

//mqtt wifi
const char* ssid = "";
const char* password = "";
const char* mqtt_server = ""; //ip
//ds18b20
const byte ONE_WIRE_BUS = D1;
const byte TEMPERATURE_PRECISION = 10;

const word MQTT_INTERVAL = 5000;
const word TR_INTERVAL = 1000;

//pin constants
const byte OverTempPin = D2;
const byte buttonpin = D4;
const byte lef = D3;
const byte psu_en = D0;
const byte driver_HC[3] = {D7, D6, D5}; //b,m,t

const int Pmax = 1024; //heating driver (Pmax=1024)

//mqtt
WiFiClient espClient;
PubSubClient client(espClient);
//ds18b20
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// arrays to hold device addresses
DeviceAddress AT[3]; //b,m,t
//button
Pushbutton button(buttonpin, PULL_UP_ENABLED, DEFAULT_STATE_HIGH);

long lastMQTT, lastTR, lastMC, lastWlan, BrewStartTime, lef_timer;
word updateID = 1, BrewTime = 25, power[3], smoothtpower;
byte werror, merror = 252, lefbuf, preReq, pottingphase = 0, Tset[2] = {54, 80}, hotpot[3];
float TC[3], tpower;
String msg;

void setup() {
pinMode(lef, OUTPUT);
pinMode(psu_en, OUTPUT);
pinMode(OverTempPin, INPUT);
//pinMode(buttonpin, INPUT);
pinMode(driver_HC[2], OUTPUT);
digitalWrite(driver_HC[2], HIGH);
pinMode(driver_HC[1], OUTPUT);
digitalWrite(driver_HC[1], HIGH);
pinMode(driver_HC[0], OUTPUT);
digitalWrite(driver_HC[0], HIGH);

Serial.begin(9600);

Serial.println("418 I’m a teapot!");

// Start up the library
sensors.begin();
// locate devices on the bus
Serial.print("Locating sensors. Found ");
Serial.print(sensors.getDeviceCount(), DEC);
Serial.println(" devices.");

//make sure all 3 devices are reachable
if (!sensors.getAddress(AT[2], 0) || !sensors.getAddress(AT[1], 1) || !sensors.getAddress(AT[0], 2)) {
allOff(0);
while (1) {
Serial.println("Unable to find i2c addresses for devices, turning off");
delay(10000);
}
}

sensors.setWaitForConversion(false);

sensors.setResolution(AT[2], TEMPERATURE_PRECISION);
sensors.setResolution(AT[1], TEMPERATURE_PRECISION);
sensors.setResolution(AT[0], TEMPERATURE_PRECISION);

if ((sensors.getResolution(AT[2]) != TEMPERATURE_PRECISION) || (sensors.getResolution(AT[1]) != TEMPERATURE_PRECISION) || (sensors.getResolution(AT[0]) != TEMPERATURE_PRECISION)) {
allOff(0);
while (1) {
Serial.println("Unable to successfully set ds18b20 resolutions, turning off!");
delay(10000);
}
}

EEPROM.begin(128);
byte eeAddress = 1;

//test
//EEPROM.write(0,255);
/*EEPROM.put(eeAddress, 15);
eeAddress += sizeof(BrewTime);
EEPROM.put(eeAddress, Tset);
eeAddress += sizeof(Tset);
eeAddress = 1;
Serial.println("saving EEPROM.. ");*/
//EEPROM.commit();

Serial.print("loading EEPROM.. ");
if (EEPROM.read(0) == 42) {
Serial.println("ok");
EEPROM.get(eeAddress, BrewTime);
eeAddress += sizeof(BrewTime);
EEPROM.get(eeAddress, Tset);
eeAddress += sizeof(Tset);
} else {
Serial.println("nothing there yet.");
}
//Serial.println(BrewTime);

setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}

void setup_wifi() {
// Set WiFi to station mode and disconnect from an AP if it was Previously
// connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);

// We start by connecting to WiFi network
Serial.print("Connecting to ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while ((WiFi.status() != WL_CONNECTED) && (millis() < 17000)) {
digitalWrite(lef, !digitalRead(lef));
Serial.print(".");
delay(500);
}
if ((WiFi.status() == WL_CONNECTED)) {
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("");
Serial.println("WiFi connection error, wlan deactivated, continuing");
werror = 254;
}

}

byte reconnect() {
Serial.print("Attempting MQTT connection… ");
// ATttempt to connect
if (client.connect("ESP_teapot")) {
Serial.println("connected");
// Once connected, publish an announcement…
msg = String(updateID);
client.publish("teapot/updateID", msg.c_str());
msg = String(pottingphase);
client.publish("teapot/pottingphase", msg.c_str());
msg = String(0);
client.publish("teapot/lef_fb", msg.c_str());
msg = String(BrewTime);
client.publish("teapot/BrewTime", msg.c_str());
msg = String(Tset[0]);
client.publish("teapot/Twarm", msg.c_str());
msg = String(Tset[1]);
client.publish("teapot/Tbrew", msg.c_str());
// … and resubscribe
client.subscribe("teapot/pottingphase");
client.subscribe("teapot/lef_cmd");
client.subscribe("teapot/BrewTime");
client.subscribe("teapot/Twarm");
client.subscribe("teapot/Tbrew");
lastMQTT = millis();
}
return client.connected();
}

void callback(char* topic, byte* payload, word length) {
byte invalidint = 0;
word payword = 0;
Serial.print("Message arrived [" + String(topic) + "] (");

for (word i = 0; i < length; i++) {
Serial.write(payload[i]);
if (length <= 4) {
if ((payload[i] < ‘0’) || (payload[i] > ‘9’)) {
invalidint = 1;
}
payword += (payload[i] – ‘0’) * pow(10, (length – 1) – i);
} else {
invalidint = 1;
}
}
Serial.println(")");
if (invalidint == 0) {

if (String(topic) == "teapot/pottingphase") {
if (payword <= 8) {
if (payword != pottingphase) {
pottingphase = payword;
if (pottingphase == 4) {
BrewStartTime = millis();
}
lef_timer = millis() – 5000 + (pottingphase + 1) * 500;
hotpot[0] = 0;
hotpot[1] = 0;
hotpot[2] = 0;
Serial.println("changing pottingphase to " + String(pottingphase));
}
} else {
if (client.connected()) {
msg = String(pottingphase);
client.publish("teapot/pottingphase", msg.c_str());
}
}
}

if (String(topic) == "teapot/BrewTime") {
if (payword <= 240) {
if (payword != BrewTime) {
BrewTime = payword;
save_stuff();
Serial.println("changing BrewTime to " + String(BrewTime));
}
} else {
if (client.connected()) {
msg = String(BrewTime);
client.publish("teapot/BrewTime", msg.c_str());
}
}
}

if (String(topic) == "teapot/Twarm") {
if ((payword >= 20) && (payword <= 80) && (payword < Tset[1])) {
if (payword != Tset[0]) {
Tset[0] = payword;
save_stuff();
Serial.println("changing Twarm to " + String(Tset[0]));
}
} else {
if (client.connected()) {
msg = String(Tset[0]);
client.publish("teapot/Twarm", msg.c_str());
}
}
}

if (String(topic) == "teapot/Tbrew") {
if ((payword >= 50) && (payword <= 95) && (payword > Tset[0])) {
if (payword != Tset[1]) {
Tset[1] = payword;
save_stuff();
Serial.println("changing Tbrew to " + String(Tset[1]));
}
} else {
if (client.connected()) {
msg = String(Tset[1]);
client.publish("teapot/Tbrew", msg.c_str());
}
}
}

if (String(topic) == "teapot/lef_cmd") {
// Switch on the LED if an 1 was received as first character
if (payload[0] == ‘1’) {
lefbuf = 1; // Turn the LED on
if (client.connected()) {
msg = String(pottingphase);
client.publish("teapot/pottingphase", msg.c_str());
msg = String(BrewTime);
client.publish("teapot/BrewTime", msg.c_str());
msg = String(Tset[0]);
client.publish("teapot/Twarm", msg.c_str());
msg = String(Tset[1]);
client.publish("teapot/Tbrew", msg.c_str());
}
} else if (payload[0] == ‘0’) {
lefbuf = 0; // Turn the LED ooff
}
if (client.connected()) {
msg = String(lefbuf);
client.publish("teapot/lef_fb", msg.c_str());
}
}
} else {
Serial.println("invalid payload");
if (client.connected()) {
msg = String(pottingphase);
client.publish("teapot/pottingphase", msg.c_str());
msg = String(lefbuf);
client.publish("teapot/lef_fb", msg.c_str());
msg = String(BrewTime);
client.publish("teapot/BrewTime", msg.c_str());
msg = String(Tset[0]);
client.publish("teapot/Twarm", msg.c_str());
msg = String(Tset[1]);
client.publish("teapot/Tbrew", msg.c_str());
}
}
}

void allOff(byte block) {
//0: non-blocking; 1: blocking;
digitalWrite(lef, HIGH);
digitalWrite(driver_HC[2], HIGH);
digitalWrite(driver_HC[1], HIGH);
digitalWrite(driver_HC[0], HIGH);
digitalWrite(psu_en, LOW);
Serial.println("AllOff");
while (block) {
delay(0);
}
}

void save_stuff(void) {
byte eeAddress = 1;
Serial.println("saving EEPROM.. ");
EEPROM.write(0, 42);
EEPROM.put(eeAddress, BrewTime);
eeAddress += sizeof(BrewTime);
EEPROM.put(eeAddress, Tset);
eeAddress += sizeof(Tset);
EEPROM.commit();
}
void checkhotpot() {
if ((hotpot[0] + hotpot[1] + hotpot[2]) >= 2) {
BrewStartTime = millis();
if (pottingphase == 8) {
pottingphase = 5;
} else {
pottingphase++;
}
hotpot[0] = 0;
hotpot[1] = 0;
hotpot[2] = 0;
if (client.connected()) {
msg = String(pottingphase);
client.publish("teapot/pottingphase", msg.c_str());
}
Serial.println("increasing pottingphase to " + String(pottingphase));
}
}
void loop() {
//on error, blink lef
if (werror < 60) {
if (WiFi.status() != WL_CONNECTED) {
if (millis() – lastWlan > 1000) {
lastWlan = millis();
//throw error but continue
Serial.print(".");
lefbuf = !lefbuf;
werror++;
}
} else {
werror = 0;
if ((merror < 60) || ((merror > 200) && (merror < 254))) {
if (!client.connected()) {
//throw error but continue

if (millis() – lastMC > 5000) {
lastMC = millis();
// Attempt to reconnect
if (reconnect()) {
lastMC = 0;
lefbuf = 0;
merror = 0;
}
else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
merror++;
if (merror > 2) {
lefbuf = 1;
}
}
}
} else {
merror = 0;
// Client connected
client.loop();
}
} else {
if (merror != 255) {
lefbuf = 0;
merror = 255;
}
}
}
} else {
if (werror != 255) {
werror = 255;
lefbuf = 0;
}
}

//check OverTemp bimetal switch
if (!digitalRead(OverTempPin)) {
allOff(0);
while (1) {
Serial.println("overtemp switch is active, turning off!");
delay(10000);
}
}

//if smoothtpower stays over (63W ~ 110W >) 86W, turn fan on; approx. 20s
if (lefbuf || ((pottingphase >= 1) && (pottingphase <= 3)) || ((smoothtpower / 16) >= 86)) {
digitalWrite(lef, HIGH);
} else {
digitalWrite(lef, LOW); // Turn the LED off
}

if ((pottingphase != 0) && (pottingphase != 5)) {
digitalWrite(psu_en, HIGH);
} else {
digitalWrite(psu_en, LOW); // Turn PSU off
}

if (((millis() – lastTR) > (TR_INTERVAL – 400)) && !preReq) { //start temp conversion 400 ms in advance
preReq = 1;
//Serial.print("Requesting temperatures… ");
sensors.requestTemperatures(); // Send the command to get temperatures

}

if ((millis() – lastTR) > TR_INTERVAL) {
lastTR = millis();
preReq = 0;

TC[2] = sensors.getTempC(AT[2]);
TC[1] = sensors.getTempC(AT[1]);
TC[0] = sensors.getTempC(AT[0]);

//Serial.println("temperatures received");
if ((TC[2] < 5) || (TC[2] > 100) || (TC[1] < 5) || (TC[1] > 100) || (TC[0] < 5) || (TC[0] > 96)) {
allOff(0);
while (1) {
Serial.println("temperature range left, turning off!");
delay(10000);
}
}

//calc total power
tpower = (float)(((power[0] * 4) + (power[1] * 9) + (power[2] * 8)) * 0.001) * 9.4; //calc power consumption
//tpower = tpower * 1.114 + 6; //ps3 psu efficiency ~87.5%
smoothtpower = smoothtpower – (smoothtpower / 16) + tpower;
}
delay(0);

//teapotting
switch (pottingphase) {
case 0://off
for (byte n = 0; n < 3; n++) {
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
hotpot[n] = 0;
}
break;
case 1://100% (TCmax: 75C @ Tset=95C) old; 90C test 94; otp:93C; 90C, limit 87C
for (byte n = 0; n < 3; n++) {
if ((TC[n] < (Tset[1] + 10)) && (TC[n] < 87)) {
if (((millis() ^ (0xFFFFFFFF * (n % 2))) % 1024) < ((Pmax / 4) * (5 – pottingphase))) {
digitalWrite(driver_HC[n], LOW);
}
else {
digitalWrite(driver_HC[n], HIGH);
}
power[n] = (Pmax / 4) * (5 – pottingphase);
}
else {
hotpot[n] = 1;
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
digitalWrite(driver_HC[0], HIGH);
power[0] = 0;
}
}
}
checkhotpot();
break;
case 2://75% (TCmax: 86C @ Tset=95C) old; 101C test 108; otp:96C; 93C, limit 90C
for (byte n = 0; n < 3; n++) {
if ((TC[n] < (Tset[1] + 13)) && (TC[n] < 90)) {
if (((millis() ^ (0xFFFFFFFF * (n % 2))) % 1024) < ((Pmax / 4) * (5 – pottingphase))) {
digitalWrite(driver_HC[n], LOW);
}
else {
digitalWrite(driver_HC[n], HIGH);
}
power[n] = (Pmax / 4) * (5 – pottingphase);
}
else {
hotpot[n] = 1;
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
digitalWrite(driver_HC[0], HIGH);
power[0] = 0;
}
}
}
checkhotpot();
break;
case 3://50% (TCmax: 97C @ Tset=95C) old; killed; otp:-; 96C; 95C
for (byte n = 0; n < 3; n++) {
if ((TC[n] < (Tset[1] + 15)) && (TC[n] < 95)) {
if (((millis() ^ (0xFFFFFFFF * (n % 2))) % 1024) < ((Pmax / 4) * (5 – pottingphase))) {
digitalWrite(driver_HC[n], LOW);
}
else {
digitalWrite(driver_HC[n], HIGH);
}
power[n] = (Pmax / 4) * (5 – pottingphase);
}
else {
hotpot[n] = 1;
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
digitalWrite(driver_HC[0], HIGH);
power[0] = 0;
}
}
}
checkhotpot();
break;
case 4://Tbrew
for (byte n = 0; n < 3; n++) {
if (TC[n] < Tset[1]) {
if (((millis() ^ (0xFFFFFFFF * (n % 2))) % 1024) < (Pmax / 2)) {
digitalWrite(driver_HC[n], LOW);
}
else {
digitalWrite(driver_HC[n], HIGH);
}
power[n] = Pmax / 2;
}
else {
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
digitalWrite(driver_HC[0], HIGH);
power[0] = 0;
}
}
}
if (((millis() – BrewStartTime) / 1000) > (BrewTime * 60)) {
pottingphase++;
if (client.connected()) {
msg = String(pottingphase);
client.publish("teapot/pottingphase", msg.c_str());
}
Serial.println("increasing pottingphase to " + String(pottingphase));
}
break;
case 5://cooldown
for (byte n = 0; n < 3; n++) {
if (TC[n] < Tset[0]) {
hotpot[n] = 1;
}
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
}
if ((hotpot[0] + hotpot[1] + hotpot[2]) >= 2) {
pottingphase++;
hotpot[0] = 0;
hotpot[1] = 0;
hotpot[2] = 0;
if (client.connected()) {
msg = String(pottingphase);
client.publish("teapot/pottingphase", msg.c_str());
}
Serial.println("increasing pottingphase to " + String(pottingphase));
}
break;
case 6://static at Twarm
for (byte n = 0; n < 3; n++) {
if (TC[n] < Tset[0]) {
if (((millis() ^ (0xFFFFFFFF * (n % 2))) % 1024) < (Pmax / 2)) {
digitalWrite(driver_HC[n], LOW);
}
else {
digitalWrite(driver_HC[n], HIGH);
}
power[n] = Pmax / 2;
}
else {
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
digitalWrite(driver_HC[0], HIGH);
power[0] = 0;
}
}
}
break;
case 7://static at Tbrew
for (byte n = 0; n < 3; n++) {
if (TC[n] < Tset[1]) {
if (((millis() ^ (0xFFFFFFFF * (n % 2))) % 1024) < (Pmax / 2)) {
digitalWrite(driver_HC[n], LOW);
}
else {
digitalWrite(driver_HC[n], HIGH);
}
power[n] = Pmax / 2;
}
else {
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
digitalWrite(driver_HC[0], HIGH);
power[0] = 0;
}
}
}
break;
case 8://heating 25% up to Tset[1]
for (byte n = 0; n < 3; n++) {
if (TC[n] < Tset[1]) {
if (((millis() ^ (0xFFFFFFFF * (n % 2))) % 1024) < (Pmax / 4)) {
digitalWrite(driver_HC[n], LOW);
}
else {
digitalWrite(driver_HC[n], HIGH);
}
power[n] = Pmax / 4;
}
else {
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
digitalWrite(driver_HC[0], HIGH);
power[0] = 0;
}
}
}//25% TCmax: 95 for each TC
for (byte n = 0; n < 3; n++) {
if ((TC[n] < (Tset[1] + 7)) && (TC[n] < 95)) {
if (((millis() ^ (0xFFFFFFFF * (n % 2))) % 1024) < (Pmax / 4)) {
digitalWrite(driver_HC[n], LOW);
}
else {
digitalWrite(driver_HC[n], HIGH);
}
power[n] = Pmax / 4;
}
else {
hotpot[n] = 1;
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
digitalWrite(driver_HC[0], HIGH);
power[0] = 0;
}
}
}
checkhotpot();
break;
default:
allOff(1);
break;
}

for (byte n = 0; n < 3; n++) {
if ((TC[n] > (TC[0] + 25)) || (TC[n] > (TC[1] + 20)) || (TC[n] > (TC[2] + 20))) {
digitalWrite(driver_HC[n], HIGH);
power[n] = 0;
if (n == 1) {
if (!(((millis() ^ (0xFFFFFFFF * (0 % 2))) % 1024) < (Pmax / 2))) {
digitalWrite(driver_HC[0], HIGH);
}
power[0] = Pmax / 2;
}
}
}

if ((millis() – lastMQTT) > MQTT_INTERVAL) {
lastMQTT = millis();

Serial.println("Temperatures are (TCt, TCm, TCb): " + String(TC[2]) + ", " + String(TC[1]) + ", " + String(TC[0]));

if (client.connected()) {

Serial.println("MQTT Publish");
msg = String(TC[2]);
client.publish("teapot/TCt", msg.c_str());
msg = String(TC[1]);
client.publish("teapot/TCm", msg.c_str());
msg = String(TC[0]);
client.publish("teapot/TCb", msg.c_str());

updateID++;
msg = String(updateID);
client.publish("teapot/updateID", msg.c_str());

msg = String(tpower);
client.publish("teapot/power", msg.c_str());
//Serial.println("MQTT Publish successfull");
}
else {
Serial.println("MQTT not connected!");
}
}

if (button.getSingleDebouncedPress()) {
switch (pottingphase) {
case 0:
pottingphase = 1;
break;
case 1:
case 2:
case 3:
case 4:
pottingphase = 5;
break;
case 5:
case 6:
case 7:
pottingphase = 8;
break;
case 8:
pottingphase = 0;
break;
}

lef_timer = millis() – 5000 + (pottingphase + 1) * 500;
//Serial.println("lef_timer active");
hotpot[0] = 0;
hotpot[1] = 0;
hotpot[2] = 0;
if (client.connected()) {
msg = String(pottingphase);
client.publish("teapot/pottingphase", msg.c_str());
}
Serial.println("changing pottingphase to " + String(pottingphase));
}

if (lef_timer != 0) {
if (millis() – lef_timer < 5000) {
lefbuf = ((millis() – lef_timer) / 250) % 2;
} else {
lefbuf = 0;
lef_timer = 0;
}
}

//check for millis overflow
if (millis() < lastMQTT) {
lastMQTT = millis();
}
if (millis() < lastTR) {
lastTR = millis();
}
if (millis() < lastWlan) {
lastWlan = millis();
}
if (millis() < lastMC) {
lastMC = millis();
}
if (millis() < BrewStartTime) {
BrewStartTime = millis();
}
if (millis() < lef_timer) {
lef_timer = millis();
}
}
[/code]