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

❇️ Smart charging demo at CharIN #44

Closed
shankari opened this issue May 23, 2024 · 58 comments
Closed

❇️ Smart charging demo at CharIN #44

shankari opened this issue May 23, 2024 · 58 comments

Comments

@shankari
Copy link
Collaborator

In a few short weeks, we are going to try to pull together a demo for CharIN, to be held in Cleveland from June 11-14. Given that we have been working on smart charging recently, the goal is to try and demo basic, end to end smart charging.

So this flow diagram:

FlowDiagramAsSequence-1
@shankari
Copy link
Collaborator Author

@drmrd @the-bay-kay @Giavotto @louisg1337 based on the discussion today, I think that this is the list of questions that we need to answer before the demo. At least a one of them will result in fallback plans, and I think there are at least two tasks for the OCPP smart charging implementation in here. Please let me know if I missed anything, and we can divvy up the tasks between NREL and AFS

To figure out whether we can do this demo, we need to determine the following:

  • How do we sent a message to the CSMS asking it to limit the power consumed?
    • Does the CSMS that we use (MaEVe) support IEEE 2030.5?
    • If not, is there a REST API that we can use to set the limit?
  • Does the car support sending a ISO 15118-2 ChargeParameterDiscoveryReq?
    • For the SIL, does the simulator support sending this?
    • what about departure time? note that per page 76, DepartureTime is not required and the EVSE can assume that the EV can start charging immediately if it is omitted. However, note also that V2G2-303 indicates that the time interval of the PMaxSchedule should match the departure time indicated. Not sure what it should (or will) do if the departure time is omitted
  • Does the OCPP 201 implementation support NotifyEVChargingNeedsRequest?
    • I assume not; that is defined in K15 which is currently unimplemented
    • If not, can we fall back to K02 or Section 3.1. ("Outside the context of a transaction as a separate message to set a charging profile to a local controller, Charging Station, or a default charging profile to an EVSE.")? (@the-bay-kay, once we figure it out, we can draw out the final diagram)
    • at a high level, somehow, the station OCPP 201 implementation needs to receive a charge profile from the CSMS and store it
  • When does MaEVe support setting the charging profile?
  • Can EVerest currently send the ISO 15118-2 ChargeParameterDiscoveryRes with a SAScheduleList containing a single item with a PMaxSchedule set (consistent with V2G2-301) based on the stored charge profile?
    • I suspect not, and this is the "hook up" we need to do
  • Does the current ISO 15118-2 implementation support PowerDeliveryReq/Res linked to a transaction?

@Giavotto
Copy link

If Departure Time is omitted and the EVSE is attempting to run a smart charging session according to K15, the EVSE should provide a schedule greater than or equal to 24 hours.

Check out [V2G2-304]:
If the EVCC did not provide a DepartureTime Target Setting (refer to subclause 8.4.3.8.2 and 8.5.3.2) in the ChargeParameterDiscoveryReq message, the sum of the individual time intervals described in the PMaxSchedule and SalesTariff provided in the ChargeParameterDiscoveryRes message, shall be greater or equal to 24 hours.

@shankari
Copy link
Collaborator Author

I read V2G2-303, should have gone on to V2G2-304 😄

@shankari
Copy link
Collaborator Author

@shankari
Copy link
Collaborator Author

It looks like ISO 151180-2 is at least sending the correct messages - I see a ChargeParameterDiscovery Req/Res pair

2024-05-16 14:58:32.319347 [INFO] evse_manager_1:  :: CAR ISO V2GChargeParameterDiscoveryReq
2024-05-16 14:58:32.376123 [INFO] evse_manager_1:  :: EVSE ISO V2G ChargeParameterDiscoveryRes


2024-05-23 17:38:26.930783 [INFO] evse_manager_1:  :: CAR ISO V2G PowerDeliveryReq
2024-05-23 17:38:26.973277 [INFO] evse_manager_1:  :: EVSE ISO V2G PowerDeliveryRes

To see what is in the message, we can look at the code, or pull in the EXI message logging from the ISO 15118-2 DC demo, or see if we can enable EXI logging/decoding through EVerest @louisg1337

On the OCPP side, though, it looks like the sequence is

Receive V2GChargeParameterDiscoveryReq
Send TransactionEvent with "eventType": "Started" and a bunch of meter values
Send TransactionEvent with "eventType": "Updated" and a charging state of "Charging"

There is no NotifyXXX message sent, and no ChargingProfile set from the CSMS.

@drmrd @Giavotto @louisg1337
One potential scaled back but hacked together demo might be:

  • hack EVerest to send the Notify..Needs message when it receives V2GChargeParameterDiscoveryReq
  • hack the CSMS to, when it receives the message, send a hardcoded SetChargingProfile with a low Pmax
  • plumb that through to the V2GChargeParameterDiscoveryRes as planned yesterday

@louisg1337 can you figure out what is in the current EXI messages for V2GChargeParameterDiscoveryReq and V2GChargeParameterDiscoveryRes and what else we can send?

@shankari will send out a message to Citrine about their smart charging support and potential for hacking

@shankari
Copy link
Collaborator Author

