diff --git a/bundles/org.openhab.binding.ipobserver/README.md b/bundles/org.openhab.binding.ipobserver/README.md index 4e7e39497eb..10058609003 100644 --- a/bundles/org.openhab.binding.ipobserver/README.md +++ b/bundles/org.openhab.binding.ipobserver/README.md @@ -4,9 +4,14 @@ This binding is for any weather station that sends data to an IP Observer module The weather stations that do this are made by a company in China called `Fine Offset` and then re-branded by many distribution companies around the world. Some of the brands include Aercus (433mhz), Ambient Weather (915mhz), Frogitt, Misol (433mhz), Pantech (433mhz), Sainlogic and many more. Whilst Ambient Weather has it own cloud based binding, the other brands will not work with that binding and Ambient Weather do not sell outside of the United States. -This binding works fully offline and uses local scraping of the weather station data at 12 second resolution if you wish and is easy to setup. + +This binding works fully offline and can work via one of two methods: + +1. Local scraping of the weather station's `livedata` webpage at 12 second resolution (non WiFi models only). +2. Both WiFi and RJ45 models can be setup to push the data directly to the openHAB (default 8080) server directly and the binding can parse the data from the weather underground data. + The other binding worth mentioning is the weather underground binding that allows the data to be intercepted on its way to WU, however many of the weather stations do not allow the redirection of the WU data and require you to know how to do redirections with a custom DNS server on your network. -This binding is by far the easiest method and works for all the brands and will not stop the data still being sent to WU if you wish to do both at the same time. +This binding with method 1 and a RJ45 model is by far the easiest method and works for all the brands and will not stop the data still being sent to WU if you wish to do both at the same time. If your weather station came with a LCD screen instead of the IP Observer, you can add on the unit and the LCD screen will still work in parallel as the RF data is sent 1 way from the outdoor unit to the inside screens and IP Observer units. ## Supported Things @@ -15,15 +20,21 @@ There is only one thing that can be added and is called `weatherstation`. ## Discovery -Auto discovery is supported and may take a while to complete as it scans all IP addresses on your network one by one. +Auto discovery is supported for the RJ45 models, while the WiFi IP Observer will need to be manually added. +Discovery may take a while to complete as it scans all IP addresses on your network one by one. ## Thing Configuration +When the id and password are supplied, you need to set the custom WU path to `/weatherstation/updateweatherstation.php` and the port to be the same as openHAB (port 8080 by default). +If they are left blank, the binding will work in the scraping mode (RJ45 model only). + | Parameter | Required | Description | |-|-|-| | `address` | Y | Hostname or IP for the IP Observer | | `pollTime` | Y | Time in seconds between each Scan of the livedata.htm from the IP Observer | | `autoReboot` | Y | Time in milliseconds to wait for a reply before rebooting the IP Observer. A value of 0 disables this feature allowing you to manually trigger or use a rule to handle the reboots. | +| `id` | N | The weather underground's `station ID` that is setup in the ipobservers settings. | +| `password` | N | The weather underground's `station key` that is setup in the ipobservers settings. | ## Channels diff --git a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverBindingConstants.java b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverBindingConstants.java index d923fba9ef0..60d50cf73fb 100644 --- a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverBindingConstants.java +++ b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverBindingConstants.java @@ -26,6 +26,7 @@ public class IpObserverBindingConstants { public static final String BINDING_ID = "ipobserver"; public static final String REBOOT_URL = "/msgreboot.htm"; public static final String LIVE_DATA_URL = "/livedata.htm"; + public static final String SERVER_UPDATE_URL = "/weatherstation/updateweatherstation.php"; public static final String STATION_SETTINGS_URL = "/station.htm"; public static final int DISCOVERY_THREAD_POOL_SIZE = 15; @@ -35,6 +36,8 @@ public class IpObserverBindingConstants { // List of all Channel ids public static final String TEMP_INDOOR = "temperatureIndoor"; public static final String TEMP_OUTDOOR = "temperatureOutdoor"; + public static final String TEMP_WIND_CHILL = "temperatureWindChill"; + public static final String TEMP_DEW_POINT = "temperatureDewPoint"; public static final String INDOOR_HUMIDITY = "humidityIndoor"; public static final String OUTDOOR_HUMIDITY = "humidityOutdoor"; public static final String ABS_PRESSURE = "pressureAbsolute"; diff --git a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverConfiguration.java b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverConfiguration.java index 0c7f61400b6..aaff7a10fc1 100644 --- a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverConfiguration.java +++ b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverConfiguration.java @@ -24,4 +24,6 @@ public class IpObserverConfiguration { public String address = ""; public int pollTime = 20; public int autoReboot = 2000; + public String password = ""; + public String id = ""; } diff --git a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverHandler.java b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverHandler.java index 36990b8e7b8..c6e47364533 100644 --- a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverHandler.java +++ b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverHandler.java @@ -19,6 +19,7 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.ExecutionException; @@ -70,10 +71,12 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class IpObserverHandler extends BaseThingHandler { private final HttpClient httpClient; + private final IpObserverUpdateReceiver ipObserverUpdateReceiver; private final Logger logger = LoggerFactory.getLogger(IpObserverHandler.class); private Map channelHandlers = new HashMap(); private @Nullable ScheduledFuture pollingFuture = null; private IpObserverConfiguration config = new IpObserverConfiguration(); + private String idPass = ""; // Config settings parsed from weather station. private boolean imperialTemperature = false; private boolean imperialRain = false; @@ -135,9 +138,47 @@ public class IpObserverHandler extends BaseThingHandler { } } - public IpObserverHandler(Thing thing, HttpClient httpClient) { + public IpObserverHandler(Thing thing, HttpClient httpClient, IpObserverUpdateReceiver UpdateReceiver) { super(thing); this.httpClient = httpClient; + ipObserverUpdateReceiver = UpdateReceiver; + } + + /** + * Takes a String of queries from the GET request made to the openHAB Jetty server and splits them + * into keys and values made up from the weather stations readings. + * + * @param update + */ + public void processServerQuery(String update) { + if (update.startsWith(idPass)) { + String matchedUpdate = update.substring(idPass.length() + 1, update.length()); + logger.trace("Update received:{}", matchedUpdate); + updateState(LAST_UPDATED_TIME, new DateTimeType(ZonedDateTime.now())); + Map mappedQuery = new HashMap<>(); + String[] readings = matchedUpdate.split("&"); + for (String pair : readings) { + int index = pair.indexOf("="); + if (index > 0) { + mappedQuery.put(pair.substring(0, index), pair.substring(index + 1, pair.length())); + } + } + handleServerReadings(mappedQuery); + } + } + + public void handleServerReadings(Map updates) { + Iterator it = updates.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + ChannelHandler localUpdater = channelHandlers.get(pair.getKey()); + if (localUpdater != null) { + logger.trace("Found element {}, value is {}", pair.getKey(), pair.getValue()); + localUpdater.processValue(pair.getValue().toString()); + } else { + logger.trace("UNKNOWN element {}, value is {}", pair.getKey(), pair.getValue()); + } + } } @Override @@ -244,6 +285,27 @@ public class IpObserverHandler extends BaseThingHandler { } } + private void setupServerChannels() { + createChannelHandler(WIND_DIRECTION, QuantityType.class, Units.DEGREE_ANGLE, "winddir"); + createChannelHandler(INDOOR_HUMIDITY, DecimalType.class, Units.PERCENT, "indoorhumidity"); + createChannelHandler(OUTDOOR_HUMIDITY, DecimalType.class, Units.PERCENT, "humidity"); + createChannelHandler(TEMP_INDOOR, QuantityType.class, ImperialUnits.FAHRENHEIT, "indoortempf"); + createChannelHandler(TEMP_OUTDOOR, QuantityType.class, ImperialUnits.FAHRENHEIT, "tempf"); + createChannelHandler(TEMP_WIND_CHILL, QuantityType.class, ImperialUnits.FAHRENHEIT, "windchillf"); + createChannelHandler(TEMP_DEW_POINT, QuantityType.class, ImperialUnits.FAHRENHEIT, "dewptf"); + createChannelHandler(HOURLY_RAIN_RATE, QuantityType.class, ImperialUnits.INCH, "rainin"); + createChannelHandler(DAILY_RAIN, QuantityType.class, ImperialUnits.INCH, "dailyrainin"); + createChannelHandler(WEEKLY_RAIN, QuantityType.class, ImperialUnits.INCH, "weeklyrainin"); + createChannelHandler(MONTHLY_RAIN, QuantityType.class, ImperialUnits.INCH, "monthlyrainin"); + createChannelHandler(YEARLY_RAIN, QuantityType.class, ImperialUnits.INCH, "yearlyrainin"); + createChannelHandler(UV_INDEX, DecimalType.class, SIUnits.CELSIUS, "UV"); + createChannelHandler(WIND_AVERAGE_SPEED, QuantityType.class, ImperialUnits.MILES_PER_HOUR, "windspeedmph"); + createChannelHandler(WIND_GUST, QuantityType.class, ImperialUnits.MILES_PER_HOUR, "windgustmph"); + createChannelHandler(SOLAR_RADIATION, QuantityType.class, Units.IRRADIANCE, "solarradiation"); + createChannelHandler(REL_PRESSURE, QuantityType.class, ImperialUnits.INCH_OF_MERCURY, "baromin"); + createChannelHandler(OUTDOOR_BATTERY, StringType.class, Units.PERCENT, "lowbatt"); + } + private void setupChannels() { if (imperialTemperature) { logger.debug("Using imperial units of measurement for temperature."); @@ -332,12 +394,20 @@ public class IpObserverHandler extends BaseThingHandler { @Override public void initialize() { config = getConfigAs(IpObserverConfiguration.class); - updateStatus(ThingStatus.UNKNOWN); - pollingFuture = scheduler.scheduleWithFixedDelay(this::pollStation, 1, config.pollTime, TimeUnit.SECONDS); + if (!config.id.isBlank() && !config.password.isBlank()) { + updateStatus(ThingStatus.ONLINE); + idPass = "ID=" + config.id + "&PASSWORD=" + config.password; + setupServerChannels(); + ipObserverUpdateReceiver.addStation(this); + } else { + updateStatus(ThingStatus.UNKNOWN); + pollingFuture = scheduler.scheduleWithFixedDelay(this::pollStation, 1, config.pollTime, TimeUnit.SECONDS); + } } @Override public void dispose() { + ipObserverUpdateReceiver.removeStation(this); channelHandlers.clear(); ScheduledFuture localFuture = pollingFuture; if (localFuture != null) { diff --git a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverHandlerFactory.java b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverHandlerFactory.java index 2d22aba0fc0..6e5c120e683 100644 --- a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverHandlerFactory.java +++ b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverHandlerFactory.java @@ -28,6 +28,7 @@ import org.openhab.core.thing.binding.ThingHandlerFactory; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.HttpService; /** * The {@link IpObserverHandlerFactory} is responsible for creating things and thing @@ -39,11 +40,14 @@ import org.osgi.service.component.annotations.Reference; @Component(configurationPid = "binding.ipobserver", service = ThingHandlerFactory.class) public class IpObserverHandlerFactory extends BaseThingHandlerFactory { private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_WEATHER_STATION); + private final IpObserverUpdateReceiver ipObserverUpdateReceiver; protected final HttpClient httpClient; @Activate - public IpObserverHandlerFactory(@Reference HttpClientFactory httpClientFactory) { + public IpObserverHandlerFactory(@Reference HttpClientFactory httpClientFactory, + @Reference HttpService httpService) { this.httpClient = httpClientFactory.getCommonHttpClient(); + ipObserverUpdateReceiver = new IpObserverUpdateReceiver(httpService); } protected HttpClient getHttpClient() { @@ -60,7 +64,7 @@ public class IpObserverHandlerFactory extends BaseThingHandlerFactory { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_WEATHER_STATION.equals(thingTypeUID)) { - return new IpObserverHandler(thing, httpClient); + return new IpObserverHandler(thing, httpClient, ipObserverUpdateReceiver); } return null; diff --git a/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverUpdateReceiver.java b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverUpdateReceiver.java new file mode 100644 index 00000000000..56229112cbf --- /dev/null +++ b/bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverUpdateReceiver.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.ipobserver.internal; + +import static org.openhab.binding.ipobserver.internal.IpObserverBindingConstants.SERVER_UPDATE_URL; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link IpObserverUpdateReceiver} captures any updates sent to the openHAB Jetty server if the weather station is + * setup to direct the weather updates to the HTTP server of openHAB which is normally port 8080. + * + * @author Matthew Skinner - Initial contribution + */ +@NonNullByDefault +public class IpObserverUpdateReceiver extends HttpServlet { + private static final long serialVersionUID = -234658674L; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private List listOfHandlers = new ArrayList<>(1); + + public IpObserverUpdateReceiver(HttpService httpService) { + try { + httpService.registerServlet(SERVER_UPDATE_URL, this, null, httpService.createDefaultHttpContext()); + } catch (NamespaceException | ServletException e) { + logger.warn("Registering servlet failed:{}", e.getMessage()); + } + } + + @Override + protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException { + if (req == null) { + return; + } + String stationUpdate = req.getQueryString(); + if (stationUpdate == null) { + return; + } + logger.debug("Weather station packet received from {}", req.getRemoteHost()); + for (IpObserverHandler ipObserverHandler : listOfHandlers) { + ipObserverHandler.processServerQuery(stationUpdate); + } + } + + public void addStation(IpObserverHandler ipObserverHandler) { + listOfHandlers.add(ipObserverHandler); + } + + public void removeStation(IpObserverHandler ipObserverHandler) { + listOfHandlers.remove(ipObserverHandler); + } +} diff --git a/bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/i18n/ipobserver.properties b/bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/i18n/ipobserver.properties index d515eb12e17..6e9dd69bb26 100644 --- a/bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/i18n/ipobserver.properties +++ b/bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/i18n/ipobserver.properties @@ -14,6 +14,10 @@ thing-type.config.ipobserver.weatherstation.address.label = Network Address thing-type.config.ipobserver.weatherstation.address.description = Hostname or IP for the IP Observer thing-type.config.ipobserver.weatherstation.autoReboot.label = Auto Reboot thing-type.config.ipobserver.weatherstation.autoReboot.description = Time in milliseconds to wait for a reply before rebooting the IP Observer. A value of 0 disables this feature allowing you to manually trigger or use a rule to handle the reboots +thing-type.config.ipobserver.weatherstation.id.label = Station ID +thing-type.config.ipobserver.weatherstation.id.description = The station ID used to connect to WeatherUnderGround. Leave blank if you wish to poll the livedata. +thing-type.config.ipobserver.weatherstation.password.label = Station Password +thing-type.config.ipobserver.weatherstation.password.description = The station password used to connect to WeatherUnderGround. Leave blank if you wish to poll the livedata. thing-type.config.ipobserver.weatherstation.pollTime.label = Poll Time thing-type.config.ipobserver.weatherstation.pollTime.description = Time in seconds between each Scan of the livedata.htm from the ObserverIP @@ -41,8 +45,12 @@ channel-type.ipobserver.responseTime.label = Response Time channel-type.ipobserver.responseTime.description = How many milliseconds it took to fetch the sensor readings from livedata.htm channel-type.ipobserver.solarRadiation.label = Solar Radiation channel-type.ipobserver.solarRadiation.description = Solar Radiation +channel-type.ipobserver.temperatureDewPoint.label = Dew Point Temperature +channel-type.ipobserver.temperatureDewPoint.description = Dew Point Temperature Outdoors channel-type.ipobserver.temperatureIndoor.label = Indoor Temperature channel-type.ipobserver.temperatureIndoor.description = Current Temperature Indoors +channel-type.ipobserver.temperatureWindChill.label = Wind Chill Temperature +channel-type.ipobserver.temperatureWindChill.description = Wind Chill Temperature Outdoors channel-type.ipobserver.uv.label = UV channel-type.ipobserver.uv.description = UV channel-type.ipobserver.uvIndex.label = UV Index diff --git a/bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/thing/thing-types.xml index 8fdd9f2e3e6..561a656bb12 100644 --- a/bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/thing/thing-types.xml @@ -10,6 +10,8 @@ + + @@ -51,6 +53,16 @@ feature allowing you to manually trigger or use a rule to handle the reboots 2000 + + + The station ID used to connect to WeatherUnderGround. Leave blank if you wish to poll the livedata. + + + password + + The station password used to connect to WeatherUnderGround. Leave blank if you wish to poll the + livedata. + @@ -70,6 +82,20 @@ + + Number:Temperature + + Wind Chill Temperature Outdoors + Temperature + + + + Number:Temperature + + Dew Point Temperature Outdoors + Temperature + + Number:Dimensionless @@ -174,10 +200,6 @@ Max wind gust for today Wind - - Measurement - Wind - @@ -207,10 +229,6 @@ Weekly Rain Rain - - Measurement - Rain - @@ -218,10 +236,6 @@ Rain since 12:00 on the 1st of this month Rain - - Measurement - Rain - @@ -229,10 +243,6 @@ Total rain since 12:00 on 1st Jan Rain - - Measurement - Rain -