Skip to content
This repository has been archived by the owner on Feb 24, 2021. It is now read-only.

feat: parse value functions #739 #741

Merged
merged 6 commits into from
Sep 25, 2020
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ The Gateway values table can be used with all gateway types to customize specifi
- **Post operation**: If you want to convert your value (eg. '/10' '/100' '*10' '*100')
- **Poll**: Enable this to set the value `enablePoll` flag
- **Verify Changes**: Used to verify changes of this values
- **Parse Send**: Enable this to allow users to specify a custom `function(value)` to parse the value sent to MQTT. The function must be sync
- **Parse receive**: Enable this to allow users to specify a custom `function(value)` to parse the value received via MQTT. The function must be sync

## :file_folder: Nodes Management

Expand Down
64 changes: 54 additions & 10 deletions lib/Gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,17 @@ function onValueChanged (valueId, node, changed) {
// Parse valueId value and create the payload
var tmpVal = valueId.value

if (valueConf && isValidOperation(valueConf.postOperation)) {
tmpVal = eval(valueId.value + valueConf.postOperation)
if (valueConf) {
if (isValidOperation(valueConf.postOperation)) {
tmpVal = eval(valueId.value + valueConf.postOperation)
}

if (valueConf.parseSend) {
var parsedVal = evalFunction(valueConf.sendFunction, valueId, tmpVal)
if (parsedVal != null) {
tmpVal = parsedVal
}
}
}

// Check if I need to update discovery topics of this device
Expand Down Expand Up @@ -430,6 +439,28 @@ function isValidOperation (op) {
return op && !/[^0-9.()\-+*/,]/g.test(op)
}

/**
* Evaluate the return value of a custom parse Function
*
* @param {String} code The function code
* @param {Object} valueId The valueId object
* @param {*} value The actual value to parse
* @returns
*/
function evalFunction (code, valueId, value) {
var result = null

try {
/* eslint-disable no-new-func */
var parseFunc = new Function('value', code)
result = parseFunc(value)
} catch (error) {
debug('Error eval function of value ', valueId.value_id, error.message)
}

return result
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is returning null on failure better than returning the original value?
can you return a promise? does it work?

Copy link
Member Author

@robertsLando robertsLando Sep 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works in this way (I have tested this with a real device). I would not return a promise as the conversion function should be fast.

I could return the original value when it fails but I think this makes the code more readable, I mean If I return the original value, by reading the code seems that the function has return that value. What do you think it's better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

promises can be fast, the question is to whether it has to be sync or can it be async.
either is probably fine, but i'd make that clear in the gui that it must be a syncronis response.
theres technically no reason not to do async/promises, but i'm sure it could introduce all sorts of peculiar race conditions

}

/**
* Converts an integer to 2 digits hex number
*
Expand Down Expand Up @@ -633,16 +664,29 @@ Gateway.prototype.parsePayload = function (payload, valueId, valueConf) {
}
}

if (valueConf && isValidOperation(valueConf.postOperation)) {
let op = valueConf.postOperation
if (valueConf) {
if (isValidOperation(valueConf.postOperation)) {
let op = valueConf.postOperation

// revert operation to write
if (op.includes('/')) op = op.replace(/\//, '*')
else if (op.includes('*')) op = op.replace(/\*/g, '/')
else if (op.includes('+')) op = op.replace(/\+/, '-')
else if (op.includes('-')) op = op.replace(/-/, '+')

// revert operation to write
if (op.includes('/')) op = op.replace(/\//, '*')
else if (op.includes('*')) op = op.replace(/\*/g, '/')
else if (op.includes('+')) op = op.replace(/\+/, '-')
else if (op.includes('-')) op = op.replace(/-/, '+')
payload = eval(payload + op)
}

payload = eval(payload + op)
if (valueConf.parseReceive) {
var parsedVal = evalFunction(
valueConf.receiveFunction,
valueId,
payload
)
if (parsedVal != null) {
payload = parsedVal
}
}
}
} catch (error) {
debug('Error while parsing payload', payload, 'for valueID', valueId)
Expand Down
51 changes: 51 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"native-url": "^0.3.4",
"nedb": "^1.8.0",
"openzwave-shared": "^1.7.1",
"prismjs": "^1.21.0",
"serialport": "^9.0.1",
"serve-favicon": "^2.5.0",
"socket.io": "^2.3.0",
Expand All @@ -116,6 +117,7 @@
"uniqid": "^5.2.0",
"vue": "^2.6.12",
"vue-d3-network": "^0.1.28",
"vue-prism-editor": "^1.2.2",
"vue-router": "^3.4.3",
"vuetify": "^2.3.10",
"vuex": "^3.5.1"
Expand Down
67 changes: 67 additions & 0 deletions src/components/dialogs/DialogGatewayValue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,50 @@
v-model="editedValue.verifyChanges"
></v-switch>
</v-flex>

<v-flex xs6>
<v-switch
label="Parse send"
hint="Create a function that parse the value sent via MQTT"
persistent-hint
v-model="editedValue.parseSend"
></v-switch>
</v-flex>

<v-container v-if="editedValue.parseSend">
<p>
Write the function here. Args are <code>value</code>. The
function is sync and must return the parsed <code>value</code>
</p>
<prism-editor
lineNumbers
v-model="editedValue.sendFunction"
language="js"
:highlight="highlighter"
></prism-editor>
</v-container>

<v-flex xs6>
<v-switch
label="Parse receive"
hint="Create a function that parse the received value from MQTT"
persistent-hint
v-model="editedValue.parseReceive"
></v-switch>
</v-flex>

<v-container v-if="editedValue.parseReceive">
<p>
Write the function here. Args are <code>value</code>. The
function is sync and must return the parsed <code>value</code>
</p>
<prism-editor
lineNumbers
v-model="editedValue.receiveFunction"
language="js"
:highlight="highlighter"
></prism-editor>
</v-container>
</v-layout>
</v-form>
</v-container>
Expand All @@ -134,7 +178,20 @@
</template>

<script>
// import Prism Editor
import { PrismEditor } from 'vue-prism-editor'
import 'vue-prism-editor/dist/prismeditor.min.css' // import the styles somewhere

// import highlighting library (you can use any library you want just return html string)
import { highlight, languages } from 'prismjs/components/prism-core'
import 'prismjs/components/prism-clike'
import 'prismjs/components/prism-javascript'
import 'prismjs/themes/prism-tomorrow.css' // import syntax highlighting styles

export default {
components: {
PrismEditor
},
props: {
value: Boolean,
gw_type: Number,
Expand Down Expand Up @@ -220,9 +277,19 @@ export default {
}
},
methods: {
highlighter (code) {
return highlight(code, languages.js) // returns html
},
isSensor (v) {
return v && (v.class_id === 0x31 || v.class_id === 0x32)
}
}
}
</script>

<style>
/* optional class for removing the outline */
.prism-editor__textarea:focus {
outline: none;
}
</style>