The current EXI messages are logged at /tmp/everest-logs/2024-05-23.../incomplete-eventlog.html on the manager.
I found a decoder online (http://exificient.github.io/javascript/) but it requires a grammar. @drmrd I know that, when we were doing the demos to stakeholders last year, you used an EXI decoder. Can you provide instructions on what it is, and how to use it to decode messages? I don't think we documented it at the time.

@louisg1337
Copy link
Contributor

OpenChargingCloud CSMS

OCPP 2.0.1
In the repo it says that 2.0.1 is fully implemented and that they aren't maintaining 2.0.1 anymore and are instead focusing on developing 2.1. It does seem a bit unclear though if that is the case. I keep on finding places where things are defined, like SetChargingProfile or Notify*** but I can't seem to find where else they are used.

OCPP 2.1
I'm not sure if we want to use the newer version but it seems like stuff is actually being used here.

@louisg1337
Copy link
Contributor

Even though CitrineOS lacks some features that we want, they at least have some way to set a charging profile and it seems like it can be set using a REST API.

@shankari
Copy link
Collaborator Author

@louisg1337 couple of notes:

@shankari
Copy link
Collaborator Author

Actually, I take my comments about Citrine back - it looks like it does expose SetChargingProfile via the swagger API (@louisg1337 let's prioritize confirming that). Per the citrine-core README,

@AsMessageEndpoint to expose functions allowing to send messages to charging stations

  @AsMessageEndpoint(
    CallAction.SetChargingProfile,
    SetChargingProfileRequestSchema,
  )
  setChargingProfile(
    identifier: string,
    tenantId: string,
    request: SetChargingProfileRequest,
    callbackUrl?: string,
  ): Promise<IMessageConfirmation> {
    return this._module.sendCall(
      identifier,
      tenantId,
      CallAction.SetChargingProfile,
      request,
      callbackUrl,
    );
  }

However, the implementation still seems to be a NOP - I don't see anything being done there other than logging the message. It would be good to test out invoking calls from that endpoint and see if it actually makes it to the station.

@shankari
Copy link
Collaborator Author

shankari commented May 25, 2024

We are going to try to see if the calls are sent to the station. However, citrine currently doesn't support PnC (related PR: citrineos/citrineos-core#100). However, it also doesn't appear to support security profile 3

8081: websocket server tcp connection without auth
8082: websocket server tcp connection with basic http auth

I don't see any mention of a TLS connection; only regular TCP. This means that EVerest has to communicate with it using security profile 1. In SP1, everest is configured to connect to port 80 with the password DEADBEEFDEADBEEF. So if we remap the basic HTTP auth port in citrine (8082) to port 80 (https://github.com/citrineos/citrineos-core/blob/63670f3adc09266a0977862d972b0f7e440c577f/Server/docker-compose.yml#L96) and add the station with the correct password, we should be able to connect and test out the setChargingProfile command.

@shankari
Copy link
Collaborator Author

Since CitrineOS doesn't support SP3, and the open PR doesn't seem to add it either, briefly looking at what it would take to add this in MaEVe. It doesn't look too bad. I assume we are going for:

  • API call to set a per-station limit (look at reconfigure in the manager API as an example, add calls to manager/api/api-spec.yaml and manager/api/server.go and store the value when received)
  • Listen for NotifyEvChargingNeedsRequest (look at TransactionEvent or BootNotification as an example, add a new route in manager/handlers/ocpp201/routing.go and a new handler in manager/handlers/ocpp201, similar to `, process the event by sending a response and triggering the next step)
  • Send setChargingProfileRequest (look at TriggerMessage as an example)

To have the NotifyEvChargingNeedsRequest trigger the setChargingProfileRequest, I think we need to just send an MQTT message to the broker - examples of how to do that are in the manager/handlers/ocpp201/routing_test.go

@shankari
Copy link
Collaborator Author

shankari commented May 25, 2024

I checked the logs and can verify that the messages from the charging station are being received through MQTT (more formally Charge Station -> websocket -> gateway -> mqtt -> manager). Checking TransactionEventHandler, it is pretty clear that the typical structure is for the handlers to call services and then send out a response. I am not sure what the planned structure is for dependencies between messages (e.g. should one handler call another handler or call a service), but I am pretty sure that for the current hacking, we can just send out two messages from the handler and it will work.

@shankari
Copy link
Collaborator Author

shankari commented May 27, 2024

From @louisg1337,

The websocket server is using security profiles 1 or 2 and the request’s Authorization header has an incorrect username or password. The username will be checked against the charging station’s id as set in the url it connected with. The password will be checked against the device model associated with the station id. Specifically, it will be the Actual Variable Attribute’s value that belongs to the BasicAuthPassword Variable on the SecurityCtrlr Component. You can set this VariableAttribute on the CSMS side using the Variable Attribute CRUD endpoints on the Monitoring module. You can set this VariableAttribute on the Charging Station side using the SetVariables message, which can be sent from CitrineOS using the Monitoring module’s message API.

When looking at the API endpoints (http://localhost:8080/docs/ if you have Citrine running) he didn't see BasicAuthPassword anywhere.

Further updates about this in the PR #45

@shankari
Copy link
Collaborator Author

I have the station connecting to the CSMS, but can't figure out how to add auth tokens. Once those are added, we should be able to charge. @louisg1337 can you try to build on this demo by setting the charging profile on the station? If it is easy, you could also figure out how to actually add new tokens to the CSMS.

After that, thought, we should switch to MaEVe since I am not sure if Citrine supports SP3

@thanaParis
Copy link
Contributor

@shankari CitrineOS has supported SP3 since the first full release in January, since SP3 is a part of the Advanced Security use cases. The System Configuration object contains the fields needed to provide keys & certificates for tls/mtls. We added additional documentation to help describe these fields, which you can find here.

If you're having difficulty figuring out how CitrineOS supports a use case from one of its currently supported certification profiles, please reach out on Github or Discord. CitrineOS currently supports Core; Advanced Security; Advanced User Interface; Advanced Device Management; and, with the open PRs you linked, ISO15118.

@shankari
Copy link
Collaborator Author

@thanaParis thank you for the quick response. It is great to hear that Citrine supports SP3. I think that what we are struggling with is that the documentation is not very clear, at least compared to MaEVe, and given the short time frame, we need to make a decision on which CSMS we should try to integrate with.

For example, you say that CitrineOS supports SP3, but the documentation just says the following:

CitrineOS (service name: citrineos) with ports
    8080: webserver http - [Swagger](http://localhost:8080/docs)
    8081: websocket server tcp connection without auth
    8082: websocket server tcp connection with basic http auth

which port should we use for SP3? It is not clear. For the record, I tried to connect to both 8081 and 8082 using ws:// and was successful (8081 added the station, 8082 failed with a 401 error as expected since we weren't able to figure out how to set the password). So it is not clear what the wss:// port even is.

In contrast, MaEVe's documentation says:

Charge stations can connect to the CSMS using:
    ws://localhost/ws/<cs-id>
    wss://localhost/ws/<cs-id>

Even with the documentation that you linked to, it is not clear what we need to do to enable SP3.

Host: The hostname for the WebSocket server.
Port: The port number for the WebSocket server.
Ping Interval: The interval in seconds between pings to keep the connection alive.
Protocol: The communication protocol used.

What do you mean by "the hostname for the websocket server"? You will presumably listen to 0.0.0.0, and the websocket connection will be initiated by the client running on the station. So which server hostname do we need to specify?

We understand that we can get help on Discord or on GitHub, but they are slower - we have to formulate the question and wait for an answer. It is a lot easier if we can use the documentation directly at least for these basic steps and then reach out to the Discord/GitHub for more complex changes.

To streamline this process, would you be open to modifying the "single line demos" that we have in this repo to showcase how to connect via SP1, SP2 and SP3, similar to #45? We have the certificates created, and they work with MaEVe - you can run curl https://raw.githubusercontent.com/everest/everest-demo/main/demo-iso15118-2-ac-plus-ocpp.sh | bash -s -- -3 on a computer that has docker installed, go to http://localhost:1880/ui and initiate a charge session.

If you could get Citrine to the same level, we could figure out what changes we need for the smart charging demo.

@shankari
Copy link
Collaborator Author

shankari commented May 28, 2024

@louisg1337 wrt looking at the ISO 15118-2 messages, I don't know if you saw https://lists.lfenergy.org/g/everest/message/1538
which we can use in our containerized environment using https://superuser.com/questions/1685605/how-to-capture-docker-container-traffic-using-wireshark

@shankari
Copy link
Collaborator Author

shankari commented May 30, 2024

@louisg1337, great job on #45! While we working on review and merge that PR, and @thanaParis and CitrineOS take a look at the issue with the tokens, I would like to return to setChargingProfile REST API call.

#44 (comment)
#44 (comment)

Can you try to invoke setChargingProfile via a REST API call (either through Swagger or through a script or both!) on CitrineOS, and see what it does? Concretely, does it actually pass the call through the websocket interface to EVerest, and does EVerest receive it successfully? If you do get it to work using a script, can you create a new PR that adds that script to this repo?

@shankari
Copy link
Collaborator Author

shankari commented Jun 1, 2024

Exploring
#44 (comment) further

can you figure out what is in the current EXI messages for V2GChargeParameterDiscoveryReq and V2GChargeParameterDiscoveryRes and what else we can send?

I followed this through to the source code. The car simulator requires iso15118_ev (and slac...).

provides:
  main:
    interface: car_simulator
    description: This implements the car simulator
requires:
  simulation_control:
    interface: yeti_simulation_control
  ev:
    interface: ISO15118_ev
    min_connections: 0
    max_connections: 1 
  slac:
    interface: slac
    min_connections: 0
    max_connections: 1

The iso15118_ev interface is implemented by (at least) PyJosev

$ grep -r --before-context=2 ISO15118_ev modules
modules/PyEvJosev/manifest.yaml-provides:  
modules/PyEvJosev/manifest.yaml-  ev:
modules/PyEvJosev/manifest.yaml:    interface: ISO15118_ev
--
modules/simulation/JsCarSimulator/manifest.yaml-    interface: yeti_simulation_control
modules/simulation/JsCarSimulator/manifest.yaml-  ev:
modules/simulation/JsCarSimulator/manifest.yaml:    interface: ISO15118_ev

or

provides:
  ev:
    interface: ISO15118_ev
    description: This module implements the ISO15118-2 implementation of an EV

pyjosev includes the python libraries from https://github.com/EVerest/ext-switchev-iso15118
Notably, here https://github.com/EVerest/ext-switchev-iso15118/blob/6aaaacf690008217bd094447d91c2a723c6261ee/iso15118/evcc/controller/simulator.py#L304

we ask for 1 WH with a departure time of 0 and a bunch of other statically configured values.
If we wanted to, I am pretty sure we can hack this to specify a departure time as well. However, it is not clear that the departure time is in fact necessary for the demo.

@shankari
Copy link
Collaborator Author

shankari commented Jun 1, 2024

Although it looks like there are other, more complex schedules defined here as well with departure times and targetSoC.
https://github.com/EVerest/ext-switchev-iso15118/blob/6aaaacf690008217bd094447d91c2a723c6261ee/iso15118/evcc/controller/simulator.py#L421
or
https://github.com/EVerest/ext-switchev-iso15118/blob/6aaaacf690008217bd094447d91c2a723c6261ee/iso15118/evcc/controller/simulator.py#L435

Next steps for mucking around with the EV SIL:

  • Figure out how to decode EXI (online decoder or wireshark + plugin)
  • Figure out how to invoke the more complex schedules

@shankari
Copy link
Collaborator Author

shankari commented Jun 1, 2024

The more complex schedules are for -20 - they create ScheduledScheduleExchangeReqParams messages, which are not defined in -2. Let's trace out how the calls get passed through from the simulator and see if we can create a new command for the new schedules.

@shankari
Copy link
Collaborator Author

shankari commented Jun 1, 2024

Here's the call stack:

  1. modules/simulation/JsCarSimulator has several "commands" which call methods on the modules that they use
    So the car simulator module ❇️ Smart charging demo at CharIN #44 (comment)
    uses slac and ev and implements commands like
  registerCmd(mod, 'iso_wait_slac_matched', 0, (mod, c) => {
    mod.state = 'pluggedin';
    if (mod.slac_state === undefined) return false;
    if (mod.slac_state === 'UNMATCHED') if (mod.uses_list.slac.length > 0) mod.uses_list.slac[0].call.enter_bcd();
    if (mod.slac_state === 'MATCHED') return true;
  });
  // --- wip
  1. The iSO v2G command is start_charging, and it is implemented in PyJosev
    https://github.com/EVerest/everest-core/blob/54ea32be8432d9292879ed0f36e181a6b37e3266/modules/PyEvJosev/module.py#L96
    def _handler_start_charging(self, args) -> bool:
    
        self._es.PaymentOption =args['PaymentOption']
        self._es.EnergyTransferMode = args['EnergyTransferMode']
        
        self._ready_event.set()
        
        return True
  1. the josev EVCC simulator then reads the energy transfer mode and passes it through to the DCEVChargeParameter

https://github.com/EVerest/ext-switchev-iso15118/blob/6aaaacf690008217bd094447d91c2a723c6261ee/iso15118/evcc/controller/simulator.py#L281

@shankari
Copy link
Collaborator Author

shankari commented Jun 2, 2024

The PyJosev module also supports a call to set EV parameters

    def _handler_set_dc_params(self, args):
        parameters = args['EV_Parameters']
        self._es.dc_max_current_limit = parameters['MaxCurrentLimit']
        self._es.dc_max_power_limit = parameters['MaxPowerLimit']
        self._es.dc_max_voltage_limit = parameters['MaxVoltageLimit']
        self._es.dc_energy_capacity = parameters['EnergyCapacity']
        self._es.dc_target_current = parameters['TargetCurrent']
        self._es.dc_target_voltage = parameters['TargetVoltage']

which is called right after we register the commands

  registerAllCmds(mod);
  mod.enabled = false;
  if (mod.uses_list.ev.length > 0) mod.uses_list.ev[0].call.set_dc_params(get_hlc_dc_parameters(mod));

and the simulator reads the values from the state

        max_current_limit_value, max_current_limit_multiplier = float2Value_Multiplier(
            EVEREST_EV_STATE.dc_max_current_limit)
        max_power_limit_value, max_power_limit_multiplier = float2Value_Multiplier(
            EVEREST_EV_STATE.dc_max_power_limit)
        max_voltage_limit_value, max_voltage_limit_multiplier = float2Value_Multiplier(
            EVEREST_EV_STATE.dc_max_voltage_limit)

...

        self.dc_ev_charge_params: DCEVChargeParams = DCEVChargeParams(
            dc_max_current_limit=PVEVMaxCurrentLimit(
                multiplier=max_current_limit_multiplier, value=max_current_limit_value, unit=UnitSymbol.AMPERE
            ),

and uses it to

            dc_charge_params = DCEVChargeParameter(
                departure_time=0,
                dc_ev_status=await self.get_dc_ev_status(),
                ev_maximum_current_limit=self.dc_ev_charge_params.dc_max_current_limit,
                ev_maximum_power_limit=self.dc_ev_charge_params.dc_max_power_limit,
                ev_maximum_voltage_limit=self.dc_ev_charge_params.dc_max_voltage_limit,
                ev_energy_capacity=self.dc_ev_charge_params.dc_energy_capacity,
                ev_energy_request=ev_energy_request,
                full_soc=100,
                bulk_soc=80,
            )

@shankari
Copy link
Collaborator Author

shankari commented Jun 2, 2024

So for the SIL demo, I think we can do the following:

No changes

  • EV simulator will send a ChargeParameterDiscoveryReq with an energy request of 6000 WH and max values as follows (current: 300, voltage: 900, power: 150000)
  • I assume that the EVSE will respond with a ChargeParameterDiscoveryRes with an SASchedule that provides the max power requested
  • We then set the ChargeProfile with a ChargeSchedule that is clamped down to current of 10
  • Unplug and plug in the car
  • Expect to see the EVSE respond with a ChargeParameterDiscoveryRes that provides 900 V @ 10amps instead of 300 amps.

WIth changes

  • Change the simulator to pull in the energy request and departure time from the arguments as well
  • Plumb through those parameters from JsCarSimulator to PyJosev to the iSO15118 EVCC simulator by adding new arguments to the V2G command
  • change the node red config to include the new arguments

We should then be able to experiment with other scenarios around power requests and/or departure times

@shankari
Copy link
Collaborator Author

shankari commented Jun 2, 2024

wrt viewing the messages so that we can see what is being sent and received, we cannot use wireshark just yet because the communication is between two modules in the same manager code. However, we can get the EXI messages using the logs and try to decode them. From #44 (comment), there is an online decoder or we can use cbexigen (https://github.com/EVerest/cbexigen) or even what is currently used (https://github.com/chargebyte/openv2g).

At least for cbiexigen and the online decoder, we need the schemas:

In order to be able to produce a codec, the standard's XML schema files are required. These cannot be distributed with the code generator. They are available within the actual standard documents, partly distributed separately by the ISO, and also available openly from other sources.

EDIT: there are schemas here https://github.com/FlUxIuS/V2Gdecoder/tree/master/schemas. Notably

@drmrd bumping this up again to see if you can provide a link to the EXI decode tool that you used in the initial set of demos.

@louisg1337
Copy link
Contributor

Can you try to invoke setChargingProfile via a REST API call (either through Swagger or through a script or both!) on CitrineOS, and see what it does? Concretely, does it actually pass the call through the websocket interface to EVerest, and does EVerest receive it successfully? If you do get it to work using a script, can you create a new PR that adds that script to this repo?

Unfortunately it seems like setChargingProfile is indeed a NOP and doesn't send anything to EVerest. I used the Swagger API and made a few requests to all of the x-ChargingProfile commands and they all returned this error within the server-citrine-1 log in Docker.

2024-06-02 17:47:32 2024-06-02 21:47:32.842     ERROR   /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:496       CitrineOS Logger:MessageRouterImpl      Failed processing call error: 
2024-06-02 17:47:32 
2024-06-02 17:47:32  Error  Method not implemented.
2024-06-02 17:47:32 error stack:
2024-06-02 17:47:32   • router.js       MessageRouterImpl.<anonymous>
2024-06-02 17:47:32     /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:595
2024-06-02 17:47:32   • 
2024-06-02 17:47:32 
2024-06-02 17:47:32   • router.js
2024-06-02 17:47:32     /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:12
2024-06-02 17:47:32   • 
2024-06-02 17:47:32 
2024-06-02 17:47:32   • router.js       __awaiter
2024-06-02 17:47:32     /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:8
2024-06-02 17:47:32   • router.js       MessageRouterImpl._routeCallError
2024-06-02 17:47:32     /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:575
2024-06-02 17:47:32   • router.js
2024-06-02 17:47:32     /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:487
2024-06-02 17:47:32   • task_queues     process.processTicksAndRejections
2024-06-02 17:47:32     internal/process/task_queues:95

Looking at the errors specifically, Failed processing call error shows up here, and Method not implemented can be found here, and it seems like they are both dead ends. There is, however, a callbackUrl parameter that we can pass to the API request, and that gets invoked a few lines above the Method not implemented error here. Not sure if this is possible/how it works, but maybe we can reroute a message to our x-ChargingProfile endpoint in EVerest using this?

@shankari
Copy link
Collaborator Author

shankari commented Jun 2, 2024

@louisg1337 the Failed processing call error is called from _onCallError so we are already in the error processing. _onCallError is invoked from here. The actual call is invoked here. Are you sure that this is a NOP and that the call was not passed through to EVerest? Note that EVerest almost certainly doesn't have setChargingProfile implemented yet.

Can you provide additional details? How did you invoke the swagger call? What were the logs on Citrine? What were the logs on EVerest? Did you check the OCCP message logs on EVerest? If you add additional logs to sendCall - are you able to see the message and the response?

@shankari
Copy link
Collaborator Author

shankari commented Jun 4, 2024

@louisg1337 I think it would be worthwhile to fix the logging so that it is clear. We shouldn't expect that everybody pokes through the code to understand which layer has not implemented the function. Isn't it possible to change the error to say something like "Received from charge station: <error>"

🚧 Will mark this at TODO for now. 🚧 ; we should file a separate issue in Citrine and potentially contribute a fix

@shankari
Copy link
Collaborator Author

shankari commented Jun 5, 2024

Thanks to @barsnick, after adding some additional zeros at the end of the EXI message (now it is 8098023b66b9df9bfffb8f90a000000000002028c140c50c0ac010a800000042000000000000), I am able to decode the ChargeParameterDiscoveryRes

ChargeParameterDiscoveryRes
<?xml version="1.0" encoding="UTF-8"?><ns7:V2G_Message xmlns:ns7="urn:iso:15118:2:2013:MsgDef" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns3="http://www.w3.org/2001/XMLSchema" xmlns:ns4="http://www.w3.org/2000/09/xmldsig#" xmlns:ns5="urn:iso:15118:2:2013:MsgBody" xmlns:ns6="urn:iso:15118:2:2013:MsgDataTypes" xmlns:ns8="urn:iso:15118:2:2013:MsgHeader">
<ns7:Header><ns8:SessionID>ED9AE77E6FFFEE3E</ns8:SessionID></ns7:Header>
<ns7:Body><ns5:ChargeParameterDiscoveryRes>
    <ns5:ResponseCode>OK</ns5:ResponseCode>
    <ns5:EVSEProcessing>Finished</ns5:EVSEProcessing>
    <ns6:SAScheduleList>
        <ns6:SAScheduleTuple>
            <ns6:SAScheduleTupleID>1</ns6:SAScheduleTupleID>
            <ns6:PMaxSchedule>
                <ns6:PMaxScheduleEntry>
                    <ns6:RelativeTimeInterval><ns6:start>0</ns6:start><ns6:duration>86400</ns6:duration></ns6:RelativeTimeInterval>
                    <ns6:PMax><ns6:Multiplier>0</ns6:Multiplier><ns6:Unit>W</ns6:Unit><ns6:Value>22080</ns6:Value></ns6:PMax>
                </ns6:PMaxScheduleEntry>
            </ns6:PMaxSchedule>
         </ns6:SAScheduleTuple>
     </ns6:SAScheduleList>
     <ns6:AC_EVSEChargeParameter>
         <ns6:AC_EVSEStatus>
             <ns6:NotificationMaxDelay>0</ns6:NotificationMaxDelay>
             <ns6:EVSENotification>None</ns6:EVSENotification>
             <ns6:RCD>false</ns6:RCD>
         </ns6:AC_EVSEStatus>
         <ns6:EVSENominalVoltage>
            <ns6:Multiplier>-1</ns6:Multiplier><ns6:Unit>V</ns6:Unit><ns6:Value>0</ns6:Value>
         </ns6:EVSENominalVoltage>
         <ns6:EVSEMaxCurrent>
            <ns6:Multiplier>-3</ns6:Multiplier><ns6:Unit>h</ns6:Unit><ns6:Value>0</ns6:Value>
         </ns6:EVSEMaxCurrent>
     </ns6:AC_EVSEChargeParameter>
</ns5:ChargeParameterDiscoveryRes></ns7:Body></ns7:V2G_Message>

@shankari
Copy link
Collaborator Author

shankari commented Jun 5, 2024

Field Req Res
Departure time 0 86400 (24 * 60 * 60)
Energy/power 60 Wh 22080 W
Voltage 400 V (max) $0 \times 10^{-1}$ V (nominal)
Current $32000 \times 10^{-3} A = 32 A max, 10 A min $0 \times 10^{-3}$ h

It is not clear why the EVSE is giving 22080 W at 0V and 0A. The 0,0 may be a decoding error after adding on zeros. But it is still not clear why the energy/power is off by so much.

@the-bay-kay
Copy link

wireshark might help. When we got SP3 working with MaEVe, I recall it being fairly useful. there are standard methods to use wireshark to inspect docker networks; we should really enable that by default. ... did you get wireshark to work when debugging the EonTI PKI?

@shankari I've had somewhat mixed results -- I've got the linuxserver wireshark container (docs) running, but there seem to be some issues with its interraction with WSL-2. Below are some notes on the setup, and where how I'm working to fix it and get it running!

The Setup: WSL 2 on Windows 10

Because I do not currently have access to my personal linux machine, and the demos do not work with my NREL laptop's M1-Chip (link), I'm running the docker image with the following setup on another personal computer:

image

With this, I have successfully been able to spin up the cert demos with wireshark running in the background...

image

But, when I attempt to connect to the wireshark application (docs) via 127.0.0.1:3000, 3001, or any similar port / url combo, it fails to connect...

image

The Issue

When inspecting the docker container, we get the following error: Of note is the line nl80211 not found. Looking at this github thread, it seems I'm not the first person who's had this issue with WSL-2, and the best workarounds seem rather complex (link).

image

Solutions:

It seems that, like with the M1 Chip, there's some inherent issues to the WSL approach -- adding wireshark appears to work otherwise! I'm currently getting VMWare Workstation 17 set up on this machine, and will report back once I run tests on a full Linux machine. For those curious, the wireshark addition to docker-compose.ocpp201.yml is as simple as the following (with more complex options found here)

services:
# Other servces ...
  wireshark:
    image: lscr.io/linuxserver/wireshark:latest
    container_name: wireshark

@shankari
Copy link
Collaborator Author

shankari commented Jun 5, 2024

But, when I attempt to connect to the wireshark application (docs) via 127.0.0.1:3000, 3001, or any similar port / url combo, it fails to connect...

You haven't exposed the appropriate port from the wireshark container.

@barsnick
Copy link

barsnick commented Jun 5, 2024

<ns6:EVSEMaxCurrent>
            <ns6:Multiplier>-3</ns6:Multiplier><ns6:Unit>h</ns6:Unit><ns6:Value>0</ns6:Value>
         </ns6:EVSEMaxCurrent>

Current shouldn't have the unit "hours". 😉
This (and the 10^-3) is a side effect of filling the truncated EXI message with zeros for it to decode without errors, In reality, some other values must have been truncated.

@shankari
Copy link
Collaborator Author

shankari commented Jun 5, 2024

Before going a lot further, let's test the other combinations (AC ISO 15118 + EIM, DC ISO 15118 + EIM and DC ISO 15118 + PnC). We will need to use at least the first two to test the HIL since we are not sure how to install certs on the vector ECCU.

AC ISO 15118-2 with EIM
  • Currently fails during payment; we specify EIM which succeeds in auth, but the external payment gets converted to "contract" at some point along the way
{
  cmd: 'iso_wait_slac_matched',
  args: [],
  exec: [Function (anonymous)]
}

....

2024-06-05 17:37:00.939139 [INFO] auth:Auth        :: Received new token: {
    "authorization_type": "RFID",
    "connectors": [
        1
    ],
    "id_token": {
        "type": "ISO14443",
        "value": "DEADBEEF"
    },
    "prevalidated": false
}
2024-06-05 17:37:00.941916 [INFO] ocpp:OCPP201     :: Found invalid entry in AuthCache: Sending new request
2024-06-05 17:37:00.982885 [INFO] auth:Auth        :: Providing authorization to connector#1
2024-06-05 17:37:01.026809 [INFO] auth:Auth        :: Result for token: DEADBEEF: ACCEPTED
...
{
  cmd: 'iso_start_v2g_session',
  args: [ 'externalpayment', 'ac_three_phase_core' ],
  exec: [Function (anonymous)]
}
...
2024-06-05 17:37:02.711473 [WARN] car_simulator_1 EverestJs::Init(Napi::Env, Napi::Object)::<lambda(const Napi::CallbackInfo&)> :: SHANKARI: arguments to the command are externalpayment,ac_three_phase_core
2024-06-05 17:37:02.711573 [WARN] car_simulator_1 EverestJs::Init(Napi::Env, Napi::Object)::<lambda(const Napi::CallbackInfo&)> :: SHANKARI: after processing, payment mode = ExternalPayment
...
2024-06-05 17:37:08.649772 [INFO] iso15118_charge  :: SelectedPaymentOption: Contract
  • The server uses the PaymentDetailsReq that the client sends
    for (idx = 0; idx < conn->ctx->evse_v2g_data.payment_option_list_len; idx++) {
        if ((conn->ctx->evse_v2g_data.payment_option_list[idx] == req->SelectedPaymentOption)) {
            list_element_found = true;
            conn->ctx->p_charger->publish_SelectedPaymentOption(
                static_cast<types::iso15118_charger::PaymentOption>(req->SelectedPaymentOption));
            break;
        }
    }

...

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);
            
    if (req->SelectedPaymentOption == iso1paymentOptionType_Contract) {
        dlog(DLOG_LEVEL_INFO, "SelectedPaymentOption: Contract");           
        conn->ctx->session.iso_selected_payment_option = iso1paymentOptionType_Contract;
        /* Set next expected req msg */
        conn->ctx->state =
            (int)iso_dc_state_id::WAIT_FOR_PAYMENTDETAILS_CERTINST_CERTUPD; // [V2G-551] (iso specification describes  
                                                                            // only the ac case... )
    } else {
        dlog(DLOG_LEVEL_INFO, "SelectedPaymentOption: ExternalPayment");
        conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH] =
            (uint8_t)iso1EVSEProcessingType_Ongoing_WaitingForCustomerInteraction; // [V2G2-854]
        /* Set next expected req msg */
        conn->ctx->state = (int)
            iso_dc_state_id::WAIT_FOR_AUTHORIZATION; // [V2G-551] (iso specification describes only the ac case... )
        conn->ctx->session.auth_start_timeout = getmonotonictime();
    }

How does the car send this message? It would be super helpful to be able to see EXI decoded messages. Fortunately, by turning on debug logging for the car module, we are able to see all messages

2024-06-05 18:36:01.826182 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"86C0F5AFF5AF1D7E"},"Body":{"ServiceDiscoveryRes":{"ResponseCode":"OK","PaymentOptionList":{"PaymentOption":["ExternalPayment","Contract"]},"ChargeService":{"ServiceID":1,"ServiceCategory":"EVCharging","FreeService":false,"SupportedEnergyTransferMode":{"EnergyTransferMode":["AC_single_phase_core","AC_three_phase_core"]}},"ServiceList":{"Service":[{"ServiceID":2,"ServiceName":"Certificate","ServiceCategory":"ContractCertificate","FreeService":true}]}}}}}
...
2024-06-05 18:36:01.892814 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "86C0F5AFF5AF1D7E"}, "Body": {"PaymentServiceSelectionReq": {"SelectedPaymentOption": "Contract", "SelectedServiceList": {"SelectedService": [{"ServiceID": 1}]}}}}}

So the server supports both ExternalPayment and Contract, but the car selects Contract. Why? To some extent, this doesn't matter because it means that when we test EIM with the vector module, it will work,since it will presumably use. It is because the car picks Contract if it is TLS

            if AuthEnum.PNC_V2 in auth_option_list and self.comm_session.is_tls:
                self.comm_session.selected_auth_option = AuthEnum.PNC_V2
            else:
                self.comm_session.selected_auth_option = AuthEnum.EIM_V2

is_tls comes from the config

def patch_josev_config(josev_config: EVCCConfig, everest_config: dict) -> None:
    josev_config.use_tls = everest_config['tls_active']
    josev_config.enforce_tls = everest_config['enforce_tls']
    josev_config.is_cert_install_needed = everest_config['is_cert_install_needed']

And we have tls_active set

  iso15118_car:
    module: PyEvJosev
    config_module:
      device: auto
      supported_ISO15118_2: true
      tls_active: true
      is_cert_install_needed: true

When we use a HIL EVCC, of course, it will only accept EIM, so we don't need to worry about this car simulator implementation just yet.

But just for my own edification, I looked at how the energy mode is handled, and the answer is that it is read from the session,

        if evcc_settings.ev_session_context.requested_energy_mode:
            logger.debug(
                "Reusing energy transfer mode "
                f"{evcc_settings.ev_session_context.requested_energy_mode} "
                "from previously paused session"
            )
            self.comm_session.selected_energy_mode = (
                evcc_settings.ev_session_context.requested_energy_mode
            )
            evcc_settings.ev_session_context.requested_energy_mode = None
        else:
            self.comm_session.selected_energy_mode = (
                await self.comm_session.ev_controller.get_energy_transfer_mode(
                    Protocol.ISO_15118_2
                )
            )

which is in turn read from the state

    async def get_energy_transfer_mode(
        self, protocol: Protocol
    ) -> EnergyTransferModeEnum:
        """Overrides EVControllerInterface.get_energy_transfer_mode()."""
        return EnergyTransferModeEnum(EVEREST_EV_STATE.EnergyTransferMode)

I think that if we plumb that through, we should be able to handle this properly

@louisg1337
Copy link
Contributor

MaEVe Exploration

API Request

The goal we had in mind was to see whether or not calling the triggerChargeStation API actually sent something to EVerest. The first step was to send an API request and check the OCPP logs to see if our request showed up. I sent the API request below, and observed the following in the picture further down.

curl -X POST \             
  'http://localhost:9410/api/v0/cs/cp001/trigger' \
  -H 'Content-Type: application/json' \
  -d '{
  "trigger": "SignCombinedCertificate"       
}'
Screenshot 2024-06-04 at 6 44 25 PM

I have not observed SignCombinedCertificate get sent out by the system before, and that time was around the time I sent it, so I think it was reasonable to say that the triggerChargeStation API reaches EVerest.

Code Tracing

After observing the above, I also wanted to trace through the MaEVe code to get a better understanding of how the triggerChargeStation API call actually gets sent to EVerest. The below demonstrates the general flow of how the trigger call works its way through the code.

API calls tracing, specifically triggers...

  • Trigger API call starts off at manager / api / server.go ... TriggerChargeStation() (link)
    • In TriggerChargeStation() we save the trigger to the store
      err := s.store.SetChargeStationTriggerMessage(r.Context(), csId, &store.ChargeStationTriggerMessage{
         TriggerMessage: store.TriggerMessage(req.Trigger),
         TriggerStatus:  store.TriggerStatusPending,
      })
      
  • Triggers eventually get called In manager / sync / triggers.go ... SyncTriggers() (link)
    • After runEvery time, check to see if any new triggers, if so then send()
      err = v201CallMaker.Send(ctx, csId, &ocpp201.TriggerMessageRequestJson{
          RequestedMessage: ocpp201.MessageTriggerEnumType(pendingTriggerMessage.TriggerMessage),
      })
      
  • In manager / handlers / call_maker.go ... Send(), physically sends the message to EVerest (link)

Note

When I was tracing the code, the "calls made internally" section is how I originally thought triggerChargeStation was handled, but once I added in logs, I realized that wasn't the case. I kept the internal call section below just to document in case we ever needed to understand this part of the codebase better.

Calls made internally (i.e. BootNotification, GetCertificateStatus)

  • Start in manager / handlers / router.go ... route() (link)
    • API call gets passed in, find which route we are using by route, ok := r.CallRoutes[message.Action]
      • All the routes are defined in manager/handlers/ocpp201/routing.go ... NewRouter() (link)
    • Then it ends up calling resp, err := route.Handler.HandleCall(ctx, chargeStationId, req)
  • In manager / handlers / occp201 / xxx.go there are a ton of different functionalities all of which have their own HandleCall functions

@louisg1337
Copy link
Contributor

MaEVe setChargingProfile Exploration

Diving deeper into the MaEVe exploration, I tried to see if setChargingProfile was supported at all. Unfortunately, it looks like this feature hasn't been developed yet as the below bullet points are all I have found that related to it. I looked in the repo for everything related to setChargingProfile, chargingProfile, charging, and profile. I also did a bit of research online to see if there was an active branch that had these changes in the works or if someone had their own forked version with it, but I didn't find either.

  • In manager / ocpi / server.go ... x-ChargingProfile() functions (link)
    • All of them have w.WriteHeader(http.StatusNotImplemented)
  • In manager / schemas / ocpp201 ... x-ChargingProfileRequest.json / x-ChargingProfileResponse.json
    • Only json schema files, not used anywhere
  • In manager / ocpp / ocpp201 / request_start_transaction_request.go ... type ChargingProfileType struct (link)
    • There is a defined data type for charging profiles, but it is not used in anything else but the schemas

@shankari
Copy link
Collaborator Author

shankari commented Jun 6, 2024

@louisg1337 yes, setChargingProfile is not currently supported on MaEVe. So we want to add support for it, plumbing it through to the charge station. You should use triggerChargeStation as an example and follow the flow that you discovered in #44 (comment)

@shankari
Copy link
Collaborator Author

shankari commented Jun 9, 2024

To follow up on #44 (comment), I tried adding a new manager, hooking up the second connector to it, and expanding the options in the node-red dropdown. The new config and new node-red are attached.

new config file:
active_modules:
  iso15118_charger:
    module: EvseV2G
    config_module:
      device: auto
      tls_security: allow
      verify_contract_cert_chain: false
    connections:
      security:
        - module_id: evse_security
          implementation_id: main
  iso15118_car:
    module: PyEvJosev
    config_module:
      device: auto
      supported_ISO15118_2: true
      tls_active: true
      is_cert_install_needed: false
  evse_manager_1:
    module: EvseManager
    config_module:
      connector_id: 1
      three_phases: true
      has_ventilation: true
      country_code: DE
      evse_id: "DE*PNX*00001"
      session_logging: true
      session_logging_xml: false
      session_logging_path: /tmp/everest-logs
      charge_mode: AC
      ac_hlc_enabled: true
      ac_hlc_use_5percent: false
      ac_enforce_hlc: false
    connections:
      bsp:
        - module_id: yeti_driver_1
          implementation_id: board_support
      powermeter_grid_side:
        - module_id: yeti_driver_1
          implementation_id: powermeter
      slac:
        - module_id: slac
          implementation_id: evse
      hlc:
        - module_id: iso15118_charger
          implementation_id: charger
  evse_manager_2:
    module: EvseManager
    config_module:
      connector_id: 2
      country_code: DE
      evse_id: "DE*PNX*E12345*2"
      session_logging: true
      session_logging_xml: false
      session_logging_path: /tmp/everest-logs
      charge_mode: DC
      hack_allow_bpt_with_iso2: true
    connections:
      bsp:
        - module_id: yeti_driver_2
          implementation_id: board_support
      powermeter_car_side:
        - module_id: powersupply_dc
          implementation_id: powermeter
      slac:
        - module_id: slac
          implementation_id: evse
      hlc:
        - module_id: iso15118_charger
          implementation_id: charger
      powersupply_DC:
        - module_id: powersupply_dc
          implementation_id: main
      imd:
        - module_id: imd
          implementation_id: main
  powersupply_dc:
    module: JsDCSupplySimulator
  yeti_driver_1:
    module: JsYetiSimulator
    config_module:
      connector_id: 1
  yeti_driver_2:
    module: JsYetiSimulator
    config_module:
      connector_id: 2
  slac:
    module: JsSlacSimulator
  imd:
    module: IMDSimulator
  car_simulator_1:
    module: JsCarSimulator
    config_module:
      connector_id: 1
      auto_enable: true
      auto_exec: false
      auto_exec_commands: sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug
    connections:
      simulation_control:
        - module_id: yeti_driver_1
          implementation_id: yeti_simulation_control
      ev:
        - module_id: iso15118_car
          implementation_id: ev
      slac:
        - module_id: slac
          implementation_id: ev
  car_simulator_2:
    module: JsCarSimulator
    config_module:
      connector_id: 2
      auto_enable: true
      auto_exec: false
      auto_exec_commands: sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug
      dc_target_current: 20
      dc_target_voltage: 400
    connections:
      simulation_control:
        - module_id: yeti_driver_2
          implementation_id: yeti_simulation_control
      ev:
        - module_id: iso15118_car
          implementation_id: ev
      slac:
        - module_id: slac
          implementation_id: ev
  ocpp:
    module: OCPP201
    connections:
      evse_manager:
        - module_id: evse_manager_1
          implementation_id: evse
        - module_id: evse_manager_2
          implementation_id: evse
      auth:
        - module_id: auth
          implementation_id: main
      system:
        - module_id: system
          implementation_id: main
      security:
        - module_id: evse_security
          implementation_id: main
  evse_security:
    module: EvseSecurity
    config_module:
      private_key_password: "123456"
  token_provider_1:
    module: DummyTokenProviderManual
  auth:
    module: Auth
    config_module:
      connection_timeout: 120
      selection_algorithm: PlugEvents
    connections:
      token_provider:
        - module_id: token_provider_1
          implementation_id: main
        - module_id: ocpp
          implementation_id: auth_provider
        - module_id: evse_manager_1
          implementation_id: token_provider
        - module_id: evse_manager_2
          implementation_id: token_provider
      token_validator:
        - module_id: ocpp
          implementation_id: auth_validator
      evse_manager:
        - module_id: evse_manager_1
          implementation_id: evse
        - module_id: evse_manager_2
          implementation_id: evse
  energy_manager:
    module: EnergyManager
    connections:
      energy_trunk:
        - module_id: grid_connection_point
          implementation_id: energy_grid
  grid_connection_point:
    module: EnergyNode
    config_module:
      fuse_limit_A: 40.0
      phase_count: 3
    connections:
      price_information: []
      energy_consumer:
        - module_id: evse_manager_1
          implementation_id: energy_grid
        - module_id: evse_manager_2
          implementation_id: energy_grid
      powermeter:
        - module_id: yeti_driver_1
          implementation_id: powermeter
  api:
    module: API
    connections:
      evse_manager:
        - module_id: evse_manager_1
          implementation_id: evse
  system:
    module: System

x-module-layout: {}
new nodered file
[
    {
        "id": "9aafbf849d4d6e12",
        "type": "tab",
        "label": "Debug",
        "disabled": false,
        "info": ""
    },
    {
        "id": "e921db8897354328",
        "type": "tab",
        "label": "RFID",
        "disabled": false,
        "info": ""
    },
    {
        "id": "ed603c51db9dcbb9",
        "type": "tab",
        "label": "Connector 1",
        "disabled": false,
        "info": ""
    },
    {
        "id": "1922139a3ea7cac2",
        "type": "tab",
        "label": "Connector 2",
        "disabled": false,
        "info": ""
    },
    {
        "id": "af1e1eeac9c4b704",
        "type": "group",
        "z": "ed603c51db9dcbb9",
        "style": {
            "stroke": "#999999",
            "stroke-opacity": "1",
            "fill": "none",
            "fill-opacity": "1",
            "label": true,
            "label-position": "nw",
            "color": "#a4a4a4"
        },
        "nodes": [
            "1295e032d7ddbc20"
        ],
        "x": 1114,
        "y": 439,
        "w": 152,
        "h": 82
    },
    {
        "id": "6459c14573f03fd2",
        "type": "group",
        "z": "1922139a3ea7cac2",
        "style": {
            "stroke": "#999999",
            "stroke-opacity": "1",
            "fill": "none",
            "fill-opacity": "1",
            "label": true,
            "label-position": "nw",
            "color": "#a4a4a4"
        },
        "nodes": [
            "22139ab4759c1b51"
        ],
        "x": 1094,
        "y": 419,
        "w": 152,
        "h": 82
    },
    {
        "id": "7140803fb3989089",
        "type": "ui_base",
        "theme": {
            "name": "theme-custom",
            "lightTheme": {
                "default": "#0094CE",
                "baseColor": "#0094CE",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "edited": true,
                "reset": false
            },
            "darkTheme": {
                "default": "#097479",
                "baseColor": "#097479",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "edited": true,
                "reset": false
            },
            "customTheme": {
                "name": "EVerest",
                "default": "#4B7930",
                "baseColor": "#2a62ac",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "reset": false
            },
            "themeState": {
                "base-color": {
                    "default": "#2a62ac",
                    "value": "#2a62ac",
                    "edited": true
                },
                "page-titlebar-backgroundColor": {
                    "value": "#2a62ac",
                    "edited": false
                },
                "page-backgroundColor": {
                    "value": "#111111",
                    "edited": false
                },
                "page-sidebar-backgroundColor": {
                    "value": "#333333",
                    "edited": false
                },
                "group-textColor": {
                    "value": "#4f88d4",
                    "edited": false
                },
                "group-borderColor": {
                    "value": "#555555",
                    "edited": false
                },
                "group-backgroundColor": {
                    "value": "#333333",
                    "edited": false
                },
                "widget-textColor": {
                    "value": "#eeeeee",
                    "edited": false
                },
                "widget-backgroundColor": {
                    "value": "#2a62ac",
                    "edited": false
                },
                "widget-borderColor": {
                    "value": "#333333",
                    "edited": false
                },
                "base-font": {
                    "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
                }
            },
            "angularTheme": {
                "primary": "indigo",
                "accents": "blue",
                "warn": "red",
                "background": "grey",
                "palette": "light"
            }
        },
        "site": {
            "name": "EVerest",
            "hideToolbar": "false",
            "allowSwipe": "false",
            "lockMenu": "false",
            "allowTempTheme": "true",
            "dateFormat": "DD.MM.YYYY",
            "sizes": {
                "sx": 48,
                "sy": 48,
                "gx": 6,
                "gy": 6,
                "cx": 6,
                "cy": 6,
                "px": 6,
                "py": 6
            }
        }
    },
    {
        "id": "fc8686af.48d178",
        "type": "mqtt-broker",
        "name": "",
        "broker": "mqtt-server",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "sessionExpiry": ""
    },
    {
        "id": "5e36140d.127f1c",
        "type": "ui_group",
        "name": "PowerMeter",
        "tab": "50c487c1.27e508",
        "order": 2,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "ebbb0e3f.53fbf",
        "type": "ui_group",
        "name": "Actions",
        "tab": "50c487c1.27e508",
        "order": 2,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "1709edaf.162962",
        "type": "ui_group",
        "name": "Debug",
        "tab": "50c487c1.27e508",
        "order": 3,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "8d6f402b.8f007",
        "type": "ui_group",
        "name": "KeepAlive",
        "tab": "50c487c1.27e508",
        "order": 5,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "1ebee360.265b5d",
        "type": "ui_group",
        "name": "PowerMeter",
        "tab": "50c487c1.27e508",
        "order": 6,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "d3f19d5c.593e5",
        "type": "ui_group",
        "name": "State",
        "tab": "50c487c1.27e508",
        "order": 4,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "b364f7eb4621082b",
        "type": "ui_group",
        "name": "Connector 1 [AC ISO15118-2]",
        "tab": "d3ada9fa4cf6ac53",
        "order": 2,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "7cd2ccabb1265f7a",
        "type": "ui_group",
        "name": "RFID",
        "tab": "d3ada9fa4cf6ac53",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "21e40a4a97a50168",
        "type": "ui_group",
        "name": "Connector 2 [DC ISO 15118-2]",
        "tab": "d3ada9fa4cf6ac53",
        "order": 3,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "50c487c1.27e508",
        "type": "ui_tab",
        "name": "Debug",
        "icon": "fa-fire",
        "disabled": false,
        "hidden": false
    },
    {
        "id": "d3ada9fa4cf6ac53",
        "type": "ui_tab",
        "name": "Home",
        "icon": "dashboard",
        "order": 1,
        "disabled": false,
        "hidden": false
    },
    {
        "id": "27225dc1005441da",
        "type": "ui_spacer",
        "z": "9aafbf849d4d6e12",
        "name": "spacer",
        "group": "27651fee38a05406",
        "order": 4,
        "width": 1,
        "height": 1
    },
    {
        "id": "7120e41583a9165f",
        "type": "ui_spacer",
        "z": "9aafbf849d4d6e12",
        "name": "spacer",
        "group": "27651fee38a05406",
        "order": 4,
        "width": 1,
        "height": 1
    },
    {
        "id": "efce370cfc8e4f9b",
        "type": "ui_spacer",
        "z": "9aafbf849d4d6e12",
        "name": "spacer",
        "group": "",
        "order": 2,
        "width": 6,
        "height": 1
    },
    {
        "id": "794d727ae2866f12",
        "type": "ui_spacer",
        "z": "9aafbf849d4d6e12",
        "name": "spacer",
        "group": "",
        "order": 5,
        "width": "6",
        "height": "1"
    },
    {
        "id": "c8955752ad17f297",
        "type": "mqtt in",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/powermeter/vrmsL1",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 180,
        "y": 100,
        "wires": [
            [
                "b1d3d31a92c2c68d"
            ]
        ]
    },
    {
        "id": "b1d3d31a92c2c68d",
        "type": "ui_chart",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "group": "5e36140d.127f1c",
        "order": 11,
        "width": 0,
        "height": 0,
        "label": "vrmsL1",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": "60",
        "removeOlderPoints": "",
        "removeOlderUnit": "1",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "x": 400,
        "y": 100,
        "wires": [
            []
        ]
    },
    {
        "id": "a4bef87a56ade625",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/enable",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 500,
        "y": 440,
        "wires": []
    },
    {
        "id": "6c1a9684ee9cff1b",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/disable",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 500,
        "y": 480,
        "wires": []
    },
    {
        "id": "7b7910abcebe9ea8",
        "type": "ui_switch",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "label": "Enabled",
        "tooltip": "",
        "group": "ebbb0e3f.53fbf",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "topic",
        "topicType": "msg",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 120,
        "y": 460,
        "wires": [
            [
                "f68fa199eb0c13b0"
            ]
        ]
    },
    {
        "id": "f68fa199eb0c13b0",
        "type": "switch",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "true"
            },
            {
                "t": "false"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 280,
        "y": 460,
        "wires": [
            [
                "a4bef87a56ade625"
            ],
            [
                "6c1a9684ee9cff1b"
            ]
        ]
    },
    {
        "id": "be7373d5f1fc78e3",
        "type": "ui_switch",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "label": "setThreePhases",
        "tooltip": "",
        "group": "ebbb0e3f.53fbf",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "topic",
        "topicType": "msg",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 140,
        "y": 540,
        "wires": [
            [
                "ee61573475970e13"
            ]
        ]
    },
    {
        "id": "ee61573475970e13",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/set_three_phases",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 370,
        "y": 540,
        "wires": []
    },
    {
        "id": "1e33ee217d09343a",
        "type": "ui_switch",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "label": "enableRCD",
        "tooltip": "",
        "group": "ebbb0e3f.53fbf",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "topic",
        "topicType": "msg",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 130,
        "y": 600,
        "wires": [
            [
                "7f1db77313661cf3"
            ]
        ]
    },
    {
        "id": "7f1db77313661cf3",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/enable_rcd",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 350,
        "y": 600,
        "wires": []
    },
    {
        "id": "23610d2f3c1a674b",
        "type": "ui_switch",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "label": "setHasVentilation",
        "tooltip": "",
        "group": "ebbb0e3f.53fbf",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "topic",
        "topicType": "msg",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 150,
        "y": 660,
        "wires": [
            [
                "d40cd5658151e3ca"
            ]
        ]
    },
    {
        "id": "d40cd5658151e3ca",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/set_has_ventilation",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 420,
        "y": 660,
        "wires": []
    },
    {
        "id": "d62a89349e2d9147",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/set_auth",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 380,
        "y": 220,
        "wires": []
    },
    {
        "id": "49a61fca4e975f0d",
        "type": "ui_button",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "group": "ebbb0e3f.53fbf",
        "order": 1,
        "width": 0,
        "height": 0,
        "passthru": false,
        "label": "setAuth(USERID)",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "USERID",
        "payloadType": "str",
        "topic": "topic",
        "topicType": "msg",
        "x": 150,
        "y": 220,
        "wires": [
            [
                "d62a89349e2d9147"
            ]
        ]
    },
    {
        "id": "4a20ae416f941363",
        "type": "ui_switch",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "label": "switch3phWhileCharging",
        "tooltip": "",
        "group": "ebbb0e3f.53fbf",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "topic",
        "topicType": "msg",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 170,
        "y": 720,
        "wires": [
            [
                "9f997a83d8c5e502"
            ]
        ]
    },
    {
        "id": "9f997a83d8c5e502",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/switch_three_phases_while_charging",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 530,
        "y": 720,
        "wires": []
    },
    {
        "id": "b0dbe5826f92035e",
        "type": "mqtt in",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/debug_json",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 160,
        "y": 960,
        "wires": [
            [
                "a62346afa82d2aa1"
            ]
        ]
    },
    {
        "id": "a62346afa82d2aa1",
        "type": "json",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 350,
        "y": 960,
        "wires": [
            [
                "971b16b8195f14bb"
            ]
        ]
    },
    {
        "id": "971b16b8195f14bb",
        "type": "function",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "func": "var json = msg.payload;\n\n\n//tab[top] = {'Variable' : top, 'Value' : pay};\n\n\nnewpayload = [];\nfor(var index in json) {\n    newpayload.push({'Variable' : index, 'Value' : json[index]});\n}\n\nmsg.payload = newpayload;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 510,
        "y": 960,
        "wires": [
            [
                "b79f8c3549cdb63e"
            ]
        ]
    },
    {
        "id": "b79f8c3549cdb63e",
        "type": "ui_table",
        "z": "9aafbf849d4d6e12",
        "group": "1709edaf.162962",
        "name": "Debug",
        "order": 12,
        "width": "6",
        "height": "11",
        "columns": [],
        "outputs": 0,
        "cts": false,
        "x": 680,
        "y": 960,
        "wires": []
    },
    {
        "id": "105b9eab50b4db7f",
        "type": "mqtt in",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/keepalive_json",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 170,
        "y": 1140,
        "wires": [
            [
                "f9bce2148fd0d745"
            ]
        ]
    },
    {
        "id": "f9bce2148fd0d745",
        "type": "json",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 350,
        "y": 1140,
        "wires": [
            [
                "81c08d60fe305390"
            ]
        ]
    },
    {
        "id": "81c08d60fe305390",
        "type": "function",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "func": "var json = msg.payload;\n\n\n//tab[top] = {'Variable' : top, 'Value' : pay};\n\n\nnewpayload = [];\nfor(var index in json) {\n    newpayload.push({'Variable' : index, 'Value' : json[index]});\n}\n\nmsg.payload = newpayload;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 510,
        "y": 1140,
        "wires": [
            [
                "24770a798c2cc78c"
            ]
        ]
    },
    {
        "id": "24770a798c2cc78c",
        "type": "ui_table",
        "z": "9aafbf849d4d6e12",
        "group": "8d6f402b.8f007",
        "name": "KeepAlive",
        "order": 12,
        "width": "6",
        "height": "4",
        "columns": [],
        "outputs": 0,
        "cts": false,
        "x": 690,
        "y": 1140,
        "wires": []
    },
    {
        "id": "b2e6e05e396ff846",
        "type": "mqtt in",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/powermeter_json",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 170,
        "y": 1200,
        "wires": [
            [
                "bff81951aab38e7c"
            ]
        ]
    },
    {
        "id": "bff81951aab38e7c",
        "type": "json",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 360,
        "y": 1200,
        "wires": [
            [
                "685dcdcf457910a6"
            ]
        ]
    },
    {
        "id": "685dcdcf457910a6",
        "type": "function",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "func": "var json = msg.payload;\n\n\n//tab[top] = {'Variable' : top, 'Value' : pay};\n\n\nnewpayload = [];\nfor(var index in json) {\n    newpayload.push({'Variable' : index, 'Value' : json[index]});\n}\n\nmsg.payload = newpayload;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 1200,
        "wires": [
            [
                "854cee03b9be0de5"
            ]
        ]
    },
    {
        "id": "854cee03b9be0de5",
        "type": "ui_table",
        "z": "9aafbf849d4d6e12",
        "group": "1ebee360.265b5d",
        "name": "PowerMeter",
        "order": 12,
        "width": "6",
        "height": "11",
        "columns": [],
        "outputs": 0,
        "cts": false,
        "x": 710,
        "y": 1200,
        "wires": []
    },
    {
        "id": "dd9a01731c23f076",
        "type": "mqtt in",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/state/#",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 140,
        "y": 1020,
        "wires": [
            [
                "181994551d5096a8"
            ]
        ]
    },
    {
        "id": "181994551d5096a8",
        "type": "function",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "func": "let cur_topic_index = -1;\nlet topics_list_length = 0;\n\nvar topics_list = global.get(\"state_topics_list\");\nvar payload_list = global.get(\"state_payload_list\");\nvar new_payload = [];\n\nfor (var topics_list_index in topics_list) {\n    if ( (topics_list[topics_list_index].indexOf(msg.topic) >= 0) && (topics_list[topics_list_index].length == msg.topic.length) ) {\n        cur_topic_index = topics_list_index;\n        break;\n    }\n    topics_list_length++;\n}\n\nif (cur_topic_index > -1) {\n    payload_list[cur_topic_index] = msg.payload;\n} else {\n    topics_list.push(msg.topic);\n    payload_list.push(msg.payload);\n}\n\nglobal.set(\"state_topics_list\", topics_list);\nglobal.set(\"state_payload_list\", payload_list);\n\n\nfor (var index in payload_list) {\n    new_payload.push({'Variable': topics_list[index].substr(16, topics_list[index].length), 'Value': payload_list[index]});\n}\n\nmsg.payload = new_payload;\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "// Code added here will be run once\n// whenever the node is started.\nglobal.set(\"state_topics_list\", []);\nglobal.set(\"state_payload_list\", []);",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 1020,
        "wires": [
            [
                "f05a2bcbad4e5e4f"
            ]
        ]
    },
    {
        "id": "f05a2bcbad4e5e4f",
        "type": "ui_table",
        "z": "9aafbf849d4d6e12",
        "group": "d3f19d5c.593e5",
        "name": "State",
        "order": 12,
        "width": "6",
        "height": "4",
        "columns": [],
        "outputs": 0,
        "cts": false,
        "x": 690,
        "y": 1020,
        "wires": []
    },
    {
        "id": "626ae76afca27c10",
        "type": "ui_switch",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "label": "enableHLC",
        "tooltip": "",
        "group": "ebbb0e3f.53fbf",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "topic",
        "topicType": "msg",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 130,
        "y": 780,
        "wires": [
            [
                "cb19212395df1ec4"
            ]
        ]
    },
    {
        "id": "cb19212395df1ec4",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/enable_hlc",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 350,
        "y": 780,
        "wires": []
    },
    {
        "id": "8761f22ed645e3d5",
        "type": "ui_switch",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "label": "Simulation RCD",
        "tooltip": "",
        "group": "ebbb0e3f.53fbf",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "topic",
        "topicType": "msg",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 140,
        "y": 840,
        "wires": [
            [
                "c423c5096c47f04a"
            ]
        ]
    },
    {
        "id": "c423c5096c47f04a",
        "type": "mqtt out",
        "z": "9aafbf849d4d6e12",
        "name": "",
        "topic": "/external/cmd/enable_rcd",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 350,
        "y": 840,
        "wires": []
    },
    {
        "id": "edcc986828bdfcc4",
        "type": "comment",
        "z": "9aafbf849d4d6e12",
        "name": "Debug",
        "info": "",
        "x": 110,
        "y": 40,
        "wires": []
    },
    {
        "id": "957c91b46df24e3a",
        "type": "mqtt out",
        "z": "e921db8897354328",
        "name": "",
        "topic": "everest_api/dummy_token_provider/cmd/provide",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 1000,
        "y": 120,
        "wires": []
    },
    {
        "id": "765ecd7720d3a54e",
        "type": "ui_dropdown",
        "z": "e921db8897354328",
        "name": "",
        "label": "id_token",
        "tooltip": "",
        "place": "Select option",
        "group": "7cd2ccabb1265f7a",
        "order": 1,
        "width": 0,
        "height": 0,
        "passthru": true,
        "multiple": false,
        "options": [
            {
                "label": "DEADBEEF",
                "value": "DEADBEEF",
                "type": "str"
            },
            {
                "label": "ABC12345",
                "value": "ABC12345",
                "type": "str"
            },
            {
                "label": "VID:AABBCCDDEEFF",
                "value": "VID:AABBCCDDEEFF",
                "type": "str"
            }
        ],
        "payload": "",
        "topic": "id_token",
        "topicType": "str",
        "x": 410,
        "y": 100,
        "wires": [
            [
                "061c0a7744e15ba2"
            ]
        ]
    },
    {
        "id": "352aa4429de054a6",
        "type": "ui_switch",
        "z": "e921db8897354328",
        "name": "",
        "label": "prevalidated",
        "tooltip": "",
        "group": "7cd2ccabb1265f7a",
        "order": 3,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "prevalidated",
        "topicType": "str",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 410,
        "y": 220,
        "wires": [
            [
                "061c0a7744e15ba2"
            ]
        ]
    },
    {
        "id": "a950f5bfc61638ee",
        "type": "ui_text_input",
        "z": "e921db8897354328",
        "name": "",
        "label": "type (2-32)",
        "tooltip": "",
        "group": "7cd2ccabb1265f7a",
        "order": 2,
        "width": 0,
        "height": 0,
        "passthru": true,
        "mode": "text",
        "delay": 300,
        "topic": "token_type",
        "topicType": "str",
        "x": 410,
        "y": 160,
        "wires": [
            [
                "061c0a7744e15ba2"
            ]
        ]
    },
    {
        "id": "be6b6aec975d1d27",
        "type": "debug",
        "z": "e921db8897354328",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 890,
        "y": 200,
        "wires": []
    },
    {
        "id": "054b87b685588c2c",
        "type": "inject",
        "z": "e921db8897354328",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "id_token",
        "payload": "DEADBEEF",
        "payloadType": "str",
        "x": 180,
        "y": 100,
        "wires": [
            [
                "765ecd7720d3a54e"
            ]
        ]
    },
    {
        "id": "045f2c88f1a7077e",
        "type": "inject",
        "z": "e921db8897354328",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "token_type",
        "payload": "RFID",
        "payloadType": "str",
        "x": 200,
        "y": 160,
        "wires": [
            [
                "a950f5bfc61638ee"
            ]
        ]
    },
    {
        "id": "b3b44ea9f00b7d45",
        "type": "inject",
        "z": "e921db8897354328",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "prevalidated",
        "payload": "false",
        "payloadType": "bool",
        "x": 170,
        "y": 220,
        "wires": [
            [
                "352aa4429de054a6"
            ]
        ]
    },
    {
        "id": "061c0a7744e15ba2",
        "type": "function",
        "z": "e921db8897354328",
        "name": "Swipe token",
        "func": "if (msg.topic.indexOf('id_token') > -1) flow.set('id_token', msg.payload);\nif (msg.topic.indexOf('token_type') > -1) flow.set('token_type', msg.payload);\nif (msg.topic.indexOf('prevalidated') > -1) flow.set('prevalidated', msg.payload);\nif (msg.topic.indexOf('connectors') > -1) flow.set('connectors', msg.payload);\nif (msg.topic.indexOf('complete') > -1) {\n    msg.payload = {\n        'id_token': {\n    'value': flow.get('id_token'),\n 'type': 'ISO14443'\n},\n        'authorization_type': flow.get('token_type'),\n        'prevalidated': flow.get('prevalidated'),\n        'connectors': [flow.get('connectors')]\n    };\n    return msg;\n}\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 690,
        "y": 160,
        "wires": [
            [
                "be6b6aec975d1d27",
                "957c91b46df24e3a"
            ]
        ]
    },
    {
        "id": "6cc9edbf2f3b9aa6",
        "type": "ui_button",
        "z": "e921db8897354328",
        "name": "",
        "group": "7cd2ccabb1265f7a",
        "order": 4,
        "width": 0,
        "height": 0,
        "passthru": false,
        "label": "Swipe RFID",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "",
        "payloadType": "str",
        "topic": "complete",
        "topicType": "str",
        "x": 410,
        "y": 380,
        "wires": [
            [
                "061c0a7744e15ba2"
            ]
        ]
    },
    {
        "id": "15f66c1947e2cb18",
        "type": "inject",
        "z": "e921db8897354328",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "connectors",
        "payload": "1",
        "payloadType": "num",
        "x": 150,
        "y": 300,
        "wires": [
            [
                "deedf2d418fbc8fd"
            ]
        ]
    },
    {
        "id": "deedf2d418fbc8fd",
        "type": "ui_numeric",
        "z": "e921db8897354328",
        "name": "",
        "label": "connectors",
        "tooltip": "",
        "group": "7cd2ccabb1265f7a",
        "order": 0,
        "width": "0",
        "height": "0",
        "wrap": false,
        "passthru": true,
        "topic": "connectors",
        "topicType": "msg",
        "format": "{{value}}",
        "min": "1",
        "max": 10,
        "step": 1,
        "x": 410,
        "y": 300,
        "wires": [
            [
                "061c0a7744e15ba2"
            ]
        ]
    },
    {
        "id": "e8e1511b6239bfa2",
        "type": "comment",
        "z": "ed603c51db9dcbb9",
        "name": "Initialize the Connector number",
        "info": "",
        "x": 230,
        "y": 80,
        "wires": []
    },
    {
        "id": "23d875eef4c57fa8",
        "type": "change",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "connector_number",
                "pt": "flow",
                "to": "1",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 440,
        "y": 140,
        "wires": [
            []
        ]
    },
    {
        "id": "e99161497760c072",
        "type": "inject",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payloadType": "date",
        "x": 190,
        "y": 140,
        "wires": [
            [
                "23d875eef4c57fa8"
            ]
        ]
    },
    {
        "id": "b70c30908c955b81",
        "type": "comment",
        "z": "ed603c51db9dcbb9",
        "name": "Data to show",
        "info": "",
        "x": 170,
        "y": 200,
        "wires": []
    },
    {
        "id": "f96ccb60614f9f18",
        "type": "mqtt out",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "topic": "",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 890,
        "y": 840,
        "wires": []
    },
    {
        "id": "3a5423dc1feed224",
        "type": "ui_button",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "group": "b364f7eb4621082b",
        "order": 1,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "Pause",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/cmd/pause_charging",
        "topicType": "str",
        "x": 150,
        "y": 700,
        "wires": [
            [
                "361b3d846c4e6673"
            ]
        ]
    },
    {
        "id": "f042bc45e3742ef7",
        "type": "ui_button",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "group": "b364f7eb4621082b",
        "order": 2,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "Resume",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/cmd/resume_charging",
        "topicType": "str",
        "x": 160,
        "y": 760,
        "wires": [
            [
                "361b3d846c4e6673"
            ]
        ]
    },
    {
        "id": "361b3d846c4e6673",
        "type": "change",
        "z": "ed603c51db9dcbb9",
        "name": "Insert Connector number",
        "rules": [
            {
                "t": "change",
                "p": "topic",
                "pt": "msg",
                "from": "#",
                "fromt": "str",
                "to": "connector_number",
                "tot": "flow"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 670,
        "y": 840,
        "wires": [
            [
                "f96ccb60614f9f18"
            ]
        ]
    },
    {
        "id": "9c6d1a5e2ba43d36",
        "type": "comment",
        "z": "ed603c51db9dcbb9",
        "name": "Commands",
        "info": "",
        "x": 170,
        "y": 640,
        "wires": []
    },
    {
        "id": "51656271f4688a67",
        "type": "mqtt in",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "topic": "everest_external/nodered/+/state/max_current",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 270,
        "y": 280,
        "wires": [
            [
                "f5ca89d3e6d1d1ba"
            ]
        ]
    },
    {
        "id": "f5ca89d3e6d1d1ba",
        "type": "function",
        "z": "ed603c51db9dcbb9",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 590,
        "y": 280,
        "wires": [
            [
                "1a019e35e580cbf4"
            ]
        ]
    },
    {
        "id": "1a019e35e580cbf4",
        "type": "ui_text",
        "z": "ed603c51db9dcbb9",
        "group": "b364f7eb4621082b",
        "order": 3,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Max Current",
        "format": "{{msg.payload | number: 1}}",
        "layout": "row-spread",
        "x": 890,
        "y": 280,
        "wires": []
    },
    {
        "id": "799130c039278e9b",
        "type": "ui_text",
        "z": "ed603c51db9dcbb9",
        "group": "b364f7eb4621082b",
        "order": 5,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Energy Charged",
        "format": "{{msg.payload | number:2}} kWh",
        "layout": "row-spread",
        "x": 880,
        "y": 340,
        "wires": []
    },
    {
        "id": "a24c1a5f8bb6a5b5",
        "type": "function",
        "z": "ed603c51db9dcbb9",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 650,
        "y": 340,
        "wires": [
            [
                "799130c039278e9b"
            ]
        ]
    },
    {
        "id": "7164db07c0a78327",
        "type": "mqtt in",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "topic": "everest_external/nodered/+/powermeter/totalKWattHr",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 300,
        "y": 340,
        "wires": [
            [
                "a24c1a5f8bb6a5b5"
            ]
        ]
    },
    {
        "id": "9715c97a5a212e0f",
        "type": "mqtt in",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "topic": "everest_external/nodered/+/state/state_string",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 270,
        "y": 440,
        "wires": [
            [
                "0139c0ace0e5f706"
            ]
        ]
    },
    {
        "id": "8059bceefd9dcdf7",
        "type": "mqtt in",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "topic": "everest_external/nodered/+/powermeter/totalKw",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 280,
        "y": 500,
        "wires": [
            [
                "6941efd3ef8e1ac4"
            ]
        ]
    },
    {
        "id": "72a90b471d6ebb44",
        "type": "ui_level",
        "z": "ed603c51db9dcbb9",
        "group": "b364f7eb4621082b",
        "order": 7,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Temperature:",
        "colorHi": "#e60000",
        "colorWarn": "#ff9900",
        "colorNormal": "#00b33c",
        "colorOff": "#595959",
        "min": "-20",
        "max": "85",
        "segWarn": "65",
        "segHigh": "75",
        "unit": "",
        "layout": "sh",
        "channelA": "",
        "channelB": "",
        "decimals": 0,
        "animations": "soft",
        "shape": "3",
        "colorschema": "valuedriven",
        "textoptions": "default",
        "colorText": "#eeeeee",
        "fontLabel": "",
        "fontValue": "",
        "fontSmall": "",
        "colorFromTheme": true,
        "textAnimations": false,
        "hideValue": false,
        "tickmode": "segments",
        "peakmode": false,
        "property": "payload",
        "peaktime": 3000,
        "x": 1010,
        "y": 560,
        "wires": []
    },
    {
        "id": "4b864ea8df7d27cf",
        "type": "mqtt in",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "topic": "everest_external/nodered/+/state/temperature",
        "qos": "0",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 280,
        "y": 560,
        "wires": [
            [
                "bc1b6adb5db9d7e4"
            ]
        ]
    },
    {
        "id": "1ef49a48bf883748",
        "type": "function",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "func": "if (msg.topic.indexOf('totalKw')>=0) {\n    console.warn(\"Received totalKw message\");\n    if (context.data.stop_updating_kw) {\n        console.error(\"stopping kw updates because this is annoying\")\n    } else {\n        context.data.totalKw = msg.payload;\n    }\n}\nelse if (msg.topic.indexOf('state_string')>=0) {\n    console.error(\"how can I show the logs\");\n    context.data.state_string = msg.payload;\n    if (msg.payload === 'Charging') {\n        context.data.totalKw = 200;\n        context.data.stop_updating_kw = true;\n    } else {\n        context.data.stop_updating_kw = false;\n    }\n}\n\n//node.warn(msg.topic);\nmsg.payload = context.data.totalKw;\nmsg.label = context.data.state_string;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "// Code added here will be run once\n// whenever the node is started.\ncontext.data = {}",
        "finalize": "",
        "libs": [],
        "x": 980,
        "y": 480,
        "wires": [
            [
                "5bd8abc274e70360",
                "1295e032d7ddbc20"
            ]
        ]
    },
    {
        "id": "5bd8abc274e70360",
        "type": "debug",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1190,
        "y": 380,
        "wires": []
    },
    {
        "id": "1295e032d7ddbc20",
        "type": "ui_gauge",
        "z": "ed603c51db9dcbb9",
        "g": "af1e1eeac9c4b704",
        "name": "",
        "group": "b364f7eb4621082b",
        "order": 6,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "{{msg.label}}",
        "label": "Kilowatt",
        "format": "{{value}} kW",
        "min": "0",
        "max": "11",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "x": 1190,
        "y": 480,
        "wires": []
    },
    {
        "id": "6941efd3ef8e1ac4",
        "type": "function",
        "z": "ed603c51db9dcbb9",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 730,
        "y": 500,
        "wires": [
            [
                "1ef49a48bf883748"
            ]
        ]
    },
    {
        "id": "0139c0ace0e5f706",
        "type": "function",
        "z": "ed603c51db9dcbb9",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 730,
        "y": 460,
        "wires": [
            [
                "1ef49a48bf883748"
            ]
        ]
    },
    {
        "id": "bc1b6adb5db9d7e4",
        "type": "function",
        "z": "ed603c51db9dcbb9",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 710,
        "y": 560,
        "wires": [
            [
                "72a90b471d6ebb44"
            ]
        ]
    },
    {
        "id": "348dfdc4b48ec881",
        "type": "comment",
        "z": "ed603c51db9dcbb9",
        "name": "Simulation control",
        "info": "",
        "x": 190,
        "y": 820,
        "wires": []
    },
    {
        "id": "352d9f34ae594f58",
        "type": "ui_switch",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "label": "Simulation enable (HIL)",
        "tooltip": "",
        "group": "b364f7eb4621082b",
        "order": 10,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "everest_external/nodered/carsim/#/cmd/enable",
        "topicType": "str",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 210,
        "y": 880,
        "wires": [
            [
                "361b3d846c4e6673"
            ]
        ]
    },
    {
        "id": "76be7e55a947f675",
        "type": "ui_button",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "group": "b364f7eb4621082b",
        "order": 8,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "Car Plugin",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "start",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/carsim/cmd/execute_charging_session",
        "topicType": "str",
        "x": 170,
        "y": 980,
        "wires": [
            [
                "620b0d248a89ece0"
            ]
        ]
    },
    {
        "id": "42f4c4f916474559",
        "type": "ui_button",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "group": "b364f7eb4621082b",
        "order": 9,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "Stop & Unplug",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "stop",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/carsim/cmd/modify_charging_session",
        "topicType": "str",
        "x": 180,
        "y": 940,
        "wires": [
            [
                "620b0d248a89ece0"
            ]
        ]
    },
    {
        "id": "cc45f1b73782292a",
        "type": "ui_slider",
        "z": "ed603c51db9dcbb9",
        "name": "MaxCurrent Slider",
        "label": "",
        "tooltip": "",
        "group": "b364f7eb4621082b",
        "order": 4,
        "width": 0,
        "height": 0,
        "passthru": false,
        "outs": "all",
        "topic": "everest_external/nodered/#/cmd/set_max_current",
        "topicType": "str",
        "min": "6",
        "max": "32",
        "step": "0.1",
        "x": 450,
        "y": 700,
        "wires": [
            [
                "361b3d846c4e6673"
            ]
        ]
    },
    {
        "id": "f2ae0c306f3052f9",
        "type": "ui_dropdown",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "label": "Car Simulation",
        "tooltip": "",
        "place": "Select option",
        "group": "b364f7eb4621082b",
        "order": 10,
        "width": 0,
        "height": 0,
        "passthru": true,
        "multiple": false,
        "options": [
            {
                "label": "AC 3ph 16A",
                "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000#unplug#pause;sleep 3600#draw_power_regulated 16,3;sleep 36000",
                "type": "str"
            },
            {
                "label": "AC 1ph 32A",
                "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 32,1;sleep 36000#unplug#pause;sleep 3600#draw_power_regulated 32,1;sleep 36000",
                "type": "str"
            },
            {
                "label": "AC Diode Fail",
                "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 32,3;sleep 5;diode_fail;sleep 36000#unplug",
                "type": "str"
            },
            {
                "label": "AC Error E",
                "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 3;error_e;sleep 36000#unplug",
                "type": "str"
            },
            {
                "label": "AC RCD Error",
                "value": "sleep 1;rcd_current 10.3;sleep 10;rcd_current 0.1#unplug",
                "type": "str"
            },
            {
                "label": "AC ISO15118-2",
                "value": "sleep 1;iso_wait_slac_matched;iso_start_v2g_session ExternalPayment,AC_three_phase_core;iso_wait_pwr_ready;iso_draw_power_regulated 16,3;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug",
                "type": "str"
            },
            {
                "label": "AC ISO15118-2 Plug&Charge",
                "value": "sleep 1;iso_wait_slac_matched;iso_start_v2g_session contract,AC_three_phase_core;iso_wait_pwr_ready;iso_draw_power_regulated 16,3;sleep 36000#;iso_stop_charging;iso_wait_v2g_session_stopped;unplug",
                "type": "str"
            }
        ],
        "payload": "",
        "topic": "sim_commands",
        "topicType": "str",
        "x": 180,
        "y": 1120,
        "wires": [
            [
                "620b0d248a89ece0"
            ]
        ]
    },
    {
        "id": "620b0d248a89ece0",
        "type": "function",
        "z": "ed603c51db9dcbb9",
        "name": "Buffer sim commands",
        "func": "if (msg.topic.indexOf('sim_commands') > -1) {\n    const s = msg.payload.split('#');\n    flow.set('sim_commands_start', s[0]);\n    flow.set('sim_commands_stop', s[1]);\n    flow.set('sim_commands_pause', s[2]);\n    flow.set('sim_commands_resume', s[3]);\n} else if (msg.payload == 'start') {\n    msg.payload = flow.get('sim_commands_start');\n    return msg;\n} else if (msg.payload == 'stop') {\n    msg.payload = flow.get('sim_commands_stop');\n    return msg;\n} else if (msg.payload == 'pause') {\n    msg.payload = flow.get('sim_commands_pause');\n    return msg;\n} else if (msg.payload == 'resume') {\n    msg.payload = flow.get('sim_commands_resume');\n    return msg;\n} else {\n    msg.payload = 'NONE';\n    return msg;\n}\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 440,
        "y": 960,
        "wires": [
            [
                "361b3d846c4e6673",
                "cc42c210398a8d50",
                "fb1511183c9a660f"
            ]
        ]
    },
    {
        "id": "fb1511183c9a660f",
        "type": "debug",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 910,
        "y": 920,
        "wires": []
    },
    {
        "id": "fef2be4575e66bda",
        "type": "inject",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "sim_commands",
        "payload": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000#unplug#pause;sleep 3600#draw_power_regulated 16,3;sleep 36000",
        "payloadType": "str",
        "x": 150,
        "y": 1180,
        "wires": [
            [
                "f2ae0c306f3052f9"
            ]
        ]
    },
    {
        "id": "cc42c210398a8d50",
        "type": "debug",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "topic",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 900,
        "y": 960,
        "wires": []
    },
    {
        "id": "29aced2052ea8795",
        "type": "ui_button",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "group": "b364f7eb4621082b",
        "order": 9,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "EV Pause",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "className": "",
        "icon": "",
        "payload": "pause",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/carsim/cmd/modify_charging_session",
        "topicType": "str",
        "x": 160,
        "y": 1020,
        "wires": [
            [
                "620b0d248a89ece0"
            ]
        ]
    },
    {
        "id": "f39e2131a37761a6",
        "type": "ui_button",
        "z": "ed603c51db9dcbb9",
        "name": "",
        "group": "b364f7eb4621082b",
        "order": 9,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "EV Resume",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "className": "",
        "icon": "",
        "payload": "resume",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/carsim/cmd/modify_charging_session",
        "topicType": "str",
        "x": 170,
        "y": 1060,
        "wires": [
            [
                "620b0d248a89ece0"
            ]
        ]
    },
    {
        "id": "ecc79f3cd38a4ac7",
        "type": "comment",
        "z": "1922139a3ea7cac2",
        "name": "Initialize the Connector number",
        "info": "",
        "x": 210,
        "y": 60,
        "wires": []
    },
    {
        "id": "d98f9f6a2a628af8",
        "type": "change",
        "z": "1922139a3ea7cac2",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "connector_number",
                "pt": "flow",
                "to": "2",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 420,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "b5681fe7ddcfb809",
        "type": "inject",
        "z": "1922139a3ea7cac2",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payloadType": "date",
        "x": 170,
        "y": 120,
        "wires": [
            [
                "d98f9f6a2a628af8"
            ]
        ]
    },
    {
        "id": "1cf693c49c5dd54f",
        "type": "comment",
        "z": "1922139a3ea7cac2",
        "name": "Data to show",
        "info": "",
        "x": 150,
        "y": 180,
        "wires": []
    },
    {
        "id": "2cfbe1b2a5481d9d",
        "type": "mqtt in",
        "z": "1922139a3ea7cac2",
        "name": "",
        "topic": "everest_external/nodered/+/state/max_current",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 250,
        "y": 260,
        "wires": [
            [
                "2653d111a7a2fbf8"
            ]
        ]
    },
    {
        "id": "2653d111a7a2fbf8",
        "type": "function",
        "z": "1922139a3ea7cac2",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 570,
        "y": 260,
        "wires": [
            [
                "8d44530a6ffa85d4"
            ]
        ]
    },
    {
        "id": "8d44530a6ffa85d4",
        "type": "ui_text",
        "z": "1922139a3ea7cac2",
        "group": "21e40a4a97a50168",
        "order": 3,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Max Current",
        "format": "{{msg.payload | number: 1}}",
        "layout": "row-spread",
        "x": 870,
        "y": 260,
        "wires": []
    },
    {
        "id": "278d0137968bab01",
        "type": "ui_text",
        "z": "1922139a3ea7cac2",
        "group": "21e40a4a97a50168",
        "order": 5,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Energy Charged",
        "format": "{{msg.payload | number:2}} kWh",
        "layout": "row-spread",
        "x": 860,
        "y": 320,
        "wires": []
    },
    {
        "id": "b0aa9456cb82aa5e",
        "type": "function",
        "z": "1922139a3ea7cac2",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 630,
        "y": 320,
        "wires": [
            [
                "278d0137968bab01"
            ]
        ]
    },
    {
        "id": "7ca5f3a2f6c6442b",
        "type": "mqtt in",
        "z": "1922139a3ea7cac2",
        "name": "",
        "topic": "everest_external/nodered/+/powermeter/totalKWattHr",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 280,
        "y": 320,
        "wires": [
            [
                "b0aa9456cb82aa5e"
            ]
        ]
    },
    {
        "id": "ba393fc06d165d2c",
        "type": "mqtt in",
        "z": "1922139a3ea7cac2",
        "name": "",
        "topic": "everest_external/nodered/+/state/state_string",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 250,
        "y": 420,
        "wires": [
            [
                "e961b36955a2f4b2"
            ]
        ]
    },
    {
        "id": "82bf4c3a0cd4e019",
        "type": "mqtt in",
        "z": "1922139a3ea7cac2",
        "name": "",
        "topic": "everest_external/nodered/+/powermeter/totalKw",
        "qos": "2",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 260,
        "y": 480,
        "wires": [
            [
                "19c5a9e152316998"
            ]
        ]
    },
    {
        "id": "314120980ce5e5d9",
        "type": "ui_level",
        "z": "1922139a3ea7cac2",
        "group": "21e40a4a97a50168",
        "order": 7,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Temperature:",
        "colorHi": "#e60000",
        "colorWarn": "#ff9900",
        "colorNormal": "#00b33c",
        "colorOff": "#595959",
        "min": "-20",
        "max": "85",
        "segWarn": "65",
        "segHigh": "75",
        "unit": "",
        "layout": "sh",
        "channelA": "",
        "channelB": "",
        "decimals": 0,
        "animations": "soft",
        "shape": "3",
        "colorschema": "valuedriven",
        "textoptions": "default",
        "colorText": "#eeeeee",
        "fontLabel": "",
        "fontValue": "",
        "fontSmall": "",
        "colorFromTheme": true,
        "textAnimations": false,
        "hideValue": false,
        "tickmode": "segments",
        "peakmode": false,
        "property": "payload",
        "peaktime": 3000,
        "x": 990,
        "y": 540,
        "wires": []
    },
    {
        "id": "eb04639c71d52ca7",
        "type": "mqtt in",
        "z": "1922139a3ea7cac2",
        "name": "",
        "topic": "everest_external/nodered/+/state/temperature",
        "qos": "0",
        "datatype": "auto",
        "broker": "fc8686af.48d178",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 260,
        "y": 540,
        "wires": [
            [
                "e3a491d163ef9631"
            ]
        ]
    },
    {
        "id": "f7f91acafa1344ef",
        "type": "function",
        "z": "1922139a3ea7cac2",
        "name": "",
        "func": "if (msg.topic.indexOf('totalKw')>=0) {\n    context.data.totalKw = msg.payload;\n}\nelse if (msg.topic.indexOf('state_string')>=0) {\n    context.data.state_string = msg.payload;\n}\n\n//node.warn(msg.topic);\nmsg.payload = context.data.totalKw;\nmsg.label = context.data.state_string;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "// Code added here will be run once\n// whenever the node is started.\ncontext.data = {}",
        "finalize": "",
        "libs": [],
        "x": 960,
        "y": 460,
        "wires": [
            [
                "84fcecbd2169e979",
                "22139ab4759c1b51"
            ]
        ]
    },
    {
        "id": "84fcecbd2169e979",
        "type": "debug",
        "z": "1922139a3ea7cac2",
        "name": "",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1170,
        "y": 360,
        "wires": []
    },
    {
        "id": "19c5a9e152316998",
        "type": "function",
        "z": "1922139a3ea7cac2",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 710,
        "y": 480,
        "wires": [
            [
                "f7f91acafa1344ef"
            ]
        ]
    },
    {
        "id": "e961b36955a2f4b2",
        "type": "function",
        "z": "1922139a3ea7cac2",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 710,
        "y": 440,
        "wires": [
            [
                "f7f91acafa1344ef"
            ]
        ]
    },
    {
        "id": "e3a491d163ef9631",
        "type": "function",
        "z": "1922139a3ea7cac2",
        "name": "Filter connector number",
        "func": "if (msg.topic.indexOf(String(flow.get('connector_number'))) > -1) return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 690,
        "y": 540,
        "wires": [
            [
                "314120980ce5e5d9"
            ]
        ]
    },
    {
        "id": "22139ab4759c1b51",
        "type": "ui_gauge",
        "z": "1922139a3ea7cac2",
        "g": "6459c14573f03fd2",
        "name": "",
        "group": "21e40a4a97a50168",
        "order": 6,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "{{msg.label}}",
        "label": "Kilowatt",
        "format": "{{value}} kW",
        "min": "0",
        "max": "11",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "x": 1170,
        "y": 460,
        "wires": []
    },
    {
        "id": "871a53340e257a24",
        "type": "mqtt out",
        "z": "1922139a3ea7cac2",
        "name": "",
        "topic": "",
        "qos": "1",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "fc8686af.48d178",
        "x": 890,
        "y": 820,
        "wires": []
    },
    {
        "id": "d6aa7ecbf651cdc8",
        "type": "ui_button",
        "z": "1922139a3ea7cac2",
        "name": "",
        "group": "21e40a4a97a50168",
        "order": 1,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "Pause",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/cmd/pause_charging",
        "topicType": "str",
        "x": 150,
        "y": 680,
        "wires": [
            [
                "1a55c4272ab7ef25"
            ]
        ]
    },
    {
        "id": "69de41290781f633",
        "type": "ui_button",
        "z": "1922139a3ea7cac2",
        "name": "",
        "group": "21e40a4a97a50168",
        "order": 2,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "Resume",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/cmd/resume_charging",
        "topicType": "str",
        "x": 160,
        "y": 740,
        "wires": [
            [
                "1a55c4272ab7ef25"
            ]
        ]
    },
    {
        "id": "1a55c4272ab7ef25",
        "type": "change",
        "z": "1922139a3ea7cac2",
        "name": "Insert Connector number",
        "rules": [
            {
                "t": "change",
                "p": "topic",
                "pt": "msg",
                "from": "#",
                "fromt": "str",
                "to": "connector_number",
                "tot": "flow"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 670,
        "y": 820,
        "wires": [
            [
                "871a53340e257a24",
                "3a84fa8a5b680643"
            ]
        ]
    },
    {
        "id": "1f34c812528f61c2",
        "type": "comment",
        "z": "1922139a3ea7cac2",
        "name": "Commands",
        "info": "",
        "x": 170,
        "y": 620,
        "wires": []
    },
    {
        "id": "41449fc1144ed163",
        "type": "comment",
        "z": "1922139a3ea7cac2",
        "name": "Simulation control",
        "info": "",
        "x": 190,
        "y": 800,
        "wires": []
    },
    {
        "id": "8b728fabda00545f",
        "type": "ui_switch",
        "z": "1922139a3ea7cac2",
        "name": "",
        "label": "Simulation enable (HIL)",
        "tooltip": "",
        "group": "21e40a4a97a50168",
        "order": 10,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "everest_external/nodered/carsim/#/cmd/enable",
        "topicType": "str",
        "style": "",
        "onvalue": "true",
        "onvalueType": "bool",
        "onicon": "",
        "oncolor": "",
        "offvalue": "false",
        "offvalueType": "bool",
        "officon": "",
        "offcolor": "",
        "animate": false,
        "x": 210,
        "y": 860,
        "wires": [
            [
                "1a55c4272ab7ef25"
            ]
        ]
    },
    {
        "id": "d54b331450b2ec12",
        "type": "ui_button",
        "z": "1922139a3ea7cac2",
        "name": "",
        "group": "21e40a4a97a50168",
        "order": 8,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "Car Plugin",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/carsim/cmd/execute_charging_session",
        "topicType": "str",
        "x": 170,
        "y": 960,
        "wires": [
            [
                "cdef4125b099333e"
            ]
        ]
    },
    {
        "id": "9f33338bd8c492d8",
        "type": "ui_button",
        "z": "1922139a3ea7cac2",
        "name": "",
        "group": "21e40a4a97a50168",
        "order": 9,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "Car unplug",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "unplug",
        "payloadType": "str",
        "topic": "everest_external/nodered/#/carsim/cmd/modify_charging_session",
        "topicType": "str",
        "x": 170,
        "y": 920,
        "wires": [
            [
                "1a55c4272ab7ef25"
            ]
        ]
    },
    {
        "id": "46ed28a1fb0c8abc",
        "type": "ui_slider",
        "z": "1922139a3ea7cac2",
        "name": "MaxCurrent Slider",
        "label": "",
        "tooltip": "",
        "group": "21e40a4a97a50168",
        "order": 4,
        "width": 0,
        "height": 0,
        "passthru": false,
        "outs": "all",
        "topic": "everest_external/nodered/#/cmd/set_max_current",
        "topicType": "str",
        "min": "6",
        "max": "32",
        "step": "0.1",
        "x": 450,
        "y": 680,
        "wires": [
            [
                "1a55c4272ab7ef25"
            ]
        ]
    },
    {
        "id": "3ce436b7f3df46a5",
        "type": "ui_dropdown",
        "z": "1922139a3ea7cac2",
        "name": "",
        "label": "Simulation",
        "tooltip": "",
        "place": "Select option",
        "group": "21e40a4a97a50168",
        "order": 10,
        "width": 0,
        "height": 0,
        "passthru": true,
        "multiple": false,
        "options": [
            {
                "label": "DC ISO15118-2",
                "value": "sleep 1;iso_wait_slac_matched;iso_start_v2g_session ExternalPayment,DC_extended;iso_wait_pwr_ready;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug#iso_pause_charging;iso_wait_for_resume#iso_start_bcb_toogle 3;iso_wait_pwm_is_running;iso_start_v2g_session ExternalPayment,DC_extended;iso_wait_pwr_ready;sleep 36000;",
                "type": "str"
            },
            {
                "label": "DC ISO15118-2 PnC",
                "value": "sleep 1;iso_wait_slac_matched;iso_start_v2g_session contract,DC_extended;iso_wait_pwr_ready;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug#iso_pause_charging;iso_wait_for_resume#iso_start_bcb_toogle 3;iso_wait_pwm_is_running;iso_start_v2g_session contract,DC_extended;iso_wait_pwr_ready;sleep 36000;",
                "type": "str"
            }
        ],
        "payload": "",
        "topic": "sim_commands",
        "topicType": "str",
        "x": 170,
        "y": 1020,
        "wires": [
            [
                "cdef4125b099333e"
            ]
        ]
    },
    {
        "id": "cdef4125b099333e",
        "type": "function",
        "z": "1922139a3ea7cac2",
        "name": "Buffer sim commands",
        "func": "if (msg.topic.indexOf('sim_commands') > -1) {\n    flow.set('sim_commands', msg.payload);\n} else {\n    msg.payload = flow.get('sim_commands');\n    return msg;\n}\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 440,
        "y": 940,
        "wires": [
            [
                "1a55c4272ab7ef25"
            ]
        ]
    },
    {
        "id": "3a84fa8a5b680643",
        "type": "debug",
        "z": "1922139a3ea7cac2",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 910,
        "y": 900,
        "wires": []
    },
    {
        "id": "63a055756688689a",
        "type": "inject",
        "z": "1922139a3ea7cac2",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "sim_commands",
        "payload": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug",
        "payloadType": "str",
        "x": 150,
        "y": 1080,
        "wires": [
            [
                "3ce436b7f3df46a5"
            ]
        ]
    }
]

However, several of the combinations failed with weird errors. For example, the AC EIM test failed because the manager reported that only the DC_Extended Energy Transfer mode was working although it is should be connected to evse_manager_1, which has AC power configured.

We need to investigate this further, but for now, since the HIL demo runs on hardware with a single port , let's have stwo separate configs (AC and DC) and two separate nodered files (AC and DC).

shankari added a commit to US-JOET/everest-demo that referenced this issue Jun 9, 2024
- Use a nodered flow that only supports AC ISO 15118-2 EIM and PnC
- Patch SwitchEV to plumb through the payment option to the ISO 15118 library
  by following the EnergyTransfer route
  EVerest#44 (comment)
  https://lfenergy.zulipchat.com/#narrow/stream/417677-EVerest.3A-Car-com.2E/topic/.E2.9C.94.20Update.20Node-red.20flows.20because.20of.20JsEvManager.20commit/near/443617217
- Enable `iso15118car` debug logging so that we can see the decoded EXI messages in the absence of
  https://lfenergy.zulipchat.com/#narrow/stream/417677-EVerest.3A-Car-com.2E/topic/EXI.20V2G.20Decoder.20Recommendations/near/442318988
- disable the maeve patches to remove the LB since it is already removed in the updated branch

Testing done:

```
patching file docker-compose.yml
Patching the CSMS to enable local mo root
patching file 'config/manager/config.toml'
Patching the CSMS to enable local mo root
patching file 'manager/handlers/ocpp201/authorize.go'
Starting the CSMS
```

```
[+] Running 4/4
 ✔ Container maeve-csms-firestore-1  Running                                                              0.0s
 ✔ Container maeve-csms-mqtt-1       Healthy                                                              0.1s
 ✔ Container maeve-csms-manager-1    Healthy                                                              0.2s
 ✔ Container maeve-csms-gateway-1    Started                                                              0.2s
Waiting 5s for CSMS to start...
MaEVe CSMS started, adding charge station with Security Profile 3 (note: profiles in MaEVe start with 0 so SP-2 == OCPP SP-3)
Charge station added, adding user token
API calls to CSMS finished, starting EVerest...
```

```
 ✔ Network everest-ac-demo_default          Created                                                       0.1s
 ✔ Container everest-ac-demo-mqtt-server-1  Healthy                                                       0.1s
 ✔ Container everest-ac-demo-nodered-1      Healthy                                                       0.2s
 ✔ Container everest-ac-demo-manager-1      Healthy                                                       0.1s
Successfully copied 60.9kB to everest-ac-demo-nodered-1:/config/config-sil-two-evse-flow.json
everest-ac-demo-nodered-1
Successfully copied 6.66kB to everest-ac-demo-manager-1:/ext/source/config/config-sil-ocpp201-pnc.yaml
Successfully copied 8.19kB to everest-ac-demo-manager-1:/tmp/
Successfully copied 3.58kB to everest-ac-demo-manager-1:/ext/source/build/dist/etc/everest/default_logging.cfg
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/edge/testing/x86_64/APKINDEX.tar.gz
(1/1) Installing patch (2.7.6-r9)
Executing busybox-1.35.0-r29.trigger
OK: 1379 MiB in 230 packages
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py
Successfully copied 29.2kB to everest-ac-demo-manager-1:/ext/source/build
```

```
2024-06-09 19:41:31.667368 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"04337FC4777FB77D"},"Body":{"ServiceDiscoveryRes":{"ResponseCode":"OK","PaymentOptionList":{"PaymentOption":["ExternalPayment","Contract"]},"ChargeService":{"ServiceID":1,"ServiceCategory":"EVCharging","FreeService":false,"SupportedEnergyTransferMode":{"EnergyTransferMode":["AC_single_phase_core","AC_three_phase_core"]}},"ServiceList":{"Service":[{"ServiceID":2,"ServiceName":"Certificate","ServiceCategory":"ContractCertificate","FreeService":true}]}}}}}
2024-06-09 19:41:31.670311 [WARN] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: V2G_PAYMENT: in function read value from state AuthEnum.PNC_V2
2024-06-09 19:41:31.671413 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "04337FC4777FB77D"}, "Body": {"PaymentServiceSelectionReq": {"SelectedPaymentOption": "Contract", "SelectedServiceList": {"SelectedService": [{"ServiceID": 1}]}}}}}
2024-06-09 19:41:34.155595 [INFO] ocpp:OCPP201     :: CSMS certificate status: Accepted
2024-06-09 19:41:34.159815 [INFO] auth:Auth        :: Providing authorization to connector#1
2024-06-09 19:41:35.873697 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "04337FC4777FB77D"}, "Body": {"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": "AC_three_phase_core", "AC_EVChargeParameter": {"DepartureTime": 0, "EAmount": {"Value": 60, "Multiplier": 0, "Unit": "Wh"}, "EVMaxVoltage": {"Value": 400, "Multiplier": 0, "Unit": "V"}, "EVMaxCurrent": {"Value": 32000, "Multiplier": -3, "Unit": "A"}, "EVMinCurrent": {"Value": 10, "Multiplier": 0, "Unit": "A"}}}}}}
2024-06-09 19:41:36.948664 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"04337FC4777FB77D"},"Body":{"ChargeParameterDiscoveryRes":{"ResponseCode":"OK","EVSEProcessing":"Finished","SAScheduleList":{"SAScheduleTuple":[{"SAScheduleTupleID":1,"PMaxSchedule":{"PMaxScheduleEntry":[{"RelativeTimeInterval":{"start":0,"duration":86400},"PMax":{"Multiplier":0,"Unit":"W","Value":22080}}]}}]},"AC_EVSEChargeParameter":{"AC_EVSEStatus":{"NotificationMaxDelay":0,"EVSENotification":"None","RCD":false},"EVSENominalVoltage":{"Multiplier":-1,"Unit":"V","Value":2300},"EVSEMaxCurrent":{"Multiplier":-1,"Unit":"A","Value":320}}}}}}
```
shankari added a commit to US-JOET/everest-demo that referenced this issue Jun 9, 2024
- Use a nodered flow that only supports AC ISO 15118-2 EIM and PnC
- Patch SwitchEV to plumb through the payment option to the ISO 15118 library
  by following the EnergyTransfer route
  EVerest#44 (comment)
  https://lfenergy.zulipchat.com/#narrow/stream/417677-EVerest.3A-Car-com.2E/topic/.E2.9C.94.20Update.20Node-red.20flows.20because.20of.20JsEvManager.20commit/near/443617217
- Enable `iso15118car` debug logging so that we can see the decoded EXI messages in the absence of
  https://lfenergy.zulipchat.com/#narrow/stream/417677-EVerest.3A-Car-com.2E/topic/EXI.20V2G.20Decoder.20Recommendations/near/442318988
- disable the maeve patches to remove the LB since it is already removed in the updated branch

Testing done:

```
patching file docker-compose.yml
Patching the CSMS to enable local mo root
patching file 'config/manager/config.toml'
Patching the CSMS to enable local mo root
patching file 'manager/handlers/ocpp201/authorize.go'
Starting the CSMS
```

```
[+] Running 4/4
 ✔ Container maeve-csms-firestore-1  Running                                                              0.0s
 ✔ Container maeve-csms-mqtt-1       Healthy                                                              0.1s
 ✔ Container maeve-csms-manager-1    Healthy                                                              0.2s
 ✔ Container maeve-csms-gateway-1    Started                                                              0.2s
