[PROJECT] Driver for Ambient API/Local and Ecowitt

Assuming you have the latest version of the drivers... That error would prevent it from updating because that is in the steps to get the data ready for processing for an Ambient Local (it also proves you have the Ambient set up to go to the correct place). So... now on to troubleshooting:

  1. On the Ambient side (just to confirm the Custom/Customized destination), does it have the correct path and protocol? It should be trying to send it to /data/report as an Ambient method (sometimes labeled AMBWeather).
  2. Also on the Ambient side, have you checked for any firmware updates for your Ambient system?
  3. Assuming neither of those help... there could be a "flaw" or oddity in the reported data that I have not handled yet (not sure how it would not have crept up before this but it happens). Could you set the parent device to "Trace" logging and send me the resulting logs (in a separate message, not posted to the thread) when it receives the data from the weather station? They should start with:
    Raw data =
    Ambient Local Headers =
    Ambient Local rawData Step 1
    Ambient Local rawData Step 2 =

Maybe I can spot something in there and account for it going forward.

As a general note, I am using Ambient Local also (I actually still have mine working after all these years although I had to do another battery replacement this winter). I have an Ambient WS-2902A and the reported firmware I see in the WSView app is Version 4.3.4.

Thank you @snell for the information. They are great weather stations. Upgraded a few years ago to the ws-5000.

The driver is version 0.8.8 and use Package Manager to update it.

The weather station was on v1.7.4 and now updated to the latest 1.9.0.

The trace logs are enabled but still only showing the same error.
2024-02-09 08:14:46.780 PMerrorjava.lang.ArrayIndexOutOfBoundsException: 1 on line 641 (method parse)

That is strange because the "Raw data" should be posted to the log (when Trace logging is enabled) BEFORE it gets to the function that is getting the error. In fact, there should be multiple entries in the log:
Raw data...
Ambient Local Headers...
Ambient Local rawData Step 1...
Ambient Local rawData Step 2...

THEN it gets to the parseURLParameters which contains line 641...

Considering the error and the portion of code, my guess is that a data point is being returned that has a name but no value (ex: "lightning_time="). But without seeing the raw data returned I cannot confirm that.

@bertabcd1234: This is actually a section of code you provided me. Do you have any recommendation for what might be happening to cause @dmorehead1988's error? For reference (it has been a while) I have copied the code below, and the error is happening at the line that corresponds to URLDecoder.decode(it.split("=")[1])] })

// parses parameters received. This code is from @bertabcd1234
Map parseURLParameters(String parameters) {
      List keysAndVals = parameters.contains("&") ? parameters.split("&") : []
      Map data = keysAndVals.collectEntries( { [(URLDecoder.decode(it.split("=")[0])):
                                             URLDecoder.decode(it.split("=")[1])] })
      return data
}

Updated Version(s):

  • AmbientEcowittWeather.groovy = 0.8.9

