[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!