Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[denonmarantz] new Denon / Marantz 2.0 binding #2859

Merged
merged 9 commits into from
May 14, 2018
Merged

[denonmarantz] new Denon / Marantz 2.0 binding #2859

merged 9 commits into from
May 14, 2018

Conversation

jwveldhuis
Copy link
Contributor

This is a OpenHAB 2 port of the existing 1.x Denon binding (created by @idserda). As the binding supports both Denon and Marantz receivers decided to change the name to 'denonmarantz'.

Improvements:

  • support for Discovery (MDNS)
  • defined channels (including an 'advanced' Channel to send any custom command)
  • improved Telnet stability (although that has been addressed in the old binding)

Note that this binding is still work in progress. Need others to test this binding as I only own one Marantz receiver (SR5008) myself. That is the main reason for this PR, as I know some cleanup, fixing and documentation is required.

This is my first binding, and it took more time and effort than I'd hoped for.. ;)
Happy to learn how to further improve.

@jwveldhuis
Copy link
Contributor Author

jwveldhuis commented Nov 19, 2017

Not sure why Jenkins / Travis builds failed? Something I can do about that?

Update nevermind, saw that the line in Travis tailing the stderr log is expandable ;)
Now saw this:

[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] Non-resolvable parent POM for org.openhab.binding:org.openhab.binding.denonmarantz:2.1.0->SNAPSHOT: Could not find artifact > org.openhab.binding:pom:pom:2.1.0-SNAPSHOT in sonatype-snapshots (https://oss.sonatype.org/content
/repositories/snapshots/) and 'parent.relativePath' points at wrong local POM @ line 6, column 11

Will check what I can do about this, any suggestions are welcomed ;)

@jwveldhuis
Copy link
Contributor Author

Build issues fixed, found the jar file of the binding so other can test now.

@martinvw martinvw added the new binding If someone has started to work on a binding. For a new binding PR. label Nov 24, 2017
@jwveldhuis jwveldhuis changed the title [denonmarantz][WIP] new Denon / Marantz 2.0 binding [denonmarantz] new Denon / Marantz 2.0 binding Dec 30, 2017
@jwveldhuis
Copy link
Contributor Author

Testing by several users has been done over the past weeks.
I need to add a README.md.

@jwveldhuis
Copy link
Contributor Author

jwveldhuis commented Jan 1, 2018

@martinvw ready for review!
Just squashed all commits and added "Signed-off-by".

@jwveldhuis
Copy link
Contributor Author

Binding is made available at the Eclipse Marketplace: https://marketplace.eclipse.org/content/denonmarantz-binding

Copy link

@triller-telekom triller-telekom left a comment

Choose a reason for hiding this comment

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

Thank you for porting this binding to openHAB2!

I did a first review and left some comments inside the code.

One major thing I would propose is that you should think about splitting your connector into one that is responsible for telnet and one that is responsible for http.

<advanced>true</advanced>
</parameter-group>

<parameter name="zoneCount" type="integer" required="true" groupName="receiverProperties">

Choose a reason for hiding this comment

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

If you provide a default value you can omit the required=true

<description>Hostname or IP address of the AVR to control.</description>
</parameter>

<parameter name="telnetEnabled" type="boolean" required="true" groupName="connectionSettings">

Choose a reason for hiding this comment

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

If you provide a default value you can omit the required=true

<default>false</default>
</parameter>

<parameter name="telnetPort" type="integer" required="false" groupName="telnetSettings">

Choose a reason for hiding this comment

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

required=false is the default so you can remove it.

<advanced>true</advanced>
</parameter>

<parameter name="httpPort" type="integer" required="true" groupName="httpSettings">

Choose a reason for hiding this comment

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

If you provide a default value you can omit the required=true

<advanced>true</advanced>
</parameter>

<parameter name="httpPollingInterval" type="integer" required="true" groupName="httpSettings">

Choose a reason for hiding this comment

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

If you provide a default value you can omit the required=true

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2017 by the respective copyright holders.

Choose a reason for hiding this comment

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

Please run mvn license:format to update it to 2018 for all files (some of yours even have 2015 :))

@Override
public Boolean unmarshal(String v) throws Exception {
if (v != null) {
return v.toLowerCase().equals("on");

Choose a reason for hiding this comment

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

Doesn't this return a "primitive" boolean instead of the Boolean object?

telnetEnable = false;
logger.debug("We can access the HTTP API, disabling the Telnet mode by default");
}
} catch (Exception e) {

Choose a reason for hiding this comment

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

See above: Please do not catch Exception

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See above, please suggest a solution for this.

Matcher matcher = DENON_MARANTZ_PATTERN.matcher(service.getQualifiedName());
if (matcher.matches()) {
logger.debug("This seems like a supported Denon/Marantz AVR!");
this.serial = matcher.group(1).toLowerCase();

Choose a reason for hiding this comment

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

What if you detect more than one? By storing the serial inside a class variable you will overwrite it in case there are 2 or more results.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, didn't realize that! Thought the Participant would get instantiated for reach result, which obviously isn't the case. Good catch, this could have resulted in a hard to debug issue.

* @author Jeroen Idserda
* @author Jan-Willem Veldhuis
*/
public class DenonMarantzTelnetClient extends Thread {

Choose a reason for hiding this comment

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

Please make it a Runnable and schedule it with the scheduler (scheduler.submit..)

@jwveldhuis
Copy link
Contributor Author

@triller-telekom thank you for reviewing! I need to find time to work on your feedback.

@jwveldhuis
Copy link
Contributor Author

jwveldhuis commented Feb 1, 2018

Note to self: need to fix issue in 2.3.0 caused by this Smarthome PR: #4787 and fixed here: #2997
(I used the Yamaha binding as a reference for adding Channels dynamically)

Edit: took care of this.. Added static mapping from ChannelType to ItemType to avoid a lot of complex code to fetch those from the thing-types.xml file. Can be refactored later on when smarthome is enhanced with eclipse-archived/smarthome#4950

@triller-telekom
Copy link

@jwveldhuis Thank you for your comments. If you have any updates on the code side please create a NEW commit on your branch and push it. Please do NOT amend your previous commit and do a force push. Reviewing the code is a lot easier if we only see the diff to the previous reviews. Thanks.

@jwveldhuis
Copy link
Contributor Author

jwveldhuis commented Feb 3, 2018

@triller-telekom how do I handle the merge with upstream/master in between, should that be 'signed-off' as well?
Sorry I'm not too familiar with git / github.

Update: Nevermind, I've reverted the branch back to previous commit and forced push a new commit. Looks like you don't need to sync the fork with master.
Still curious how the git workflow should work, as I want to test locally against the latest changes on upstream/master.

Looks like this could work:

# merge the current master in local checkout of fork, don't commit
git merge upstream/master --no-commit

# do dev work / testing

# abort the merge 
git merge --abort 

# now commit and push to fork..

Copy link

@triller-telekom triller-telekom left a comment

Choose a reason for hiding this comment

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

Thank you for your changes. the code looks much cleaner now with the separation of the http and telnet clients.

If you want to include the current status of the master branch in your feature branch, please rebase your branch onto the current master, never to a merge. Easiest thing to do that is to change tot he master branch and pull it: git checkout master; git pull and afterwards go into your branch git checkout YOUR_BRANCH; git rebase master.

This will replay all your changes on top of the current master to keep a clean and straight version history without any merge commits.

I have left some minor comments in the code.

org.openhab.binding.denonmarantz,
org.openhab.binding.denonmarantz.handler,
org.osgi.service.component.annotations;resolution:=optional,

Choose a reason for hiding this comment

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

You can remove this optional import.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I removed it I get this error for the Handler:
The type org.eclipse.jdt.annotation.NonNullByDefault cannot be resolved. It is indirectly referenced from required .class files

Choose a reason for hiding this comment

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

Are you sure that you removed org.osgi.service.component.annotations;resolution:=optional, and NOT accidentally org.eclipse.jdt.annotation;resolution:=optional,? Because the later one would explain your error message...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mea culpa, yes I did.
However, when I remove the org.osgi.service.component.annotations;resolution:=optional, I see the following warning in Eclipse:
DS Annotations missing from permanent build path

Choose a reason for hiding this comment

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

This is because your branch is too old. I have just tested a plain checkout with the current master and everything is fine in the IDE. if I rebase your branch onto the current master and remove org.osgi.service.component.annotations;resolution:=optional, from the MANIFEST.MF, everything is alright.

So please rebase your branch onto the current master and do a push with --force. Careful though, because this will overwrite the branch on github. So you might want to do a backup of your current local repository before doing so since you said you had problems with git before.

However, I just did a git rebase master in your branch locally (after pulling the latest master branch of course) on my machine and everything went smooth.

Copy link
Contributor Author

@jwveldhuis jwveldhuis Feb 6, 2018

Choose a reason for hiding this comment

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

I am on the latest commit (commit dc204c6) of openhab2-addons, still see the warning.
Is it perhaps a setting outside of this repo? What was changed to resolve this warning?

I am using openhab2-addons and openhab-distro in my IDE, I don't use the other internal repo's.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nevertheless, I will apply the change and ignore the local warning (I see them for other bindings as well), perhaps related to my IDE setup.

Choose a reason for hiding this comment

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

All of you checked out repos have to be on the newest version, i.e. "openhab1-addons openhab2-addons openhab-core openhab-distro smarthome"

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 only have openhab2-addons and openhab-distro and both are up to date (while setting up the IDE I only picked "openHAB Development" (required) and "openHAB2 Add-ons" from the optional set of repos).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirmed warning is gone when all git repos are included in the IDE.

@@ -57,26 +62,30 @@ The DenonMarantz AVR supports the following channels (some channels are model sp
| mainVolume | Dimmer | Main zone volume

Choose a reason for hiding this comment

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

Please add (R) or (RW) on all channels to make it consistent

@Override
public void initialize() {
// create connection (either Telnet & HTTP or HTTP only)
config = getConfigAs(DenonMarantzConfiguration.class);
denonMarantzState = new DenonMarantzState(this);

Choose a reason for hiding this comment

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

Maybe you want to check the configuration before doing anything?

@@ -58,6 +58,10 @@ public DenonMarantzState(DenonMarantzStateChangedListener listener) {
this.listener = listener;
}

public void connectionError(String errorMessage) {
listener.connectionError(errorMessage);

Choose a reason for hiding this comment

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

What if the listener is null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can't be. State is instantiated from the DenonMarantzHandler like this, the handler is the (only) listener.

denonMarantzState = new DenonMarantzState(this);

Choose a reason for hiding this comment

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

Yes I saw that is is initialized with the handler, but code gets changed over time and who knows if there won't be another instance created somewhere in the future? Just saying this because with the term listener I usually connect that it is a 0-n relationship, i.e. it is optional or there may be many.

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 agree, will rename the variable to handler to avoid future confusion.


private void startPolling() {
if (!isPolling()) {
logger.info("HTTP polling started.");

Choose a reason for hiding this comment

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

Please decrease to debug level.

}

private void updateSecondaryZones() {
for (int i = 2; i <= config.getZoneCount(); i++) {

Choose a reason for hiding this comment

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

Why are you using a loop at all if you have a switch-case later on anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To reuse the code to construct the url, to get the document and to log about that.

String ret = IOUtils.toString(is);

connection.disconnect();

Choose a reason for hiding this comment

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

empty line

try {
httpClient.start();
response = httpClient.newRequest("http://" + host + "/goform/Deviceinfo.xml")
.timeout(3, TimeUnit.SECONDS).send();
int status = response.getStatus();
if (status == 200) {
if (response.getStatus() == 200) {

Choose a reason for hiding this comment

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

Isn't there a constant like HTTP_STATUS.OK or something instead of the 200?

try {
response = httpClient.newRequest("http://" + host + ":8080/goform/Deviceinfo.xml")
.timeout(3, TimeUnit.SECONDS).send();
if (response.getStatus() == 200) {

Choose a reason for hiding this comment

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

See above

logger.debug("Failed in fetching the Deviceinfo.xml to determine zone count: {}", e.getMessage());
}

if (status == 200 && response != null) {

Choose a reason for hiding this comment

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

See above

@jwveldhuis
Copy link
Contributor Author

jwveldhuis commented Feb 5, 2018

@triller-telekom ahh, thanks for this basic git-HOWTO :) Searched and tried a lot, couldn't find this.
Will work on your latest comments.
And indeed, the code is a lot cleaner now. When I started this I wanted to keep as close as possible to the 1.x binding, now eventually almost everything got changed.. ;)

@jwveldhuis
Copy link
Contributor Author

jwveldhuis commented Feb 5, 2018

@triller-telekom what is the recommended pattern to handle RunTimeException in a Runnable scheduled with ScheduledExecutorService?
Learned today that the Runnable just stops and NO error is logged anywhere.. :/
See https://stackoverflow.com/a/24902026/2486310

I am planning to handle it like this:

pollingJob = scheduler.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
        try {
            refreshHttpProperties();
        } catch (Throwable t) {
            StringBuilder sb = new StringBuilder();
            for (StackTraceElement s : t.getStackTrace()) {
                sb.append(s.toString()).append("\n");
            }
            logger.error("Error while polling Http: \"{}\". Stacktrace: \n{}", t.getMessage(),
                    sb.toString());
        }
    }
}, config.httpPollingInterval, config.httpPollingInterval, TimeUnit.SECONDS);

In this case (see trail of comments in #2204) the following happened:
User assigned Dimmer item and assigned it to a Number channel (not using Paper UI).
At update of the channel state a java.lang.IllegalArgumentException: Value must be between 0 and 100 was thrown. This caused the Runnable to stop.

Took me 4 hours to diagnose this 😕 😦

@triller-telekom
Copy link

Regarding your RuntimeException in a Runnable problem: We had eclipse-archived/smarthome#4772

But it turned out that some jobs were not executed and thus we had to revert it via eclipse-archived/smarthome#4932

I just tried to play around with it but unfortunately I could not really reproduce it :( Neither in my IDE pure ESH setup nor in a basic installation of OH 2.2 with a patched smarthome.core bundle (one that contained the wrappedscheduler).

So for now I guess you have to stick to catching the RuntimeException on your own. If you found out some more details while you where near the code in your debug session anyway, help from your side is more than welcome :)

@triller-telekom
Copy link

@jwveldhuis FYI: With eclipse-archived/smarthome#5045 I have fixed the logging of Exceptions in scheduled jobs :)

If you address the few minor issues from above we can handover your PR to the committers for a final review and merge.

@jwveldhuis
Copy link
Contributor Author

@triller-telekom excellent! Thank you!
Need to find time to finish the last issues.
Also started to work on dynamically fetching the Input options (e.g. TV, NET, TUNER). I will postpone that for a follow-up PR to not further delay this.

@jwveldhuis
Copy link
Contributor Author

jwveldhuis commented Feb 8, 2018

@triller-telekom does your fix only address the error logging, or does it also keep the scheduler running for subsequent tasks (I don't think so)?
So I think I still need to catch RuntimeException to prevent the scheduler from stopping. Will perform some testing around this.

@triller-telekom
Copy link

So I think I still need to catch RuntimeException to prevent the scheduler from stopping. Will perform some testing around this.

Yes indeed. My fix is only to make the error show up in the logs. if you already know that a RuntimeException may occur and how to deal with it, then you can catch it. So my fix doesn't change any behavior it just makes a wrong behavior obvious.

@jwveldhuis
Copy link
Contributor Author

Pushed changes.
Again struggled with git. I rebased to master, but then couldn't push:

 ! [rejected]          denon-marantz-binding -> denon-marantz-binding (non-fast-forward)
error: failed to push some refs to 'https://github.com/jwveldhuis/openhab2-addons.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.

In git log I saw my previous commits got a different commit hash due to the git rebase. Would GitHub still recognise the previous commits or would it mess up the code review? I now reverted back to the previous state of my fork and then put the changes on top, then pushed.

Copy link

@triller-telekom triller-telekom left a comment

Choose a reason for hiding this comment

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

Thank you for your changes. The code looks really good now except for two small things where I have left a comment.

Regarding the git rebase: You noticed correctly that the hash of you commit has changed because it was rewritten onto the master branch. In order to push this rebased version of you branch you have to somehow "force" it via git push --force-with-lease

A git push --force would also be sufficient but in projects where you work with others together on the same branch you could overwrite their work if you are not careful. I just recently learned the lease command and think its a very good thing to have.

A colleague of me has recently shared a nice link to a good introduction for git rebase.

@@ -123,7 +126,7 @@ public void handleCommand(ChannelUID channelUID, Command command) {
break;

default:
logger.warn("Command for channel {} not supported.", channelUID.getId());
logger.warn("Command for (read only) channel {} not supported.", channelUID.getId());

Choose a reason for hiding this comment

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

Why did you add the "(read only)" here? You could get a command for an arbitrary channel...

But I am thinking about whether if it would make sense to reduce the log level to debug, because you are not interested in commands for other channels.

@@ -12,6 +12,7 @@
import java.math.RoundingMode;
import java.util.concurrent.ScheduledExecutorService;

import org.eclipse.jdt.annotation.NonNull;

Choose a reason for hiding this comment

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

Please do not use @NonNull, use @NonNullByDefault on the class and @Nullable where appropriate instead, if you want to make use of the null annotations.

@jwveldhuis
Copy link
Contributor Author

@triller-telekom done.

@triller-telekom
Copy link

@kaikreuzer @martinvw This PR is now ready for a final review and can be merged afterwards. Please have a final look at it. Thanks.

@triller-telekom
Copy link

Thank you very much @jwveldhuis !

@jwveldhuis
Copy link
Contributor Author

Thank you for the extensive review! Enjoyed the collaboration!
Once this is merged will come back with some more enhancements ;)

@kaikreuzer
Copy link
Member

Thanks for this contribution @jwveldhuis!
I just started with doing a final review. What immediately caught my eye is that you do not seem to be familiar with the concept of channel groups - those are actually an ideal fit for the different zones of an AVR, which all have the very same features (= set of channels). You'd therefore only have to define all those channels once and then add three groups for the three zones. Would that make sense?

@jwveldhuis
Copy link
Contributor Author

@kaikreuzer there are many models (Denon and Marantz) and I don't have a clear overview which ones support multiple zones (2 or 3). Also more models come to market over time, by dynamically detecting (or letting the user set) the number of zones we avoid having to update the binding for each new model.

Adding channels dynamically is already done in the current version of this binding.
When I add 'zoneX#volume', how will the framework know that I am referring to a channel-group of type 'zone'? I guess I do need to assign all channel-groups upfront and then remove the unsupported channels later on?

@kaikreuzer
Copy link
Member

by dynamically detecting the number of zones we avoid having to update the binding

ok, fine.

I guess I do need to assign all channel-groups upfront

As mentioned above: There is no information about channel groups at all in the Thing, so there is no need to create or assign any. All that exists as a reference is the channel id with 'zoneX#volume' format (in constrast to the normal 'volume' channel id).

@jwveldhuis
Copy link
Contributor Author

@kaikreuzer I start to get it now.. Struggling with the code which dynamically adds / removes channels. Was using the ChannelTypeUID as a unique identifier to add/remove channels, this obviously does no longer work. Need to think of an alternative mechanism.

Big drawback of using channel groups is the lack of custom labels / descriptions for each channel.
How to override the labels / descriptions? (I guess that defeats the purpose of channel groups?)
In Paper UI it is becoming messy for users now the custom channels for each zone are replaced by generic ones:

  • Zone control
    • Power (zone)
    • Volume
    • Mute
    • Input source
  • Zone control
    • Power (zone)
    • Volume
    • Mute
    • Input source
  • Zone control
    • Power (zone)
    • Volume
    • Mute
    • Input source

How to distinguish Main Zone from Zone1 and Zone2? I used to have custom labels, e.g. "Power (zone2)", "Mute (zone3)", etc.

Signed-off-by: Jan-Willem Veldhuis <[email protected]> (@github: jwveldhuis)
Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: jwveldhuis)
Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: jwveldhuis)
Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: jwveldhuis)
Zone3.
Fixed comments according to code analysis report.

Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: jwveldhuis)
@jwveldhuis
Copy link
Contributor Author

Just pushed the updated Binding. Have created separate channel groups in order to provide a custom description for each channel group (while still reusing the common channels).

@kaikreuzer
Copy link
Member

Sorry for my late reply - there actually is no need to define multiple channel-group-types. For custom labels&descriptions, you can simply overwrite them for each channel group:

<channel-groups>
    <channel-group typeId="zone" id="mainZone">
        <label>Main Zone</label>
        <description>The main zone of this AVR</description>
    </channel-group>
    <channel-group typeId="zone" id="zone2">
	    <label>Zone 2</label>
        <description>Zone 2 of this AVR</description>
    </channel-group>
    ...
</channel-groups>

This should simplify you xmls again a bit (and hopefully not require any code changes at all).

Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: jwveldhuis)
@jwveldhuis
Copy link
Contributor Author

@kaikreuzer ah thanks, now it all makes sense 😁
Just pushed the suggested change.

Copy link
Member

@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.

Thanks! Now that we have settled the modelling, I had a detailed look through the code as well and left a few review comments on it.

Please also note that you should add an "Also-By Jeroen Idserda..." to your commit message as @idserda is a co-author (and by adding him, I assume we also have his approval for this contribution).

*/
public class DenonMarantzHandler extends BaseThingHandler implements DenonMarantzStateChangedListener {

private Logger logger = LoggerFactory.getLogger(DenonMarantzHandler.class);
Copy link
Member

Choose a reason for hiding this comment

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

add "final" keyword


<config-description>
<parameter-group name="receiverProperties">
<label>Receiver properties</label>
Copy link
Member

Choose a reason for hiding this comment

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

You have a mix of cases in your labels - please follow a single style. Usually, we use capitals in labels, here e.g. "Receiver Properties".

</parameter>

<parameter name="telnetEnabled" type="boolean" groupName="connectionSettings">
<label>Use Telnet port?</label>
Copy link
Member

Choose a reason for hiding this comment

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

remove the question mark

}

if (connector instanceof DenonMarantzHttpConnector && command instanceof RefreshType) {
// Refreshing individual channels isn't supported by the Http connector.
Copy link
Member

Choose a reason for hiding this comment

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

Not ideal, but if the polling is done so frequently (every 5 secs if I see it right), it should be ok.

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 know, but the AVR can't handle too many HTTP requests at once. I had to power cycle my AVR during tests when all linked channels got polled during startup.

Copy link
Member

Choose a reason for hiding this comment

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

Sure. Just fyi: We have a expiring cache that is meant for this situation (reducing the number of calls that go out to the device).

denonMarantzState = new DenonMarantzState(this);
configureZoneChannels();
// create connection (either Telnet or HTTP)
// ThingStatus ONLINE/OFFLINE is set when AVR status is known.
Copy link
Member

Choose a reason for hiding this comment

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

How long can that take? If that might be a few seconds (what I assume due to potential HTTP timeouts), you should set the status to UNKNOWN here first.

/**
*
*/
public void receivedLine(String line);
Copy link
Member

Choose a reason for hiding this comment

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

remove public


/**
* The telnet client has successfully connected to the receiver.
*/
Copy link
Member

Choose a reason for hiding this comment

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

add JavaDoc for parameter

/**
* The telnet client has successfully connected to the receiver.
*/
public void telnetClientConnected(boolean connected);
Copy link
Member

Choose a reason for hiding this comment

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

remove public


logger.debug("IP Address: {}", host);

// try a HTTP request to autoconfigure the HTTP / Telnet parameter
Copy link
Member

Choose a reason for hiding this comment

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

This does not look like a good idea - this method is expected to return fast; doing an HTTP call means it will block for potentially a few seconds. Better leave some properties unset in the discovery result.

Copy link
Contributor Author

@jwveldhuis jwveldhuis May 2, 2018

Choose a reason for hiding this comment

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

I agree it's not perfect. For some models HTTP is not an option, so for those I want to set 'Use Telnet' to true in the Thing parameter. The default for this parameter is false.

Should I do this instead?

  • set some property in the Thing indicating it was created via a Discovery Result (I guess there is no way to determine otherwise?)
  • if set, auto-detect and alter the value for parameter 'Use Telnet' during Initialization

E.g. I don't want to override the parameter when "explicitly" (there is a default) set by the user during manual Thing creation (for whatever reason).

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 found another option, but have trouble implementing this:
Limit the number of times the procedure is invoked to exactly once, by stopping further (relatively expensive) discovery activities as soon as it's clear we already configured or discovered the Thing.
https://www.eclipse.org/smarthome/documentation/development/bindings/discovery-services.html#compare-with-existing-results

I tried to implement the ExtendedDiscoveryService with the MDNSDiscoveryParticipant, this does not work unfortunately. How can I add this callback to the MDNSDiscoveryService? How do I approach this?

Copy link

@triller-telekom triller-telekom May 3, 2018

Choose a reason for hiding this comment

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

@jwveldhuis This is a missing feature which I have just implemented in eclipse-archived/smarthome#5526

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@triller-telekom perfect!

Copy link
Member

Choose a reason for hiding this comment

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

I am not sure whether this would be a solution. After all, you still have to do HTTP requests in a method that must return fast. And discovery is supposed to be passive in most cases, it should really just sit there and listen.

Couldn't you simply leave this property unset (=null) and upon handler initialisation, you do the HTTP call and figure out the right value, if it is still null? This would allow you to set the value without bothering the user and still avoid the risk of overwriting a value that the user has specifically set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I have now moved this part over the the Handler. Added benefit is the auto configuration for telnet/http and zone count is now also applied to Things configured in text files.

@@ -31,6 +31,7 @@
<module>org.openhab.binding.coolmasternet</module>
<module>org.openhab.binding.dlinksmarthome</module>
<module>org.openhab.binding.dscalarm</module>
<module>org.openhab.binding.denonmarantz</module>
Copy link
Member

Choose a reason for hiding this comment

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

@idserda
Copy link

idserda commented May 2, 2018

Great work! Not sure how much of my original code is still left but you definitely have my approval.

Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: jwveldhuis)
Also-by: Jeroen Idserda (github: idserda)
Copy link
Member

@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.

Excellent, thanks for your updates!

@kaikreuzer kaikreuzer merged commit 5bf4630 into openhab:master May 14, 2018
@jwveldhuis jwveldhuis deleted the denon-marantz-binding branch May 14, 2018 07:53
@jwveldhuis jwveldhuis restored the denon-marantz-binding branch May 14, 2018 08:03
@jwveldhuis jwveldhuis deleted the denon-marantz-binding branch May 14, 2018 08:03
@jwveldhuis jwveldhuis restored the denon-marantz-binding branch May 14, 2018 08:03
@martinvw martinvw added this to the 2.3 milestone May 25, 2018
hww3 pushed a commit to hww3/openhab2-addons that referenced this pull request Jul 4, 2018
* Initial contribution for new Denon-Marantz 2.0 binding

Also-by: Jeroen Idserda (github: idserda)
Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: @jwveldhuis)
divyachauhan25 pushed a commit to divyachauhan25/openhab2-addons that referenced this pull request Jul 6, 2018
* Initial contribution for new Denon-Marantz 2.0 binding

Also-by: Jeroen Idserda (github: idserda)
Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: @jwveldhuis)
@grzegorz914
Copy link

grzegorz914 commented Jul 11, 2018

Hi,

I use Your binding wit OH2.4 and AVR-X6300H, all working OK except "surroundProgram", this tag show nothing, connection HTTP or Telnet make no differences. Look on screenshot.

zrzut ekranu 2018-07-11 o 18 21 21

Thanks
Grzegorz

New issue #3724

@jwveldhuis
Copy link
Contributor Author

@grzegorz914 please create a new issue for this. Please don’t add comments to closed issues.

koa pushed a commit to koa/openhab2-addons that referenced this pull request Oct 28, 2018
* Initial contribution for new Denon-Marantz 2.0 binding

Also-by: Jeroen Idserda (github: idserda)
Signed-off-by: Jan-Willem Veldhuis <[email protected]> (github: @jwveldhuis)
clinique pushed a commit to clinique/openhab-addons that referenced this pull request Apr 14, 2022
* Remove duplications
* Better null handling
* Use constants
* Use default waitForAssert timeout (10s)

This may help to get the test more stable (openhab#1089).

Signed-off-by: Wouter Born <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new binding If someone has started to work on a binding. For a new binding PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants