// Heltec WiFi LoRa 32 V3 (SX1262) thermostat slave, continuous control
// 7/12/25 Heltec V3 slave thermostat
// Users/graham/Dropbox/electronics/Arduino/lora/heltec_tests/heltec1/slow_updates/heltec_t_command_send_one_DS18B20_temp3/heltec_t_command_send_one_DS18B20_temp3.ino
// - DS18B20 on GPIO7 (inside / control sensor)
// - DS18B20 on GPIO5 (greenhouse)
// - DS18B20 on GPIO4 (outside)
// - Relay on GPIO6 (via transistor + your LED)
// - Device ID prefix: commands must start with DEVICE_ID
//
// Commands:
//
// 1) Set config + read:
// "APPLEtTTHH"
//
// "APPLE" = DEVICE_ID (5 chars)
// 't' = thermostat config command (heating mode)
// TT = setpoint in °C (2 digits, integer)
// HH = hysteresis in °C (2 digits, integer)
//
// Example: APPLEt3002 => setpoint 30C, hysteresis 2C
//
// Behaviour: updates setpoint & hysteresis, reads temps, updates relay,
// replies "IN,GH,OUT,SETPOINT"
//
// 2) Read-only (no config change):
// "APPLEr0000" (or simply "APPLEr")
//
// Behaviour: reads temps, updates relay using existing setpoint/hysteresis,
// replies "IN,GH,OUT,SETPOINT"
//
// OLED:
// - Big top: inside temp + "RL ON"/"RL OFF" at top-right
// - Line2: "GH:xx.x OUT:yy.y"
// - Line3: "SP:xx H:yy"
// - Line4: "RSS:-nn APPLE"

#define HELTEC_POWER_BUTTON // long-press PRG = power control
#include <heltec_unofficial.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <math.h>

// ---- Device ID prefix ----
#define DEVICE_ID "APPLE" // 5-character ID for this node

// ---- DS18B20 pins ----
#define PIN_INSIDE 7 // inside / control sensor
#define PIN_GREENHOUSE 5 // greenhouse sensor
#define PIN_OUTSIDE 4 // outside sensor

// ---- Relay on GPIO6 ----
#define RELAY_PIN 6

// ---- OneWire + DallasTemperature instances ----
OneWire oneWireInside(PIN_INSIDE);
OneWire oneWireGH(PIN_GREENHOUSE);
OneWire oneWireOut(PIN_OUTSIDE);

DallasTemperature sensorInside(&oneWireInside);
DallasTemperature sensorGH(&oneWireGH);
DallasTemperature sensorOut(&oneWireOut);

// ---- Temperature variables ----
float tempInsideC = NAN; // control temperature (inside)
float tempGreenhouseC = NAN; // greenhouse
float tempOutsideC = NAN; // outside

// ---- Thermostat settings (initial) ----
float setpointC = 30.0;
float hysteresisC = 2.0;

// LoRa receive
String rxData;
volatile bool rxFlag = false;

// Relay state (true = ON)
bool relayOn = false;

// Periodic temperature update interval
const unsigned long TEMP_INTERVAL_MS = 5000; // 5 seconds
unsigned long lastTempMillis = 0;

// Last radio stats for OLED
float lastRSSI = NAN;

// ISR forward declaration
void onRx();

// ---- Generic DS18B20 read ----
float readTemperature(DallasTemperature &s) {
s.requestTemperatures();
float t = s.getTempCByIndex(0);
if (t == DEVICE_DISCONNECTED_C) {
return NAN;
}
return t;
}

// ---- Read all three sensors ----
void readAllTemperatures() {
tempInsideC = readTemperature(sensorInside);
tempGreenhouseC = readTemperature(sensorGH);
tempOutsideC = readTemperature(sensorOut);

Serial.print("Temps: IN=");
if (isnan(tempInsideC)) Serial.print("NaN"); else Serial.printf("%.2f", tempInsideC);
Serial.print(" GH=");
if (isnan(tempGreenhouseC)) Serial.print("NaN"); else Serial.printf("%.2f", tempGreenhouseC);
Serial.print(" OUT=");
if (isnan(tempOutsideC)) Serial.print("NaN"); else Serial.printf("%.2f", tempOutsideC);
Serial.println();
}

// ---- Apply thermostat logic (heating mode, using inside temp) ----
void updateThermostat(float insideC) {
if (isnan(insideC)) {
// Sensor error -> turn relay OFF for safety
relayOn = false;
} else {
float onThreshold = setpointC - hysteresisC;
float offThreshold = setpointC;

// Heating mode with hysteresis
if (!relayOn && insideC <= onThreshold) {
relayOn = true;
} else if (relayOn && insideC >= offThreshold) {
relayOn = false;
}
}

digitalWrite(RELAY_PIN, relayOn ? HIGH : LOW);

Serial.printf("Thermostat: set=%.1fC, hyst=%.1fC, IN=%.2fC, relay=%s\n",
setpointC, hysteresisC,
isnan(insideC) ? -999.0 : insideC,
relayOn ? "ON" : "OFF");
}

// ---- Build and send reply: IN,GH,OUT,SETPOINT ----


/*
void sendTemperatureReply() {
// Build reply: IN,GH,OUT,SETPOINT
char replyMsg[64];
snprintf(replyMsg, sizeof(replyMsg),
"%.2f,%.2f,%.2f,%.2f",
tempInsideC, tempGreenhouseC, tempOutsideC, setpointC);

Serial.printf("Replying with: %s\n", replyMsg);

radio.clearDio1Action();
heltec_led(50);

RADIOLIB(radio.transmit(replyMsg));
Serial.printf("[RadioLib] radio.transmit(replyMsg) returned %d\n", _radiolib_status);

heltec_led(0);
radio.setDio1Action(onRx);
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
}
*/


void sendTemperatureReply() {
char replyMsg[96];
snprintf(replyMsg, sizeof(replyMsg),
"%s,%.2f,%.2f,%.2f,%.2f",
DEVICE_ID, tempInsideC, tempGreenhouseC, tempOutsideC, setpointC);

Serial.printf("Replying with: %s\n", replyMsg);

radio.clearDio1Action();
heltec_led(50);

RADIOLIB(radio.transmit(replyMsg));
Serial.printf("[RadioLib] radio.transmit(replyMsg) returned %d\n", _radiolib_status);

heltec_led(0);
radio.setDio1Action(onRx);
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
}






// ---- OLED UI ----
void updateDisplay() {
display.clear();

// Big inside temperature at top (left)
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_24);

String tempStr;
if (isnan(tempInsideC)) {
tempStr = "--.-C";
} else {
tempStr = String(tempInsideC, 1) + "C";
}
display.drawString(0, 0, tempStr);

// Small "RL ON/OFF" at top-right
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
String rlStr = relayOn ? "RL ON" : "RL OFF";
display.drawString(127, 4, rlStr);

display.setTextAlignment(TEXT_ALIGN_LEFT);

// Line 2: greenhouse + outside, e.g. "GH:23.8 OUT:12.3"
String line2 = "GH:";
if (isnan(tempGreenhouseC)) line2 += "--.-";
else line2 += String(tempGreenhouseC, 1);
line2 += " OUT:";
if (isnan(tempOutsideC)) line2 += "--.-";
else line2 += String(tempOutsideC, 1);
display.drawString(0, 28, line2);

// Line 3: setpoint + hysteresis
int spInt = (int)round(setpointC);
int hyInt = (int)round(hysteresisC);
String line3 = "SP:" + String(spInt) + " H:" + String(hyInt);
display.drawString(0, 40, line3);

// Line 4: RSSI + device name, e.g. "RSS:-34 APPLE"
String line4 = "RSS:";
if (isnan(lastRSSI)) line4 += "--";
else line4 += String((int)lastRSSI);
line4 += " ";
line4 += DEVICE_ID;
display.drawString(0, 52, line4);

display.display();
}