Waiting 5s for CSMS to start...
MaEVe CSMS started, adding charge station with Security Profile 3 (note: profiles in MaEVe start with 0 so SP-2 == OCPP SP-3)
Charge station added, adding user token
API calls to CSMS finished, starting EVerest...
```

```
 ✔ Network everest-ac-demo_default          Created                                                       0.1s
 ✔ Container everest-ac-demo-mqtt-server-1  Healthy                                                       0.1s
 ✔ Container everest-ac-demo-nodered-1      Healthy                                                       0.2s
 ✔ Container everest-ac-demo-manager-1      Healthy                                                       0.1s
Successfully copied 60.9kB to everest-ac-demo-nodered-1:/config/config-sil-two-evse-flow.json
everest-ac-demo-nodered-1
Successfully copied 6.66kB to everest-ac-demo-manager-1:/ext/source/config/config-sil-ocpp201-pnc.yaml
Successfully copied 8.19kB to everest-ac-demo-manager-1:/tmp/
Successfully copied 3.58kB to everest-ac-demo-manager-1:/ext/source/build/dist/etc/everest/default_logging.cfg
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/edge/testing/x86_64/APKINDEX.tar.gz
(1/1) Installing patch (2.7.6-r9)
Executing busybox-1.35.0-r29.trigger
OK: 1379 MiB in 230 packages
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py
Successfully copied 29.2kB to everest-ac-demo-manager-1:/ext/source/build
```

```
2024-06-09 19:41:31.667368 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"04337FC4777FB77D"},"Body":{"ServiceDiscoveryRes":{"ResponseCode":"OK","PaymentOptionList":{"PaymentOption":["ExternalPayment","Contract"]},"ChargeService":{"ServiceID":1,"ServiceCategory":"EVCharging","FreeService":false,"SupportedEnergyTransferMode":{"EnergyTransferMode":["AC_single_phase_core","AC_three_phase_core"]}},"ServiceList":{"Service":[{"ServiceID":2,"ServiceName":"Certificate","ServiceCategory":"ContractCertificate","FreeService":true}]}}}}}
2024-06-09 19:41:31.670311 [WARN] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: V2G_PAYMENT: in function read value from state AuthEnum.PNC_V2
2024-06-09 19:41:31.671413 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "04337FC4777FB77D"}, "Body": {"PaymentServiceSelectionReq": {"SelectedPaymentOption": "Contract", "SelectedServiceList": {"SelectedService": [{"ServiceID": 1}]}}}}}
2024-06-09 19:41:34.155595 [INFO] ocpp:OCPP201     :: CSMS certificate status: Accepted
2024-06-09 19:41:34.159815 [INFO] auth:Auth        :: Providing authorization to connector#1
2024-06-09 19:41:35.873697 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "04337FC4777FB77D"}, "Body": {"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": "AC_three_phase_core", "AC_EVChargeParameter": {"DepartureTime": 0, "EAmount": {"Value": 60, "Multiplier": 0, "Unit": "Wh"}, "EVMaxVoltage": {"Value": 400, "Multiplier": 0, "Unit": "V"}, "EVMaxCurrent": {"Value": 32000, "Multiplier": -3, "Unit": "A"}, "EVMinCurrent": {"Value": 10, "Multiplier": 0, "Unit": "A"}}}}}}
2024-06-09 19:41:36.948664 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"04337FC4777FB77D"},"Body":{"ChargeParameterDiscoveryRes":{"ResponseCode":"OK","EVSEProcessing":"Finished","SAScheduleList":{"SAScheduleTuple":[{"SAScheduleTupleID":1,"PMaxSchedule":{"PMaxScheduleEntry":[{"RelativeTimeInterval":{"start":0,"duration":86400},"PMax":{"Multiplier":0,"Unit":"W","Value":22080}}]}}]},"AC_EVSEChargeParameter":{"AC_EVSEStatus":{"NotificationMaxDelay":0,"EVSENotification":"None","RCD":false},"EVSENominalVoltage":{"Multiplier":-1,"Unit":"V","Value":2300},"EVSEMaxCurrent":{"Multiplier":-1,"Unit":"A","Value":320}}}}}}
```

Signed-off-by: Shankari <[email protected]>
shankari added a commit to US-JOET/everest-demo that referenced this issue Jun 9, 2024
- Use a nodered flow that only supports AC ISO 15118-2 EIM and PnC
- Patch SwitchEV to plumb through the payment option to the ISO 15118 library
  by following the EnergyTransfer route
  EVerest#44 (comment)
  https://lfenergy.zulipchat.com/#narrow/stream/417677-EVerest.3A-Car-com.2E/topic/.E2.9C.94.20Update.20Node-red.20flows.20because.20of.20JsEvManager.20commit/near/443617217
- Enable `iso15118car` debug logging so that we can see the decoded EXI messages in the absence of
  https://lfenergy.zulipchat.com/#narrow/stream/417677-EVerest.3A-Car-com.2E/topic/EXI.20V2G.20Decoder.20Recommendations/near/442318988
- disable the maeve patches to remove the LB since it is already removed in the updated branch

Testing done:

```
patching file docker-compose.yml
Patching the CSMS to enable local mo root
patching file 'config/manager/config.toml'
Patching the CSMS to enable local mo root
patching file 'manager/handlers/ocpp201/authorize.go'
Starting the CSMS
```

```
[+] Running 4/4
 ✔ Container maeve-csms-firestore-1  Running                                                              0.0s
 ✔ Container maeve-csms-mqtt-1       Healthy                                                              0.1s
 ✔ Container maeve-csms-manager-1    Healthy                                                              0.2s
 ✔ Container maeve-csms-gateway-1    Started                                                              0.2s
