Skip to content
This repository was archived by the owner on Sep 6, 2023. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ jobs:
rm features/support/env.rb
- id: set-matrix
run: |
JSON=$(bundle exec rake github:matrix[20])
JSON=$(bundle exec rake github:matrix[40])
echo $JSON
echo "::set-output name=matrix::$( echo "$JSON" )"

Expand All @@ -126,7 +126,7 @@ jobs:
bundler-cache: true
- uses: actions/setup-java@v1
with:
java-version: "11"
java-version: "17"
java-package: jre
- name: Restore OpenHAB setup
uses: actions/cache@v2
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ inherit_mode:
- Exclude # we want our Exclude to build on the excludes from the default config

AllCops:
TargetRubyVersion: 2.6
TargetRubyVersion: 3.1
NewCops: enable
Exclude:
- bin/*
Expand Down
101 changes: 101 additions & 0 deletions features/pattern_matching.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
Feature: semantics
Rule languages supports openHAB's semantics model

Background:
Given Clean OpenHAB with latest Ruby Libraries

Scenario Outline: Pattern matching work with base item properties
Given feature 'openhab-binding-astro' installed
And things:
| id | thing_uid | label | config | status |
| home | astro:sun | Astro Sun Data | {"geolocation":"0,0"} | enable |
Given groups:
| name |
| Patio_Lights |
And items:
| type | name | groups | tags | label | state |
| Switch | Patio_Light | Patio_Lights | foo, bar | baz | ON |
| Number | Sun_Elevation | | | | |
And linked:
| item | channel |
| Sun_Elevation | astro:sun:home:position#elevation |
And code in a rules file
"""
astro_thing = things['astro:sun:home']
group = groups['Patio_Lights']
logger.debug(group)
#Patio_Light.deconstruct_keys(nil).each { |key, value| logger.debug("#{key}=#{value}")}
items => [*, { <pattern> } => matched, *]
logger.debug("Matched #{matched.name}")
"""
When I deploy the rules file
Then It should log 'Matched <item_name>' within 5 seconds
Examples: Basic patterns
| pattern | item_name |
| name: "Patio_Light" | Patio_Light |
| name: /Light$/ | Patio_Light |
| id: "baz" | Patio_Light |
| label: "baz" | Patio_Light |
| state: ON | Patio_Light |
| type: SwitchItem | Patio_Light |
| type_name: "Switch" | Patio_Light |
| tags: { bar: true} | Patio_Light |
| tags: { bar: true, foo: true} | Patio_Light |
| tags: { foo: true} | Patio_Light |
| thing: ^astro_thing | Sun_Elevation |
| groups: [^group] | Patio_Light |
# | things: [^astro_thing] | Sun_Elevation | # I don't know why this doesn't currenty work


Scenario Outline: Pattern matching work with item semantics
Given groups:
| name | groups | tags |
| Patio_Light_Bulb | gPatio | Lightbulb |
| gOutdoor | | Outdoor |
| gPatio | gOutdoor | Patio |
| Patio_Light_Bulb | gPatio | Lightbulb |
And items:
| type | name | groups | tags |
| Color | Patio_Light_Color | Patio_Light_Bulb | Control,Light |
And code in a rules file
"""
items => [*, { <pattern> } => matched, *]
logger.debug("Matched #{matched.name}")
"""
When I deploy the rules file
Then It should log 'Matched <item_name>' within 5 seconds
Examples: Semantic patterns
| pattern | item_name |
| type: ColorItem, point_type: Semantics::Control | Patio_Light_Color |
| type: ColorItem, equipment_type: Semantics::Lightbulb | Patio_Light_Color |
| type: ColorItem, property_type: Semantics::Light | Patio_Light_Color |
| type: ColorItem, semantic_type: Semantics::Control | Patio_Light_Color |
| type: ColorItem, location_type: Semantics::Patio | Patio_Light_Color |


# TODO: For some reason pattern matching is failing if that group does not exist
Scenario Outline: Item extension pattern matching
Given groups:
| name | groups | tags |
| gPatio | gOutdoor | Patio |
Given items:
| type | name | state |
| Color | Patio_Light | 0, 100, 100 |
| Image | image | |

And code in a rules file
"""
image.update "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
Patio_Light.deconstruct_keys(nil).each { |key, value| logger.debug("#{key}=#{value}")}
items.to_a => [*, { <pattern> } => matched, *]
logger.debug("Matched #{matched.name}")
"""
When I deploy the rules file
Then It should log 'Matched <item_name>' within 5 seconds
Examples: Semantic patterns
| pattern | item_name |
| mime_type: 'image/png' | image |
| name: "Patio_Light" | Patio_Light |
| id: "Patio_Light" | Patio_Light |
| hex: '#ff0000' | Patio_Light |
| rgb: {red: 255, green: 0, blue: 0} | Patio_Light |
2 changes: 1 addition & 1 deletion features/step_definitions/openhab_rules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def deploy_ruby_file(code:, directory:, filename: '', check_position: :end, chec
log_line = case check_position
when :start then identifying_started_log_line(uid)
when :end then identifying_log_line(uid)
else raise ArgumentError, 'log_line can either be :start or :end'
else raise ArgumentError, 'check_position can either be :start or :end'
end

create_log_markers(code, uid) if check
Expand Down
14 changes: 14 additions & 0 deletions lib/openhab/dsl/items/color_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module Items
class ColorItem < DimmerItem
extend Forwardable
include ComparableItem
include Log

# !@visibility private
def ==(other)
Expand All @@ -25,6 +26,15 @@ def ==(other)
super
end

#
# Adds Color specific keys to GenericItem keys for use in pattern matching
#
def deconstruct_keys(keys)
logger.debug('Deconstructing a color item')
super.deconstruct_keys(keys).merge({ hue: state&.hue, saturation: state&.saturation, brightness: state&.brightness, hex: state&.to_hex, hsb: state&.to_h,
rgb: state&.to_h(:rgb) })
end

#
# Type Coercion
#
Expand All @@ -44,6 +54,10 @@ def coerce(other)
# any method that exists on {Types::HSBType} gets forwarded to +state+
delegate (Types::HSBType.instance_methods - instance_methods) => :state

def to_h
logger.debug('to_h called')
end

# string commands aren't allowed on ColorItems, so try to implicitly
# convert it to an HSBType
# @!visibility private
Expand Down
15 changes: 14 additions & 1 deletion lib/openhab/dsl/items/generic_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def ACCEPTED_DATA_TYPES
# Override to support ItemProxy
#
def ===(other)
other.instance_of?(self)
other.eql?(self) || other.instance_of?(self)
end
end
# rubocop:enable Naming/MethodName
Expand Down Expand Up @@ -205,6 +205,19 @@ def format_type(command)
command.to_s
end

#
# Converts properties of an item to a hash such that an item can be used with pattern matching
#
def deconstruct_keys(_keys)
{ category:, groups:, label:, name:, state:,
tags: tags.to_a.map(&:to_sym).product([true]).to_h,
type: self.class,
location:, location_type:, equipment:, equipment_type:, point_type:, property_type:, semantic_type:,
type_name: type, id:, thing:, things:, meta: }.tap do |decon|
logger.trace "Deconstructed (#{name}): #{decon}"
end
end

private

# convert items to their state before formatting, so that subclasses
Expand Down
17 changes: 12 additions & 5 deletions lib/openhab/dsl/items/image_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ImageItem < GenericItem
def update_from_file(file, mime_type: nil)
file_data = File.binread(file)
mime_type ||= Marcel::MimeType.for(Pathname.new(file)) || Marcel::MimeType.for(file_data)
update_from_bytes(file_data, mime_type: mime_type)
update_from_bytes(file_data, mime_type:)
end

#
Expand All @@ -37,8 +37,8 @@ def update_from_url(uri)
response = Net::HTTP.get_response(URI(uri))
mime_type = response['content-type']
bytes = response.body
mime_type ||= detect_mime_from_bytes(bytes: bytes)
update_from_bytes(bytes, mime_type: mime_type)
mime_type ||= detect_mime_from_bytes(bytes:)
update_from_bytes(bytes, mime_type:)
end

#
Expand All @@ -49,8 +49,8 @@ def update_from_url(uri)
#
#
def update_from_bytes(bytes, mime_type: nil)
mime_type ||= detect_mime_from_bytes(bytes: bytes)
base_64_image = encode_image(mime_type: mime_type, bytes: bytes)
mime_type ||= detect_mime_from_bytes(bytes:)
base_64_image = encode_image(mime_type:, bytes:)
update(base_64_image)
end

Expand All @@ -72,6 +72,13 @@ def bytes
state&.get_bytes
end

#
# Adds image specific keys to GenericItem keys for use in pattern matching
#
def deconstruct_keys(keys)
super.deconstruct_keys(keys).merge({ mime_type: })
end

private

#
Expand Down
4 changes: 4 additions & 0 deletions lib/openhab/dsl/items/semantics.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require 'openhab/log/logger'
require_relative 'semantics/enumerable'

module OpenHAB
Expand All @@ -17,6 +18,7 @@ module Items
#
# See https://github.com/openhab/openhab-core/blob/main/bundles/org.openhab.core.semantics/model/SemanticTags.csv
module Semantics
include Log
# @!visibility private
# import the actual semantics action
SemanticsAction = org.openhab.core.model.script.actions.Semantics
Expand All @@ -29,6 +31,8 @@ module Semantics
org.openhab.core.semantics.model.location.Locations].each do |parent_tag|
parent_tag.stream.for_each do |tag|
const_set(tag.simple_name.to_sym, tag.ruby_class)
# Set === so that semantic classes work in case statements
tag.ruby_class.define_singleton_method(:===) { |other| eql?(other) }
end
end

Expand Down
9 changes: 8 additions & 1 deletion lib/openhab/dsl/things.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ def initialize(thing)
define_action_methods
end

#
# Case equality
#
# @return [Boolean] if the values are of the same thing
#
def ===(other) = other.eql?(self)

#
# Defines boolean thing status methods
# uninitialized?
Expand Down Expand Up @@ -78,7 +85,7 @@ def define_action_methods
actions_for_thing(uid).each do |action|
methods = action.java_class.declared_instance_methods
methods.select { |method| method.annotation_present?(RuleAction.java_class) }
.each { |method| define_action_method(action: action, method: method.name) }
.each { |method| define_action_method(action:, method: method.name) }
end
end

Expand Down
2 changes: 1 addition & 1 deletion rakelib/github.rake
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require 'json'
require 'open3'
require 'pp'

OPENHAB_VERSIONS = ['3.2.0', '3.3.0'].freeze
OPENHAB_VERSIONS = ['3.2.0', '3.3.0', '3.3.0.M7+jruby9.4'].freeze

# Get list
# rubocop: disable Metrics/MethodLength
Expand Down
10 changes: 9 additions & 1 deletion rakelib/openhab.rake
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require 'net/http'
# rubocop: disable Rake/MethodDefinitionInTask Legacy code
namespace :openhab do
@openhab_version = ENV['OPENHAB_VERSION'] || '3.2.0'
@openhab_version, @bundle_version = @openhab_version.split('+')
@port_numbers = {
ssh: { port: ENV['OPENHAB_SSH_PORT'] || 8101, config: 'org.apache.karaf.shell:sshPort' },
lsp: { port: ENV['OPENHAB_LSP_PORT'] || 5007, config: 'org.openhab.lsp:port' }
Expand Down Expand Up @@ -191,7 +192,14 @@ namespace :openhab do
desc 'Install JRuby Bundle'
task bundle: [:download, :services, @deploy_dir] do |task|
state(task.name) do
File.write(@addons_config_file, "\nautomation=jrubyscripting\n", mode: 'a')
case @bundle_version
when 'jruby9.4'
download_url = 'https://github.com/jimtng/openhab-addons/releases/download/jruby-9.4-0.0.1/org.openhab.automation.jrubyscripting-3.3.0-SNAPSHOT.jar'
file = File.join(OPENHAB_DIR, 'addons/org.openhab.automation.jrubyscripting-3.3.0-SNAPSHOT.jar')
IO.copy_stream(open(download_url), file) # rubocop: disable Security/Open
else
File.write(@addons_config_file, "\nautomation=jrubyscripting\n", mode: 'a')
end
end
end

Expand Down