[ipobserver] Add support for WiFi version and push based method. (#12151)

* Add support for Wifi version of ipObserver.
* make config private again.
* Add logging.
* Remove tags

Signed-off-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
Matthew Skinner
2022-02-12 19:34:16 +11:00
committed by GitHub
parent 1be50736e3
commit 35a6fdde60
8 changed files with 207 additions and 24 deletions

View File

@@ -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";

View File

@@ -24,4 +24,6 @@ public class IpObserverConfiguration {
public String address = "";
public int pollTime = 20;
public int autoReboot = 2000;
public String password = "";
public String id = "";
}

View File

@@ -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<String, ChannelHandler> channelHandlers = new HashMap<String, ChannelHandler>();
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<String, String> 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<String, String> 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) {

View File

@@ -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<ThingTypeUID> 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;

View File

@@ -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<IpObserverHandler> 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);
}
}

View File

@@ -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

View File

@@ -10,6 +10,8 @@
<channels>
<channel id="temperatureIndoor" typeId="temperatureIndoor"/>
<channel id="temperatureOutdoor" typeId="system.outdoor-temperature"/>
<channel id="temperatureWindChill" typeId="temperatureWindChill"/>
<channel id="temperatureDewPoint" typeId="temperatureDewPoint"/>
<channel id="humidityIndoor" typeId="humidityIndoor"/>
<channel id="humidityOutdoor" typeId="system.atmospheric-humidity"/>
<channel id="pressureAbsolute" typeId="pressureAbsolute"/>
@@ -51,6 +53,16 @@
feature allowing you to manually trigger or use a rule to handle the reboots</description>
<default>2000</default>
</parameter>
<parameter name="id" type="text">
<label>Station ID</label>
<description>The station ID used to connect to WeatherUnderGround. Leave blank if you wish to poll the livedata.</description>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Station Password</label>
<description>The station password used to connect to WeatherUnderGround. Leave blank if you wish to poll the
livedata.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="responseTime" advanced="true">
@@ -70,6 +82,20 @@
</tags>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="temperatureWindChill" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Wind Chill Temperature</label>
<description>Wind Chill Temperature Outdoors</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="temperatureDewPoint" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Dew Point Temperature</label>
<description>Dew Point Temperature Outdoors</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="humidityIndoor">
<item-type>Number:Dimensionless</item-type>
<label>Indoor Humidity</label>
@@ -174,10 +200,6 @@
<label>Wind Max Gust</label>
<description>Max wind gust for today</description>
<category>Wind</category>
<tags>
<tag>Measurement</tag>
<tag>Wind</tag>
</tags>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="rainHourlyRate">
@@ -207,10 +229,6 @@
<label>Rain for Week</label>
<description>Weekly Rain</description>
<category>Rain</category>
<tags>
<tag>Measurement</tag>
<tag>Rain</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="rainForMonth" advanced="true">
@@ -218,10 +236,6 @@
<label>Rain for Month</label>
<description>Rain since 12:00 on the 1st of this month</description>
<category>Rain</category>
<tags>
<tag>Measurement</tag>
<tag>Rain</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="rainForYear">
@@ -229,10 +243,6 @@
<label>Rain for Year</label>
<description>Total rain since 12:00 on 1st Jan</description>
<category>Rain</category>
<tags>
<tag>Measurement</tag>
<tag>Rain</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="lastUpdatedTime" advanced="true">