// -------------------------------------------------------------------
// SETUP
// -------------------------------------------------------------------
void setup() {
heltec_setup(); // sets up Serial, display, radio, button, etc.
Serial.println("Heltec V3 LoRa thermostat SLAVE (3 sensors, ID-only commands)");
Serial.println("Initialising radio and DS18B20s...");

// Start DS18B20 sensors
sensorInside.begin();
sensorGH.begin();
sensorOut.begin();

// Relay pin
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
relayOn = false;

// Basic radio init
RADIOLIB_OR_HALT(radio.begin());

// Disable CRC to match SX1278 master
RADIOLIB_OR_HALT(radio.setCRC(0));

// Match SX1278 master settings
RADIOLIB_OR_HALT(radio.setFrequency(868.0));
RADIOLIB_OR_HALT(radio.setBandwidth(125.0));
RADIOLIB_OR_HALT(radio.setSpreadingFactor(9));
RADIOLIB_OR_HALT(radio.setCodingRate(5)); // 4/5
RADIOLIB_OR_HALT(radio.setPreambleLength(8));
RADIOLIB_OR_HALT(radio.setSyncWord(0x34));
RADIOLIB_OR_HALT(radio.setOutputPower(17));

// Set callback on DIO1 for received packets and start RX
radio.setDio1Action(onRx);
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));

Serial.println("Commands:");
Serial.println(" " DEVICE_ID "tTTHH -> set setpoint/hyst, reply IN,GH,OUT,SET");
Serial.println(" " DEVICE_ID "r0000 -> read-only, reply IN,GH,OUT,SET");
Serial.println("Example: " DEVICE_ID "t3002 (30C, hyst 2C)");
Serial.printf("Initial setpoint = %.1f C, hysteresis = %.1f C\n",
setpointC, hysteresisC);

// Initial temperature read + thermostat decision
readAllTemperatures();
updateThermostat(tempInsideC);
lastTempMillis = millis();

updateDisplay();
}

// -------------------------------------------------------------------
// LOOP
// -------------------------------------------------------------------
void loop() {
heltec_loop(); // keep Heltec power button etc working

unsigned long now = millis();

// Periodic background temperature check and thermostat control
if (now - lastTempMillis >= TEMP_INTERVAL_MS) {
lastTempMillis = now;
readAllTemperatures();
updateThermostat(tempInsideC);
updateDisplay();
}

// Handle received LoRa packets
if (rxFlag) {
rxFlag = false;

// Read received data
radio.readData(rxData);

if (_radiolib_status == RADIOLIB_ERR_NONE) {
rxData.trim();

Serial.printf("RX: \"%s\"\n", rxData.c_str());
float rssi = radio.getRSSI();
float snr = radio.getSNR();
Serial.printf(" RSSI: %.2f dBm\n", rssi);
Serial.printf(" SNR: %.2f dB\n", snr);

lastRSSI = rssi;

// Expect at least "APPLEx" = 6 chars
if (rxData.length() >= 6) {
String prefix = rxData.substring(0, 5);
char cmdType = rxData.charAt(5);

if (prefix == DEVICE_ID) {

// 1) Config command: DEVICE_ID + 't' + TTHH (>= 10 chars)
if (cmdType == 't') {
if (rxData.length() >= 10) {
String setStr = rxData.substring(6, 8); // TT
String hystStr = rxData.substring(8, 10); // HH

int setInt = setStr.toInt();
int hystInt = hystStr.toInt();

// Simple sanity limits
if (setInt >= -40 && setInt <= 99 && hystInt >= 0 && hystInt <= 50) {
setpointC = (float)setInt;
hysteresisC = (float)hystInt;

Serial.printf("New config: set=%.1fC, hyst=%.1fC\n",
setpointC, hysteresisC);

// Read all temps, update thermostat, reply
readAllTemperatures();
updateThermostat(tempInsideC);
updateDisplay();
sendTemperatureReply();
} else {
Serial.println("Out-of-range setpoint/hysteresis, ignoring.");
}
} else {
Serial.println("Thermostat command too short, ignoring.");
}
}

// 2) Read-only command: DEVICE_ID + 'r' (with or without extra digits)
else if (cmdType == 'r') {
Serial.println("Read-only command: no config change.");
// Just read temps, apply thermostat with existing setpoint/hyst, reply
readAllTemperatures();
updateThermostat(tempInsideC);
updateDisplay();
sendTemperatureReply();
}

else {
Serial.println("Unknown command type for this device, ignoring.");
}

} else {
Serial.println("Prefix does not match this device, ignoring.");
}

} else {
Serial.println("Received too-short packet, ignoring.");
}

// Resume listening
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));

} else {
Serial.printf("RX error (%d), restarting RX\n", _radiolib_status);
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
}
}
}

// ---- ISR – keep it very short: just set a flag ----
void onRx() {
rxFlag = true;
}