]> git.basschouten.com Git - openhab-addons.git/commitdiff
[ipp] Added representation property and code improvements (#12039)
authorChristoph Weitkamp <github@christophweitkamp.de>
Fri, 14 Jan 2022 10:32:58 +0000 (11:32 +0100)
committerGitHub <noreply@github.com>
Fri, 14 Jan 2022 10:32:58 +0000 (11:32 +0100)
* Added representation property

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/IppBindingConstants.java
bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/IppHandlerFactory.java [deleted file]
bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/discovery/IppPrinterDiscoveryParticipant.java
bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/factory/IppHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/handler/IppPrinterHandler.java
bundles/org.openhab.binding.ipp/src/main/resources/OH-INF/thing/thing-types.xml

index 3dedca8a787b4dec9216f329d94f6de320895c44..5649587b363965afaeb00b72d89f3b4cf34313e2 100644 (file)
@@ -12,7 +12,6 @@
  */
 package org.openhab.binding.ipp.internal;
 
-import java.util.Collections;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -39,7 +38,8 @@ public class IppBindingConstants {
 
     public static final String PRINTER_PARAMETER_URL = "url";
     public static final String PRINTER_PARAMETER_NAME = "name";
+    public static final String PRINTER_PARAMETER_UUID = "uuid";
     public static final String PRINTER_PARAMETER_REFRESH_INTERVAL = "refresh";
 
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(PRINTER_THING_TYPE);
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(PRINTER_THING_TYPE);
 }
diff --git a/bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/IppHandlerFactory.java b/bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/IppHandlerFactory.java
deleted file mode 100644 (file)
index 897808e..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * 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.ipp.internal;
-
-import static org.openhab.binding.ipp.internal.IppBindingConstants.PRINTER_THING_TYPE;
-
-import org.openhab.binding.ipp.internal.handler.IppPrinterHandler;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.config.discovery.DiscoveryServiceRegistry;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-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;
-import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link IppHandlerFactory} is responsible for creating things and thing
- * handlers.
- *
- * @author Tobias Braeutigam - Initial contribution
- */
-@Component(service = ThingHandlerFactory.class, configurationPid = "binding.ipp")
-public class IppHandlerFactory extends BaseThingHandlerFactory {
-    private final Logger logger = LoggerFactory.getLogger(IppHandlerFactory.class);
-
-    private DiscoveryServiceRegistry discoveryServiceRegistry;
-
-    @Reference
-    protected void setDiscoveryServiceRegistry(DiscoveryServiceRegistry discoveryServiceRegistry) {
-        this.discoveryServiceRegistry = discoveryServiceRegistry;
-    }
-
-    protected void unsetDiscoveryServiceRegistry(DiscoveryServiceRegistry discoveryServiceRegistry) {
-        this.discoveryServiceRegistry = null;
-    }
-
-    @Override
-    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
-        return IppBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
-    }
-
-    @Override
-    public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID,
-            ThingUID bridgeUID) {
-        logger.trace("createThing({},{},{},{})", thingTypeUID, configuration, thingUID, bridgeUID);
-        if (IppBindingConstants.PRINTER_THING_TYPE.equals(thingTypeUID)) {
-            ThingUID deviceUID = getIppPrinterUID(thingTypeUID, thingUID, configuration);
-            logger.debug("creating thing {} from deviceUID: {}", thingTypeUID, deviceUID);
-            return super.createThing(thingTypeUID, configuration, deviceUID, null);
-        }
-        throw new IllegalArgumentException("The thing type {} " + thingTypeUID + " is not supported by the binding.");
-    }
-
-    private ThingUID getIppPrinterUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration) {
-        if (thingUID == null) {
-            String name = (String) configuration.get(IppBindingConstants.PRINTER_PARAMETER_NAME);
-            thingUID = new ThingUID(thingTypeUID, name);
-        }
-        return thingUID;
-    }
-
-    @Override
-    protected ThingHandler createHandler(Thing thing) {
-        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
-        if (thingTypeUID.equals(PRINTER_THING_TYPE)) {
-            return new IppPrinterHandler(thing, discoveryServiceRegistry);
-        }
-        return null;
-    }
-}
index 5ae1f96538dff86013b71c51d18b1c03c6bc52be..fe9a0f7166a174aac41f37f9b9e9296d05f13e06 100644 (file)
  */
 package org.openhab.binding.ipp.internal.discovery;
 
