Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

allow referencing a channel type in the DSL #2343

Merged
merged 1 commit into from
Oct 28, 2016
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import org.eclipse.smarthome.core.thing.Bridge
import org.eclipse.smarthome.core.thing.Channel
import org.eclipse.smarthome.core.thing.Thing
import org.eclipse.smarthome.core.thing.ThingRegistry
import org.eclipse.smarthome.core.thing.type.ChannelKind
import org.eclipse.smarthome.core.thing.type.ChannelTypeUID
import org.eclipse.smarthome.model.core.ModelRepository
import org.eclipse.smarthome.test.OSGiTest
import org.junit.After
Expand Down Expand Up @@ -279,4 +281,45 @@ class GenericThingProviderTest extends OSGiTest {
assertThat bridge.things.contains(thing), is(true)
}

@Test
void 'assert that channel definitions can be referenced'() {
def things = thingRegistry.getAll()
assertThat things.size(), is(0)

String model =
'''
Bridge hue:bridge:bridge1 [] {
LCT001 bulb_default []
LCT001 bulb_custom [] {
Channels:
Type color : manual []
}
LCT001 bulb_broken [] {
Channels:
Type broken : manual []
}
}

'''

modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.bytes))
def List<Thing> actualThings = thingRegistry.getAll()

assertThat actualThings.size(), is(4)

Thing thingDefault = actualThings.find { it.getUID().getId().equals("bulb_default") }
assertThat thingDefault.getChannels().size(), is(2)

Thing thingCustom = actualThings.find { it.getUID().getId().equals("bulb_custom") }
assertThat thingCustom.getChannels().size(), is(3)
assertThat thingCustom.getChannel("manual").getChannelTypeUID(), is(equalTo(new ChannelTypeUID("hue", "color")))

Thing thingBroken = actualThings.find { it.getUID().getId().equals("bulb_broken") }
assertThat thingBroken.getChannels().size(), is(3)
assertThat thingBroken.getChannel("manual").getChannelTypeUID(), is(equalTo(new ChannelTypeUID("hue", "broken")))
assertThat thingBroken.getChannel("manual").getKind(), is(ChannelKind.STATE)
assertThat thingBroken.getChannel("manual").getAcceptedItemType(), is(nullValue())

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ ModelThing:
;

ModelChannel:
(channelKind= ('State' | 'Trigger'))? type=ModelItemType ':' id=UID_SEGMENT
(((channelKind= ('State' | 'Trigger'))? type=ModelItemType) | 'Type' channelType=UID_SEGMENT) ':' id=UID_SEGMENT
('['
properties+=ModelProperty (',' properties+=ModelProperty)*
']')?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import org.eclipse.smarthome.core.thing.type.TypeResolver
import java.util.Locale
import org.eclipse.smarthome.core.i18n.LocaleProvider
import org.eclipse.smarthome.core.thing.type.ChannelKind
import org.eclipse.smarthome.core.thing.type.ChannelTypeUID

/**
* {@link ThingProvider} implementation which computes *.things files.
Expand Down Expand Up @@ -275,11 +276,30 @@ class GenericThingProvider extends AbstractProvider<Thing> implements ThingProvi
val List<Channel> channels = newArrayList
modelChannels.forEach [
if (addedChannelIds.add(id)) {
val kind = if (it.channelKind == null) "State" else it.channelKind
val parsedKind = ChannelKind.parse(kind)
val channel = ChannelBuilder.create(new ChannelUID(thingTypeUID, thingUID, id), type)
var ChannelKind parsedKind = ChannelKind.STATE

var ChannelTypeUID channelTypeUID
var String itemType
if (it.channelType != null) {
channelTypeUID = new ChannelTypeUID(thingUID.bindingId, it.channelType)
val resolvedChannelType = TypeResolver.resolve(channelTypeUID)
if (resolvedChannelType != null) {
itemType = resolvedChannelType.itemType
parsedKind = resolvedChannelType.kind
} else {
logger.error("Channel type {} could not be resolved.", channelTypeUID.asString)
}
} else {
itemType = it.type

val kind = if (it.channelKind == null) "State" else it.channelKind
parsedKind = ChannelKind.parse(kind)
}

var channel = ChannelBuilder.create(new ChannelUID(thingUID, id), itemType)
.withKind(parsedKind)
.withConfiguration(createConfiguration)
.withType(channelTypeUID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know anything about Xtext, but what about channelTypeUID if it.channelType == null (the else branch). It seems to be undefined / null. Is the call to withType okay for a non-defined type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is. The ChannelBuilder is implemented in a way that setting it to null is the same as not setting it at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And more important, channels don't need to have a ChannelTypeUID at all:

    /**
     * Returns the channel type UID
     *
     * @return channel type UID or null if no channel type is specified
     */
    public ChannelTypeUID getChannelTypeUID() {
        return channelTypeUID;
    }

channels += channel.build()
}
]
Expand Down
31 changes: 31 additions & 0 deletions docs/documentation/features/dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,34 @@ Thing yahooweather:weather:losangeles [ location=2442047, unit="us", refresh=120
```

Trigger channels are defined with the keyword `Trigger` and only support the type String.

### Referencing existing channel types

Many bindings provide standalone channel type definitions like this:

```
<thing:thing-descriptions bindingId="yahooweather" [...]>
<channel-type id="temperature">
<item-type>Number</item-type>
<label>Temperature</label>
<description>Current temperature in degrees celsius</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f °C">
</state>
</channel-type>
[...]
</thing:thing-descriptions>
```

They can be referenced within a thing's channel definition, so that they need to be defined only once and can be reused for many channels. You may do so in the DSL as well:

```
Thing yahooweather:weather:losangeles [ location=2442047, unit="us", refresh=120 ] {
Channels:
Type temperature : my_yesterday_temperature
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Shall we actually also allow overriding (or defining) labels on channels (irrespectively whether they are based on a type or not)? Or is it good enough if people define their default label on the item (which probably suffices). Labels on channels are rather meant for bindings to provide them, so it might not be relevant for the DSL - just asking nonetheless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point. If you have a UI where you bind items to channels, you might want to see a nice name (aka label) instead of just the technical id. Therefore I think we should.
I would propose to create a separate PR for it, if that's okay?

}
```

The `Type` keyword indicates a reference to an existing channel definition. The channel kind and accepted item types of course are takes from the channel definition, therefore they don't need to be specified here again.