// googleHome.ino - ESP32 + Sinric Pro, with physical switches that still work.
#include <WiFi.h>
#include <SinricPro.h>
#include <SinricProSwitch.h>
#include <map>
#define WIFI_SSID "YOUR_WIFI_NAME"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
// From the Sinric Pro portal: Credentials, then each device's own page.
// These are secrets. Anyone holding them can control your house - never
// commit them, screenshot them, or paste them into a blog post.
#define APP_KEY "YOUR_APP_KEY"
#define APP_SECRET "YOUR_APP_SECRET"
#define BAUD_RATE 115200
#define DEBOUNCE_TIME 250
// deviceId -> { relay pin, flip-switch pin, last state, last change }
typedef struct {
int relayPin;
int flipPin;
bool lastFlipState;
unsigned long lastFlipChange;
} DeviceConfig;
// GPIO numbers, not D labels - the ESP32 has none. These four relay pins and
// four switch pins are all safe outputs/inputs on a standard dev board.
std::map<String, DeviceConfig> devices = {
{ "YOUR_DEVICE_ID_1", { 23, 13, true, 0 } },
{ "YOUR_DEVICE_ID_2", { 22, 12, true, 0 } },
{ "YOUR_DEVICE_ID_3", { 21, 14, true, 0 } },
{ "YOUR_DEVICE_ID_4", { 19, 27, true, 0 } },
};
void setupPins() {
for (auto &entry : devices) {
pinMode(entry.second.relayPin, OUTPUT);
// Relay modules are almost always active LOW: HIGH is off.
digitalWrite(entry.second.relayPin, HIGH);
// INPUT_PULLUP so the switch needs two wires and no resistor.
pinMode(entry.second.flipPin, INPUT_PULLUP);
entry.second.lastFlipState = digitalRead(entry.second.flipPin);
}
}
// Called when Google Home (via Sinric Pro) asks for a change.
bool onPowerState(const String &deviceId, bool &state) {
auto it = devices.find(deviceId);
if (it == devices.end()) return false;
digitalWrite(it->second.relayPin, state ? LOW : HIGH); // active LOW
return true;
}
// Physical switches. Polled with a debounce rather than an interrupt: these
// are mechanical toggles, and a bouncing contact fires an interrupt dozens of
// times per flip.
void handleFlipSwitches() {
unsigned long now = millis();
for (auto &entry : devices) {
DeviceConfig &cfg = entry.second;
bool state = digitalRead(cfg.flipPin);
if (state != cfg.lastFlipState && now - cfg.lastFlipChange > DEBOUNCE_TIME) {
cfg.lastFlipChange = now;
cfg.lastFlipState = state;
bool on = digitalRead(cfg.relayPin) == HIGH; // active LOW, so invert
digitalWrite(cfg.relayPin, on ? LOW : HIGH);
// Tell the cloud, so the app and Google agree with the wall switch.
SinricProSwitch &device = SinricPro[entry.first];
device.sendPowerStateEvent(on);
}
}
}
void setupWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) { delay(250); Serial.print("."); }
Serial.println();
Serial.print("connected, IP = ");
Serial.println(WiFi.localIP());
}
void setupSinricPro() {
for (auto &entry : devices) {
SinricProSwitch &device = SinricPro[entry.first];
device.onPowerState(onPowerState);
}
SinricPro.begin(APP_KEY, APP_SECRET);
}
void setup() {
Serial.begin(BAUD_RATE);
setupPins();
setupWiFi();
setupSinricPro();
}
void loop() {
SinricPro.handle(); // keeps the websocket alive - must run often
handleFlipSwitches();
}