+import static org.openhab.binding.ipp.internal.IppBindingConstants.*;
+
 import java.net.InetAddress;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
 import javax.jmdns.ServiceInfo;
 
-import org.openhab.binding.ipp.internal.IppBindingConstants;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
 import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
@@ -36,13 +37,14 @@ import org.slf4j.LoggerFactory;
  * @author Tobias Bräutigam - Initial contribution
  */
 @Component
+@NonNullByDefault
 public class IppPrinterDiscoveryParticipant implements MDNSDiscoveryParticipant {
 
     private final Logger logger = LoggerFactory.getLogger(IppPrinterDiscoveryParticipant.class);
 
     @Override
     public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
-        return Collections.singleton(IppBindingConstants.PRINTER_THING_TYPE);
+        return Set.of(PRINTER_THING_TYPE);
     }
 
     @Override
@@ -51,15 +53,11 @@ public class IppPrinterDiscoveryParticipant implements MDNSDiscoveryParticipant
     }
 
     @Override
-    public ThingUID getThingUID(ServiceInfo service) {
-        if (service != null) {
-            logger.trace("ServiceInfo: {}", service);
-            if (service.getType() != null) {
-                if (service.getType().equals(getServiceType())) {
-                    String uidName = getUIDName(service);
-                    return new ThingUID(IppBindingConstants.PRINTER_THING_TYPE, uidName);
-                }
-            }
+    public @Nullable ThingUID getThingUID(ServiceInfo service) {
+        logger.trace("ServiceInfo: {}", service);
+        if (getServiceType().equals(service.getType())) {
+            String uidName = getUIDName(service);
+            return new ThingUID(PRINTER_THING_TYPE, uidName);
         }
         return null;
     }
@@ -68,8 +66,7 @@ public class IppPrinterDiscoveryParticipant implements MDNSDiscoveryParticipant
         return service.getName().replaceAll("[^A-Za-z0-9_]", "_");
     }
 
-    private InetAddress getIpAddress(ServiceInfo service) {
-        InetAddress address = null;
+    private @Nullable InetAddress getIpAddress(ServiceInfo service) {
         for (InetAddress addr : service.getInet4Addresses()) {
             return addr;
         }
@@ -77,37 +74,36 @@ public class IppPrinterDiscoveryParticipant implements MDNSDiscoveryParticipant
         for (InetAddress addr : service.getInet6Addresses()) {
             return addr;
         }
-        return address;
+        return null;
     }
 
     @Override
-    public DiscoveryResult createResult(ServiceInfo service) {
-        DiscoveryResult result = null;
+    public @Nullable DiscoveryResult createResult(ServiceInfo service) {
         String rp = service.getPropertyString("rp");
         if (rp == null) {
             return null;
         }
         ThingUID uid = getThingUID(service);
         if (uid != null) {
-            Map<String, Object> properties = new HashMap<>(2);
             // remove the domain from the name
             InetAddress ip = getIpAddress(service);
             if (ip == null) {
                 return null;
             }
             String inetAddress = ip.toString().substring(1); // trim leading slash
-
             String label = service.getName();
-
             int port = service.getPort();
+            String uuid = service.getPropertyString("UUID");
 
-            properties.put(IppBindingConstants.PRINTER_PARAMETER_URL, "http://" + inetAddress + ":" + port + "/" + rp);
-            properties.put(IppBindingConstants.PRINTER_PARAMETER_NAME, label);
+            Map<String, Object> properties = Map.of( //
+                    PRINTER_PARAMETER_URL, "http://" + inetAddress + ":" + port + "/" + rp, //
+                    PRINTER_PARAMETER_NAME, label, //
+                    PRINTER_PARAMETER_UUID, uuid //
+            );
 
-            result = DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build();
-            logger.debug("Created a DiscoveryResult {} for ipp printer on host '{}' name '{}'", result,
-                    properties.get(IppBindingConstants.PRINTER_PARAMETER_URL), label);
+            return DiscoveryResultBuilder.create(uid).withProperties(properties)
+                    .withRepresentationProperty(PRINTER_PARAMETER_UUID).withLabel(label).build();
         }
-        return result;
+        return null;
     }
 }
diff --git a/bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/factory/IppHandlerFactory.java b/bundles/org.openhab.binding.ipp/src/main/java/org/openhab/binding/ipp/internal/factory/IppHandlerFactory.java
new file mode 100644 (file)
index 0000000..31e7670
--- /dev/null
@@ -0,0 +1,86 @@
+/**
+ * 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.ipp.internal.factory;
+
+import static org.openhab.binding.ipp.internal.IppBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.ipp.internal.handler.IppPrinterHandler;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.config.discovery.DiscoveryServiceRegistry;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+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.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link IppHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Tobias Braeutigam - Initial contribution
+ */
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.ipp")
+@NonNullByDefault
+public class IppHandlerFactory extends BaseThingHandlerFactory {
+    private final Logger logger = LoggerFactory.getLogger(IppHandlerFactory.class);
+
+    private final DiscoveryServiceRegistry discoveryServiceRegistry;
+
+    @Activate
+    public IppHandlerFactory(final @Reference DiscoveryServiceRegistry discoveryServiceRegistry) {
+        this.discoveryServiceRegistry = discoveryServiceRegistry;
+    }
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
+            @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
+        logger.trace("createThing({},{},{},{})", thingTypeUID, configuration, thingUID, bridgeUID);
+        if (PRINTER_THING_TYPE.equals(thingTypeUID)) {
+            ThingUID deviceUID = getIppPrinterUID(thingTypeUID, thingUID, configuration);
+            logger.debug("creating thing {} from deviceUID: {}", thingTypeUID, deviceUID);
+            return super.createThing(thingTypeUID, configuration, deviceUID, null);
+        }
+        throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the binding.");
+    }
+
+    private ThingUID getIppPrinterUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
+            Configuration configuration) {
+        if (thingUID == null) {
+            String name = (String) configuration.get(PRINTER_PARAMETER_NAME);
+            return new ThingUID(thingTypeUID, name);
+        }
+        return thingUID;
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+        if (PRINTER_THING_TYPE.equals(thingTypeUID)) {
+            return new IppPrinterHandler(thing, discoveryServiceRegistry);
+        }
+        return null;
+    }
+}
index 6c56e09271afb18b508b0361c82fd1a6bd178748..86e9c1e65a2df84b311650f1a8fe03c7b1838648 100644 (file)
  */
 package org.openhab.binding.ipp.internal.handler;
 
