From e2ebc1de524b27ed085c4a9a88103ae988f79fe0 Mon Sep 17 00:00:00 2001 From: stefan-hoehn Date: Sun, 8 Oct 2023 21:16:12 +0200 Subject: [PATCH] [blockly] Support Quantity in more math blocks (#2041) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #2001. Adds Quantity support for more math blocks: - math_single had to be reimplemented - math_minmax was added Note that there is a special case on min/max if the inputs are not of equal type an error will be shown to user. In the special case of variables Blockly does its best to detect the right code to be generated for the min/max block: - both are variables -> then numerical input is expected - one of inputs is a variable: then blockly uses the type of the other non-var-block to base the generation on (either number or quantity comparison) - note that no type conversion of the inputs is done Also-by: Florian Hotze Signed-off-by: Stefan Höhn --- .../assets/definitions/blockly/blocks-math.js | 178 +++++++++++++++++- .../config/controls/blockly-editor.vue | 12 ++ 2 files changed, 189 insertions(+), 1 deletion(-) 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 + + +