/** * MIT License * Copyright 2022 Jonathan Bradshaw (jb@nrgup.net) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ metadata { definition( name: 'ESPHome Everything Presence One', namespace: 'esphome', author: 'Jonathan Bradshaw', singleThreaded: true, importUrl: 'https://raw.githubusercontent.com/bradsjm/hubitat-drivers/main/ESPHome/ESPHome-EverythingPresenceOnce.groovy') { capability 'Configuration' capability 'IlluminanceMeasurement' capability 'MotionSensor' capability 'Sensor' capability 'Refresh' capability 'RelativeHumidityMeasurement' capability 'TemperatureMeasurement' capability 'Initialize' attribute 'distance', 'number' attribute 'mmwave', 'enum', [ 'active', 'not active' ] attribute 'pir', 'enum', [ 'active', 'not active' ] // attribute populated by ESPHome API Library automatically attribute 'networkStatus', 'enum', [ 'connecting', 'online', 'offline' ] command 'restart' } preferences { input name: 'ipAddress', // required setting for API library type: 'text', title: 'Device IP Address', required: true input name: 'password', // optional setting for API library type: 'text', title: 'Device Password', description: '(if required)', required: false input name: 'mmwaveSensitivity', type: 'number', title: 'mmWave Sensitivity', description: 'How much motion is required to trigger the sensor (0 is most, 9 is least sensitive)', required: false, range: '0..9' input name: 'mmwaveDistance', type: 'number', title: 'mmWave Distance', description: 'Set to the distance of the room you have the EP1 in (0 to 800cm)', required: false, range: '0..800' input name: 'mmwaveOnLatency (Advanced)', type: 'number', title: 'mmWave On Latency', description: 'How long motion must be detected for before triggering (0 to 60 seconds)', required: false, range: '0..60' input name: 'mmwaveOffLatency (Advanced)', type: 'number', title: 'mmWave Off Latency', description: 'How long after motion is no longer detected to wait (1 to 60 seconds)', required: false, range: '1..60' input name: 'statusLedEnable', type: 'bool', title: 'Enable Status LED', required: false, defaultValue: true input name: 'mmwaveLedEnable', type: 'bool', title: 'Enable mmWave LED', required: false, defaultValue: true input name: 'txtEnable', type: 'bool', title: 'Enable descriptionText logging', defaultValue: true, description: \ 'Enables command logging.' input name: 'logEnable', type: 'bool', title: 'Enable debug logging', defaultValue: false, description: \ 'Turns on debug logging for 30 minutes.' } } void configure() { if (isNullOrEmpty(settings['mmwaveOnLatency'])) { final Long key = state.entities['mmwave_on_latency'] as Long if (key) { final BigDecimal value = settings['mmwaveOnLatency'] as BigDecimal if (value < 0 || value > 60) { log.warn "mmWave On Latency must be between 0 and 60 seconds" } else { log.info "Setting mmWave On Latency to ${value}" espHomeNumberCommand([key: key, state: value]) } } } if (isNullOrEmpty(settings['mmwaveOffLatency'])) { final Long key = state.entities['mmwave_off_latency'] as Long if (key) { final BigDecimal value = settings['mmwaveOffLatency'] as BigDecimal if (value < 1 || value > 60) { log.warn "mmWave Off Latency must be between 1 and 60 seconds" } else { log.info "Setting mmWave Off Latency to ${value}" espHomeNumberCommand([key: key, state: value]) } } } if (isNullOrEmpty(settings['mmwaveDistance'])) { final Long key = state.entities['mmwave_distance'] as Long if (key) { final BigDecimal value = settings['mmwaveDistance'] as Integer if (value < 0 || value > 800) { log.warn "mmWave Distance must be between 0 and 800 cm" } else { log.info "Setting mmWave Distance to ${value}" espHomeNumberCommand([key: key, state: value]) } } } if (isNullOrEmpty(settings['mmwaveSensitivity'])) { final Long key = state.entities['mmwave_sensitivity'] as Long if (key) { final BigDecimal value = settings['mmwaveSensitivity'] as Integer if (value < 0 || value > 9) { log.warn "mmWave Sensitivity must be between 0 and 9" } else { log.info "Setting mmWave Sensitivity to ${value}" espHomeNumberCommand([key: key, state: value]) } } } } void initialize() { // API library command to open socket to device, it will automatically reconnect if needed openSocket() if (logEnable) { runIn(1800, 'logsOff') } } void installed() { log.info "${device} driver installed" } void logsOff() { espHomeSubscribeLogs(LOG_LEVEL_INFO, false) // disable device logging device.updateSetting('logEnable', false) log.info "${device} debug logging disabled" } void refresh() { log.info "${device} refresh" state.clear() state.entities = [:] state.requireRefresh = true espHomeDeviceInfoRequest() } void restart() { log.info "${device} restart" espHomeButtonCommand([ key: state.entities['restart'] as Long ]) } void updated() { log.info "${device} driver configuration updated" initialize() runIn(5, 'configure') } void uninstalled() { closeSocket('driver uninstalled') // make sure the socket is closed when uninstalling log.info "${device} driver uninstalled" } // the parse method is invoked by the API library when messages are received void parse(final Map message) { if (logEnable) { log.debug "ESPHome received: ${message}" } switch (message.type) { case 'device': // Device information break case 'entity': parseKeys(message) break case 'state': parseState(message) } } void parseKeys(final Map message) { if (logEnable) { log.debug "ESPHome entity: ${message}" } if (state.entities == null) { state.entities = [:] } final long key = message.key as long switch (message.objectId) { case ~/_illuminance$/: // Illuminance Sensor state.entities['illuminance'] = key break case ~/_mmwave$/: // Millimeter wave radar sensor state.entities['mmwave'] = key break case ~/_occupancy$/: // Occupancy Sensor state.entities['occupancy'] = key break case ~/_pir$/: // Passive Infrared Sensor state.entities['pir'] = key break case ~/_temperature$/: // Temperature Sensor state.entities['temperature'] = key break case ~/_humidity$/: // Humidity Sensor state.entities['humidity'] = key break case 'esp32_status_led': // ESP32 Status LED state.entities['status_led'] = key break case 'everything-presence-one_safe_mode': // Safe mode switch state.entities['safe_mode'] = key break case 'mmwave_distance': // Millimeter wave radar sensor distance state.entities['mmwave_distance'] = key break case 'mmwave_led': // Millimeter wave radar sensor LED state.entities['mmwave_led'] = key break case 'mmwave_off_latency': // Millimeter wave radar sensor off latency state.entities['mmwave_off_latency'] = key break case 'mmwave_on_latency': // Millimeter wave radar sensor on latency state.entities['mmwave_on_latency'] = key break case 'mmwave_sensitivity': // Millimeter wave radar sensor sensitivity state.entities['mmwave_sensitivity'] = key break case 'mmwave_sensor': // Millimeter wave radar sensor switch state.entities['mmwave_switch'] = key break case 'restart_everything-presence-one': // Restart everything switch state.entities['restart'] = key break case 'uart_presence_output': case 'uart_target_output': // ignore break default: log.warn "ESPHome entity not supported: ${message}" break } } void parseState(final Map message) { if (logEnable) { log.debug "ESPHome state: ${message}" } if (message.key == null) { return } final long key = message.key as long switch (key) { case state.entities['illuminance']: // Illuminance Sensor if (message.hasState) { updateAttribute('illuminance', message.state as Integer, 'lx') } break case state.entities['mmwave']: // Millimeter wave radar sensor if (message.hasState) { updateAttribute('mmwave', message.state ? 'active' : 'inactive') } break case state.entities['occupancy']: // Combined Millimeter wave radar and PIR if (message.hasState) { updateAttribute('motion', message.state ? 'active' : 'inactive') } break case state.entities['pir']: // PIR sensor if (message.hasState) { updateAttribute('pir', message.state ? 'active' : 'inactive') } break case state.entities['temperature']: // Temperature Sensor if (message.hasState) { final String value = convertTemperatureIfNeeded(message.state as BigDecimal, 'C', 0) updateAttribute('temperature', value) } break case state.entities['humidity']: // Humidity Sensor if (message.hasState) { updateAttribute('humidity', message.state as Integer, '%rh') } break case state.entities['esp32_status_led']: // ESP32 Status LED if (message.hasState) { log.info "ESP32 Status LED: ${message.state}" device.updateSetting('statusLedEnable', message.state) } break case state.entities['mmwave_distance']: // Millimeter wave radar sensor distance if (message.hasState) { log.info "Millimeter wave radar sensor distance: ${message.state}" device.updateSetting('mmwaveDistance', message.state) } break case state.entities['mmwave_led']: // Millimeter wave radar sensor LED if (message.hasState) { log.info "Millimeter wave radar sensor LED: ${message.state}" device.updateSetting('mmwaveLedEnable', message.state) } break case state.entities['mmwave_off_latency']: // Millimeter wave radar sensor off latency if (message.hasState) { log.info "Millimeter wave radar sensor off latency: ${message.state}" device.updateSetting('mmwaveOffLatency', message.state) } break case state.entities['mmwave_on_latency']: // Millimeter wave radar sensor on latency if (message.hasState) { log.info "Millimeter wave radar sensor on latency: ${message.state}" device.updateSetting('mmwaveOnLatency', message.state) } break case state.entities['mmwave_sensitivity']: // Millimeter wave radar sensor sensitivity if (message.hasState) { log.info "Millimeter wave radar sensor sensitivity: ${message.state}" device.updateSetting('mmwaveSensitivity', message.state) } break } } /** * Check if the specified value is null or empty * @param value value to check * @return true if the value is null or empty, false otherwise */ private static boolean isNullOrEmpty(final Object value) { return value == null || (value as String).trim().isEmpty() } /** * Update the specified device attribute with the specified value and log if changed * @param attribute name of the attribute * @param value value of the attribute * @param unit unit of the attribute * @param type type of the attribute */ private void updateAttribute(final String attribute, final Object value, final String unit = null, final String type = null) { final String descriptionText = "${attribute} was set to ${value}${unit ?: ''}" if (device.currentValue(attribute) != value && settings.txtEnable) { log.info descriptionText } sendEvent(name: attribute, value: value, unit: unit, type: type, descriptionText: descriptionText) } // Put this line at the end of the driver to include the ESPHome API library helper #include esphome.espHomeApiHelper