diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-persistence.js b/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-persistence.js index 2d800898aa..a99fc7b155 100644 --- a/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-persistence.js +++ b/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-persistence.js @@ -14,25 +14,28 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe Blockly.Blocks['oh_get_persistvalue'] = { init: function () { this.appendDummyInput() - .appendField('get the') + .appendField('get') .appendField(new Blockly.FieldDropdown([ ['persisted state', 'persistedState'], - ['historic state average', 'averageSince'], ['future state average', 'averageUntil'], - ['historic state delta', 'deltaSince'], ['future state delta', 'deltaUntil'], - ['historic state deviation', 'deviationSince'], ['future state deviation', 'deviationUntil'], - ['historic state variance', 'varianceSince'], ['future state variance', 'varianceUntil'], - ['historic evolution rate', 'evolutionRateSince'], ['future evolution rate', 'evolutionRateUntil'], - ['historic state minimum', 'minimumSince'], ['future state minimum', 'minimumUntil'], - ['historic state maximum', 'maximumSince'], ['future state maximum', 'maximumUntil'], - ['historic state sum', 'sumSince'], ['future state sum', 'sumUntil'], + ['historic state average', 'averageSince'], ['future state average', 'averageUntil'], ['state average between', 'averageBetween'], + ['historic state delta', 'deltaSince'], ['future state delta', 'deltaUntil'], ['state delta between', 'deltaBetween'], + ['historic state deviation', 'deviationSince'], ['future state deviation', 'deviationUntil'], ['state deviation between', 'deviationBetween'], + ['historic state variance', 'varianceSince'], ['future state variance', 'varianceUntil'], ['state variance between', 'varianceBetween'], + ['historic evolution rate', 'evolutionRateSince'], ['future evolution rate', 'evolutionRateUntil'], ['state evolution rate between', 'evolutionRateBetween'], + ['historic state minimum', 'minimumSince'], ['future state minimum', 'minimumUntil'], ['state minimum between', 'minimumBetween'], + ['historic state maximum', 'maximumSince'], ['future state maximum', 'maximumUntil'], ['state maximum between', 'maximumBetween'], + ['historic state sum', 'sumSince'], ['future state sum', 'sumUntil'], ['state sum between', 'sumBetween'], + ['historic state updates count', 'countSince'], ['future state updates count', 'countUntil'], ['state updates count between', 'countBetween'], + ['historic state changes count', 'countStateChangesSince'], ['future state changes count', 'countStateChangesUntil'], ['state changes count between', 'countStateChangesBetween'], ['previous state value', 'previousState'], ['next state value', 'nextState'], + ['all states since', 'getAllStatesSince'], ['all states until', 'getAllStatesUntil'], ['all states between', 'getAllStatesBetween'], ['previous state numeric value', 'previousNumericState'], ['next state numeric value', 'nextNumericState'], ['previous state value time', 'previousStateTime'], ['next state value time', 'nextStateTime'] ], this.handleTypeSelection.bind(this) ), 'methodName') this.methodName = this.getFieldValue('methodName') this.appendValueInput('itemName') - .appendField('of item ') + .appendField('of item') .setAlign(Blockly.ALIGN_RIGHT) .setCheck(['String', 'oh_item', 'oh_itemtype']) this.appendValueInput('persistenceName') @@ -46,29 +49,46 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe this.setTooltip(() => { let methodName = this.getFieldValue('methodName') let TIP = { - 'averageSince': 'Gets the average value of the State of a persisted Item since a certain point in time. This method uses a time-weighted average calculation', - 'averageUntil': 'Gets the average value of the State of a persisted Item until a certain point in time. This method uses a time-weighted average calculation', - 'deltaSince': 'Gets the difference in value of the State of a given Item since a certain point in time', - 'deltaUntil': 'Gets the difference in value of the State of a given Item until a certain point in time', - 'deviationSince': 'Gets the standard deviation of the state of the given Item since a certain point in time', - 'deviationUntil': 'Gets the standard deviation of the state of the given Item until a certain point in time', - 'varianceSince': 'Gets the variance of the state of the given Item since a certain point in time', - 'varianceUntil': 'Gets the variance of the state of the given Item until a certain point in time', - 'evolutionRateSince': 'Gets the evolution rate of the state of a given Item since a certain point in time', - 'evolutionRateUntil': 'Gets the evolution rate of the state of a given Item until a certain point in time', - 'minimumSince': 'Gets the minimum value of the State of a persisted Item since a certain point in time', - 'minimumUntil': 'Gets the minimum value of the State of a persisted Item until a certain point in time', - 'maximumSince': 'Gets the maximum value of the State of a persisted Item since a certain point in time', - 'maximumUntil': 'Gets the maximum value of the State of a persisted Item until a certain point in time', - 'sumSince': 'Gets the sum of the previous States of a persisted Item since a certain point in time', - 'sumUntil': 'Gets the sum of the previous States of a persisted Item until a certain point in time', - 'previousState': 'Gets the previous state with option to skip to different value as current', - 'nextState': 'Gets the next state with option to skip to different value as current', - 'previousNumericState': 'Gets the previous state without the unit with option to skip to different value as current', - 'nextNumericState': 'Gets the next state without the unit with option to skip to different value as current', - 'previousStateTime': 'Gets the time when previous state last occurred with option to skip to different value as current', - 'nextStateTime': 'Gets the time when next state will occur with option to skip to different value as current', - 'persisted': 'Gets the persisted state at a certain point in time' + 'averageSince': 'Gets the average value of the State of the Item since a certain point in time. This method uses a time-weighted average calculation', + 'averageUntil': 'Gets the average value of the State of the Item until a certain point in time. This method uses a time-weighted average calculation', + 'averageBetween': 'Gets the average value of the State of the Item between two points in time. This method uses a time-weighted average calculation', + 'deltaSince': 'Gets the difference in value of the State of the Item since a certain point in time', + 'deltaUntil': 'Gets the difference in value of the State of the Item until a certain point in time', + 'deltaBetween': 'Gets the difference in value of the State of the Item between two points in time', + 'deviationSince': 'Gets the standard deviation of the State of the Item since a certain point in time', + 'deviationUntil': 'Gets the standard deviation of the State of the Item until a certain point in time', + 'deviationBetween': 'Gets the standard deviation of the State of the Item between two points in time', + 'varianceSince': 'Gets the variance of the State of the Item since a certain point in time', + 'varianceUntil': 'Gets the variance of the State of the Item until a certain point in time', + 'varianceBetween': 'Gets the variance of the State of the Item between two points in time', + 'evolutionRateSince': 'Gets the evolution rate of the State of the Item since a certain point in time', + 'evolutionRateUntil': 'Gets the evolution rate of the State of the Item until a certain point in time', + 'evolutionRateBetween': 'Gets the evolution rate of the State of the Item between two points in time', + 'minimumSince': 'Gets the minimum value of the State of the Item since a certain point in time', + 'minimumUntil': 'Gets the minimum value of the State of the Item until a certain point in time', + 'minimumBetween': 'Gets the minimum value of the State of the Item between two points in time', + 'maximumSince': 'Gets the maximum value of the State of the Item since a certain point in time', + 'maximumUntil': 'Gets the maximum value of the State of the Item until a certain point in time', + 'maximumBetween': 'Gets the maximum value of the State of the Item between two points in time', + 'sumSince': 'Gets the sum of the previous States of the Item since a certain point in time', + 'sumUntil': 'Gets the sum of the future States of the Item until a certain point in time', + 'sumBetween': 'Gets the sum of the States of the Item between two points in time', + 'previousState': 'Gets the previous State of the Item, with option to skip to different value as current', + 'nextState': 'Gets the next State of the Item, with option to skip to different value as current', + 'getAllStatesSince': 'Gets Array of timestamp and state pairs of persisted items since a certain point in time', + 'getAllStatesUntil': 'Gets Array of timestamp and state pairs of persisted items until a certain point in time', + 'getAllStatesBetween': 'Gets Array of timestamp and state pairs of persisted items between two points in time', + 'previousNumericState': 'Gets the previous State of the Item without the unit, with option to skip to different value as current', + 'nextNumericState': 'Gets the next State of the Item without the unit, with option to skip to different value as current', + 'previousStateTime': 'Gets the time when previous State of the Item last occurred, with option to skip to different value as current', + 'nextStateTime': 'Gets the time when next State of the Item will occur, with option to skip to different value as current', + 'countSince': 'Gets the number of stored State updates of the Item since a certain point in time', + 'countUntil': 'Gets the number of stored State updates of the Item until a certain point in time', + 'countBetween': 'Gets the number of stored State updates of the Item between two points in time', + 'countStateChangesSince': 'Gets the number of State changes of the Item since a certain point in time', + 'countStateChangesUntil': 'Gets the number of State changes of the Item until a certain point in time', + 'countStateChangesBetween': 'Gets the number of State changes of the Item between two points in time', + 'persistedState': 'Gets the State of the Item at a certain point in time' } return TIP[methodName] }) @@ -85,10 +105,35 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe if (!persistenceNameInput.getShadowDom()) { persistenceNameInput.setShadowDom(Blockly.utils.xml.textToDom('')) } + + // Always remove when switching, so the sequence of the selection list gets updated + if (this.getInput('returnTypeInput')) { + this.removeInput('returnTypeInput') + } + if (isGraalJs && ![ + 'evolutionRateSince', 'evolutionRateUntil', 'evolutionRateBetween', + 'countSince', 'countUntil', 'countBetween', + 'countStateChangesSince', 'countStateChangesUntil', 'countStateChangesBetween', + 'previousNumericState', 'nextNumericState', 'previousStateTime', 'nextStateTime' + ].includes(this.methodName)) { + this.appendDummyInput('returnTypeInput') + .appendField('as') + .appendField(new Blockly.FieldDropdown(this.returnTypeNames()), 'returnTypeName') + .setAlign(Blockly.ALIGN_RIGHT) + this.moveInputBefore('returnTypeInput', 'itemName') + } + + let hasSinceField = this.methodName.endsWith('Since') || this.methodName.endsWith('Between') || (this.methodName === 'persistedState') + let hasUntilField = this.methodName.endsWith('Until') || this.methodName.endsWith('Between') + + if (this.getInput('dayInfoSince') && !hasSinceField) { + this.removeInput('dayInfoSince') + } + if (this.getInput('dayInfoUntil') && !hasUntilField) { + this.removeInput('dayInfoUntil') + } + if (['previousState', 'nextState', 'previousNumericState', 'nextNumericState', 'previousStateTime', 'nextStateTime'].includes(this.methodName)) { - if (this.getInput('dayInfo')) { - this.removeInput('dayInfo') - } if (!this.getInput('skipPrevious')) { this.appendValueInput('skipPrevious') .appendField('skip same ') @@ -100,20 +145,113 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe this.removeInput('skipPrevious') } - const preposition = (this.methodName === 'persistedState') ? 'at' : (this.methodName.endsWith('Until') ? 'until' : 'since') + const prepositionSince = (this.methodName === 'persistedState') ? 'at' : (this.methodName.endsWith('Since') ? 'since' : 'between') + const prepositionUntil = this.methodName.endsWith('Until') ? 'until' : 'and' + + if (hasSinceField) { + if (!this.getInput('dayInfoSince')) { + this.appendValueInput('dayInfoSince') + .appendField(prepositionSince, 'prepositionSince') + .setCheck(['ZonedDateTime']) + this.getInput('dayInfoSince').setShadowDom( + Blockly.utils.xml.textToDom(` + + + 1 + + + Hours + minus + `)) + if (this.getInput('dayInfoUntil')) { + this.moveInputBefore('dayInfoSince', 'dayInfoUntil') + } else { + this.moveInputBefore('dayInfoSince', 'persistenceName') + } + } else { + const prepositionField = this.getField('prepositionSince') + if (prepositionField.getText() !== prepositionSince) { + prepositionField.setValue(prepositionSince) + } + } + } - if (!this.getInput('dayInfo')) { - this.appendValueInput('dayInfo') - .appendField(preposition, 'preposition') - .setCheck(['ZonedDateTime']) - this.moveInputBefore('dayInfo', 'persistenceName') - } else { - const prepositionField = this.getField('preposition') - if (prepositionField.getText() !== preposition) { - prepositionField.setValue(preposition) + if (hasUntilField) { + if (!this.getInput('dayInfoUntil')) { + this.appendValueInput('dayInfoUntil') + .appendField(prepositionUntil, 'prepositionUntil') + .setCheck(['ZonedDateTime']) + this.getInput('dayInfoUntil').setShadowDom( + Blockly.utils.xml.textToDom(` + + + 1 + + + Hours + plus + `)) + this.moveInputBefore('dayInfoUntil', 'persistenceName') + } else { + const prepositionField = this.getField('prepositionUntil') + if (prepositionField.getText() !== prepositionUntil) { + prepositionField.setValue(prepositionUntil) + } } } } + }, + returnTypeNames: function () { + // use different list of return types and sequence to make sure first entry is old behaviour for backward compatibility + let returnTypes = [] + switch (this.methodName) { + case 'persistedState': + case 'previousState': + case 'nextState': + case 'maximumSince': + case 'maximumUntil': + case 'maximumBetween': + case 'minimumSince': + case 'minimumUntil': + case 'minimumBetween': + returnTypes = [['String', 'state'], + ['Quantity', 'quantityState'], + ['Number', 'numericState'], + ['Timestamp', 'timestamp']] + break + + case 'averageSince': + case 'averageUntil': + case 'averageBetween': + case 'deltaSince': + case 'deltaUntil': + case 'deltaBetween': + case 'deviationSince': + case 'deviationUntil': + case 'deviationBetween': + case 'sumSince': + case 'sumUntil': + case 'sumBetween': + case 'varianceSince': + case 'varianceUntil': + case 'varianceBetween': + returnTypes = [['Number', 'numericState'], + ['Quantity', 'quantityState'], + ['String', 'state']] + break + + case 'getAllStatesSince': + case 'getAllStatesUntil': + case 'getAllStatesBetween': + returnTypes = [['String', 'state'], + ['Quantity', 'quantityState'], + ['Number', 'numericState']] + break + + default: + break + } + return returnTypes } } @@ -126,64 +264,78 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe const inputType = blockGetCheckedInputType(block, 'itemName') const methodName = block.getFieldValue('methodName') + const returnTypeName = block.getFieldValue('returnTypeName') const persistenceName = javascriptGenerator.valueToCode(block, 'persistenceName', javascriptGenerator.ORDER_NONE) const persistence = (isGraalJs) ? null : addPersistence() const itemCode = generateItemCode(itemName, inputType) let code = '' - let dayInfo = '' + const dayInfoSince = javascriptGenerator.valueToCode(block, 'dayInfoSince', javascriptGenerator.ORDER_NONE) + const dayInfoUntil = javascriptGenerator.valueToCode(block, 'dayInfoUntil', javascriptGenerator.ORDER_NONE) + const dayInfo = dayInfoSince + ((dayInfoSince && dayInfoUntil) ? ' ,' : '') + dayInfoUntil let skipPrevious = javascriptGenerator.valueToCode(block, 'skipPrevious', javascriptGenerator.ORDER_NONE) skipPrevious = ((skipPrevious === 'undefined') ? false : skipPrevious) const persistenceExtension = (persistenceName === '\'default\'') ? '' : `, ${persistenceName}` switch (methodName) { - // Returning JS PersistedItem (GraalJS) or org.openhab.core.persistence.HistoricItem + // Returning JS PersistedItem mapped to return type (GraalJS) or org.openhab.core.persistence.HistoricItem + case 'persistedState': case 'maximumSince': case 'maximumUntil': + case 'maximumBetween': case 'minimumSince': case 'minimumUntil': - case 'persistedState': - dayInfo = javascriptGenerator.valueToCode(block, 'dayInfo', javascriptGenerator.ORDER_NONE) - code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${dayInfo}${persistenceExtension})?.state` : `${persistence}.${methodName}(${itemCode}, ${dayInfo}${persistenceExtension}).getState()` + case 'minimumBetween': + code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${dayInfo}${persistenceExtension})?.${returnTypeName}` : `${persistence}.${methodName}(${itemCode}, ${dayInfo}${persistenceExtension}).getState()` break case 'previousState': case 'nextState': - code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${skipPrevious}${persistenceExtension})?.state` : `${persistence}.${methodName}(${itemCode},${skipPrevious}${persistenceExtension}).getState()` + code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${skipPrevious}${persistenceExtension})?.${returnTypeName}` : `${persistence}.${methodName}(${itemCode}, ${skipPrevious}${persistenceExtension}).getState()` break case 'previousNumericState': - code = (isGraalJs) ? `${itemCode}.persistence.previousState(${skipPrevious}${persistenceExtension})?.numericState` : `${persistence}.previousState(${itemCode},${skipPrevious}${persistenceExtension}).getNumericState()` + code = (isGraalJs) ? `${itemCode}.persistence.previousState(${skipPrevious}${persistenceExtension})?.numericState` : `${persistence}.previousState(${itemCode}, ${skipPrevious}${persistenceExtension}).getNumericState()` break case 'nextNumericState': - code = (isGraalJs) ? `${itemCode}.persistence.nextState(${skipPrevious}${persistenceExtension})?.numericState` : `${persistence}.nextState(${itemCode},${skipPrevious}${persistenceExtension}).getNumericState()` + code = (isGraalJs) ? `${itemCode}.persistence.nextState(${skipPrevious}${persistenceExtension})?.numericState` : `${persistence}.nextState(${itemCode}, ${skipPrevious}${persistenceExtension}).getNumericState()` break case 'previousStateTime': - code = (isGraalJs) ? `${itemCode}.persistence.previousState(${skipPrevious}${persistenceExtension})?.timestamp` : `${persistence}.previousState(${itemCode},${skipPrevious}${persistenceExtension}).getTimestamp()` + code = (isGraalJs) ? `${itemCode}.persistence.previousState(${skipPrevious}${persistenceExtension})?.timestamp` : `${persistence}.previousState(${itemCode}, ${skipPrevious}${persistenceExtension}).getTimestamp()` break case 'nextStateTime': - code = (isGraalJs) ? `${itemCode}.persistence.nextState(${skipPrevious}${persistenceExtension})?.timestamp` : `${persistence}.nextState(${itemCode},${skipPrevious}${persistenceExtension}).getTimestamp()` + code = (isGraalJs) ? `${itemCode}.persistence.nextState(${skipPrevious}${persistenceExtension})?.timestamp` : `${persistence}.nextState(${itemCode}, ${skipPrevious}${persistenceExtension}).getTimestamp()` break - // Returning JS PersistedState (GraalJS) or org.openhab.core.types.State + // Returning JS PersistedState mapped to return type (GraalJS) or org.openhab.core.types.State cast to float case 'averageSince': case 'averageUntil': + case 'averageBetween': case 'deltaSince': case 'deltaUntil': + case 'deltaBetween': case 'deviationSince': case 'deviationUntil': + case 'deviationBetween': case 'sumSince': case 'sumUntil': + case 'sumBetween': case 'varianceSince': case 'varianceUntil': - dayInfo = javascriptGenerator.valueToCode(block, 'dayInfo', javascriptGenerator.ORDER_NONE) - code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${dayInfo}${persistenceExtension})?.numericState` : `parseFloat(${persistence}.${methodName}(${itemCode}, ${dayInfo}${persistenceExtension}).getState())` + case 'varianceBetween': + code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${dayInfo}${persistenceExtension})?.${returnTypeName}` : `parseFloat(${persistence}.${methodName}(${itemCode}, ${dayInfo}${persistenceExtension}).getState())` + break + + // Returning JS Array of timestamp and state pairs, whereby PersistedState is mapped to return type (GraalJS) or org.openhab.core.persistence.HistoricItem + case 'getAllStatesSince': + case 'getAllStatesUntil': + case 'getAllStatesBetween': + code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${dayInfo}${persistenceExtension}).map(v => ([v.timestamp, v.${returnTypeName}]))` : `${persistence}.${methodName}(${itemCode}, ${dayInfo}${persistenceExtension}.map(v => ([v.getTimestamp(), v.getState()]))` break default: - dayInfo = javascriptGenerator.valueToCode(block, 'dayInfo', javascriptGenerator.ORDER_NONE) code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${dayInfo}${persistenceExtension})` : `${persistence}.${methodName}(${itemCode}, ${dayInfo}${persistenceExtension})` break } @@ -208,9 +360,13 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe persistenceNameInput.setShadowDom(Blockly.utils.xml.textToDom('')) } this.appendValueInput('dayInfo') - .appendField(new Blockly.FieldDropdown([['has changed since', 'changedSince'], ['will have changed until', 'changedUntil'], ['has been updated since', 'updatedSince'], ['will have been updated until', 'updatedUntil']]), 'methodName') + .appendField(new Blockly.FieldDropdown([ + ['has changed since', 'changedSince'], ['will have changed until', 'changedUntil'], ['changes between', 'changedBetween'], + ['has been updated since', 'updatedSince'], ['will have been updated until', 'updatedUntil'], ['is updated between', 'updatedBetween'] + ], this.handleTypeSelection.bind(this)), 'methodName') .setAlign(Blockly.ALIGN_RIGHT) .setCheck(['ZonedDateTime']) + this.methodName = this.getFieldValue('methodName') this.setInputsInline(false) this.setOutput(true, null) @@ -222,13 +378,42 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe let TIP = { 'changedSince': 'Checks if the State of the Item has (ever) changed since a certain point in time', 'changedUntil': 'Checks if the State of the Item will have (ever) changed until a certain point in time', + 'changedBetween': 'Checks if the State of the Item will have (ever) changed between two points in time', 'updatedSince': 'Checks if the State of the Item has been updated since a certain point in time', - 'updatedUntil': 'Checks if the State of the Item will have been updated until a certain point in time' + 'updatedUntil': 'Checks if the State of the Item will have been updated until a certain point in time', + 'updatedBetween': 'Checks if the State of the Item will have been updated between two points in time' } return TIP[methodName] }) this.setHelpUrl('https://www.openhab.org/docs/configuration/blockly/rules-blockly-persistence.html#check-item-change-update-since-a-point-in-time') + }, + handleTypeSelection: function (methodName) { + if (this.methodName !== methodName) { + this.methodName = methodName + this.updateShape() + } + }, + updateShape: function () { + if (this.methodName.endsWith('Between')) { + if (!this.getInput('dayInfo2')) { + this.appendValueInput('dayInfo2') + .appendField('and') + .setCheck(['ZonedDateTime']) + this.getInput('dayInfo2').setShadowDom( + Blockly.utils.xml.textToDom(` + + + 1 + + + Hours + plus + `)) + } + } else if (this.getInput('dayInfo2')) { + this.removeInput('dayInfo2') + } } } @@ -241,7 +426,9 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe const inputType = blockGetCheckedInputType(block, 'itemName') const methodName = block.getFieldValue('methodName') - const dayInfo = javascriptGenerator.valueToCode(block, 'dayInfo', javascriptGenerator.ORDER_NONE) + const dayInfo1 = javascriptGenerator.valueToCode(block, 'dayInfo', javascriptGenerator.ORDER_NONE) + const dayInfo2 = methodName.endsWith('Between') ? javascriptGenerator.valueToCode(block, 'dayInfo2', javascriptGenerator.ORDER_NONE) : undefined + const dayInfo = dayInfo2 ? `${dayInfo1}, ${dayInfo2}` : dayInfo1 const persistenceName = javascriptGenerator.valueToCode(block, 'persistenceName', javascriptGenerator.ORDER_NONE) const persistenceExtension = (persistenceName === '\'default\'') ? '' : `, ${persistenceName}` @@ -264,10 +451,11 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe init: function () { this.appendDummyInput() .appendField(new Blockly.FieldDropdown([ - ['last', 'lastUpdate'], ['next', 'nextUpdate'] + ['last updated', 'lastUpdate'], ['next updated', 'nextUpdate'], + ['last changed', 'lastChange'], ['next changed', 'nextChange'] ]), 'methodName') this.appendDummyInput() - .appendField(' updated date of') + .appendField(' date of') this.appendValueInput('itemName') .setCheck(['String', 'oh_item', 'oh_itemtype']) const persistenceNameInput = this.appendValueInput('persistenceName') @@ -285,8 +473,10 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe this.setTooltip(() => { const methodName = this.getFieldValue('methodName') const TIP = { - 'lastUpdate': 'Get the last update time of the provided item', - 'nextUpdate': 'Get the next update time of the provided item' + 'lastUpdate': 'Get the last update time of the provided item (null if the item state changed since last being persisted)', + 'nextUpdate': 'Get the next update time of the provided item', + 'lastChange': 'Get the last changed time of the provided item (null if the item state changed since last being persisted)', + 'nextChange': 'Get the next changed time of the provided item' } return TIP[methodName] }) @@ -304,7 +494,7 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe const persistenceName = javascriptGenerator.valueToCode(block, 'persistenceName', javascriptGenerator.ORDER_NONE) const persistenceExtension = (persistenceName === '\'default\'') ? '' : ((!isGraalJs) ? ',' : '') + ` ${persistenceName}` - let itemCode = generateItemCode(itemName, inputType) + const itemCode = generateItemCode(itemName, inputType) if (isGraalJs) { return [`${itemCode}.persistence.${methodName}(${persistenceExtension})`, 0] @@ -316,6 +506,299 @@ export default function defineOHBlocks_Persistence (f7, isGraalJs, persistenceSe } } + /* + * Persist a state or list of states + * Blockly part + */ + Blockly.Blocks['oh_persist'] = { + init: function () { + const statesInput = this.appendValueInput('states') + .appendField('persist') + .appendField(new Blockly.FieldDropdown([ + ['state (at current time)', 'currentState'], ['state (at specific time)', 'stateAt'], ['list of states (adding)', 'statesListADD'], ['list of states (replacing)', 'statesListREPLACE'] + ], this.handleTypeSelection.bind(this)), 'persistType') + .setCheck(['String', 'Array']) + statesInput.setShadowDom( + Blockly.utils.xml.textToDom(` + state + `)) + this.appendValueInput('itemName') + .appendField('for item') + .setAlign(Blockly.ALIGN_RIGHT) + .setCheck(['String', 'oh_item', 'oh_itemtype']) + const persistenceNameInput = this.appendValueInput('persistenceName') + .appendField('to') + .setCheck(null) + if (!persistenceNameInput.getShadowDom()) { + persistenceNameInput.setShadowDom(Blockly.utils.xml.textToDom('')) + } + + this.setInputsInline(false) + this.setColour(0) + + this.setTooltip(() => { + const persistType = this.getFieldValue('persistType') + const TIP = { + 'currentState': 'Persist a state to Item Persistence at current time (this does not update the state of the item)', + 'stateAt': 'Persist a state to Item Persistence at a given point in time', + 'statesListADD': 'Persist a list of timestamp and state pairs to Item Persistence, update/add to existing persisted states', + 'statesListREPLACE': 'Persist a list of timestamp and state pairs to Item Persistence, replace all persisted states between earlies and latest of new states' + } + return TIP[persistType] + }) + this.setHelpUrl('https://www.openhab.org/docs/configuration/blockly/rules-blockly-persistence.html#persist-item') + + this.setPreviousStatement(true, null) + this.setNextStatement(true, null) + }, + handleTypeSelection: function (persistType) { + if (this.persistType !== persistType) { + this.persistType = persistType + this.updateShape() + } + }, + updateShape: function () { + const persistenceNameInput = this.getInput('persistenceName') + if (!persistenceNameInput.getShadowDom()) { + persistenceNameInput.setShadowDom(Blockly.utils.xml.textToDom('')) + } + + const hasAtField = (this.persistType === 'stateAt') + if (this.getInput('at') && !hasAtField) { + this.removeInput('at') + } + if (hasAtField && !this.getInput('at')) { + this.appendValueInput('at') + .appendField('at') + .setCheck(['ZonedDateTime']) + this.getInput('at').setShadowDom( + Blockly.utils.xml.textToDom(` + + + 1 + + + Hours + minus + `)) + this.moveInputBefore('at', 'persistenceName') + } + + const hasStatesList = this.persistType.startsWith('statesList') + const statesInput = this.getInput('states') + if (hasStatesList) { + statesInput.setShadowDom( + Blockly.utils.xml.textToDom(` + + + + + 1 + Hours + plus + + state + + + + + 2 + Hours + plus + + state + + `)) + } else { + statesInput.setShadowDom( + Blockly.utils.xml.textToDom(` + state + `)) + } + } + } + + /* + * Persist a state or list of states + * Code part + */ + javascriptGenerator.forBlock['oh_persist'] = function (block) { + const itemName = javascriptGenerator.valueToCode(block, 'itemName', javascriptGenerator.ORDER_ATOMIC) + const inputType = blockGetCheckedInputType(block, 'itemName') + const itemCode = generateItemCode(itemName, inputType) + + const persistType = block.getFieldValue('persistType') + + const states = javascriptGenerator.valueToCode(block, 'states', javascriptGenerator.ORDER_ATOMIC) + const at = javascriptGenerator.valueToCode(block, 'at', javascriptGenerator.ORDER_NONE) + const policy = persistType.endsWith('REPLACE') ? 'REPLACE' : 'ADD' + + const persistence = (isGraalJs) ? null : addPersistence() + const persistenceName = javascriptGenerator.valueToCode(block, 'persistenceName', javascriptGenerator.ORDER_NONE) + const persistenceExtension = (persistenceName === '\'default\'') ? '' : `, ${persistenceName}` + + let code = '' + switch (persistType) { + case 'currentState': + code += isGraalJs ? `${itemCode}.persistence.persist(${states}${persistenceExtension});` : `${persistence}.persist(${itemCode}, ${states}${persistenceExtension});` + break + case 'stateAt': + code += isGraalJs ? `${itemCode}.persistence.persist(${at}, ${states}${persistenceExtension});` : `${persistence}.persist(${itemCode}, ${at}, ${states}${persistenceExtension});` + break + case 'statesListADD': + case 'statesListREPLACE': + const timeSeriesVar = javascriptGenerator.nameDB_.getDistinctName('timeSeries', Blockly.Names.NameType.VARIABLE) + code += `var ${timeSeriesVar} = new items.TimeSeries('${policy}');\n` + code += `${states}.forEach(s => ${timeSeriesVar}.add(s[0], s[1]));\n` + code += isGraalJs ? `${itemCode}.persistence.persist(timeSeries${persistenceExtension});` : `${persistence}.persist(${itemCode}, timeSeries${persistenceExtension});` + break + default: + break + } + code += '\n' + return code + } + + /* + * Delete persisted values for an item + * Blockly part + */ + Blockly.Blocks['oh_delete_persistedvalues'] = { + init: function () { + this.appendDummyInput() + .appendField('remove') + .appendField(new Blockly.FieldDropdown([ + ['all states since', 'removeAllStatesSince'], ['all states until', 'removeAllStatesUntil'], ['all states between', 'removeAllStatesBetween'] + ], this.handleTypeSelection.bind(this) + ), 'methodName') + this.methodName = this.getFieldValue('methodName') + this.appendValueInput('itemName') + .appendField('of item') + .setAlign(Blockly.ALIGN_RIGHT) + .setCheck(['String', 'oh_item', 'oh_itemtype']) + this.appendValueInput('persistenceName') + .appendField('from') + .setCheck(null) + this.updateShape() + this.setInputsInline(false) + this.setColour(0) + + this.setTooltip(() => { + const methodName = this.getFieldValue('methodName') + const TIP = { + 'removeAllStatesSince': 'Delete all persisted states of an Item since a certain point in time', + 'removeAllStatesUntil': 'Delete all persisted states of an Item until a certain point in time', + 'removeAllStatesBetween': 'Delete all persisted states of an Item between two points in time' + } + return TIP[methodName] + }) + this.setHelpUrl('https://www.openhab.org/docs/configuration/blockly/rules-blockly-persistence.html#remove_persisted_states_for_an_item') + + this.setPreviousStatement(true, null) + this.setNextStatement(true, null) + }, + handleTypeSelection: function (methodName) { + if (this.methodName !== methodName) { + this.methodName = methodName + this.updateShape() + } + }, + updateShape: function () { + const persistenceNameInput = this.getInput('persistenceName') + if (!persistenceNameInput.getShadowDom()) { + persistenceNameInput.setShadowDom(Blockly.utils.xml.textToDom('')) + } + + let hasSinceField = this.methodName.endsWith('Since') || this.methodName.endsWith('Between') + let hasUntilField = this.methodName.endsWith('Until') || this.methodName.endsWith('Between') + + if (this.getInput('dayInfoSince') && !hasSinceField) { + this.removeInput('dayInfoSince') + } + if (this.getInput('dayInfoUntil') && !hasUntilField) { + this.removeInput('dayInfoUntil') + } + + const prepositionSince = this.methodName.endsWith('Since') ? 'since' : 'between' + const prepositionUntil = this.methodName.endsWith('Until') ? 'until' : 'and' + + if (hasSinceField) { + if (!this.getInput('dayInfoSince')) { + this.appendValueInput('dayInfoSince') + .appendField(prepositionSince, 'prepositionSince') + .setCheck(['ZonedDateTime']) + this.getInput('dayInfoSince').setShadowDom( + Blockly.utils.xml.textToDom(` + + + 1 + + + Hours + minus + `)) + if (this.getInput('dayInfoUntil')) { + this.moveInputBefore('dayInfoSince', 'dayInfoUntil') + } else { + this.moveInputBefore('dayInfoSince', 'persistenceName') + } + } else { + const prepositionField = this.getField('prepositionSince') + if (prepositionField.getText() !== prepositionSince) { + prepositionField.setValue(prepositionSince) + } + } + } + + if (hasUntilField) { + if (!this.getInput('dayInfoUntil')) { + this.appendValueInput('dayInfoUntil') + .appendField(prepositionUntil, 'prepositionUntil') + .setCheck(['ZonedDateTime']) + this.getInput('dayInfoUntil').setShadowDom( + Blockly.utils.xml.textToDom(` + + + 1 + + + Hours + plus + `)) + this.moveInputBefore('dayInfoUntil', 'persistenceName') + } else { + const prepositionField = this.getField('prepositionUntil') + if (prepositionField.getText() !== prepositionUntil) { + prepositionField.setValue(prepositionUntil) + } + } + } + } + } + + /* + * Delete persisted values for an item + * Code part + */ + javascriptGenerator.forBlock['oh_delete_persistedvalues'] = function (block) { + const itemName = javascriptGenerator.valueToCode(block, 'itemName', javascriptGenerator.ORDER_ATOMIC) + const inputType = blockGetCheckedInputType(block, 'itemName') + const itemCode = generateItemCode(itemName, inputType) + + const methodName = block.getFieldValue('methodName') + + const dayInfoSince = javascriptGenerator.valueToCode(block, 'dayInfoSince', javascriptGenerator.ORDER_NONE) + const dayInfoUntil = javascriptGenerator.valueToCode(block, 'dayInfoUntil', javascriptGenerator.ORDER_NONE) + const dayInfo = dayInfoSince + ((dayInfoSince && dayInfoUntil) ? ' ,' : '') + dayInfoUntil + + const persistence = (isGraalJs) ? null : addPersistence() + const persistenceName = javascriptGenerator.valueToCode(block, 'persistenceName', javascriptGenerator.ORDER_NONE) + const persistenceExtension = (persistenceName === '\'default\'') ? '' : `, ${persistenceName}` + + const code = (isGraalJs) ? `${itemCode}.persistence.${methodName}(${dayInfo}${persistenceExtension});\n` : `${persistence}.${methodName}(${itemCode}, ${dayInfo}${persistenceExtension});\n` + return code + } + function generateItemCode (itemName, inputType) { if (isGraalJs) { return (inputType === 'oh_item' || inputType === 'String') ? `items.getItem(${itemName})` : `${itemName}` 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 2401ea2927..6a2d0e84c5 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 @@ -921,7 +921,7 @@ - + @@ -954,6 +954,27 @@ + + + + + + + + + + + + + + 1 + + + Hours + minus + + +