Change(s):

  • Added a check for when Ambient Local data is provided. It turns out that in at least one instance (per @dmorehead1988's issue) the station can return the data with an unexpected data separator. Basically, it was adding an "&" at the beginning of the data (the & is used in the data to separate between different returned fields, so the extra was causing things to break). A check has been added to remove the extra character before continuing to process the data normally.

Updated Version(s):

  • AmbientEcowittWeather.groovy = 0.8.10
  • WeatherSensorChild.groovy = 0.8.14

Change(s):

  • Correction in the ProcessEvent function in the drivers.
  • Replaced driver-specific attributes "Device Name", "Device Version", and "Device Status" with ones that do not have spaces. They will be removed from the device the next time Save Preferences is run.

After changing my EcoWitt Wittboy GW2001 from wired to wireless, I had lost the connection to Hubitat (the MAC address changed). After updating that and getting things working again, I thought I would add an evaluation based on the RainRate reported by my GW2001 represented by the state variable rrain_piezo. It didn't work. The value returned for the RainRate attribute (and, in fact, every rain related attribute) was a null.


Looking at the AmbientEcowittWeather.groovy, it looks like the rain related state variables from my GW2001 aren't handled anyplace. As a test I added some lines of my own following your formatting to get a value, i.e.,
attribute "rrain_piezo", "number" up where you declare attributes
and
case "rrain_piezo":
ProcessState( "rrain_piezo", it.value as float )
if( MeasurementStandard == "Metric" ){
ProcessEvent( "RainRate", ConvertInches( "Imperial", state.rrain_piezo ), "mm" )
PostEventToChild( "Outdoor Station", "RainRate", state.RainRate, "mm" )
} else {
ProcessEvent( "RainRate", state.rrain_piezo, "in" )
PostEventToChild( "Outdoor Station", "RainRate", state.RainRate, "in" )
}
break
// case "rainratein":
// ProcessState( "rainratein", it.value as float )
// if( MeasurementStandard == "Metric" ){
// ProcessEvent( "RainRate", ConvertInches( "Imperial", state.rainratein ), "mm" )
// PostEventToChild( "Outdoor Station", "RainRate", state.RainRate, "mm" )
// } else {
// ProcessEvent( "RainRate", state.rainratein, "in" )
// PostEventToChild( "Outdoor Station", "RainRate", state.RainRate, "in" )
// }
// break
(I commented out rainratein since I was reusing RainRate).

Is this correct? If so, can we add the rain related values for the GW2001 to the attributes handling?

I will have to look and try it this evening, but I assume if it shows up for you that you added it correctly. I am certainly happy to add in additional data.

As I do not have every variable possible if I have not seen them myself or been informed of it. It you set the parent device to Debug logging, most of my drivers will show in the log if there are any "unhandled" variables so I can add them in.

As for the null values... I have found that with the Ecowitt, if you have not had any rain (or lightning) since when a particular method was started (in this case the new MAC) it generally will leave the value null.

Updated Version(s):

  • AmbientEcowittWeather.groovy = 0.8.11

Change(s):

  • Changed the RainRate portion to allow for using the rrain_piezo if that data point is returned instead. This should not impact anyone whose system does not report it. No new attribute was needed because only the already-existing RainRate was sent as an event to the parent and child device. Thanks to @MajorEvent for pointing out this variable.

Thanks for updating to include the rain rate for the WS90. The GW2001 is a combination of the GW2000 hub and the WS90 7 in 1 sensor (array). I mention this distinction because I foresee a potential conflict. The GW2000 is compatible with the full range of Ecowitt sensors including other rain guages.

As further evidence of the potential for conflict, I found this in the Ecowitt wiki under the GW2000

The GW2000 console (gateway) (firmware 2.1.1, 03 March 2022) can also display a classic rain sensor (WH65, WH40) synoptically when using a WS90 combo sensor array with haptic (piezoelectric) rain gauge.

The null values were only for the attributes because they aren't handled for the WS90 _piezo data. The values are present in the state variables.

Updating the ,groovy, I'm able to get all the values into attributes.

I declared new variables:

		// WS90 7 in 1 Sensor
        // WS90 Attributes that have been created for conversion between measurement standards
        attribute "rRate", "number"
        attribute "hRain", "number"
        attribute "dRain", "number"
        attribute "wRain", "number"
        attribute "mRain", "number"
        attribute "yRain", "number"
        attribute "eRain", "number"
		// WS90 Attributes for returned data
        attribute "rrain_piezo", "number"
        attribute "hrain_piezo", "number"
        attribute "drain_piezo", "number"
        attribute "wrain_piezo", "number"
        attribute "mrain_piezo", "number"
        attribute "yrain_piezo", "number"
        attribute "erain_piezo", "number"

Added these new cases and returned case "rainratein" to it's original state

				case "erain_piezo": //WS90
					ProcessState( "erain_piezo", it.value as float )
					if( MeasurementStandard == "Metric" ){
						ProcessEvent( "eRain", ConvertInches( "Imperial", state.erain_piezo ), "mm" )
						PostEventToChild( "Outdoor Station", "eRain", state.eRain, "mm" )
					} else {
						ProcessEvent( "eRain", state.erain_piezo, "in" )
						PostEventToChild( "Outdoor Station", "eRain", state.eRain, "in" )
					}
					break
				case "rrain_piezo": //WS90
					ProcessState( "rrain_piezo", it.value as float )
					if( MeasurementStandard == "Metric" ){
						ProcessEvent( "rRate", ConvertInches( "Imperial", state.rrain_piezo ), "mm" )
						PostEventToChild( "Outdoor Station", "rRate", state.rRate, "mm" )
					} else {
						ProcessEvent( "rRate", state.rrain_piezo, "in" )
						PostEventToChild( "Outdoor Station", "rRate", state.rRate, "in" )
					}
					break
				case "hrain_piezo": //WS90
					ProcessState( "hrain_piezo", it.value as float )
					if( MeasurementStandard == "Metric" ){
						ProcessEvent( "hRain", ConvertInches( "Imperial", state.hrain_piezo ), "mm" )
						PostEventToChild( "Outdoor Station", "hRain", state.hRain, "mm" )
					} else {
						ProcessEvent( "hRain", state.hrain_piezo, "in" )
						PostEventToChild( "Outdoor Station", "hRain", state.hRain, "in" )
					}
					break
				case "drain_piezo": //WS90
					ProcessState( "drain_piezo", it.value as float )
					if( MeasurementStandard == "Metric" ){
						ProcessEvent( "dRain", ConvertInches( "Imperial", state.drain_piezo ), "mm" )
						PostEventToChild( "Outdoor Station", "dRain", state.dRain, "mm" )
					} else {
						ProcessEvent( "dRain", state.drain_piezo, "in" )
						PostEventToChild( "Outdoor Station", "dRain", state.dRain, "in" )
					}
					break
				case "wrain_piezo": //WS90
					ProcessState( "wrain_piezo", it.value as float )
					if( MeasurementStandard == "Metric" ){
						ProcessEvent( "wRain", ConvertInches( "Imperial", state.wrain_piezo ), "mm" )
						PostEventToChild( "Outdoor Station", "wRain", state.wRain, "mm" )
					} else {
						ProcessEvent( "wRain", state.wrain_piezo, "in" )
						PostEventToChild( "Outdoor Station", "wRain", state.wRain, "in" )
					}
					break
				case "mrain_piezo": //WS90
					ProcessState( "mrain_piezo", it.value as float )
					if( MeasurementStandard == "Metric" ){
						ProcessEvent( "mRain", ConvertInches( "Imperial", state.mrain_piezo ), "mm" )
						PostEventToChild( "Outdoor Station", "mRain", state.mRain, "mm" )
					} else {
						ProcessEvent( "mRain", state.mrain_piezo, "in" )
						PostEventToChild( "Outdoor Station", "mRain", state.mRain, "in" )
					}
					break
				case "yrain_piezo": //WS90
					ProcessState( "yrain_piezo", it.value as float )
					if( MeasurementStandard == "Metric" ){
						ProcessEvent( "yRain", ConvertInches( "Imperial", state.yrain_piezo ), "mm" )
						PostEventToChild( "Outdoor Station", "yRain", state.yRain, "mm" )
					} else {
						ProcessEvent( "yRain", state.yrain_piezo, "in" )
						PostEventToChild( "Outdoor Station", "yRain", state.yRain, "in" )
					}
					break
				case "rainratein":
					ProcessState( "rainratein", it.value as float )
					if( MeasurementStandard == "Metric" ){
						ProcessEvent( "RainRate", ConvertInches( "Imperial", state.rainratein ), "mm" )
						PostEventToChild( "Outdoor Station", "RainRate", state.RainRate, "mm" )
					} else {
						ProcessEvent( "RainRate", state.rainratein, "in" )
						PostEventToChild( "Outdoor Station", "RainRate", state.RainRate, "in" )
					}
					break

I was not aware of all these new piezo-based data points until you mentioned it. I will get them all added in tonight though, so thank you for letting me know about them. They also need to be added to the WeatherSensorChild. All those "PostEventToChild" calls will not "stick" on the child device because the attributes do not exist there either.

As for conflicts... what will happen right now is that the parent device's RainRate would get overwritten by each sensor as data is received. However, the individual child sensors would have their individual values. So, if someone wanted to pick a particular one to use they would not have "overlap" of the data. I will have to build something in to let people set which station the parent's data should go to in case of conflicts, up until now it has not been an issue but a similar oe came up for my WeatherFlow driver. That change will not be in tonight.

I'm guessing that because of the capability to run "synoptically" with other (traditional) rain guages that we get all these additional data points. I admit I took the easy way of avoiding conflicts by assigning a bunch of new variables. (Edit: Ecowitt is a bit unclear about whether it's an accuracy issue that you would need to add a traditional guage)

