added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.fronius-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-fronius" description="Fronius Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.fronius/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal;
|
||||
|
||||
/**
|
||||
* The {@link FroniusBaseDeviceConfiguration} is the class used to match the
|
||||
* thing configuration.
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class FroniusBaseDeviceConfiguration {
|
||||
public Integer deviceId;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link FroniusBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
* @author Peter Schraffl - Added device status and error status channels
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FroniusBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "fronius";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_INVERTER = new ThingTypeUID(BINDING_ID, "powerinverter");
|
||||
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String InverterDataChannelDayEnergy = "inverterdatachanneldayenergy";
|
||||
public static final String InverterDataChannelPac = "inverterdatachannelpac";
|
||||
public static final String InverterDataChannelTotal = "inverterdatachanneltotal";
|
||||
public static final String InverterDataChannelYear = "inverterdatachannelyear";
|
||||
public static final String InverterDataChannelFac = "inverterdatachannelfac";
|
||||
public static final String InverterDataChannelIac = "inverterdatachanneliac";
|
||||
public static final String InverterDataChannelIdc = "inverterdatachannelidc";
|
||||
public static final String InverterDataChannelUac = "inverterdatachanneluac";
|
||||
public static final String InverterDataChannelUdc = "inverterdatachanneludc";
|
||||
public static final String InverterDataChannelDeviceStatusErrorCode = "inverterdatadevicestatuserrorcode";
|
||||
public static final String InverterDataChannelDeviceStatusStatusCode = "inverterdatadevicestatusstatuscode";
|
||||
public static final String PowerFlowpGrid = "powerflowchannelpgrid";
|
||||
public static final String PowerFlowpLoad = "powerflowchannelpload";
|
||||
public static final String PowerFlowpAkku = "powerflowchannelpakku";
|
||||
|
||||
// List of all Urls
|
||||
public static final String INVERTER_REALTIME_DATA_URL = "http://%IP%/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceId=%DEVICEID%&DataCollection=CommonInverterData";
|
||||
public static final String POWERFLOW_REALTIME_DATA = "http://%IP%/solar_api/v1/GetPowerFlowRealtimeData.fcgi";
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal;
|
||||
|
||||
/**
|
||||
* The {@link FroniusBridgeConfiguration} is the class used to match the
|
||||
* bridge configuration.
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class FroniusBridgeConfiguration {
|
||||
public String hostname;
|
||||
public Integer refreshInterval;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal;
|
||||
|
||||
import static org.openhab.binding.fronius.internal.FroniusBindingConstants.*;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.openhab.binding.fronius.internal.handler.FroniusBridgeHandler;
|
||||
import org.openhab.binding.fronius.internal.handler.FroniusSymoInverterHandler;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link FroniusHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.fronius")
|
||||
public class FroniusHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<ThingTypeUID>() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
{
|
||||
add(THING_TYPE_INVERTER);
|
||||
add(THING_TYPE_BRIDGE);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_INVERTER)) {
|
||||
return new FroniusSymoInverterHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
|
||||
return new FroniusBridgeHandler((Bridge) thing);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* base class for a response-object from the API
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class BaseFroniusResponse {
|
||||
@SerializedName("Head")
|
||||
private Head head;
|
||||
|
||||
public Head getHead() {
|
||||
if (head == null) {
|
||||
head = new Head();
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
public void setHead(Head head) {
|
||||
this.head = head;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link DeviceStatus} is responsible for storing
|
||||
* the "devicestatus" node
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class DeviceStatus {
|
||||
@SerializedName("StatusCode")
|
||||
private int statusCode;
|
||||
@SerializedName("MgmtTimerRemainingTime")
|
||||
private int mgmtTimerRemainingTime;
|
||||
@SerializedName("ErrorCode")
|
||||
private int errorCode;
|
||||
@SerializedName("LEDColor")
|
||||
private int ledColor;
|
||||
@SerializedName("LEDState")
|
||||
private int ledState;
|
||||
@SerializedName("StateToReset")
|
||||
private boolean stateToReset;
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public void setStatusCode(int statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public int getMgmtTimerRemainingTime() {
|
||||
return mgmtTimerRemainingTime;
|
||||
}
|
||||
|
||||
public void setMgmtTimerRemainingTime(int mgmtTimerRemainingTime) {
|
||||
this.mgmtTimerRemainingTime = mgmtTimerRemainingTime;
|
||||
}
|
||||
|
||||
public int getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public void setErrorCode(int errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public int getLedColor() {
|
||||
return ledColor;
|
||||
}
|
||||
|
||||
public void setLedColor(int ledColor) {
|
||||
this.ledColor = ledColor;
|
||||
}
|
||||
|
||||
public int getLedState() {
|
||||
return ledState;
|
||||
}
|
||||
|
||||
public void setLedState(int ledState) {
|
||||
this.ledState = ledState;
|
||||
}
|
||||
|
||||
public boolean isStateToReset() {
|
||||
return stateToReset;
|
||||
}
|
||||
|
||||
public void setStateToReset(boolean stateToReset) {
|
||||
this.stateToReset = stateToReset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link Head} is responsible for storing
|
||||
* the "head" node of the JSON response from the Fronius Solar APIs (V1)
|
||||
*
|
||||
* The contents of the response object will vary depending on the preceding request but it always contains a common
|
||||
* response header and a request body.
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class Head {
|
||||
@SerializedName("RequestArguments")
|
||||
private HeadRequestArguments requestArguments;
|
||||
@SerializedName("Status")
|
||||
private HeadStatus status;
|
||||
@SerializedName("Timestamp")
|
||||
private String timestamp;
|
||||
|
||||
public HeadRequestArguments getRequestArguments() {
|
||||
if (requestArguments == null) {
|
||||
requestArguments = new HeadRequestArguments();
|
||||
}
|
||||
return requestArguments;
|
||||
}
|
||||
|
||||
public void setRequestArguments(HeadRequestArguments requestArguments) {
|
||||
this.requestArguments = requestArguments;
|
||||
}
|
||||
|
||||
public HeadStatus getStatus() {
|
||||
if (status == null) {
|
||||
status = new HeadStatus();
|
||||
status.setCode(255);
|
||||
status.setReason("undefined runtime error");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(HeadStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
if (timestamp == null) {
|
||||
timestamp = "";
|
||||
}
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(String timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link HeadRequestArguments} is responsible for storing
|
||||
* the "RequestArguments" node from the {@link Head}
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class HeadRequestArguments {
|
||||
@SerializedName("DataCollection")
|
||||
private String dataCollection;
|
||||
@SerializedName("DeviceClass")
|
||||
private String deviceClass;
|
||||
@SerializedName("DeviceId")
|
||||
private String deviceId;
|
||||
@SerializedName("Scope")
|
||||
private String scope;
|
||||
|
||||
public String getDataCollection() {
|
||||
if (null == dataCollection) {
|
||||
dataCollection = "";
|
||||
}
|
||||
return dataCollection;
|
||||
}
|
||||
|
||||
public void setDataCollection(String dataCollection) {
|
||||
this.dataCollection = dataCollection;
|
||||
}
|
||||
|
||||
public String getDeviceClass() {
|
||||
if (null == deviceClass) {
|
||||
deviceClass = "";
|
||||
}
|
||||
return deviceClass;
|
||||
}
|
||||
|
||||
public void setDeviceClass(String deviceClass) {
|
||||
this.deviceClass = deviceClass;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
if (null == deviceId) {
|
||||
deviceId = "";
|
||||
}
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
if (null == scope) {
|
||||
scope = "";
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link HeadStatus} is responsible for storing
|
||||
* the "status" node from the {@link Head}
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class HeadStatus {
|
||||
@SerializedName("Code")
|
||||
private int code;
|
||||
@SerializedName("Reason")
|
||||
private String reason;
|
||||
@SerializedName("UserMessage")
|
||||
private String userMessage;
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
if (reason == null) {
|
||||
reason = "";
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
public void setReason(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getUserMessage() {
|
||||
if (userMessage == null) {
|
||||
userMessage = "";
|
||||
}
|
||||
return userMessage;
|
||||
}
|
||||
|
||||
public void setUserMessage(String userMessage) {
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link InverterRealtimeBody} is responsible for storing
|
||||
* the "body" node of the JSON response
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class InverterRealtimeBody {
|
||||
@SerializedName("Data")
|
||||
private InverterRealtimeBodyData data;
|
||||
|
||||
public InverterRealtimeBodyData getData() {
|
||||
if (data == null) {
|
||||
data = new InverterRealtimeBodyData();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(InverterRealtimeBodyData data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link InverterRealtimeBodyData} is responsible for storing
|
||||
* the "data" node of the JSON response
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class InverterRealtimeBodyData {
|
||||
|
||||
@SerializedName("DAY_ENERGY")
|
||||
private ValueUnit dayEnergy;
|
||||
@SerializedName("FAC")
|
||||
private ValueUnit fac;
|
||||
@SerializedName("IAC")
|
||||
private ValueUnit iac;
|
||||
@SerializedName("IDC")
|
||||
private ValueUnit idc;
|
||||
@SerializedName("PAC")
|
||||
private ValueUnit pac;
|
||||
@SerializedName("TOTAL_ENERGY")
|
||||
private ValueUnit totalEnergy;
|
||||
@SerializedName("UAC")
|
||||
private ValueUnit uac;
|
||||
@SerializedName("UDC")
|
||||
private ValueUnit udc;
|
||||
@SerializedName("YEAR_ENERGY")
|
||||
private ValueUnit yearEnergy;
|
||||
@SerializedName("DeviceStatus")
|
||||
private DeviceStatus deviceStatus;
|
||||
|
||||
public ValueUnit getDayEnergy() {
|
||||
return dayEnergy;
|
||||
}
|
||||
|
||||
public void setDayEnergy(ValueUnit dayEnergy) {
|
||||
this.dayEnergy = dayEnergy;
|
||||
}
|
||||
|
||||
public ValueUnit getPac() {
|
||||
return pac;
|
||||
}
|
||||
|
||||
public void setPac(ValueUnit pac) {
|
||||
this.pac = pac;
|
||||
}
|
||||
|
||||
public ValueUnit getTotalEnergy() {
|
||||
return totalEnergy;
|
||||
}
|
||||
|
||||
public void setTotalEnergy(ValueUnit totalEnergy) {
|
||||
this.totalEnergy = totalEnergy;
|
||||
}
|
||||
|
||||
public ValueUnit getYearEnergy() {
|
||||
return yearEnergy;
|
||||
}
|
||||
|
||||
public void setYearEnergy(ValueUnit yearEnergy) {
|
||||
this.yearEnergy = yearEnergy;
|
||||
}
|
||||
|
||||
public DeviceStatus getDeviceStatus() {
|
||||
if (deviceStatus == null) {
|
||||
deviceStatus = new DeviceStatus();
|
||||
}
|
||||
return deviceStatus;
|
||||
}
|
||||
|
||||
public void setDeviceStatus(DeviceStatus deviceStatus) {
|
||||
this.deviceStatus = deviceStatus;
|
||||
}
|
||||
|
||||
public ValueUnit getFac() {
|
||||
return fac;
|
||||
}
|
||||
|
||||
public void setFac(ValueUnit fac) {
|
||||
this.fac = fac;
|
||||
}
|
||||
|
||||
public ValueUnit getIac() {
|
||||
return iac;
|
||||
}
|
||||
|
||||
public void setIac(ValueUnit iac) {
|
||||
this.iac = iac;
|
||||
}
|
||||
|
||||
public ValueUnit getIdc() {
|
||||
return idc;
|
||||
}
|
||||
|
||||
public void setIdc(ValueUnit idc) {
|
||||
this.idc = idc;
|
||||
}
|
||||
|
||||
public ValueUnit getUac() {
|
||||
return uac;
|
||||
}
|
||||
|
||||
public void setUac(ValueUnit uac) {
|
||||
this.uac = uac;
|
||||
}
|
||||
|
||||
public ValueUnit getUdc() {
|
||||
return udc;
|
||||
}
|
||||
|
||||
public void setUdc(ValueUnit udc) {
|
||||
this.udc = udc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link InverterRealtimeResponse} is responsible for storing
|
||||
* the response from the realtime api
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class InverterRealtimeResponse extends BaseFroniusResponse {
|
||||
|
||||
@SerializedName("Body")
|
||||
private InverterRealtimeBody body;
|
||||
|
||||
public InverterRealtimeBody getBody() {
|
||||
if (body == null) {
|
||||
body = new InverterRealtimeBody();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(InverterRealtimeBody body) {
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link PowerFlowRealtimeBody} is responsible for storing
|
||||
* the "body" node of the JSON response
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class PowerFlowRealtimeBody {
|
||||
@SerializedName("Data")
|
||||
private PowerFlowRealtimeBodyData data;
|
||||
|
||||
public PowerFlowRealtimeBodyData getData() {
|
||||
if (data == null) {
|
||||
data = new PowerFlowRealtimeBodyData();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(PowerFlowRealtimeBodyData data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link PowerFlowRealtimeBodyData} is responsible for storing
|
||||
* the "data" node of the JSON response
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class PowerFlowRealtimeBodyData {
|
||||
|
||||
@SerializedName("Site")
|
||||
private PowerFlowRealtimeSite site;
|
||||
|
||||
@SerializedName("Inverters")
|
||||
private Map<String, PowerFlowRealtimeInverter> inverters;
|
||||
|
||||
public Map<String, PowerFlowRealtimeInverter> getInverters() {
|
||||
if (inverters == null) {
|
||||
inverters = new HashMap<>();
|
||||
}
|
||||
return inverters;
|
||||
}
|
||||
|
||||
public void setInverters(Map<String, PowerFlowRealtimeInverter> inverters) {
|
||||
this.inverters = inverters;
|
||||
}
|
||||
|
||||
public PowerFlowRealtimeSite getSite() {
|
||||
if (site == null) {
|
||||
site = new PowerFlowRealtimeSite();
|
||||
}
|
||||
return site;
|
||||
}
|
||||
|
||||
public void setSite(PowerFlowRealtimeSite site) {
|
||||
this.site = site;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link PowerFlowRealtimeInverter} is responsible for storing
|
||||
* the "inverter" node of the JSON response
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class PowerFlowRealtimeInverter {
|
||||
|
||||
@SerializedName("DT")
|
||||
private double dt;
|
||||
@SerializedName("P")
|
||||
private double p;
|
||||
@SerializedName("E_Day")
|
||||
private double eDay;
|
||||
@SerializedName("E_Year")
|
||||
private double eYear;
|
||||
@SerializedName("E_Total")
|
||||
private double eTotal;
|
||||
|
||||
public double getDt() {
|
||||
return dt;
|
||||
}
|
||||
|
||||
public void setDt(double dt) {
|
||||
this.dt = dt;
|
||||
}
|
||||
|
||||
public double getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
public void setP(double p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
public double geteDay() {
|
||||
return eDay;
|
||||
}
|
||||
|
||||
public void seteDay(double eDay) {
|
||||
this.eDay = eDay;
|
||||
}
|
||||
|
||||
public double geteYear() {
|
||||
return eYear;
|
||||
}
|
||||
|
||||
public void seteYear(double eYear) {
|
||||
this.eYear = eYear;
|
||||
}
|
||||
|
||||
public double geteTotal() {
|
||||
return eTotal;
|
||||
}
|
||||
|
||||
public void seteTotal(double eTotal) {
|
||||
this.eTotal = eTotal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link PowerFlowRealtimeResponse} is responsible for storing
|
||||
* the response from the powerflowrealtime api
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class PowerFlowRealtimeResponse extends BaseFroniusResponse {
|
||||
@SerializedName("Body")
|
||||
private PowerFlowRealtimeBody body;
|
||||
|
||||
public PowerFlowRealtimeBody getBody() {
|
||||
if (body == null) {
|
||||
body = new PowerFlowRealtimeBody();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(PowerFlowRealtimeBody body) {
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link PowerFlowRealtimeSite} is responsible for storing
|
||||
* the "site" node
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class PowerFlowRealtimeSite {
|
||||
|
||||
@SerializedName("Mode")
|
||||
private String mode;
|
||||
@SerializedName("P_Grid")
|
||||
private double pgrid;
|
||||
@SerializedName("P_Load")
|
||||
private double pload;
|
||||
@SerializedName("P_Akku")
|
||||
private double pakku;
|
||||
@SerializedName("P_PV")
|
||||
private double ppv;
|
||||
@SerializedName("rel_SelfConsumption")
|
||||
private double relSelfConsumption;
|
||||
@SerializedName("rel_Autonomy")
|
||||
private double relAutonomy;
|
||||
@SerializedName("E_Day")
|
||||
private double eDay;
|
||||
@SerializedName("E_Year")
|
||||
private double eYear;
|
||||
@SerializedName("E_Total")
|
||||
private double eTotal;
|
||||
@SerializedName("Meter_Location")
|
||||
private String meterLocation;
|
||||
|
||||
public String getMode() {
|
||||
if (mode == null) {
|
||||
mode = "";
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public double getPgrid() {
|
||||
return pgrid;
|
||||
}
|
||||
|
||||
public void setPgrid(double pgrid) {
|
||||
this.pgrid = pgrid;
|
||||
}
|
||||
|
||||
public double getPload() {
|
||||
return pload;
|
||||
}
|
||||
|
||||
public void setPload(double pload) {
|
||||
this.pload = pload;
|
||||
}
|
||||
|
||||
public double getPakku() {
|
||||
return pakku;
|
||||
}
|
||||
|
||||
public void setPakku(double pakku) {
|
||||
this.pakku = pakku;
|
||||
}
|
||||
|
||||
public double getPpv() {
|
||||
return ppv;
|
||||
}
|
||||
|
||||
public void setPpv(double ppv) {
|
||||
this.ppv = ppv;
|
||||
}
|
||||
|
||||
public double getRelSelfConsumption() {
|
||||
return relSelfConsumption;
|
||||
}
|
||||
|
||||
public void setRelSelfConsumption(double relSelfConsumption) {
|
||||
this.relSelfConsumption = relSelfConsumption;
|
||||
}
|
||||
|
||||
public double getRelAutonomy() {
|
||||
return relAutonomy;
|
||||
}
|
||||
|
||||
public void setRelAutonomy(double relAutonomy) {
|
||||
this.relAutonomy = relAutonomy;
|
||||
}
|
||||
|
||||
public double geteDay() {
|
||||
return eDay;
|
||||
}
|
||||
|
||||
public void seteDay(double eDay) {
|
||||
this.eDay = eDay;
|
||||
}
|
||||
|
||||
public double geteYear() {
|
||||
return eYear;
|
||||
}
|
||||
|
||||
public void seteYear(double eYear) {
|
||||
this.eYear = eYear;
|
||||
}
|
||||
|
||||
public double geteTotal() {
|
||||
return eTotal;
|
||||
}
|
||||
|
||||
public void seteTotal(double eTotal) {
|
||||
this.eTotal = eTotal;
|
||||
}
|
||||
|
||||
public String getMeterLocation() {
|
||||
return meterLocation;
|
||||
}
|
||||
|
||||
public void setMeterLocation(String meterLocation) {
|
||||
this.meterLocation = meterLocation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.api;
|
||||
|
||||
import org.openhab.binding.fronius.internal.math.KilowattConverter;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link InverterRealtimeResponse} is responsible for storing
|
||||
* a value
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*/
|
||||
public class ValueUnit {
|
||||
@SerializedName("Value")
|
||||
private double value;
|
||||
@SerializedName("Unit")
|
||||
private String unit;
|
||||
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
if (unit == null) {
|
||||
unit = "";
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit) {
|
||||
this.setValue(KilowattConverter.convertTo(this.getValue(), this.getUnit(), unit));
|
||||
this.unit = unit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.handler;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
||||
import org.openhab.binding.fronius.internal.api.ValueUnit;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Basic Handler class for all Fronius services.
|
||||
*
|
||||
* @author Gerrit Beine - Initial contribution
|
||||
* @author Thomas Rokohl - Refactoring to merge the concepts
|
||||
*/
|
||||
public abstract class FroniusBaseThingHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FroniusBaseThingHandler.class);
|
||||
private final String serviceDescription;
|
||||
private FroniusBridgeHandler bridgeHandler;
|
||||
|
||||
public FroniusBaseThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
serviceDescription = getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateChannel(channelUID.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (getFroniusBridgeHandler() == null) {
|
||||
logger.debug("Initializing {} Service is only supported within a bridge", serviceDescription);
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
return;
|
||||
}
|
||||
logger.debug("Initializing {} Service", serviceDescription);
|
||||
getFroniusBridgeHandler().registerService(this);
|
||||
}
|
||||
|
||||
private synchronized FroniusBridgeHandler getFroniusBridgeHandler() {
|
||||
if (this.bridgeHandler == null) {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
return null;
|
||||
}
|
||||
ThingHandler handler = bridge.getHandler();
|
||||
if (handler instanceof FroniusBridgeHandler) {
|
||||
this.bridgeHandler = (FroniusBridgeHandler) handler;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return this.bridgeHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all Channels
|
||||
*/
|
||||
protected void updateChannels() {
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
updateChannel(channel.getUID().getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the channel from the last data
|
||||
*
|
||||
* @param channelId the id identifying the channel to be updated
|
||||
*/
|
||||
protected void updateChannel(String channelId) {
|
||||
if (!isLinked(channelId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object value = getValue(channelId);
|
||||
if (value == null) {
|
||||
logger.debug("Value retrieved for channel '{}' was null. Can't update.", channelId);
|
||||
return;
|
||||
}
|
||||
|
||||
State state = null;
|
||||
if (value instanceof BigDecimal) {
|
||||
state = new DecimalType((BigDecimal) value);
|
||||
} else if (value instanceof Integer) {
|
||||
state = new DecimalType(BigDecimal.valueOf(((Integer) value).longValue()));
|
||||
} else if (value instanceof Double) {
|
||||
state = new DecimalType((double) value);
|
||||
} else if (value instanceof ValueUnit) {
|
||||
state = new DecimalType(((ValueUnit) value).getValue());
|
||||
} else {
|
||||
logger.warn("Update channel {}: Unsupported value type {}", channelId, value.getClass().getSimpleName());
|
||||
}
|
||||
logger.debug("Update channel {} with state {} ({})", channelId, (state == null) ? "null" : state.toString(),
|
||||
value.getClass().getSimpleName());
|
||||
|
||||
// Update the channel
|
||||
if (state != null) {
|
||||
updateState(channelId, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return an internal description for logging
|
||||
*
|
||||
* @return the description of the thing
|
||||
*/
|
||||
protected abstract String getDescription();
|
||||
|
||||
/**
|
||||
* get the "new" associated value for a channelId
|
||||
*
|
||||
* @param channelId the id identifying the channel
|
||||
* @return the "new" associated value
|
||||
*/
|
||||
protected abstract Object getValue(String channelId);
|
||||
|
||||
/**
|
||||
* do whatever a thing must do to update the values
|
||||
* this function is called from the bridge in a given interval
|
||||
*
|
||||
* @param bridgeConfiguration the connected bridge configuration
|
||||
*/
|
||||
public abstract void refresh(FroniusBridgeConfiguration bridgeConfiguration);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Bridge for Fronius devices.
|
||||
*
|
||||
* @author Gerrit Beine - Initial contribution
|
||||
* @author Thomas Rokohl - Refactoring to merge the concepts.
|
||||
* Check if host is reachable.
|
||||
*/
|
||||
public class FroniusBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FroniusBridgeHandler.class);
|
||||
private static final int DEFAULT_REFRESH_PERIOD = 10;
|
||||
private final Set<FroniusBaseThingHandler> services = new HashSet<>();
|
||||
private ScheduledFuture<?> refreshJob;
|
||||
|
||||
public FroniusBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
public void registerService(final FroniusBaseThingHandler service) {
|
||||
this.services.add(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
final FroniusBridgeConfiguration config = getConfigAs(FroniusBridgeConfiguration.class);
|
||||
|
||||
boolean validConfig = true;
|
||||
String errorMsg = null;
|
||||
if (StringUtils.trimToNull(config.hostname) == null) {
|
||||
errorMsg = "Parameter 'hostname' is mandatory and must be configured";
|
||||
validConfig = false;
|
||||
}
|
||||
if (config.refreshInterval != null && config.refreshInterval <= 0) {
|
||||
errorMsg = "Parameter 'refresh' must be at least 1 second";
|
||||
validConfig = false;
|
||||
}
|
||||
|
||||
if (validConfig) {
|
||||
startAutomaticRefresh(config);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (refreshJob != null) {
|
||||
refreshJob.cancel(true);
|
||||
}
|
||||
services.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job refreshing the data
|
||||
*/
|
||||
private void startAutomaticRefresh(FroniusBridgeConfiguration config) {
|
||||
if (refreshJob == null || refreshJob.isCancelled()) {
|
||||
Runnable runnable = () -> {
|
||||
boolean online = false;
|
||||
try {
|
||||
InetAddress inet;
|
||||
inet = InetAddress.getByName(config.hostname);
|
||||
if (inet.isReachable(5000)) {
|
||||
online = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Connection Error: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!online) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"hostname or ip is not reachable");
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
for (FroniusBaseThingHandler service : services) {
|
||||
service.refresh(config);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int delay = (config.refreshInterval != null) ? config.refreshInterval.intValue() : DEFAULT_REFRESH_PERIOD;
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, delay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
|
||||
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
|
||||
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
||||
import org.openhab.binding.fronius.internal.api.BaseFroniusResponse;
|
||||
import org.openhab.binding.fronius.internal.api.InverterRealtimeResponse;
|
||||
import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeResponse;
|
||||
import org.openhab.binding.fronius.internal.api.ValueUnit;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link FroniusSymoInverterHandler} is responsible for updating the data, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
* @author Peter Schraffl - Added device status and error status channels
|
||||
*/
|
||||
public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
||||
|
||||
private static final int API_TIMEOUT = 5000;
|
||||
private final Logger logger = LoggerFactory.getLogger(FroniusSymoInverterHandler.class);
|
||||
private InverterRealtimeResponse inverterRealtimeResponse;
|
||||
private PowerFlowRealtimeResponse powerFlowResponse;
|
||||
private FroniusBaseDeviceConfiguration config;
|
||||
private final Gson gson;
|
||||
|
||||
public FroniusSymoInverterHandler(Thing thing) {
|
||||
super(thing);
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDescription() {
|
||||
return "Fronius Symo Inverter";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
|
||||
updateData(bridgeConfiguration, config);
|
||||
updateChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(FroniusBaseDeviceConfiguration.class);
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the channel from the last data retrieved
|
||||
*
|
||||
* @param channelId the id identifying the channel to be updated
|
||||
* @return the last retrieved data
|
||||
*/
|
||||
@Override
|
||||
protected Object getValue(String channelId) {
|
||||
String[] fields = StringUtils.split(channelId, "#");
|
||||
|
||||
String fieldName = fields[0];
|
||||
|
||||
if (inverterRealtimeResponse == null) {
|
||||
return null;
|
||||
}
|
||||
switch (fieldName) {
|
||||
case FroniusBindingConstants.InverterDataChannelDayEnergy:
|
||||
ValueUnit day = inverterRealtimeResponse.getBody().getData().getDayEnergy();
|
||||
if (day != null) {
|
||||
day.setUnit("kWh");
|
||||
}
|
||||
return day;
|
||||
case FroniusBindingConstants.InverterDataChannelPac:
|
||||
return inverterRealtimeResponse.getBody().getData().getPac();
|
||||
case FroniusBindingConstants.InverterDataChannelTotal:
|
||||
ValueUnit total = inverterRealtimeResponse.getBody().getData().getTotalEnergy();
|
||||
if (total != null) {
|
||||
total.setUnit("MWh");
|
||||
}
|
||||
return total;
|
||||
case FroniusBindingConstants.InverterDataChannelYear:
|
||||
ValueUnit year = inverterRealtimeResponse.getBody().getData().getYearEnergy();
|
||||
if (year != null) {
|
||||
year.setUnit("MWh");
|
||||
}
|
||||
return year;
|
||||
case FroniusBindingConstants.InverterDataChannelFac:
|
||||
return inverterRealtimeResponse.getBody().getData().getFac();
|
||||
case FroniusBindingConstants.InverterDataChannelIac:
|
||||
return inverterRealtimeResponse.getBody().getData().getIac();
|
||||
case FroniusBindingConstants.InverterDataChannelIdc:
|
||||
return inverterRealtimeResponse.getBody().getData().getIdc();
|
||||
case FroniusBindingConstants.InverterDataChannelUac:
|
||||
return inverterRealtimeResponse.getBody().getData().getUac();
|
||||
case FroniusBindingConstants.InverterDataChannelUdc:
|
||||
return inverterRealtimeResponse.getBody().getData().getUdc();
|
||||
case FroniusBindingConstants.InverterDataChannelDeviceStatusErrorCode:
|
||||
return inverterRealtimeResponse.getBody().getData().getDeviceStatus().getErrorCode();
|
||||
case FroniusBindingConstants.InverterDataChannelDeviceStatusStatusCode:
|
||||
return inverterRealtimeResponse.getBody().getData().getDeviceStatus().getStatusCode();
|
||||
}
|
||||
if (powerFlowResponse == null) {
|
||||
return null;
|
||||
}
|
||||
switch (fieldName) {
|
||||
case FroniusBindingConstants.PowerFlowpGrid:
|
||||
return powerFlowResponse.getBody().getData().getSite().getPgrid();
|
||||
case FroniusBindingConstants.PowerFlowpLoad:
|
||||
return powerFlowResponse.getBody().getData().getSite().getPload();
|
||||
case FroniusBindingConstants.PowerFlowpAkku:
|
||||
return powerFlowResponse.getBody().getData().getSite().getPakku();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get new data
|
||||
*/
|
||||
private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config) {
|
||||
inverterRealtimeResponse = getRealtimeData(bridgeConfiguration.hostname, config.deviceId);
|
||||
powerFlowResponse = getPowerFlowRealtime(bridgeConfiguration.hostname);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param type response class type
|
||||
* @param url to request
|
||||
* @return the object representation of the json response
|
||||
*/
|
||||
private <T extends BaseFroniusResponse> T collectDataFormUrl(Class<T> type, String url) {
|
||||
T result = null;
|
||||
boolean resultOk = false;
|
||||
String errorMsg = null;
|
||||
|
||||
try {
|
||||
logger.debug("URL = {}", url);
|
||||
String response = HttpUtil.executeUrl("GET", url, API_TIMEOUT);
|
||||
|
||||
if (response != null) {
|
||||
logger.debug("aqiResponse = {}", response);
|
||||
result = gson.fromJson(response, type);
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
errorMsg = "no data returned";
|
||||
} else {
|
||||
if (result.getHead().getStatus().getCode() == 0) {
|
||||
resultOk = true;
|
||||
} else {
|
||||
errorMsg = result.getHead().getStatus().getReason();
|
||||
}
|
||||
}
|
||||
if (!resultOk) {
|
||||
logger.debug("Error in fronius response: {}", errorMsg);
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
errorMsg = "Configuration is incorrect";
|
||||
logger.debug("Error running fronius request: {}", errorMsg);
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
logger.debug("Error running fronius request: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Update the thing status
|
||||
if (resultOk) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errorMsg);
|
||||
}
|
||||
return resultOk ? result : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the PowerFlowRealtimeDataRequest
|
||||
*
|
||||
* @param ip address of the device
|
||||
* @return {PowerFlowRealtimeResponse} the object representation of the json response
|
||||
*/
|
||||
private PowerFlowRealtimeResponse getPowerFlowRealtime(String ip) {
|
||||
String location = FroniusBindingConstants.POWERFLOW_REALTIME_DATA.replace("%IP%", StringUtils.trimToEmpty(ip));
|
||||
return collectDataFormUrl(PowerFlowRealtimeResponse.class, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the InverterRealtimeDataRequest
|
||||
*
|
||||
* @param ip address of the device
|
||||
* @param deviceId of the device
|
||||
* @return {InverterRealtimeResponse} the object representation of the json response
|
||||
*/
|
||||
private InverterRealtimeResponse getRealtimeData(String ip, int deviceId) {
|
||||
String location = FroniusBindingConstants.INVERTER_REALTIME_DATA_URL.replace("%IP%",
|
||||
StringUtils.trimToEmpty(ip));
|
||||
location = location.replace("%DEVICEID%", Integer.toString(deviceId));
|
||||
return collectDataFormUrl(InverterRealtimeResponse.class, location);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.math;
|
||||
|
||||
/**
|
||||
* Helper class for unit conversions
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class KilowattConverter {
|
||||
|
||||
public static double getConvertFactor(String fromUnit, String toUnit) {
|
||||
String adjustedFromUnit = fromUnit.replace("Wh", "");
|
||||
String adjustedtoUnit = toUnit.replace("Wh", "");
|
||||
return SiPrefixFactors.getFactorToBaseUnit(adjustedFromUnit) * 1
|
||||
/ SiPrefixFactors.getFactorToBaseUnit(adjustedtoUnit);
|
||||
}
|
||||
|
||||
public static double convertTo(double value, String fromUnit, String toUnit) {
|
||||
return value * getConvertFactor(fromUnit, toUnit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fronius.internal.math;
|
||||
|
||||
/**
|
||||
* Helper class for unit conversions
|
||||
*
|
||||
* @author Thomas Rokohl - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class SiPrefixFactors {
|
||||
|
||||
/**
|
||||
* return the relative factor to the base unit
|
||||
* k == 1000, M = 1000000 ...
|
||||
* Not completely!!! Rank from n to T
|
||||
*
|
||||
* @param prefix of the unit
|
||||
* @return relative factor to the base unit
|
||||
*/
|
||||
|
||||
public static double getFactorToBaseUnit(String prefix) {
|
||||
if (prefix.isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
switch (prefix) {
|
||||
case "T":
|
||||
return 1000000000000d;
|
||||
case "G":
|
||||
return 1000000000;
|
||||
case "M":
|
||||
return 1000000;
|
||||
case "k":
|
||||
return 1000;
|
||||
case "h":
|
||||
return 100;
|
||||
case "da":
|
||||
return 10;
|
||||
case "d":
|
||||
return 0.1;
|
||||
case "c":
|
||||
return 0.01;
|
||||
case "m":
|
||||
return 0.001;
|
||||
case "µ":
|
||||
return 0.000001;
|
||||
case "n":
|
||||
return 0.000000001;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="fronius" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Fronius Binding</name>
|
||||
<description>Binding for Fronius inverters.</description>
|
||||
<author>Thomas Rokohl, Gerrit Beine</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="fronius"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="bridge">
|
||||
<label>Fronius Bridge</label>
|
||||
<description>A bridge to connect Fronius devices</description>
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text" required="true" groupName="network">
|
||||
<context>network-address</context>
|
||||
<label>Hostname</label>
|
||||
<description>The hostname or IP address of the Fronius gateway/device</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" min="2">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in seconds.</description>
|
||||
<default>10</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="fronius"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- Powerinverter Thing Type -->
|
||||
<thing-type id="powerinverter">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Fronius Symo Inverter</label>
|
||||
<description>Fronius Symo power inverter</description>
|
||||
|
||||
<channels>
|
||||
<channel id="inverterdatachannelpac" typeId="pac"/>
|
||||
<channel id="inverterdatachanneldayenergy" typeId="day_energy"/>
|
||||
<channel id="inverterdatachanneltotal" typeId="total_energy"/>
|
||||
<channel id="inverterdatachannelyear" typeId="year_energy"/>
|
||||
<channel id="inverterdatachannelfac" typeId="fac"/>
|
||||
<channel id="inverterdatachanneliac" typeId="iac"/>
|
||||
<channel id="inverterdatachannelidc" typeId="idc"/>
|
||||
<channel id="inverterdatachanneluac" typeId="uac"/>
|
||||
<channel id="inverterdatachanneludc" typeId="udc"/>
|
||||
<channel id="inverterdatadevicestatuserrorcode" typeId="devicestatus_errorcode"/>
|
||||
<channel id="inverterdatadevicestatusstatuscode" typeId="devicestatus_statuscode"/>
|
||||
<channel id="powerflowchannelpgrid" typeId="pGrid"/>
|
||||
<channel id="powerflowchannelpload" typeId="pLoad"/>
|
||||
<channel id="powerflowchannelpakku" typeId="pAkku"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="deviceId" type="integer">
|
||||
<label>Device ID</label>
|
||||
<description>Specific device identifier</description>
|
||||
<default>1</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="day_energy">
|
||||
<item-type>Number</item-type>
|
||||
<label>Day Energy</label>
|
||||
<description>Energy generated on current day</description>
|
||||
<state pattern="%.2f kWh" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="pac">
|
||||
<item-type>Number</item-type>
|
||||
<label>AC Power</label>
|
||||
<description>AC power</description>
|
||||
<state pattern="%f W" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="total_energy">
|
||||
<item-type>Number</item-type>
|
||||
<label>Total Energy</label>
|
||||
<description>Energy generated overall</description>
|
||||
<state pattern="%.3f MWh" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="year_energy">
|
||||
<item-type>Number</item-type>
|
||||
<label>Year Energy</label>
|
||||
<description>Energy generated in current year</description>
|
||||
<state pattern="%.3f MWh" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fac">
|
||||
<item-type>Number</item-type>
|
||||
<label>AC Frequency</label>
|
||||
<description>AC frequency</description>
|
||||
<state pattern="%.2f Hz" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="iac">
|
||||
<item-type>Number</item-type>
|
||||
<label>AC Current</label>
|
||||
<description>AC current</description>
|
||||
<state pattern="%.2f A" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="idc">
|
||||
<item-type>Number</item-type>
|
||||
<label>DC Current</label>
|
||||
<description>DC current</description>
|
||||
<state pattern="%.2f A" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="uac">
|
||||
<item-type>Number</item-type>
|
||||
<label>AC Voltage</label>
|
||||
<description>AC voltage</description>
|
||||
<state pattern="%.1f V" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="udc">
|
||||
<item-type>Number</item-type>
|
||||
<label>DC Voltage</label>
|
||||
<description>DC voltage</description>
|
||||
<state pattern="%.1f V" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="devicestatus_errorcode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Error Code</label>
|
||||
<description>Current device error code</description>
|
||||
<state pattern="%d" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="devicestatus_statuscode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Status Code</label>
|
||||
<description>Current device status code</description>
|
||||
<state pattern="%d" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pGrid">
|
||||
<item-type>Number</item-type>
|
||||
<label>Grid Power</label>
|
||||
<description>Grid Power ( + from grid, - to grid )</description>
|
||||
<state pattern="%.2f W" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="pLoad">
|
||||
<item-type>Number</item-type>
|
||||
<label>Load Power</label>
|
||||
<description>Load Power ( + generator, - consumer )</description>
|
||||
<state pattern="%.2f W" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="pAkku">
|
||||
<item-type>Number</item-type>
|
||||
<label>Battery Power</label>
|
||||
<description>Battery Power ( + charge, - discharge )</description>
|
||||
<state pattern="%.2f W" readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user