mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
Compare commits
5 Commits
942a58ad26
...
74184aa889
Author | SHA1 | Date | |
---|---|---|---|
|
74184aa889 | ||
|
98ff656400 | ||
|
adacdebb9f | ||
|
3327261c71 | ||
|
bb2a06f398 |
@ -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,56 +130,42 @@ 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
|
||||||
|
|
||||||
After installing this add-on, you will find configuration options in the openHAB portal under _Settings -> Add-on Settings -> JRuby Scripting_.
|
After installing this add-on, you will find configuration options in the openHAB portal under _Settings -> Add-on Settings -> JRuby Scripting_.
|
||||||
Alternatively, JRuby configuration parameters may be set by creating a `jruby.cfg` file in `conf/services/`.
|
Alternatively, JRuby configuration parameters may be set by creating a `jruby.cfg` file in `conf/services/`.
|
||||||
|
|
||||||
|
> **_NOTE:_**
|
||||||
|
> In openHAB 3.4.x, the `gems` and `require` settings must be manually configured to the value given in the table below.
|
||||||
|
Starting from openHAB 4.0, the correct defaults were added, so manual configurations are no longer necessary.
|
||||||
|
|
||||||
By default this add-on includes the [openhab-scripting](https://github.com/openhab/openhab-jruby) Ruby gem and automatically `require`s it.
|
By default this add-on includes the [openhab-scripting](https://github.com/openhab/openhab-jruby) Ruby gem and automatically `require`s it.
|
||||||
This allows the use of [items](https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL.html#items-class_method), [rules](https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL.html#rules-class_method), [shared_cache](https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL.html#shared_cache-class_method) and other objects in your scripts.
|
This allows the use of [items](https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL.html#items-class_method), [rules](https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL.html#rules-class_method), [shared_cache](https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL.html#shared_cache-class_method) and other objects in your scripts.
|
||||||
This functionality can be disabled for users who prefer to manage their own gems and `require`s via the add-on configuration options.
|
This functionality can be disabled for users who prefer to manage their own gems and `require`s via the add-on configuration options.
|
||||||
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` |
|
||||||
| `require` | List of scripts to be required automatically. <br/><br/>Default: `openhab/dsl` |
|
| `require` | List of scripts to be required automatically. <br/><br/>Default: `openhab/dsl` |
|
||||||
| `rubylib` | Search path for user libraries. <br/><br/>Default: `$OPENHAB_CONF/automation/ruby/lib` |
|
| `rubylib` | Search path for user libraries. <br/><br/>Default: `$OPENHAB_CONF/automation/ruby/lib` |
|
||||||
| `dependency_tracking` | Enable dependency tracking. <br/><br/>Default: `true` |
|
| `dependency_tracking` | Enable dependency tracking. <br/><br/>Default: `true` |
|
||||||
| `local_context` | See notes below. <br/><br/>Default: `singlethread` |
|
| `local_context` | See notes below. <br/><br/>Default: `singlethread` |
|
||||||
| `local_variables` | See notes below. <br/><br/>Default: `transient` |
|
| `local_variables` | See notes below. <br/><br/>Default: `transient` |
|
||||||
|
|
||||||
When using file-based configuration, these parameters must be prefixed with `org.openhab.automation.jrubyscripting:`, for example:
|
When using file-based configuration, these parameters must be prefixed with `org.openhab.automation.jrubyscripting:`, for example:
|
||||||
|
|
||||||
@ -766,8 +794,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 +805,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 +844,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 +1094,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)
|
||||||
|
@ -51,7 +51,6 @@ public class DigiplexBindingConstants {
|
|||||||
public static final String BRIDGE_MESSAGES_SENT = "statistics#messages_sent";
|
public static final String BRIDGE_MESSAGES_SENT = "statistics#messages_sent";
|
||||||
public static final String BRIDGE_RESPONSES_RECEIVED = "statistics#responses_received";
|
public static final String BRIDGE_RESPONSES_RECEIVED = "statistics#responses_received";
|
||||||
public static final String BRIDGE_EVENTS_RECEIVED = "statistics#events_received";
|
public static final String BRIDGE_EVENTS_RECEIVED = "statistics#events_received";
|
||||||
|
|
||||||
public static final String BRIDGE_TLM_TROUBLE = "troubles#tlm_trouble";
|
public static final String BRIDGE_TLM_TROUBLE = "troubles#tlm_trouble";
|
||||||
public static final String BRIDGE_AC_FAILURE = "troubles#ac_failure";
|
public static final String BRIDGE_AC_FAILURE = "troubles#ac_failure";
|
||||||
public static final String BRIDGE_BATTERY_FAILURE = "troubles#battery_failure";
|
public static final String BRIDGE_BATTERY_FAILURE = "troubles#battery_failure";
|
||||||
|
@ -52,6 +52,9 @@ public interface DigiplexMessageHandler {
|
|||||||
default void handleUnknownResponse(UnknownResponse response) {
|
default void handleUnknownResponse(UnknownResponse response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void handleErroneousResponse(ErroneousResponse response) {
|
||||||
|
}
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
default void handleZoneEvent(ZoneEvent event) {
|
default void handleZoneEvent(ZoneEvent event) {
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
package org.openhab.binding.digiplex.internal.communication;
|
package org.openhab.binding.digiplex.internal.communication;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.AreaEvent;
|
import org.openhab.binding.digiplex.internal.communication.events.AreaEvent;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.AreaEventType;
|
import org.openhab.binding.digiplex.internal.communication.events.AreaEventType;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.GenericEvent;
|
import org.openhab.binding.digiplex.internal.communication.events.GenericEvent;
|
||||||
@ -29,21 +30,19 @@ import org.openhab.binding.digiplex.internal.communication.events.ZoneStatusEven
|
|||||||
* Resolves serial messages to appropriate classes
|
* Resolves serial messages to appropriate classes
|
||||||
*
|
*
|
||||||
* @author Robert Michalak - Initial contribution
|
* @author Robert Michalak - Initial contribution
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class DigiplexResponseResolver {
|
public class DigiplexResponseResolver {
|
||||||
|
|
||||||
private static final String OK = "&ok";
|
private static final String OK = "&ok";
|
||||||
// TODO: handle failures
|
|
||||||
private static final String FAIL = "&fail";
|
private static final String FAIL = "&fail";
|
||||||
|
|
||||||
public static DigiplexResponse resolveResponse(String message) {
|
public static DigiplexResponse resolveResponse(String message) {
|
||||||
if (message.length() < 4) { // sanity check: try to filter out malformed responses
|
if (message.length() < 4) { // sanity check: try to filter out malformed responses
|
||||||
return new UnknownResponse(message);
|
return new ErroneousResponse(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
int zoneNo, areaNo;
|
Integer zoneNo, areaNo;
|
||||||
String commandType = message.substring(0, 2);
|
String commandType = message.substring(0, 2);
|
||||||
switch (commandType) {
|
switch (commandType) {
|
||||||
case "CO": // communication status
|
case "CO": // communication status
|
||||||
@ -53,24 +52,36 @@ public class DigiplexResponseResolver {
|
|||||||
return CommunicationStatus.OK;
|
return CommunicationStatus.OK;
|
||||||
}
|
}
|
||||||
case "ZL": // zone label
|
case "ZL": // zone label
|
||||||
zoneNo = Integer.valueOf(message.substring(2, 5));
|
zoneNo = getZoneOrArea(message);
|
||||||
|
if (zoneNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return ZoneLabelResponse.failure(zoneNo);
|
return ZoneLabelResponse.failure(zoneNo);
|
||||||
} else {
|
} else {
|
||||||
return ZoneLabelResponse.success(zoneNo, message.substring(5).trim());
|
return ZoneLabelResponse.success(zoneNo, message.substring(5).trim());
|
||||||
}
|
}
|
||||||
case "AL": // area label
|
case "AL": // area label
|
||||||
areaNo = Integer.valueOf(message.substring(2, 5));
|
areaNo = getZoneOrArea(message);
|
||||||
|
if (areaNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return AreaLabelResponse.failure(areaNo);
|
return AreaLabelResponse.failure(areaNo);
|
||||||
} else {
|
} else {
|
||||||
return AreaLabelResponse.success(areaNo, message.substring(5).trim());
|
return AreaLabelResponse.success(areaNo, message.substring(5).trim());
|
||||||
}
|
}
|
||||||
case "RZ": // zone status
|
case "RZ": // zone status
|
||||||
zoneNo = Integer.valueOf(message.substring(2, 5));
|
zoneNo = getZoneOrArea(message);
|
||||||
|
if (zoneNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return ZoneStatusResponse.failure(zoneNo);
|
return ZoneStatusResponse.failure(zoneNo);
|
||||||
} else {
|
} else {
|
||||||
|
if (message.length() < 10) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
return ZoneStatusResponse.success(zoneNo, // zone number
|
return ZoneStatusResponse.success(zoneNo, // zone number
|
||||||
ZoneStatus.fromMessage(message.charAt(5)), // status
|
ZoneStatus.fromMessage(message.charAt(5)), // status
|
||||||
toBoolean(message.charAt(6)), // alarm
|
toBoolean(message.charAt(6)), // alarm
|
||||||
@ -79,10 +90,16 @@ public class DigiplexResponseResolver {
|
|||||||
toBoolean(message.charAt(9))); // battery low
|
toBoolean(message.charAt(9))); // battery low
|
||||||
}
|
}
|
||||||
case "RA": // area status
|
case "RA": // area status
|
||||||
areaNo = Integer.valueOf(message.substring(2, 5));
|
areaNo = getZoneOrArea(message);
|
||||||
|
if (areaNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return AreaStatusResponse.failure(areaNo);
|
return AreaStatusResponse.failure(areaNo);
|
||||||
} else {
|
} else {
|
||||||
|
if (message.length() < 12) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
return AreaStatusResponse.success(areaNo, // zone number
|
return AreaStatusResponse.success(areaNo, // zone number
|
||||||
AreaStatus.fromMessage(message.charAt(5)), // status
|
AreaStatus.fromMessage(message.charAt(5)), // status
|
||||||
toBoolean(message.charAt(6)), // zone in memory
|
toBoolean(message.charAt(6)), // zone in memory
|
||||||
@ -95,7 +112,10 @@ public class DigiplexResponseResolver {
|
|||||||
case "AA": // area arm
|
case "AA": // area arm
|
||||||
case "AQ": // area quick arm
|
case "AQ": // area quick arm
|
||||||
case "AD": // area disarm
|
case "AD": // area disarm
|
||||||
areaNo = Integer.valueOf(message.substring(2, 5));
|
areaNo = getZoneOrArea(message);
|
||||||
|
if (areaNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return AreaArmDisarmResponse.failure(areaNo, ArmDisarmType.fromMessage(commandType));
|
return AreaArmDisarmResponse.failure(areaNo, ArmDisarmType.fromMessage(commandType));
|
||||||
} else {
|
} else {
|
||||||
@ -105,21 +125,41 @@ public class DigiplexResponseResolver {
|
|||||||
case "PG": // PGM events
|
case "PG": // PGM events
|
||||||
default:
|
default:
|
||||||
if (message.startsWith("G")) {
|
if (message.startsWith("G")) {
|
||||||
return resolveSystemEvent(message);
|
if (message.length() >= 12) {
|
||||||
|
return resolveSystemEvent(message);
|
||||||
|
} else {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return new UnknownResponse(message);
|
return new UnknownResponse(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @Nullable Integer getZoneOrArea(String message) {
|
||||||
|
if (message.length() < 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.valueOf(message.substring(2, 5));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean toBoolean(char value) {
|
private static boolean toBoolean(char value) {
|
||||||
return value != 'O';
|
return value != 'O';
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DigiplexResponse resolveSystemEvent(String message) {
|
private static DigiplexResponse resolveSystemEvent(String message) {
|
||||||
int eventGroup = Integer.parseInt(message.substring(1, 4));
|
int eventGroup, eventNumber, areaNumber;
|
||||||
int eventNumber = Integer.parseInt(message.substring(5, 8));
|
try {
|
||||||
int areaNumber = Integer.parseInt(message.substring(9, 12));
|
eventGroup = Integer.parseInt(message.substring(1, 4));
|
||||||
|
eventNumber = Integer.parseInt(message.substring(5, 8));
|
||||||
|
areaNumber = Integer.parseInt(message.substring(9, 12));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
switch (eventGroup) {
|
switch (eventGroup) {
|
||||||
case 0:
|
case 0:
|
||||||
return new ZoneStatusEvent(eventNumber, ZoneStatus.CLOSED, areaNumber);
|
return new ZoneStatusEvent(eventNumber, ZoneStatus.CLOSED, areaNumber);
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.digiplex.internal.communication;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erroneous message from PRT3.
|
||||||
|
*
|
||||||
|
* Message that is invalid, which happens sometimes due to communication errors.
|
||||||
|
*
|
||||||
|
* @author Robert Michalak - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ErroneousResponse implements DigiplexResponse {
|
||||||
|
|
||||||
|
public final String message;
|
||||||
|
|
||||||
|
public ErroneousResponse(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(DigiplexMessageHandler visitor) {
|
||||||
|
visitor.handleErroneousResponse(this);
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,9 @@ package org.openhab.binding.digiplex.internal.communication;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unknown message from PRT3
|
* Unknown message from PRT3.
|
||||||
|
*
|
||||||
|
* Message that is otherwise valid, but not handled in this binding.
|
||||||
*
|
*
|
||||||
* @author Robert Michalak - Initial contribution
|
* @author Robert Michalak - Initial contribution
|
||||||
*
|
*
|
||||||
|
@ -38,6 +38,7 @@ import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandle
|
|||||||
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
|
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
|
||||||
import org.openhab.binding.digiplex.internal.communication.DigiplexResponse;
|
import org.openhab.binding.digiplex.internal.communication.DigiplexResponse;
|
||||||
import org.openhab.binding.digiplex.internal.communication.DigiplexResponseResolver;
|
import org.openhab.binding.digiplex.internal.communication.DigiplexResponseResolver;
|
||||||
|
import org.openhab.binding.digiplex.internal.communication.ErroneousResponse;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.AbstractEvent;
|
import org.openhab.binding.digiplex.internal.communication.events.AbstractEvent;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.TroubleEvent;
|
import org.openhab.binding.digiplex.internal.communication.events.TroubleEvent;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.TroubleStatus;
|
import org.openhab.binding.digiplex.internal.communication.events.TroubleStatus;
|
||||||
@ -295,6 +296,12 @@ public class DigiplexBridgeHandler extends BaseBridgeHandler implements SerialPo
|
|||||||
updateState(channel, state);
|
updateState(channel, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleErroneousResponse(ErroneousResponse response) {
|
||||||
|
logger.debug("Erroneous response: {}", response.message);
|
||||||
|
handleCommunicationError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DigiplexReceiverThread extends Thread {
|
private class DigiplexReceiverThread extends Thread {
|
||||||
|
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.digiplex.internal.communication;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.openhab.binding.digiplex.internal.communication.events.GenericEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DigiplexResponseResolver}
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DigiplexResponseResolverTest {
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsErroneousResponseWhenMessageIsMalformed")
|
||||||
|
void resolveResponseReturnsErroneousResponseWhenMessageIsMalformed(String message) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(ErroneousResponse.class)));
|
||||||
|
if (actual instanceof ErroneousResponse erroneousResponse) {
|
||||||
|
assertThat(erroneousResponse.message, is(equalTo(message)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsErroneousResponseWhenMessageIsMalformed() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("CO&"), Arguments.of("ZL&fail"), Arguments.of("ZL12"), Arguments.of("AL&fail"),
|
||||||
|
Arguments.of("AL12"), Arguments.of("RZZZ3COOOO&fail"), Arguments.of("RZ123C"),
|
||||||
|
Arguments.of("RZ123COOO"), Arguments.of("RA&fail"), Arguments.of("RA123DOOXOO"),
|
||||||
|
Arguments.of("AA&fail"), Arguments.of("GGGGGGGGGGGG"), Arguments.of("G1234567890"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveResponseReturnsCommunicationStatusSuccessWhenWellformed() {
|
||||||
|
String message = "CO&ok";
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(CommunicationStatus.class)));
|
||||||
|
if (actual instanceof CommunicationStatus communicationStatus) {
|
||||||
|
assertThat(communicationStatus.success, is(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveResponseReturnsCommunicationStatusFailureWhenMessageContainsFail() {
|
||||||
|
String message = "CO&fail";
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(CommunicationStatus.class)));
|
||||||
|
if (actual instanceof CommunicationStatus communicationStatus) {
|
||||||
|
assertThat(communicationStatus.success, is(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsZoneLabelResponse")
|
||||||
|
void resolveResponseReturnsZoneLabelResponse(String message, boolean expectedSuccess, int expectedZoneNo,
|
||||||
|
String expectedName) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(ZoneLabelResponse.class)));
|
||||||
|
if (actual instanceof ZoneLabelResponse zoneLabelResponse) {
|
||||||
|
assertThat(zoneLabelResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(zoneLabelResponse.zoneNo, is(expectedZoneNo));
|
||||||
|
assertThat(zoneLabelResponse.zoneName, is(expectedName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsZoneLabelResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("ZL123", true, 123, ""), Arguments.of("ZL123test ", true, 123, "test"),
|
||||||
|
Arguments.of("ZL123&fail", false, 123, null), Arguments.of("ZL123test&fail", false, 123, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsAreaLabelResponse")
|
||||||
|
void resolveResponseReturnsAreaLabelResponse(String message, boolean expectedSuccess, int expectedAreaNo,
|
||||||
|
String expectedName) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(AreaLabelResponse.class)));
|
||||||
|
if (actual instanceof AreaLabelResponse areaLabelResponse) {
|
||||||
|
assertThat(areaLabelResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(areaLabelResponse.areaNo, is(expectedAreaNo));
|
||||||
|
assertThat(areaLabelResponse.areaName, is(expectedName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsAreaLabelResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("AL123", true, 123, ""), Arguments.of("AL123test ", true, 123, "test"),
|
||||||
|
Arguments.of("AL123&fail", false, 123, null), Arguments.of("AL123test&fail", false, 123, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsZoneStatusResponse")
|
||||||
|
void resolveResponseReturnsZoneStatusResponse(String message, boolean expectedSuccess, int expectedZoneNo,
|
||||||
|
ZoneStatus expectedZoneStatus, boolean expectedAlarm, boolean expectedFireAlarm,
|
||||||
|
boolean expectedSupervisionLost, boolean expectedLowBattery) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(ZoneStatusResponse.class)));
|
||||||
|
if (actual instanceof ZoneStatusResponse zoneStatusResponse) {
|
||||||
|
assertThat(zoneStatusResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(zoneStatusResponse.zoneNo, is(expectedZoneNo));
|
||||||
|
assertThat(zoneStatusResponse.status, is(expectedZoneStatus));
|
||||||
|
assertThat(zoneStatusResponse.alarm, is(expectedAlarm));
|
||||||
|
assertThat(zoneStatusResponse.fireAlarm, is(expectedFireAlarm));
|
||||||
|
assertThat(zoneStatusResponse.supervisionLost, is(expectedSupervisionLost));
|
||||||
|
assertThat(zoneStatusResponse.lowBattery, is(expectedLowBattery));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsZoneStatusResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("RZ123COOOO", true, 123, ZoneStatus.CLOSED, false, false, false, false),
|
||||||
|
Arguments.of("RZ123OOOOO", true, 123, ZoneStatus.OPEN, false, false, false, false),
|
||||||
|
Arguments.of("RZ123TOOOO", true, 123, ZoneStatus.TAMPERED, false, false, false, false),
|
||||||
|
Arguments.of("RZ123FOOOO", true, 123, ZoneStatus.FIRE_LOOP_TROUBLE, false, false, false, false),
|
||||||
|
Arguments.of("RZ123uOOOO", true, 123, ZoneStatus.UNKNOWN, false, false, false, false),
|
||||||
|
Arguments.of("RZ123cOOOO", true, 123, ZoneStatus.UNKNOWN, false, false, false, false),
|
||||||
|
Arguments.of("RZ123cXOOO", true, 123, ZoneStatus.UNKNOWN, true, false, false, false),
|
||||||
|
Arguments.of("RZ123cOXOO", true, 123, ZoneStatus.UNKNOWN, false, true, false, false),
|
||||||
|
Arguments.of("RZ123cOOXO", true, 123, ZoneStatus.UNKNOWN, false, false, true, false),
|
||||||
|
Arguments.of("RZ123cOOOX", true, 123, ZoneStatus.UNKNOWN, false, false, false, true),
|
||||||
|
Arguments.of("RZ123&fail", false, 123, null, false, false, false, false),
|
||||||
|
Arguments.of("RZ123COOOO&fail", false, 123, null, false, false, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsAreaStatusResponse")
|
||||||
|
void resolveResponseReturnsAreaStatusResponse(String message, boolean expectedSuccess, int expectedAreaNo,
|
||||||
|
AreaStatus expectedAreaStatus, boolean expectedZoneInMemory, boolean expectedTrouble, boolean expectedReady,
|
||||||
|
boolean expectedInProgramming, boolean expectedAlarm, boolean expectedStrobe) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(AreaStatusResponse.class)));
|
||||||
|
if (actual instanceof AreaStatusResponse areaStatusResponse) {
|
||||||
|
assertThat(areaStatusResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(areaStatusResponse.areaNo, is(expectedAreaNo));
|
||||||
|
assertThat(areaStatusResponse.status, is(expectedAreaStatus));
|
||||||
|
assertThat(areaStatusResponse.zoneInMemory, is(expectedZoneInMemory));
|
||||||
|
assertThat(areaStatusResponse.trouble, is(expectedTrouble));
|
||||||
|
assertThat(areaStatusResponse.ready, is(expectedReady));
|
||||||
|
assertThat(areaStatusResponse.inProgramming, is(expectedInProgramming));
|
||||||
|
assertThat(areaStatusResponse.alarm, is(expectedAlarm));
|
||||||
|
assertThat(areaStatusResponse.strobe, is(expectedStrobe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsAreaStatusResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("RA123DOOXOOO", true, 123, AreaStatus.DISARMED, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123AOOXOOO", true, 123, AreaStatus.ARMED, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123FOOXOOO", true, 123, AreaStatus.ARMED_FORCE, false, false, false, false, false,
|
||||||
|
false),
|
||||||
|
Arguments.of("RA123SOOXOOO", true, 123, AreaStatus.ARMED_STAY, false, false, false, false, false,
|
||||||
|
false),
|
||||||
|
Arguments.of("RA123IOOXOOO", true, 123, AreaStatus.ARMED_INSTANT, false, false, false, false, false,
|
||||||
|
false),
|
||||||
|
Arguments.of("RA123uOOXOOO", true, 123, AreaStatus.UNKNOWN, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123dOOXOOO", true, 123, AreaStatus.UNKNOWN, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123dXOXOOO", true, 123, AreaStatus.UNKNOWN, true, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123dOXxOOO", true, 123, AreaStatus.UNKNOWN, false, true, false, false, false, false),
|
||||||
|
Arguments.of("RA123dOOOOOO", true, 123, AreaStatus.UNKNOWN, false, false, true, false, false, false),
|
||||||
|
Arguments.of("RA123dOOXXOO", true, 123, AreaStatus.UNKNOWN, false, false, false, true, false, false),
|
||||||
|
Arguments.of("RA123dOOXOXO", true, 123, AreaStatus.UNKNOWN, false, false, false, false, true, false),
|
||||||
|
Arguments.of("RA123dOOXOOX", true, 123, AreaStatus.UNKNOWN, false, false, false, false, false, true),
|
||||||
|
Arguments.of("RA123&fail", false, 123, null, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123DOOXOOO&fail", false, 123, null, false, false, false, false, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsAreaArmDisarmResponse")
|
||||||
|
void resolveResponseReturnsAreaArmDisarmResponse(String message, boolean expectedSuccess, int expectedAreaNo,
|
||||||
|
ArmDisarmType expectedType) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(AreaArmDisarmResponse.class)));
|
||||||
|
if (actual instanceof AreaArmDisarmResponse armDisarmResponse) {
|
||||||
|
assertThat(armDisarmResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(armDisarmResponse.areaNo, is(expectedAreaNo));
|
||||||
|
assertThat(armDisarmResponse.type, is(expectedType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsAreaArmDisarmResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("AA123", true, 123, ArmDisarmType.ARM),
|
||||||
|
Arguments.of("AQ123", true, 123, ArmDisarmType.QUICK_ARM),
|
||||||
|
Arguments.of("AD123", true, 123, ArmDisarmType.DISARM),
|
||||||
|
Arguments.of("AA123&fail", false, 123, ArmDisarmType.ARM),
|
||||||
|
Arguments.of("AQ123&fail", false, 123, ArmDisarmType.QUICK_ARM),
|
||||||
|
Arguments.of("AD123&fail", false, 123, ArmDisarmType.DISARM));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveResponseReturnsGenericEventWhenWellformed() {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse("G123 456 789");
|
||||||
|
assertThat(actual, is(instanceOf(GenericEvent.class)));
|
||||||
|
if (actual instanceof GenericEvent genericEvent) {
|
||||||
|
assertThat(genericEvent.getEventGroup(), is(123));
|
||||||
|
assertThat(genericEvent.getEventNumber(), is(456));
|
||||||
|
assertThat(genericEvent.getAreaNo(), is(789));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user