Since RainRate etc, are all "Attributes that have been created for conversion between measurement standards", I guess there wouldn't be any conflicts but since I am only casually looking at the code, it was safer to use the new variables.

I hadn't looked closely at the child; is that adding the new attribute variables?

Since there's no overlap, conflicts would seem like a low probability and correspondingly low priority. I don't know of any other situation in which there's these extra data points covering the same information either so it may not be an issue at all.

Updated Version(s):

  • AmbientEcowittWeather.groovy = 0.8.12
  • WeatherSensorChild.groovy = 0.8.18

Change(s):

  • Additional data points for the GW2001 (based on the data sent from @MajorEvent) have been added into the parent driver. These were all able to be aligned with existing Events (ex: yrain_piezo matches up with yearlyRain) so no new attributes were needed for those.
  • A large number of items were reworked to not set a state variable, then re-reference that state variable to set other things immediately afterwards in case their was any timing issues happening on the hub and a value did not get written before the read was attempted. Instead they have now been rewritten to reuse the data point (generally it.value) that was being used for setting the state in the first place. Long story short, it makes much of the code more consistent AND it should reduce the possibility of errors with the states.
  • LastUpdateString and LastUpdateEpoch were added for the parent device and child devices. For the parent these populate when the data finishes processing. Child devices will have these set when their battery value is reported (so it is not every single data point and updated an absurd number of times). String contains the human-readable date/time. Epoch is the number representing how many seconds since 1970... basically a common programming-centric method of tracking time that can be easily compared if needed.