+import static org.openhab.binding.ipp.internal.IppBindingConstants.*;
+
 import java.math.BigDecimal;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.Set;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 import org.cups4j.CupsPrinter;
 import org.cups4j.WhichJobsEnum;
-import org.openhab.binding.ipp.internal.IppBindingConstants;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.config.discovery.DiscoveryListener;
 import org.openhab.core.config.discovery.DiscoveryResult;
@@ -46,36 +49,34 @@ import org.slf4j.LoggerFactory;
  *
  * @author Tobias Braeutigam - Initial contribution
  */
+@NonNullByDefault
 public class IppPrinterHandler extends BaseThingHandler implements DiscoveryListener {
 
+    /**
+     * Refresh interval defaults to every minute (60s)
+     */
+    private static final int DEFAULT_REFRESH_INTERVAL_IN_SECONDS = 60;
+
     private final Logger logger = LoggerFactory.getLogger(IppPrinterHandler.class);
 
-    private URL url;
-    private String name;
-    private CupsPrinter printer;
+    private @Nullable URL url;
+    private @Nullable CupsPrinter printer;
 
-    private int refresh = 60; // refresh every minute as default
-    ScheduledFuture<?> refreshJob;
+    private @Nullable ScheduledFuture<?> refreshJob;
 
-    private DiscoveryServiceRegistry discoveryServiceRegistry;
+    private final DiscoveryServiceRegistry discoveryServiceRegistry;
 
     public IppPrinterHandler(Thing thing, DiscoveryServiceRegistry discoveryServiceRegistry) {
         super(thing);
-        if (discoveryServiceRegistry != null) {
-            this.discoveryServiceRegistry = discoveryServiceRegistry;
-        }
+        this.discoveryServiceRegistry = discoveryServiceRegistry;
     }
 
     @Override
     public void initialize() {
         Configuration config = getThing().getConfiguration();
+        String name = (String) config.get(PRINTER_PARAMETER_NAME);
         try {
-            Object obj = config.get(IppBindingConstants.PRINTER_PARAMETER_URL);
-            name = (String) config.get(IppBindingConstants.PRINTER_PARAMETER_NAME);
-            if (config.get(IppBindingConstants.PRINTER_PARAMETER_REFRESH_INTERVAL) != null) {
-                BigDecimal ref = (BigDecimal) config.get(IppBindingConstants.PRINTER_PARAMETER_REFRESH_INTERVAL);
-                refresh = ref.intValue();
-            }
+            Object obj = config.get(PRINTER_PARAMETER_URL);
             if (obj instanceof URL) {
                 url = (URL) obj;
             } else if (obj instanceof String) {
@@ -83,36 +84,45 @@ public class IppPrinterHandler extends BaseThingHandler implements DiscoveryList
             }
             printer = new CupsPrinter(url, name, false);
         } catch (MalformedURLException e) {
-            logger.error("malformed url {}, printer thing creation failed",
-                    config.get(IppBindingConstants.PRINTER_PARAMETER_URL));
+            logger.error("malformed url {}, printer thing creation failed", config.get(PRINTER_PARAMETER_URL));
         }
-        // until we get an update put the Thing offline
-        updateStatus(ThingStatus.OFFLINE);
-        deviceOnlineWatchdog();
-        if (this.discoveryServiceRegistry != null) {
-            this.discoveryServiceRegistry.addDiscoveryListener(this);
+
+        int refresh = DEFAULT_REFRESH_INTERVAL_IN_SECONDS;
+        Object obj = config.get(PRINTER_PARAMETER_REFRESH_INTERVAL);
+        if (obj != null) {
+            BigDecimal ref = (BigDecimal) obj;
+            refresh = ref.intValue();
         }
+
+        updateStatus(ThingStatus.UNKNOWN);
+        deviceOnlineWatchdog(refresh);
+        discoveryServiceRegistry.addDiscoveryListener(this);
     }
 
     @Override
     public void dispose() {
-        if (refreshJob != null && !refreshJob.isCancelled()) {
-            refreshJob.cancel(true);
-            refreshJob = null;
-        }
+        stopRefreshJob();
         logger.debug("IppPrinterHandler {} disposed.", url);
         super.dispose();
     }
 
-    private void deviceOnlineWatchdog() {
-        Runnable runnable = () -> {
+    private void deviceOnlineWatchdog(int refresh) {
+        stopRefreshJob();
+        refreshJob = scheduler.scheduleWithFixedDelay(() -> {
             try {
                 onDeviceStateChanged(printer);
             } catch (Exception e) {
                 logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
             }
-        };
-        refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, refresh, TimeUnit.SECONDS);
+        }, 0, refresh, TimeUnit.SECONDS);
+    }
+
+    private void stopRefreshJob() {
+        ScheduledFuture<?> localRefreshJob = refreshJob;
+        if (localRefreshJob != null && !localRefreshJob.isCancelled()) {
+            localRefreshJob.cancel(true);
+            refreshJob = null;
+        }
     }
 
     @Override
@@ -123,25 +133,24 @@ public class IppPrinterHandler extends BaseThingHandler implements DiscoveryList
         }
     }
 
-    public void onDeviceStateChanged(CupsPrinter device) {
-        if (device.getPrinterURL().equals(url)) {
+    public void onDeviceStateChanged(@Nullable CupsPrinter device) {
+        if (device != null && device.getPrinterURL().equals(url)) {
             boolean online = false;
             try {
-                updateState(new ChannelUID(getThing().getUID(), IppBindingConstants.JOBS_CHANNEL),
-                        new DecimalType(device.getJobs(WhichJobsEnum.ALL, "", false).size()));
+                updateState(JOBS_CHANNEL, new DecimalType(device.getJobs(WhichJobsEnum.ALL, "", false).size()));
                 online = true;
             } catch (Exception e) {
                 logger.debug("error updating jobs channel, reason: {}", e.getMessage());
             }
             try {
-                updateState(new ChannelUID(getThing().getUID(), IppBindingConstants.WAITING_JOBS_CHANNEL),
+                updateState(WAITING_JOBS_CHANNEL,
                         new DecimalType(device.getJobs(WhichJobsEnum.NOT_COMPLETED, "", false).size()));
                 online = true;
             } catch (Exception e) {
                 logger.debug("error updating waiting-jobs channel, reason: {}", e.getMessage());
             }
             try {
-                updateState(new ChannelUID(getThing().getUID(), IppBindingConstants.DONE_JOBS_CHANNEL),
+                updateState(DONE_JOBS_CHANNEL,
                         new DecimalType(device.getJobs(WhichJobsEnum.COMPLETED, "", false).size()));
                 online = true;
             } catch (Exception e) {
@@ -155,21 +164,21 @@ public class IppPrinterHandler extends BaseThingHandler implements DiscoveryList
 
     @Override
     public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
-        if (result.getThingUID().equals(this.getThing().getUID())) {
+        if (result.getThingUID().equals(getThing().getUID())) {
             updateStatus(ThingStatus.ONLINE);
         }
     }
 
     @Override
     public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
-        if (thingUID.equals(this.getThing().getUID())) {
+        if (thingUID.equals(getThing().getUID())) {
             updateStatus(ThingStatus.OFFLINE);
         }
     }
 
     @Override
-    public Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
-            Collection<ThingTypeUID> thingTypeUIDs, ThingUID bridgeUID) {
-        return Collections.emptyList();
+    public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
+            @Nullable Collection<ThingTypeUID> thingTypeUIDs, @Nullable ThingUID bridgeUID) {
+        return Set.of();
     }
 }
index 554fdccdf0ce4e13b85824a42902801e0bf6e72b..518157ecb79b2fc6d92a646c6177074ca3ca4403 100644 (file)
@@ -11,6 +11,9 @@
                        <channel id="waitingJobs" typeId="waitingJobs"/>
                        <channel id="doneJobs" typeId="doneJobs"/>
                </channels>
+
+               <representation-property>uuid</representation-property>
+
                <config-description>
                        <parameter name="name" type="text" required="true">
                                <label>Name</label>
                <item-type>Number</item-type>
                <label>Total Jobs</label>
                <description>Total number of print jobs on the printer</description>
-               <state readOnly="true"></state>
+               <state readOnly="true"/>
        </channel-type>
        <channel-type id="waitingJobs">
                <item-type>Number</item-type>
                <label>Waiting Jobs</label>
                <description>Number of waiting print jobs on the printer</description>
-               <state readOnly="true"></state>
+               <state readOnly="true"/>
        </channel-type>
        <channel-type id="doneJobs" advanced="true">
                <item-type>Number</item-type>
                <label>Done Jobs</label>
                <description>Number of completed print jobs on the printer</description>
-               <state readOnly="true"></state>
+               <state readOnly="true"/>
        </channel-type>
 
 </thing:thing-descriptions>