In this project, we'll explore how to control Govee smart lights using an ESP32 development board and a motion sensor. By integrating with the Govee API, we can programmatically turn lights on and off based on motion detection, enabling seamless home automation.
Govee smart lights offer a convenient way to add ambiance and automation to your home lighting. With their ability to change colors, brightness, and even sync with music or entertainment systems, these smart lights provide endless possibilities for customization. However, sometimes you may want to automate your lights based on motion detection, turning them on when someone enters a room and off when it's vacant, saving energy and adding an extra layer of convenience.
Hardware Requirements
To get started, you'll need the following hardware components:
- Govee AM312 Motion Sensor: https://amzn.to/3HgShIX
- ESP32 Development Board: https://amzn.to/3tOSyzI
- LED (for visual feedback): https://amzn.to/3RW0gAf
- Resistors (330 Ω): https://amzn.to/4aUrXSp
Software Requirements
In addition to the hardware, you'll need the following software components:
- Arduino IDE
- ArduinoJson library
- WiFi library
Setting up the Hardware
Connect the motion sensor, LED, and ESP32 board as follows:
- Connect the OUT pin of the AM312 motion sensor to GPIO pin 2 of the ESP32 board.
- Connect the positive leg of the LED to GPIO pin 13 of the ESP32 board, and the negative leg to a ground pin through a 330Ω resistor.
- Connect the ESP32 board to your computer via a USB cable.
With the hardware set up, you're ready to dive into the code and integrate with the Govee API.
Govee API Integration
The Govee API allows developers to interact with Govee smart devices programmatically. To use the API, you'll need to obtain an API key from the Govee developer portal.
In the code, we'll be using the following API endpoints:
/devices
: Retrieve information about Govee devices associated with your account./control
: Send commands to control Govee devices (turn on/off, change color, etc.)./state
: Fetch the current state (power, online status) of a Govee device.
How to get Govee API credentials
Download the Govee Home App
Navigate to the My Profile page
- Open the Govee Home App and click on the 👤 icon to go to the My Profile page.
Access Settings
- On the My Profile page, click on the ⚙️ icon in the top-right corner to access the Settings.
Apply for API Key
- In the Settings, you should see an option to "Apply for API Key". Click on it.
Fill in the Application Form
- Enter your "Name" and "Reason for application" in the respective fields.
- Possible reasons can include home automation, third-party integration, API Days Tutorial (education & research), or any other relevant use case.
Accept the Govee Developer API Terms of Service
- Read the Govee Developer API Terms of Service carefully, then click the checkbox to accept the terms.
Submit the Application
- After filling in all the required information and accepting the terms, click the "Submit" button to submit your API key application. You should receive an email with your credentials instantly.
Copy the API Key
- Once you receive your API key, copy it and store it securely. You'll need to use this key in your code to authenticate requests to the Govee API.
You can also refer to the official guide provided by Govee: https://developer.govee.com/reference/apply-you-govee-api-key
Code Walkthrough
Setup
The setup()
function connects the ESP32 board to your WiFi network, initializes the pins for the motion sensor and LED, and fetches information about your Govee devices using the API.
void setup() {
Serial.begin(115200);
pinMode(motionPin, INPUT);
pinMode(led1Pin, OUTPUT);
pinMode(led2Pin, OUTPUT);
// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
delay(1000);
getDevices(); // Fetch Govee device information
delay(5000);
}
Loop
The loop()
function continuously checks the state of the motion sensor. If motion is detected, it turns on an LED and starts a timer to keep track of the motion duration. After a certain amount of time (e.g., 4 seconds), it calls the printDevices()
function to turn on the associated Govee devices. If no motion is detected, it turns off the LED and calls printDevices()
to turn off the Govee devices after a certain idle duration.
void loop() {
int motionState = digitalRead(motionPin);
if (motionState == HIGH) {
Serial.println("Motion detected!");
time_in_motion++;
time_idle = 0;
if (time_in_motion == 4) {
printDevices("on"); // Turn on Govee devices
}
turn1on2off(); // Visual feedback with LED
} else {
Serial.println("No motion!");
time_in_motion = 0;
time_idle++;
if (time_idle == 4) {
printDevices("off"); // Turn off Govee devices
}
turn2on1off(); // Visual feedback with LED
}
delay(1000); // 1s delay
}
Functions
The code includes several functions to interact with the Govee API and control devices.
getDevices()
This function sends a GET request to the /devices
endpoint of the Govee API to fetch information about your Govee devices.
void getDevices() {
HTTPClient http;
http.begin(url);
http.addHeader("Govee-API-Key", apiKey);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
deserializeJson(doc, payload);
getDevicesSuccess = true; // Successful API call
// Print device information
JsonArray devices = doc["data"]["devices"];
for (JsonObject device : devices) {
const char *deviceName = device["deviceName"];
// ...
Serial.println("Device Name: " + String(deviceName));
// ...
}
}
http.end();
}
printDevices()
This function prints information about Govee devices and controls them based on their current state and the desired action (on
or off
).
void printDevices(const String &action) {
if (getDevicesSuccess) {
JsonArray devices = doc["data"]["devices"];
for (JsonObject device : devices) {
const char *deviceName = device["deviceName"];
const char *device_address = device["device"];
const char *model = device["model"];
DeviceState state = getDeviceState(device_address, model);
Serial.println("Device Name: " + String(deviceName));
Serial.println("Power State: " + state.powerState);
Serial.println("Online: " + String(state.online ? "Yes" : "No"));
if (state.online) {
if (action != state.powerState) {
controlDevice(device_address, model, "turn", action);
Serial.println("[ACTION] Turn " + String(action));
} else {
Serial.println("do nothing...");
}
}
Serial.println("------");
}
} else {
Serial.println("API call was not successful.");
}
}
controlDevice()
This function sends a PUT request to the /control
endpoint of the Govee API to turn a device on or off.
void controlDevice(const String &device, const String &model, const String &cmdName, const String &cmdValue) {
HTTPClient http;
http.begin(control_endpoint);
http.addHeader("Content-Type", "application/json");
http.addHeader("Govee-API-Key", apiKey);
StaticJsonDocument<200> jsonBody;
jsonBody["device"] = device;
jsonBody["model"] = model;
JsonObject cmdObject = jsonBody.createNestedObject("cmd");
cmdObject["name"] = cmdName;
cmdObject["value"] = cmdValue;
String jsonString;
serializeJson(jsonBody, jsonString);
int httpCode = http.PUT(jsonString);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
} else {
Serial.printf("[HTTP] PUT request failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
getDeviceState()
This function sends a GET request to the /state
endpoint of the Govee API to fetch the current state (power, online status) of a Govee device.
DeviceState getDeviceState(const String &device, const String &model) {
HTTPClient http;
String apiUrl = state_endpoint + "?device=" + device + "&model=" + model;
http.begin(apiUrl);
http.addHeader("Govee-API-Key", apiKey);
DeviceState result;
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
DynamicJsonDocument doc(1024);
deserializeJson(doc, payload);
JsonObject data = doc["data"];
result.powerState = data["properties"][1]["powerState"].as<String>();
result.online = data["properties"][0]["online"];
} else {
Serial.printf("[HTTP] GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
return result;
}
Testing and Demonstration
To test the project, follow these steps:
- Open the Arduino IDE and upload the code to your ESP32 board.
- Verify that the LED turns on and off correctly based on motion detection.
- Simulate motion detection by waving your hand in front of the motion sensor. After a few seconds, your Govee devices should turn on.
- Wait for the idle duration (e.g., 4 seconds), and your Govee devices should turn off.
You can monitor the serial output in the Arduino IDE to see the device information and the actions being performed.
Conclusion
In this project, we've explored how to integrate an ESP32 board, a motion sensor, and the Govee API to create a home automation system that controls Govee smart lights based on motion detection. By leveraging the Govee API, we can programmatically turn devices on and off, enabling seamless automation and energy-saving capabilities.
This project serves as a starting point for further exploration of home automation and the Govee ecosystem. You can extend the functionality by adding more sensors, integrating with other smart home platforms, or exploring additional features offered by the Govee API.
Additional Resources
- Govee API Documentation: https://govee-public.s3.amazonaws.com/developer-docs/GoveeDeveloperAPIReference.pdf
- ArduinoJson Library: https://arduinojson.org/
- WiFi Library for ESP32: https://github.com/espressif/arduino-esp32/tree/master/libraries/WiFi
- Govee Developer Portal: https://developer.govee.com/
Full Source code
Happy automating!
Full source code
#include <WiFi.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
HTTPClient http;
// Parse JSON response
DynamicJsonDocument doc(1024);
const int motionPin = 2; // GPIO pin connected the OUT pin of the AM312 sensor to
const int led1Pin = 13; // Replace with the pin of the first LED (green)
const int led2Pin = 15; // Replace with the pin of the second LED (red)
int time_idle = 0;
int time_in_motion = 0;
const char *ssid = "wifi-ssid";
const char *password = "wifi-password";
const char *apiKey = "govee-api-key"; // get api key from govee
const char *url = "https://developer-api.govee.com/v1/devices";
const char *control_endpoint = "https://developer-api.govee.com/v1/devices/control";
const String &state_endpoint = "https://developer-api.govee.com/v1/devices/state";
// devices that we want to turn on and off
const char *devicesToControl[] = { "[1] Smart LED Bulb", "[2] Smart LED Bulb", "[3] Smart LED Bulb", "[4] Smart LED Bulb" };
bool getDevicesSuccess = false;
struct DeviceState {
String powerState;
bool online;
};
void setup() {
Serial.begin(115200);
pinMode(motionPin, INPUT);
pinMode(led1Pin, OUTPUT);
pinMode(led2Pin, OUTPUT);
delay(10);
// Connect to Wi-Fi
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
delay(1000);
getDevices();
delay(5000);
}
// Function to turn on LED1 and turn off LED2
void turn1on2off() {
digitalWrite(led1Pin, HIGH);
digitalWrite(led2Pin, LOW);
}
// Function to turn on LED2 and turn off LED1
void turn2on1off() {
digitalWrite(led1Pin, LOW);
digitalWrite(led2Pin, HIGH);
}
void loop() {
int motionState = digitalRead(motionPin);
if (motionState == HIGH) {
Serial.print("Motion detected! - ");
Serial.print("Motion lasted ");
Serial.print(time_in_motion);
Serial.println(" s.");
time_in_motion++;
time_idle = 0;
if(time_in_motion == 4) {
printDevices("on");
}
turn1on2off();
} else {
Serial.print("No motion! - ");
Serial.print("Idleness lasted ");
Serial.print(time_idle);
Serial.println(" s.");
time_in_motion = 0;
time_idle++;
if(time_idle == 4) {
printDevices("off");
}
turn2on1off();
}
delay(1000); // 1s delay
}
void getDevices() {
HTTPClient http;
Serial.print("Sending GET request to Govee API...");
// Your Govee API URL
http.begin(url);
// Set headers
http.addHeader("Govee-API-Key", apiKey);
// Send the request
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
deserializeJson(doc, payload);
getDevicesSuccess = true; // a successful API call
int numDevicesToControl = sizeof(devicesToControl) / sizeof(devicesToControl[0]);
// Extract and print device information
JsonArray devices = doc["data"]["devices"];
for (JsonObject device : devices) {
const char *deviceName = device["deviceName"];
const char *device_address = device["device"];
for (int i = 0; i < numDevicesToControl; i++) {
if (strcmp(deviceName, devicesToControl[i]) == 0) {
const char *model = device["model"];
bool controllable = device["controllable"];
bool retrievable = device["retrievable"];
Serial.println("Device Name: " + String(deviceName) + " " + String(device_address));
Serial.println("Model: " + String(model));
Serial.println("Controllable: " + String(controllable ? "Yes" : "No"));
Serial.println("Retrievable: " + String(retrievable ? "Yes" : "No"));
Serial.println("------");
break;
}
}
}
}
} else {
Serial.printf("[HTTP] GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
}
// Free resources
http.end();
}
void printDevices(const String &action) {
// Check if the API call was successful before printing devices
if (getDevicesSuccess) {
// Extract and print device information
JsonArray devices = doc["data"]["devices"];
for (JsonObject device : devices) {
const char *deviceName = device["deviceName"];
const char *device_address = device["device"];
const char *model = device["model"];
bool controllable = device["controllable"];
bool retrievable = device["retrievable"];
DeviceState state = getDeviceState(device_address, model);
Serial.println("Device Name: " + String(deviceName));
Serial.println("Model: " + String(model));
Serial.println("Power State: " + state.powerState);
Serial.println("Online: " + String(state.online ? "Yes" : "No"));
if(state.online) {
// then we can either turn on or off
if(action != state.powerState) {
controlDevice(device_address, model, "turn", action);
Serial.println("[ACTION] Turn " + String(action));
} else {
Serial.println("do nothing...");
}
}
// Serial.println("Controllable: " + String(controllable ? "Yes" : "No"));
// Serial.println("Retrievable: " + String(retrievable ? "Yes" : "No"));
Serial.println("------");
}
} else {
Serial.println("API call was not successful. Check the previous logs for details.");
}
}
void controlDevice(const String &device, const String &model, const String &cmdName, const String &cmdValue) {
HTTPClient http;
Serial.print("[CONTROL DEVICE]");
http.begin(control_endpoint);
http.addHeader("Content-Type", "application/json");
http.addHeader("Govee-API-Key", apiKey);
// Create JSON request body
StaticJsonDocument<200> jsonBody;
jsonBody["device"] = device;
jsonBody["model"] = model;
JsonObject cmdObject = jsonBody.createNestedObject("cmd");
cmdObject["name"] = cmdName;
cmdObject["value"] = cmdValue;
// Serialize JSON to string
String jsonString;
serializeJson(jsonBody, jsonString);
// Send the request
int httpCode = http.PUT(jsonString);
if (httpCode > 0) {
// HTTP header has been sent and the response status code received
Serial.printf("[HTTP] PUT... code: %d\n", httpCode);
// File found at the server
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTP] PUT request failed, error: %s\n", http.errorToString(httpCode).c_str());
}
// Free resources
http.end();
}
DeviceState getDeviceState(const String &device, const String &model) {
HTTPClient http;
Serial.print("[GET STATE]");
String apiUrl = state_endpoint + "?device=" + device + "&model=" + model;
http.begin(apiUrl);
// Set headers
http.addHeader("Govee-API-Key", apiKey);
DeviceState result; // Create a struct to store the result
// Send the request
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
// Parse JSON response
DynamicJsonDocument doc(1024);
deserializeJson(doc, payload);
// Extract device state information
JsonObject data = doc["data"];
result.powerState = data["properties"][1]["powerState"].as<String>();
result.online = data["properties"][0]["online"];
}
} else {
Serial.printf("[HTTP] GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
}
// Free resources
http.end();
return result;
}