:+1: Thanks for adding those extra data points!

I am having an issue with the device. The device driver has updated to 0.8.12, but the device still says 0.8.10. I am using the Ambient API method. I can still see the weather station update on its device webpage via Ambientweather.net.

Can anyone help troubleshoot what needs to happen to get it communicating again?

Main Device
image
image

Child Device
image

Drivers Modified
8/21/2024 8:57:53 pm

image

Good afternoon,

Installed today via HPM, I have the AmbientEcowittWeather and WeatherSensorChild drivers, but I do not have an accompanying app. What did I do wrong?

@evanandmaryanne:
The driver version & status can take up to 24 hours (when it does the next check) to "correct" itself because there is no way for it to know a driver has changed. You can "force" it by doing Save Preferences in a driver (I have it update the version then to whatever is in the code, but I forgot to clear the status... that is coming in 0.8.13). As for communicating... have you rebooted your hub lately?

@starckie:
There is no accompanying app for these drivers. None of my drivers have apps for them, they are purely meant to be device drivers that you check the information on the Devices page for, or use the attributes in Dashboards, Rule Machine, Maker API, whatever else. So you did nothing wrong. The parent driver has instructions for how to configure the device for whichever method you want to get your weather station data.

Updated Version(s):

  • WeatherSensorChild.groovy = 0.8.20

Change(s):

  • Complete rewrite to how Tile is handled within the WeatherSensorChild driver (I will have to update child drivers in other projects over time). In my testing on my main-version Hubitat it works fine with spaces in the name... but what you need to do now is structure the Preference differently. You can use the [ ] method to have that replaced with HTML still, but for a variable name you need to surround them with @.

Couple examples would be:

Temperature: @temperature@ Should provide you with the current temperature.

Wind Direction= @Wind Direction@ Should provide the wind's direction.

