diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/deviceattributes.js b/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/deviceattributes.js index 9e097ad84..7df9dca9d 100644 --- a/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/deviceattributes.js +++ b/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/deviceattributes.js @@ -48,7 +48,7 @@ export default { parameters: (itemType, item) => [ p.inverted(itemType === 'Rollershutter'), p.presets(item.stateDescription, '20=Morning,60=Afternoon,80=Evening:@Setting.Night'), - p.language(item.settings && item.settings.regional.language), + p.language(item.settings?.regional?.language), p.actionMappings({ default: 'value' }, 'Close=0,Open=100,Lower=0,Raise=100', (config) => { const primaryControl = getGroupParameter('primaryControl', item.groups) || 'position' if (itemType === 'Dimmer') { @@ -73,7 +73,7 @@ export default { parameters: (itemType, item) => [ p.inverted(itemType === 'Rollershutter'), p.presets(item.stateDescription, '20=Morning,60=Afternoon,80=Evening:@Setting.Night'), - p.language(item.settings && item.settings.regional.language), + p.language(item.settings?.regional?.language), ...(getGroupParameter('primaryControl', item.groups) !== 'tilt' ? [] : [ p.actionMappings({ default: 'value' }, 'Close=0,Open=100', (config) => { if (itemType === 'Dimmer') { @@ -107,7 +107,7 @@ export default { itemTypes: ['Number', 'String'], parameters: (itemType, item) => [ p.supportedInputs(item.stateDescription, itemType === 'String' ? 'HDMI1=Cable,HDMI2=Kodi' : '1=Cable,2=Kodi'), - p.language(item.settings && item.settings.regional.language), + p.language(item.settings?.regional?.language), p.retrievable() ] }, @@ -410,7 +410,7 @@ export default { p.retrievable(), p.supportedModes(item.stateDescription), p.ordered(), - p.language(item.settings && item.settings.regional.language), + p.language(item.settings?.regional?.language), p.actionMappings( { set: 'mode', ...(config.ordered && { adjust: '(±deltaValue)' }) }, 'Close=Down,Open=Up,Lower=Down,Raise=Up' @@ -421,7 +421,7 @@ export default { RangeValue: { itemTypes: ['Dimmer', 'Number', 'Number:*', 'Rollershutter'], supports: ['multiInstance'], - parameters: (itemType, item) => [ + parameters: (itemType, item, config) => [ p.capabilityNames(item.groups.length ? item.label : '@Setting.RangeValue', '@Setting.FanSpeed,Speed'), p.inverted(itemType === 'Rollershutter'), p.nonControllable(item.stateDescription), @@ -432,12 +432,17 @@ export default { ? [p.supportedCommands(['UP', 'DOWN', 'MOVE', 'STOP'], 'UP=@Value.Open,DOWN=@Value.Close,STOP=@Value.Stop')] : []), p.supportedRange( - item.stateDescription, - itemType === 'Dimmer' || itemType === 'Rollershutter' ? '0:100:1' : '0:10:1' + item, + config, + itemType === 'Dimmer' || itemType === 'Rollershutter' + ? '0:100:1' + : config.nonControllable + ? '0:10:0.01' + : '0:10:1' ), p.presets(item.stateDescription, '1=@Value.Low:Lowest,10=@Value.High:Highest'), p.unitOfMeasure(item), - p.language(item.settings && item.settings.regional.language), + p.language(item.settings?.regional?.language), p.actionMappings({ set: 'value', adjust: '(±deltaValue)' }, 'Close=0,Open=100,Lower=(-10),Raise=(+10)'), p.stateMappings({ default: 'value', range: 'minValue:maxValue' }, 'Closed=0,Open=1:100') ] @@ -452,7 +457,7 @@ export default { : [p.inverted()]), p.nonControllable(item.stateDescription), p.retrievable(), - p.language(item.settings && item.settings.regional.language), + p.language(item.settings?.regional?.language), p.actionMappings(['ON', 'OFF'], 'Close=OFF,Open=ON'), p.stateMappings(['ON', 'OFF'], 'Closed=OFF,Open=ON') ] diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/devicetypes.js b/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/devicetypes.js index 8bd93d8a8..1f93bcb1b 100644 --- a/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/devicetypes.js +++ b/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/devicetypes.js @@ -60,7 +60,7 @@ const thermostatAttributes = [ const blindParameters = (_, item) => { const attributes = ['PositionState', 'TiltAngle'] - const metadata = item.members.map((mbr) => mbr.metadata && mbr.metadata.alexa.value).join(',') + const metadata = item.members.map((mbr) => mbr.metadata?.alexa?.value).filter(Boolean).join(',') return attributes.every((attr) => metadata.includes(attr)) ? [p.primaryControl()] : [] } diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/helpers.js b/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/helpers.js index 1a69d77cc..119e3d865 100644 --- a/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/helpers.js +++ b/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/helpers.js @@ -19,7 +19,7 @@ export const docLink = (title, anchor) => { return `${title}` } -export const getGroupParameter = (parameter, groups) => { +export const getGroupParameter = (parameter, groups = []) => { for (const group of groups) { const config = group.metadata.alexa.config || {} if (parameter in config) return config[parameter] @@ -39,25 +39,34 @@ export const getSemanticFormat = (type, format) => '' ) +export const getSupportedRange = (item, config, defaultValue) => { + const { minimum, maximum, step, pattern } = item.stateDescription || {} + if (!isNaN(minimum) && !isNaN(maximum) && !isNaN(step)) return `${minimum}:${maximum}:${step}` + const itemType = item.groupType || item.type + if (itemType.startsWith('Number') && config.nonControllable) { + const { precision, specifier } = pattern?.match(/%\d*(?:\.(?\d+))?(?[df])/)?.groups || {} + const [minimum, maximum] = defaultValue.split(':', 2) + if (specifier === 'd') return `${minimum}:${maximum}:1` + if (precision <= 16) return `${minimum}:${maximum}:${1 / 10 ** precision}` + } + return defaultValue +} + export const getTemperatureScale = (item) => { const itemType = item.groupType || item.type - const unitSymbol = item.unitSymbol - const statePresentation = (item.stateDescription && item.stateDescription.pattern) || '' - const format = (itemType === 'Number:Temperature' && unitSymbol) || statePresentation - if (format.endsWith('°C')) return 'CELSIUS' - if (format.endsWith('°F')) return 'FAHRENHEIT' - const { measurementSystem } = (item.settings && item.settings.regional) || {} + const format = (itemType === 'Number:Temperature' && item.unitSymbol) || item.stateDescription?.pattern + if (format?.endsWith('°C')) return 'CELSIUS' + if (format?.endsWith('°F')) return 'FAHRENHEIT' + const measurementSystem = item.settings?.regional?.measurementSystem if (measurementSystem === 'SI') return 'CELSIUS' if (measurementSystem === 'US') return 'FAHRENHEIT' } export const getUnitOfMeasure = (item) => { const itemType = item.groupType || item.type - const unitSymbol = item.unitSymbol - const statePresentation = (item.stateDescription && item.stateDescription.pattern) || '' const format = ((itemType === 'Dimmer' || itemType === 'Rollershutter') && '%') || - (itemType.startsWith('Number:') && unitSymbol) || - statePresentation - return Object.keys(UNITS_OF_MEASURE).find((id) => format.endsWith(UNITS_OF_MEASURE[id])) + (itemType.startsWith('Number:') && item.unitSymbol) || + item.stateDescription?.pattern + return Object.keys(UNITS_OF_MEASURE).find((id) => format?.endsWith(UNITS_OF_MEASURE[id])) } diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/parameters.js b/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/parameters.js index cfbfe10b0..5153406f1 100644 --- a/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/parameters.js +++ b/bundles/org.openhab.ui/web/src/assets/definitions/metadata/alexa/parameters.js @@ -13,6 +13,7 @@ import { getGroupParameter, getOptions, getSemanticFormat, + getSupportedRange, getTemperatureScale, getUnitOfMeasure, titleCase @@ -202,7 +203,7 @@ export default { name: 'nonControllable', label: 'Non-Controllable', type: 'BOOLEAN', - default: (stateDescription && stateDescription.readOnly) === true, + default: stateDescription?.readOnly === true, visible: (_, config) => !!config.retrievable }), ordered: () => ({ @@ -229,14 +230,12 @@ export default { ` (${docLink('Asset Catalog')})`, type: 'TEXT', default: - stateDescription && - stateDescription.options && - stateDescription.options - .filter((option) => !isNaN(option.value)) + stateDescription?.options?.filter((option) => !isNaN(option.value)) .map((option) => `${option.value}=${option.label}`) .slice(0, STATE_DESCRIPTION_OPTIONS_LIMIT), placeholder: placeholder.replace(/,/g, '\n'), - multiple: true + multiple: true, + visible: (_, config) => !config.nonControllable }), primaryControl: () => ({ name: 'primaryControl', @@ -356,10 +355,7 @@ export default { description: 'Each input formatted as inputValue=inputName1:inputName2:...', type: 'TEXT', default: - stateDescription && - stateDescription.options && - stateDescription.options - .map((option) => `${option.value}=${option.label}`) + stateDescription?.options?.map((option) => `${option.value}=${option.label}`) .slice(0, STATE_DESCRIPTION_OPTIONS_LIMIT), placeholder: placeholder.replace(/,/g, '\n'), multiple: true, @@ -372,10 +368,7 @@ export default { `Each mode formatted as mode=@assetIdOrName1:@assetIdOrName2:... (${docLink('Asset Catalog')})`, type: 'TEXT', default: - stateDescription && - stateDescription.options && - stateDescription.options - .map((option) => `${option.value}=${option.label}`) + stateDescription?.options?.map((option) => `${option.value}=${option.label}`) .slice(0, STATE_DESCRIPTION_OPTIONS_LIMIT), placeholder: 'Normal=Normal:Cottons\nWhites=Whites', multiple: true, @@ -391,18 +384,12 @@ export default { multiple: true, advanced: true }), - supportedRange: (stateDescription, defaultValue) => ({ + supportedRange: (item, config, defaultValue) => ({ name: 'supportedRange', label: 'Supported Range', description: 'Formatted as minValue:maxValue:precision', type: 'TEXT', - default: - stateDescription && - !isNaN(stateDescription.minimum) && - !isNaN(stateDescription.maximum) && - !isNaN(stateDescription.step) - ? `${stateDescription.minimum}:${stateDescription.maximum}:${stateDescription.step}` - : defaultValue, + default: getSupportedRange(item, config, defaultValue), pattern: '[+-]?[0-9]+:[+-]?[0-9]+:[0-9]+' }), supportedThermostatModes: () => ({