Is it possible to make implementing discovery easier for binding developers?

Hi.

I am trying to add discovery to my new binding. I find it super hard. Suddenly I need to deal with kar files instead of jar files, openhab needs restarts to pick up changes instead of just copying a jar file, and I get errors I have no idea about how to investigate. (The last one is “Error downloading mvn:org.jmdns/jmdns/3.5.9”). I don’t need mDNS. I just need an object to call thingDiscovered() on.

I have added discovery to another binding earlier, and managed to make it work in the end. But I do remember that I had lots of problems and that it suddenly started working.

I think I have the simplest use case possible. I don’t want to locate bridges, just things inside the bridge. My startScan() method is around 20 lines, and I have about 30 lines of enterprise ceremony to make it a prototype component and to make the bridge handler communicate that it needs it in some way.
My bridge handler knows everything it needs to create discovery results, so it doesn’t really need it.

I understand that when I want to discover bridges, I need a component for discovery, but is it overkill for discovery that is bound to a bridge? Especially if you have only discovery that is bound to a bridge?

Is it possible (or could we make it possible) to inject (@Reference) something in the handler factory constructor which has a thingDiscovered() method? I think it would be more user friendly for binding developers.

You do not need a Kar, a simple jar that is created with ‘mvn Clean install’ is all that is needed. Possible you are missing dependacies that are needed that is causing trouble? If so, you can download the jar for any dependacies and drop them into the addons folder.

I do not find a restart is needed but I have not made a change to a discovery service recently to know if that has changed.
There is cache that may need to get cleared if a restart is not enough but I have not needed to do that in a long time.

That sound like it is missing a dependancy. Google ‘jmdns maven’ and you will find the place you can download the jar from. Sometimes when you take care of that a new one will then start as that jmdns may need two other dependacies.

If you do not believe you need this library, then look at the top of your Java files at the import statements and one of your used classes will have that in it.

Where possible try to use the libraries / dependacies that the development rules guidelines suggest.

Don’t get frustrated or grind wheels, just post here and I am sure you will get help. Thanks for being a contributor.

1 Like

Do you have a link to where your discovery code can be read?

From memory you do not implement a normal discovery service for mdns. You use a different discovery method and the framework finds the mdns result for you and then you just process the result. If you provide a link to your code someone can take a quick look and then suggest another merged example that you can draw from. It’s open-source so feel free to grab what you need.

I am getting a little bit closer, I have installed 2 different jars (org.osgi.service.component.annotations/1.5.0, org.jmdns/3.5.9) to the runtime directory, and I have added openhab-transport-upnp to the features file (), and now I get

java.lang.NoClassDefFoundError: org/openhab/core/config/discovery/AbstractThingHandlerDiscoveryService
        at org.openhab.binding.airgradient.internal.handler.AirGradientAPIHandler.getServices(AirGradientAPIHandler.java:226) ~[?:?]
Caused by: java.lang.ClassNotFoundException: org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService cannot be found by org.openhab.binding.airgradient_4.2.0.202401102113

Other plugins which need discovery don’t have anything in their features.xml files.

I havent checked it in yet because I haven’t gotten the code to work, but

@Component(scope = ServiceScope.PROTOTYPE, service = AirGradientLocationDiscoveryService.class)
@NonNullByDefault
public class AirGradientLocationDiscoveryService extends AbstractThingHandlerDiscoveryService<AirGradientAPIHandler> {

    private final Logger logger = LoggerFactory.getLogger(AirGradientLocationDiscoveryService.class);

    private @NonNullByDefault({}) ThingUID bridgeUid;

    public AirGradientLocationDiscoveryService() {
        super(AirGradientAPIHandler.class, Set.of(THING_TYPE_LOCATION), SEARCH_TIME, false);
        logger.debug("Constructing discovery service");
    }

    @Override
    public void activate() {
        logger.debug("Activating discovery service");
        super.activate();
    }

    @Override
    public void initialize() {
        logger.debug("Initializing discovery service");
        bridgeUid = thingHandler.getThing().getUID();
        super.initialize();
    }

    @Override
    protected void startScan() {
        logger.debug("Starting Location discovery for bridge {}", bridgeUid);
        List<Measure> measures = thingHandler.getMeasures();
        Set<String> registeredLocationIds = new HashSet<>(thingHandler.getRegisteredLocationIds());

        for (Measure measure : measures) {
            if (measure != null && !registeredLocationIds.contains(measure.getLocationId())) {
                Map<String, Object> properties = new HashMap<>(1);
                properties.put(PROPERTY_NAME, measure.getLocationName());
                properties.put(PROPERTY_FIRMWARE_VERSION, measure.getFirmwareVersion());
                properties.put(PROPERTY_SERIAL_NO, measure.getSerialNo());

                ThingUID thingUID = new ThingUID(THING_TYPE_LOCATION, measure.getLocationId());

                logger.debug("Adding location {} with id {}", measure.getLocationName(), thingUID);
                DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
                        .withBridge(bridgeUid).withLabel(measure.getLocationName()).build();

                thingDiscovered(discoveryResult);
            }
        }
    }
}

And in AirGradientAPIHandler

    @Override
    public Collection<Class<? extends ThingHandlerService>> getServices() {
        logger.debug("Getting supported services");
        return Set.of(AirGradientLocationDiscoveryService.class);
    }

I think I may have also hit this myself. It may be the HUE binding is a good example. The hue bridge gets found by dns and added. Then the light globes are found by the bridge handler and get added from memory. It is not a simple way to simply add a new result if your not calling it from a discovery service. I can not recall the solution, but I think it was HUE that gave clues for me. Someone else is sure to post.

1 Like

Yeah, HueDeviceDiscoveryService is a typical example which would benefit.

Today it creates a component which registers a listener with the bridge at initialization and then the bridge calls back.

If the bridge had injected something with the thingDiscovered() method, the component-callback indirection wouldn’t be needed.

I just had a look and the wled binding is the one I wrote that fits this example. It has two discovery services. One is for the mdns bridge. Then another for the things that the bridge discovers from parsing a json result from a http get request. The segment discovery is an example of what your asking about.

Edit: documention is here for discovery that is bound to a thing. @austvik

Did you find everything you needed or do you want to propose a better way? Perhaps better documentation so people understand what is possible and where to look for examples of different use cases?

Sounds like you have more Java knowledge then me so sorry if I am missing your point or not helpful.

Now (after many restarts and copying first the kar and then deleting that and then copying the jar to the addons directory) I am in the state where the discovery service loads:

23:07:20.141 [DEBUG] [y.AirGradientLocationDiscoveryService] - bundle org.openhab.binding.airgradient:4.2.0.202401102203 (261)[org.openhab.binding.airgradient.internal.discovery.AirGradient
LocationDiscoveryService(342)] : registration change queue [registered]

(I believe - maybe there should be something more after this?)

But after there there are no logs. And the UI is unresponsive. The diff shows that I have added one component and implemented getServices() in the bridge handler. All the methods in the discovery service have logs and they are not called.

With an injected service for adding discovered things, I don’t think it would be possible to create something that is this difficult to debug and that can stop the system so completely when something is wrong.

You are absolutely helpful, @matt1! Thank you for the encouragement!

Use the wled binding as an example as it is very basic compared to the more complex HUE. I had trouble getting it going due to my lack of Java knowledge so I feel your pain.

Documentation explains what to do here. It lists the steps you need to implement, perhaps you missed one step…

Feel free to create a PR and mark it with the label work in progress. This can be handy as if a reviewer has spare time they may take a quick glance and comment if your breaking a dev rule like using wrong naming convention. It’s a pain when you have to change a lot of channel names to comply. Also if you need help we can then make a code suggestion that you can just click accept on.

If you have a better way then suggest it at the core github issues. If your wanting to understand if there is a good reason it is this way you will need to wait for someone with more core knowledge comes along.

your missing the implements DiscoveryService, ThingHandlerService
Try adding it like this…

public class AirGradientLocationDiscoveryService extends AbstractThingHandlerDiscoveryService<AirGradientAPIHandler> implements DiscoveryService, ThingHandlerService {

I added the implements, but still cant create the abstract thing handler discovery service:

java.lang.NoClassDefFoundError: org/openhab/core/config/discovery/AbstractThingHandlerDiscoveryService

I think it shouldn’t be needed because

public abstract class AbstractThingHandlerDiscoveryService<T extends ThingHandler> extends AbstractDiscoveryService implements ThingHandlerService {

and

public abstract class AbstractDiscoveryService implements DiscoveryService {

Please link to the GitHub repo, it‘s hard to tell from small snippets what‘s wrong.

I am guessing some library AbstractThingHandlerDiscoveryService is depending on is missing

Caused by: java.lang.ClassNotFoundException: org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService cannot be found by org.openhab.binding.airgradient_4.2.0.202401111857
        at org.eclipse.osgi.internal.loader.BundleLoader.generateException(BundleLoader.java:541) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.loader.BundleLoader.findClass0(BundleLoader.java:487) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:416) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:168) ~[org.eclipse.osgi-3.18.0.jar:?]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:525) ~[?:?]

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.