It also handles three of the Hubitat location functions (latitude, longitude, and temperatureMeasurement) if you use:
@location.latitude@
@location.longitude@
@location.getTemperatureScale()@

Known Issue(s):

  • It is not working properly with Beta Hubitat software (HTML formatting or names with spaces), so no worse than present as the current method does not work with the beta either. I am trying to come up with workarounds.
1 Like

Every since I got my WS-2000, the local API has been reporting a value for batt_co2=1 even though I do not have an any Air Quality/CO2 sensors. E.g.,

Ambient Local rawData Step 1 = /data/report/stationtype=AMBWeatherV4.3.4&PASSKEY=HEX:DIGITS&dateutc=2025-02-10+17:35:20&tempinf=67.3&battin=1&humidityin=42&baromrelin=29.950&baromabsin=29.950&tempf=30.6&battout=1&humidity=54&winddir=287&winddir_avg10m=11&windspeedmph=0.4&windspdmph_avg10m=2.9&windgustmph=8.1&maxdailygust=12.5&hourlyrainin=0.000&eventrainin=0.000&dailyrainin=0.000&weeklyrainin=0.000&monthlyrainin=0.299&yearlyrainin=2.449&solarradiation=140.40&uv=1&soilhum1=0&soilhum2=0&battsm1=1&battsm2=1&temp1f=32.2&humidity1=50&temp2f=34.9&humidity2=45&temp3f=41.9&humidity3=80&temp4f=34.7&humidity4=67&batt1=1&batt2=1&batt3=1&batt4=1&batt_co2=1

You can see there are no other aqi_, co2_, or pm_ values. None-the-less, this causes the AmbientEcowittWeather driver to populate the Air Quality Sensor child device because of this code:

				case "batt_co2":
					ProcessEvent( "Air Quality Sensor Battery", BatteryValue( it.value as int ) )
					PostEventToChild( "Air Quality Sensor", "battery", BatteryValue( it.value as int ) )
                    PostEventToChild( "Air Quality Sensor", "LastUpdateString", new Date(), null, true )
                    PostEventToChild( "Air Quality Sensor", "LastUpdateEpoch", new Date().getTime(), null, true )
					break

I keep commenting out the PostEventToChild in this code. I wonder if there is some better way to handle this.

Not exactly sure how I could... that value is supposed to be for the co2 related sensor, ie: the Air Quality one. As the driver iterates through the data it does not know that there are no other data points for that sensor. I suppose it could be possible to work out a way to look for them, as a validation, when this specific field is showing up.

While it creates another child device, it should not be causing any problems otherwise. You could probably just leave it existing until I make an update to try to hide it going forward. My biggest worry would be adding code to ignore it, but having someone else have one that maybe is not reporting because the battery is dead. I have an Air Quality Sensor myself, but I do not have a WS-2000. It seems very strange on their API side to provide it... wonder why they chose to and what it might be representing otherwise.

I see battout, that should represent the outdoor station's battery status. Is there any chance that batt_co2 might have been co-opted for them to report the display's battery status? If you do not mind, do you happen to have a set of batteries that are really weak that you could swap in to the display to see if that changes?

A bit of digging online did not really come to any conclusion from the results I saw. You are not the only person with a WS-2000 that has seen a batt_co2 show up in the data (from online sources, this is the first I have heard of). One person thought it might represent solar vs battery power... another person thinks that the station is seeing "someone's" Air Quality sensor, but not a good enough signal from it to actually use, etc...

It's always reported "batt_co2=1" from the day I first set it up (you can see it in my original post on this thread).

I didn't think it was one of the phantom sensors I pick up from my neighbor (e.g., a lighting sensor and some leak sensors). I just checked the console and I did have something listed under IN PM2.5. I disabled it and even rebooted the console, but I still see the batt_co2=1 value being reported.

If I'm not the only person to have reported this, perhaps just not having those three calls to PostEventToChild would be a reasonable change -- someone can always check the battery value for an actual co2 sensor via the Air Quality Sensor Battery state on the Ambient station device.

1 Like