Skip to content

Commit

Permalink
Allow passing an array of channels to item builder
Browse files Browse the repository at this point in the history
Also add fluent aliases for adding multiple channels, groups and tags,
and clarify documentation.

Also fixes calling `channel` on a `GroupItemBuilder`, because it was
being shadowed by `Terse#channel`, because `DSL` was getting included
unecessarily (we only need `EntityLookup`, which is already explicitly
included).

Fixes #374

Signed-off-by: Cody Cutrer <cody@cutrer.us>
  • Loading branch information
ccutrer committed Jan 3, 2025
1 parent 6f5bb23 commit 1da6fbf
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 32 deletions.
142 changes: 112 additions & 30 deletions lib/openhab/dsl/items/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ def group_item(*args, **kwargs, &block)
result
end

include DSL

private

def item(*args, **kwargs, &block)
Expand Down Expand Up @@ -159,10 +157,11 @@ def add(builder)

# make sure to add the item to the registry before linking it
channel_uids = builder.channels.to_set do |(channel, config)|
# fill in partial channel names from group's thing id
channel = channel.to_s
# fill in partial channel names from item's or group's thing id
if !channel.include?(":") &&
(group = builder.groups.find { |g| g.is_a?(GroupItemBuilder) && g.thing })
thing = group.thing
(thing = builder.thing ||
thing = builder.groups.find { |g| g.is_a?(GroupItemBuilder) && g.thing }&.thing)
channel = "#{thing}:#{channel}"
end

Expand Down Expand Up @@ -243,18 +242,12 @@ class ItemBuilder
# The icon to be associated with the item
# @return [Symbol, String, nil]
attr_accessor :icon
# Groups to which this item should be added
# @return [Array<String, GroupItem>]
attr_reader :groups
# Tags to apply to this item
# @return [Array<String, Semantics::Tag>]
attr_reader :tags
# Autoupdate setting
# @return [true, false, nil]
attr_accessor :autoupdate
# {Core::Things::ChannelUID Channel} to link the item to
# @return [String, Core::Things::ChannelUID, nil]
attr_accessor :channels
# @return [String, Core::Things::Thing, Core::Things::ThingUID, nil]
# {Core::Things::ThingUID Thing} from which to resolve relative channel ids
attr_accessor :thing
# @return [Core::Items::Metadata::NamespaceHash]
attr_reader :metadata
# Initial state
Expand All @@ -264,6 +257,19 @@ class ItemBuilder
# @return [Core::Types::State]
attr_reader :state

attr_writer :channels, :groups, :tags

# @!attribute [rw] channels
# @return [Array<String, Symbol, Core::Things::ChannelUID, Core::Things::Channel, Array>]
# {Core::Things::ChannelUID Channel} to link the item to

# @!attribute [rw] groups
# @return [Array<String, GroupItem>] Groups to which this item should be added

# @!attribute [rw] tags
# @return [Array<String>] Tags to apply to this item

