diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-math.js b/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-math.js
index 0756241d4b..d0bd926ed2 100644
--- a/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-math.js
+++ b/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-math.js
@@ -138,7 +138,7 @@ export default function (f7, isGraalJs) {
const decimals = javascriptGenerator.valueToCode(block, 'DECIMALS', javascriptGenerator.ORDER_NONE)
const operand = block.getFieldValue('op')
- let code = ''
+ let code
if (operand !== 'toFixed') {
let method = ''
switch (operand) {
@@ -162,4 +162,180 @@ export default function (f7, isGraalJs) {
}
return [code, 0]
}
+
+ Blockly.Blocks['math_single'] = {
+ init: function () {
+ const dropDown = new Blockly.FieldDropdown([
+ ['square root', 'ROOT'],
+ ['absolute', 'ABS'],
+ ['-', 'NEG'],
+ ['ln', 'LN'],
+ ['log10', 'LOG10'],
+ ['e^', 'EXP'],
+ ['10^', 'POW10']
+ ])
+ this.appendValueInput('NUM')
+ .setCheck(['Number', 'oh_quantity'])
+ .appendField(dropDown, 'OP')
+
+ this.setColour('%{BKY_MATH_HUE}')
+ this.setInputsInline(false)
+ let thisBlock = this
+ this.setTooltip(function () {
+ const operand = thisBlock.getFieldValue('OP')
+ switch (operand) {
+ case 'ROOT': return 'Return the square root of the input'
+ case 'ABS': return 'Return the absolute value of the input'
+ case 'NEG': return 'Return the negation of the input'
+ case 'LN': return 'Return the natural (base e) logarithm of the input'
+ case 'LOG10': return 'Return the base 10 logarithm of the input'
+ case 'EXP': return 'Return e to the power of the input'
+ case 'POW10': return 'Return 10 to the power of the input'
+ }
+ })
+ this.setHelpUrl('https://www.openhab.org/docs/configuration/blockly/rules-blockly-math.html#functions')
+ this.setOutput(true, 'Number')
+ }
+ }
+
+ javascriptGenerator['math_single'] = function (block) {
+ const inputType = blockGetCheckedInputType(block, 'NUM')
+ const math_number_input = javascriptGenerator.valueToCode(block, 'NUM', javascriptGenerator.ORDER_FUNCTION_CALL)
+ let math_number = math_number_input
+ if (inputType === 'oh_quantity') {
+ math_number = math_number_input + '.float'
+ }
+ const operand = block.getFieldValue('OP')
+
+ let method = ''
+ switch (operand) {
+ case 'ROOT':
+ method = `Math.sqrt(${math_number})`
+ break
+ case 'ABS':
+ method = `Math.abs(${math_number})`
+ break
+ case 'NEG':
+ method = `-${math_number}`
+ break
+ case 'LN':
+ method = `Math.log(${math_number})`
+ break
+ case 'LOG10':
+ method = `Math.log10(${math_number})`
+ break
+ case 'EXP':
+ method = `Math.exp(${math_number})`
+ break
+ case 'POW10':
+ method = `Math.pow(10, ${math_number})`
+ break
+ }
+
+ let code = `${method}`
+
+ if (inputType === 'oh_quantity') {
+ code = `Quantity((${code}).toString() + ' ' + ${math_number_input}.symbol)`
+ }
+ return [code, javascriptGenerator.ORDER_FUNCTION_CALL]
+ }
+
+ Blockly.Blocks['oh_math_minmax'] = {
+ init: function () {
+ const dropDown = new Blockly.FieldDropdown([
+ ['minimum of', 'min'],
+ ['maximum of', 'max']
+ ])
+ this.appendDummyInput()
+ .appendField(dropDown, 'OP')
+ this.appendValueInput('NUM1')
+ .setCheck(['Number', 'oh_quantity'])
+ this.appendValueInput('NUM2')
+ .appendField(' and ')
+ .setCheck(['Number', 'oh_quantity'])
+
+ this.setColour('%{BKY_MATH_HUE}')
+ this.setInputsInline(true)
+ let thisBlock = this
+ this.setTooltip(function () {
+ const operand = thisBlock.getFieldValue('OP')
+ switch (operand) {
+ case 'min': return 'Return the mimimum of both inputs'
+ case 'max': return 'Return the maximum of both inputs'
+ }
+ })
+ this.setOnChange(function (changeEvent) {
+ if (changeEvent.type === 'move') {
+ const typeInfo = computeMinMaxOutputType(this)
+ this.setOutput(true, typeInfo.leadType)
+ }
+ })
+ this.setHelpUrl('https://www.openhab.org/docs/configuration/blockly/rules-blockly-math.html#minmax')
+ this.setOutput(true, null)
+ }
+ }
+
+ function computeMinMaxOutputType (block, throwError = false) {
+ const operand = block.getFieldValue('OP')
+
+ let math_number_input1
+ let math_number_input2
+ try { // may throw an exception on workspace startup in uninitialized state but in this case we can ignore
+ math_number_input1 = javascriptGenerator.valueToCode(block, 'NUM1', javascriptGenerator.ORDER_FUNCTION_CALL)
+ math_number_input2 = javascriptGenerator.valueToCode(block, 'NUM2', javascriptGenerator.ORDER_FUNCTION_CALL)
+ } catch (e) {}
+
+ /*
+ When dealing with variables, Blockly does not provide type information (type is "").
+ In this case, we fall back to checking whether the actual input contains "Quantity" or is a number.
+ */
+ let inputType1
+ let inputType2
+ try { // may throw an exception on workspace startup in uninitialized state but in this case we can ignore
+ inputType1 = blockGetCheckedInputType(block, 'NUM1') || getVariableType(math_number_input1)
+ inputType2 = blockGetCheckedInputType(block, 'NUM2') || getVariableType(math_number_input2)
+ } catch (e) {}
+
+ /*
+ If exactly one of the two inputs is a variable, assume it has the same type as the other input.
+ In case both inputs are vars, assume they are numbers.
+ */
+ const containsOneVar = (inputType1 === '' && inputType2 !== '') || (inputType1 !== '' && inputType2 === '')
+
+ /*
+ If both inputs are not the same type and none of them is a variable, throw an Error on code generation.
+ */
+ if (throwError && inputType1 !== inputType2 && !containsOneVar) {
+ throw new Error(`Both operand types need to be equal for ${operand.toUpperCase()}-block (${math_number_input1} -> ${inputType1}, ${math_number_input2} -> ${inputType2})`)
+ }
+
+ const leadType = inputType1 || inputType2 || 'Number'
+ return { leadType, math_number_input1, math_number_input2, operand }
+ }
+
+ javascriptGenerator['oh_math_minmax'] = function (block) {
+ const { leadType, math_number_input1, math_number_input2, operand } = computeMinMaxOutputType(block, true)
+ let code = ''
+
+ switch (leadType) {
+ case 'oh_quantity':
+ const op = (operand === 'min') ? 'lessThan' : 'greaterThan'
+ code = `(${math_number_input1}.${op}(${math_number_input2})) ? ${math_number_input1} : ${math_number_input2}`
+ break
+ default:
+ code = `Math.${operand}(${math_number_input1},${math_number_input2})`
+ break
+ }
+
+ return [code, javascriptGenerator.ORDER_FUNCTION_CALL]
+ }
+
+ /*
+ * As Blockly does not provide type information for variables, try to determine it based on the content of the block.
+ */
+ function getVariableType (math_number_input) {
+ if (math_number_input.includes('Quantity')) return 'oh_quantity'
+ if (!isNaN(math_number_input)) return 'Number'
+ return ''
+ }
}
diff --git a/bundles/org.openhab.ui/web/src/components/config/controls/blockly-editor.vue b/bundles/org.openhab.ui/web/src/components/config/controls/blockly-editor.vue
index 1d28e61558..4db677bae7 100644
--- a/bundles/org.openhab.ui/web/src/components/config/controls/blockly-editor.vue
+++ b/bundles/org.openhab.ui/web/src/components/config/controls/blockly-editor.vue
@@ -116,6 +116,18 @@
+
+
+
+ 3
+
+
+
+
+ 4
+
+
+