Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[blockly] Support Quantity in more math blocks #2041

Merged
merged 7 commits into from
Oct 8, 2023
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 @@ -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) {
Expand All @@ -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 ''
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@
</shadow>
</value>
</block>
<block type="oh_math_minmax">
<value name="NUM1">
<shadow type="math_number">
<field name="NUM">3</field>
</shadow>
</value>
<value name="NUM2">
<shadow type="math_number">
<field name="NUM">4</field>
</shadow>
</value>
</block>
<block type="math_on_list" />
<block type="math_modulo">
<value name="DIVIDEND">
Expand Down