Hubitat allows development of custom drivers in Groovy.
Its pretty easy to learn and the Hubitat framework allows connection and management of devices not supported by Hubitat officially like all your home-brew Arduino devices.
You can also create custom applications to extend the capabilities of the devices.
e.g Calculate dew point
app:11532020-09-04 22:39:02.775 infoRho = 1.2405 kg/m3
app:11532020-09-04 22:39:02.771 infooutsidetemp = 11.1 °C
app:11532020-09-04 22:39:02.767 infoPressure = 100.8 Kpa
app:11532020-09-04 22:39:02.763 infoAbsolute Humidity = 125.0 kg/m3
app:11532020-09-04 22:39:02.759 infoDew point temperature = 3.5 °C
Still haven't finished the absolute Humidity calculation
Credit to Bruce Ravenel for the original app code I based this on
definition( name: "Calculate dew point", namespace: "hubitat", author: "Bruce Ravenel", description: "Calculate dew point", category: "Convenience", iconUrl: "", iconX2Url: "") preferences { page(name: "mainPage") } def mainPage() { dynamicPage(name: "mainPage", title: " ", install: true, uninstall: true) { section { input "thisName", "text", title: "Name this dew point calculator", submitOnChange: true if(thisName) app.updateLabel("$thisName") input "tempSensors", "capability.temperatureMeasurement", title: "Temperature Sensor", submitOnChange: true, required: true, multiple: false input "humidSensors", "capability.relativeHumidityMeasurement", title: "Humidity Sensor", submitOnChange: true, required: true, multiple: false input "pressureSensors", "capability.pressureMeasurement", title: "Outside Pressure Sensor", submitOnChange: true, required: true, multiple: false /* paragraph "Enter weight factors and offsets" tempSensors.each { input "weight$it.id", "decimal", title: "$it ($it.currentTemperature)", defaultValue: 1.0, submitOnChange: true, width: 3 input "offset$it.id", "decimal", title: "$it Offset", defaultValue: 0.0, submitOnChange: true, range: "*..*", width: 3 } input "useRun", "number", title: "Compute running average over this many sensor events:", defaultValue: 1, submitOnChange: true if(tempSensors) paragraph "Current sensor average is ${averageTemp()}°" if(useRun > 1) { initRun() if(tempSensors) paragraph "Current running average is ${averageTemp(useRun)}°" } */ } } } def installed() { initialize() } def updated() { unsubscribe() initialize() } def initialize() { Map map = [: ] def averageDev = getChildDevice("DewPointCalc_${app.id}") //ChildDeviceWrapper addChildDevice(String namespace, String typeName, String deviceNetworkId) (since 2.1.9) //ChildDeviceWrapper addChildDevice(String namespace, String typeName, String deviceNetworkId, Map properties) (since 2.1.9) //Map properties if(!averageDev) averageDev = addChildDevice("hubitat", "Virtual Temperature Sensor", "DewPointCalc_${app.id}", map, [label: thisName, name: thisName]) /* void subscribe(InstalledAppWrapper app, handlerMethod) void subscribe(Location location, handlerMethod) void subscribe(DeviceWrapper device, String handlerMethod, Map options = null) (Since 2.2.1) void subscribe(DeviceWrapperList devices, String handlerMethod, Map options = null) (Since 2.2.1) void subscribe(DeviceWrapper device, String attributeName, handlerMethod, Map options = null) void subscribe(DeviceWrapperList devices, String attributeName, handlerMethod, Map options = null) void subscribe(Location location, String attributeName, handlerMethod, Map options = null) */ subscribe(tempSensors, "temperature", handler) subscribe(humidSensors, "thumidity", handler) subscribe(pressureSensors, "pressure", handler) } def handler(evt) { def temp = 0 def totalh = 0 def totalp = 0 def outsidetemp = 0 def Rho = 0 tempSensors.each { /*def offset = settings["offset$it.id"] != null ? settings["offset$it.id"] : 0*/ /*total += (it.currentTemperature + offset) * (settings["weight$it.id"] != null ? settings["weight$it.id"] : 1)*/ temp += it.currentTemperature } humidSensors.each { totalh += it.currentHumidity } pressureSensors.each { totalp += it.currentPressure outsidetemp += it.currentTemperature } dewpoint = (temp - ((100 - totalh) / 5)) def averageDev = getChildDevice("DewPointCalc_${app.id}") averageDev.setTemperature( dewpoint.toDouble().round(1)) /* You can show that this matches theory by re-arranging the ideal gas law: PV = mRT for pressure P, volume V, mass of the gas m, gas constant R (0.167226 J/kg K) and temperature T to get ρ = P/RT in which ρ is density in units of m/V mass/volume (kg/m3). Temperature (°C) = 13.55 Air pressure (hPa) 1018 Dew point (°C) = -6.4 rho (kg/m3) = 1.2352 */ Rho = ((totalp.toDouble().round(1) * 10) / (0.167226 * ( 273 + temp) )) / 16.8896534974 //sendEvent(name: "Rho", value: "Rho $Rho kg/m3") ABShumid = Rho * totalp //averageDev.setHumidity( ABShumid.toDouble().round(1)) log.info "Dew point temperature = ${dewpoint.toDouble().round(1)} °C" log.info "Absolute Humidity = ${ABShumid.toDouble().round(1)} kg/m3" log.info "Pressure = ${ totalp.toDouble().round(1)} Kpa" log.info "outsidetemp = ${ outsidetemp.toDouble().round(1)} °C" log.info "Rho = ${ Rho.toDouble().round(4)} kg/m3" state.OutsideTemp = "Outside temp ${ outsidetemp.toDouble().round(1)} °C " state.Rho = "Rho ${Rho.toDouble().round(4)} kg/m3" state.Baro = "Pressure ${totalp.toDouble().round(1)} Kpa" state.ABShumid = "ABS Humidity ${ABShumid.toDouble().round(1)} kg/m3" state.DewPoint = "Dew point temperature = ${dewpoint.toDouble().round(1)} °C" //averageDev.setTemperature( tile) tile = "" tile += "" //sendEvent(getEventMap("DeviceTile", "$tile", false)) sendEvent(name: "DeviceTile", value: "$tile"); def now = new Date() sendEvent(name: "lastCheckin", value: now.format("YYYY-MM-dd-HH:mm:ss")) } private parseTest(val) { return [ name: 'illuminance', value: val, unit: '?', isStateChange: true, descriptionText: "Value is ${val} ?" ] }Dew point ${dewpoint.toDouble().round(1)} °C" tile += "Outside temp ${ outsidetemp.toDouble().round(1)} °C" tile += "Pressure ${totalp.toDouble().round(1)} Kpa" tile += "Rho ${Rho.toDouble().round(4)} kg/m3" tile += "ABS Humidity ${ABShumid.toDouble().round(1)} kg/m3" tile += "