Fortezz Water Meter

Hell all

I am new to the Hubitat community and home automation. I purchased a home that had some zwave products so I thought I would give it a shot. I was able to join and control some light switches which were on the prior owners system that he took with him.

My issue is this water meter. It is joined to the hubitat but does not seem to have any native drivers. How do I get this to report data. They do have install guides for ST but I am not sure how that translates or how to even complete that task.

If any one could point me in the right direction I would appreciate it.

I have no way to test this, but a quick port of the ST DTH may give you something to start with:

Fortezz Flow Meter Device Driver
/**
 *  FortrezZ Flow Meter Interface
 *
 *  Copyright 2016 FortrezZ, LLC
 *
 *  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.
 *
 ********** Ported from the ST DTH ******************
 */
metadata {
	definition (name: "FortrezZ Flow Meter Interface", namespace: "fortrezz", author: "Daniel Kurin") {
		capability "Battery"
		capability "Energy Meter"
		capability "Image Capture"
		capability "Temperature Measurement"
        capability "Sensor"
        capability "Water Sensor"
        
        attribute "gpm", "number"
        attribute "cumulative", "number"
        attribute "alarmState", "string"
        attribute "chartMode", "string"
        attribute "lastThreshhold", "number"

        
        command "chartMode"
        command "zero"
        command "setHighFlowLevel", ["number"]

	    fingerprint deviceId: "0x2101", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x73, 0x71, 0x85, 0x59, 0x32, 0x31, 0x70, 0x80, 0x7A"
	}
   
    preferences {
        input ("version", "text", title: "Plugin Version 1.5", description:"", required: false, displayDuringSetup: true)
       input "gallonThreshhold", "decimal", title: "High Flow Rate Threshhold", description: "Flow rate (in gpm) that will trigger a notification.", defaultValue: 5, required: false, displayDuringSetup: true
    }

    
}

// parse events into attributes
def parse(String description) {
	def results = []
	if (description.startsWith("Err")) {
	    results << createEvent(descriptionText:description, displayed:true)
	} else {
		def cmd = zwave.parse(description, [ 0x80: 1, 0x84: 1, 0x71: 2, 0x72: 1 ])
		if (cmd) {
			results << createEvent( zwaveEvent(cmd) )
		}
	}
	//log.debug "\"$description\" parsed to ${results.inspect()}"
    if(gallonThreshhold != device.currentValue("lastThreshhold"))
    {
    	results << setThreshhold(gallonThreshhold)
    }
	log.debug "zwave parsed to ${results.inspect()}"
	return results
}

def updated()
{
	log.debug("Updated")
}

def setHighFlowLevel(level)
{
	setThreshhold(level)
}

def take() {
	def mode = device.currentValue("chartMode")
    if(mode == "day")
    {
    	take1()
    }
    else if(mode == "week")
    {
    	take7()
    }
    else if(mode == "month")
    {
    	take28()
    }
}

def chartMode(string) {
	log.debug("ChartMode")
	def state = device.currentValue("chartMode")
    def tempValue = ""
	switch(state)
    {
    	case "day":
        	tempValue = "week"
            break
        
        case "week":
        	tempValue = "month"
            break
            
        case "month":
        	tempValue = "day"
            break
            
        default:
        	tempValue = "day"
            break
    }
	sendEvent(name: "chartMode", value: tempValue)
    take()
}

def take1() {
    api("24hrs", "") {
        log.debug("Image captured")

        if(it.headers.'Content-Type'.contains("image/png")) {
            if(it.data) {
                storeImage(getPictureName("24hrs"), it.data)
            }
        }
    }
}

def take7() {
    api("7days", "") {
        log.debug("Image captured")

        if(it.headers.'Content-Type'.contains("image/png")) {
            if(it.data) {
                storeImage(getPictureName("7days"), it.data)
            }
        }
    }
}

def take28() {
    api("4weeks", "") {
        log.debug("Image captured")

        if(it.headers.'Content-Type'.contains("image/png")) {
            if(it.data) {
                storeImage(getPictureName("4weeks"), it.data)
            }
        }
    }
}

def zero()
{
	delayBetween([
		zwave.meterV3.meterReset().format(),
        zwave.meterV3.meterGet().format(),
        zwave.firmwareUpdateMdV2.firmwareMdGet().format(),
    ], 100)
}

