Integration to Delta VoiceIQ

@Sebastien @tomw

Driver works very well but "usageLastDay" stil always displays 0
Can you please change it to display previous day usage or simply remove this reporting.
Seeing permanent 0 in this field is absolutely useless.

image

Also since this driver is basically done can you please, add it to HPM?

TomW’s update on “usageLastDay” has been incorporated. I plan to add it to HPM at some point soon. Juggling priorities. :slight_smile:

2 Likes

@tomw Not sure if anything can be done, but the usage for last week is now showing zero. Here’s my usage for last week when I pull it up on the web portal.

Let me know if you need to see HE log information. I am on my phone right now because I’m recovering from surgery and it’s a bit hard to navigate the hub from iOS. I don’t know how you do it Sebastien! :grin:

1 Like

That’s likely because the last week is one day which would also be 0 if it hadn’t been updated to be the previous day.

2 Likes

I added an if condition that will get the previous “everything” if the value that the driver gets back is ==0. Seems to work for me so I checked it in. There will no longer be any 0 values unless both previous values are 0, which can be good or bad…

This might be an issue for the year if used on the first day though, since there won’t be a previous year… Not sure how I could test this…

1 Like

Working great! So impressed by this quick work you and @tomw have put forward. So much fun to be a Hubitat owner! :pray:

2 Likes

@tomw @Sebastien @SmartHomePrimer

STATUS UPDATE
Today is August 4. Token should be expired on August 2. So far so good, integration is still
working very well with suppose to be expired token. I am very happy with this driver.

2 Likes

My token expired at some point while I was away (Sept 8-15). Refreshed and all is working again.
I get daily use from this for our coffee pot, so if I have to refresh the token every three months it's not such a big deal.

1 Like

According to @tomw investigation my token should expire around August 3.
So far it is still active. But "yes", adding token auto refresh capability to the driver will
remove one extra headache.

If someone knows how to do this and wants to add it to the code (or show me how to), it would be great. Unfortunately, this is way, way above my current capabilities…

When you created this nice driver with @tomw assistance he said if necessary adding
token auto refresh capability to the driver will not be a problem. I hope, Tom is still
around and can help with this.
Meanwhile my integration is still active despite taken expiration date was Augus 3.

1 Like

Mine also seems to be going strong. :slight_smile:

1 Like

Not sure if anyone's actually put together how to get a Gen2 to work, but it wasn't too bad. Here are my steps. Serious thank you to @Sebastien for putting the driver together!

  1. In a Chromium browser (I used Edge), navigate to https://device.deltafaucet.com/
  2. Click Login with Google (this probably works for any of them but not sure) and follow the prompts.
  3. Open the browser dev console (CTRL+SHIFT+I) and click on Network (approx. top center of the console)
  4. On the Delta page, click on the blue button labelled Water Usage - This Week
  5. On the Name column (in the lower pane of the Dev console), click on the result that starts with UsageReport?deviceId=
  6. Click on Headers in the lower dev pane
  7. Under Request Headers you will find the required deviceId (it'll be the ':path' request header string between 'deviceId=' and '&interval...'), and Authorization token (the large string below 'Bearer')
  8. In Hubitat, you should already have loaded Sebastien's custom driver and created a virtual 'Delta VoiceIQ Faucet' device
  9. Open 'Drivers Code' section and find the driver, and edit it. Where the URL contains 'device.legacy.deltafaucet.com' replace that with 'device.deltafaucet.com' (just remove the '.legacy' portion). I only found two references, at lines 471 and 479. Click Save.
  10. In your virtual device, under Save Token, paste in the authorization token. No bracketing or markup should be needed; just the long string). Click Save Token.
  11. Just below this (still in your virtual device), paste the deviceId in the Delta Device ID field. Click 'Save Preferences'.

On/Off and dispense x mL now work for me, and nearly real-time usage, maybe?

I haven't poked at using imperial units instead of mL but I thought I saw someone comment on that earlier in the thread.

1 Like

BTW, my token has not expired since September of 2023. Maybe soon? :person_shrugging:

My original token (about 8 months old) is still alive.

1 Like

Same here. I thought that it has stopped working earlier this week as issuing the command through Alexa wasn’t working, but I just tested it directly in Hubitat and it works. :blush:

2 Likes

Well...I built this out a little bit.
Nothing revolutionary and it's a bit ugly, but I added a bunch of different unit-based dispensing options, plus two 'custom' fills.

Here's the whole thing. I think it's ready to update in GitHub.
If someone is better at this than me and could convert the many unit-based commands from individual actions into an ENUM where each list item still accepts an integer input, this could be nearly 1:1 with the Delta portal.

/*

Copyright 2023 - SebastienViel

Licensed under the Apache License, Version 2.0 (the "License");

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software

distributed under the License is distributed on an "AS IS" BASIS,

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and

limitations under the License.

-------------------------------------------

This driver was written with the help of OpenAI, tomw and the hubitat community based on the following script from veelar:

https://gist.github.com/velaar/4e18a200c1db7b06109a3fd840838684

Change history:

1.0 - SebastienViel - Initial release.

1.1 - SebastienViel - Removed "Driver" from the driver name.

1.2 - TomW - Added option to dispence a specific quantity and a query for information (logged for now) and other fixes.

1.3 - SebastienViel - Added sending events with on/off/dispense and fixed logging and definitions

1.4 - TomW & SebastienViel - Multiple updates

1.5 - SebastienViel - Updated some of the labels and did a bit of OCD code clean-up...

1.6 - SebastienViel - Added a delay to the off command when dispensing a specific quantity

1.7 - TomW - Updated the delay code to a better approach that would prevent potential issues

1.7 - SebastienViel - Updated Debug Logging

1.8 - TomW - Added a way to display usage data

1.9 - SebastienViel - Fixed Logging when it shouldn't

1.10 - TomW - Update Usage Refresh code to show previous day for daily value since current day is always 0

1.11 - SebastienViel - Updated Usage Refresh code to show previous day when the value received was 0

1.12 - green.aarond - Updated with non-legacy deltafaucet device URL

1.13 - green.aarond - Added dispense options for other units (cL, dL, L, fl oz, c, pt, qt, gal) + 'custom' options to fill a glass or a coffee pot

*/

//import groovy.json.JsonSlurper

//import groovy.transform.Field

metadata

{

    definition(

        name: "Delta VoiceIQ Faucet",

        namespace: "SebastienViel",

        author: "Sébastien Viel and TomW",

        importUrl: "https://raw.githubusercontent.com/SebastienViel/Hubitat_Delta_Faucet/main/Hubitat_Delta_Faucet_Driver"

        )

    {

        capability "Refresh"

        capability "Switch"

        

        attribute "usageLastDay", "number"

        attribute "usageLastWeek", "number"

        attribute "usageLastMonth", "number"

        attribute "usageLastYear", "number"

        

        command "saveToken", ["Token"]

        command "dispense_fl_oz", [[name: "Fluid Ounces", title: "Dispense fl oz", type: "NUMBER", description: "Input number of fluid ounces to dispense (integers only)"]] //30 mL
		
		command "dispense_c", [[name: "Cups", title: "Dispense cups", type: "NUMBER", description: "Input number of cups to dispense (integers only)"]] //237 mL

		command "dispense_pt", [[name: "Pints", title: "Dispense pints", type: "NUMBER", description: "Input number of pints to dispense (integers only)"]] //473 mL
		
		command "dispense_qt", [[name: "Quarts", title: "Dispense quarts", type: "NUMBER", description: "Input number of quarts to dispense (integers only)"]] //946 mL
		
		command "dispense_gal", [[name: "Gallons", title: "Dispense gallons", type: "NUMBER", description: "Input number of gallons to dispense (integers only)"]] //3785 mL
        
        command "dispense_mL", [[name: "Milliliters", title: "Dispense mL", type: "NUMBER", description: "Input number of milliliters to dispense (integers only)"]]
		
		command "dispense_cL", [[name: "Centiliters", title: "Dispense cL", type: "NUMBER", description: "Input number of centiliters to dispense (integers only)"]] //10 mL

		command "dispense_dL", [[name: "Deciliters", title: "Dispense dL", type: "NUMBER", description: "Input number of deciliters to dispense (integers only)"]] //100 mL

		command "dispense_L", [[name: "Liters", title: "Dispense L", type: "NUMBER", description: "Input number of liters to dispense (integers only)"]] //1000 mL        

		command "fill_glass", [[name: "Fill Glass", title: "Fill Glass", description: "Dispense 1 glass (12 fl oz) of water"]] //355 mL

		command "fill_coffee_pot", [[name: "Fill Coffee Pot", title: "Fill Coffee Pot", description: "Dispense 4 cups (32 fl oz) of water"]] //943 mL

        command "queryUsageReport", [[name: "Time interval", title: "Query Usage Report", type:"ENUM", constraints: knownTimePeriods().keySet()]]        

     }

}

preferences

{

    input(name: "delta_device_id", type: "text", title: "Delta Device ID", description: "Enter Delta Device ID", defaultValue: "")

    input(name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true)

}

def installed()

{

    log.debug "installed"

}

def updated()

{

    log.debug "updated"

}

def on()

{

    toggleWater("on")

}

def off()

{

    toggleWater("off")

}

def refresh()

{

    def resp

    knownTimePeriods().each

    {

        resp = queryUsageReport(it.key)

        

        // daily usage is zero for current day, so go to previous day only for that case

        //def dataOffset = (it.key == "usageLastDay") ? 2 : 1

        def dataOffset = 1

        def value = resp?.getAt(resp.size() - dataOffset)

        

        if(logEnable){log.debug "${it.key} key value is ${value} for response ${resp.size()} less a ${dataOffset} offset"}

        if(value == 0)

        {

            value = resp?.getAt(resp.size() - (dataOffset + 1))

            if(logEnable){log.debug "Retrying since value was 0 - ${it.key} key value is ${value} for response ${resp.size()} less a ${dataOffset + 1} offset"}

        }

        

        sendEvent(name: it.key, value: value)

    }

}

def knownTimePeriods()

{

    def periods = [ usageLastDay: 0, usageLastWeek: 1, usageLastMonth: 2, usageLastYear: 3 ]

    

    return periods

}

def queryUsageReport(interval)

{

    interval = knownTimePeriods()?.getAt(interval)

     

    try

    {

        if(logEnable){log.debug "Getting Usage Report for ${interval} interval."}

        def response = httpExec("GET", "UsageReport?deviceId=${delta_device_id}&interval=${interval}")

        if(response?.status?.toInteger() == 200)

        {

            if (logEnale){log.info "Usage Report = ${response.data}"}

            return response.data?.retObject?.datasets?.getAt(0)?.data

        }

        else

        {

            log.error "Usage Report ${interval} failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error Usage Report: ${e.message}"

    }

}

def saveToken(token)

{

    device.updateDataValue("token", token)

}

def currentToken()

{

    return device.getDataValue("token")

}

private toggleWater(String toggle)

{    

    try

    {

        if (logEnable){log.debug "Preparing the ${toggle} command to send to Delta."}

        

        def response = httpExec("POST", "toggleWater?deviceId=${delta_device_id}&toggle=${toggle}")

        if (logEnable){log.debug "Response Status (from toggleWater): ${response.status}"}

        if (logEnable){log.debug "Response Data (from toggleWater): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            sendSwitchState(toggle)

//            sendEvent(name: "switch", value: toggle, descriptionText: "${device.displayName} is ${toggle}")

            if (logEnable){log.debug "Toggle water ${toggle} successful"}

        }

        else

        {

            log.error "Toggle water ${toggle} failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error toggling water: ${e.message}"

    }

}

private dispense_mL(mL_toDispense)

{   

    try

    {

        if (logEnable){log.debug "Preparing the ${mL_toDispense} mL to dispense command to send to Delta."}

        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${mL_toDispense}")

        if (logEnable){log.debug "Response Status (from dispense_mL): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_mL): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense mL ${mL_toDispense} successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${mL_toDispense}"}

            

            def pauseDuration = 10

            

            pauseDuration = mL_toDispense.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense mL failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private dispense_fl_oz(fl_oz_toDispense)

{   

    try

    {
        
        def fl_oz_in_mL = 30
        
        fl_oz_in_mL = fl_oz_toDispense.toInteger() * 30
        
        if (logEnable){log.debug "Preparing the ${fl_oz_in_mL} mL to dispense command to send to Delta (calculated from ${fl_oz_toDispense} fl. oz.)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${fl_oz_in_mL}")

        if (logEnable){log.debug "Response Status (from dispense_fl_oz): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_fl_oz): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense ${fl_oz_toDispense} fl. oz. (sent as ${fl_oz_in_mL} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${fl_oz_in_mL} (calculated from ${fl_oz_toDispense} fl. oz.)"}

            

            def pauseDuration = 10

            

            pauseDuration = fl_oz_in_mL.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense fl oz failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private dispense_c(c_toDispense)

{   

    try

    {
        
        def c_in_mL = 237
        
        c_in_mL = c_toDispense.toInteger() * 237
        
        if (logEnable){log.debug "Preparing the ${c_in_mL} mL to dispense command to send to Delta (calculated from ${c_toDispense}c)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${c_in_mL}")

        if (logEnable){log.debug "Response Status (from dispense_c): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_c): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense ${c_toDispense}c (sent as ${c_in_mL} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${c_in_mL} (calculated from ${c_toDispense}c)"}

            

            def pauseDuration = 10

            

            pauseDuration = c_in_mL.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense c failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private dispense_pt(pt_toDispense)

{   

    try

    {
        
        def pt_in_mL = 473
        
        pt_in_mL = pt_toDispense.toInteger() * 473
        
        if (logEnable){log.debug "Preparing the ${pt_in_mL} mL to dispense command to send to Delta (calculated from ${pt_toDispense}pt)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${pt_in_mL}")

        if (logEnable){log.debug "Response Status (from dispense_pt): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_pt): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense ${pt_toDispense}pt (sent as ${pt_in_mL} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${pt_in_mL} (calculated from ${pt_toDispense}pt)"}

            

            def pauseDuration = 10

            

            pauseDuration = pt_in_mL.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense pt failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private dispense_qt(qt_toDispense)

{   

    try

    {
        
        def qt_in_mL = 946
        
        qt_in_mL = qt_toDispense.toInteger() * 946
        
        if (logEnable){log.debug "Preparing the ${qt_in_mL} mL to dispense command to send to Delta (calculated from ${qt_toDispense}qt)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${qt_in_mL}")

        if (logEnable){log.debug "Response Status (from dispense_qt): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_qt): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense ${qt_toDispense}qt (sent as ${qt_in_mL} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${qt_in_mL} (calculated from ${qt_toDispense}qt)"}

            

            def pauseDuration = 10

            

            pauseDuration = qt_in_mL.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense qt failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private dispense_gal(gal_toDispense)

{   

    try

    {
        
        def gal_in_mL = 3785
        
        gal_in_mL = gal_toDispense.toInteger() * 3785
        
        if (logEnable){log.debug "Preparing the ${gal_in_mL} mL to dispense command to send to Delta (calculated from ${gal_toDispense}gal)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${gal_in_mL}")

        if (logEnable){log.debug "Response Status (from dispense_gal): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_gal): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense ${gal_toDispense}gal (sent as ${gal_in_mL} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${gal_in_mL} (calculated from ${gal_toDispense}gal)"}

            

            def pauseDuration = 10

            

            pauseDuration = gal_in_mL.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense gal failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private dispense_cL(cL_toDispense)

{   

    try

    {
        
        def cL_in_mL = 10
        
        cL_in_mL = cL_toDispense.toInteger() * 10
        
        if (logEnable){log.debug "Preparing the ${cL_in_mL} mL to dispense command to send to Delta (calculated from ${cL_toDispense} cL)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${cL_in_mL}")

        if (logEnable){log.debug "Response Status (from dispense_cL): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_cL): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense ${cL_toDispense} cL (sent as ${cL_in_mL} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${cL_in_mL} (calculated from ${cL_toDispense} cL)"}

            

            def pauseDuration = 10

            

            pauseDuration = cL_in_mL.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense cL failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private dispense_dL(dL_toDispense)

{   

    try

    {
        
        def dL_in_mL = 100
        
        dL_in_mL = dL_toDispense.toInteger() * 100
        
        if (logEnable){log.debug "Preparing the ${dL_in_mL} mL to dispense command to send to Delta (calculated from ${dL_toDispense} dL)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${dL_in_mL}")

        if (logEnable){log.debug "Response Status (from dispense_dL): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_dL): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense ${dL_toDispense} dL (sent as ${dL_in_mL} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${dL_in_mL} (calculated from ${dL_toDispense} dL)"}

            

            def pauseDuration = 10

            

            pauseDuration = dL_in_mL.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense dL failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private dispense_L(L_toDispense)

{   

    try

    {
        
        def L_in_mL = 100
        
        L_in_mL = L_toDispense.toInteger() * 100
        
        if (logEnable){log.debug "Preparing the ${L_in_mL} mL to dispense command to send to Delta (calculated from ${L_toDispense} L)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${L_in_mL}")

        if (logEnable){log.debug "Response Status (from dispense_L): ${response.status}"}

        if (logEnable){log.debug "Response Data (from dispense_L): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense ${L_toDispense} L (sent as ${L_in_mL} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${L_in_mL} (calculated from ${L_toDispense} L)"}

            

            def pauseDuration = 10

            

            pauseDuration = L_in_mL.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Dispense L failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private fill_glass(glass_fill)

{   

    try

    {
        
        def glassful = 355
        
        if (logEnable){log.debug "Preparing the ${glassful} mL to dispense command to send to Delta (one 12 fl. oz. glass of water)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${glassful}")

        if (logEnable){log.debug "Response Status (from fill_glass): ${response.status}"}

        if (logEnable){log.debug "Response Data (from fill_glass): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense 1 glass (sent as ${glassful} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${glassful} (one 12 fl. oz. glass of water)"}

            

            def pauseDuration = 10

            

            pauseDuration = glassful.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Fill glass failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

private fill_coffee_pot(coffee_pot_fill)

{   

    try

    {
        
        def coffeepot = 943
        
        if (logEnable){log.debug "Preparing the ${coffeepot} mL to dispense command to send to Delta (32 fl. oz. of water; about 1 coffee pot)."}
        
        def response = httpExec("POST", "dispense?deviceId=${delta_device_id}&milliliters=${coffeepot}")

        if (logEnable){log.debug "Response Status (from fill_coffee_pot): ${response.status}"}

        if (logEnable){log.debug "Response Data (from fill_coffee_pot): ${response.data}"}

        

        if(response?.status?.toInteger() == 200)

        {

            if (logEnable){ log.debug "Dispense 1 coffee pot (sent as ${coffeepot} mL) successful" }

            //sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} is on")

            if (logEnable){ log.debug "mL_toDispense is set to ${coffeepot} (32 fl. oz. of water; about 1 coffee pot)"}

            

            def pauseDuration = 10

            

            pauseDuration = coffeepot.toInteger() / 70

            if (logEnable){ log.debug "Pause duration is set to ${pauseDuration}"}

            

            if(pauseDuration.toInteger() > 300)

            {

                pauseDuration = 300                

                if (logEnable){ log.debug "Pause duration was changed to ${pauseDuration}"}

            }

            pauseDuration = pauseDuration.intValue()

            

            sendSwitchState("on", pauseDuration)

        }

        else

        {

            log.error "Fill coffee pot failed: ${response.status}"

        }

    }

    catch(Exception e)

    {

        log.error "Error dispensing water: ${e.message}"

    }

}

def sendSwitchState(value, timeout = null)

{

    unschedule(sendSwitchState)

    

    // set presence to incoming state

    sendEvent(name: "switch", value: value, descriptionText: "${device.displayName} is off")

    if (logEnable) {log.debug "Timeout delay is set to ${timeout} seconds."}

    if(null != timeout)

    {

        // if timeout reached, assume "off" is indicated state

        if (logEnable) {log.debug "Set sendSwitchState to run in ${timeout} seconds."}

        runIn(timeout, sendSwitchState, [data: "off"])

    }

    if (logEnable) {log.debug "Switch State updated to ${value}."}    

}

def httpExec(operation, String path)

{

    def result = null

    

    def httpClosure = 

    { resp ->

        result = resp

    }

    

    def httpOp

    

    switch(operation)

    {

        case "POST":

            httpOp = this.delegate.&httpPost

            break

        case "GET":

            httpOp = this.delegate.&httpGet

            break

    }

    

    def deltaToken = "Bearer ${currentToken()}"

    def apiUrl = "https://device.deltafaucet.com/api/device/" + path

    def headers =

        [

            'Authorization': deltaToken, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36',

            'Referer': 'https://device.deltafaucet.com/'

        ]

    

    if (logEnable){log.debug "Sending the ${operation} for the command to be executed."}

    httpOp(uri: apiUrl, headers: headers, httpClosure)

    

    return result

}


I should note: the reason that I've used mL with conversions for everything in my modded driver, is that this is how the DeltaFaucet device portal sends these commands. I checked the request headers for each of these; always mL, even for the ounces and gallons and stuff.

Is it any possibility to report Open/Closed (or On/Off) faucet status?
Some sort of indirect status reporting also may work.
Right now when rule sends an ON command it waits for 3sec assuming this time is enough (usually is) for actual command to go through (this stupid cloud integration). However there is no guarantee the water is actually flowing.