@homebridge-plugins/homebridge-govee
Version:
Homebridge plugin to integrate Govee devices into HomeKit.
523 lines (518 loc) • 21.7 kB
HTML
<style>
.dark-mode {
background-color: #242424;
color: lightgrey;
.form-control {
background-color: #333333 ;
border: none ;
color: #eeeeee ;
}
thead, tbody, tr {
border-style: hidden;
}
th {
color: #eeeeee ;
font-weight: 500 ;
}
}
select {
background-image:
linear-gradient(45deg, transparent 50%, gray 50%),
linear-gradient(135deg, gray 50%, transparent 50%),
linear-gradient(to right, #ccc, #ccc);
background-position:
calc(100% - 20px) calc(1em + 2px),
calc(100% - 15px) calc(1em + 2px),
calc(100% - 2.5em) 0.5em;
background-size:
5px 5px,
5px 5px,
1px 1.5em;
background-repeat: no-repeat;
}
</style>
<p class="text-center">
<img
src="https://user-images.githubusercontent.com/43026681/101324574-5e997d80-3862-11eb-81b0-932330f6e242.png"
alt="homebridge-govee logo"
style="width: 60%;"
/>
</p>
<div id="pageIntro" class="text-center" style="display: none;">
<p class="lead">Thank you for installing <strong>homebridge-govee</strong></p>
<p>
Enter your Govee credentials and/or your Govee OpenAPI key on the following page.
</p>
<p style="font-size: 0.85rem;">
Govee may email you with a code the first time the plugin tries to log you in (if you have provided your Govee credentials).
Please <a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/AWS-Control" target="_blank">read this process</a> before continuing if you plan to provide your credentials.
</p>
<p style="font-size: 0.85rem;">
The OpenAPI key is optional and can be used for OpenAPI discovery, state, and control where supported.
</p>
<button type="button" class="btn btn-primary" id="introContinue">Continue →</button>
</div>
<div
id="menuWrapper"
class="btn-group w-100 mb-0"
role="group"
aria-label="UI Menu"
style="display: none;"
>
<button type="button" class="btn btn-primary ml-0" id="menuSettings">Settings</button>
<button type="button" class="btn btn-primary" id="menuDevices">My Devices</button>
<button type="button" class="btn btn-primary mr-0" id="menuHome">Support</button>
</div>
<div id="pageDevices" class="mt-4" style="display: none;">
<div id="connectionOverview" style="display: none; margin-bottom: 1.5rem; padding: 1rem; border: 1px solid #dee2e6; border-radius: 0.375rem;">
<h6 style="margin-bottom: 0.75rem; font-weight: 600;">Connection Overview</h6>
<div id="connSummary" style="font-size: 0.85rem;"></div>
</div>
<div id="deviceInfo">
<form>
<div class="form-group">
<select class="form-control" id="deviceSelect"></select>
</div>
</form>
<table class="table w-100 mt-3" id="deviceTable" style="display: none;">
<thead>
<tr class="table-active">
<th scope="col" style="width: 40%;">Device Name</th>
<th scope="col" style="width: 60%;" id="displayName"></th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Govee Device ID</th>
<td id="deviceId"></td>
</tr>
<tr>
<th scope="row">Govee Model</th>
<td id="model"></td>
</tr>
<tr>
<th scope="row">AWS Supported</th>
<td id="aws_supported"></td>
</tr>
<tr id="row_aws_enabled" style="display: none;">
<th scope="row">AWS Enabled</th>
<td id="aws_enabled"></td>
</tr>
<tr>
<th scope="row">Bluetooth Supported</th>
<td id="ble_supported"></td>
</tr>
<tr id="row_ble_enabled" style="display: none;">
<th scope="row">Bluetooth Enabled</th>
<td id="ble_enabled"></td>
</tr>
<tr id="row_ble_address" style="display: none;">
<th scope="row">Bluetooth Address</th>
<td id="ble_address"></td>
</tr>
<tr id="row_ble_name" style="display: none;">
<th scope="row">Bluetooth Name</th>
<td id="ble_name"></td>
</tr>
<tr>
<th scope="row">OpenAPI Supported</th>
<td id="openapi_supported"></td>
</tr>
<tr id="row_openapi_enabled" style="display: none;">
<th scope="row">OpenAPI Enabled</th>
<td id="openapi_enabled"></td>
</tr>
<tr>
<th scope="row">LAN Supported</th>
<td id="lan_supported"></td>
</tr>
<tr id="row_lan_enabled" style="display: none;">
<th scope="row">LAN Enabled</th>
<td id="lan_enabled"></td>
</tr>
<tr id="row_ip_address" style="display: none;">
<th scope="row">IP Address</th>
<td id="ip_address"></td>
</tr>
<tr id="row_supported_cmds" style="display: none;">
<th scope="row">Supported Commands</th>
<td id="supported_cmds"></td>
</tr>
<tr>
<th scope="row">Firmware Version</th>
<td id="firmware"></td>
</tr>
<tr>
<th scope="row">Hardware Version</th>
<td id="hardware"></td>
</tr>
<tr id="row_kelvin_range" style="display: none;">
<th scope="row">Kelvin Range</th>
<td id="kelvin_range"></td>
</tr>
<tr id="row_temp_range" style="display: none;">
<th scope="row">Temperature Range</th>
<td id="temp_range"></td>
</tr>
<tr id="row_humi_range" style="display: none;">
<th scope="row">Humidity Range</th>
<td id="humi_range"></td>
</tr>
<tr>
<td colspan="2" style="text-align: center" id="image"></td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="pageSupport" class="mt-4" style="display: none;">
<p class="text-center lead">Thank you for using <strong>homebridge-govee</strong></p>
<p class="text-center">The links below will take you to our GitHub wiki</p>
<h4>Setup</h4>
<ul>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/Installation" target="_blank"
>Installation</a
>
</li>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/Configuration" target="_blank"
>Configuration</a
>
</li>
<li>
<a href="https://github.com/homebridge/homebridge/wiki/How-to-Install-Alternate-Plugin-Versions" target="_blank"
>Beta Version</a
>
</li>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/Node-Version" target="_blank"
>Node Version</a
>
</li>
</ul>
<h4>Features</h4>
<ul>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/Supported-Devices" target="_blank"
>Supported Devices</a
>
</li>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/Connection-Methods" target="_blank"
>Connection Methods</a
>
</li>
<ul>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/LAN-Control" target="_blank"
>LAN Control</a
>
</li>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/API-Control" target="_blank"
>API Control</a
>
</li>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/AWS-Control" target="_blank"
>AWS Control</a
>
</li>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/Bluetooth-Control" target="_blank"
>BLE Control</a
>
</li>
</ul>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/Scene%2C-Music%2C-DIY-Modes" target="_blank"
>Scene, Music, DIY Modes</a
>
</li>
</ul>
<h4>Help/About</h4>
<ul>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/wiki/Common-Errors" target="_blank"
>Common Errors</a
>
</li>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/issues/new/choose" target="_blank"
>Support Request</a
>
</li>
<li>
<a href="https://github.com/homebridge-plugins/homebridge-govee/blob/latest/CHANGELOG.md" target="_blank"
>Changelog</a
>
</li>
<li><a href="https://github.com/sponsors/bwp91" target="_blank">About Me</a></li>
</ul>
<h4>Credits</h4>
<ul>
<li>To all users who have shared their devices to enable functionality.</li>
<li>
To the creator/owner of the
<a href="https://www.npmjs.com/package/govee-led-client" target="_blank">govee-led-client</a>
library which made the bluetooth connection possible.
</li>
<li>
To the creator/owner of the
<a href="https://github.com/towlerj/govee_api" target="_blank">govee_api</a>
library which made the AWS connection possible.
</li>
<li>
To <a href="https://github.com/alboiuvlad29" target="_blank">@alboiuvlad29</a> who made the LAN connection possible.
</li>
<li>
To <a href="https://github.com/JeremyDunn" target="_blank">@JeremyDunn</a> for his code from
<a href="https://github.com/JeremyDunn/homebridge-govee-water-detectors" target="_blank"
>homebridge-govee-water-detectors</a
>
for leak sensor support.
</li>
<li>
To the creator/owner of the
<a href="https://www.npmjs.com/package/govee-bt-client" target="_blank"
>govee-bt-client</a
>
library which made the BLE connection to sensors possible.
</li>
<li>
To the creator of the awesome plugin header logo:
<a href="https://www.instagram.com/keryan.me" target="_blank">Keryan Belahcene</a>.
</li>
<li>
To the creators/contributors of
<a href="https://homebridge.io" target="_blank">Homebridge</a> who make this plugin possible.
</li>
</ul>
<h4>Disclaimer</h4>
<ul>
<li>
I am in no way affiliated with Govee and this plugin is a personal project that I maintain in
my free time.
</li>
<li>Use this plugin entirely at your own risk - please see licence for more information.</li>
</ul>
</div>
<script>
;(async () => {
try {
const currentConfig = await homebridge.getPluginConfig()
showIntro = () => {
homebridge.disableSaveButton?.()
const introContinue = document.getElementById('introContinue')
introContinue.addEventListener('click', () => {
homebridge.showSpinner()
document.getElementById('pageIntro').style.display = 'none'
document.getElementById('menuWrapper').style.display = 'inline-flex'
showSettings()
homebridge.hideSpinner()
})
document.getElementById('pageIntro').style.display = 'block'
}
showDevices = async () => {
homebridge.showSpinner()
homebridge.disableSaveButton?.()
homebridge.hideSchemaForm()
document.getElementById('menuHome').classList.remove('btn-elegant')
document.getElementById('menuHome').classList.add('btn-primary')
document.getElementById('menuDevices').classList.add('btn-elegant')
document.getElementById('menuDevices').classList.remove('btn-primary')
document.getElementById('menuSettings').classList.remove('btn-elegant')
document.getElementById('menuSettings').classList.add('btn-primary')
document.getElementById('pageSupport').style.display = 'none'
document.getElementById('pageDevices').style.display = 'block'
const cachedAccessories =
typeof homebridge.getCachedAccessories === 'function'
? await homebridge.getCachedAccessories()
: await homebridge.request('/getCachedAccessories')
if (cachedAccessories.length > 0) {
cachedAccessories.sort((a, b) => {
return a.displayName.toLowerCase() > b.displayName.toLowerCase()
? 1
: b.displayName.toLowerCase() > a.displayName.toLowerCase()
? -1
: 0
})
}
// Build connection overview
if (cachedAccessories.length > 0) {
const counts = { lan: 0, aws: 0, openapi: 0, ble: 0 }
cachedAccessories.forEach(a => {
if (a.context.useLanControl) counts.lan++
if (a.context.useAwsControl) counts.aws++
if (a.context.useOpenApiControl) counts.openapi++
if (a.context.useBleControl) counts.ble++
})
const active = []
if (counts.lan > 0) active.push(`LAN (${counts.lan})`)
if (counts.aws > 0) active.push(`AWS (${counts.aws})`)
if (counts.openapi > 0) active.push(`OpenAPI (${counts.openapi})`)
if (counts.ble > 0) active.push(`BLE (${counts.ble})`)
const lines = []
lines.push(`<strong>Active connections:</strong> ${active.length > 0 ? active.join(', ') : 'None'}`)
lines.push(`<strong>Send priority:</strong> LAN → AWS → OpenAPI → BLE`)
lines.push(`<strong>Total devices:</strong> ${cachedAccessories.length}`)
document.getElementById('connSummary').innerHTML = lines.join('<br>')
document.getElementById('connectionOverview').style.display = 'block'
}
const deviceSelect = document.getElementById('deviceSelect')
deviceSelect.innerHTML = ''
cachedAccessories.forEach(a => {
const option = document.createElement('option')
option.text = a.displayName
option.value = a.context.gvDeviceId
deviceSelect.add(option)
})
showDeviceInfo = async hbDeviceId => {
homebridge.showSpinner()
const thisAcc = cachedAccessories.find(x => x.context.gvDeviceId === hbDeviceId)
const context = thisAcc.context
document.getElementById('displayName').innerHTML = thisAcc.displayName
document.getElementById('deviceId').innerHTML = context.gvDeviceId || 'N/A'
document.getElementById('model').innerHTML = context.gvModel || 'N/A'
if (context.hasAwsControl) {
document.getElementById('aws_supported').innerHTML = 'Yes'
document.getElementById('aws_enabled').innerHTML = context.useAwsControl ? 'Yes' : 'No'
document.getElementById('row_aws_enabled').style.display = 'table-row'
} else {
document.getElementById('aws_supported').innerHTML = 'No'
document.getElementById('row_aws_enabled').style.display = 'none'
}
if (context.hasBleControl) {
document.getElementById('ble_supported').innerHTML = 'Yes'
document.getElementById('ble_enabled').innerHTML = context.useBleControl ? 'Yes' : 'No'
document.getElementById('ble_address').innerHTML = context.bleAddress
document.getElementById('ble_name').innerHTML = context.bleName
document.getElementById('row_ble_enabled').style.display = 'table-row'
document.getElementById('row_ble_address').style.display = 'table-row'
document.getElementById('row_ble_name').style.display = 'table-row'
} else {
document.getElementById('ble_supported').innerHTML = 'No'
document.getElementById('row_ble_enabled').style.display = 'none'
document.getElementById('row_ble_address').style.display = 'none'
document.getElementById('row_ble_name').style.display = 'none'
}
if (context.hasOpenApiControl) {
document.getElementById('openapi_supported').innerHTML = 'Yes'
document.getElementById('openapi_enabled').innerHTML = context.useOpenApiControl ? 'Yes' : 'No'
document.getElementById('row_openapi_enabled').style.display = 'table-row'
} else {
document.getElementById('openapi_supported').innerHTML = 'No'
document.getElementById('row_openapi_enabled').style.display = 'none'
}
if (context.hasLanControl) {
document.getElementById('lan_supported').innerHTML = 'Yes'
document.getElementById('lan_enabled').innerHTML = context.useLanControl ? 'Yes' : 'No'
document.getElementById('row_lan_enabled').style.display = 'table-row'
if (context.useLanControl && context.ipAddress) {
document.getElementById('ip_address').innerHTML = context.ipAddress
document.getElementById('row_ip_address').style.display = 'table-row'
} else {
document.getElementById('row_ip_address').style.display = 'none'
}
} else {
document.getElementById('lan_supported').innerHTML = 'No'
document.getElementById('row_lan_enabled').style.display = 'none'
document.getElementById('row_ip_address').style.display = 'none'
}
if (context.supportedCmds && Array.isArray(context.supportedCmds)) {
document.getElementById('supported_cmds').innerHTML = context.supportedCmds.join(', ')
document.getElementById('row_supported_cmds').style.display = 'table-row'
} else {
document.getElementById('row_supported_cmds').style.display = 'none'
}
document.getElementById('firmware').innerHTML = context.firmware
? context.firmware
: 'N/A'
document.getElementById('hardware').innerHTML = context.hardware
? context.hardware
: 'N/A'
if (context?.supportedCmdsOpts?.colorTem?.range?.min) {
document.getElementById('kelvin_range').innerHTML =
context.supportedCmdsOpts.colorTem.range.min + 'K - ' + context.supportedCmdsOpts.colorTem.range.max + 'K'
document.getElementById('row_kelvin_range').style.display = 'table-row'
} else {
document.getElementById('row_kelvin_range').style.display = 'none'
}
if (context.maxTemp) {
document.getElementById('temp_range').innerHTML =
context.minTemp + '°C - ' + context.maxTemp + '°C'
document.getElementById('row_temp_range').style.display = 'table-row'
} else {
document.getElementById('row_temp_range').style.display = 'none'
}
if (context.maxHumi) {
document.getElementById('humi_range').innerHTML =
context.minHumi + '% - ' + context.maxHumi + '%'
document.getElementById('row_humi_range').style.display = 'table-row'
} else {
document.getElementById('row_humi_range').style.display = 'none'
}
document.getElementById('image').innerHTML = context.image
? '<img src="' + context.image + '" style="width: 150px;">'
: ''
document.getElementById('deviceTable').style.display = 'inline-table'
homebridge.hideSpinner()
}
deviceSelect.addEventListener('change', event => showDeviceInfo(event.target.value))
if (cachedAccessories.length > 0) {
showDeviceInfo(cachedAccessories[0].context.gvDeviceId)
} else {
const option = document.createElement('option')
option.text = 'No Devices'
deviceSelect.add(option)
deviceSelect.disabled = true
}
homebridge.hideSpinner()
}
showSupport = () => {
homebridge.showSpinner()
homebridge.disableSaveButton?.()
homebridge.hideSchemaForm()
document.getElementById('menuHome').classList.add('btn-elegant')
document.getElementById('menuHome').classList.remove('btn-primary')
document.getElementById('menuDevices').classList.remove('btn-elegant')
document.getElementById('menuDevices').classList.add('btn-primary')
document.getElementById('menuSettings').classList.remove('btn-elegant')
document.getElementById('menuSettings').classList.add('btn-primary')
document.getElementById('pageSupport').style.display = 'block'
document.getElementById('pageDevices').style.display = 'none'
homebridge.hideSpinner()
}
showSettings = () => {
homebridge.showSpinner()
homebridge.enableSaveButton?.()
document.getElementById('menuHome').classList.remove('btn-elegant')
document.getElementById('menuHome').classList.add('btn-primary')
document.getElementById('menuDevices').classList.remove('btn-elegant')
document.getElementById('menuDevices').classList.add('btn-primary')
document.getElementById('menuSettings').classList.add('btn-elegant')
document.getElementById('menuSettings').classList.remove('btn-primary')
document.getElementById('pageSupport').style.display = 'none'
document.getElementById('pageDevices').style.display = 'none'
homebridge.showSchemaForm()
homebridge.hideSpinner()
}
menuHome.addEventListener('click', () => showSupport())
menuDevices.addEventListener('click', () => showDevices())
menuSettings.addEventListener('click', () => showSettings())
if (currentConfig.length) {
document.getElementById('menuWrapper').style.display = 'inline-flex'
showSettings()
} else {
currentConfig.push({ name: 'Govee' })
await homebridge.updatePluginConfig(currentConfig)
showIntro()
}
} catch (err) {
homebridge.toast.error(err.message, 'Error')
} finally {
homebridge.hideSpinner()
}
})()
</script>