Compare commits

...

8 Commits

Author SHA1 Message Date
jimtng
65d58920c7
Merge ad499a5eff into 4e88f48a71 2025-01-09 21:14:33 +10:00
lsiepel
4e88f48a71
[tacmi] Fix SAT errors (#18046)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-01-09 12:07:50 +01:00
Jacob Laursen
e69c44b85e
Disable another unstable test (#18069)
Related to #12474

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-01-09 12:07:01 +01:00
Jacob Laursen
46d27b6fb5
Disable another unstable test (#18068)
Related to #12667

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-01-09 12:06:30 +01:00
mlobstein
f6efa87fb2
[roku] Add End Time and Media Progress channels (#18059)
Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
2025-01-09 08:12:57 +01:00
Jimmy Tanagra
ad499a5eff remove note for openhab 3.4
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
2025-01-09 13:57:14 +10:00
Jimmy Tanagra
3327261c71 Rules DSL, table formatting
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
2025-01-05 08:40:10 +10:00
Jimmy Tanagra
bb2a06f398 [jrubyscripting] Minor updates, add short examples at the top of README.md
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
2025-01-04 12:11:50 +10:00
12 changed files with 193 additions and 89 deletions

View File

@ -3,11 +3,55 @@
# JRuby Scripting # JRuby Scripting
This add-on provides [JRuby](https://www.jruby.org/) scripting language for automation rules. This add-on provides Ruby scripting language for automation rules.
Also included is [openhab-scripting](https://openhab.github.io/openhab-jruby/), a fairly high-level Ruby gem to support automation in openHAB. It includes the [openhab-scripting](https://openhab.github.io/openhab-jruby/) helper library, a comprehensive Ruby gem designed to enhance automation in openHAB.
It provides native Ruby access to common openHAB functionality within rules including items, things, actions, logging and more. This library offers a streamlined syntax for writing file-based and UI-based rules, making it easier and more intuitive than Rules DSL, while delivering the full features of the Ruby language.
If you're new to Ruby, you may want to check out [Ruby Basics](https://openhab.github.io/openhab-jruby/main/file.ruby-basics.html). If you're new to Ruby, you may want to check out [Ruby Basics](https://openhab.github.io/openhab-jruby/main/file.ruby-basics.html).
Example file-based rules:
```ruby
rule "Turn on light when sensor changed to open" do
changed Door_Sensor # a Contact item
run do |event|
if event.open?
Cupboard_Light.on for: 3.minutes # Automatically turn it off after 3 minutes
else
Cupboard_Light.off # This will automatically cancel the timer set above
end
end
end
```
```ruby
rule "Door open reminder" do
changed Doors.members, to: OPEN
run do |event|
# Create a timer using the triggering item as the timer id
# If a timer with the given id already exists, it will be rescheduled
after 5.minutes, id: event.item do |timer|
next if timer.cancelled? || event.item.closed?
Voice.say "The #{event.item} is open"
timer.reschedule # Use the original duration by default
end
end
end
```
Example UI-based rules:
```ruby
only_every(2.minutes) do # apply rate-limiting
Audio.play_sound("doorbell.mp3")
Notification.send("Someone pressed the doorbell")
end
```
Additional [example rules are available](https://openhab.github.io/openhab-jruby/main/file.examples.html), as well as examples of [conversions from Rules DSL, JavaScript, and Python rules](https://openhab.github.io/openhab-jruby/main/file.conversions.html).
- [Why Ruby?](#why-ruby) - [Why Ruby?](#why-ruby)
- [Installation](#installation) - [Installation](#installation)
- [Configuration](#configuration) - [Configuration](#configuration)
@ -66,14 +110,12 @@ If you're new to Ruby, you may want to check out [Ruby Basics](https://openhab.g
- [Calling Java From JRuby](#calling-java-from-jruby) - [Calling Java From JRuby](#calling-java-from-jruby)
- [Full Documentation](#full-documentation) - [Full Documentation](#full-documentation)
Additional [example rules are available](https://openhab.github.io/openhab-jruby/main/file.examples.html), as well as examples of [conversions from DSL and Python rules](https://openhab.github.io/openhab-jruby/main/file.conversions.html).
## Why Ruby? ## Why Ruby?
- Ruby is designed for programmers' productivity with the idea that programming should be fun for programmers. - Ruby is designed for programmers' productivity with the idea that programming should be fun for programmers.
- Ruby emphasizes the necessity for software to be understood by humans first and computers second. - Ruby emphasizes the necessity for software to be understood by humans first and computers second.
- Ruby makes writing automation enjoyable without having to fight with compilers and interpreters. - Ruby makes writing automation enjoyable with its readable syntax and a rich collection of useful methods in its built-in classes.
- Rich ecosystem of tools, including things like Rubocop to help developers write clean code and RSpec to test the libraries. - Rich ecosystem of tools and libraries, including things like Rubocop to help developers write clean code and RSpec to test the libraries.
- Ruby is really good at letting one express intent and create a DSL to make that expression easier. - Ruby is really good at letting one express intent and create a DSL to make that expression easier.
### Design points ### Design points
@ -88,35 +130,17 @@ Additional [example rules are available](https://openhab.github.io/openhab-jruby
- Designed and tested using [Test-Driven Development](https://en.wikipedia.org/wiki/Test-driven_development) with [RSpec](https://rspec.info/) - Designed and tested using [Test-Driven Development](https://en.wikipedia.org/wiki/Test-driven_development) with [RSpec](https://rspec.info/)
- Extensible. - Extensible.
- Anyone should be able to customize and add/remove core language features - Anyone should be able to customize and add/remove core language features
- Easy access to the Ruby ecosystem in rules through Ruby gems. - Easy access to the Ruby ecosystem in rules through [Ruby Gems](https://rubygems.org/).
## Installation ## Installation
### Prerequisites
1. openHAB 3.4+
1. The JRuby Scripting Language Addon
### From the User Interface ### From the User Interface
1. Go to `Settings -> Add-ons -> Automation` and install the jrubyscripting automation addon following the [openHAB instructions](https://www.openhab.org/docs/configuration/addons.html). - Go to `Settings -> Add-ons -> Automation` and install the jrubyscripting automation addon following the [openHAB instructions](https://www.openhab.org/docs/configuration/addons.html).
In openHAB 4.0+ the defaults are set so the next step can be skipped.
1. Go to `Settings -> Add-on Settings -> JRuby Scripting`:
- **Ruby Gems**: `openhab-scripting=~>5.0`
- **Require Scripts**: `openhab/dsl` (not required, but recommended)
### Using Files ### Using Files
1. Edit `<OPENHAB_CONF>/services/addons.cfg` and ensure that `jrubyscripting` is included in an uncommented `automation=` list of automations to install. - Edit `<OPENHAB_CONF>/services/addons.cfg` and ensure that `jrubyscripting` is included in an uncommented `automation=` list of automations to install.
In openHAB 4.0+ the defaults are set so the next step can be skipped.
1. Configure JRuby openHAB services
Create a file called `jruby.cfg` in `<OPENHAB_CONF>/services/` with the following content:
```ini
org.openhab.automation.jrubyscripting:gems=openhab-scripting=~>5.0
org.openhab.automation.jrubyscripting:require=openhab/dsl
```
## Configuration ## Configuration
@ -129,7 +153,7 @@ This functionality can be disabled for users who prefer to manage their own gems
Simply change the `gems` and `require` configuration settings. Simply change the `gems` and `require` configuration settings.
| Parameter | Description | | Parameter | Description |
| --------------------- | -------------------------------------------------------------------------------------------------------- | | --------------------- | ---------------------------------------------------------------------------------------------------------- |
| `gem_home` | The path to store Ruby Gems. <br/><br/>Default: `$OPENHAB_CONF/automation/ruby/.gem/RUBY_ENGINE_VERSION` | | `gem_home` | The path to store Ruby Gems. <br/><br/>Default: `$OPENHAB_CONF/automation/ruby/.gem/RUBY_ENGINE_VERSION` |
| `gems` | A list of gems to install. <br/><br/>Default: `openhab-scripting=~>5.0` | | `gems` | A list of gems to install. <br/><br/>Default: `openhab-scripting=~>5.0` |
| `check_update` | Check for updated version of `gems` on start up or settings change. <br/><br/>Default: `true` | | `check_update` | Check for updated version of `gems` on start up or settings change. <br/><br/>Default: `true` |
@ -766,8 +790,9 @@ To log a message on `INFO` log level:
logger.info("The current time is #{Time.now}") logger.info("The current time is #{Time.now}")
``` ```
The default logger name for UI rules is `org.openhab.automation.jrubyscripting.script`. The main logger prefix is `org.openhab.automation.jrubyscripting`.
For file-based rules, it's based on the rule's ID, such as `org.openhab.automation.jrubyscripting.rule.myrule.rb:15`. The default logger name for UI rules includes the rule ID: `org.openhab.automation.jrubyscripting.script.<RULE_ID>`.
The logger name for file-based rules includes the rule's filename and the rule ID: `org.openhab.automation.jrubyscripting.<filename>.rule.<RULE_ID>`.
To use a custom logger name: To use a custom logger name:
@ -776,7 +801,7 @@ logger = OpenHAB::Log.logger("org.openhab.custom")
``` ```
Please be aware that messages might not appear in the logs if the logger name does not start with `org.openhab`. Please be aware that messages might not appear in the logs if the logger name does not start with `org.openhab`.
This behaviour is due to [log4j2](https://logging.apache.org/log4j/2.x/) requiring definition for each logger prefix. This behavior is due to [log4j2](https://logging.apache.org/log4j/2.x/) requiring definition for each logger prefix.
The [logger](https://openhab.github.io/openhab-jruby/main/OpenHAB/Logger.html) is similar to a standard [Ruby Logger](https://docs.ruby-lang.org/en/master/Logger.html). The [logger](https://openhab.github.io/openhab-jruby/main/OpenHAB/Logger.html) is similar to a standard [Ruby Logger](https://docs.ruby-lang.org/en/master/Logger.html).
Supported logging functions include: Supported logging functions include:
@ -815,7 +840,7 @@ end
``` ```
Alternatively a timer can be used in either a file-based rule or in a UI based rule using [after](https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL.html#after-class_method). Alternatively a timer can be used in either a file-based rule or in a UI based rule using [after](https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL.html#after-class_method).
After takes a [Duration](#durations), e.g. `10.minutes` instead of using [ZonedDateTime](https://openhab.github.io/openhab-jruby/main/OpenHAB/CoreExt/Java/ZonedDateTime.html). After takes a [Duration](#durations) relative to `now`, e.g. `10.minutes`, or an absolute time with [ZonedDateTime](https://openhab.github.io/openhab-jruby/main/OpenHAB/CoreExt/Java/ZonedDateTime.html) or [Time](https://openhab.github.io/openhab-jruby/main/Time.html).
```ruby ```ruby
rule "simple timer" do rule "simple timer" do
@ -1065,6 +1090,10 @@ end
start_of_day = ZonedDateTime.now.with(LocalTime::MIDNIGHT) start_of_day = ZonedDateTime.now.with(LocalTime::MIDNIGHT)
# or # or
start_of_day = LocalTime::MIDNIGHT.to_zoned_date_time start_of_day = LocalTime::MIDNIGHT.to_zoned_date_time
# or
start_of_day = LocalDate.now.to_zoned_date_time
# or using Ruby Date
start_of_day = Date.today.to_zoned_date_time
# Comparing ZonedDateTime against LocalTime with `<` # Comparing ZonedDateTime against LocalTime with `<`
max = Solar_Power.maximum_since(24.hours.ago) max = Solar_Power.maximum_since(24.hours.ago)

View File

@ -46,6 +46,8 @@ The following channels are available:
| playMode | String | The current playback mode ie: stop, play, pause (ReadOnly). | | playMode | String | The current playback mode ie: stop, play, pause (ReadOnly). |
| timeElapsed | Number:Time | The total number of seconds of playback time elapsed for the current playing title (ReadOnly). | | timeElapsed | Number:Time | The total number of seconds of playback time elapsed for the current playing title (ReadOnly). |
| timeTotal | Number:Time | The total length of the current playing title in seconds (ReadOnly). This data is not provided by all streaming apps. | | timeTotal | Number:Time | The total length of the current playing title in seconds (ReadOnly). This data is not provided by all streaming apps. |
| endTime | DateTime | The date/time when the currently playing media will end (ReadOnly). N/A if timeTotal is not provided by the current streaming app. |
| progress | Dimmer | The current progress [0-100%] of playing media (ReadOnly). N/A if timeTotal is not provided by the current streaming app. |
| activeChannel | String | A dropdown containing a list of available TV channels on the Roku TV. The channel currently tuned is automatically selected. The list updates every 10 minutes. | | activeChannel | String | A dropdown containing a list of available TV channels on the Roku TV. The channel currently tuned is automatically selected. The list updates every 10 minutes. |
| signalMode | String | The signal type of the current TV channel, ie: 1080i (ReadOnly). | | signalMode | String | The signal type of the current TV channel, ie: 1080i (ReadOnly). |
| signalQuality | Number:Dimensionless | The signal quality of the current TV channel, 0-100% (ReadOnly). | | signalQuality | Number:Dimensionless | The signal quality of the current TV channel, 0-100% (ReadOnly). |
@ -59,6 +61,7 @@ The following channels are available:
Some Notes: Some Notes:
- The values for `activeApp`, `activeAppName`, `playMode`, `timeElapsed`, `timeTotal`, `activeChannel`, `signalMode`, `signalQuality`, `channelName`, `programTitle`, `programDescription`, `programRating`, `power` & `powerState` refresh automatically per the configured `refresh` interval. - The values for `activeApp`, `activeAppName`, `playMode`, `timeElapsed`, `timeTotal`, `activeChannel`, `signalMode`, `signalQuality`, `channelName`, `programTitle`, `programDescription`, `programRating`, `power` & `powerState` refresh automatically per the configured `refresh` interval.
- The `endTime` and `progress` channels may not be accurate for some streaming apps especially 'live' streams where the `timeTotal` value constantly increases.
**List of available button commands for Roku streaming devices:** **List of available button commands for Roku streaming devices:**
@ -120,6 +123,8 @@ Player Player_Control "Control" { channel="roku:roku_
String Player_PlayMode "Status: [%s]" { channel="roku:roku_player:myplayer1:playMode" } String Player_PlayMode "Status: [%s]" { channel="roku:roku_player:myplayer1:playMode" }
Number:Time Player_TimeElapsed "Elapsed Time: [%d %unit%]" { channel="roku:roku_player:myplayer1:timeElapsed" } Number:Time Player_TimeElapsed "Elapsed Time: [%d %unit%]" { channel="roku:roku_player:myplayer1:timeElapsed" }
Number:Time Player_TimeTotal "Total Time: [%d %unit%]" { channel="roku:roku_player:myplayer1:timeTotal" } Number:Time Player_TimeTotal "Total Time: [%d %unit%]" { channel="roku:roku_player:myplayer1:timeTotal" }
DateTime Player_EndTime "End Time: [%1$tl:%1$tM %1$tp]" { channel="roku:roku_player:myplayer1:endTime" }
Dimmer Player_Progress "Progress [%.0f%%]" { channel="roku:roku_player:myplayer1:progress" }
// Roku TV items: // Roku TV items:
@ -132,6 +137,8 @@ Player Player_Control "Control" { channel="roku:rok
String Player_PlayMode "Status: [%s]" { channel="roku:roku_tv:mytv1:playMode" } String Player_PlayMode "Status: [%s]" { channel="roku:roku_tv:mytv1:playMode" }
Number:Time Player_TimeElapsed "Elapsed Time: [%d %unit%]" { channel="roku:roku_tv:mytv1:timeElapsed" } Number:Time Player_TimeElapsed "Elapsed Time: [%d %unit%]" { channel="roku:roku_tv:mytv1:timeElapsed" }
Number:Time Player_TimeTotal "Total Time: [%d %unit%]" { channel="roku:roku_tv:mytv1:timeTotal" } Number:Time Player_TimeTotal "Total Time: [%d %unit%]" { channel="roku:roku_tv:mytv1:timeTotal" }
DateTime Player_EndTime "End Time: [%1$tl:%1$tM %1$tp]" { channel="roku:roku_tv:mytv1:endTime" }
Dimmer Player_Progress "Progress [%.0f%%]" { channel="roku:roku_tv:mytv1:progress" }
String Player_ActiveChannel "Current Channel: [%s]" { channel="roku:roku_tv:mytv1:activeChannel" } String Player_ActiveChannel "Current Channel: [%s]" { channel="roku:roku_tv:mytv1:activeChannel" }
String Player_SignalMode "Signal Mode: [%s]" { channel="roku:roku_tv:mytv1:signalMode" } String Player_SignalMode "Signal Mode: [%s]" { channel="roku:roku_tv:mytv1:signalMode" }
Number Player_SignalQuality "Signal Quality: [%d %%]" { channel="roku:roku_tv:mytv1:signalQuality" } Number Player_SignalQuality "Signal Quality: [%d %%]" { channel="roku:roku_tv:mytv1:signalQuality" }
@ -154,6 +161,8 @@ sitemap roku label="Roku" {
Text item=Player_PlayMode Text item=Player_PlayMode
Text item=Player_TimeElapsed icon="time" Text item=Player_TimeElapsed icon="time"
Text item=Player_TimeTotal icon="time" Text item=Player_TimeTotal icon="time"
Text item=Player_EndTime icon="time"
Slider item=Player_Progress icon="time"
// The following items apply to Roku TVs only // The following items apply to Roku TVs only
Switch item=Player_Power Switch item=Player_Power
Text item=Player_PowerState Text item=Player_PowerState

View File

@ -55,6 +55,8 @@ public class RokuBindingConstants {
public static final String PLAY_MODE = "playMode"; public static final String PLAY_MODE = "playMode";
public static final String TIME_ELAPSED = "timeElapsed"; public static final String TIME_ELAPSED = "timeElapsed";
public static final String TIME_TOTAL = "timeTotal"; public static final String TIME_TOTAL = "timeTotal";
public static final String END_TIME = "endTime";
public static final String PROGRESS = "progress";
public static final String ACTIVE_CHANNEL = "activeChannel"; public static final String ACTIVE_CHANNEL = "activeChannel";
public static final String SIGNAL_MODE = "signalMode"; public static final String SIGNAL_MODE = "signalMode";
public static final String SIGNAL_QUALITY = "signalQuality"; public static final String SIGNAL_QUALITY = "signalQuality";

View File

@ -14,6 +14,8 @@ package org.openhab.binding.roku.internal.handler;
import static org.openhab.binding.roku.internal.RokuBindingConstants.*; import static org.openhab.binding.roku.internal.RokuBindingConstants.*;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -34,8 +36,10 @@ import org.openhab.binding.roku.internal.dto.DeviceInfo;
import org.openhab.binding.roku.internal.dto.Player; import org.openhab.binding.roku.internal.dto.Player;
import org.openhab.binding.roku.internal.dto.TvChannel; import org.openhab.binding.roku.internal.dto.TvChannel;
import org.openhab.binding.roku.internal.dto.TvChannels.Channel; import org.openhab.binding.roku.internal.dto.TvChannels.Channel;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.NextPreviousType; import org.openhab.core.library.types.NextPreviousType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.PlayPauseType; import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
@ -195,21 +199,32 @@ public class RokuHandler extends BaseThingHandler {
PLAY.equalsIgnoreCase(playerInfo.getState()) ? PlayPauseType.PLAY : PlayPauseType.PAUSE); PLAY.equalsIgnoreCase(playerInfo.getState()) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
// Remove non-numeric from string, ie: ' ms' // Remove non-numeric from string, ie: ' ms'
String position = playerInfo.getPosition().replaceAll(NON_DIGIT_PATTERN, EMPTY); final String positionStr = playerInfo.getPosition().replaceAll(NON_DIGIT_PATTERN, EMPTY);
if (!EMPTY.equals(position)) { int position = -1;
updateState(TIME_ELAPSED, if (!EMPTY.equals(positionStr)) {
new QuantityType<>(Integer.parseInt(position) / 1000, API_SECONDS_UNIT)); position = Integer.parseInt(positionStr) / 1000;
updateState(TIME_ELAPSED, new QuantityType<>(position, API_SECONDS_UNIT));
} else { } else {
updateState(TIME_ELAPSED, UnDefType.UNDEF); updateState(TIME_ELAPSED, UnDefType.UNDEF);
} }
String duration = playerInfo.getDuration().replaceAll(NON_DIGIT_PATTERN, EMPTY); final String durationStr = playerInfo.getDuration().replaceAll(NON_DIGIT_PATTERN, EMPTY);
if (!EMPTY.equals(duration)) { int duration = -1;
updateState(TIME_TOTAL, if (!EMPTY.equals(durationStr)) {
new QuantityType<>(Integer.parseInt(duration) / 1000, API_SECONDS_UNIT)); duration = Integer.parseInt(durationStr) / 1000;
updateState(TIME_TOTAL, new QuantityType<>(duration, API_SECONDS_UNIT));
} else { } else {
updateState(TIME_TOTAL, UnDefType.UNDEF); updateState(TIME_TOTAL, UnDefType.UNDEF);
} }
if (position >= 0 && duration > 0) {
updateState(END_TIME, new DateTimeType(Instant.now().plusSeconds(duration - position)));
updateState(PROGRESS,
new PercentType(BigDecimal.valueOf(Math.round(position / (double) duration * 100.0))));
} else {
updateState(END_TIME, UnDefType.UNDEF);
updateState(PROGRESS, UnDefType.UNDEF);
}
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
logger.debug("Unable to parse playerInfo integer value. Exception: {}", e.getMessage()); logger.debug("Unable to parse playerInfo integer value. Exception: {}", e.getMessage());
} catch (RokuLimitedModeException e) { } catch (RokuLimitedModeException e) {
@ -224,6 +239,8 @@ public class RokuHandler extends BaseThingHandler {
updateState(PLAY_MODE, UnDefType.UNDEF); updateState(PLAY_MODE, UnDefType.UNDEF);
updateState(TIME_ELAPSED, UnDefType.UNDEF); updateState(TIME_ELAPSED, UnDefType.UNDEF);
updateState(TIME_TOTAL, UnDefType.UNDEF); updateState(TIME_TOTAL, UnDefType.UNDEF);
updateState(END_TIME, UnDefType.UNDEF);
updateState(PROGRESS, UnDefType.UNDEF);
} }
if (thingTypeUID.equals(THING_TYPE_ROKU_TV) && tvActive) { if (thingTypeUID.equals(THING_TYPE_ROKU_TV) && tvActive) {

View File

@ -80,6 +80,8 @@ channel-type.roku.channelName.label = Channel Name
channel-type.roku.channelName.description = The Name of the Channel Currently Selected channel-type.roku.channelName.description = The Name of the Channel Currently Selected
channel-type.roku.control.label = Control channel-type.roku.control.label = Control
channel-type.roku.control.description = Control playback e.g. Play/Pause/Next/Previous channel-type.roku.control.description = Control playback e.g. Play/Pause/Next/Previous
channel-type.roku.endTime.label = End Time
channel-type.roku.endTime.description = The date/time when the currently playing media will end
channel-type.roku.playMode.label = Play Mode channel-type.roku.playMode.label = Play Mode
channel-type.roku.playMode.description = The Current Playback Mode channel-type.roku.playMode.description = The Current Playback Mode
channel-type.roku.powerState.label = Power State channel-type.roku.powerState.label = Power State
@ -93,6 +95,8 @@ channel-type.roku.programRating.label = Program Rating
channel-type.roku.programRating.description = The TV Parental Guideline Rating of the Current TV Program channel-type.roku.programRating.description = The TV Parental Guideline Rating of the Current TV Program
channel-type.roku.programTitle.label = Program Title channel-type.roku.programTitle.label = Program Title
channel-type.roku.programTitle.description = The Name of the Current TV Program channel-type.roku.programTitle.description = The Name of the Current TV Program
channel-type.roku.progress.label = Media Progress
channel-type.roku.progress.description = The current progress of playing media
channel-type.roku.signalMode.label = Signal Mode channel-type.roku.signalMode.label = Signal Mode
channel-type.roku.signalMode.description = The Signal Type of the Current TV Channel, ie: 1080i channel-type.roku.signalMode.description = The Signal Type of the Current TV Channel, ie: 1080i
channel-type.roku.signalQuality.label = Signal Quality channel-type.roku.signalQuality.label = Signal Quality

View File

@ -19,6 +19,8 @@
<channel id="playMode" typeId="playMode"/> <channel id="playMode" typeId="playMode"/>
<channel id="timeElapsed" typeId="timeElapsed"/> <channel id="timeElapsed" typeId="timeElapsed"/>
<channel id="timeTotal" typeId="timeTotal"/> <channel id="timeTotal" typeId="timeTotal"/>
<channel id="endTime" typeId="endTime"/>
<channel id="progress" typeId="progress"/>
</channels> </channels>
<properties> <properties>
@ -28,7 +30,7 @@
<property name="Serial Number">unknown</property> <property name="Serial Number">unknown</property>
<property name="Device Id">unknown</property> <property name="Device Id">unknown</property>
<property name="Software Version">unknown</property> <property name="Software Version">unknown</property>
<property name="thingTypeVersion">1</property> <property name="thingTypeVersion">2</property>
</properties> </properties>
<representation-property>uuid</representation-property> <representation-property>uuid</representation-property>
@ -52,6 +54,8 @@
<channel id="playMode" typeId="playMode"/> <channel id="playMode" typeId="playMode"/>
<channel id="timeElapsed" typeId="timeElapsed"/> <channel id="timeElapsed" typeId="timeElapsed"/>
<channel id="timeTotal" typeId="timeTotal"/> <channel id="timeTotal" typeId="timeTotal"/>
<channel id="endTime" typeId="endTime"/>
<channel id="progress" typeId="progress"/>
<channel id="activeChannel" typeId="activeChannel"/> <channel id="activeChannel" typeId="activeChannel"/>
<channel id="signalMode" typeId="signalMode"/> <channel id="signalMode" typeId="signalMode"/>
<channel id="signalQuality" typeId="signalQuality"/> <channel id="signalQuality" typeId="signalQuality"/>
@ -69,7 +73,7 @@
<property name="Serial Number">unknown</property> <property name="Serial Number">unknown</property>
<property name="Device Id">unknown</property> <property name="Device Id">unknown</property>
<property name="Software Version">unknown</property> <property name="Software Version">unknown</property>
<property name="thingTypeVersion">1</property> <property name="thingTypeVersion">2</property>
</properties> </properties>
<representation-property>uuid</representation-property> <representation-property>uuid</representation-property>
@ -185,6 +189,24 @@
<state readOnly="true" pattern="%d %unit%"/> <state readOnly="true" pattern="%d %unit%"/>
</channel-type> </channel-type>
<channel-type id="endTime">
<item-type>DateTime</item-type>
<label>End Time</label>
<description>The date/time when the currently playing media will end</description>
<category>Time</category>
<tags>
<tag>Status</tag>
<tag>Timestamp</tag>
</tags>
<state readOnly="true"/>
</channel-type>
<channel-type id="progress">
<item-type>Dimmer</item-type>
<label>Media Progress</label>
<description>The current progress of playing media</description>
</channel-type>
<channel-type id="activeChannel"> <channel-type id="activeChannel">
<item-type>String</item-type> <item-type>String</item-type>
<label>Active Channel</label> <label>Active Channel</label>

View File

@ -12,6 +12,15 @@
<type>roku:control</type> <type>roku:control</type>
</add-channel> </add-channel>
</instruction-set> </instruction-set>
<instruction-set targetVersion="2">
<add-channel id="endTime">
<type>roku:endTime</type>
</add-channel>
<add-channel id="progress">
<type>roku:progress</type>
</add-channel>
</instruction-set>
</thing-type> </thing-type>
<thing-type uid="roku:roku_tv"> <thing-type uid="roku:roku_tv">
@ -29,6 +38,15 @@
<type>roku:control</type> <type>roku:control</type>
</add-channel> </add-channel>
</instruction-set> </instruction-set>
<instruction-set targetVersion="2">
<add-channel id="endTime">
<type>roku:endTime</type>
</add-channel>
<add-channel id="progress">
<type>roku:progress</type>
</add-channel>
</instruction-set>
</thing-type> </thing-type>
</update:update-descriptions> </update:update-descriptions>

View File

@ -185,6 +185,7 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
} else if ("durchsichtig".equals(classFlag)) { // link } else if ("durchsichtig".equals(classFlag)) { // link
this.fieldType = FieldType.IGNORE; this.fieldType = FieldType.IGNORE;
} else if ("bord".equals(classFlag)) { // special button style - not of our interest... } else if ("bord".equals(classFlag)) { // special button style - not of our interest...
continue;
} else { } else {
logger.debug("Unhanndled class in {}:{}:{}: '{}' ", id, line, col, classFlag); logger.debug("Unhanndled class in {}:{}:{}: '{}' ", id, line, col, classFlag);
} }
@ -192,7 +193,7 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
} }
} else if (this.parserState == ParserState.DATA_ENTRY && this.fieldType == FieldType.BUTTON } else if (this.parserState == ParserState.DATA_ENTRY && this.fieldType == FieldType.BUTTON
&& "span".equals(elementName)) { && "span".equals(elementName)) {
// ignored... return; // ignored...
} else { } else {
logger.debug("Unexpected OpenElement in {}:{}: {} [{}]", line, col, elementName, attributes); logger.debug("Unexpected OpenElement in {}:{}: {} [{}]", line, col, elementName, attributes);
} }
@ -245,14 +246,14 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
getApiPageEntry(id, line, col, shortName, description, this.buttonValue); getApiPageEntry(id, line, col, shortName, description, this.buttonValue);
} }
} else if (this.fieldType == FieldType.IGNORE) { } else if (this.fieldType == FieldType.IGNORE) {
// ignore return; // ignore
} else { } else {
logger.debug("Unhandled setting {}:{}:{} [{}] : {}", id, line, col, this.fieldType, sb); logger.debug("Unhandled setting {}:{}:{} [{}] : {}", id, line, col, this.fieldType, sb);
} }
} }
} else if (this.parserState == ParserState.DATA_ENTRY && this.fieldType == FieldType.BUTTON } else if (this.parserState == ParserState.DATA_ENTRY && this.fieldType == FieldType.BUTTON
&& "span".equals(elementName)) { && "span".equals(elementName)) {
// ignored... return;// ignored...
} else { } else {
logger.debug("Unexpected CloseElement in {}:{}: {}", line, col, elementName); logger.debug("Unexpected CloseElement in {}:{}: {}", line, col, elementName);
} }
@ -307,7 +308,7 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
} }
} else if (this.parserState == ParserState.INIT && ((len == 1 && buffer[offset] == '\n') } else if (this.parserState == ParserState.INIT && ((len == 1 && buffer[offset] == '\n')
|| (len == 2 && buffer[offset] == '\r' && buffer[offset + 1] == '\n'))) { || (len == 2 && buffer[offset] == '\r' && buffer[offset + 1] == '\n'))) {
// single newline - ignore/drop it... return; // single newline - ignore/drop it...
} else { } else {
String msg = new String(buffer, offset, len).replace("\n", "\\n").replace("\r", "\\r"); String msg = new String(buffer, offset, len).replace("\n", "\\n").replace("\r", "\\r");
logger.debug("Unexpected Text {}:{}: ParserState: {} ({}) `{}`", line, col, parserState, len, msg); logger.debug("Unexpected Text {}:{}: ParserState: {} ({}) `{}`", line, col, parserState, len, msg);
@ -400,9 +401,9 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
// failed to get unit... // failed to get unit...
if ("Imp".equals(unitStr) || "€$".contains(unitStr)) { if ("Imp".equals(unitStr) || "€$".contains(unitStr)) {
// special case // special case
unitData = taCmiSchemaHandler.SPECIAL_MARKER; unitData = TACmiSchemaHandler.SPECIAL_MARKER;
} else { } else {
unitData = taCmiSchemaHandler.NULL_MARKER; unitData = TACmiSchemaHandler.NULL_MARKER;
logger.warn( logger.warn(
"Unhandled UoM '{}' - seen on channel {} '{}'; Message from QuantityType: {}", "Unhandled UoM '{}' - seen on channel {} '{}'; Message from QuantityType: {}",
valParts[1], shortName, description, iae.getMessage()); valParts[1], shortName, description, iae.getMessage());
@ -410,12 +411,12 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
} }
taCmiSchemaHandler.unitsCache.put(unitStr, unitData); taCmiSchemaHandler.unitsCache.put(unitStr, unitData);
} }
if (unitData == taCmiSchemaHandler.NULL_MARKER) { if (unitData == TACmiSchemaHandler.NULL_MARKER) {
// no UoM mappable - just send value // no UoM mappable - just send value
channelType = "Number"; channelType = "Number";
unit = null; unit = null;
state = new DecimalType(bd); state = new DecimalType(bd);
} else if (unitData == taCmiSchemaHandler.SPECIAL_MARKER) { } else if (unitData == TACmiSchemaHandler.SPECIAL_MARKER) {
// special handling for unknown UoM // special handling for unknown UoM
if ("Imp".equals(unitStr)) { // Number of Pulses if ("Imp".equals(unitStr)) { // Number of Pulses
// impulses - no idea how to map this to something useful here? // impulses - no idea how to map this to something useful here?

View File

@ -102,7 +102,7 @@ public class ChangerX2Parser extends AbstractSimpleMarkupHandler {
this.optionFieldName = attributes == null ? null : attributes.get("name"); this.optionFieldName = attributes == null ? null : attributes.get("name");
} else if ((this.parserState == ParserState.INIT || this.parserState == ParserState.INPUT) } else if ((this.parserState == ParserState.INIT || this.parserState == ParserState.INPUT)
&& "br".equals(elementName)) { && "br".equals(elementName)) {
// ignored return; // ignored
} else if ((this.parserState == ParserState.INIT || this.parserState == ParserState.INPUT) } else if ((this.parserState == ParserState.INIT || this.parserState == ParserState.INPUT)
&& "input".equals(elementName) && "changeto".equals(id)) { && "input".equals(elementName) && "changeto".equals(id)) {
this.parserState = ParserState.INPUT_DATA; this.parserState = ParserState.INPUT_DATA;
@ -171,7 +171,6 @@ public class ChangerX2Parser extends AbstractSimpleMarkupHandler {
} }
this.options.put(ChangerX2Entry.TIME_PERIOD_PARTS, timeParts); this.options.put(ChangerX2Entry.TIME_PERIOD_PARTS, timeParts);
} else { } else {
logger.warn("Error parsing options for {}: Unhandled input field in {}:{}: {}", channelName, line, logger.warn("Error parsing options for {}: Unhandled input field in {}:{}: {}", channelName, line,
col, attributes); col, attributes);
} }
@ -218,7 +217,7 @@ public class ChangerX2Parser extends AbstractSimpleMarkupHandler {
} }
} }
} else if (this.parserState == ParserState.INPUT && "span".equals(elementName)) { } else if (this.parserState == ParserState.INPUT && "span".equals(elementName)) {
// span's are ignored... return; // span's are ignored...
} else { } else {
logger.debug("Error parsing options for {}: Unexpected CloseElement in {}:{}: {}", channelName, line, col, logger.debug("Error parsing options for {}: Unexpected CloseElement in {}:{}: {}", channelName, line, col,
elementName); elementName);
@ -275,10 +274,11 @@ public class ChangerX2Parser extends AbstractSimpleMarkupHandler {
sb.append(buffer, offset, len); sb.append(buffer, offset, len);
} }
} else if (this.parserState == ParserState.INIT && len == 1 && buffer[offset] == '\n') { } else if (this.parserState == ParserState.INIT && len == 1 && buffer[offset] == '\n') {
// single newline - ignore/drop it... return; // single newline - ignore/drop it...
} else if (this.parserState == ParserState.INPUT) { } else if (this.parserState == ParserState.INPUT) {
// this is a label next to the value input field - we currently have no use for it so // this is a label next to the value input field - we currently have no use for it so
// it's dropped... // it's dropped...
return;
} else { } else {
logger.debug("Error parsing options for {}: Unexpected Text {}:{}: (ctx: {} len: {}) '{}' ", logger.debug("Error parsing options for {}: Unexpected Text {}:{}: (ctx: {} len: {}) '{}' ",
this.channelName, line, col, this.parserState, len, new String(buffer, offset, len)); this.channelName, line, col, this.parserState, len, new String(buffer, offset, len));

View File

@ -90,9 +90,9 @@ public class TACmiSchemaHandler extends BaseThingHandler {
// this is the units lookup cache. // this is the units lookup cache.
protected final Map<String, UnitAndType> unitsCache = new ConcurrentHashMap<>(); protected final Map<String, UnitAndType> unitsCache = new ConcurrentHashMap<>();
// marks an entry with known un-resolveable unit // marks an entry with known un-resolveable unit
protected final UnitAndType NULL_MARKER = new UnitAndType(Units.ONE, ""); protected static final UnitAndType NULL_MARKER = new UnitAndType(Units.ONE, "");
// marks an entry with special handling - i.e. 'Imp' // marks an entry with special handling - i.e. 'Imp'
protected final UnitAndType SPECIAL_MARKER = new UnitAndType(Units.ONE, "s"); protected static final UnitAndType SPECIAL_MARKER = new UnitAndType(Units.ONE, "s");
public TACmiSchemaHandler(final Thing thing, final HttpClient httpClient, public TACmiSchemaHandler(final Thing thing, final HttpClient httpClient,
final TACmiChannelTypeProvider channelTypeProvider) { final TACmiChannelTypeProvider channelTypeProvider) {

View File

@ -165,6 +165,7 @@ public class HomieImplementationTest extends MqttOSGiTest {
"Connection " + homieConnection.getClientId() + " not retrieving all topics "); "Connection " + homieConnection.getClientId() + " not retrieving all topics ");
} }
@Disabled("https://github.com/openhab/openhab-addons/issues/12667")
@Test @Test
public void retrieveOneAttribute() throws Exception { public void retrieveOneAttribute() throws Exception {
WaitForTopicValue watcher = new WaitForTopicValue(homieConnection, DEVICE_TOPIC + "/$homie"); WaitForTopicValue watcher = new WaitForTopicValue(homieConnection, DEVICE_TOPIC + "/$homie");

View File

@ -107,6 +107,7 @@ public class WemoMakerHandlerOSGiTest extends GenericWemoOSGiTest {
} }
@Test @Test
@Disabled("https://github.com/openhab/openhab-addons/issues/12474")
public void assertThatThingHandlesREFRESHCommand() public void assertThatThingHandlesREFRESHCommand()
throws MalformedURLException, URISyntaxException, ValidationException, IOException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = RefreshType.REFRESH; Command command = RefreshType.REFRESH;