diff --git a/drivers/athom-human-presence-sensor.groovy b/drivers/athom-human-presence-sensor.groovy
new file mode 100644
index 0000000..e681390
--- /dev/null
+++ b/drivers/athom-human-presence-sensor.groovy
@@ -0,0 +1,278 @@
+/**
+ * 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: 'Athom Human Presence Sensor',
+ namespace: 'esphome',
+ author: 'Joshua Glemza',
+ singleThreaded: true,
+ importUrl: 'https://github.com/jglemza/hubitat/raw/main/drivers/athom-human-presence-sensor.groovy') {
+
+ capability 'Configuration'
+ capability 'IlluminanceMeasurement'
+ capability 'MotionSensor'
+ capability 'Sensor'
+ capability 'Refresh'
+ 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: '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() {
+
+}
+
+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 'light_sensor':
+ // Illuminance Sensor
+ state.entities['illuminance'] = key
+ break
+ case 'occupancy':
+ // Occupancy Sensor
+ state.entities['occupancy'] = key
+ break
+ case 'pir_sensor':
+ // Passive Infrared Sensor
+ state.entities['pir'] = key
+ break
+ case 'status_led':
+ // ESP32 Status LED
+ state.entities['status_led'] = key
+ break
+ case 'farthest_detection':
+ // Millimeter wave radar sensor distance
+ state.entities['mmwave_distance'] = key
+ break
+ case 'fading_time':
+ // Millimeter wave radar sensor off latency
+ state.entities['mmwave_off_latency'] = key
+ break
+ case 'detection_delay':
+ // Millimeter wave radar sensor on latency
+ state.entities['mmwave_on_latency'] = key
+ break
+ case 'trigger_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 '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['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_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