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

[Russound] Added discovery of new devices #2135

Merged
merged 1 commit into from
Apr 28, 2017

Conversation

tmrobert8
Copy link

This pull request adds auto discovery of Russound devices (and all controllers/sources/zones on the device itself). Likewise, I've added all the media management functionality (to support UIs - like the HABPanel widget I've developed for this). Removed/rearranged alot of functionality as well.

Let me know what I've done wrong ;)

@mention-bot
Copy link

@tmrobert8, thanks for your PR! By analyzing the history of the files in this pull request, we identified @kaikreuzer and @martinvw to be potential reviewers.

@martinvw
Copy link
Member

@tmrobert8 can you take a look at the errors reported by the build?

@tmrobert8
Copy link
Author

Martin - interesting decision enforcing IAE over NPE - but did the change...

@martinvw
Copy link
Member

martinvw commented Apr 12, 2017

@tmrobert8 I did not look at the errors, what was the exact error? Was it that you should not throw your own NPE?

I think I agree with this answer: http://stackoverflow.com/questions/3322638/is-it-okay-to-throw-nullpointerexception-programmatically#answer-3323006

@tmrobert8
Copy link
Author

Yep - I personally don't agree with that but I don't feel strongly enough to really care that much (nor care to waste my time arguing one way or the other)

;)

At any rate, changed them all to IAE and it passes now.

Copy link
Member

@martinvw martinvw left a comment

Choose a reason for hiding this comment

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

Thanks for the big update of your/this binding. Great to see bindings being actively maintained by their authors!

I see that you reverted some changes which have been made in the meanwhile I don't know whether this was on purpose or not, but please keep them intact :-)

  • So the prefered order of modifiers final static
  • Loggers should be non-static and called logger.

I think that the code guidelines might have been updated since this binding was first developed. But it would be great if we could make your binding follow the current guidelines, if you really want we could make that a separate PR (before or after this one) but I prefer not to add new 'violations'.

So can you also please check the full report, you can find it in:

openhab2-addons\addons\binding\org.openhab.binding.russound\target\code-analysis\report.html

Besides that I added some other comments (but most of them are about the (current) guidelines). Questions? Please let me know I'll try to answer them ASAP.

@@ -33,11 +35,10 @@
* @author Tim Roberts
*/
public class RussoundHandlerFactory extends BaseThingHandlerFactory {
private final static Logger logger = LoggerFactory.getLogger(RussoundHandlerFactory.class);
Copy link
Member

Choose a reason for hiding this comment

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

loggers should non-static, can you check all of them?

logger.trace("Try to register Discovery service on BundleID: {} Service: {}",
bundleContext.getBundle().getBundleId(), DiscoveryService.class.getName());

Hashtable<String, String> prop = new Hashtable<String, String>();
Copy link
Member

Choose a reason for hiding this comment

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

Hashtable should not be used anymore, see the following remark

If a thread-safe implementation is not needed, it is recommended to use HashMap in place of Hashtable. If a thread-safe highly-concurrent implementation is desired, then it is recommended to use ConcurrentHashMap in place of Hashtable.

Source: https://docs.oracle.com/javase/7/docs/api/java/util/Hashtable.html

Copy link
Author

Choose a reason for hiding this comment

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

Unfortunately the bundleContext.registerService call requires a Dictionary<String, ?> - of which Hashtable is the only Dictionary implementation (there might be something in apache utils that I'm unaware of). Ignoring this one

Copy link
Member

Choose a reason for hiding this comment

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

Pity, sorry for the false alarm 🙁

private final RioSystemHandler sysHandler;

/** Pattern to identify controller notifications */
private final Pattern RSP_CONTROLLERNOTIFICATION = Pattern.compile("(?i)^[SN] C\\[(\\d+)\\]\\.(\\w+)=\"(.*)\"$");
Copy link
Member

Choose a reason for hiding this comment

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

All caps are reserved for constants. If this is one, can you then make it private final static

Copy link
Author

Choose a reason for hiding this comment

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

Pretty sure I got them all (over about 7 classes). Let me know if I didn't on the next commit

_listener = null;
}

// sysHandler.unregisterDiscoveryService();
Copy link
Member

Choose a reason for hiding this comment

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

Please remove this commented line of code

Copy link
Author

Choose a reason for hiding this comment

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

I did this - however, just food for thought. I fully disagree with this standard. Commented code has alot of value to someone reading the code (including the original author) since it's like code that was commented out either for a specific reason or for code that could potentially be used in the future. I see it as very much like documentation. But again, don't really care about this one - so I did it (any any others that I coudl find)

/**
* The {@link SocketSession} that will be used to scan the device
*/
private SocketSession _session;
Copy link
Member

Choose a reason for hiding this comment

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

Please follow the java naming convention instance fields are camelCased and do not have underscores in them.
See bullat A.1 @ http://docs.openhab.org/developers/development/guidelines.html#a-code-style

/**
* The minimum timespan between updates of source presets via {@link #refreshPresets(Integer)}
*/
private static final long UPDATETIMESPAN = 60000;
Copy link
Member

Choose a reason for hiding this comment

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

Minor: I think the correct name would be UPDATE_TIME_SPAN.

Just a suggestion did you know java allows underscore in number to be able to properly display large value, so you could do this:

private static final long UPDATE_TIME_SPAN_MS = 60_000;

/**
* Sets the zone favorites to what is currently playing
*
* @param favJsona possibly null, possibly empty json for favorites to set
Copy link
Member

Choose a reason for hiding this comment

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

Minor: favJsona => missing space in docblock


final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withProperty(RioZoneConfig.Zone, z).withBridge(controllerUID)
.withLabel((StringUtils.isEmpty(name) || name.equalsIgnoreCase("null") ? "Zone" : name)
Copy link
Member

Choose a reason for hiding this comment

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

You just checked name StringUtils.isNotEmpty(name) on line 218. And can it really equals the string "null", then this should suffice: name.equalsIgnoreCase("null") ? "Zone" : name

Copy link
Author

Choose a reason for hiding this comment

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

Remove the redundant isnotempty name - unfortunately Russound will put out a 'null' name

for (int i = 1; i < 255; i++) {
addresses[i - 1] = subnet + i;
}
for (int i = 0; i < addresses.length; i++) {
Copy link
Member

Choose a reason for hiding this comment

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

Why do you iterate twice? You should only need the first one with the body of the second one, does this work?

for (int i = 1; i < 255; i++) {
     final String address = subnet + i;
    scheduler.execute(new Runnable() {
        @Override
        public void run() {
            scanAddress(address);
        }
    });
}

Copy link
Author

Choose a reason for hiding this comment

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

lol - use to be a ton of code between those statements and didn't notice that when I removed it all!

logger.trace("Starting scan of {}", subnet);

final String[] addresses = new String[254];
for (int i = 1; i < 255; i++) {
Copy link
Member

Choose a reason for hiding this comment

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

Are you aware that subnets can also be bigger?

You can have this complete range in a single home network: 192.168.0.0 - 192.168.255.255 or even bigger.

Besides that maybe a warning should be added before starting discovery like this, because some (company) networks might not like this port-scan. And if someone is connected to a vpn that network will be scanned as well.

Copy link
Author

Choose a reason for hiding this comment

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

In reverse - is there a way to 'warn' someone before starting a discovery? Not aware but let me know if there is one.

Of course they can be bigger, but I'm looking at a normal use case (ie home network) which, 99.9% of the time, will be a simple /24. Would be too onerous to try to do a full scan. For those people that fall into the .1% with complex HOME networks - they can simply define the system bridge directly (they'd certainly not need a discovery process).

Copy link
Member

Choose a reason for hiding this comment

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

Note that the Network binding does the same, so most likely this is not a problem at all 👍 . I just happened to see that the Network binding uses a subnet calculation via an exernal jar maybe you could check it out too?

The only plausible ways to add a warning are:

  • the Readme (most likely only read after any problem)
  • or optionally in description of the binding it's displayed if you click from the PaperUI inbox to discover new devices, an impression of how this could look like.

image

Its up to you :-)

Copy link
Author

Choose a reason for hiding this comment

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

Didn't know about subnetutils - incorporated it (and the executor as well)

*/
@Override
public void responseReceived(String response) {
if (response == null || response == "") {
Copy link
Member

Choose a reason for hiding this comment

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

You cannot compare a string like this, maybe you should use commons stringutils I saw that it was used in other places of this binding as well.

Copy link
Member

Choose a reason for hiding this comment

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

You might have other occurrences of this same line in other places as well, can you search for it?

Copy link
Author

Choose a reason for hiding this comment

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

lol - I work with too many languages and that occasionally slips through!

@martinvw
Copy link
Member

Yep - I personally don't agree with that but I don't feel strongly enough to really care that much (nor care to waste my time arguing one way or the other)

;)

At any rate, changed them all to IAE and it passes now.
Note that there is another option Objects.requireNonNull(gson, "gson cannot be null"), this will change your 3 line check to single line, if you like you can check it out. And you can still throw NPE's from the precondition :-)

https://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#requireNonNull(T,%20java.lang.String)

Although I do think that you should NOT throw NPE for things which non-null but empty.

@SuppressWarnings("rawtypes")
public RioSystemFavoritesProtocol getSystemFavoritesHandler() {
final Bridge bridge = getBridge();
if (bridge != null && bridge.getHandler() instanceof AbstractBridgeHandler) {
Copy link
Member

Choose a reason for hiding this comment

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

Why the difference with line 110

Copy link
Author

Choose a reason for hiding this comment

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

Martin - btw just wanted to thank you for the time reviewing. I have no idea what you mean by this comment - would you mind explaining a bit more?

Copy link
Member

Choose a reason for hiding this comment

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

I would expect that since line 109 and 125 are the same and line 111 and 127 are doing more or less the same (calling a method on the bridge its handler). I would expected the lines 110 and 125 to be resilient to the same amount of null values. But I might miss some knowledge which would make it unnecessary.

@tmrobert8
Copy link
Author

Martin, I believe I've gotten all the changes (might have missed some). You'll see a few did change quite radically (mainly SocketChannelSession) because I took the opportunity to rewrite it a bit (to simplify) - always wanted to do that but never had the motivation to sit down and do it!

Let me know if you see anything I missed

Copy link
Member

@martinvw martinvw left a comment

Choose a reason for hiding this comment

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

I'm glad you're okay with my review. Great that you were able to fix all of it in such a short time!

I owed you an explanation of an earlier remark so I gave it. And you missed one thing, the order of some of the modifiers maybe you can search and replace all?

Did you btw also see my message about requireNonNull?

@@ -15,6 +15,11 @@
*/
public class RioSourceConfig {
/**
* Constant defined for the "source" configuration field
*/
public final static String Source = "source";
Copy link
Member

Choose a reason for hiding this comment

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

It seems that you missed this one

@SuppressWarnings("rawtypes")
public RioSystemFavoritesProtocol getSystemFavoritesHandler() {
final Bridge bridge = getBridge();
if (bridge != null && bridge.getHandler() instanceof AbstractBridgeHandler) {
Copy link
Member

Choose a reason for hiding this comment

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

I would expect that since line 109 and 125 are the same and line 111 and 127 are doing more or less the same (calling a method on the bridge its handler). I would expected the lines 110 and 125 to be resilient to the same amount of null values. But I might miss some knowledge which would make it unnecessary.

@@ -19,118 +19,134 @@
public class RioConstants {

// BRIDGE TYPE IDS
public static final ThingTypeUID BRIDGE_TYPE_RIO = new ThingTypeUID(RussoundBindingConstants.BINDING_ID, "rio");
public static final ThingTypeUID BRIDGE_TYPE_CONTROLLER = new ThingTypeUID(RussoundBindingConstants.BINDING_ID,
public final static ThingTypeUID BRIDGE_TYPE_RIO = new ThingTypeUID(RussoundBindingConstants.BINDING_ID, "rio");
Copy link
Member

Choose a reason for hiding this comment

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

Some public final static are still changed maybe you can do a replace-all 'final static' -> 'static final'

Copy link
Author

Choose a reason for hiding this comment

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

lol - I did a global replace on private static final and missed the public ones. Not sure why I did that! (must have been caught up in the moment!)

Copy link
Author

Choose a reason for hiding this comment

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

Ah - I see what you meant about the lines - don't know why they differ (probably copy/pasted and fixed one without the other). At any rate, I've updated to these latest comments and pushed.

Note: I've known about the requireNotNull and use it in current (other) bindings - but I didn't think about it when I made all these mods and tell you the truth - will probably leave it for a future date at this point (want to get this off my plate to get back to the sony binding)

Copy link
Member

@martinvw martinvw left a comment

Choose a reason for hiding this comment

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

@tmrobert8 thanks!

@kaikreuzer since its not a small PR on an existing binding its yours :-)

A lot of 'warnings' from the code guidelines were resolved, new functions were added and the readme was extended. So LGTM

@martinvw martinvw assigned martinvw and kaikreuzer and unassigned martinvw Apr 14, 2017
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.

I think @tmrobert8 just wants to avoid any code reviews by producing more code than anyone would be willing to review ;-) Amazing work...

So I only managed to scan very superfluously over it and came up with a few very unimportant comments.

discoverControllers();
discoverSources();
} catch (IOException e) {
logger.warn("Trying to scan device but couldn't connect: {}", e.getMessage(), e);
Copy link
Member

Choose a reason for hiding this comment

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

that might be expected, it the device is not reachable, right? So better reduce this to debug.

this.scheduler.execute(new Runnable() {
@Override
public void run() {
logger.info("Starting device discovery");
Copy link
Member

Choose a reason for hiding this comment

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

please reduce this to debug

* keypress/keyrelease/keyhold/keycode/event are advanced commands that will pass the related event string to Russound (i.e. "EVENT C[x].Z[y]!KeyPress [stringtype]"). Please see the "RIO Protocol for 3rd Party Integrators.pdf" (found at the Russound Portal) for proper string forms.
* If you send a OnOffType to the volume will have the same affect as turning the zone on/off (ie sending OnOffType to "status")
* The volume PercentType will be scaled to Russound's volume of 0-50 (ie 50% = volume of 25, 100% = volume of 50)
##### System Favorites
Copy link
Member

Choose a reason for hiding this comment

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

please add an empty line after this

| mminit | W | Switch**** | Whether to initial a media management session (ON) or close an existing one (OFF) |
| mmcontextmenu | W | Switch**** | Whether to initial a media management context session (ON) or close an existing one (OFF) |

#### Notes:
Copy link
Member

Choose a reason for hiding this comment

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

please add an empty line after this


There are three different ways to use this channel:
Copy link
Member

Choose a reason for hiding this comment

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

please add an empty line after this

| name | RW | String | The name of the zone favorite (only saved when the 'savexxx' cmd is issued) |
| valid | R | Switch | If favorite is valid or not ('on' when favorite is saved, 'off' when deleted |
| cmd | W | String | The favorite command (see note below) |
##### Zone Favorites
Copy link
Member

Choose a reason for hiding this comment

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

please add an empty line after this


The favorite command channel ("cmd") supports the following
There are two different ways to use this channel:
Copy link
Member

Choose a reason for hiding this comment

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

please add an empty line after this

| name | RW | String | The name of the preset (only saved when the 'save' preset cmd is issued) |
| valid | R | Switch | If favorite is valid or not ('on' when a preset is saved, 'off' when preset is deleted) |
| cmd | W | String | The preset command (see note below) |
##### Zone Presets
Copy link
Member

Choose a reason for hiding this comment

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

please add an empty line after this


The preset command channel ("cmd") supports the following
There are two different ways to use this channel:
Copy link
Member

Choose a reason for hiding this comment

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

please add an empty line after this

@martinvw martinvw closed this Apr 24, 2017
@martinvw martinvw reopened this Apr 24, 2017
@tmrobert8
Copy link
Author

Kai - submitted your changes (note: the log.warn one is actually questionable - device discovery starts AFTER we successfully connected to the device - so if we couldn't connect in discovery, something really went wrong [unit turned off, etc]). Don't think it's big enough of an issue to argue however...

@martinvw
Copy link
Member

@tmrobert8 can you squash your commits and change commit message to include a Signed-off-by (to certify that you wrote it or otherwise have the right to pass it on as an open-source), see http://docs.openhab.org/developers/contributing/contributing.html#sign-your-work

For squashing / rebasing see:
http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html

Changes from Review

Updated README.md and removed unnecessary functionality (converted NPE
to IAE).
Fixed turnonvolume and updated license headers

Updated for autodiscovery and media management channels

Signed-off-by: Tim Roberts <troberts@bigfoot.com> (github: tmrobert8)
@tmrobert8
Copy link
Author

Should be done

@martinvw
Copy link
Member

martinvw commented Apr 26, 2017

LGTM thanks!

Approved with PullApprove

@tmrobert8
Copy link
Author

Any idea when this will be merged? I wouldn't normally ask but I've got someone waiting on the merge to start writing the rnet protocol and he is waiting on the merge to start working on it Russound RNET

@kaikreuzer
Copy link
Member

@tmrobert8 Thanks for your quick updates and sorry to have kept you waiting!

@kaikreuzer kaikreuzer merged commit 38df024 into openhab:master Apr 28, 2017
@tmrobert8
Copy link
Author

No problems - enjoy the foundation meeting!

@kaikreuzer kaikreuzer modified the milestone: 2.1 Jun 23, 2017
@kaikreuzer kaikreuzer added the enhancement An enhancement or new feature for an existing add-on label Jun 24, 2017
@martinvw martinvw changed the title 1974 Russound Discovery [Russound] Added discovery of new devices Jun 25, 2017
@tmrobert8 tmrobert8 deleted the 1974-Russound-Discovery branch July 31, 2018 12:40
Flole998 pushed a commit to Flole998/openhab-addons that referenced this pull request Dec 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement An enhancement or new feature for an existing add-on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants