Rainforest Zigbee Smart Meter support

I moved it over to node-red. Polling the eagle the putting the data to mqtt. let me know if you would like the flow.

Cool, that would be awesome. I’m new to OH (coming from SmartThings) and haven’t gone into MQTT yet but looks like I’ve got a few other things to add later that will be using MQTT as well.

Cheers!

Here ya go.

First it builds the query replace MacId with the id of your EAGLE

<LocalCommand>   <Name>get_usage_data</Name>   <MacId>0xd8d5b90000005aeb</MacId> </LocalCommand>

Then it will POST to the EAGLE http://192.168.0.109/cgi-bin/cgi_manager replace the ip with yours.
The JSON data is parsed and seperated.

The first branch takes the power usage and converts kW to W and sends it to graphs and out to MQTT
also converting current usage to cost.

The delivered sum doesn’t update as often as the usage so it gets checked and only passed if it is different than the last message with the rbe and then sent out to MQTT

The last branch takes out the data and converts it back to JSON to make the raw data available on mqtt.

[{"id":"ead95b90.b7dc28","type":"inject","z":"1f5ff597.a9c6da","name":"10sec Loop","topic":"","payload":"","payloadType":"str","repeat":"10","crontab":"","once":false,"x":238.88888549804688,"y":197.77777004241943,"wires":[["8d43225d.18a7d"]]},{"id":"8d43225d.18a7d","type":"change","z":"1f5ff597.a9c6da","name":"EAGLE Request","rules":[{"t":"set","p":"payload","pt":"msg","to":"<LocalCommand>   <Name>get_usage_data</Name>   <MacId>0xd8d5b90000005aeb</MacId> </LocalCommand>","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":248.88888549804688,"y":237.77777004241943,"wires":[["4408e4f7.42df2c"]]},{"id":"4408e4f7.42df2c","type":"http request","z":"1f5ff597.a9c6da","name":"EAGLE POST","method":"POST","ret":"obj","url":"http://192.168.0.109/cgi-bin/cgi_manager","tls":"","x":248.88888549804688,"y":277.77777004241943,"wires":[["ab65ea2f.aed7b8","f455e57f.ec02a8","2b82e58d.0cc42a","2ecba0de.c2a92"]]},{"id":"6a7bc971.502e88","type":"rbe","z":"1f5ff597.a9c6da","name":"","func":"rbe","gap":"","start":"","inout":"out","x":658.8888854980469,"y":297.77777004241943,"wires":[["e04d4b53.15dc98"]]},{"id":"ab65ea2f.aed7b8","type":"change","z":"1f5ff597.a9c6da","name":"Delivered","rules":[{"t":"set","p":"payload","pt":"msg","to":"msg.payload.summation_delivered","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":488.8888854980469,"y":297.77777004241943,"wires":[["6a7bc971.502e88"]]},{"id":"e3d21794.5c7298","type":"mqtt out","z":"1f5ff597.a9c6da","name":"Lexor_MQTT","topic":"","qos":"2","retain":"true","broker":"fee1fac2.261a88","x":1172.2222900390625,"y":296.66665267944336,"wires":[]},{"id":"3cb8b99.2976846","type":"ui_chart","z":"1f5ff597.a9c6da","name":"","group":"dfc4c73a.069fe8","order":0,"width":"0","height":"0","label":"1hr","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"x":827.77783203125,"y":122.22222137451172,"wires":[[],[]]},{"id":"74d9ddcb.082af4","type":"ui_chart","z":"1f5ff597.a9c6da","name":"","group":"dfc4c73a.069fe8","order":0,"width":0,"height":0,"label":"5min","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","ymin":"","ymax":"","removeOlder":"5","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"x":826.6666831970215,"y":165.55555152893066,"wires":[[],[]]},{"id":"f455e57f.ec02a8","type":"function","z":"1f5ff597.a9c6da","name":"Multiply1000","func":"var demandkw = Number(msg.payload.demand);\nvar price = 949.4;\nvar demandw = { payload: Math.round(1000 * demandkw)};\nvar L = Math.round(price * demandkw);\nvar priceh = { payload: (L/10000) };\nreturn [demandw, priceh];","outputs":"2","noerr":0,"x":498.8888854980469,"y":237.77777004241943,"wires":[["74d9ddcb.082af4","3cb8b99.2976846","62741850.d8fb08","7466596b.be66c8"],["109d42e2.f12a9d"]]},{"id":"109d42e2.f12a9d","type":"ui_text","z":"1f5ff597.a9c6da","group":"dfc4c73a.069fe8","order":0,"width":0,"height":0,"name":"","label":"Power Cost","format":"{{msg.payload| number:3}}$/h","layout":"col-center","x":832.2222213745117,"y":258.88889503479004,"wires":[]},{"id":"2b82e58d.0cc42a","type":"json","z":"1f5ff597.a9c6da","name":"","x":479.99999237060547,"y":348.88887214660645,"wires":[["eed1b386.2814f"]]},{"id":"62741850.d8fb08","type":"ui_gauge","z":"1f5ff597.a9c6da","name":"","group":"dfc4c73a.069fe8","order":0,"width":0,"height":0,"gtype":"gage","title":"Gauge","label":"W","format":"{{value}}","min":"1000","max":"14000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":827.7777938842773,"y":80.00000762939453,"wires":[]},{"id":"eed1b386.2814f","type":"change","z":"1f5ff597.a9c6da","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"lexor/eagle/json","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":831.1111145019531,"y":364.44443130493164,"wires":[["e3d21794.5c7298"]]},{"id":"e04d4b53.15dc98","type":"change","z":"1f5ff597.a9c6da","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"openHAB/out/HouseMeterRead/state","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":832.2222213745117,"y":320.0000190734863,"wires":[["e3d21794.5c7298"]]},{"id":"7466596b.be66c8","type":"change","z":"1f5ff597.a9c6da","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"openHAB/out/HousePowerInstant/state","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":845.5555648803711,"y":214.44443893432617,"wires":[["e3d21794.5c7298"]]},{"id":"fee1fac2.261a88","type":"mqtt-broker","z":"","broker":"mqtt.YOUR.BROKER","port":"1883","clientid":"nodered","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"","willQos":"0","willPayload":"","birthTopic":"","birthQos":"0","birthPayload":""},{"id":"dfc4c73a.069fe8","type":"ui_group","z":"","name":"Main","tab":"36c72615.fbcdea","order":1,"disp":true,"width":"6"},{"id":"36c72615.fbcdea","type":"ui_tab","z":"","name":"Home","icon":"fa-home","order":"1"}]
1 Like

Hi Everyone,

I am just starting out trying to figure out how to get my Rainforest Eagle-200 setup with OpenHab.

This thread has been dormant for over 2 years. Can anyone tell me if the code will work for the Eagle-200, rather than the original Eagle?

If not, any other threads you could point me to in order to set this up with their newer 200 product?

Thanks,

Jon

Your probably better off starting a new thread. When you do, post some screens shots of the setup screens. It does say there is a rest interface so it might be possible.

After looking at the actual device I see it is a replacement not a different device to the Eagle. definitely would belong here. so others can benefit from the work to make the data available to openhab.

This would be a good start.

I got a initial shot at this working. Needs a bit of clean-up, I’ll post it over the weekend.

I just tried the new binding with my legacy Rainforest Eagle. Although the binding finds my device, it will not initialize after entering the install code in the paper UI. This leads me to believe the API is different.

I guess it’s time to buy a new one!

Yep, sorry, they are different animals when it comes to the API. I don’t have the old style Eagle to test, so can’t really support it in that binding.

Cheers,
-Th

Hi. I’m trying to get this binding to work with my legacy Rainforest Eagle 100 under OH2 on an Openhabian RPI 3B+.

I’m using @LeXLuther422’s original code with the improvements suggested by @swamiller. Data seems to be coming from the Eagle but I keep getting bad data errors. I’ve posted my log and code below.

Does anyone have any thoughts on what’s going wrong?

log

2019-01-09 01:08:06.298 [INFO ] [eclipse.smarthome.model.script.eagle] - {"meter_status":"Connected",

"demand":"0.9800",

"demand_units":"kW",

"demand_timestamp":"1546996080",

"summation_received":"6286.000",

"summation_delivered":"17101.000",

"summation_units":"kWh",

"price":"0.2400",

"price_units":"840",

"price_label":"Set by User",

"message_timestamp":"946684800",

"message_confirmed":"N",

"message_confirm_required":"N",

"message_id":"0",

"message_queue":"active",

"message_read":"Y",

"threshold_upper_demand":"14.570000",

"threshold_lower_demand":"-5.300000",

"fast_poll_frequency":"0x00",

"fast_poll_endtime":"0x00000000"}

2019-01-09 01:08:06.308 [ERROR] [eclipse.smarthome.model.script.eagle] - Bad Data {"meter_status":"Connected", "demand":"0.9800", "demand_units":"kW", "demand_timestamp":"1546996080", "summation_received":"6286.000", "summation_delivered":"17101.000", "summation_units":"kWh", "price":"0.2400", "price_units":"840", "price_label":"Set by User", "message_timestamp":"946684800", "message_confirmed":"N", "message_confirm_required":"N", "message_id":"0", "message_queue":"active", "message_read":"Y", "threshold_upper_demand":"14.570000", "threshold_lower_demand":"-5.300000", "fast_poll_frequency":"0x00", "fast_poll_endtime":"0x00000000"} 

2019-01-09 01:08:06.314 [INFO ] [eclipse.smarthome.model.script.eagle] - PERF Pull-Data-from-Eagle elapsed: 1191ms

Rule

import java.lang.Float
import java.lang.Long
import org.joda.time.DateTime

var DateTime lastTimestamp = null
var float lastDelivered
var float lastReceived
    
rule "Pull Data from Eagle"
when
    Time cron "0 0-59 * * * ?"
then
    var t = now
    val String EAGLE_MAC = "0xD8D5B900000011ab"
    val String EAGLE_URL = "http://192.168.1.22/cgi-bin/cgi_manager"
    
    var String postData = String::format("<LocalCommand>
      <Name>get_usage_data</Name>
      <MacId>%s</MacId>
      </LocalCommand>", EAGLE_MAC, EAGLE_MAC)
    
    var result = sendHttpPostRequest(EAGLE_URL, "application/x-www-form-urlencoded", postData.toString, 3000)
    logInfo("eagle", result)
    try {
        var long timestamp = Long::parseLong(transform("JSONPATH", "$.demand_timestamp", result.toString))
        var DateTime currTimestamp = new DateTime(timestamp * 1000)
        var float price = Float::parseFloat(transform("JSONPATH", "$.price", result.toString))
        var float currDemand = Float::parseFloat(transform("JSONPATH", "$.demand", result.toString))
        var float currDelivered = Float::parseFloat(transform("JSONPATH", "$.summation_delivered", result.toString))
        var float currReceived = Float::parseFloat(transform("JSONPATH", "$.summation_received", result.toString))
        postUpdate(HousePowerInstant, currDemand * 1000)
    
        if (lastTimestamp !== null && !lastTimestamp.equals(currTimestamp)) {
            var float used = currDelivered - lastDelivered
            var float sent = currReceived - lastReceived
            logInfo("eagle ", String::format("Energy %s demand=%.3f received=%.3f delivered=%.3f", currTimestamp.toString,
              currDemand, used, sent)) 
            logDebug("eagle", String::format("Energy %s demand=%.3f received=%.3f delivered=%.3f", currTimestamp.toString,
              currDemand, used, sent))
            postUpdate(HouseEnergySent, sent * 1000)
            postUpdate(HouseEnergyDelivered, used * 1000)
            postUpdate(HouseEnergyCost, (used - sent) * 1000 * price)
        }
    
        postUpdate(HouseEnergyPrice, price)
    
        lastDelivered = currDelivered
        lastReceived = currReceived
        lastTimestamp = currTimestamp  
    } catch (NumberFormatException nfe) {
        logError("eagle", "Bad Data " + result.replaceAll("\n", " "))
    }
    var long x = now.getMillis - t.getMillis
    logInfo("eagle", "PERF Pull-Data-from-Eagle elapsed: " + String::valueOf(x) + "ms")
end

You do have the JSONPATH service loaded, right? I’d like to see this work because I too have an Eagle 100 which I’d like to get connected. If you do have that loaded, then my debug suggestion would be to shorten that long try{} to something much smaller and see if you can get a single data point such as the demand_timestamp. Use logInfo to push it to the log file. If that works, move on to the others. If it doesn’t, try to debug just that one.

Yep. That was the problem! It looks like it’s running ok now.

Has anyone had issues with frequent requests (every 1 minute) knocking down the EAGLE?
After a while, I am experiencing ‘stale’ data - i.e. demand values that don’t change - and then when I go to the EAGLE’s own site, instead of a green ‘Connected’, I am always redirected to the Settings page, where I see a red “Rejoining CH xx” indicator. It keeps cycling through the channels and never rejoins.

If I reboot the EAGLE, it seems to rejoin and start getting real data again.

I saw some discussion over on a Home Assistant forum that says some others have experienced this. The solution for some seemed to be to configure the EAGLE to push its data stream to a locally-configured web server, and that have web server parse the EAGLE’s HTTP POST requests and push the data into OH (or HA) over MQTT. That’s possibly an option for me but a heavy lift - I’m not confident I could reasonably quickly figure out how to set up a Python web server to list and parse the incoming stuff.

Another weird item is that I have looked at Rainforest’s API documentation, and tried several running other commands via HTTP POST request, but in general I just blank responses back, which confuses me…

My rule for pulling data out of the EAGLE is below. Any suggestions on what I might be doing wrong to knock it down?

Thanks in advance!

import java.lang.Float
import java.lang.Long
import org.joda.time.DateTime

var DateTime lastTimestamp = null
var float lastDelivered
var float lastReceived
    
rule "Pull Data from Eagle"
when
    Time cron "0 0-59 * * * ?" or
    Item ManualDataPull changed to ON
then
    // turn the manual pull off immediately
    postUpdate(ManualDataPull, OFF)
    logInfo("eagle", "Starting data pull from Eagle")

    var t = now
    val String EAGLE_MAC = "0xd8d5b9xxxxxetc"
    val String EAGLE_URL = "http://192.168.x.x/cgi-bin/cgi_manager"
    
    var String postData = String::format("<LocalCommand>
   <Name>get_usage_data</Name>
   <MacId>%s</MacId>
</LocalCommand>", EAGLE_MAC, EAGLE_MAC)
    
    var result = sendHttpPostRequest(EAGLE_URL, "application/x-www-form-urlencoded", postData.toString, 5000)
    logInfo("eagle", result)
           
    try {
        var String currStatus = transform("JSONPATH", "$.meter_status", result)
        var long timestamp = Long::parseLong(transform("JSONPATH", "$.demand_timestamp", result))
        var DateTime currTimestamp = new DateTime(timestamp * 1000)
        var float price = Float::parseFloat(transform("JSONPATH", "$.price", result))
        var float currDemand = Float::parseFloat(transform("JSONPATH", "$.demand", result))
        var float currDelivered = Float::parseFloat(transform("JSONPATH", "$.summation_delivered", result))
        var float currReceived = Float::parseFloat(transform("JSONPATH", "$.summation_received", result))
      
        postUpdate(HousePowerInstant, currDemand * 1000)
        postUpdate(LastEagleTimestamp, new DateTimeType(currTimestamp.toCalendar(null)))
        postUpdate(EagleStatus, currStatus)
    
        if (lastTimestamp !== null && !lastTimestamp.equals(currTimestamp)) {
            var float used = currDelivered - lastDelivered
            var float sent = currReceived - lastReceived
    
            logDebug("eagle", String::format("Energy %s demand=%.3f received=%.3f delivered=%.3f", currTimestamp.toString,
              currDemand, used, sent))
            postUpdate(HouseEnergySent, sent * 1000)
            postUpdate(HouseEnergyDelivered, used * 1000)
            postUpdate(HouseEnergyCost, (used - sent) * 1000 * price)
        }
    
        postUpdate(HouseEnergyPrice, price)
    
        lastDelivered = currDelivered
        lastReceived = currReceived
        lastTimestamp = currTimestamp  
    } catch (NumberFormatException nfe) {
        logError("eagle", "Bad Data " + result.replaceAll("\n", " "))
    }
    var long x = now.getMillis - t.getMillis
    //logInfo("eagle", "PERF Pull-Data-from-Eagle elapsed: " + String::valueOf(x) + "ms")
end

Do you have the Eagle 200 or 100? I had some issues with the Eagle 200 crashing. Eventually Rainforest updated the firmware and it seems to work better. Make sure you get the latest firmware. But I do not poll every minute. I think I use 10 or 15 minutes which is good enough for me.

Try your POST requests using curl first. Then you can try them in a rule. Which requests are not working?

Ah, sheesh … sorry for letting this thread die, John, and thanks for writing me back.

I have the Eagle 100 - the old one!

I am polling data at a 15 second frequency now, and generally things are stable, but it will intermittently disconnect and reconnect. Every week or so, it ‘decidedly’ disconnects and I need to power cycle the dang thing to make it reconnect.

I initially considered the inelegant solution of just putting a cheap TP-Link HS105 on its plug, and power cycling it every night at midnight … ideally to ‘flush out the demons’.

However, at least right now I am trying something a little less ‘brute force’, and am using the API to reboot the thing daily just after midnight.

For anyone who is interested, below is my rule to do so; it took a bit of goofing around with to get it right. For me, the <Target>All</Target> option in the reboot command didn’t work; it returned a <Text>not available</Text> message, so I have split up the reboots into Linux side first, then Zigbee side:

rule "Reboot Eagle"
when
	Item RE_Reboot_Flag changed to ON or	// on manual reboot flag from sitemap, or
	Time cron "15 1 0 * * ?" 				// not long after midnight..
then
	
	// reset reboot flag
	RE_Reboot_Flag.postUpdate(OFF)
	
	var t = now
	
	//val String EAGLE_MAC = "0xd8d5b9000000xxxx"
	val String EAGLE_URL = "http://192.168.x.x/cgi-bin/post_manager"	

	// verified that the below Command works when pointed to above post_manager URL..
	// cgi_manager URL used in other API calls does NOT work for the reboot..
	var String postData = String::format("<Command>		
	<Name>reboot</Name>
	<MacId>0xd8d5b9000000xxxx</MacId>
	<Target>Eagle</Target>
	</Command>")
	logDebug("eagle","Sending reboot command to Eagle Linux.")       
	var result = sendHttpPostRequest(EAGLE_URL, "application/x-www-form-urlencoded", postData.toString, 5000)      
	try {
		logInfo("RainEagle Reboot", "Eagle Linux reboot command returned: " + result.toString())		
	} catch (NumberFormatException nfe) {
		logError("eagle", "Bad Data on Eagle Linux reboot cmd" + result.replaceAll("\n", " "))
	}

	var long x = now.getMillis - t.getMillis
	logInfo("eagle", "PERF Reboot Eagle Linux elapsed: " + String::valueOf(x) + "ms")

	// wait a while before rebooting the other side of the RainEagle..
	createTimer(now.plusSeconds(85))
	[
		var t = now
		
		// verified that the below Command works when pointed to above post_manager URL..
		// cgi_manager URL used in other API calls does NOT work for the reboot..
		var String postData = String::format("<Command>		
		<Name>reboot</Name>
		<MacId>0xd8d5b9000000xxxx</MacId>
		<Target>Zigbee</Target>
		</Command>")
		logDebug("eagle","Sending reboot command to Zigbee.")       
		var result = sendHttpPostRequest(EAGLE_URL, "application/x-www-form-urlencoded", postData.toString, 5000)      
		try {
			logInfo("RainEagle Reboot", "Zigbee reboot command returned: " + result.toString())		
		} catch (NumberFormatException nfe) {
			logError("eagle", "Bad Data on Zigbee reboot cmd" + result.replaceAll("\n", " "))
		}

		var long x = now.getMillis - t.getMillis
		logInfo("eagle", "PERF Reboot Eagle Zigbee elapsed: " + String::valueOf(x) + "ms")

	]

end

That’s an interesting solution. I have never had any problem with the Eagle 100, but I don’t poll nearly so often.

By the way, it’s not hard with nodejs to create a server to accept the POST requests and send them off to openHAB. I did this for PurpleAir and I’m doing it as well for the Eagle 200. I used the binding for a while with the Eagle 200, but it blasts out an error message every night (I think this is when the Eagle phones home or does some maintenance). And I didn’t see much need for the binding since it is only collecting data, not really interacting with the device.

The trick with the Eagle 100 is that the data looks like it will be in XML, and the instructions for an uploader are not as extensive as for the 200. An XML to JSON parser is pretty easy to find, and I think you could make it work, but doesn’t look like there is any need now as you have something that works for you.

Yeah, I convinced myself I really wanted to be able to see and historize near-real-time power usage data, hence the fast poll times.

(I also pull high frequency data from my solar inverter via Modbus TCP, so it is nice to be able to see what I am generating vs. using vs. drawing from the grid at any given moment.)

Thanks for the encouragement on the nodejs server to consume POST requests; if I run into future issues I may consider it.

There is existing Rules code out there for http requests to the Eagle 100 that was a good starting point for me, and from what I’ve read, the binding is no good for the 100 model anyway.

Quick update - my Eagle 100 seems to have more definitely died on me. I did end up sticking a wifi plug on it to power cycle it once per day and also on demand. Eventually the Eagle simply stopped connecting to the power meter altogether, even after a power cycle. The local web pages don’t even work properly, although it seems they “sort of try” since my browser shows an grey gradient background and then seems to hang.
Due to COVID I haven’t been physically at that house since March, but will be back soon and may try some sort of hard reset if it exists. This meter is operated by Centerpoint in Texas, and I have read that Centerpoint has stopped supporting the ability to interact with meters over Zigbee, so I may be SOL here.
At this point I’ve ordered an OpenEnergyMonitor Arduino shield and am planning to fall back to “good old physics” to measure my power consumption.

I had the same problem in Texas with Oncor no longer supporting HEM’s. I moved to a zwave based monitor.