Even more control, now with MQTT and GoVee
parent
10d516b574
commit
b83878afff
@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
myCommand=""
|
||||||
|
debug=""
|
||||||
|
PODip="192.168.0.72"
|
||||||
|
PODport="32000"
|
||||||
|
PPBAkey=""
|
||||||
|
PPBAname=""
|
||||||
|
myDevice="all"
|
||||||
|
|
||||||
|
do_help() {
|
||||||
|
cat << HELP_EOF
|
||||||
|
Usage: $0 <command> [OPTIONS]
|
||||||
|
command is one of:
|
||||||
|
--mqtt) Send HA MQTT discovery information to broker
|
||||||
|
--stats) Send MQTT stats to broker
|
||||||
|
--init) Not sure
|
||||||
|
--start) Starts the PPBA driver
|
||||||
|
--power [on|off] <DEV>) Powers on a device (see DEV, below)
|
||||||
|
--ip) IP of the PPBA API server
|
||||||
|
--port) Port of the PPBA API server
|
||||||
|
-d|--debug) Print debug information
|
||||||
|
-h|--help) print this help information
|
||||||
|
|
||||||
|
DEV is a device name:
|
||||||
|
all | quad | var | dew1 | dew2 | autodew
|
||||||
|
HELP_EOF
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help) do_help; shift 1;;
|
||||||
|
--mqtt*) myCommand="mqtt"; shift 1;;
|
||||||
|
--stat*) myCommand="stats"; shift 1;;
|
||||||
|
--init*) myCommand="init"; shift 1;;
|
||||||
|
--start) myCommand="start"; shift 1;;
|
||||||
|
--power) myCommand="power${2:-on}"; myDevice="$3"; shift 3;;
|
||||||
|
--poweron) myCommand="poweron"; myDevice="$2"; shift 2;;
|
||||||
|
--poweroff) myCommand="poweroff"; myDevice="$2"; shift 2;;
|
||||||
|
--ip) PODip="$2"; shift 2;;
|
||||||
|
--port) PODport="$2"; shift 2;;
|
||||||
|
-d|--debug) debug="true"; shift 1;;
|
||||||
|
*) shift 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
do_debug() {
|
||||||
|
[ -n "$debug" ] && echo "DEBUG: $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
do_api() {
|
||||||
|
ep="${1#/}"
|
||||||
|
method="${2:-GET}"
|
||||||
|
notes="${3}"
|
||||||
|
url="http://${PODip}:${PODport}/${ep}"
|
||||||
|
do_debug "curl -s -X ${method} \"$url\""
|
||||||
|
[ -n "$notes" ] && echo "$notes" >&2
|
||||||
|
curl -s -X ${method} "$url"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_getKey() {
|
||||||
|
# Get POD PPBA unique key and name
|
||||||
|
PPBAkey=$(do_api "/Server/DeviceManager/Connected" | jq -r '.data[] | select (.name =="PPBAdvance") | .uniqueKey')
|
||||||
|
PPBAname=$(do_api "/Server/DeviceManager/Device/${PPBAkey}/ProfileName" | jq -r '.data')
|
||||||
|
do_debug "PPBAkey=$PPBAkey, PPBAname=$PPBAname"
|
||||||
|
if [ -z "$PPBAkey" -o -z "$PPBAname" ]; then
|
||||||
|
# The PPBA is not powered on or cannot be found
|
||||||
|
do_debug "The PPBA is not powered on or cannot be found"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
do_init() {
|
||||||
|
do_getKey
|
||||||
|
do_api "/Server/Start" PUT "Starting server..." | jq '.status'
|
||||||
|
sleep 2
|
||||||
|
}
|
||||||
|
|
||||||
|
do_start() {
|
||||||
|
do_getKey
|
||||||
|
do_api "/Driver/PPBAdvance/Start?DriverUniqueKey=${PPBAkey}" OPTIONS "Starting PPBAdvance driver..." | jq '.status'
|
||||||
|
do_api "/Driver/PPBAdvance/Power/Hub/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning off Quad Power..." | jq '.status'
|
||||||
|
do_api "/Driver/PPBAdvance/Power/Variable/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning off Variable Power..." | jq '.status'
|
||||||
|
do_api "/Driver/PPBAdvance/Dew/1/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning off Dew Heater 1..." | jq '.status'
|
||||||
|
do_api "/Driver/PPBAdvance/Dew/2/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning off Dew Heater 2..." | jq '.status'
|
||||||
|
do_api "/Driver/PPBAdvance/Dew/Auto/Off?DriverUniqueKey=${PPBAkey}" POST "Turning off Auto Dew..." | jq '.status'
|
||||||
|
}
|
||||||
|
|
||||||
|
do_poweroff() {
|
||||||
|
do_getKey
|
||||||
|
case "$myDevice" in
|
||||||
|
quad*|all) do_api "/Driver/PPBAdvance/Power/Hub/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning Off Quad Power..." | jq '.status';;&
|
||||||
|
var*|all) do_api "/Driver/PPBAdvance/Power/Variable/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning Off Variable Power..." | jq '.status';;&
|
||||||
|
dew1|all) do_api "/Driver/PPBAdvance/Dew/1/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning Off Dew Heater 1..." | jq '.status';;&
|
||||||
|
dew2|all) do_api "/Driver/PPBAdvance/Dew/2/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning Off Dew Heater 2..." | jq '.status';;&
|
||||||
|
autodew|all) do_api "/Driver/PPBAdvance/Dew/Auto/Off?DriverUniqueKey=${PPBAkey}" POST "Turning Off Auto Dew..." | jq '.status';;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
do_poweron() {
|
||||||
|
do_getKey
|
||||||
|
case "$myDevice" in
|
||||||
|
quad*|all) do_api "/Driver/PPBAdvance/Power/Hub/On?DriverUniqueKey=${PPBAkey}" PUT "Turning on Quad Power..." | jq '.status';;&
|
||||||
|
var*|all) do_api "/Driver/PPBAdvance/Power/Variable/On?DriverUniqueKey=${PPBAkey}" PUT "Turning on Variable Power..." | jq '.status';;&
|
||||||
|
dew1|all) do_api "/Driver/PPBAdvance/Dew/1/On?DriverUniqueKey=${PPBAkey}" PUT "Turning on Dew Heater 1..." | jq '.status';;&
|
||||||
|
dew2|all) do_api "/Driver/PPBAdvance/Dew/2/On?DriverUniqueKey=${PPBAkey}" PUT "Turning on Dew Heater 2..." | jq '.status';;&
|
||||||
|
autodew|all) do_api "/Driver/PPBAdvance/Dew/Auto/On?DriverUniqueKey=${PPBAkey}" POST "Turning on Auto Dew..." | jq '.status';;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mqtt_pub() {
|
||||||
|
topic="$1"
|
||||||
|
message="$2"
|
||||||
|
mosquitto_pub -h ssdnode-1.bitnetix.com -t "PPBA/status/$topic" -m "$message"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_stats() {
|
||||||
|
do_getKey
|
||||||
|
stats=$(do_api "/Driver/PPBAdvance/Report?DriverUniqueKey=${PPBAkey}")
|
||||||
|
for stat in "voltage" "current" "quadCurrent" "power" "temperature" "humidity" "dewPoint" "isOverCurrent" "averageAmps" "ampsPerHour" "wattPerHour" "upTime"; do
|
||||||
|
val=$(echo "$stats" | jq -r ".data.message.$stat")
|
||||||
|
mqtt_pub "$stat" "$val"
|
||||||
|
done
|
||||||
|
for stat in "powerHubStatus" "powerVariablePortStatus" "ppbA_DualUSB2Status"; do
|
||||||
|
val=$(echo "$stats" | jq -r ".data.message.$stat.state")
|
||||||
|
mqtt_pub "$stat" "$val"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
###
|
||||||
|
|
||||||
|
do_mqtt() {
|
||||||
|
do_getKey
|
||||||
|
JSON_DATA=$(do_api "/Driver/PPBAdvance/Report?DriverUniqueKey=${PPBAkey}")
|
||||||
|
MQTT_HOST="ssdnode-1.bitnetix.com" # <-- change this!
|
||||||
|
MQTT_USER="" # optional
|
||||||
|
MQTT_PASS="" # optional
|
||||||
|
DEVICE_ID="ppbadv_gen2"
|
||||||
|
DISCOVERY_PREFIX="homeassistant"
|
||||||
|
STATE_TOPIC="$DEVICE_ID/state"
|
||||||
|
STATE_TOPIC="PPBA/status"
|
||||||
|
POLL_INTERVAL=30 # seconds
|
||||||
|
|
||||||
|
publish_discovery() {
|
||||||
|
echo "Publishing Home Assistant MQTT discovery configs..."
|
||||||
|
# Helper to publish a single discovery config message
|
||||||
|
publish_sensor() {
|
||||||
|
local ENTITY="$1"
|
||||||
|
local NAME="$2"
|
||||||
|
local UNIT="$3"
|
||||||
|
local DEVICE_CLASS="$4"
|
||||||
|
local VALUE_TEMPLATE="{{ value_json.$ENTITY }}"
|
||||||
|
local TOPIC="$DISCOVERY_PREFIX/sensor/$DEVICE_ID/${ENTITY}/config"
|
||||||
|
local PAYLOAD="{ \
|
||||||
|
\"name\": \"$NAME\", \
|
||||||
|
\"state_topic\": \"$STATE_TOPIC/${ENTITY}\", \
|
||||||
|
\"unique_id\": \"${DEVICE_ID}_${ENTITY}\", \
|
||||||
|
\"device\": { \
|
||||||
|
\"identifiers\": [\"$DEVICE_ID\"], \
|
||||||
|
\"manufacturer\": \"Pegasus Astro\", \
|
||||||
|
\"model\": \"Pocket Powerbox Advance Gen2\", \
|
||||||
|
\"name\": \"PPB Advance Gen2\" \
|
||||||
|
}"
|
||||||
|
|
||||||
|
# Optional fields
|
||||||
|
if [ -n "$UNIT" ]; then
|
||||||
|
PAYLOAD="$PAYLOAD, \"unit_of_measurement\": \"$UNIT\""
|
||||||
|
fi
|
||||||
|
if [ -n "$DEVICE_CLASS" ]; then
|
||||||
|
PAYLOAD="$PAYLOAD, \"device_class\": \"$DEVICE_CLASS\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
PAYLOAD="$PAYLOAD }"
|
||||||
|
mosquitto_pub -h "$MQTT_HOST" -u "$MQTT_USER" -P "$MQTT_PASS" \
|
||||||
|
-t "$TOPIC" -m "$PAYLOAD" -r
|
||||||
|
}
|
||||||
|
# Sensors
|
||||||
|
publish_sensor "voltage" "PPB Voltage" "V" "voltage"
|
||||||
|
publish_sensor "current" "PPB Current" "A" "current"
|
||||||
|
publish_sensor "temperature" "PPB Temperature" "°C" "temperature"
|
||||||
|
publish_sensor "humidity" "PPB Humidity" "%" "humidity"
|
||||||
|
publish_sensor "dewPoint" "PPB Dew Point" "°C" ""
|
||||||
|
publish_sensor "averageAmps" "PPB Average Amps" "A" ""
|
||||||
|
publish_sensor "ampsPerHour" "PPB Amps Per Hour" "Ah" ""
|
||||||
|
publish_sensor "wattPerHour" "PPB Watt Per Hour" "Wh" ""
|
||||||
|
publish_sensor "upTime" "PPB Uptime" "" ""
|
||||||
|
publish_sensor "powerHubStatus" "PPB Power Hub Status" "" ""
|
||||||
|
publish_sensor "powerVariablePortStatus" "PPB Variable Port" "" ""
|
||||||
|
publish_sensor "ppbA_DualUSB2Status" "PPB Dual USB2 Status" "" ""
|
||||||
|
}
|
||||||
|
publish_discovery
|
||||||
|
|
||||||
|
### echo "Starting PPBADV → MQTT polling loop..."
|
||||||
|
### while true; do
|
||||||
|
### if [ -n "$JSON_DATA" ]; then
|
||||||
|
### mosquitto_pub -h "$MQTT_HOST" -u "$MQTT_USER" -P "$MQTT_PASS" \
|
||||||
|
### -t "$STATE_TOPIC" -m "$JSON_DATA"
|
||||||
|
### else
|
||||||
|
### echo "WARNING: Failed to fetch API data from PPBADV Gen2"
|
||||||
|
### fi
|
||||||
|
###
|
||||||
|
### sleep "$POLL_INTERVAL"
|
||||||
|
### done
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
do_debug "Command = $myCommand"
|
||||||
|
case "$myCommand" in
|
||||||
|
mqtt) do_mqtt;;
|
||||||
|
stats) do_stats;;
|
||||||
|
init) do_init;;
|
||||||
|
start) do_start;;
|
||||||
|
poweroff) do_poweroff;;
|
||||||
|
poweron) do_poweron;;
|
||||||
|
*) do_help;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit
|
||||||
@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# govee.sh - small CLI wrapper for Govee "control device" API
|
||||||
|
# Requirements: curl, jq, uuidgen (or openssl)
|
||||||
|
# Usage examples at bottom.
|
||||||
|
|
||||||
|
API_BASE="${GOVEE_API_BASE:-https://openapi.api.govee.com}"
|
||||||
|
CONTROL_ENDPOINT="${API_BASE}/router/api/v1/device/control"
|
||||||
|
#API_KEY="${GOVEE_API_KEY:-}"
|
||||||
|
API_KEY="933194fa-d341-4362-b371-de290cb99a58"
|
||||||
|
|
||||||
|
mySKU="H6110"
|
||||||
|
myID="20:11:A4:C1:38:9B:CB:54"
|
||||||
|
|
||||||
|
if [[ -z "$API_KEY" ]]; then
|
||||||
|
echo "ERROR: Set GOVEE_API_KEY environment variable (export GOVEE_API_KEY=xxxx)"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
get_info() {
|
||||||
|
local entry="router/api/v1/user/devices"
|
||||||
|
json=$(curl -s -k ${API_BASE}/${entry} \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Govee-API-Key: ${API_KEY}")
|
||||||
|
sku=$(echo "$json" | jq -r ".data[].sku")
|
||||||
|
device=$(echo "$json" | jq -r ".data[].device")
|
||||||
|
}
|
||||||
|
|
||||||
|
# helpers
|
||||||
|
uuid() {
|
||||||
|
if command -v uuidgen >/dev/null 2>&1; then
|
||||||
|
uuidgen
|
||||||
|
else
|
||||||
|
# fallback
|
||||||
|
date +%s%N | sha1sum | cut -c1-32
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# convert hex/rrggbb or r,g,b to integer (0..16777215)
|
||||||
|
rgb_to_int() {
|
||||||
|
local in="$1"
|
||||||
|
if [[ "$in" =~ ^#?[0-9A-Fa-f]{6}$ ]]; then
|
||||||
|
# hex
|
||||||
|
in="${in#'#'}"
|
||||||
|
printf "%d\n" "$((0x$in))"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
# r,g,b
|
||||||
|
IFS=',' read -r r g b <<< "$in"
|
||||||
|
r=${r:-0}; g=${g:-0}; b=${b:-0}
|
||||||
|
# validate numeric
|
||||||
|
for val in "$r" "$g" "$b"; do
|
||||||
|
if ! [[ "$val" =~ ^[0-9]+$ ]] || (( val < 0 )) || (( val > 255 )); then
|
||||||
|
echo "ERR" # caller should handle
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo $(( (r << 16) + (g << 8) + b ))
|
||||||
|
}
|
||||||
|
|
||||||
|
# build and send the control request
|
||||||
|
_govee_control() {
|
||||||
|
local sku="$1"; shift
|
||||||
|
local device="$1"; shift
|
||||||
|
local type="$1"; shift
|
||||||
|
local instance="$1"; shift
|
||||||
|
local value="$1"
|
||||||
|
|
||||||
|
local reqid
|
||||||
|
reqid="$(uuid)"
|
||||||
|
|
||||||
|
local body
|
||||||
|
body=$(jq -n \
|
||||||
|
--arg rid "$reqid" \
|
||||||
|
--arg sku "$sku" \
|
||||||
|
--arg device "$device" \
|
||||||
|
--arg type "$type" \
|
||||||
|
--arg instance "$instance" \
|
||||||
|
--argjson value "$value" \
|
||||||
|
'{requestId:$rid, payload: { sku:$sku, device:$device, capability: { type:$type, instance:$instance, value:$value } } }')
|
||||||
|
|
||||||
|
curl -sS -X POST "$CONTROL_ENDPOINT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Govee-API-Key: $API_KEY" \
|
||||||
|
-d "$body"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: govee.sh <command> [options]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
power <sku> <device_id> <on|off> Turn device on or off
|
||||||
|
toggle <sku> <device_id> <instance> <0|1>
|
||||||
|
brightness <sku> <device_id> <0..100> Set brightness (range depends on device)
|
||||||
|
color_rgb <sku> <device_id> <hex|r,g,b> Set RGB color (hex like ff8800 or "255,100,0")
|
||||||
|
temp_k <sku> <device_id> <kelvin> Set color temperature (e.g. 2700)
|
||||||
|
mode <sku> <device_id> <instance> <value> Set a mode or scene by numeric value
|
||||||
|
raw Accepts raw JSON payload from stdin and sends it
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
./govee.sh power H605C "64:09:C5:32:37:36:2D:13" on
|
||||||
|
./govee.sh brightness H605C "64:09:C5:32:37:36:2D:13" 50
|
||||||
|
./govee.sh color_rgb H605C "64:09:C5:32:37:36:2D:13" ff0088
|
||||||
|
./govee.sh color_rgb H605C "64:09:C5:32:37:36:2D:13" 255,0,136
|
||||||
|
|
||||||
|
Note: You must know device SKU and device id (from /user/devices endpoint).
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
print_usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### cmd="$1"; shift
|
||||||
|
cmdline=""
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
--sku) mySKU="$2"; shift 2;;
|
||||||
|
--dev|--id) myID="$2"; shift 2;;
|
||||||
|
-c|--cmd) cmd="$2"; shift 2;;
|
||||||
|
*) break;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
|
po*)
|
||||||
|
# args: sku device on|off
|
||||||
|
sku="$mySKU"; device="$myID"; state="$1"
|
||||||
|
if [[ -z "$sku" || -z "$device" || -z "$state" ]]; then print_usage; exit 1; fi
|
||||||
|
if [[ "$state" == "on" ]]; then val=1; else val=0; fi
|
||||||
|
_govee_control "$sku" "$device" "devices.capabilities.on_off" "powerSwitch" "$val" | jq .
|
||||||
|
;;
|
||||||
|
|
||||||
|
to*)
|
||||||
|
# sku device instance 0|1
|
||||||
|
sku="$mySKU"; device="$myID"; instance="$1"; val="$2"
|
||||||
|
_govee_control "$sku" "$device" "devices.capabilities.toggle" "$instance" "$val" | jq .
|
||||||
|
;;
|
||||||
|
|
||||||
|
br*)
|
||||||
|
sku="$mySKU"; device="$myID"; val="$1"
|
||||||
|
if [[ -z "$val" ]]; then print_usage; exit 1; fi
|
||||||
|
_govee_control "$sku" "$device" "devices.capabilities.range" "brightness" "$val" | jq .
|
||||||
|
;;
|
||||||
|
|
||||||
|
co*)
|
||||||
|
sku="$mySKU"; device="$myID"; color="$1"
|
||||||
|
if [[ -z "$color" ]]; then print_usage; exit 1; fi
|
||||||
|
rgbint=$(rgb_to_int "$color") || { echo "Bad color"; exit 1; }
|
||||||
|
_govee_control "$sku" "$device" "devices.capabilities.color_setting" "colorRgb" "$rgbint" | jq .
|
||||||
|
;;
|
||||||
|
|
||||||
|
te*)
|
||||||
|
sku="$mySKU"; device="$myID"; kelvin="$1"
|
||||||
|
if [[ -z "$kelvin" ]]; then print_usage; exit 1; fi
|
||||||
|
_govee_control "$sku" "$device" "devices.capabilities.color_setting" "colorTemperatureK" "$kelvin" | jq .
|
||||||
|
;;
|
||||||
|
|
||||||
|
mo*)
|
||||||
|
sku="$mySKU"; device="$myID"; instance="$1"; val="$2"
|
||||||
|
_govee_control "$sku" "$device" "devices.capabilities.mode" "$instance" "$val" | jq .
|
||||||
|
;;
|
||||||
|
|
||||||
|
raw)
|
||||||
|
# read raw payload JSON from stdin (should include payload object)
|
||||||
|
body=$(cat -)
|
||||||
|
if [[ -z "$body" ]]; then echo "Provide JSON on stdin"; exit 1; fi
|
||||||
|
curl -sS -X POST "$CONTROL_ENDPOINT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Govee-API-Key: $API_KEY" \
|
||||||
|
-d "$body" | jq .
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
print_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
Loading…
Reference in New Issue