[Homewizard] Initial contribution (#9831)

Signed-off-by: Daniël van Os <daniel@supercell.nl>
This commit is contained in:
Daniël van Os
2021-04-10 10:18:21 +02:00
committed by GitHub
parent 5ba64517ff
commit 95cdc3cb35
14 changed files with 883 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.homewizard-${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-homewizard" description="HomeWizard Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.homewizard/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2021 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.homewizard.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link HomeWizardBindingConstants} class defines common constants, which are
* used across the full binding.
*
* @author Daniël van Os - Initial contribution
*/
@NonNullByDefault
public class HomeWizardBindingConstants {
private static final String BINDING_ID = "homewizard";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_P1_WIFI_METER = new ThingTypeUID(BINDING_ID, "p1_wifi_meter");
// List of all Channel ids
public static final String CHANNEL_ENERGY_IMPORT_T1 = "total_energy_import_t1";
public static final String CHANNEL_ENERGY_IMPORT_T2 = "total_energy_import_t2";
public static final String CHANNEL_ENERGY_EXPORT_T1 = "total_energy_export_t1";
public static final String CHANNEL_ENERGY_EXPORT_T2 = "total_energy_export_t2";
public static final String CHANNEL_ACTIVE_POWER = "active_power";
public static final String CHANNEL_ACTIVE_POWER_L1 = "active_power_l1";
public static final String CHANNEL_ACTIVE_POWER_L2 = "active_power_l2";
public static final String CHANNEL_ACTIVE_POWER_L3 = "active_power_l3";
public static final String CHANNEL_TOTAL_GAS = "total_gas";
public static final String CHANNEL_GAS_TIMESTAMP = "gas_timestamp";
public static final String PROPERTY_METER_MODEL = "meterModel";
public static final String PROPERTY_METER_VERSION = "meterVersion";
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2021 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.homewizard.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link HomeWizardConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Daniël van Os - Initial contribution
*/
@NonNullByDefault
public class HomeWizardConfiguration {
/**
* IP Address or host for the P1 Meter
*/
public String ipAddress = "";
/**
* Refresh delay in seconds
*/
public Integer refreshDelay = 5;
}

View File

@@ -0,0 +1,200 @@
/**
* Copyright (c) 2010-2021 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.homewizard.internal;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* The {@link HomeWizardHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Daniël van Os - Initial contribution
*/
@NonNullByDefault
public class HomeWizardHandler extends BaseThingHandler {
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
private HomeWizardConfiguration config = new HomeWizardConfiguration();
private @Nullable ScheduledFuture<?> pollingJob;
private String apiURL = "";
private String meterModel = "";
private int meterVersion = 0;
/**
* Constructor
*
* @param thing The thing to handle
*/
public HomeWizardHandler(Thing thing) {
super(thing);
}
/**
* Not listening to any commands.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
/**
* If a host has been specified start polling it
*/
@Override
public void initialize() {
config = getConfigAs(HomeWizardConfiguration.class);
if (configure()) {
pollingJob = scheduler.scheduleWithFixedDelay(this::pollingCode, 0, config.refreshDelay, TimeUnit.SECONDS);
}
}
/**
* Check the current configuration
*
* @return true if the configuration is ok to start polling, false otherwise
*/
private boolean configure() {
if (config.ipAddress.trim().isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Missing ipAddress/host configuration");
return false;
} else {
updateStatus(ThingStatus.UNKNOWN);
apiURL = String.format("http://%s/api/v1/data", config.ipAddress.trim());
return true;
}
}
/**
* dispose: stop the poller
*/
@Override
public void dispose() {
var job = pollingJob;
if (job != null) {
job.cancel(true);
}
pollingJob = null;
}
/**
* The actual polling loop
*/
private void pollingCode() {
final String result;
try {
result = HttpUtil.executeUrl("GET", apiURL, 30000);
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Unable to query P1 Meter: %s", e.getMessage()));
return;
}
if (result.trim().isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"P1 Meter API returned empty status");
return;
}
P1Payload payload = gson.fromJson(result, P1Payload.class);
if (payload == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Unable to parse response from P1 meter");
return;
}
if ("".equals(payload.getMeterModel())) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Results from API are empty");
return;
}
updateStatus(ThingStatus.ONLINE);
if (!meterModel.equals(payload.getMeterModel())) {
meterModel = payload.getMeterModel();
updateProperty(HomeWizardBindingConstants.PROPERTY_METER_MODEL, meterModel);
}
if (meterVersion != payload.getSmrVersion()) {
meterVersion = payload.getSmrVersion();
updateProperty(HomeWizardBindingConstants.PROPERTY_METER_VERSION, String.format("%d", meterVersion));
}
updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T1,
new QuantityType<>(payload.getTotalEnergyImportT1Kwh(), Units.KILOWATT_HOUR));
updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T2,
new QuantityType<>(payload.getTotalEnergyImportT2Kwh(), Units.KILOWATT_HOUR));
updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T1,
new QuantityType<>(payload.getTotalEnergyExportT1Kwh(), Units.KILOWATT_HOUR));
updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T2,
new QuantityType<>(payload.getTotalEnergyExportT2Kwh(), Units.KILOWATT_HOUR));
updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER,
new QuantityType<>(payload.getActivePowerW(), Units.WATT));
updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L1,
new QuantityType<>(payload.getActivePowerL1W(), Units.WATT));
updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L2,
new QuantityType<>(payload.getActivePowerL2W(), Units.WATT));
updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L3,
new QuantityType<>(payload.getActivePowerL3W(), Units.WATT));
updateState(HomeWizardBindingConstants.CHANNEL_TOTAL_GAS,
new QuantityType<>(payload.getTotalGasM3(), SIUnits.CUBIC_METRE));
// 210119164000
long dtv = payload.getGasTimestamp();
long seconds = dtv % 100;
dtv /= 100;
long minutes = dtv % 100;
dtv /= 100;
long hours = dtv % 100;
dtv /= 100;
long day = dtv % 100;
dtv /= 100;
long month = dtv % 100;
dtv /= 100;
long year = dtv + 2000; // Where (When?) have I seen this before?
DateTimeType dtt = DateTimeType
.valueOf(String.format("%04d-%02d-%02dT%02d:%02d:%02d", year, month, day, hours, minutes, seconds));
updateState(HomeWizardBindingConstants.CHANNEL_GAS_TIMESTAMP, dtt);
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2021 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.homewizard.internal;
import static org.openhab.binding.homewizard.internal.HomeWizardBindingConstants.THING_TYPE_P1_WIFI_METER;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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 HomeWizardHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Daniël van Os - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.homewizard", service = ThingHandlerFactory.class)
public class HomeWizardHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_P1_WIFI_METER);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_P1_WIFI_METER.equals(thingTypeUID)) {
return new HomeWizardHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,308 @@
/**
* Copyright (c) 2010-2021 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.homewizard.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* Class that provides storage for the json object obtained from the P1 meter API
*
* @author Daniël van Os - Initial contribution
*
*/
@NonNullByDefault
public class P1Payload {
private int smrVersion = 0;
private String meterModel = "";
private String wifiSsid = "";
private int wifiStrength = 0;
@SerializedName("total_power_import_t1_kwh")
private double totalEnergyImportT1Kwh;
@SerializedName("total_power_import_t2_kwh")
private double totalEnergyImportT2Kwh;
@SerializedName("total_power_export_t1_kwh")
private double totalEnergyExportT1Kwh;
@SerializedName("total_power_export_t2_kwh")
private double totalEnergyExportT2Kwh;
private double activePowerW;
private double activePowerL1W;
private double activePowerL2W;
private double activePowerL3W;
private double totalGasM3;
private long gasTimestamp;
/**
* Getter for the smart meter version
*
* @return The most recent smart meter version obtained from the API
*/
public int getSmrVersion() {
return smrVersion;
}
/**
* Setter for the smart meter version
*
* @param smrVersion The smart meter version to set
*/
public void setSmrVersion(int smrVersion) {
this.smrVersion = smrVersion;
}
/**
* Getter for the meter model
*
* @return meter model
*/
public String getMeterModel() {
return meterModel;
}
/**
* Setter for the meter model
*
* @param meterModel meter model
*/
public void setMeterModel(String meterModel) {
this.meterModel = meterModel;
}
/**
* Getter for the meter's wifi ssid
*
* @return the meter's wifi sid
*/
public String getWifiSsid() {
return wifiSsid;
}
/**
* Setter for the wifi ssid
*
* @param wifiSsid wifi ssid
*/
public void setWifiSsid(String wifiSsid) {
this.wifiSsid = wifiSsid;
}
/**
* Getter for the wifi rssi
*
* @return wifi rssi
*/
public int getWifiStrength() {
return wifiStrength;
}
/**
* Setter for the wifi rssi
*
* @param wifiStrength wifi rssi
*/
public void setWifiStrength(int wifiStrength) {
this.wifiStrength = wifiStrength;
}
/**
* Getter for the total imported energy on counter 1
*
* @return total imported energy on counter 1
*/
public double getTotalEnergyImportT1Kwh() {
return totalEnergyImportT1Kwh;
}
/**
* Setter for the total imported energy on counter 1
*
* @param totalEnergyImportT1Kwh total imported energy on counter 1
*/
public void setTotalEnergyImportT1Kwh(double totalEnergyImportT1Kwh) {
this.totalEnergyImportT1Kwh = totalEnergyImportT1Kwh;
}
/**
* Getter for the total imported energy on counter 2
*
* @return total imported energy on counter 2
*/
public double getTotalEnergyImportT2Kwh() {
return totalEnergyImportT2Kwh;
}
/**
* Setter for the total imported energy on counter 2
*
* @param totalEnergyImportT2Kwh
*/
public void setTotalEnergyImportT2Kwh(double totalEnergyImportT2Kwh) {
this.totalEnergyImportT2Kwh = totalEnergyImportT2Kwh;
}
/**
* Getter for the total exported energy on counter 1
*
* @return total exported energy on counter 1
*/
public double getTotalEnergyExportT1Kwh() {
return totalEnergyExportT1Kwh;
}
/**
* Setter for the total exported energy on counter 1
*
* @param totalEnergyExportT1Kwh
*/
public void setTotalEnergyExportT1Kwh(double totalEnergyExportT1Kwh) {
this.totalEnergyExportT1Kwh = totalEnergyExportT1Kwh;
}
/**
* Getter for the total exported energy on counter 2
*
* @return total exported energy on counter 2
*/
public double getTotalEnergyExportT2Kwh() {
return totalEnergyExportT2Kwh;
}
/**
* Setter for the total exported energy on counter 2
*
* @param totalEnergyExportT2Kwh
*/
public void setTotalEnergyExportT2Kwh(double totalEnergyExportT2Kwh) {
this.totalEnergyExportT2Kwh = totalEnergyExportT2Kwh;
}
/**
* Getter for the current active total power
*
* @return current active total power
*/
public double getActivePowerW() {
return activePowerW;
}
/**
* Setter for the current active total power
*
* @param activePowerW
*/
public void setActivePowerW(double activePowerW) {
this.activePowerW = activePowerW;
}
/**
* Getter for the current active total power on phase 1
*
* @return current active total power on phase 1
*/
public double getActivePowerL1W() {
return activePowerL1W;
}
/**
* Setter for the current active power on phase 1
*
* @param activePowerL1W current active total power on phase 1
*/
public void setActivePowerL1W(double activePowerL1W) {
this.activePowerL1W = activePowerL1W;
}
/**
* Getter for the current active total power on phase 2
*
* @return current active total power on phase 2
*/
public double getActivePowerL2W() {
return activePowerL2W;
}
/**
* Setter for the current active power on phase 2
*
* @param activePowerL2W current active total power on phase 2
*/
public void setActivePowerL2W(double activePowerL2W) {
this.activePowerL2W = activePowerL2W;
}
/**
* Getter for the current active total power on phase 3
*
* @return current active total power on phase 3
*/
public double getActivePowerL3W() {
return activePowerL3W;
}
/**
* Setter for the current active power on phase 3
*
* @param activePowerL3W current active total power on phase 3
*/
public void setActivePowerL3W(double activePowerL3W) {
this.activePowerL3W = activePowerL3W;
}
/**
* Getter for the total imported gas volume
*
* @return total imported gas volume
*/
public double getTotalGasM3() {
return totalGasM3;
}
/**
* Setter for the total imported gas volume
*
* @param totalGasM3 total imported gas volume
*/
public void setTotalGasM3(double totalGasM3) {
this.totalGasM3 = totalGasM3;
}
/**
* Getter for the time stamp of the last gas update
*
* @return time stamp of the last gas update
*/
public long getGasTimestamp() {
return gasTimestamp;
}
/**
* Setter for the time stamp of the last gas update
*
* @param gasTimestamp time stamp of the last gas update
*/
public void setGasTimestamp(long gasTimestamp) {
this.gasTimestamp = gasTimestamp;
}
@Override
public String toString() {
return String.format("P1 [version: %d model: %s ssid: %s signal: %d"
+ " imp1: %f imp2: %f exp1: %f exp2: %f active: %f active1: %f active2: %f active3: %f gas: %f timestamp: %.0f]",
smrVersion, meterModel, wifiSsid, wifiStrength, totalEnergyImportT1Kwh, totalEnergyImportT2Kwh,
totalEnergyExportT1Kwh, totalEnergyExportT2Kwh, activePowerW, activePowerL1W, activePowerL2W,
activePowerL3W, totalGasM3, gasTimestamp);
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="homewizard" 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>HomeWizard Binding</name>
<description>This binding provides access to the data provided by the HomeWizard Wi-Fi P1 meter on it's local HTTP
interface.</description>
</binding:binding>

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="homewizard"
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">
<thing-type id="p1_wifi_meter">
<label>HomeWizard Wi-Fi P1 Meter</label>
<description>This thing provides the measurement data that is available through the http interface of the HomeWizard
Wi-Fi P1 Meter.</description>
<channels>
<channel id="total_energy_import_t1" typeId="total_energy_import_t1"/>
<channel id="total_energy_import_t2" typeId="total_energy_import_t2"/>
<channel id="total_energy_export_t1" typeId="total_energy_export_t1"/>
<channel id="total_energy_export_t2" typeId="total_energy_export_t2"/>
<channel id="active_power" typeId="active_power"/>
<channel id="active_power_l1" typeId="active_power_l1"/>
<channel id="active_power_l2" typeId="active_power_l2"/>
<channel id="active_power_l3" typeId="active_power_l3"/>
<channel id="total_gas" typeId="total_gas"/>
<channel id="gas_timestamp" typeId="gas_timestamp"/>
</channels>
<properties>
<property name="meterModel">Unknown</property>
</properties>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<label>Network Address</label>
<description>The IP or host name of the P1 Meter.</description>
<context>network-address</context>
</parameter>
<parameter name="refreshDelay" type="integer" min="1" unit="s">
<label>Refresh Interval</label>
<description>The refresh interval in seconds for polling the P1 Meter.</description>
<default>5</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="total_energy_import_t1">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy Counter 1</label>
<description>This channel provides the most recently reported total imported energy in kWh by counter 1, most commonly
used for import during the night or weekend.</description>
</channel-type>
<channel-type id="total_energy_import_t2">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy Counter 2</label>
<description>
This channel provides the most recently reported total imported energy in kWh by counter 2, most commonly
used for import during the day.
</description>
</channel-type>
<channel-type id="total_energy_export_t1">
<item-type>Number:Energy</item-type>
<label>Total Exported Energy Counter 1</label>
<description>
This channel provides the most recently reported total exported energy in kWh by counter 1, most commonly
used for export during the night or weekend.
</description>
</channel-type>
<channel-type id="total_energy_export_t2">
<item-type>Number:Energy</item-type>
<label>Total Exported Energy Counter 2</label>
<description>
This channel provides the most recently reported total exported energy in kWh by counter 2, most commonly
used for export during the day.
</description>
</channel-type>
<channel-type id="active_power">
<item-type>Number:Power</item-type>
<label>Current Total Net Power</label>
<description>
This channel provides the current net total power in W. It will be below 0 if power is currently being
exported.
</description>
</channel-type>
<channel-type id="active_power_l1">
<item-type>Number:Power</item-type>
<label>Current Phase 1 Net Power</label>
<description>
This channel provides the current net phase 1 power in W. It will be below 0 if power is currently being
exported.
</description>
</channel-type>
<channel-type id="active_power_l2">
<item-type>Number:Power</item-type>
<label>Current Phase 2 Net Power</label>
<description>
This channel provides the current net phase 2 power in W. It will be below 0 if power is currently being
exported. It will be 0 for single phase systems.
</description>
</channel-type>
<channel-type id="active_power_l3">
<item-type>Number:Power</item-type>
<label>Current Phase 3 Net Power</label>
<description>
This channel provides the current net phase 3 power in W. It will be below 0 if power is currently being
exported. It will be 0 for single phase systems.
</description>
</channel-type>
<channel-type id="total_gas">
<item-type>Number:Volume</item-type>
<label>Total Imported Gas</label>
<description>
This channel provides the most recently reported total imported gas in m^3. It does not get updated as
frequently as the data in the other channels, the gas_timestamp channel provides the time stamp of the most recent
update.
</description>
</channel-type>
<channel-type id="gas_timestamp">
<item-type>DateTime</item-type>
<label>Gas Update Time Stamp</label>
<description>
This channel provides the time stamp of the total_gas measurement.
</description>
</channel-type>
</thing:thing-descriptions>