
Home Assistant and GoveeLife Humidifier
I was given a GoveeLife Humidifier as a present and have used the Govee app to turn it on and off and set the lighting scheme. Now that I am comfortable with it I'd like to automate the process using my Home Assistant setup which is running on a Raspberry Pi 4 and already has an Ikea Air Quality sensor connected that provides humidity data.
I initially thought that it would be straight forward and I could just setup a simple Perl script to get kicked off by Home Assistant. Unfortunately, it turns out that the precanned Home Assistant Operating System (HAOS) doesn't allow for anything like installing pretty much anything of your own on the device! (sigh).
Using the Advanced SSH setup you can get at the guts of it and roll your own but it is just fighting against the current the whole time so best to jump through the hoops and accept the approach they have mandated.
Home Assistant is originally written in Python, so it should be ultra simple to setup a Python script to do the same thing as the Perl script. Great, so install the python scripts addon and we're golden.
No, turns out that you can run a Python script but not one that does anything useful as it's not allowed to import anything! (sigh).
OK, so how am I supposed to do anything. Well turns out that there are additional Python addons.
I installed AppDaemon and had a look at the documentation. Wow, now I have to write a class with various hoop jumping to get anything done (sigh).
Fortunately, I don't have to write it anymore I can get Claude to do the work and convert my Perl script into something suitable for AppDaemon.
Firstly, you need to find out where you can place the scripts to be used by AppDaemon. It's in /addon_configs/a0d7b954_appdaemon/apps. I expect the a0d7b954 will be different in your installation.
Setup some yaml to supply the information required by the script. So the contents of /addon_configs/a0d7b954_appdaemon/apps/apps.yaml
---
govee_humidifier:
module: govee_humidifier_controller
class: GoveeHumidifierController
api_key: "<YOUR API KEY>" # Your Govee API key
device_id: "<YOUR MAC ADDRESS>" # Your device ID (MAC Address)
sku: "H7140" # Your device SKU
input_boolean_entity: input_boolean.humidifier_switch
Now the script that ticks all the AppDaemon boxes ...
import appdaemon.plugins.hass.hassapi as hass
import json
import uuid
import http.client
from urllib.parse import urlparse
class GoveeHumidifierController(hass.Hass):
"""
AppDaemon app to control a Govee humidifier device via API.
This class allows Home Assistant to turn a Govee humidifier on or off through
the Govee API. It can be triggered by events, services, or automations in Home Assistant.
"""
def initialize(self):
"""
Initialize the AppDaemon app.
Set up the configuration and register callbacks.
"""
self.log("Initializing Govee Humidifier Controller")
# Get configuration from apps.yaml
self.api_url = self.args.get('api_url', 'https://openapi.api.govee.com/router/api/v1/device/control')
self.api_key = self.args.get('api_key', 'default api key')
self.sku = self.args.get('sku', 'H7140')
self.device_id = self.args.get('device_id', 'default mac address')
# Parse the URL
parsed_url = urlparse(self.api_url)
self.host = parsed_url.netloc
self.path = parsed_url.path
# Listen for events to control the humidifier
self.listen_event(self.event_callback, "GOVEE_HUMIDIFIER_COMMAND")
# Listen for any state changes from an input_boolean entity (optional)
if 'input_boolean_entity' in self.args:
self.listen_state(self.state_change_callback, self.args['input_boolean_entity'])
self.log("Govee Humidifier Controller initialized")
def event_callback(self, event, data, kwargs):
"""
Event callback to control the humidifier.
Triggered by 'GOVEE_HUMIDIFIER_COMMAND' events.
Args:
event: Event name (string)
data: Event data (dict)
kwargs: Additional parameters
"""
self.log(f"Received event: {event} with data: {data}")
# Extract command from the nested structure
command = None
# Try to extract command from different possible data structures
if data and isinstance(data, dict):
# Direct command in data
if 'command' in data:
command = data['command']
# Command in event_data
elif 'event_data' in data and isinstance(data['event_data'], dict):
command = data['event_data'].get('command')
# Command in nested data structure
elif 'data' in data and isinstance(data['data'], dict):
if 'event_data' in data['data'] and isinstance(data['data']['event_data'], dict):
command = data['data']['event_data'].get('command')
# Convert to lowercase if command exists
if command:
command = str(command).lower()
self.log(f"Extracted command: {command}")
if command in ['on', 'off']:
self.control_humidifier(command)
else:
self.log(f"Invalid command: {command}. Use 'on' or 'off'", level="ERROR")
def state_change_callback(self, entity, attribute, old, new, kwargs):
"""
Callback for state changes of a linked input_boolean.
Args:
entity: Entity that changed
attribute: Attribute that changed
old: Old state
new: New state
kwargs: Additional parameters
"""
self.log(f"State change for {entity}: {old} -> {new}")
if new == "on":
self.control_humidifier("on")
elif new == "off":
self.control_humidifier("off")
def control_humidifier(self, command):
"""
Send control command to the Govee humidifier.
Args:
command: 'on' or 'off'
Returns:
True if successful, False otherwise
"""
self.log(f"Sending '{command}' command to Govee humidifier")
# Set the value based on the command
value = 1 if command == 'on' else 0
# Generate a UUID for requestId
request_id = str(uuid.uuid4())
# Create the JSON payload
payload = {
"requestId": request_id,
"payload": {
"sku": self.sku,
"device": self.device_id,
"capability": {
"type": "devices.capabilities.on_off",
"instance": "powerSwitch",
"value": value
}
}
}
# Set up the headers
headers = {
'Content-Type': 'application/json',
'Govee-API-Key': self.api_key
}
# Send the request using http.client
try:
# Create a secure connection
conn = http.client.HTTPSConnection(self.host)
# Convert payload to JSON string
payload_json = json.dumps(payload)
# Send the request
conn.request("POST", self.path, body=payload_json, headers=headers)
# Get the response
response = conn.getresponse()
# Read the response data
response_data = response.read().decode('utf-8')
# Check the response
if 200 <= response.status < 300:
self.log(f"Request successful! Device turned {command}.")
self.log(f"Response code: {response.status}")
self.log(f"Response body: {response_data}")
result = True
else:
self.log("Request failed!", level="ERROR")
self.log(f"Error code: {response.status}", level="ERROR")
self.log(f"Error message: {response.reason}", level="ERROR")
self.log(f"Response body: {response_data}", level="ERROR")
result = False
# Close the connection
conn.close()
return result
except Exception as e:
self.log(f"Error: {e}", level="ERROR")
return False
All the script does is listen for an event (GOVEE_HUMIDIFIER_COMMAND) and then it sends the API request to Govee.
To get it to manage the humidity setup an automation that is dependent on the numeric value of the Air quality Sensor Humidity. This is the value collected from the Ikea Air Quality sensor. I setup one to turn on the humifier when the value is below 52 and one to turn it off when the humidity is over 55.