/**************
*
* RoastESP32 - NodeMCU ESP32 thermocouple reader based on Arduino TC4
* *******************************************************************
*
* Resources
* =========
* TC-4
* https://github.com/FilePhil/TC4-Emulator
* https://github.com/greencardigan/TC4-shield/blob/master/applications/Artisan/aArtisan/trunk/src/aArtisan/commands.txt
* https://www.youtube.com/watch?v=0-Co-pXF2NM
* DHT22
* https://randomnerdtutorials.com/esp32-dht11-dht22-temperature-humidity-sensor-arduino-ide/
* MAX6675
* https://arduino.stackexchange.com/questions/37193/multiple-3-wire-spi-sensor-interfacing-with-arduino
* SPI
* https://www.thaieasyelec.com/article-wiki/embedded-electronics-application/09-espino32-spi.html
*
* Command sequence from artisan
* =============================
* CHAN;1200
* UNITS;C
* FILT;85;85;70;70
* READ
* https://www.home-barista.com/home-roasting/configuring-artisan-pid-t32351.html
*
*/
#include "user.h"
#include <SerialCommands.h> // https://github.com/ppedro74/Arduino-SerialCommands
#include <BluetoothSerial.h> // Classic Bluetooth
#include <DHT.h> // DHT22
#include <SPI.h> // MAX6675 over hardware SPI
// Bluetooth
#define ROAST_ESP32_BLUETOOTH_NAME "U.A.F. Mk. 2" //Bluetooth device name
BluetoothSerial SerialBT;
// DHT22
#define DHTPIN 22 // GPIO22 -> DHT22 Output
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
double humidity;
double ambientc;
double ambientf;
// MAX6675
#define TC1_CS 32 // GPIO32 -> CS MAX6675[TC1]
#define TC2_CS 33 // GPIO33 -> CS MAX6675[TC2]
#define TC3_CS 25 // GPIO25 -> CS MAX6675[TC3]
#define TC4_CS 26 // GPIO26 -> CS MAX6675[TC4]
/* Note: All MAX6675
* MAX6675 to EPS32
* VCC -> 3.3V
* GND -> GND
* SCK -> GPIO18/CLK
* SO -> GPIO19/MISO
*/
bool unit_F = false;
#define SMA 5
int sma_idx = 0;
bool sma_filled = false;
double tc1s[SMA], tc2s[SMA], tc3s[SMA], tc4s[SMA];
double tc1, tc2, tc3, tc4;
// DHT22
void readDHT() {
float h = dht.readHumidity();
float c = dht.readTemperature();
float f = dht.readTemperature(true);
if (!isnan(h)) {
humidity = h;
}
if (!isnan(c)) {
ambientc = c;
}
if (!isnan(f)) {
ambientf = f;
}
}
// MAX6675
double readCelsius(uint8_t cs) {
uint16_t v;
digitalWrite(cs, LOW);
v = SPI.transfer(0x00);
v <<= 8;
v |= SPI.transfer(0x00);
digitalWrite(cs, HIGH);
if (v & 0x4) {
// uh oh, no thermocouple attached!
return NAN;
}
v >>= 3;
return v * 0.25;
}
double readFahrenheit(uint8_t cs) {
return readCelsius(cs) * 1.8 + 32;
}
bool readTCs() {
tc1s[sma_idx] = readCelsius(TC1_CS);
tc2s[sma_idx] = readCelsius(TC2_CS);
tc3s[sma_idx] = readCelsius(TC3_CS);
tc4s[sma_idx] = readCelsius(TC4_CS);
if (!isnan(tc1s[sma_idx]) && !isnan(tc1s[sma_idx]) && !isnan(tc1s[sma_idx]) && !isnan(tc1s[sma_idx])) {
sma_idx++;
if (sma_idx >= SMA) {
sma_filled = true;
sma_idx = 0;
}
tc1 = 0;
tc2 = 0;
tc3 = 0;
tc4 = 0;
if (sma_filled) {
for (int i = 0; i < SMA; i++) {
tc1 += tc1s[i];
tc2 += tc2s[i];
tc3 += tc3s[i];
tc4 += tc4s[i];
}
tc1 /= SMA;
tc2 /= SMA;
tc3 /= SMA;
tc4 /= SMA;
}
return true;
}
return false;
}
// USB & Bluetooth SerialCommands
char serialbt_cmds_buffer[32];
char serial_cmds_buffer[32];
SerialCommands serialbt_cmds(&SerialBT, serialbt_cmds_buffer, sizeof(serialbt_cmds_buffer), "\n", ";");
SerialCommands serial_cmds(&Serial, serial_cmds_buffer, sizeof(serial_cmds_buffer), "\n", ";");
//This is the default handler, and gets called when no other command matches.
void cmd_unrecognized(SerialCommands* sender, const char* cmd) {
sender->GetSerial()->print("Unrecognized command [");
sender->GetSerial()->print(cmd);
sender->GetSerial()->println("]");
}
void cmdSetChannel(SerialCommands* sender) {
sender->GetSerial()->println("#OK");
}
SerialCommand serialCmdSetChannel("CHAN", cmdSetChannel);
void cmdSetUnits(SerialCommands* sender) {
char* units = sender->Next();
if (units[0] == 'F') {
unit_F = true;
sender->GetSerial()->println("#OK Farenheit");
} else if (units[0] == 'C') {
unit_F = false;
sender->GetSerial()->println("#OK Celsius");
}
}
SerialCommand serialCmdSetUnits("UNITS", cmdSetUnits);
void cmdSetFilter(SerialCommands* sender) {
sender->GetSerial()->println("#OK");
}
SerialCommand serialCmdSetFilter("FILT", cmdSetFilter);
void cmdRead(SerialCommands* sender) {
readDHT();
if (unit_F) {
sender->GetSerial()->print(ambientf);
sender->GetSerial()->print(",");
sender->GetSerial()->print(tc1 * 1.8 + 32);
sender->GetSerial()->print(",");
sender->GetSerial()->print(tc2 * 1.8 + 32);
sender->GetSerial()->print(",");
sender->GetSerial()->print(tc3 * 1.8 + 32);
sender->GetSerial()->print(",");
sender->GetSerial()->print(tc4 * 1.8 + 32);
sender->GetSerial()->print(",0.00,0.00,0.00"); // Heater, Fan, PID set value
} else {
sender->GetSerial()->print(ambientc);
sender->GetSerial()->print(",");
sender->GetSerial()->print(tc1);
sender->GetSerial()->print(",");
sender->GetSerial()->print(tc2);
sender->GetSerial()->print(",");
sender->GetSerial()->print(tc3);
sender->GetSerial()->print(",");
sender->GetSerial()->print(tc4);
sender->GetSerial()->print(",0.00,0.00,0.00"); // Heater, Fan, PID set value
}
sender->GetSerial()->println("");
}
SerialCommand serialCmdRead("READ", cmdRead);
void setup() {
// DHT22 for ambient and humidity
pinMode(DHTPIN, INPUT);
dht.begin();
// Thermocouple (MAX6675 x4 over hardware SPI)
pinMode(TC1_CS, OUTPUT);
pinMode(TC2_CS, OUTPUT);
pinMode(TC3_CS, OUTPUT);
pinMode(TC4_CS, OUTPUT);
digitalWrite(TC1_CS, HIGH);
digitalWrite(TC2_CS, HIGH);
digitalWrite(TC3_CS, HIGH);
digitalWrite(TC4_CS, HIGH);
SPI.begin();
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
// USB Serial
Serial.begin(115200);
serial_cmds.SetDefaultHandler(cmd_unrecognized);
serial_cmds.AddCommand(&serialCmdSetChannel);
serial_cmds.AddCommand(&serialCmdSetUnits);
serial_cmds.AddCommand(&serialCmdSetFilter);
serial_cmds.AddCommand(&serialCmdRead);
// Bluetooth Serial
SerialBT.begin(ROAST_ESP32_BLUETOOTH_NAME);
serialbt_cmds.SetDefaultHandler(cmd_unrecognized);
serialbt_cmds.AddCommand(&serialCmdSetChannel);
serialbt_cmds.AddCommand(&serialCmdSetUnits);
serialbt_cmds.AddCommand(&serialCmdSetFilter);
serialbt_cmds.AddCommand(&serialCmdRead);
sma_idx = 0;
sma_filled = true;
tc1 = 0;
tc2 = 0;
tc3 = 0;
tc4 = 0;
}
void loop() {
serialbt_cmds.ReadSerial();
serial_cmds.ReadSerial();
if (readTCs()) {
delay(200);
}
}
//Parsing Serial Commands
void handleSerialCommand() {
if (SerialBT.available() > 0) {
String msg = SerialBT.readStringUntil('\n');
// if (Serial.available()>0){
// String msg = Serial.readStringUntil('\n'); //Serial.println(msg); //use for debug
if (msg.indexOf("CHAN;") == 0) { //connected to Artisan
//Started = true;
SerialBT.print("#OK");
// Serial.print("#OK");
} else if (msg.indexOf("UNITS;") == 0) {
if (msg.substring(6, 7) == "F") { //Change to Farenheit
unit_F = true;
// Serial.println("#OK Farenheit");
SerialBT.println("#OK Farenheit");
} else if (msg.substring(6, 7) == "C") { //Change to Celsius
unit_F = false;
// Serial.println("#OK Celsius");
SerialBT.println("#OK Celsius");
}
} else if (msg.indexOf("OT1") == 0) { //heater set command
// Serial.print("#OK");
SerialBT.print("#OK");
// get OT1 value, convert to integer to save in dutyCycle
dutyCycle = (msg.substring(4).toInt());
//TelnetStream.print("dutyCycle is ");
//TelnetStream.println (dutyCycle);
if (ManualMode == false) {
ManDutyCycle = dutyCycle; //set manual duty cycle to last artisan duty cycle
//set PWM for heater
PWMDutyCycle = map(dutyCycle, 0, 100, 1, MaxDutyCycle);
ledcWrite(HeatChannel, PWMDutyCycle);
}
}
else if (msg.indexOf("IO3") == 0) { //fan power set command
SerialBT.print("#OK");
// get OT1 value, convert to integer to save in dutyCycle
fanPwr = (msg.substring(4).toInt());
FanDutyCycle = map(fanPwr, 0, 100, 0, 255);
dacWrite(FanPin, FanDutyCycle);
}
else if (msg.indexOf("READ") == 0) { //Send Temps
//"Command_READ" is not defined - should probably be a function to call cmdRead but with a parameter
//Command_READ();
}
}
}