DiscoveryResult from own DiscoveryService not showing up in the inbox

Hello everyone,
I’m trying to discover Things for my binding using my own implementation of a DiscoveryService.

The DiscoveryService picks up the Things correctly and a DiscoveryResult is sent using thingDiscovered, inherited from AbstractDiscoveryService.

The Code responsible for this:

@Component( service = DiscoveryService.class, immediate = true, configurationPid = "discovery.stellwerk")
public class DiscoveryService extends AbstractDiscoveryService {

[...]

    private void handleNewIOComponentDiscovered( IIOComponent component )
    {
        ThingUID uid = ComponentStructureBuilder.getFromThing(component);
        Map<String, Object> properties = new HashMap<>();
        
        properties.put(COMPONENT_TYPE, ((component.getType())));
        properties.put(COMPONENT_ID, (component.getId()));
        properties.put(IS_THING_COMPONENT, "true");
        properties.put(COMPONENT_REFERENCE, component);
        properties.put(UNIQUE_ID, component.getIdentity());
        
        DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(uid);
        builder.withThingType(thingTypeProvider.getFromIOComponent(component));
        builder.withLabel("Stellwerk IOComponent - "+component.getIdentity());
        builder.withRepresentationProperty(UNIQUE_ID);
        builder.withProperties(properties);

        DiscoveryResult result = builder.build();
        super.thingDiscovered(result);
        logger.debug("New IOComponent");
    }

[...]
}

Setting a breakpoint after the DiscoveryResult was created reveals the following structure:
grafik

Now, from my understanding of how the Discovery Process works, I would expect for the result to appear in the Inbox.
Unfortunately, this does not happen.

One important thing to note is that I am not using XMLs to define the Things’ structures. Instead, I have implemented a ThingTypeProvider-OSGi Service and @Reference it inside my ThingHandlerFactory. The Factory does only get constructed, but not called. This is expected, as the User has not chosen the Thing from the Inbox yet.

Is there something I am missing? Do some of the properties have to have some specific value?
The ThingTypeProvider is annotated as

@Component(service = {StellwerkThingTypeProvider.class, ThingTypeProvider.class})

Could it be that openHAB cannot find the ThingTypeProvider, and because of that, discards the result?

EDIT:

I think I can rule out the ThingTypeProvider being the Issue. When I try to add a Thing manually, the Thing Types show up correctly.

EDIT2:

I have followed the code with the debugger and it seems that the AbstractDiscoveryService has no listeners attached to it.
This would explain why nothing shows up in the inbox.


Does anyone have an idea as to why this might be happening?

Is your thing is a thing or bridge? As far I remember there were changes in UID construction.

Also do you run standalone openhab or you rely on bndrun files?

Is your thing is a thing or bridge? As far I remember there were changes in UID construction.

In the example above this is a Thing. But I have swapped to a different System. I now have Bridges with their own Discovery Services, as well as a Discovery Service for discovering Bridges.

The Discovery Services for the different Bridges work perfectly using the getServices( )-Method Override in the Bridge handler.

The “main” Discovery Service for the Bridges (using the @Component annotation) however does not work. I believe this is because openHAB does not add some callback to the DiscoveryListener, something that happens with the getServices( )-Implementation.

Also do you run standalone openhab or you rely on bndrun files?

I am not quite sure what you mean by that, but I think it’s the former.
I have installed openHAB manually (as described here > Linux > Option 2).

I am running the Binding by Dropping it and its dependency into the /addons Folder.

The Binding itself works. It’s just the Main Discovery Service that is causing issues.

EDIT: I hope this Illustration makes it more clear

I do use same pattern in bindings I wrote hence it should work. My suggestion - please verify activation logic (lifecycle methods such as activate/deactivate).
I know for sure that AbstractDiscoveryService does few non obvious things.
Lack of processors which are always present in active system (standalone installation you use for testing) might be result of that.
Check if you have same parts of base class called for both services you implemented.

I have looked at the AbstractDiscoveryServices sources.

AbstractDiscoveryService#activate is responsible for starting the Discovery Service and storing some config values.

When a new Thing is discovered the DiscoveryResult is passed to all listeners in AbstractDiscoveryService#thingDiscovered.

My @Component-DiscoveryService has no DiscoveryListeners, so it skips over notifying anyone.
My getServices-DiscoveryService on the other hand does register a listener.

DiscoveryListeners are added via AbstractDiscoveryService#addDiscoveryListener.

I have overridden the AbstractDiscoveryService#addDiscoveryListener-Method in both my Service-Implementations to log the DiscoveryListener, and then pass it on to the parent.

By doing so I can confirm that the getServices( )-DiscoveryService’s addDiscoveryListener method is called,
while the @Component-DiscoveryService’s is not.

But, looking at DiscoveryServiceRegistryImpl#getDiscoveryServices I can see that getSupportedThingTypes() is called, and when the binding IDs does not match, the Discovery Service is skipped.

The constructor if my @Component-Service looks like the following:

public DiscoveryService()
{
    super(null, 0, true);
}

Here I pass null as the SupportedThingTypes-Set. I think this is the root of the issue.
I do this because I rely on another OSGi-Service to get my thing types, to which I have no access inside the constructor.

I will try to override AbstractDiscoveryService#getSupportedThingTypes and see if that solves the issue.

EDIT:
Nope. That was not it unfortunately. The @Component-DiscoveryService’s getSupportedThingTypes( ) is not called at all.

EDIT 2:
I have set a Breakpoint on DiscoveryServiceRegistryImpl#addDiscoveryService.
From that I can see that only one DiscoveryService of Type MDNSDiscoveryService is added. So my own DiscoveryService is not called here.

I’m not exactly sure what the OSGi-Reference annoation @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) means, but I assume it calls the Method with each implementation of said Service (in this case DiscoveryService)

Okay. I have found the bug.
And ugh… it’s the stupid bugs that are the hardest :grinning_face_with_smiling_eyes:

I have called my DiscoveryService plainly “DiscoveryService”.

this resulted in the following Class Signature:

@Component( service = DiscoveryService.class, immediate = true, configurationPid = "discovery.stellwerk")
public class DiscoveryService extends AbstractDiscoveryService implements NewIONodeDiscoveredEvent

The Service in the @Component-Annotation does not reference the DiscoveryService-Interface provided by openHAB, but rather this class itself. Because of this, openHAB could never find this service.

I have resolved the issue by changing the class name:

@Component( service = {DiscoveryService.class}, immediate = true, configurationPid = "discovery.stellwerk")
public class StellwerkDiscoveryService extends AbstractDiscoveryService implements NewIONodeDiscoveredEvent

Now DiscoveryService.class references the Interface, and not this class.

Glad you solved it. This kind of bugs is hardest to solve.

For reference if you or anybody face similar issue there is a way to debug it faster. If you don’t know why service is not registered properly you can inspect it over shell.

Commands are:

scr:list
# you can also do following
scr:list|grep -i discover

Result of these calls is list of OSGi services which are available within registry. This is pretty much a runtime representation of @Component annotation declarations.

List has quite basic layout, below I just show two of discovery services which are relevant from testing point of view:

org.connectorio.addons.binding.can.internal.discovery.DBusCANInterfaceDiscoveryService in bundle 143 (org.connectorio.addons.binding.can:3.0.0.SNAPSHOT) enabled, 1 instance.
org.connectorio.addons.binding.plc4x.canopen.internal.discovery.CoInterfaceDiscoveryDelegate in bundle 145 (org.connectorio.addons.binding.plc4x.canopen:3.0.0.SNAPSHOT) enabled, 1 instance.
.... more to follow

What is relevant for next step is first value of list (service instance name). You can use it with scr commands to do various things. Below is result of inspection:

scr:info org.connectorio.addons.binding.can.internal.discovery.DBusCANInterfaceDiscoveryService
Component Description: org.connectorio.addons.binding.can.internal.discovery.DBusCANInterfaceDiscoveryService
=============================================================================================================
Class:         org.connectorio.addons.binding.can.internal.discovery.DBusCANInterfaceDiscoveryService
Bundle:        143 (org.connectorio.addons.binding.can:3.0.0.SNAPSHOT)
Enabled:       true
Immediate:     false
Services:      [org.openhab.core.config.discovery.DiscoveryService]
Scope:         singleton
Config PID(s): [org.connectorio.addons.binding.can.internal.discovery.DBusCANInterfaceDiscoveryService], Policy: optional
Base Props:    (0 entries)

Component Configuration Id: 15
------------------------------
State:        ACTIVE
Service:      157 [org.openhab.core.config.discovery.DiscoveryService]
    Used by bundle 232 (....)
Config Props: (2 entries)
  component.id<Long> = 15
  component.name<String> = org.connectorio.addons.binding.can.internal.discovery.DBusCANInterfaceDiscoveryService
References:   (total 1)
  - DiscoveryDelegate: org.connectorio.addons.binding.can.discovery.CANInterfaceDiscoveryDelegate SATISFIED 0..1 dynamic
    target=(*) scope=bundle (1 binding):
    * Bound to [159] from bundle 145 (org.connectorio.addons.binding.plc4x.canopen:3.0.0.SNAPSHOT)

Additional commands there is also scr:enable / disable to get service removed and brought back from OSGi service registry. You can also see in such a way if there are any unresolved runtime service references (dependencies) which block your component from becoming active.

1 Like