UNPKG

@homebridge-plugins/homebridge-air

Version:

The AirNow plugin allows you to monitor the current AirQuality for your Zip Code from HomeKit and Siri.

419 lines (394 loc) 18.5 kB
<p class="text-center"> <img src="https://raw.githubusercontent.com/homebridge-plugins/homebridge-air/latest/branding/Homebridge_x_Air.svg" alt="homebridge-air logo" style="width: 40%" /> </p> <div id="pageIntro" style="display: none"> <p class="lead text-center">Thank you for installing <strong>homebridge-air</strong></p> <p class="lead text-center">Before continuing:</p> <ol> <li class="mb-3">Login / create an account at <a href="https://docs.airnowapi.org/faq" target="_blank" rel="noreferrer noopener">https://docs.airnowapi.org/faq</a>.</li> <li class="mb-3">Click <strong>Web Services</strong>.</li> <li>Copy Your API Key in the top right.</li> </ol> <div class="text-center"> <button type="button" class="btn btn-primary" id="introLink">Continue &rarr;</button> </div> </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" id="menuLocation">Location</button> <button type="button" class="btn btn-primary" id="menuSettings">Settings</button> <button type="button" class="btn btn-primary" id="menuDevices">Devices</button> <button type="button" class="btn btn-primary mr-0" id="menuHome">Support</button> </div> <div id="disabledBanner" class="alert alert-secondary mb-0 mt-3" role="alert" style="display: none"> Plugin is currently disabled <button id="disabledEnable" type="button" class="btn btn-link p-0 m-0 float-right">Enable</button> </div> <div id="pageLocation" style="display: none;"> <button type="button" class="btn btn-primary mb-3" id="getLocation">Use Current Location</button> <div class="mb-2"> <label for="latitudeField">Latitude:</label> <input type="text" id="latitudeField" name="latitude"> <button type="button" class="btn btn-outline-secondary btn-sm" id="copyLatitude">Copy</button> </div> <div class="mb-3"> <label for="longitudeField">Longitude:</label> <input type="text" id="longitudeField" name="longitude"> <button type="button" class="btn btn-outline-secondary btn-sm" id="copyLongitude">Copy</button> </div> <div class="text-center"> <button type="button" class="btn btn-success" id="saveToConfig">Save to Config</button> </div> </div> <div id="pageDevices" class="mt-4" style="display: none"> <div id="deviceInfo"> <form> <div class="form-group"> <select class="form-control" id="deviceSelect"></select> </div> </form> <table class="table w-100" 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">Serial Number</th> <td id="serialNumber"></td> </tr> <tr> <th scope="row">Model</th> <td id="model"></td> </tr> <tr> <th scope="row">Firmware Version</th> <td id="firmwareRevision"></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-air</strong></p> <p class="text-center">The links below will take you to our GitHub wiki</p> <h5>Setup</h5> <ul> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/wiki" target="_blank">Wiki Home</a> </li> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/wiki/Configuration" target="_blank">Configuration</a> </li> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/wiki/Beta-Version" target="_blank">Beta Version</a> </li> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/wiki/Node-Version" target="_blank">Node Version</a> </li> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/wiki/Uninstallation" target="_blank">Uninstallation</a> </li> </ul> <h5>Features</h5> <ul> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/wiki/Supported-Devices" target="_blank">Supported Devices</a> </li> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/wiki/Fan-Modes" target="_blank">Fan Modes</a> </li> </ul> <h5>Help/About</h5> <ul> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/issues/new/choose" target="_blank">Support Request</a> </li> <li> <a href="https://github.com/homebridge-plugins/homebridge-air/blob/latest/CHANGELOG.md" target="_blank">Changelog</a> </li> <li> <a href="https://github.com/sponsors/donavanbecker" target="_blank">About Me</a> </li> </ul> <h5>Disclaimer</h5> <ul> <li>I am in no way affiliated with Airnow 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(); const ensurePrimaryConfig = () => { if (!Array.isArray(currentConfig)) { return { name: 'Air' }; } if (!currentConfig[0]) { currentConfig[0] = { name: 'Air' }; } if (!Array.isArray(currentConfig[0].devices)) { currentConfig[0].devices = []; } return currentConfig[0]; }; const primaryConfig = ensurePrimaryConfig(); const bindClick = (id, handler) => { const element = document.getElementById(id); if (!element) { console.warn(`homebridge-air UI: missing element #${id}`); return; } element.addEventListener('click', handler); }; const showIntro = () => { const introLink = document.getElementById('introLink'); introLink.addEventListener('click', () => { homebridge.showSpinner(); document.getElementById('pageIntro').style.display = 'none'; document.getElementById('menuWrapper').style.display = 'inline-flex'; showLocation(); homebridge.hideSpinner(); }); document.getElementById('menuWrapper').style.display = 'none'; document.getElementById('pageIntro').style.display = 'block'; }; const showDevices = async () => { homebridge.showSpinner(); 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('menuLocation').classList.remove('btn-elegant'); document.getElementById('menuLocation').classList.add('btn-primary'); document.getElementById('menuSettings').classList.remove('btn-elegant'); document.getElementById('menuSettings').classList.add('btn-primary'); document.getElementById('pageLocation').style.display = 'none'; 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; }); } const deviceSelect = document.getElementById('deviceSelect'); deviceSelect.innerHTML = ''; cachedAccessories.forEach((a) => { const option = document.createElement('option'); option.text = a.displayName; option.value = a.UUID; deviceSelect.add(option); }); const showDeviceInfo = async (UUID) => { homebridge.showSpinner(); const thisAcc = cachedAccessories.find((x) => x.UUID === UUID); const context = thisAcc.context; document.getElementById('displayName').innerHTML = thisAcc.displayName; document.getElementById('serialNumber').innerHTML = context.serialNumber; document.getElementById('model').innerHTML = context.model; document.getElementById('firmwareRevision').innerHTML = context.firmwareRevision || 'N/A'; document.getElementById('deviceTable').style.display = 'inline-table'; homebridge.hideSpinner(); }; deviceSelect.addEventListener('change', (event) => showDeviceInfo(event.target.value)); if (cachedAccessories.length > 0) { showDeviceInfo(cachedAccessories[0].UUID); } else { const option = document.createElement('option'); option.text = 'No Devices'; deviceSelect.add(option); deviceSelect.disabled = true; } homebridge.hideSpinner(); }; const showSupport = () => { homebridge.showSpinner(); 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('menuLocation').classList.remove('btn-elegant'); document.getElementById('menuLocation').classList.add('btn-primary'); document.getElementById('menuSettings').classList.remove('btn-elegant'); document.getElementById('menuSettings').classList.add('btn-primary'); document.getElementById('pageLocation').style.display = 'none'; document.getElementById('pageSupport').style.display = 'block'; document.getElementById('pageDevices').style.display = 'none'; homebridge.hideSpinner(); }; const showLocation = () => { document.getElementById('getLocation').addEventListener('click', () => { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition((position) => { const latitude = position.coords.latitude; const longitude = position.coords.longitude; // Assign the latitude and longitude to the input fields document.getElementById('latitudeField').value = latitude; document.getElementById('longitudeField').value = longitude; homebridge.toast.success('Location fetched successfully.', 'Success'); }, (error) => { let errorMessage = 'Error getting location: '; switch (error.code) { case error.PERMISSION_DENIED: errorMessage += 'User denied the request for Geolocation.'; break; case error.POSITION_UNAVAILABLE: errorMessage += 'Location information is unavailable.'; break; case error.TIMEOUT: errorMessage += 'The request to get user location timed out.'; break; case error.UNKNOWN_ERROR: errorMessage += 'An unknown error occurred.'; break; } console.error(errorMessage, error); homebridge.toast.error(errorMessage, 'Error'); }); } else { homebridge.toast.error('Geolocation is not supported by this browser.', 'Error'); } }); document.getElementById('copyLatitude').addEventListener('click', () => { const latitude = document.getElementById('latitudeField').value; navigator.clipboard.writeText(latitude).then(() => { homebridge.toast.success('Latitude copied to clipboard.', 'Success'); }).catch((error) => { console.error('Error copying latitude:', error); homebridge.toast.error('Error copying latitude: ' + error.message, 'Error'); }); }); document.getElementById('copyLongitude').addEventListener('click', () => { const longitude = document.getElementById('longitudeField').value; navigator.clipboard.writeText(longitude).then(() => { homebridge.toast.success('Longitude copied to clipboard.', 'Success'); }).catch((error) => { console.error('Error copying longitude:', error); homebridge.toast.error('Error copying longitude: ' + error.message, 'Error'); }); }); document.getElementById('saveToConfig').addEventListener('click', async () => { const latitude = document.getElementById('latitudeField').value; const longitude = document.getElementById('longitudeField').value; if (!latitude || !longitude) { homebridge.toast.error('Please enter or fetch both latitude and longitude before saving.', 'Error'); return; } try { homebridge.showSpinner(); const config = await homebridge.getPluginConfig(); // Initialize devices array if it doesn't exist if (!config[0]) { config[0] = { name: 'Air', devices: [] }; } if (!Array.isArray(config[0].devices)) { config[0].devices = []; } // Check if a device with these exact coordinates already exists const existingDevice = config[0].devices.find(device => device.latitude === latitude && device.longitude === longitude ); if (existingDevice) { homebridge.toast.warning('A device with these coordinates already exists in the config.', 'Warning'); homebridge.hideSpinner(); return; } // Add a new device to the array config[0].devices.push({ provider: 'airnow', apiKey: '', latitude: latitude, longitude: longitude }); await homebridge.updatePluginConfig(config); await homebridge.savePluginConfig(); homebridge.toast.success('Location added to config successfully!', 'Success'); } catch (error) { console.error('Error saving to config:', error); homebridge.toast.error('Error saving to config: ' + error.message, 'Error'); } finally { homebridge.hideSpinner(); } }); homebridge.showSpinner(); homebridge.hideSchemaForm(); 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('menuLocation').classList.add('btn-elegant'); document.getElementById('menuLocation').classList.remove('btn-primary'); document.getElementById('menuSettings').classList.remove('btn-elegant'); document.getElementById('menuSettings').classList.add('btn-primary'); document.getElementById('pageLocation').style.display = 'block'; document.getElementById('pageSupport').style.display = 'none'; document.getElementById('pageDevices').style.display = 'none'; homebridge.hideSpinner(); }; document.getElementById('menuLocation').addEventListener('click', showLocation); const showSettings = () => { homebridge.showSpinner(); 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('menuLocation').classList.remove('btn-elegant'); document.getElementById('menuLocation').classList.add('btn-primary'); document.getElementById('menuSettings').classList.add('btn-elegant'); document.getElementById('menuSettings').classList.remove('btn-primary'); document.getElementById('pageLocation').style.display = 'none'; document.getElementById('pageSupport').style.display = 'none'; document.getElementById('pageDevices').style.display = 'none'; homebridge.showSchemaForm(); homebridge.hideSpinner(); }; const showDisabledBanner = () => { document.getElementById('disabledBanner').style.display = 'block'; }; const enablePlugin = async () => { homebridge.showSpinner(); document.getElementById('disabledBanner').style.display = 'none'; currentConfig[0].disablePlugin = false; await homebridge.updatePluginConfig(currentConfig); await homebridge.savePluginConfig(); homebridge.hideSpinner(); }; bindClick('menuHome', () => showSupport()); bindClick('menuDevices', () => showDevices()); bindClick('menuLocation', () => showLocation()); bindClick('menuSettings', () => showSettings()); bindClick('disabledEnable', () => enablePlugin()); if (currentConfig.length) { document.getElementById('menuWrapper').style.display = 'inline-flex'; // Show Location tab if no devices configured, otherwise show Settings tab if (!primaryConfig.devices || primaryConfig.devices.length === 0) { showLocation(); } else { showSettings(); } if (primaryConfig.disablePlugin) { showDisabledBanner(); } } else { currentConfig.push({ name: 'Air', devices: [] }); await homebridge.updatePluginConfig(currentConfig); showIntro(); } } catch (err) { homebridge.toast.error(err.message, 'Error'); } finally { homebridge.hideSpinner(); } })(); </script>