Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Introduction of Profiles #4108

Merged
merged 6 commits into from
Sep 6, 2017
Merged

Conversation

sjsf
Copy link
Contributor

@sjsf sjsf commented Aug 22, 2017

This PR now reworks the factored CommunicationManager (#4067) and makes its behavior modifiable by "profiles". Such profiles are basically exchangeable parts which the communication manager delegates its actual duties to. I also outlined their purpose in the profiles.md file below to become part of the documentation.

The idea is to make the used profile configurable on ItemChannelLinks. In this first step the profile selection is more or less static though. Although I already anticipated a "ProfileFactory" interface (mainly for testing purposes here), this PR is still missing the configurability by purpose. As this will involve touching many files and the Thing-DSL, I would like to handle this in a separate follow-up PR.

Using profiles also opens up another interesting use-case: I noticed that so far no binding implements the ThingHandler.handleUpdate(...) method. Technically spoken some very few do, but they don't do anything there. The main idea of this method apparently was to allow devices to run as "slaves" of other devices, i.e. react on item updates only. A typical use-case for this would be a light switch with an indicator light which should follow the state of the the physical lamp without the risk of running out-of-sync.
Therefore I already added a "slave" profile which does exactly that: it listens to item updates, transforms them to commands (if possible) and forwards them to the ThingHandler.handleCommand(...) directly. At the same time it cuts off all other communication coming out of the thing handle. With this it would be easily possible to run any device now as a slave without the need to fix/implement this in all of the existing bindings.

At the same time I deprecated the ThingHandler.handleUpdate(...) method to prevent any new binding to implement it. I think it is fair to allow for a grace period before we drop this method from the interface.

The profiles here (especially the TriggerProfiles) are required for the second part of #1043 a.k.a. #2226. I only created a RawButtonTriggerProfile here as an example and proof-of-concept. But there are more required, of course. Especially for the system channels they make much sense! Nevertheless, I would like to keep the work of implementing these out this PR as well.

@sjsf sjsf changed the title Profiles static Introduction of Profiles Aug 22, 2017
@davidgraeff
Copy link
Contributor

davidgraeff commented Aug 22, 2017

Hm, I find it very hard to follow the ESH documentation in some parts and the newly added profiles.md is one of those sections that would highly confuse me. I think this is mainly because there are no diagrams that provide an overview with whom and how profiles are interacting with other components and there are no example usages.

@sjsf
Copy link
Contributor Author

sjsf commented Aug 22, 2017

I agree, the docu is not yet complete. Examples can be added only once it actually can be configured. As this part is yet missing, there is not much to put there right now. I will add some pictures for the concrete profiles once all this here somehow got agreed. I hope it will get clearer then. For the time being I just thought it's important to write it down anywhere at all.

For all the other confusing parts: Fully agreed. Any help is highly appreciated!

Copy link
Contributor

@kaikreuzer kaikreuzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good, just some very minor remarks.


private final Logger logger = LoggerFactory.getLogger(CommunicationManager.class);
private final Set<String> subscribedEventTypes = ImmutableSet.of(ItemStateEvent.TYPE, ItemCommandEvent.TYPE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you do it without Guava?

* @author Simon Kaufmann - initial contribution and API.
*
*/
public class DefaultSlaveProfile extends DefaultMasterProfile {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it extend the master profile? Shouldn't it rather implements StateProfile as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just was trying to prevent code duplication by delegating slave's onUpdate(...) to master's onCommand(...). But I see this is confusing. I will change that.

* This is the default profile for stateful channels.
*
* It forwards commands to the {@link ThingHandler}. In the other direction it posts events to the event bus
* for state updates and commands which are initiated by the handler.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"...and commands which are initiated by the handler" - was that really a use case for a "master" device? Do you have an example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really good point - I just mindlessly took that from the old API. Now thinking about it, I don't have a good example.

No binding in ESH is using it.

In openhab2-addons there are only two bindings using it: lutron and squeezebox. I'm not familiar with both yet, so it's hard to judge whether this in fact is correct or not. On a first glance it looks like it might not be needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS: Just do double-check I read you correctly: This is not only about the JavaDoc, but you are suggesting to make master's "postCommand" a no-op. Correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

import org.eclipse.smarthome.core.thing.link.ItemChannelLink;
import org.eclipse.smarthome.core.thing.profiles.TriggerProfile;

public class RawButtonTriggerProfile implements TriggerProfile {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaDoc missing

* <p>
* This allows devices to be operated as "slaves" of another one directly, without the need to write any rules.
* <p>
* No events are send for state updates or commands initiated by the ThingHandler. They are assumed to be passive only.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: are sent

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if I forgot the details again: Taking the KNX wall switch as an example. Would this have to be modelled as two channels? One with master-profile only sending commands and one with slave-profile only receiving updates? What spoke against allowing slave-channels to send commands (to their "master" within ESH)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What spoke against allowing slave-channels to send commands (to their "master" within ESH)?

Nothing really, IIRC.

If a slave issues a command towards ESH, it will have to live with the fact that the state bounces back as an update via the master (which then gets turned into a command again by the slave profile, this time flowing from ESH towards the binding). But that shouldn't matter much - it rather makes sense because it gets the real value that the master reported back as an update, which is not necessarily identical to the command that the slave initially sent towards ESH.

@kaikreuzer kaikreuzer added the Core label Sep 1, 2017
Simon Kaufmann added 5 commits September 4, 2017 12:47
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
...between the framework and the binding (i.e. ThingHandler).

By now, the profiles are still statically set. Selecting a different
profile for a link will be done in a next step.

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
...and adding a slave profile which could be used instead in order to
configure channels to react on item state updates and therefore
running as "slave" of another one.

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
@kaikreuzer
Copy link
Contributor

There are 3 test failures:

  ThingManagerOSGiTest.ThingManager handles post command correctly:804->OSGiTest.waitForAssert:-1->OSGiTest.waitForAssert:191->OSGiTest.waitForAssert:220 
Expected: not null
     but: was null
  DefaultSlaveProfileTest.testPostCommand:117 
No interactions wanted here:
-> at org.eclipse.smarthome.core.thing.internal.profiles.DefaultSlaveProfileTest.testPostCommand(DefaultSlaveProfileTest.java:117)
But found this interaction:
-> at org.eclipse.smarthome.core.thing.internal.profiles.DefaultSlaveProfile.postCommand(DefaultSlaveProfile.java:92)
Actually, above is the only interaction with this mock.
  DefaultMasterProfileTest.testPostCommand:121 
Wanted but not invoked:
eventPublisher.post(<Capturing argument>);
-> at org.eclipse.smarthome.core.thing.internal.profiles.DefaultMasterProfileTest.testPostCommand(DefaultMasterProfileTest.java:121)
Actually, there were zero interactions with this mock.

@sjsf
Copy link
Contributor Author

sjsf commented Sep 5, 2017

My bad, I neglected to update the tests...
Done.

@kaikreuzer
Copy link
Contributor

Still one failing test :-(

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
@kaikreuzer kaikreuzer merged commit 74880d0 into eclipse-archived:master Sep 6, 2017
Copy link
Contributor

@maggu2810 maggu2810 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been my first reading of the code, so I added some comments. All the nullness stuff for private method is just because I realized it while reading. If we only care about on public methods ignore it.

return new NoOpProfile();
}

Channel channel = thing.getChannel(link.getLinkedUID().getId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the parameter link must not be null.
ItemChannelLink link => @NonNull ItemChannelLink link

"Cannot delegate command '{}' for item '{}' to handler for channel '{}', "
+ "because no thing with the UID '{}' could be found.",
command, itemName, channelUID, channelUID.getThingUID());
private Profile getProfile(ItemChannelLink link, Item item, Thing thing) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could mark the return type with @NonNull

for (ProfileFactory profileFactory : profileFactories.keySet()) {
Profile profile = profileFactory.createProfile(link, item, channel);
if (profile != null) {
profileFactories.get(profileFactory).add(link.getLinkedUID());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need access to the profileFactory, wouldn't it make sense to use entrySet() instead of keySet()?

return null;
}

private Profile createDefaultProfile(Channel channel) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NonNull Channel

return null;
}

private void receiveCommand(ItemCommandEvent commandEvent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NonNull ItemCommandEvent

});
}

private void receiveTrigger(ChannelTriggeredEvent channelTriggeredEvent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NonNull ChannelTriggeredEvent

eventPublisher
.post(ItemEventFactory.createStateEvent(item.getName(), acceptedState, channelUID.toString()));
}
final Thing thing = getThing(channelUID.getThingUID());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NonNull ChannelUID channelUID

for (String item : items) {
eventPublisher.post(ItemEventFactory.createCommandEvent(item, command, channelUID.toString()));
}
final Thing thing = getThing(channelUID.getThingUID());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nonnull ChannelUID channelUID

* This profile allows a channel of the "system:rawbutton" type to be bound to an item.
*
* It reads the triggered events and uses the item's current state to calculate the new state once it detects that the
* button was pressed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, it is a toggle profile. Correct?





Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you add all that empty lines?

@maggu2810
Copy link
Contributor

Damn, merged while reviewing 😉

@kaikreuzer
Copy link
Contributor

Sorry, I had put my approval 18 hours ago and the PR is 15 days old - so I was hoping you didn't have any further comments.

But never mind - I am sure @SJKA will happily address everything in a follow-up PR :-)

@maggu2810
Copy link
Contributor

I had put my approval 18 hours ago and the PR is 15 days old

I am slow 😉

@sjsf sjsf deleted the profiles_static branch September 6, 2017 13:17
@sjsf
Copy link
Contributor Author

sjsf commented Sep 6, 2017

Sure, no problem

sjsf pushed a commit to sjsf/smarthome that referenced this pull request Sep 6, 2017
...as a follow-up of eclipse-archived#4108

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
sjsf pushed a commit to sjsf/smarthome that referenced this pull request Sep 6, 2017
...as a follow-up of eclipse-archived#4108

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
@sjsf sjsf mentioned this pull request Sep 6, 2017
sjsf pushed a commit to sjsf/smarthome that referenced this pull request Sep 7, 2017
...as a follow-up of eclipse-archived#4108

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
kaikreuzer pushed a commit that referenced this pull request Sep 11, 2017
...as a follow-up of #4108

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
@sjsf sjsf mentioned this pull request Sep 15, 2017
@sjsf sjsf mentioned this pull request Sep 25, 2017
@kaikreuzer kaikreuzer added this to the 0.9.0 milestone Nov 30, 2017
@openhab-bot
Copy link
Contributor

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/what-is-the-difference-between-system-default-and-system-follow/53076/5

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants