evcc icon indicating copy to clipboard operation
evcc copied to clipboard

combination of MQTT/HTTP/... plugin with javascript

Open tisoft opened this issue 3 years ago • 5 comments

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.

tisoft avatar Jan 06 '23 07:01 tisoft

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.

andig avatar Jan 06 '23 11:01 andig

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. 😄

tisoft avatar Jan 06 '23 20:01 tisoft

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.

andig avatar Jan 08 '23 13:01 andig

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?

tisoft avatar Jan 09 '23 07:01 tisoft

Discussing the implementation should probably continue in #5730

tisoft avatar Jan 09 '23 08:01 tisoft