Skip to content

Commit

Permalink
Fix tile labels, add sunrise/sunset and report sensor failure (#285)
Browse files Browse the repository at this point in the history
* Add support to report failing Tempest sensors

Fix #279
Fully parse the sensor failure number, and log the failure.
Add support to report SensorFault to HomeKit. There are only two options, No failure or General Failure.

Tested by setting random numbers for message.sensor_status, and verifying that failures appeared in the log, and that Eve app reports general error to the user. Apple's Home app doesn't seem to expose Sensor Fault.

* Ignore other values from sensor_status

#279
While testing, I noticed that sometimes my Tempest unit would have values in the sensor_status that are not documented. The documentation does say to ignore them.

* Update Tile text with value

#247
The value for unsupported fields were being reported in the title of a sensor. It seems this functionality broke in iOS 16, when they changed the behaviour of Characteristic.Name. To fix this, we need to use Characteristic.ConfiguredName.

* Drop duplicate messages

Seeing duplicate UDP messages events, though duplicates are not appearing on the wire/network. Duplicate messages are a problem for rain totals, as the rain amount is added twice.
Add some code to drop duplicate messages.

* Add Openweather Sunrise & Sunset support

#240 Sunrise/Sunset
Openweather API 2.5 has Sunrise and Sunset values. This patch adds support for it.
I also saw that 3.0 API also has support for it for the current conditions. This patch also adds support for that too.

* Update config.schema.json

Fix for #122

* Add Tempest forecast support

Add the support for Tempest weather station forecasts. The weather station broadcasts new current conditions each minute so the refresh interval is hardcoded to 1 minute. However forecasts don't update that frequently, so I have forecast updating each hour.

* Improve error reporting for OpenWeatherMap fails

To help diagnose #282 and to help with general debugging,  log not just the result but the error when OpenWeatherMap fails to load the URL.

* Add light level history

Fix #287
Through fakegato "custom" history support, the light level (lux) history is now supported.
Adjust history logging to include light level.

Tested with Tempest weather station and OpenWeatherMap V2.5 API (that does *not* include light level).

* Remove unsupported Yahoo weather.

#45
Yahoo doesn't provide weather information anymore. Support for the Yahoo weather service has been removed, so remove the api file.

Update the packages.json file, remove yahoo. Add the company that makes the Tempest weather station, Weatherflow.

* Add better error handling to Weather Underground.

Debug #246
Add a check to see if WeatherUnderground it returning correctly, but the PWS is offline.

* Increase maximum UV Index value

Australia's outback has higher UV Index values than 12. I see it hit 13 at times.

* Add rain sensor for weather underground

Fix: #112
Add support for the rain sensor (RainBool). If the current precipitation rate is greater than zero, then it is raining.

Also, always use metric values. Conversion from metric to desired unit happens during rendering the value.

Tested with both an offline PWS and online PWS.

* Report values even for sensor failed

Fix #288
Even if the sensor has been reported as failed, it has been observed that values may still be good. As the values could be out of range, don't use them for calculations.

* Calculate values only if numbers in range

#285 Patch feedback. Calculate the calculated values only if temperature and humidity values are within range. The values may be out of range if the sensors have failed.

* Log sensor failures once an hour

#285. Patch feedback
Log is spammed if a sensor fails, so only log once every hour.

Also, don't need to track Temperature and Humidity sensor failures specifically anymore as calculations are bounds checked.

* Revert "Log sensor failures once an hour"

This reverts commit e8ca350.

* Log sensor failures once an hour

#285. Patch feedback
Log is spammed if a sensor fails, so only log once every hour. As sensor status for AIR unit could be ok, but the sensor status for SKY unit be bad, I need to track sensor logging separately. I also overload the AIR unit to report Tempest unit data.

Also, don't need to track Temperature and Humidity sensor failures specifically anymore as calculations are bounds checked.

* Fix forecasts to be error resilient

#285  Patch feedback
Fix crash when the apiKey and/or stationId is missing. Use the lastForecastUpdate variable to indicate if we have valid parameters at all. If they are not set, don't define the variable, and as such never attempt to fetch a forecast. This is a valid configuration so I don't log this state.
If invalid apiKey and/or stationId is used, then the URL request will fail with a response code other than 200 (OK). If that happens, fail with an error. This error will get logged. As the forecast is only fetched every hour, this error will only appear once an hour and won't spam the logs.

* Sensor error string format

#285  Patch feedback
Add better formatting for sensor error logs, so that each failing sensor is comma delimited.
Also fix range checking for calculated values, allow temperatures to be between -100 to +100 degC.

* Fix battery reporting for AIR/SKY

#285  Patch feedback
Move battery reporting/handling to one place, in the observation messages. Tempest handling was already correct. Add reporting for AIR/SKY battery level. As the levels can be different but we only have one battery value we can report, report the lowest one.
Change AIR/SKY battery percentage calculator to be the same as Tempest.

* AIR/SKY battery level never goes up

I should not compare to the previous reported value, as it will never increase. I need to compare Sky battery with Air battery and vice versa when determining the lowest value to report.

* Increase robustness of Forecast

Patch feedback #285
Even though the server says it returned forecast ok with a 200 OK status code, parsing sometimes fails and crashes. Catch this error and print it out if it happens, including the full received result and the parse error.

* Forecast not updating

Patch feedback #285
Forecast wasn't updating when it clocked over to the new year. The code was testing for an undefined variable incorrectly.

* Forecast said there will be rain when there won't be

Patch feedback #285
Should not use the precipitation icons for snow and rain state as the chance precipitation can be zero, but there are still icons.
Instead do the same as OpenWeatherMap, normalize the conditions to the condition details numbered values, then check to see if there will be rain or snow.

* Fix crash when Wunderground server unreachable

There could be no response object if the internet and/or weather underground server is not reachable. So before trying to read the response from the server, make sure that there is a response.

* Fix today's forecast

Today's forecast isn't sent after around 11pm.
To detect this, make sure that the current month day is the same as the month day of the forecast. If they are different, then offset the forecasts.
Save the current day forecast, so that when it is not available, the last forecast can be inserted into the forecasts array.
  • Loading branch information
dacarson authored Mar 18, 2024
1 parent 0792301 commit 8d361d3
Show file tree
Hide file tree
Showing 12 changed files with 476 additions and 324 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,17 @@
## 3.3.3
* Fix Tempest weather station breakage introduced in 3.3.2
* Fix Rain accumulation

## 3.3.4
* Add support to detect failing sensors on Tempest weather station and report failure HomeKit
* Fix tile naming for Home app so it contains name and value
* Add support for Sunrise and Sunset for current conditions and forecast with OpenWeather v2.5 (Legacy/Free) API
* Add support for Sunrise and Sunset for current conditions with OpenWeather v3.0 OneCall API
* Fix configuration UI to save the threshold values
* Add support for Forecasts with Tempest weather station.
* Add extra error logging when OpenWeatherMap URL load fails
* Add support for light level history
* Add error handling to WeatherUnderground to indicate if PWS is offline
* Increase max UV Index value as UV Index values in Australia's outback goes higher than 12
* Add Rain sensor support to WeatherUnderground
* Fix units in WeatherUnderground so that values are not converted multiple times
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,18 @@ This plugin supports multiple weather services. Each has its own advantages. The
| | OpenWeatherMap (recommended) | Weather Underground <sup>[2](#a2)</sup> | Tempest weather station <sup>[7](#a7)</sup> |
|----------------------------|:-----------------------------------------------------:|:----------------------------------------------------:|:----------------------------------------------------:|
| Current observation values | 15 | 12 | 20 |
| Forecast values | 18<sup>[6](#a6)</sup> | 0 | 0 |
| Forecast days | today + 7<sup>[6](#a6)</sup> | 0 | 0<sup>[8](#a8)</sup> |
| Forecast values | 18<sup>[6](#a6)</sup> | 0 | 10 |
| Forecast days | today + 7<sup>[6](#a6)</sup> | 0 | today + 9 |
| Location | city name, geo-coordinates | station id | local |
| Personal weather stations | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Free | :heavy_check_mark: | :heavy_check_mark: (only if you own a station) | :heavy_check_mark: (you need the station) |
| Register | [here](https://home.openweathermap.org/users/sign_up) | [here](https://www.wunderground.com/member/api-keys) | - |
| Register | [here](https://home.openweathermap.org/users/sign_up) | [here](https://www.wunderground.com/member/api-keys) | [here](https://tempestwx.com/settings/tokens) |

*You can add more services easily by forking the project and submitting a pull request for a new api file.*

> <b name="a2">2</b> You can use the weather underground service only if you can provide weather data from your own station in exchange.
> <b name="a6">6</b> uv-index, dew point, sunrise, sunset are only available after registering for the new OpenWeatherMap One Call API 3.0, which is free as well. The old API also has 4 instead of 7 forecast days.
> <b name="a7">7</b> [Weatherflow's Tempest](https://weatherflow.com) is a physical weather station that can be installed in your home. Current weather conditions are published on home network.
> <b name="a8">8</b> Weatherflow does provide an API to retrieve forecast. That capability has not been added to this plugin.
> <b name="a7">7</b> [Weatherflow's Tempest](https://weatherflow.com) is a physical weather station that can be installed in your home. Current weather conditions are published on home network.
## Installation

Expand Down Expand Up @@ -171,13 +170,21 @@ Used to indicate Weewx version. The homekit plugin requires an location field t

### Tempest Weatherflow

The [Tempest Weatherflow](https://weatherflow.com/tempest-home-weather-system/) is a local weather reporting device that publishes the current weather on the local network via [UDP packets](https://weatherflow.github.io/Tempest/api/). Data is broadcast once per minute, so the Interval setting is ignored. The physical station can only provide the current weather. Future forecasts are available with this weather source, though has not been implemented yet. This uses data published on your local network for the current weather, and therefore runs fine without an internet connection.
The [Tempest Weatherflow](https://weatherflow.com/tempest-home-weather-system/) is a local weather reporting device that publishes the current weather on the local network via [UDP packets](https://weatherflow.github.io/Tempest/api/). Data is broadcast once per minute, so the Interval setting is ignored. The physical station can only provide the current weather. This uses data published on your local network for the current weather, and therefore runs fine without an internet connection. Future forecasts are available from Tempest for your weather station using a Personal Use Token and your station id.

**key**
(Optional - only needed for Forecasts) The API key that you get by logging into your [Account](https://tempestwx.com/settings/tokens) and creating a personal use token.

**stationId**
(Optional - only needed for Forecasts) Your personal StationID. Viewing your [station settings](https://tempestwx.com/settings/station/), it is the number on the end of the URL.

```json
"platforms": [
{
"platform": "WeatherPlus",
"service": "tempest"
"service": "tempest",
"key": "PERSONAL_USE_TOKEN",
"stationId": "STATION_ID"
}
]
```
Expand Down Expand Up @@ -338,3 +345,4 @@ This plugin is a fork of [homebridge-weather-station](https://github.com/kcharwo
## Attribution
- [Powered by Weather Underground](https://www.wunderground.com/)
- [Powered by OpenWeatherMap](https://openweathermap.org/)
- [Powered by WeatherFlow - Tempest](https://tempest.earth/tempest-home-weather-system/)
13 changes: 11 additions & 2 deletions accessories/currentConditions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function CurrentConditionsWeatherAccessory(platform, stationIndex)
this.name = this.config.nameNow;
this.displayName = this.config.nameNow;
this.stationIndex = stationIndex;
this.services = [];

// Use homekit temperature service or eve weather service depending on compatibility setting
this.log.debug("Using compatibility mode '%s'", this.config.compatibility);
Expand Down Expand Up @@ -60,6 +61,7 @@ function CurrentConditionsWeatherAccessory(platform, stationIndex)
});
}
}
this.services.push(this.CurrentConditionsService);


// Add all current condition characteristics that are supported by the selected api
Expand Down Expand Up @@ -128,13 +130,18 @@ function CurrentConditionsWeatherAccessory(platform, stationIndex)
{
this.CurrentConditionsService.addCharacteristic(Characteristic.ChargingState);
}
else if (name === "StatusFault")
{
this.CurrentConditionsService.addCharacteristic(Characteristic.StatusFault);
}
// Add everything else as a custom characteristic to the temperature service
else
{
this.CurrentConditionsService.addCharacteristic(CustomCharacteristic[name]);
}
}
});
this.services.concat(compatibility.getServices(this));

// Create information service
this.informationService = new Service.AccessoryInformation();
Expand All @@ -143,9 +150,11 @@ function CurrentConditionsWeatherAccessory(platform, stationIndex)
.setCharacteristic(Characteristic.Model, this.platform.stations[stationIndex].attribution)
.setCharacteristic(Characteristic.SerialNumber, this.config.serial)
.setCharacteristic(Characteristic.FirmwareRevision, version);
this.services.push(this.informationService);

// Create history service
this.historyService = new FakeGatoHistoryService("weather", this, this.config.fakegatoParameters);
this.historyService = new FakeGatoHistoryService("custom", this, this.config.fakegatoParameters);
this.services.push(this.historyService);
}

CurrentConditionsWeatherAccessory.prototype = {
Expand All @@ -170,6 +179,6 @@ CurrentConditionsWeatherAccessory.prototype = {

getServices: function ()
{
return [this.informationService, this.CurrentConditionsService, this.historyService].concat(compatibility.getServices(this));
return this.services;
}
};
46 changes: 29 additions & 17 deletions apis/openweathermap.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class OpenWeatherMapAPI
'Rain1h',
'RainBool',
'SnowBool',
'SunriseTime',
'SunsetTime',
'Temperature',
'TemperatureApparent',
'UVIndex',
Expand Down Expand Up @@ -106,10 +108,12 @@ class OpenWeatherMapAPI
this.getWeatherData(this.apiBaseURL + "/data/2.5/forecast", (error, result) =>
{
if (!error) {
this.generateForecasts(weather, result["list"], result["city"]["timezone"], callback);
// Pass the entire "city" JSON array as it has both the timezone and sunrise & sunset values
this.generateForecasts(weather, result["list"], result["city"], callback);
} else {
that.log.error("Error retrieving weather Forecast from API 2.5");
that.log.error(result);
that.log.error("Error retrieving OpenWeatherMap Forecast from API 2.5");
that.log.error("Error result: " + result);
that.log.error("Error message: " + error);
callback();
}
});
Expand All @@ -131,22 +135,22 @@ class OpenWeatherMapAPI
this.removeCharacteristic(this.reportCharacteristics, "DewPoint");
this.removeCharacteristic(this.forecastCharacteristics, "UVIndex");
this.removeCharacteristic(this.forecastCharacteristics, "DewPoint");
this.removeCharacteristic(this.forecastCharacteristics, "SunriseTime");
this.removeCharacteristic(this.forecastCharacteristics, "SunsetTime");
this.forecastDays = 5;
this.update(forecastDays, callback);
}
else
{
that.log.error("Could not retreive weather report with neither API 3.0 or API 2.5. You may need to wait up to 30 minutes after creating your api key. If the error persist, check if you copied the api key correctly.");
that.log.error(result);
that.log.error("Error result: " + result);
that.log.error("Error message: " + error);
callback();
}
}
else
{
that.log.error("Error retrieving weather report");
that.log.error(result);
that.log.error("Error retrieving OpenWeatherMap report");
that.log.error("Error result: " + result);
that.log.error("Error message: " + error);
callback();
}
}
Expand All @@ -165,7 +169,7 @@ class OpenWeatherMapAPI
}
else
{
this.parseReportOneCall(weather.report, values["current"]);
this.parseReportOneCall(weather.report, values["current"], timezone);
weather.report.ObservationTime = moment.unix(values["current"].dt).tz(timezone).format('HH:mm:ss');
}

Expand All @@ -181,6 +185,7 @@ class OpenWeatherMapAPI

// API 2.5 does not send a summary for the forecast day, instead it sends a report for every 3 hours.
// We need to combine 8 x 3hrs reports to get the forecast for 1 day.
// Also for API 2.5, timezone parameter is actually the result "city" JSON array
let legacyDays = [];
if (this.api === "2.5")
{
Expand All @@ -199,7 +204,7 @@ class OpenWeatherMapAPI
{
if (this.api === "2.5")
{
forecasts[forecasts.length] = this.parseForecastLegacy(values[i], timezone / 60);
forecasts[forecasts.length] = this.parseForecastLegacy(values[i], timezone);
}
else
{
Expand All @@ -224,6 +229,8 @@ class OpenWeatherMapAPI
let detailedCondition = this.getConditionCategory(values.weather[0].id, true);
report.RainBool = [5, 6, 9].includes(detailedCondition);
report.SnowBool = [7, 8].includes(detailedCondition);
report.SunriseTime = moment.unix(values.sys.sunrise).utcOffset(values.timezone / 60).format('HH:mm:ss');
report.SunsetTime = moment.unix(values.sys.sunset).utcOffset(values.timezone / 60).format('HH:mm:ss');
report.TemperatureApparent = typeof values.main.feels_like === 'object' ? parseInt(values.main.feels_like.day) : parseInt(values.main.feels_like);
report.TemperatureMax = parseInt(values.main.temp_max);
report.TemperatureMin = parseInt(values.main.temp_min);
Expand All @@ -249,7 +256,7 @@ class OpenWeatherMapAPI
* @param timezoneShift shift in seconds from utc of location timezone
* @returns {{}} forecast day
*/
parseForecastLegacy(values, timezoneShift)
parseForecastLegacy(values, city)
{
let forecast = {};
let combinedHourlyValues = {
Expand All @@ -265,6 +272,10 @@ class OpenWeatherMapAPI
"all": values.map(v => v.clouds.all).reduce((acc, v, i, a) => (acc + v / a.length), 0)
},
"weather": values[4].weather,
"sys": {
"sunrise": city["sunrise"],
"sunset": city["sunset"]
},
"wind": {
"speed": Math.max(...values.map(v => v.wind.speed)),
"deg": values[4].wind.deg,
Expand All @@ -273,16 +284,17 @@ class OpenWeatherMapAPI
"rain": {
"24h": values.map(v => v.rain === undefined || isNaN(parseFloat(v.rain['3h'])) ? 0 : parseFloat(v.rain['3h'])).reduce((a, b) => a + b)
},
"pop": Math.max(...values.map(v => v.pop))
"pop": Math.max(...values.map(v => v.pop)),
"timezone" : city["timezone"]
}
this.parseReportLegacy(forecast, combinedHourlyValues, true);

forecast.ForecastDay = moment.unix(combinedHourlyValues.dt).utcOffset(timezoneShift).format('dddd');
forecast.ForecastDay = moment.unix(combinedHourlyValues.dt).utcOffset(city["timezone"] / 60).format('dddd');

return forecast;
}

parseReportOneCall(report, values, isForecast = false)
parseReportOneCall(report, values, timezone, isForecast = false)
{
report.AirPressure = parseInt(values.pressure);
report.CloudCover = parseInt(values.clouds);
Expand All @@ -293,6 +305,8 @@ class OpenWeatherMapAPI
let detailedCondition = this.getConditionCategory(values.weather[0].id, true);
report.RainBool = [5, 6, 9].includes(detailedCondition);
report.SnowBool = [7, 8].includes(detailedCondition);
report.SunriseTime = moment.unix(values.sunrise).tz(timezone).format('HH:mm:ss');
report.SunsetTime = moment.unix(values.sunset).tz(timezone).format('HH:mm:ss');
report.TemperatureApparent = typeof values.feels_like === 'object' ? parseInt(values.feels_like.day) : parseInt(values.feels_like);
report.UVIndex = parseInt(values.uvi);
report.WindDirection = converter.getWindDirection(values.wind_deg);
Expand All @@ -318,11 +332,9 @@ class OpenWeatherMapAPI
parseForecastOneCall(values, timezone)
{
let forecast = {};
this.parseReportOneCall(forecast, values, true);
this.parseReportOneCall(forecast, values, timezone, true);

forecast.ForecastDay = moment.unix(values.dt).tz(timezone).format('dddd');
forecast.SunriseTime = moment.unix(values.sunrise).tz(timezone).format('HH:mm:ss');
forecast.SunsetTime = moment.unix(values.sunset).tz(timezone).format('HH:mm:ss');

return forecast;
}
Expand Down
Loading

0 comments on commit 8d361d3

Please sign in to comment.