combination of MQTT/HTTP/... plugin with javascript
Is your feature request related to a problem? Please describe. I want to transform values after reading them from MQTT/HTTP and before sending them over MQTT/HTTP. I want to have the flexibility to transform data for all kinds of source and target systems.
Describe the solution you'd like I would like to have the possibility to combine the MQTT/HTTP/... plugins with the javascript plugin, in such a way that values read from the source plugin are send through javascript before being passed to evcc and values send from evcc are being transformed before being send to the target plugin.
Describe alternatives you've considered For reading, there is the calc plugin, but it is very limited and the jq transformations. For writing there is the no possibility to convert values.
Additional context Example configuration syntax I could imagine:
charger:
- type: custom
maxcurrent:
source: js
script: |
maxcurrent*1000
output:
source: mqtt
topic: mymqtt/topic/set
This would multiply the max current with 1000 and then send it to MQTT.
charger:
- type: custom
enabled:
source: js
script: |
value>1000 && value <2000
input:
source: mqtt
topic: mymqtt/topic
This would query the mymqtt/topic and transform the numeric value to a boolean by checking if it matches the javascript expression before sending it to evcc.
An alternative syntax could be the following:
charger:
- type: custom
maxcurrent:
source: mqtt
topic: mymqtt/topic/set
transform:
source: js
script: |
maxcurrent*1000 // or better a generic "value" variable?
charger:
- type: custom
enabled:
source: mqtt
topic: mymqtt/topic
transform:
source: js
script: |
value>1000 && value <2000
I personally like the 2nd syntax more, but the first syntax is more in line how the Calc and CombinedStatus plugins work.
I understand you want to have a separate, configurable transform step that is able to convert values. I feel this can already be done externally using home automation by providing converted values as exepected.
Yes, that is what I want.
I can do it in my external home automation system, but I would like to have the most direct connection between my different systems, that's possible. So if I can get some in-between system out of the way, I would like to do so.
Another benefit, that I would see for evcc is, easier prototyping. If someone wants to add a new system, one can use the existing HTTP/MQTT plugins and combine them with a bit of javascript to get a working demonstrator, before going all the way of providing a native go implementation.
Anyway, would you accept a PR that implements this? Or would this be something, that you consider out of scope for evcc?
If you are open for a PR, I would have a go (pun intended) at it, as I always wanted to try go out, but had never a real use case. If not, that's also totally fine for me. 😄
Looking at the code, all getters/setters are created from provider.Config. You're adding another layer of indirection by requiring a provider (or its user) to go through transform which is another provider. I'd be interested in a PR if you have a good idea how to do this elegantly without duplicating code. This should apply to meters and vehicles, too.
Btw: it would also be nice to have golang plugins using https://github.com/traefik/yaegi, but that's another topic.
I played around a bit with the code and came up with another solution:
I have changed the javacript plugin, to support getting values from other plugins, if the js plugin is used as a getter. For example:
meters:
- name: grid
type: custom
power:
source: js
vm: shared
script: |
state.gridpower = state.loadpoints[0].chargepower + state.loadpoints[1].chargepower + state.residualpower - get_1 - get_0;
state.gridpower;
get:
- source: js
vm: shared
script: |
state.pvpower = 8000+500*Math.random();
console.log("state.pvpower:", state.pvpower);
state.pvpower;
- source: js
vm: shared
script: |
state.batterypower = state.gridpower > 0 ? 1000 * Math.random() : 0;
console.log("state.batterypower:", state.batterypower);
state.batterypower;
the variables get_0 and get_1 in the outer javascript will be set to the values provided by the getters of the nested plugins.
For setters:
chargers:
- name: charger_1
type: custom
enable:
source: js
vm: shared
script: |
console.log("Outer Set!!!");
set();
var lp = state.loadpoints[0];
lp.enabled = val;
if (lp.enabled) ret = lp.maxcurrent * 230 * lp.phases; else ret = 0;
ret;
set:
source: js
vm: shared
script: |
console.log("Inner Set!!!");
set();
lp.chargepower = val
The val of the inner plugin is set to the return vlaue of the outer javascript plugin.
This way all "normal" plugins can be left unchanged, and only "transformation" plugins need to get extended with this functionality.
The code is probably not very good and buggy, just wanted something for a first proof of concept.
Is this a direction you would find suitable for evcc?
Discussing the implementation should probably continue in #5730