-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
[vdr] Initial contribution #9947
[vdr] Initial contribution #9947
Conversation
Signed-off-by: Matthias Klocke <[email protected]>
corrected formatting and comment issues Signed-off-by: Matthias Klocke <[email protected]>
comment and formatting issues Signed-off-by: Matthias Klocke <[email protected]>
Unit Tests comparing Start and End Dates of EPG events did not reflect TimeZones. That resulted in failed tests when tests where executed on Machines that did not run in local time zone but in UTC. Unit Tests were corrected so that the desired values to check against were enhanced by a time zone. Signed-off-by: Matthias Klocke <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your contribution! I reviewed your code and here is my feedback.
|
||
## Supported Things | ||
|
||
The binding provides only one thing type: VDR. You can create one thing for each VDR instance at your home. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you mention the Thing Type ID?
| currentEventSubTitle | String | Current EPG Event Sub Title | | ||
| currentEventBegin | DateTime | Current EPG Event Begin | | ||
| currentEventEnd | DateTime | Current EPG Event End | | ||
| currentEventDuration | Number | Current EPG Event Duration in Minutes | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could make use of Units of Measure and make the type Number:Time
. See https://www.openhab.org/docs/concepts/units-of-measurement.html
Same for nextEventDuration
.
this.getThing().getUID(), channelUID, e.getMessage()); | ||
} | ||
}); | ||
updateStatus(ThingStatus.OFFLINE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could set the message your are logging here as the status detail message to display it in the UI. Then, you can remove the logging, as the message is also logged by the framework.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I thought about it and removed the logging. The code is reached when the thing is really offline and there's nothing wrong so it is not needed to give it a status detail message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good practice to set a text message anyway, as you might troubleshoot why the Thing is offline. It could also be an error in name resolution or a network failure. The status detail message is only displayed if you click on the Thing and not in the overview. So, don't be afraid of polluting the UI with detailed messages.
if ("power".equals(channelUID.getIdWithoutGroup())) { | ||
updateState(channelUID, OnOffType.OFF); | ||
} | ||
} catch (Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What type of exception do you expect here? Can you specify the concrete type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed try and catch as it was not necessary.
bundles/org.openhab.binding.vdr/src/main/resources/OH-INF/thing/thing-types.xml
Show resolved
Hide resolved
bundles/org.openhab.binding.vdr/src/main/resources/OH-INF/thing/thing-types.xml
Outdated
Show resolved
Hide resolved
bundles/org.openhab.binding.vdr/src/main/resources/OH-INF/thing/thing-types.xml
Outdated
Show resolved
Hide resolved
<channel-type id="vdrPower"> | ||
<item-type>Switch</item-type> | ||
<label>Power State</label> | ||
<description>Power State (to switch off VDR)</description> | ||
<state readOnly="false"/> | ||
</channel-type> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are predefined channel types you can use. See https://www.openhab.org/docs/developer/bindings/thing-xml.html#system-state-channel-types This could replaced by system.power.
@NonNullByDefault | ||
public class SVDRPChannelTest { | ||
|
||
private final String channelResponseOk = "3 WDR HD Bielefeld"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this channel even exist? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You got me here. It's all part of a big conspiracy ;-)
Signed-off-by: Matthias Klocke <[email protected]> Co-authored-by: Fabian Wolter <[email protected]> Signed-off-by: Matthias Klocke <[email protected]>
Signed-off-by: Matthias Klocke <[email protected]>
90602c4
to
13a2b52
Compare
this.getThing().getUID(), channelUID, e.getMessage()); | ||
} | ||
}); | ||
updateStatus(ThingStatus.OFFLINE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good practice to set a text message anyway, as you might troubleshoot why the Thing is offline. It could also be an error in name resolution or a network failure. The status detail message is only displayed if you click on the Thing and not in the overview. So, don't be afraid of polluting the UI with detailed messages.
result = new DateTimeType(LocalDateTime | ||
.ofInstant(entry.getBegin().toInstant(), timeZoneProvider.getTimeZone()) | ||
.atZone(TimeZone.getDefault().toZoneId())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The semantic of your code is "take the VDR time and treat it as it was the timezone configured within OH and convert it to the system default time". But what you want is to convert it to the timezone configured within OH.
This suggestion assumes that the time, VDR reports, has the same timezone as the operating system OH runs on.
result = new DateTimeType(LocalDateTime | |
.ofInstant(entry.getBegin().toInstant(), timeZoneProvider.getTimeZone()) | |
.atZone(TimeZone.getDefault().toZoneId())); | |
result = new DateTimeType(LocalDateTime | |
.ofInstant(entry.getBegin().toInstant(), ZoneId.systemDefault()) | |
.atZone(timeZoneProvider.getTimeZone())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, removed all old java date time api.
*/ | ||
@Override | ||
public void openConnection() throws SVDRPConnectionException, SVDRPParseResponseException { | ||
@Nullable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compiler is more intelligent on local variables. Therefore, the Nullable could be removed. Please check all.
long begin = Long.parseLong(lt.nextToken()); | ||
begin *= 1000L; | ||
entry.setBegin(new Date(begin)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could directly use Instant
instead of converting it to Date
and then to Instant
.
long begin = Long.parseLong(lt.nextToken()); | |
begin *= 1000L; | |
entry.setBegin(new Date(begin)); | |
long begin = Long.parseLong(lt.nextToken()) | |
entry.setBegin(Instant.ofEpochSecond(begin)); |
Calendar calendar = Calendar.getInstance(); | ||
calendar.setTime(entry.getBegin()); | ||
calendar.add(Calendar.MINUTE, entry.getDuration()); | ||
entry.setEnd(calendar.getTime()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then, you also get rid of the old Calendar API.
Calendar calendar = Calendar.getInstance(); | |
calendar.setTime(entry.getBegin()); | |
calendar.add(Calendar.MINUTE, entry.getDuration()); | |
entry.setEnd(calendar.getTime()); | |
entry.setEnd(entry.getBegin().plus(entry.getDuration(), ChronoUnit.MINUTES)); |
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd"> | ||
|
||
<name>VDR Binding</name> | ||
<description>The Video Disk Recorder (VDR) binding allows openHAB to control your own Video Disk Recorder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<description>The Video Disk Recorder (VDR) binding allows openHAB to control your own Video Disk Recorder | |
<description>The Video Disk Recorder (VDR) binding allows to control your own Video Disk Recorder |
</channel-type> | ||
<channel-type id="vdrEventDuration"> | ||
<item-type>Number:Time</item-type> | ||
<label>Event Duration </label> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<label>Event Duration </label> | |
<label>Event Duration</label> |
better thing status handling, removed old java date/time api, formatting issues Signed-off-by: MatthiasKlocke <[email protected]> Signed-off-by: Matthias Klocke <[email protected]>
d0f94ce
to
6968a91
Compare
updateStatus(ThingStatus.OFFLINE); | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, ce.getMessage()); | ||
} catch (SVDRPParseResponseException se) { | ||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, se.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UNKNOWN status has no status detail nor message. You could make this OFFLINE, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see my comment below for the other issue.
// also update power channel when thing is offline when initializing | ||
thing.getChannels().stream().map(c -> c.getUID()).filter(this::isLinked).forEach(channelUID -> { | ||
if (VDRBindingConstants.CHANNEL_UID_POWER.equals(channelUID.getIdWithoutGroup())) { | ||
updateState(channelUID, OnOffType.OFF); | ||
} | ||
}); | ||
updateStatus(ThingStatus.OFFLINE); | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, ce.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems rather ThingStatusDetail.COMMUNICATION_ERROR
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intention was that this is normal offline state, that is not critical or an error. If the VDR cannot be reached because it is turned off I did not want to show it in Error State but as suggested by you with a message on detail page.
.
If there is a problem (which is represented by SVDRPParseResponseException) then I set ThingStatus to unknown because of a communication error and I don't really know if it's really online or offline. If this is the case then ThingStatus on thing overview page is also displayed as communication error.
As a VDR is not running 24x7 for many people it's status will be offline regularly because it cannot be reached. So there's no action for the openhab user to do. If there is a problem in communication (and it's reachable) then currently Communication Error is displayed also in Thing list in openHAB.
If you say that "Communication Error" is the normal state for things that cannot be reached (also in other bindings) then I will change ThingStatusDetail to communication error (also it feels a little wrong for me).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your point. But, indeed, other bindings set the Thing status OFFLINE, when their Thing is not reachable. Whether this is supposed to happen on a regular basis or not, doesn't play any role. In fact it is a communication error if the Thing is not reachable, for whatever reason.
The UNKNOWN status should only be set during initialization. See the definition https://www.openhab.org/docs/concepts/things.html#thing-status
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your explanation, Fabian. I now set the communication error in both cases. And thanks for your patience.
Just one more question: I now set the combination of Status Offline and StatusDetail Communication Error. that was what you meant, or did you also mean to set status to Online in error case?
set thing status to communication error when communication with thing goes wrong. Signed-off-by: Matthias Klocke <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
As this is a new binding, another maintainer needs to review your code before it can be merged.
updateStatus(ThingStatus.UNKNOWN); | ||
|
||
// background initialization: | ||
scheduler.execute(() -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason that you couldn't just execute scheduleRefreshThread()
instead of this asynchronous process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your advice!
The scheduleRefreshThread did almost everything as the coding in initaliziation. So I changed to your suggestion, but did not yet commit as I have one question about it.
As I interpret the diagram (Things | openhab) the openHAB thing lifecycle allows transitions from initialized back to uninitialized and the back to initialized. So I'd like to keep the stopRefreshThread call in line 93 to ensure that there is no existing refresh thread still running. Would that be OK or is there a better alternative I have overlooked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no harm in calling stopRefreshThread
first if you think it would be safer, although I don't really think it is required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
I left the line in for now but will do some long-term analysis on my local machine with extra debugging to see if and when it's called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @cpmeister !
I have removed the stopRefreshThread call in initialization now as suggested by you. In my local environment with debug output the method's content was never called in the last month. Therefore I now also think the call is not necessary.
Apart from that I solved a little bug in the SVDRP client.
Can I do anything else for you?
Consolidated onVDRRefresh and initialize methods Signed-off-by: Matthias Klocke <[email protected]>
corrected Channel label for Next EPG Event End Signed-off-by: Matthias Klocke <[email protected]>
Removed stopRefreshThread in initialization method. Optimized SVDRP Parsing for volume in case VDR is muted Signed-off-by: Matthias Klocke <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I see correctly, @cpmeister's comment is addressed.
Due to lack of review capacity, the rules for new bindings were weakened. New bindings need only the approval of one reviewer instead of two from now on.
Signed-off-by: Matthias Klocke <[email protected]> Signed-off-by: John Marshall <[email protected]>
Signed-off-by: Matthias Klocke <[email protected]>
Signed-off-by: Matthias Klocke <[email protected]>
Signed-off-by: Matthias Klocke <[email protected]>
This is the initial contibution of the VDR binding as announced in #9931.
A VDR binding was already part of openHAB 1 but until now nobody converted it to openHAB 3.
Therefore I created a new binding more or less from scratch that supports the old binding's functionality.
As there are still some users of the old vdr binding I decided to open a pull request so the binding could be included in the official openHAB distribution.
There has been a community discussion about it here: Vdr binding
The Video Disk Recorder (VDR) binding allows openHAB to control your own Video Disk Recorder.
The binding is based on VDR's own SVDRP (Simple VDR Protocol) connectivity. It supports remote control like changing volume and channels as well as sending key commands to your VDR. Also current and next EPG event data is available.
All supported functionality can be found in the README file.
The old binding was using an external library (svdrp4j) to connect to VDR. For the new binding I decided to write my own thin client implementation (package org.openhab.binding.vdr.internal.svdrp) for SVDRP connection as the external library is not available in public maven repositories and for the binding only specific functionality was needed.
To use the binding a VDR instance is needed that the binding can connect to with VDR's SVDRP protocol.
Binding Build:
org.openhab.binding.vdr-3.1.0-SNAPSHOT.jar