def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
{
	log.debug cmd
	def map = [:]
	if(cmd.sensorType == 1) {
		map = [name: "temperature"]
        if(cmd.scale == 0) {
        	map.value = getTemperature(cmd.scaledSensorValue)
        } else {
	        map.value = cmd.scaledSensorValue
        }
        map.unit = location.temperatureScale
	} /* else if(cmd.sensorType == 2) {
    	map = [name: "waterState"]
        if(cmd.sensorValue[0] == 0x80) {
        	map.value = "flow"
            sendEvent(name: "water", value: "dry")
        } else if(cmd.sensorValue[0] == 0x00) {
	        map.value = "none"
            sendEvent(name: "water", value: "dry")
        } else if(cmd.sensorValue[0] == 0xFF) {
	        map.value = "overflow"
            sendEvent(name: "water", value: "wet")
            sendAlarm("waterOverflow")
        }
	} */
	return map
}

def zwaveEvent(hubitat.zwave.commands.meterv3.MeterReport cmd)
{
	def map = [:]
    map.name = "gpm"
    def delta = cmd.scaledMeterValue - cmd.scaledPreviousMeterValue
    if (delta < 0 || delta > 10000) {
        log.error(cmd)
    	delta = 0
    }

    map.value = delta
    map.unit = "gpm"
    sendDataToCloud(delta)
    sendEvent(name: "cumulative", value: cmd.scaledMeterValue, displayed: false, unit: "gal")
	return map
}

def zwaveEvent(hubitat.zwave.commands.alarmv2.AlarmReport cmd)
{
	def map = [:]
    if (cmd.zwaveAlarmType == 8) // Power Alarm
    {
    	map.name = "powerState" // For Tile (shows in "Recently")
        if (cmd.zwaveAlarmEvent == 2) // AC Mains Disconnected
        {
            map.value = "disconnected"
            sendAlarm("acMainsDisconnected")
        }
        else if (cmd.zwaveAlarmEvent == 3) // AC Mains Reconnected
        {
            map.value = "reconnected"
            sendAlarm("acMainsReconnected")
        }
        else if (cmd.zwaveAlarmEvent == 0x0B) // Replace Battery Now
        {
            map.value = "noBattery"
            sendAlarm("replaceBatteryNow")
        }
        else if (cmd.zwaveAlarmEvent == 0x00) // Battery Replaced
        {
            map.value = "batteryReplaced"
            sendAlarm("batteryReplaced")
        }
    }
    else if (cmd.zwaveAlarmType == 4) // Heat Alarm
    {
    	map.name = "heatState"
        if (cmd.zwaveAlarmEvent == 0) // Normal
        {
            map.value = "normal"
        }
        else if (cmd.zwaveAlarmEvent == 1) // Overheat
        {
            map.value = "overheated"
            sendAlarm("tempOverheated")
        }
        else if (cmd.zwaveAlarmEvent == 5) // Underheat
        {
            map.value = "freezing"
            sendAlarm("tempFreezing")
        }
    }
    else if (cmd.zwaveAlarmType == 5) // Water Alarm
    {
    	map.name = "waterState"
        if (cmd.zwaveAlarmEvent == 0) // Normal
        {
            map.value = "none"
            sendEvent(name: "water", value: "dry")
        }
        else if (cmd.zwaveAlarmEvent == 6) // Flow Detected
        {
        	if(cmd.eventParameter[0] == 2)
            {
                map.value = "flow"
                sendEvent(name: "water", value: "dry")
            }
            else if(cmd.eventParameter[0] == 3)
            {
            	map.value = "overflow"
                sendAlarm("waterOverflow")
                sendEvent(name: "water", value: "wet")
            }
        }
    }
    //log.debug "alarmV2: $cmd"
    
	return map
}

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
	def map = [:]
	if(cmd.batteryLevel == 0xFF) {
		map.name = "battery"
		map.value = 1
		map.descriptionText = "${device.displayName} has a low battery"
		map.displayed = true
	} else {
		map.name = "battery"
		map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1
		map.unit = "%"
		map.displayed = false
	}
	return map
}

def zwaveEvent(hubitat.zwave.Command cmd)
{
	log.debug "COMMAND CLASS: $cmd"
}

def sendDataToCloud(double data)
{
    def params = [
        uri: "https://iot.swiftlet.technology",
        path: "/fortrezz/post.php",
        body: [
            id: device.id,
            value: data,
            email: registerEmail
        ]
    ]

	//log.debug("POST parameters: ${params}")
    try {
        httpPostJson(params) { resp ->
            resp.headers.each {
                //log.debug "${it.name} : ${it.value}"
            }
            log.debug "sendDataToCloud query response: ${resp.data}"
        }
    } catch (e) {
        log.debug "something went wrong: $e"
    }
}

def getTemperature(value) {
	if(location.temperatureScale == "C"){
		return value
    } else {
        return Math.round(celsiusToFahrenheit(value))
    }
}

private getPictureName(category) {
  //def pictureUuid = device.id.toString().replaceAll('-', '')
  def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')

  def name = "image" + "_$pictureUuid" + "_" + category + ".png"
  return name
}

def api(method, args = [], success = {}) {
  def methods = [
    //"snapshot":        [uri: "http://${ip}:${port}/snapshot.cgi${login()}&${args}",        type: "post"],
    "24hrs":      [uri: "https://iot.swiftlet.technology/fortrezz/chart.php?uuid=${device.id}&tz=${location.timeZone.ID}&type=1", type: "get"],
    "7days":      [uri: "https://iot.swiftlet.technology/fortrezz/chart.php?uuid=${device.id}&tz=${location.timeZone.ID}&type=2", type: "get"],
    "4weeks":     [uri: "https://iot.swiftlet.technology/fortrezz/chart.php?uuid=${device.id}&tz=${location.timeZone.ID}&type=3", type: "get"],
  ]

  def request = methods.getAt(method)

  return doRequest(request.uri, request.type, success)
}

private doRequest(uri, type, success) {
  log.debug(uri)

  if(type == "post") {
    httpPost(uri , "", success)
  }

  else if(type == "get") {
    httpGet(uri, success)
  }
}

def sendAlarm(text)
{
	sendEvent(name: "alarmState", value: text, descriptionText: text, displayed: false)
}

def setThreshhold(rate)
{
	log.debug "Setting Threshhold to ${rate}"
    
    def event = createEvent(name: "lastThreshhold", value: rate, displayed: false)
    def cmds = []
    cmds << zwave.configurationV2.configurationSet(configurationValue: [(int)Math.round(rate*10)], parameterNumber: 5, size: 1).format()
    sendEvent(event)
    return response(cmds) // return a list containing the event and the result of response()
}

Welcome to the Hubitat community!

Interesting typo.

Because you are new to Hubitat, here is how to install a custom driver:

https://docs.hubitat.com/index.php?title=How_to_Install_Custom_Drivers

1 Like

The learning curve is a little hellish but I am sure I will get there. Thanks for the info.

1 Like

The ST Fortrezz driver mentioned above is for an earlier 2016 ST version and now loads as an energy meter. I unfortunately also purchased this Fortrezz Flow Meter and have not been able to get functionality with either a ST or Hubitat hub. I would appreciate help from anyone that can get flow reporting functionality from this meter to create safety routines to shut water off when leaks exist.

1 Like

Well ive had this thing collecting dust in my basement for a long time. The ST implementation was functional but due to limitations of ST I couldn’t make much use of it so not really helpful and it got forgotten.

I managed to connect the device to my HE C7 with a bit of trouble and lots of button pushing. Not sure where the hangup is with pairing to HE, but it reliably pairs with ST.

Made sure it was excluded from a zwave net.

It seems like the device will intermittently flash 4 times with power applied when it is not included in a zwave net.

I took the batteries out and used the external power to power on and push the button once. I think this did it… not real confident what steps were needed to get it to include successfully.

As far as i can tell it is a zwave plus 500 series device.

This unit could be a decent addition to my home if the shortcomings can be ironed out.

The flow meter is a quality brass meter with true analog gauges. There is a sensor attached to the glass that picks up the movement of the last gauge. This sensor is likely a reed switch but I've never felt the need to tear it apart and verify.

The FMI (flow meter interface) is a dual powered zwave interface. It has a micro usb and a set of AA batteries for keeping the device powered.

@thebearmay your groovy porting of the driver works perfectly in HE.

Device is communicating.

1 Like