Waiting 5s for CSMS to start...
MaEVe CSMS started, adding charge station with Security Profile 3 (note: profiles in MaEVe start with 0 so SP-2 == OCPP SP-3)
Charge station added, adding user token
API calls to CSMS finished, starting EVerest...
```

```
 ✔ Network everest-ac-demo_default          Created                                                       0.1s
 ✔ Container everest-ac-demo-mqtt-server-1  Healthy                                                       0.1s
 ✔ Container everest-ac-demo-nodered-1      Healthy                                                       0.2s
 ✔ Container everest-ac-demo-manager-1      Healthy                                                       0.1s
Successfully copied 60.9kB to everest-ac-demo-nodered-1:/config/config-sil-two-evse-flow.json
everest-ac-demo-nodered-1
Successfully copied 6.66kB to everest-ac-demo-manager-1:/ext/source/config/config-sil-ocpp201-pnc.yaml
Successfully copied 8.19kB to everest-ac-demo-manager-1:/tmp/
Successfully copied 3.58kB to everest-ac-demo-manager-1:/ext/source/build/dist/etc/everest/default_logging.cfg
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/edge/testing/x86_64/APKINDEX.tar.gz
(1/1) Installing patch (2.7.6-r9)
Executing busybox-1.35.0-r29.trigger
OK: 1379 MiB in 230 packages
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py
Successfully copied 29.2kB to everest-ac-demo-manager-1:/ext/source/build
```

```
2024-06-09 19:41:31.667368 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"04337FC4777FB77D"},"Body":{"ServiceDiscoveryRes":{"ResponseCode":"OK","PaymentOptionList":{"PaymentOption":["ExternalPayment","Contract"]},"ChargeService":{"ServiceID":1,"ServiceCategory":"EVCharging","FreeService":false,"SupportedEnergyTransferMode":{"EnergyTransferMode":["AC_single_phase_core","AC_three_phase_core"]}},"ServiceList":{"Service":[{"ServiceID":2,"ServiceName":"Certificate","ServiceCategory":"ContractCertificate","FreeService":true}]}}}}}
2024-06-09 19:41:31.670311 [WARN] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: V2G_PAYMENT: in function read value from state AuthEnum.PNC_V2
2024-06-09 19:41:31.671413 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "04337FC4777FB77D"}, "Body": {"PaymentServiceSelectionReq": {"SelectedPaymentOption": "Contract", "SelectedServiceList": {"SelectedService": [{"ServiceID": 1}]}}}}}
2024-06-09 19:41:34.155595 [INFO] ocpp:OCPP201     :: CSMS certificate status: Accepted
2024-06-09 19:41:34.159815 [INFO] auth:Auth        :: Providing authorization to connector#1
2024-06-09 19:41:35.873697 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "04337FC4777FB77D"}, "Body": {"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": "AC_three_phase_core", "AC_EVChargeParameter": {"DepartureTime": 0, "EAmount": {"Value": 60, "Multiplier": 0, "Unit": "Wh"}, "EVMaxVoltage": {"Value": 400, "Multiplier": 0, "Unit": "V"}, "EVMaxCurrent": {"Value": 32000, "Multiplier": -3, "Unit": "A"}, "EVMinCurrent": {"Value": 10, "Multiplier": 0, "Unit": "A"}}}}}}
2024-06-09 19:41:36.948664 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"04337FC4777FB77D"},"Body":{"ChargeParameterDiscoveryRes":{"ResponseCode":"OK","EVSEProcessing":"Finished","SAScheduleList":{"SAScheduleTuple":[{"SAScheduleTupleID":1,"PMaxSchedule":{"PMaxScheduleEntry":[{"RelativeTimeInterval":{"start":0,"duration":86400},"PMax":{"Multiplier":0,"Unit":"W","Value":22080}}]}}]},"AC_EVSEChargeParameter":{"AC_EVSEStatus":{"NotificationMaxDelay":0,"EVSENotification":"None","RCD":false},"EVSENominalVoltage":{"Multiplier":-1,"Unit":"V","Value":2300},"EVSEMaxCurrent":{"Multiplier":-1,"Unit":"A","Value":320}}}}}}
```

Signed-off-by: Shankari <[email protected]>
@shankari
Copy link
Collaborator Author

shankari commented Jun 10, 2024

For the record, we have also now plumbed through setChargingProfile support in MaEVe so that we can showcase PnC with SmartCharging (at least in the SIL, and even in the HIL if we can get the certs configured correctly).
#55

And have cleaned up the demo to support both PnC and EIM with ISO 15118. The NREL HIL we will use only supports EIM, and this also gives us flexibility during the testival. Both Louis and I have verified that we can run it successfully to the level that the charge session starts; setChargingProfile, of course, is still unimplemented.
#57 (comment)

@shankari
Copy link
Collaborator Author

shankari commented Jun 10, 2024

We also need to be able to limit the power for testing at NREL (and potentially elsewhere). Now, we would do this using the micromegawatt charger BSP https://lfenergy.zulipchat.com/#narrow/stream/417678-EVerest.3A-Framework-.26-Tools/topic/Configuring.20the.20uMWC/near/442699434

But the version of everest currently installed on the uMWC is older and does not support that config option.
EVerest/everest-core@867f83e

We are trying the switch_to_minimum_voltage_after_cable_check flag instead. That should use the min_export_voltage_V, which should be 100V

                        float minvoltage =
                            (config.switch_to_minimum_voltage_after_cable_check ? caps.min_export_voltage_V
                                                                                : config.dc_isolation_voltage_V);

@shankari
Copy link
Collaborator Author

shankari commented Jun 12, 2024

@couryrr-afs has created an updated image, but it is built off the current head. So we need to address the simulator and node-red changes that were introduced in the interim. These are primarily:

Given that none of these are related to certs and it would be desirable to demo PnC and smart charging together, let's try to fix these three issues tonight and focus on getting SP3 MaEVe to work.

shankari added a commit to US-JOET/everest-demo that referenced this issue Jun 12, 2024
This is no longer required given 3372b2b
And it can potentially break given EVerest#44 (comment)

given that our simulator has a single port, let's restore to simplicity

Signed-off-by: Shankari <[email protected]>
the-bay-kay pushed a commit to the-bay-kay/everest-demo that referenced this issue Jun 12, 2024
- Use a nodered flow that only supports AC ISO 15118-2 EIM and PnC
- Patch SwitchEV to plumb through the payment option to the ISO 15118 library
  by following the EnergyTransfer route
  EVerest#44 (comment)
  https://lfenergy.zulipchat.com/#narrow/stream/417677-EVerest.3A-Car-com.2E/topic/.E2.9C.94.20Update.20Node-red.20flows.20because.20of.20JsEvManager.20commit/near/443617217
- Enable `iso15118car` debug logging so that we can see the decoded EXI messages in the absence of
  https://lfenergy.zulipchat.com/#narrow/stream/417677-EVerest.3A-Car-com.2E/topic/EXI.20V2G.20Decoder.20Recommendations/near/442318988
- disable the maeve patches to remove the LB since it is already removed in the updated branch

Testing done:

```
patching file docker-compose.yml
Patching the CSMS to enable local mo root
patching file 'config/manager/config.toml'
Patching the CSMS to enable local mo root
patching file 'manager/handlers/ocpp201/authorize.go'
Starting the CSMS
```

```
[+] Running 4/4
 ✔ Container maeve-csms-firestore-1  Running                                                              0.0s
 ✔ Container maeve-csms-mqtt-1       Healthy                                                              0.1s
 ✔ Container maeve-csms-manager-1    Healthy                                                              0.2s
 ✔ Container maeve-csms-gateway-1    Started                                                              0.2s
Waiting 5s for CSMS to start...
MaEVe CSMS started, adding charge station with Security Profile 3 (note: profiles in MaEVe start with 0 so SP-2 == OCPP SP-3)
Charge station added, adding user token
API calls to CSMS finished, starting EVerest...
```

```
 ✔ Network everest-ac-demo_default          Created                                                       0.1s
 ✔ Container everest-ac-demo-mqtt-server-1  Healthy                                                       0.1s
 ✔ Container everest-ac-demo-nodered-1      Healthy                                                       0.2s
 ✔ Container everest-ac-demo-manager-1      Healthy                                                       0.1s
Successfully copied 60.9kB to everest-ac-demo-nodered-1:/config/config-sil-two-evse-flow.json
everest-ac-demo-nodered-1
Successfully copied 6.66kB to everest-ac-demo-manager-1:/ext/source/config/config-sil-ocpp201-pnc.yaml
Successfully copied 8.19kB to everest-ac-demo-manager-1:/tmp/
Successfully copied 3.58kB to everest-ac-demo-manager-1:/ext/source/build/dist/etc/everest/default_logging.cfg
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/edge/testing/x86_64/APKINDEX.tar.gz
(1/1) Installing patch (2.7.6-r9)
Executing busybox-1.35.0-r29.trigger
OK: 1379 MiB in 230 packages
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py
patching file source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py
Successfully copied 29.2kB to everest-ac-demo-manager-1:/ext/source/build
```

```
2024-06-09 19:41:31.667368 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"04337FC4777FB77D"},"Body":{"ServiceDiscoveryRes":{"ResponseCode":"OK","PaymentOptionList":{"PaymentOption":["ExternalPayment","Contract"]},"ChargeService":{"ServiceID":1,"ServiceCategory":"EVCharging","FreeService":false,"SupportedEnergyTransferMode":{"EnergyTransferMode":["AC_single_phase_core","AC_three_phase_core"]}},"ServiceList":{"Service":[{"ServiceID":2,"ServiceName":"Certificate","ServiceCategory":"ContractCertificate","FreeService":true}]}}}}}
2024-06-09 19:41:31.670311 [WARN] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: V2G_PAYMENT: in function read value from state AuthEnum.PNC_V2
2024-06-09 19:41:31.671413 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "04337FC4777FB77D"}, "Body": {"PaymentServiceSelectionReq": {"SelectedPaymentOption": "Contract", "SelectedServiceList": {"SelectedService": [{"ServiceID": 1}]}}}}}
2024-06-09 19:41:34.155595 [INFO] ocpp:OCPP201     :: CSMS certificate status: Accepted
2024-06-09 19:41:34.159815 [INFO] auth:Auth        :: Providing authorization to connector#1
2024-06-09 19:41:35.873697 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Message to encode (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message": {"Header": {"SessionID": "04337FC4777FB77D"}, "Body": {"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": "AC_three_phase_core", "AC_EVChargeParameter": {"DepartureTime": 0, "EAmount": {"Value": 60, "Multiplier": 0, "Unit": "Wh"}, "EVMaxVoltage": {"Value": 400, "Multiplier": 0, "Unit": "V"}, "EVMaxCurrent": {"Value": 32000, "Multiplier": -3, "Unit": "A"}, "EVMinCurrent": {"Value": 10, "Multiplier": 0, "Unit": "A"}}}}}}
2024-06-09 19:41:36.948664 [DEBG] iso15118_car    pybind11_init_everestpy(pybind11::module_&)::<lambda(const std::string&)> :: Decoded message (ns=urn:iso:15118:2:2013:MsgDef): {"V2G_Message":{"Header":{"SessionID":"04337FC4777FB77D"},"Body":{"ChargeParameterDiscoveryRes":{"ResponseCode":"OK","EVSEProcessing":"Finished","SAScheduleList":{"SAScheduleTuple":[{"SAScheduleTupleID":1,"PMaxSchedule":{"PMaxScheduleEntry":[{"RelativeTimeInterval":{"start":0,"duration":86400},"PMax":{"Multiplier":0,"Unit":"W","Value":22080}}]}}]},"AC_EVSEChargeParameter":{"AC_EVSEStatus":{"NotificationMaxDelay":0,"EVSENotification":"None","RCD":false},"EVSENominalVoltage":{"Multiplier":-1,"Unit":"V","Value":2300},"EVSEMaxCurrent":{"Multiplier":-1,"Unit":"A","Value":320}}}}}}
```

Signed-off-by: Shankari <[email protected]>
Signed-off-by: the-bay-kay <[email protected]>
@shankari
Copy link
Collaborator Author

shankari commented Jun 13, 2024

@couryrr-afs @drmrd I am a bit confused about the version of libocpp in the docker image that @couryrr-afs created.
As @drmrd knows, I created several just-in-time patches to get the smart charging demo to not crash. In order to figure out what the patches were, I:

  • docker cped the directory from my container to my laptop
  • checked out the charin-e2e-demo
  • diffed the two diff -r -uw lib /tmp/create_patches/libocpp/lib

However, this is generating several changes that I definitely did not make - including several changes in OCPP 1.6
Hasn't the image been created with the correct version of the OCPP library?

diff -r -uw lib/ocpp/common/database/database_connection.cpp /tmp/create_patches/libocpp/lib/ocpp/common/database/database_connection.cpp
--- lib/ocpp/common/database/database_connection.cpp    2024-06-12 08:52:38
+++ /tmp/create_patches/libocpp/lib/ocpp/common/database/database_connection.cpp    2024-06-11 14:29:41
@@ -63,6 +63,7 @@

     // Add special exception for databases in ram; we don't need to create a path for them
     if (this->database_file_path.string().find(":memory:") == std::string::npos and
+        this->database_file_path.string().find("mode=memory") == std::string::npos and
         !fs::exists(this->database_file_path.parent_path())) {
         fs::create_directories(this->database_file_path.parent_path());
     }
diff -r -uw lib/ocpp/common/websocket/websocket_libwebsockets.cpp /tmp/create_patches/libocpp/lib/ocpp/common/websocket/websocket_libwebsockets.cpp
--- lib/ocpp/common/websocket/websocket_libwebsockets.cpp   2024-06-12 08:52:38
+++ /tmp/create_patches/libocpp/lib/ocpp/common/websocket/websocket_libwebsockets.cpp   2024-06-11 14:29:41
@@ -208,7 +208,7 @@

 WebsocketTlsTPM::WebsocketTlsTPM(const WebsocketConnectionOptions& connection_options,
                                  std::shared_ptr<EvseSecurity> evse_security) :
-    WebsocketBase(), evse_security(evse_security) {
+    WebsocketBase(), evse_security(evse_security), stop_deferred_handler(false) {

I double checked the code and:

  • US-JOET/charin-e2e-demo: ` if (this->database_file_path.string().find(":memory:") == std::string::npos and
  • US-JOET/main: if (this->database_file_path.string().find(":memory:") == std::string::npos and this->database_file_path.string().find("mode=memory") == std::string::npos and
  • everest/main: if (this->database_file_path.string().find(":memory:") == std::string::npos and this->database_file_path.string().find("mode=memory") == std::string::npos and
  • my container: path /ext/cache/cpm/libocpp/6502037f667273b3f55e917ec94a3fe0a2d27720/libocpp
    if (this->database_file_path.string().find(":memory:") == std::string::npos and this->database_file_path.string().find("mode=memory") == std::string::npos and

So it seems pretty clear that the container does not seem to have pulled from the correct branch, and I wonder if this is the cause of the demo issues we have had. I am going to switch back to main and find the diffs and see if they are more meaningful. But somebody needs to check the resulting container and verify that the correct code is actually in place.

EDIT: There are also significant differences from main. I don't know where this branch came from. So I will cherry-pick my changes only as a patch.

EDIT: Spun up a new container, copied out the libocpp and used diff -r -uw libocpp_unmod/lib libocpp/lib . This seems to give the correct diffs.

@shankari
Copy link
Collaborator Author

shankari commented Jun 13, 2024

At this point, the SIL demo works for me although it is definitely "patchy" 🩹
And AFS needs to fix the 1ph versus 3ph issue that results in the current drawn being much lower than the value set in the profile US-JOET#4 (comment)

Then we need to start the long and tedious process of incorporating the patches in actual code and backing them out of the demo script. In the meanwhile, I will move on to the HIL 🤞

@drmrd
Copy link
Contributor

drmrd commented Jun 13, 2024

Glad this is coming together @shankari! I'm working on a patch this morning for the 1ph vs. 3ph issue we encountered when running E2E yesterday. I'll reach out or come find you if I have build questions.

@drmrd
Copy link
Contributor

drmrd commented Jun 13, 2024

@shankari: I haven't been able to test that this actually works E2E, but the following (very hacky) patch for everest-core should™:

diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp
index 8f8cd979..19df5119 100644
--- a/modules/EvseManager/EvseManager.cpp
+++ b/modules/EvseManager/EvseManager.cpp
@@ -1045,6 +1045,7 @@ bool EvseManager::updateLocalEnergyLimit(types::energy::ExternalLimits l) {
             updateLocalMaxWattLimit(get_powersupply_capabilities().max_export_power_W);
         }
     } else {
+        EVLOG_info << "Setting the following external limits on energy node" << l;
         // apply external limits if they are lower
         local_energy_limits = l;
     }
diff --git a/modules/OCPP201/OCPP201.cpp b/modules/OCPP201/OCPP201.cpp
index ca859750..833ed213 100644
--- a/modules/OCPP201/OCPP201.cpp
+++ b/modules/OCPP201/OCPP201.cpp
@@ -583,13 +583,18 @@ void OCPP201::set_external_limits(const std::map<int32_t, ocpp::v201::CompositeS
             types::energy::LimitsReq limits_req;
             const auto timestamp = start_time.to_time_point() + std::chrono::seconds(period.startPeriod);
             schedule_req_entry.timestamp = ocpp::DateTime(timestamp).to_rfc3339();
+            auto limit = period.limit;
             if (schedule.chargingRateUnit == ocpp::v201::ChargingRateUnitEnum::A) {
-                limits_req.ac_max_current_A = period.limit;
                 if (period.numberPhases.has_value()) {
                     limits_req.ac_max_phase_count = period.numberPhases.value();
+                    // Hack to address single phase AC charging
+                    if (period.numberPhases.value() == 1) {
+                        limit *= 3;
+                    }
                 }
+                limits_req.ac_max_current_A = limit;
             } else {
-                limits_req.total_power_W = period.limit;
+                limits_req.total_power_W = limit;
             }
             schedule_req_entry.limits_to_leaves = limits_req;
             schedule_import.push_back(schedule_req_entry);

It'd also be worth testing if AC 3ph or DC profiles work as expected. The main issue here is that we have yet to work through the card that addresses power limit calculations, since it requires adding plumbing in everest-core:

https://github.com/US-JOET/base-camp/issues/111

@shankari
Copy link
Collaborator Author

@drmrd As I indicated, I am busy with setting up HIL. Can AFS please make all the changes and test them so that I can have a good build to put on the HIL? I am also fine with skipping the hack if it works for AC 3-phase, but I would like that to be tested, and the appropriate config provided to me.

@shankari
Copy link
Collaborator Author

shankari commented Jul 2, 2024

The smart charging demo works with multiple patches and will be demoed at the JO All-Hands in the next couple of works.
It is very patchy and hacky but it works.
The patches are being submitted to various repos (#61)
We will continue hardware testing in and follow-ons in separate issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants