mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
Compare commits
6 Commits
00161f3e98
...
a3a5f06f80
Author | SHA1 | Date | |
---|---|---|---|
|
a3a5f06f80 | ||
|
a1fc3632e6 | ||
|
4e88f48a71 | ||
|
e69c44b85e | ||
|
46d27b6fb5 | ||
|
f6efa87fb2 |
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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";
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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?
|
||||||
|
@ -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));
|
||||||
|
@ -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) {
|
||||||
|
@ -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");
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user