Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased

| Type | Namespace | Description | | Reference | Breaking |
|-------------|---------------|-----------------------------------------------------------------------------------|:--|--------------------------------------------------------|----------|
| Enhancement | `environment` | Add `isFileBasedScript()` function to determine whether script is file-based | | [#441](https://github.com/openhab/openhab-js/pull/441) | no |
| Enhancement | `items` | Add support for providing Items, metadata & channel links from file-based scripts | | [#441](https://github.com/openhab/openhab-js/pull/441) | no |

## 5.11.1 (5.11.0)

| Type | Namespace | Description | Reference | Breaking |
Expand Down
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,9 @@ See [openhab-js : items](https://openhab.github.io/openhab-js/items.html) for fu
- .getItem(name, nullIfMissing) ⇒ `Item`
- .getItems() ⇒ `Array[Item]`
- .getItemsByTag(...tagNames) ⇒ `Array[Item]`
- .addItem([itemConfig](#itemconfig))
- .removeItem(itemOrItemName) ⇒ `boolean`
- .replaceItem([itemConfig](#itemconfig))
- .addItem([itemConfig](#itemconfig), persist) ⇒ `Item`
- .removeItem(itemOrItemName) ⇒ `Item|null`
- .replaceItem([itemConfig](#itemconfig)) ⇒ `Item|null`
- .safeItemName(s) ⇒ `string`

```javascript
Expand Down Expand Up @@ -502,6 +502,20 @@ items.replaceItem({

See [openhab-js : ItemConfig](https://openhab.github.io/openhab-js/global.html#ItemConfig) for full API documentation.

#### Providing Items (& metadata & channel links) from Scripts

The `addItem` method can be used to provide Items from scripts in a configuration-as-code manner.
It also allows providing metadata and channel configurations for the Item, basically creating the Item as if it was defined in a `.items` file.
The benefit of using `addItem` is that you can use loops, conditions or generator functions to create lots of Items without the need to write them all out in a file or manually in the UI.

When called from file-based scripts, the created Item will share the lifecycle with the script, meaning it will be removed when the script is unloaded.
You can use the `persist` parameter to optionally persist the Item from file-based scripts.

When called from UI-based scripts, the Item will be stored permanently and will not be removed when the script is unloaded.
Keep in mind that attempting to add an Item with the same name as an existing Item will result in an error.

See [openhab-js : Item](https://openhab.github.io/openhab-js/items.html#.addItem) for full API documentation.

#### `ItemPersistence`

Calling `Item.persistence` returns an `ItemPersistence` object with the following functions:
Expand Down
46 changes: 46 additions & 0 deletions src/environment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// This module MUST NOT depend on any other library code to avoid circular dependencies

/**
* Environment namespace.
* This namespace handles utilities for determining the script environment and retrieving information about it.
*
* @namespace environment
*/

/**
* Returns whether the code is running from a file-based script.
* This is determined by checking if the `javax.script.filename` global variable is defined.
* This is useful to distinguish between file-based scripts and UI-based scripts in openHAB.
*
* @memberOf environment
* @return {boolean} true if the script is file-based, false otherwise
*/
function isFileBasedScript () {
return globalThis['javax.script.filename'] !== undefined;
}

/**
* Returns whether the host openHAB version supports providing openHAB entities via the `@runtime/provider` module.
*
* @private
* @return {boolean} true if the provider module is available, false otherwise
*/
function _hasProviderSupport () {
return !!require('@runtime/provider').itemRegistry;
}

/**
* Returns whether the registry implementations from the `@runtime/provider` module should be used instead of the default ones from the `@runtime` module.
* Provider implementations should be used if the host openHAB version supports it and the script is running from a file-based script.
*
* @memberOf environment
* @return {boolean} true if the provider registry implementations should be used, false otherwise
*/
function useProviderRegistries () {
return _hasProviderSupport() && isFileBasedScript();
}

module.exports = {
isFileBasedScript,
useProviderRegistries
};
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ module.exports = {
get osgi () { return require('./osgi'); },
get cache () { return require('./cache'); },
get time () { return require('./time'); },
get Quantity () { return require('./quantity').getQuantity; }
get Quantity () { return require('./quantity').getQuantity; },
get environment () { return require('./environment'); }
};
194 changes: 194 additions & 0 deletions src/items/itemchannellink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
const osgi = require('../osgi');
const utils = require('../utils');
const environment = require('../environment');
const log = require('../log')('itemchannellink');
const { _getItemName } = require('../helpers');

const itemChannelLinkRegistry = environment.useProviderRegistries()
? require('@runtime/provider').itemChannelLinkRegistry
: osgi.getService('org.openhab.core.thing.link.ItemChannelLinkRegistry');
const JavaItemChannelLink = Java.type('org.openhab.core.thing.link.ItemChannelLink');
const ChannelUID = Java.type('org.openhab.core.thing.ChannelUID');
const Configuration = Java.type('org.openhab.core.config.core.Configuration');

/**
* Item channel link namespace.
* This namespace provides access to Item channel links.
*
* @namespace items.itemChannelLink
*/

/**
* @typedef {import('./items').Item} Item
* @private
*/

/**
* Class representing an openHAB Item -> channel link.
*
* @memberof items.itemChannelLink
* @hideconstructor
*/
class ItemChannelLink {
itemName;
channelUID;
configuration;

/**
* @param {*} rawItemChannelLink {@link https://www.openhab.org/javadoc/latest/org/openhab/core/thing/link/itemchannellink org.openhab.core.thing.link.ItemChannelLink}
*/
constructor (rawItemChannelLink) {
this.itemName = rawItemChannelLink.getItemName().toString();
this.channelUID = rawItemChannelLink.getLinkedUID().toString();
this.configuration = utils.javaMapToJsObj(rawItemChannelLink.getConfiguration().getProperties());
}
}

/**
* Gets a channel link of from an Item.
*
* @memberof items.itemChannelLink
* @param {Item|string} itemOrName {@link Item} or the name of the Item
* @param {string} channelUID
* @returns {ItemChannelLink|null} the ItemChannelLink or `null` if none exists
*/
function getItemChannelLink (itemOrName, channelUID) {
const itemName = _getItemName(itemOrName);
log.debug(`Getting ItemChannelLink ${itemName} -> ${channelUID} from registry.`);
const itemChannelLink = itemChannelLinkRegistry.get(itemName + ' -> ' + channelUID);
if (itemChannelLink === null) return null;
return new ItemChannelLink(itemChannelLink);
}

/**
* Creates a new ItemChannelLink object.
* This ItemChannelLink is not registered with any provider and therefore cannot be accessed.
*
* @private
* @param {string} itemName the name of the Item
* @param {string} channelUID
* @param {object} [conf] channel configuration
* @returns {JavaItemChannelLink} ItemChannelLink object
*/
function _createItemChannelLink (itemName, channelUID, conf) {
log.debug(`Creating ItemChannelLink ${itemName} -> ${channelUID}`);
if (typeof conf === 'object') {
log.debug(` with configuration: ${JSON.stringify(conf)}`);
return new JavaItemChannelLink(itemName, new ChannelUID(channelUID), new Configuration(conf));
} else {
return new JavaItemChannelLink(itemName, new ChannelUID(channelUID));
}
}

/**
* Adds a new channel link to an Item.
*
* If this is called from file-based scripts, the Item -> channel link is registered with the ScriptedItemChannelLinkProvider and shares the same lifecycle as the script.
* You can still persist the Item -> channel link permanently in this case by setting the `persist` parameter to `true`.
* If this is called from UI-based scripts, the Item -> channel link is stored to the ManagedItemChannelLinkProvider and independent of the script's lifecycle.
*
* @memberOf items.itemChannelLink
* @param {Item|string} itemOrName {@link Item} or the name of the Item
* @param {string} channelUID
* @param {object} [configuration] channel configuration
* @param {boolean} [persist=false] whether to persist the Item -> channel link permanently (only respected for file-based scripts)
* @returns {ItemChannelLink} the ItemChannelLink
* @throws {Error} if the Item -> channel link already exists
*/
function addItemChannelLink (itemOrName, channelUID, configuration, persist = false) {
const itemName = _getItemName(itemOrName);
log.debug(`Adding ItemChannelLink ${itemName} -> ${channelUID} to registry...`);
let itemChannelLink = _createItemChannelLink(itemName, channelUID, configuration);
try {
itemChannelLink = (persist && environment.useProviderRegistries()) ? itemChannelLinkRegistry.addPermanent(itemChannelLink) : itemChannelLinkRegistry.add(itemChannelLink);
} catch (e) {
if (e instanceof Java.type('java.lang.IllegalArgumentException')) {
throw new Error(`Cannot add ItemChannelLink ${itemName} -> ${channelUID}: already exists`);
} else {
throw e; // re-throw other errors
}
}
return new ItemChannelLink(itemChannelLink);
}

/**
* Updates a channel link of an Item.
*
* @private
* @param {string} itemName the name of the Item
* @param {string} channelUID
* @param {object} [configuration] channel configuration
* @returns {ItemChannelLink|null} the old ItemChannelLink or `null` if none exists
*/
function _updateItemChannelLink (itemName, channelUID, configuration) {
log.debug(`Updating ItemChannelLink ${itemName} -> ${channelUID} in registry...`);
let itemChannelLink = _createItemChannelLink(itemName, channelUID, configuration);
itemChannelLink = itemChannelLinkRegistry.update(itemChannelLink);
if (itemChannelLink === null) return null;
return new ItemChannelLink(itemChannelLink);
}

/**
* Adds or updates a channel link of an Item.
* If you use this in file-based scripts, better use {@link addItemChannelLink} to provide channel links.
*
* If an Item -> channel link is not provided by this script or the ManagedItemChannelLinkProvider, it is not editable and a warning is logged.
*
* @memberof items.itemChannelLink
* @param {Item|string} itemOrName {@link Item} or the name of the Item
* @param {string} channelUID
* @param {object} [configuration] channel configuration
* @returns {ItemChannelLink|null} the old ItemChannelLink or `null` if it did not exist
*/
function replaceItemChannelLink (itemOrName, channelUID, configuration) {
const itemName = _getItemName(itemOrName);
const itemChannelLink = getItemChannelLink(itemName, channelUID);
return (itemChannelLink === null) ? addItemChannelLink(itemName, channelUID, configuration) : _updateItemChannelLink(itemName, channelUID, configuration);
}

/**
* Removes a channel link from an Item.
*
* @memberof items.itemChannelLink
* @param {Item|string} itemOrName {@link Item} or the name of the Item
* @param {string} channelUID
* @returns {ItemChannelLink|null} the removed ItemChannelLink or `null` if none exists, or it cannot be removed
*/
function removeItemChannelLink (itemOrName, channelUID) {
const itemName = _getItemName(itemOrName);
log.debug(`Removing ItemChannelLink ${itemName} -> ${channelUID} from registry...`);
const itemChannelLink = itemChannelLinkRegistry.remove(itemName + ' -> ' + channelUID);
if (itemChannelLink === null) return null;
return new ItemChannelLink(itemChannelLink);
}

/**
* Removes all channel links from the given Item.
*
* @memberof items.itemChannelLink
* @param {string} itemName the name of the Item
* @returns {number} number of links removed
*/
function removeLinksForItem (itemName) {
return itemChannelLinkRegistry.removeLinksForItem(itemName);
}

/**
* Removes all orphaned (Item or channel missing) links.
*
* @memberof items.itemChannelLink
* @returns {number} number of links removed
*/
function removeOrphanedItemChannelLinks () {
return itemChannelLinkRegistry.purge();
}

module.exports = {
getItemChannelLink,
addItemChannelLink,
replaceItemChannelLink,
removeItemChannelLink,
removeLinksForItem,
removeOrphanedItemChannelLinks,
ItemChannelLink
};
Loading