# This comment needs to exist otherwise YARD thinks the above attribute should apply to the metaclass
class << self
# @!visibility private
def item_factory
Expand Down Expand Up @@ -352,6 +358,7 @@ def initialize(type,
autoupdate: nil,
thing: nil,
channel: nil,
channels: nil,
expire: nil,
alexa: nil,
ga: nil, # rubocop:disable Naming/MethodParameterName
Expand Down Expand Up @@ -408,12 +415,13 @@ def initialize(type,
self.state = state

self.group(*group)
self.group(*groups)
self.groups(*groups)

self.tag(*tag)
self.tag(*tags)
self.tags(*tags)

self.channel(*channel) if channel
self.channel(*channel)
self.channels(*channels)
end

#
Expand All @@ -428,20 +436,45 @@ def to_s
#
# Tag item
#
# @param tags [String, Symbol, Semantics::Tag]
# @return [void]
# @return [Array<String>]
#
# @overload tag(tag)
# @param tag [String, Symbol, Semantics::Tag]
# @return [Array<String>]
#
# @overload tags
# @return [Array<String>]
#
# @overload tags(*tags)
# @param tags [String, Symbol, Semantics::Tag]
# @return [Array<String>]
#
def tag(*tags)
@tags += self.class.normalize_tags(*tags)
return @tags if tags.empty?

@tags.concat(self.class.normalize_tags(*tags))
end
alias_method :tags, :tag

#
# Add this item to a group
#
# @param groups [String, GroupItemBuilder, GroupItem]
# @return [void]
# @return [Array<String, GroupItemBulder, GroupItem>]
#
# @overload group(group)
# @param group [String, GroupItemBuilder, GroupItem]
# @return [Array<String, GroupItemBulder, GroupItem>]
#
# @overload groups
# @return [Array<String, GroupItemBulder, GroupItem>]
#
# @overload groups(*groups)
# @param groups [String, GroupItemBuilder, GroupItem]
# @return [Array<String, GroupItemBulder, GroupItem>]
#
def group(*groups)
return @groups if groups.empty?

unless groups.all? do |group|
group.is_a?(String) || group.is_a?(Core::Items::GroupItem) || group.is_a?(GroupItemBuilder)
end
Expand All @@ -450,6 +483,7 @@ def group(*groups)

@groups.concat(groups)
end
alias_method :groups, :group

#
# @!method alexa(value, config = nil)
Expand Down Expand Up @@ -494,10 +528,23 @@ def group(*groups)
#
# Add a channel link to this item.
#
# @param channel [String, Symbol, Core::Things::ChannelUID, Core::Things::Channel]
# Channel to link the item to. When thing is set, this can be a relative channel name.
# @param config [Hash] Additional configuration, such as profile
# @return [void]
# @return [Array<String, Symbol, Core::Things::ChannelUID, Core::Things::Channel, Array>]
#
# @overload channel(channel, config = {})
# @param channel [String, Symbol, Core::Things::ChannelUID, Core::Things::Channel]
# Channel to link the item to. When {thing} is set, this can be a relative channel name.
# @param config [Hash] Additional configuration, such as profile
# @return [Array<String, Symbol, Core::Things::ChannelUID, Core::Things::Channel, Array>]
#
# @overload channels
# @return [Array<String, Symbol, Core::Things::ChannelUID, Core::Things::Channel, Array>]
#
# @overload channels(*channels)
# @param channels [String, Symbol, Core::Things::ChannelUID, Core::Things::Channel, Array]
# Channels to link the item to. When {thing} is set, these can be relative channel names.
# Each array element can also be a two element array with the first element being the
# channel, and the second element being a config hash.
# @return [Array<String, Symbol, Core::Things::ChannelUID, Core::Things::Channel, Array>]
#
# @example
# items.build do
Expand All @@ -511,11 +558,46 @@ def group(*groups)
# switch_item Bedroom_Light, thing: "mqtt:topic:bedroom-light", channel: :power
# end
#
def channel(channel, config = {})
channel = channel.to_s
channel = "#{@thing}:#{channel}" if @thing && !channel.include?(":")
@channels << [channel, config]
# @example Multiple channels
# items.build do
# dimmer_item DemoDimmer, channels: ["hue:0210:bridge:1:color", "knx:device:bridge:generic:controlDimmer"]
# end
#
# @example Multiple channels in a block
# items.build do
# dimmer_item DemoDimmer do
# channel "hue:0210:bridge:1:color"
# channel "knx:device:bridge:generic:controlDimmer"
# end
# end
#
# @example Multiple channels with config
# items.build do
# dimmer_item DemoDimmer, channels: [["hue:0210:bridge:1:color", profile: "system:follow"],
# "knx:device:bridge:generic:controlDimmer"]
# end
#
def channel(*channels)
return @channels if channels.empty?

channels = [channels] if channels.length == 2 && channels[1].is_a?(Hash)

channels.each do |channel|
orig_channel = channel
channel = channel.first if channel.is_a?(Array)
next if channel.is_a?(String) ||
channel.is_a?(Symbol) ||
channel.is_a?(Core::Things::ChannelUID) ||
channel.is_a?(Core::Things::Channel)

raise ArgumentError, "channel #{orig_channel.inspect} must be a `String`, `Symbol`, `ChannelUID`, or " \
"`Channel`, or a two element array with the first element those types, and the " \
"second element a Hash"
end

@channels.concat(channels)
end
alias_method :channels, :channel

#
# @!method expire(duration, command: nil, state: nil, ignore_state_updates: nil, ignore_commands: nil)
Expand Down
24 changes: 22 additions & 2 deletions spec/openhab/dsl/items/builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ def build_and_update(org_config, new_config, item_to_keep: :new_item, &block)
install_addon "binding-astro", ready_markers: "openhab.xmlThingTypes"
things.build do
thing "astro:sun:home", "Astro Sun Data", config: { "geolocation" => "0,0" }
thing "astro:moon:home", "Astro Moon Data", config: { "geolocation" => "0,0" }
end
end

Expand All @@ -689,13 +690,32 @@ def build_and_update(org_config, new_config, item_to_keep: :new_item, &block)
end
items.build do
date_time_item "DateTime1" do
channel "astro:sun:home:rise#start"
channel "astro:moon:home:rise#start"
channel "astro:sun:home:rise#start"
channel "astro:moon:home:rise#start"
end
end
expect(DateTime1.things).to match_array([things["astro:sun:home"], things["astro:moon:home"]])
end

it "accepts multiple channels in an immediate argument" do
items.build { string_item "StringItem1", channels: ["astro:sun:home:season#name", "astro:moon:home:season#name"] }
expect(StringItem1.things).to match [things["astro:sun:home"], things["astro:moon:home"]]
end

it "accepts multiple channels with config in an immediate argument" do
items.build do
string_item "StringItem1",
channels: [["astro:sun:home:season#name", { config: 1 }], "astro:moon:home:season#name"]
end
expect(StringItem1.things).to match [things["astro:sun:home"], things["astro:moon:home"]]
end

it "rejects invalid channel data" do
expect do
items.build { string_item "StringItem1", channels: 1 }
end.to raise_error(ArgumentError)
end

it "can link to an item channel with a profile" do
items.build do
date_time_item "LastUpdated", channel: ["astro:sun:home:season#name", { profile: "system:timestamp-update" }]
Expand Down

0 comments on commit 1da6fbf

Please sign in to comment.