[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:
@@ -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";
|
||||
|
||||
@@ -24,4 +24,6 @@ public class IpObserverConfiguration {
|
||||
public String address = "";
|
||||
public int pollTime = 20;
|
||||
public int autoReboot = 2000;
|
||||
public String password = "";
|
||||
public String id = "";
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user