#include #include #include #include #include #include // ========================= // LoRa pins - LilyGO T3S3 SX1262 // ========================= static const int PIN_LORA_SCK = 5; static const int PIN_LORA_MISO = 3; static const int PIN_LORA_MOSI = 6; static const int PIN_LORA_CS = 7; static const int PIN_LORA_RST = 8; static const int PIN_LORA_DIO1 = 33; static const int PIN_LORA_BUSY = 34; // ========================= // Button - LilyGO T3S3 // BOOT button usually GPIO0, active low // ========================= static const int PIN_BUTTON = 0; // ========================= // OLED pins - LilyGO T3S3 OLED // ========================= static const int OLED_SDA = 18; static const int OLED_SCL = 17; static const int OLED_ADDR = 0x3C; static const int SCREEN_WIDTH = 128; static const int SCREEN_HEIGHT = 64; // ========================= // ACTIVE PROFILE - Shelly / custom LoRa // ========================= static const float LORA_FREQ_MHZ = 865.000; static const float LORA_BW_KHZ = 125.0; static const uint8_t LORA_SF = 12; static const uint8_t LORA_CR = 5; static const uint8_t LORA_SYNC_WORD = 0x12; static const int8_t LORA_TX_POWER = 10; static const uint16_t LORA_PREAMBLE = 10; static const float LORA_TCXO_VOLTAGE = 1.6; static const bool LORA_USE_LDO = false; static const bool LORA_USE_CRC = false; static const bool LORA_EXPLICIT_HDR = true; // ========================= // Protocol // ========================= static const String DEVICE_ID = "MG64738"; static const String CMD_ON = "ON"; static const String CMD_OFF = "OF"; // ========================= // Objects // ========================= Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); SX1262 radio = new Module(PIN_LORA_CS, PIN_LORA_DIO1, PIN_LORA_RST, PIN_LORA_BUSY); // ========================= // Runtime state // ========================= bool outputState = true; // start with ON bool lastButtonState = HIGH; unsigned long lastDebounceMs = 0; unsigned long txCount = 0; int lastTxState = 0; static const unsigned long DEBOUNCE_MS = 60; // ========================= // Helpers // ========================= String formatCR(uint8_t cr) { return "4/" + String(cr); } String formatHexByte(uint8_t value) { char buf[5]; snprintf(buf, sizeof(buf), "%02X", value); return String(buf); } String currentPayload() { return DEVICE_ID + "|" + (outputState ? CMD_ON : CMD_OFF); } void drawLine(int x, int y, const String& text) { display.setCursor(x, y); display.print(text); } void drawStatusScreen(const String& status) { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); drawLine(0, 0, "LoRa Button TX"); drawLine(88, 0, String(txCount) + " tx"); drawLine(0, 10, "F:" + String(LORA_FREQ_MHZ, 3)); drawLine(0, 20, "BW:" + String(LORA_BW_KHZ, 1) + " SF:" + String(LORA_SF)); drawLine(0, 30, "CR:" + formatCR(LORA_CR) + " SW:0x" + formatHexByte(LORA_SYNC_WORD)); drawLine(0, 42, status); drawLine(0, 54, currentPayload()); display.display(); } void drawErrorScreen(const String& title, int code) { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); drawLine(0, 0, title); drawLine(0, 12, "code: " + String(code)); display.display(); } void sendCurrentState() { String payload = currentPayload(); drawStatusScreen("Sending..."); Serial.print("TX: "); Serial.println(payload); int state = radio.transmit(payload); lastTxState = state; if (state == RADIOLIB_ERR_NONE) { txCount++; Serial.println("TX OK"); drawStatusScreen(outputState ? "Sent ON" : "Sent OF"); } else { Serial.print("TX failed, code "); Serial.println(state); drawStatusScreen("TX failed: " + String(state)); } } void setup() { Serial.begin(115200); delay(1500); pinMode(PIN_BUTTON, INPUT_PULLUP); Wire.begin(OLED_SDA, OLED_SCL); if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) { Serial.println("OLED init failed"); while (true) delay(1000); } drawStatusScreen("Booting..."); SPI.begin(PIN_LORA_SCK, PIN_LORA_MISO, PIN_LORA_MOSI, PIN_LORA_CS); Serial.println(); Serial.println("Starting LoRa button sender..."); int state = radio.begin( LORA_FREQ_MHZ, LORA_BW_KHZ, LORA_SF, LORA_CR, LORA_SYNC_WORD, LORA_TX_POWER, LORA_PREAMBLE, LORA_TCXO_VOLTAGE, LORA_USE_LDO ); if (state != RADIOLIB_ERR_NONE) { Serial.print("radio.begin failed, code "); Serial.println(state); drawErrorScreen("LoRa init failed", state); while (true) delay(1000); } if (LORA_EXPLICIT_HDR) { state = radio.explicitHeader(); } else { state = radio.implicitHeader(LORA_PREAMBLE); } if (state != RADIOLIB_ERR_NONE) { Serial.print("header mode failed, code "); Serial.println(state); drawErrorScreen("Header config failed", state); while (true) delay(1000); } state = radio.setCRC(LORA_USE_CRC); if (state != RADIOLIB_ERR_NONE) { Serial.print("CRC config failed, code "); Serial.println(state); drawErrorScreen("CRC config failed", state); while (true) delay(1000); } drawStatusScreen("Ready"); sendCurrentState(); // first send = MG64738|ON } void loop() { bool buttonState = digitalRead(PIN_BUTTON); if (buttonState != lastButtonState) { lastDebounceMs = millis(); lastButtonState = buttonState; } if ((millis() - lastDebounceMs) > DEBOUNCE_MS) { static bool pressHandled = false; if (buttonState == LOW && !pressHandled) { pressHandled = true; outputState = !outputState; sendCurrentState(); } if (buttonState == HIGH) { pressHandled = false; } } }