How to create a custom Driver

Hi,

The energy monitoring device I use just released there first api.
Seeing the data I get from a http-get request, it looks like a driver for this device should not be that hard to create. That sayed, I'm not a coding star, so will need som help, pointing me in the right direction.

When I make a http-get request to the device itself ( http://{IP address}/api/v1/data ) I'm getting a nice result set:

{"smr_version":50,"meter_model":"Landis + Gyr LGBBLA4412146022","wifi_ssid":"Berg-WiFi","wifi_strength":100,"total_power_import_t1_kwh":3983.931,"total_power_import_t2_kwh":4761.867,"total_power_export_t1_kwh":840.138,"total_power_export_t2_kwh":1911.247,"active_power_w":-121,"active_power_l1_w":-121,"active_power_l2_w":null,"active_power_l3_w":null,"total_gas_m3":2742.1,"gas_timestamp":200919172958}

Wat i do not know how to make:

  • Have Hubitat poll the URL
  • Parse the result to result attributes

So if someone could give me some nooby help, that would be great.

Please feel free to take a look at my custom driver for the IoTaWatt power monitoring device. It should help get you started with a possible design to achieve your goals. Good luck with your project!

2 Likes

Is there an API reference on their website? I couldn't find it.

There is, but it is in Dutch:
https://energy.homewizard.net/nl/support/solutions/articles/19000117051-homewizard-p1-meter-lokale-api-beta-

It was still pretty easy to read (especially with Google translate :)).

There's really only one API call supported, unless you want the raw data from the meter.

I took a stab at supporting it as a PowerMeter in Hubitat. Your version of the meter seems to update its power reading every 1 second, but I made the default refresh rate 10 seconds. The API document says not to poll any faster than twice per second.

Do you need access to any of the other readings, like cumulative power or instantaneous power for any of the individual phases? Those could be added using custom attributes, and they would be a good way for you to experiment with driver development if you are interested.

/*

 */

metadata
{
    definition(name: "HomeWizard P1 Meter", namespace: "tomw", author: "tomw", importUrl: "")
    {
        capability "Initialize"
        capability "PowerMeter"
        capability "Refresh"
        
        attribute "commStatus", "string"
    }
}

preferences
{
    section
    {
        input "ipAddress", "text", title: "IP address meter", required: true
        input "refreshInterval", "number", title: "Refresh interval (seconds)", defaultValue: 10
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
    }
}

def logDebug(msg) 
{
    if (logEnable)
    {
        log.debug(msg)
    }
}

def updated()
{
    refresh()
}

def initialize()
{
    sendEvent(name: "commStatus", value: "unknown")
    sendEvent(name: "power", value: "unknown")
    refresh()
}

def refresh()
{
    unschedule()
    
    try
    {
        def res = httpGetExec([uri: getBaseURI()], true)
        sendEvent(name: "commStatus", value: "good")
        sendEvent(name: "power", value: res?.active_power_w?.toInteger())
        
        // schedule next refresh
        runIn(refreshInterval.toInteger(), refresh)
    }
    catch (Exception e)
    {
        logDebug("refresh() failed: ${e.message}")
        logDebug("run Refresh command re-start polling")
        sendEvent(name: "commStatus", value: "error")
    }    
}

def getBaseURI()
{
    return "http://${ipAddress}/api/v1/data"
}

def httpGetExec(params, throwToCaller = false)
{
    logDebug("httpGetExec(${params})")
    
    try
    {
        def result
        httpGet(params)
        { resp ->
            if (resp.data)
            {
                logDebug("resp.data = ${resp.data}")
                result = resp.data
            }
        }
        return result
    }
    catch (Exception e)
    {
        logDebug("httpGetExec() failed: ${e.message}")
        if(throwToCaller)
        {
            throw(e)
        }
    }
}
1 Like

Looking good Tomw.
looking at your code in detail, and trying to grasp what you are doing with each line of code.

If I understand correctly, with the line sendEvent(name: "power", value: res?.active_power_w?.toInteger()) you define the "active_power_w" result as an integer?

Yeah, seems to work:

    sendEvent(name: "commStatus", value: "good")
    sendEvent(name: "power", value: res?.active_power_w?.toInteger())
    sendEvent(name: "WiFi-Strength", value: res?.wifi_strength.toInteger())
    sendEvent(name: "total_power_import_t1_kwh", value: res?.total_power_import_t1_kwh.toInteger())

Screenie

1 Like

Hi @fanmanrules!

The toInteger() is, at least to me, conceptually similar to a reinterpet_cast in C languages. sendEvent is a Hubitat platform function call that sets the current value of an attribute and also logs an event with the system.

If you want to report additional attributes, check the built-in driver capability list first: Driver Capability List - Hubitat Documentation

For example, you may want to use SignalStrength for the Wi-Fi signal, though for the record the value reported as 100 seems suspect and in different units than either rssi or lqi, which are the attributes supported by SignalStrength.

If you want to support an attribute that is not part of a built in capability class, be sure to declare that attribute in the driver definition section like I did with commStatus. Then you can use sendEvent like you did with total_power_import_t1_kwh

1 Like

Did a whole definition in the beginning of the driver.:
* capability "Initialize"
* capability "PowerMeter"
* capability "Refresh"
* capability "EnergyMeter"
* attribute "commStatus", "string"
* attribute "smr_version", "string"
* attribute "meter_model", "string"
* attribute "wifi_ssid", "string"
* attribute "wifi_strength", "string"
* attribute "total_power_import_t1_kwh", "number"
* attribute "total_power_import_t2_kwh", "number"
* attribute "total_power_export_t1_kwh", "number"
* attribute "total_power_export_t2_kwh", "number"
* attribute "active_power_w", "number"
* attribute "active_power_l1_w", "string"
* attribute "active_power_l2_w", "string"
* attribute "active_power_l3_w", "string"
* attribute "total_gas_m3", "number"

But how to define the "units" to be able to display them on a tile?

I found a potentially useful discussion here: Attributes - Questions on characteristics

I haven't used the units functionality with events personally, but maybe you can get it to do what you are looking for. Please share results here if you figure it out.

1 Like

Tom,

Tomorrow evening I have time to play with the driver again.
As soon as I figure it all out, I'll keep you posted.

Hmm,

If I use the default sendevent, I do not have the chance to define the unit type:
sendEvent(name: "total_power_import_t1_kwh", value: res?.total_power_import_t1_kwh.toInteger())

If I use a defined unit in the sendevent, it wil not return the units:
sendEvent([name: "total_power_import_t1_kwh", value: res?.total_power_import_t1_kwh.toInteger(), unit: "kWh"])

Need to look at more existing code to get an idee how to define custom displayable units.

Another way I have done it is making the entire value a string, as described here: Display Units on Dashboard Tiles

This is a good option is you only want to display the value with units. Make the whole attribute a string. It isn't a good option if you need to make numerical comparisons with the measurement, for example in Rule Machine automatons.

Getting the units in the device events now, but not on the tiles.
What I have read, we cannot define the tiles ourselves?

Ok, new driver released: Repository

Fixed:

  • When only using L1 power lead, set pref so no error wil display
  • Value units now show in events

Known issues

  • When only using L1 power lead, and pref is not set, an error wil show
  • Custom units do not show in tiles