EVerest / everest-demo

EVerest demo: Dockerized demo with software in the loop simulation
Apache License 2.0
11 stars 13 forks source link

❇️ Smart charging demo at CharIN #44

Closed shankari closed 1 week ago

shankari commented 1 month ago

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 commented 1 month ago

@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:

Giavotto commented 1 month ago

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 commented 1 month ago

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

shankari commented 1 month ago
shankari commented 1 month ago

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:

@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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

@louisg1337 couple of notes:

shankari commented 1 month ago

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 commented 1 month ago

We are going to try to see if the calls are sent to the station. However, citrine currently doesn't support PnC (related PR: https://github.com/citrineos/citrineos-core/pull/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 commented 1 month ago

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:

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 commented 1 month ago

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 commented 1 month ago

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 https://github.com/EVerest/everest-demo/pull/45

shankari commented 1 month ago

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 commented 1 month ago

@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 commented 1 month ago

@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 https://github.com/EVerest/everest-demo/pull/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 commented 1 month ago

@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 commented 1 month ago

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

https://github.com/EVerest/everest-demo/issues/44#issuecomment-2129978468 https://github.com/EVerest/everest-demo/issues/44#issuecomment-2130071076

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 commented 1 month ago

Exploring https://github.com/EVerest/everest-demo/issues/44#issuecomment-2127744537 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 commented 1 month ago

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:

shankari commented 1 month ago

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 commented 1 month ago

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 https://github.com/EVerest/everest-demo/issues/44#issuecomment-2143307139 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 commented 1 month ago

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 commented 1 month ago

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

No changes

WIth changes

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

shankari commented 1 month ago

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 https://github.com/EVerest/everest-demo/issues/44#issuecomment-2127954519, 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 commented 1 month ago

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 commented 1 month ago

@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 commented 1 month ago

Following up on https://github.com/EVerest/everest-demo/issues/44#issuecomment-2143965306, I am able to decode most messages after dropping the initial bytes based on https://sourceforge.net/p/openv2g/discussion/1226276/thread/e4bca31da7/ I noticed that the example in V2GDecoder.jar begins with 8098, so I tried to decode the EXI beginning at that point (so if the log has 01fe8001000000208098023b66b9df9bfffb8f9094400006301e0188120060018407d00818601400, we decode 8098023b66b9df9bfffb8f9094400006301e0188120060018407d00818601400). With that, I am able to decode most messages from an ISO 15118-2 charge session EXCEPT the following messages: SessionSetupRes, ServiceDiscoveryRes, PaymentServiceSelectionReq, PaymentServiceSelectionRes, ChargeParameterDiscoveryRes which return null.

I have attached the eventLog HTML here and the decoded outputs for the main V2G messages.

incomplete-eventlog.html.gz

`SupportedAppProtocolReq` ```xml urn:iso:15118:2:2013:MsgDef2 0 11 ```
`SupportedAppProtocolRes` ```xml OK_SuccessfulNegotiation 1 ```
`SessionSetupReq` ```xml 00 0242AC150004 ```
`SessionSetupRes` ```xml null ```
`ServiceDiscoveryReq` ```xml ED9AE77E6FFFEE3E ```
`ServiceDiscoveryRes` ```xml null ```
`PaymentServiceSelectionReq` ```xml null ```
`PaymentServiceSelectionRes` ```xml null ```
`PaymentDetailsReq` ```xml ED9AE77E6FFFEE3E UKSWI123456789G ... ...... ```
`PaymentDetailsRes` ```xml ED9AE77E6FFFEE3E OK R+FVk5m8dM2nUQivkJzOaw==1717366560 ```
`AuthorizationReq` ```xml ED9AE77E6FFFEE3E ... ... ... ```
`AuthorizationRes` ```xml ED9AE77E6FFFEE3E OK Finished ```
`ChargeParameterDiscoveryReq` ```xml ED9AE77E6FFFEE3E AC_three_phase_core 0 0Wh60 0V400 -3A32000 0A10 ```
`ChargeParameterDiscoveryRes` ```xml null ```
`PowerDeliveryReq` ```xml ED9AE77E6FFFEE3E Start 1 0 0W22080 86400 0W0 ```
`PowerDeliveryRes` ```xml ED9AE77E6FFFEE3E OK 0 None false ```
louisg1337 commented 1 month ago

Tracing Code


the Failed processing call error is called from _onCallError so we are already in the error processing.

This was definitely an oversight on my part, thanks for catching that for me. I took some time to go back and trace what happens from when we call setChargingProfile() and it backs up the fact that we successfully send a message to EVerest.

Tracing it from the start...

EDIT:

I am a bit confused now because it seems like the above should be the general flow of trying to do setChargingProfile(). However, when I was trying to add in more logs to debug further, I realized none of the logs were actually from the functions above. Maybe this is because we are manipulating things through an API, so we skip any abstraction work that the functions above may do. Here is the new trace of what happens when we send the API request...

Answers to the following questions you had...


How did you invoke the swagger call?

I invoked it via the API ui on localhost:8080/docs, then navigated to the POST request for setChargingProfile. For the identifier field I put in cp001 and tenantId I put in louis. The whole CURL request is as follows.

CURL Request ``` curl -X 'POST' \ 'http://localhost:8080/ocpp/smartcharging/setChargingProfile?identifier=cp001&tenantId=louis' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "customData": { "vendorId": "string" }, "evseId": 0, "chargingProfile": { "customData": { "vendorId": "string" }, "id": 0, "stackLevel": 0, "chargingProfilePurpose": "ChargingStationExternalConstraints", "chargingProfileKind": "Absolute", "recurrencyKind": "Daily", "validFrom": "2024-06-03T18:02:07.234Z", "validTo": "2024-06-03T18:02:07.234Z", "chargingSchedule": [ { "customData": { "vendorId": "string" }, "id": 0, "startSchedule": "2024-06-03T18:02:07.234Z", "duration": 0, "chargingRateUnit": "W", "chargingSchedulePeriod": [ { "customData": { "vendorId": "string" }, "startPeriod": 0, "limit": 0, "numberPhases": 0, "phaseToUse": 0 } ], "minChargingRate": 0, "salesTariff": { "customData": { "vendorId": "string" }, "id": 0, "salesTariffDescription": "string", "numEPriceLevels": 0, "salesTariffEntry": [ { "customData": { "vendorId": "string" }, "relativeTimeInterval": { "customData": { "vendorId": "string" }, "start": 0, "duration": 0 }, "ePriceLevel": 0, "consumptionCost": [ { "customData": { "vendorId": "string" }, "startValue": 0, "cost": [ { "customData": { "vendorId": "string" }, "costKind": "CarbonDioxideEmission", "amount": 0, "amountMultiplier": 0 } ] } ] } ] } } ], "transactionId": "string" } }' ```

What were the logs on Citrine?

Citrine Logs ``` 2024-06-03 14:17:22 2024-06-03 18:17:22.820 DEBUG /usr/local/apps/citrineos/02_Util/dist/queue/rabbit-mq/sender.js:108 CitrineOS Logger:RabbitMqSender Publishing to citrineos: { 2024-06-03 14:17:22 origin: 'csms', 2024-06-03 14:17:22 eventGroup: 'smartcharging', 2024-06-03 14:17:22 action: 'SetChargingProfile', 2024-06-03 14:17:22 context: { 2024-06-03 14:17:22 stationId: 'cp001', 2024-06-03 14:17:22 correlationId: '74161e3b-3d29-4f17-80db-b22a8584106e', 2024-06-03 14:17:22 tenantId: 'louis' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 state: 1, 2024-06-03 14:17:22 payload: { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 evseId: 0, 2024-06-03 14:17:22 chargingProfile: { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 id: 0, 2024-06-03 14:17:22 stackLevel: 0, 2024-06-03 14:17:22 chargingProfilePurpose: 'ChargingStationExternalConstraints', 2024-06-03 14:17:22 chargingProfileKind: 'Absolute', 2024-06-03 14:17:22 recurrencyKind: 'Daily', 2024-06-03 14:17:22 validFrom: '2024-06-03T18:02:07.234Z', 2024-06-03 14:17:22 validTo: '2024-06-03T18:02:07.234Z', 2024-06-03 14:17:22 chargingSchedule: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 id: 0, 2024-06-03 14:17:22 startSchedule: '2024-06-03T18:02:07.234Z', 2024-06-03 14:17:22 duration: 0, 2024-06-03 14:17:22 chargingRateUnit: 'W', 2024-06-03 14:17:22 chargingSchedulePeriod: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 startPeriod: 0, 2024-06-03 14:17:22 limit: 0, 2024-06-03 14:17:22 numberPhases: 0, 2024-06-03 14:17:22 phaseToUse: 0 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ], 2024-06-03 14:17:22 minChargingRate: 0, 2024-06-03 14:17:22 salesTariff: { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 id: 0, 2024-06-03 14:17:22 salesTariffDescription: 'string', 2024-06-03 14:17:22 numEPriceLevels: 0, 2024-06-03 14:17:22 salesTariffEntry: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 relativeTimeInterval: { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 start: 0, 2024-06-03 14:17:22 duration: 0 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 ePriceLevel: 0, 2024-06-03 14:17:22 consumptionCost: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 startValue: 0, 2024-06-03 14:17:22 cost: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 costKind: 'CarbonDioxideEmission', 2024-06-03 14:17:22 amount: 0, 2024-06-03 14:17:22 amountMultiplier: 0 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ] 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ] 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ] 2024-06-03 14:17:22 } 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ], 2024-06-03 14:17:22 transactionId: 'string' 2024-06-03 14:17:22 } 2024-06-03 14:17:22 } 2024-06-03 14:17:22 } 2024-06-03 14:17:22 2024-06-03 18:17:22.828 DEBUG /usr/local/apps/citrineos/02_Util/dist/queue/rabbit-mq/receiver.js:170 CitrineOS Logger:RabbitMqReceiver _onMessage:Received message: { 2024-06-03 14:17:22 contentType: 'application/json', 2024-06-03 14:17:22 contentEncoding: 'utf-8', 2024-06-03 14:17:22 headers: { 2024-06-03 14:17:22 action: 'SetChargingProfile', 2024-06-03 14:17:22 correlationId: '74161e3b-3d29-4f17-80db-b22a8584106e', 2024-06-03 14:17:22 eventGroup: 'smartcharging', 2024-06-03 14:17:22 origin: 'csms', 2024-06-03 14:17:22 state: '1', 2024-06-03 14:17:22 stationId: 'cp001', 2024-06-03 14:17:22 tenantId: 'louis' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 deliveryMode: undefined, 2024-06-03 14:17:22 priority: undefined, 2024-06-03 14:17:22 correlationId: undefined, 2024-06-03 14:17:22 replyTo: undefined, 2024-06-03 14:17:22 expiration: undefined, 2024-06-03 14:17:22 messageId: undefined, 2024-06-03 14:17:22 timestamp: undefined, 2024-06-03 14:17:22 type: undefined, 2024-06-03 14:17:22 userId: undefined, 2024-06-03 14:17:22 appId: undefined, 2024-06-03 14:17:22 clusterId: undefined 2024-06-03 14:17:22 } {"origin":"csms","eventGroup":"smartcharging","action":"SetChargingProfile","context":{"stationId":"cp001","correlationId":"74161e3b-3d29-4f17-80db-b22a8584106e","tenantId":"louis"},"state":1,"payload":{"customData":{"vendorId":"string"},"evseId":0,"chargingProfile":{"customData":{"vendorId":"string"},"id":0,"stackLevel":0,"chargingProfilePurpose":"ChargingStationExternalConstraints","chargingProfileKind":"Absolute","recurrencyKind":"Daily","validFrom":"2024-06-03T18:02:07.234Z","validTo":"2024-06-03T18:02:07.234Z","chargingSchedule":[{"customData":{"vendorId":"string"},"id":0,"startSchedule":"2024-06-03T18:02:07.234Z","duration":0,"chargingRateUnit":"W","chargingSchedulePeriod":[{"customData":{"vendorId":"string"},"startPeriod":0,"limit":0,"numberPhases":0,"phaseToUse":0}],"minChargingRate":0,"salesTariff":{"customData":{"vendorId":"string"},"id":0,"salesTariffDescription":"string","numEPriceLevels":0,"salesTariffEntry":[{"customData":{"vendorId":"string"},"relativeTimeInterval":{"customData":{"vendorId":"string"},"start":0,"duration":0},"ePriceLevel":0,"consumptionCost":[{"customData":{"vendorId":"string"},"startValue":0,"cost":[{"customData":{"vendorId":"string"},"costKind":"CarbonDioxideEmission","amount":0,"amountMultiplier":0}]}]}]}}],"transactionId":"string"}}} 2024-06-03 14:17:22 2024-06-03 18:17:22.831 DEBUG /usr/local/apps/citrineos/00_Base/dist/interfaces/router/AbstractRouter.js:83 CitrineOS Logger:MessageRouterImpl Received message: Message { 2024-06-03 14:17:22 _origin: 'csms', 2024-06-03 14:17:22 _eventGroup: 'smartcharging', 2024-06-03 14:17:22 _action: 'SetChargingProfile', 2024-06-03 14:17:22 _state: 1, 2024-06-03 14:17:22 _context: { 2024-06-03 14:17:22 stationId: 'cp001', 2024-06-03 14:17:22 correlationId: '74161e3b-3d29-4f17-80db-b22a8584106e', 2024-06-03 14:17:22 tenantId: 'louis' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 _payload: { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 evseId: 0, 2024-06-03 14:17:22 chargingProfile: { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 id: 0, 2024-06-03 14:17:22 stackLevel: 0, 2024-06-03 14:17:22 chargingProfilePurpose: 'ChargingStationExternalConstraints', 2024-06-03 14:17:22 chargingProfileKind: 'Absolute', 2024-06-03 14:17:22 recurrencyKind: 'Daily', 2024-06-03 14:17:22 validFrom: '2024-06-03T18:02:07.234Z', 2024-06-03 14:17:22 validTo: '2024-06-03T18:02:07.234Z', 2024-06-03 14:17:22 chargingSchedule: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 id: 0, 2024-06-03 14:17:22 startSchedule: '2024-06-03T18:02:07.234Z', 2024-06-03 14:17:22 duration: 0, 2024-06-03 14:17:22 chargingRateUnit: 'W', 2024-06-03 14:17:22 chargingSchedulePeriod: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 startPeriod: 0, 2024-06-03 14:17:22 limit: 0, 2024-06-03 14:17:22 numberPhases: 0, 2024-06-03 14:17:22 phaseToUse: 0 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ], 2024-06-03 14:17:22 minChargingRate: 0, 2024-06-03 14:17:22 salesTariff: { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 id: 0, 2024-06-03 14:17:22 salesTariffDescription: 'string', 2024-06-03 14:17:22 numEPriceLevels: 0, 2024-06-03 14:17:22 salesTariffEntry: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 relativeTimeInterval: { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 start: 0, 2024-06-03 14:17:22 duration: 0 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 ePriceLevel: 0, 2024-06-03 14:17:22 consumptionCost: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 startValue: 0, 2024-06-03 14:17:22 cost: [ 2024-06-03 14:17:22 { 2024-06-03 14:17:22 customData: { 2024-06-03 14:17:22 vendorId: 'string' 2024-06-03 14:17:22 }, 2024-06-03 14:17:22 costKind: 'CarbonDioxideEmission', 2024-06-03 14:17:22 amount: 0, 2024-06-03 14:17:22 amountMultiplier: 0 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ] 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ] 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ] 2024-06-03 14:17:22 } 2024-06-03 14:17:22 } 2024-06-03 14:17:22 ], 2024-06-03 14:17:22 transactionId: 'string' 2024-06-03 14:17:22 } 2024-06-03 14:17:22 } 2024-06-03 14:17:22 } 2024-06-03 14:17:22 2024-06-03 18:17:22.841 DEBUG /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:474 CitrineOS Logger:MessageRouterImpl Process CallError cp001 [ 2024-06-03 14:17:22 4, 2024-06-03 14:17:22 '74161e3b-3d29-4f17-80db-b22a8584106e', 2024-06-03 14:17:22 'NotImplemented', 2024-06-03 14:17:22 '', 2024-06-03 14:17:22 {} 2024-06-03 14:17:22 ] 2024-06-03 14:17:22 2024-06-03 18:17:22.848 ERROR /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:496 CitrineOS Logger:MessageRouterImpl Failed processing call error: 2024-06-03 14:17:22 2024-06-03 14:17:22 Error Method not implemented. 2024-06-03 14:17:22 error stack: 2024-06-03 14:17:22 • router.js MessageRouterImpl. 2024-06-03 14:17:22 /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:595 2024-06-03 14:17:22 • 2024-06-03 14:17:22 2024-06-03 14:17:22 • router.js 2024-06-03 14:17:22 /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:12 2024-06-03 14:17:22 • 2024-06-03 14:17:22 2024-06-03 14:17:22 • router.js __awaiter 2024-06-03 14:17:22 /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:8 2024-06-03 14:17:22 • router.js MessageRouterImpl._routeCallError 2024-06-03 14:17:22 /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:575 2024-06-03 14:17:22 • router.js 2024-06-03 14:17:22 /usr/local/apps/citrineos/03_Modules/OcppRouter/dist/module/router.js:487 2024-06-03 14:17:22 • task_queues process.processTicksAndRejections 2024-06-03 14:17:22 internal/process/task_queues:95 ```

What were the logs on EVerest? / Did you check the OCCP message logs on EVerest?

In the command line that is running the SIL simulation nothing shows up whenever I send the setChargingProfile API request. I'm not sure if there is a different place I am supposed to be looking for logs as well, if so, please let me know!


If you add additional logs to sendCall - are you able to see the message and the response?

I've been trying to add logs to Citrine but I just can't seem to get the logs to show in the server-citrine-1 logs section. I put some logs in the handle function in AbstractRouter, right next to the Received message: Message { log that we get above, but my logs don't show. I made all the changes in my forked repo here, and I have been redirecting the bash script to point to my citrine repo instead using this code:

    c)  CSMS="citrine"
        CSMS_REPO="https://github.com/louisg1337/citrineos-core" 
        CSMS_BRANCH="test" ;;

Any ideas on how to get these logs to show would be appreciated as it would help us dig a bit deeper.

shankari commented 1 month ago

Any ideas on how to get these logs to show would be appreciated as it would help us dig a bit deeper.

Can you look at the logs that do show up, and how they are generated? If you push your test branch, I can also take a look tonight.

louisg1337 commented 1 month ago

The logs are being generated using tslog and are invoked using this._logger.debug/error(...args). I am not sure how that translates to the Logs tab in the server-citrine-1 docker container though, and if there is an extra step between tslog and the Logs tab that I am missing.

My CitrineOS test branch is pushed and has all the logs I've been testing. I've been noticing though that even when I try to change the wording of logs we currently see, Method not implemented and Received message in particular, the changes still aren't even showing. Maybe theres a possibility that I am still copying over the same original repo or there is some type of caching happening that prevents me from seeing these changes.

To reproduce, I pushed my newly updated demo script to my citrine_demo branch, which incorporates the CitrineOS log changes I have been trying. Running the demo in that branch should recreate what I am experiencing above.

shankari commented 1 month ago

@louisg1337 this may be non-obvious because you are running this through a script, but docker-compose up -d doesn't rebuild the images. You need to manually build first. If I run the script from your repo, the checked out version is

Resolving deltas: 100% (5046/5046), done.
...
HEAD is now at 1b26f2c Changed another word to see if the changes actually do anything

And the containers start

[+] Running 5/5
 ✔ Network server_default          Created                                                                0.1s 
 ✔ Container server-ocpp-db-1      Healthy                                                                0.1s 
 ✔ Container server-amqp-broker-1  Healthy                                                                0.1s 
 ✔ Container server-directus-1     Healthy                                                                0.1s 
 ✔ Container server-citrine-1      Started                                                               15.5s 
Waiting 5s for CSMS to start...

But the images were built 7 days ago

server-citrine                                  latest                     3b38adb4231f   7 days ago      517MB
server-directus                                 latest                     6ee9bfbd8b11   7 days ago      807MB

If I add a build step

diff --git a/demo-iso15118-2-ac-plus-ocpp.sh b/demo-iso15118-2-ac-plus-ocpp.sh
index 115b251..8ee463b 100755
--- a/demo-iso15118-2-ac-plus-ocpp.sh
+++ b/demo-iso15118-2-ac-plus-ocpp.sh
@@ -176,6 +176,7 @@ if [[ "$DEMO_VERSION" != v1.6j ]]; then
     fi
   fi

+  docker compose build
   docker compose up -d

We rebuild before starting

Starting the CSMS
Replaced mapping CitrineOS 8082 to 80 completed successfully.
WARN[0000] /var/folders/y5/cx3cfzrd2q116myv9ly86sw1rnlmdj/T/tmp.GIByGpUd/citrine-csms/Server/docker-compose.yml: `version` is obsolete 
[+] Building 37.2s (22/26)                                                                docker:desktop-linux
 => [directus] exporting to image                                                                         0.0s
 => => exporting layers                                                                                   0.0s
 => => writing image sha256:6ee9bfbd8b11c8551d6a6a1fcfec870a6bc64c73f30df25ca1454e612315526e              0.0s
 => => naming to docker.io/library/server-directus                                                        0.0s
 => [citrine internal] load build definition from deploy.Dockerfile                                       0.1s
 => => transferring dockerfile: 591B                                                                      0.0s
 => [citrine internal] load metadata for docker.io/library/node:18-slim                                   0.7s
 => [citrine internal] load metadata for docker.io/library/node:18                                        0.7s
 => [citrine auth] library/node:pull token for registry-1.docker.io                                       0.0s
 => [citrine internal] load .dockerignore                                                                 0.0s
 => => transferring context: 439B                                                                         0.0s
 => [citrine build 1/5] FROM docker.io/library/node:18@sha256:b08b1356559e2e9945f47ded630c9eb9d4e3ca04f1  0.0s
 => [citrine stage-1 1/3] FROM docker.io/library/node:18-slim@sha256:1da7652745e9ba5de396e436aa086588ea5  0.0s
 => [citrine internal] load build context                                                                 0.1s
 => => transferring context: 4.85MB                                                                       0.1s
 => CACHED [citrine build 2/5] WORKDIR /usr/local/apps/citrineos                                          0.0s
 => [citrine build 3/5] COPY . .                                                                          0.2s
 => [citrine build 4/5] RUN npm install --workspaces --verbose && npm run compile --workspaces --verbos  34.4s
 => => # npm http fetch GET 200 https://registry.npmjs.org/@sideway%2faddress 160ms (cache miss)              
 => => # npm http fetch GET 200 https://registry.npmjs.org/@sideway%2fpinpoint 179ms (cache miss)             
 => => # npm http fetch GET 200 https://registry.npmjs.org/uri-js 92ms (cache miss)                           
 => => # npm http fetch GET 200 https://registry.npmjs.org/content-disposition 97ms (cache miss)              
 => => # npm http fetch GET 200 https://registry.npmjs.org/p-limit 109ms (cache miss)                         
 => => # npm http fetch GET 200 https://registry.npmjs.org/@fastify%2faccept-negotiator 455ms (cache miss)    
shankari commented 1 month ago

@drmrd @couryrr-afs @wjmp had a conversation today about what we plan to support. Given the short timeframe, we plan to support a very simple version of what is already the most basic use case:

Beyond this basic scenario, we can experiment with complex composite schedules that will be generated using the composite schedule tool validation tool. We will not be adding support for NotifyEVChargingNeedsRequest/Response and the transaction level charging profile at this time.

Note that I am not 100% sure if this meets the OCPP 2.0.1 standard; the standard clearly indicates that async charging profiles are supported (from 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.)

But if there is an "out of band" charging profile, will the charge station not send a NotifyEVChargingNeedsRequest when the EV plugs in? Or is that only for "load leveling"? The problem is that the OCPP spec refers to ISO 15118-1, not -2, so it is hard to figure out what it is referring to. Will double-check at the next working group.

thanaParis commented 1 month ago

We've created a fork which supports SP 1 and SP 2 for CitrineOS

Example usage: ./demo-iso15118-2-ac-plus-ocpp.sh -2 -c -r $(pwd) This will start CitrineOS alongside EVerest on SP 2.

There is an outstanding issue with SP 3 that I haven't been able to pin down. We've connected chargers to CitrineOS via SP 3 in the past at OCA Plugfests, but were not able to replicate those successes with EVerest. Previously, I thought this was because CitrineOS didn't use intermediary certificates, but we now support those and it still doesn't work. The everest-demo SP 3 setup works with MaEVe, so I hunted for differences between our handshakes with openssl s_client -connect, but was unable to find anything of note. I will continue to look into this.

My CitrineOS test branch is pushed and has all the logs I've been testing. I've been noticing though that even when I try to change the wording of logs we currently see, Method not implemented and Received message in particular, the changes still aren't even showing. Maybe theres a possibility that I am still copying over the same original repo or there is some type of caching happening that prevents me from seeing these changes.

If you are using docker to run Citrine, which I highly recommend, you do need to remember to execute docker compose down between builds--simply docker compose build && docker compose up -d will often reuse the last run image rather than the latest.

shankari commented 1 month ago

@thanaParis great to see the changes in the fork; can you submit it as a PR that I can review and merge? Note the changes to the run script location were a regression that I fixed in https://github.com/EVerest/everest-demo/pull/49

so I hunted for differences between our handshakes with openssl s_client -connect, but was unable to find anything of note. I will continue to look into this.

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. @the-bay-kay, did you get wireshark to work when debugging the EonTI PKI?

barsnick commented 1 month ago

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

The ISO -2 and -20 schemas can be obtained here: https://standards.iso.org/iso/15118/

The DIN schemas in V2Gdecoder are outdated and broken. Check this location instead: https://github.com/SwitchEV/iso15118/tree/master/iso15118/shared/schemas

louisg1337 commented 1 month ago

setChargingProfile Update

Thank you @shankari and @thanaParis for the help with figuring out my docker issues I had above. Now that I was able to add additional logs, I was able to clear up some of my confusion and I think it is safe to say that setChargingProfile does in fact send a message to EVerest. Please correct me if I'm wrong, but it seems like the general flow once sending an POST request to setChargingProfile is as follows...

... wait for reply ...

onMessage should only get called if Citrine receives any messages, so I think it is safe to say that Citrine successfully communicates with EVerest.

shankari commented 1 month ago

@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 commented 1 month ago

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 ED9AE77E6FFFEE3E OK Finished 1 086400 0W22080 0 None false -1V0 -3h0 ```
shankari commented 1 month ago
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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago
<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 commented 1 month ago

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):: :: 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):: :: 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(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_&):: :: 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_&):: :: 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 commented 1 month ago

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

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)

louisg1337 commented 1 month ago

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.

shankari commented 1 month ago

@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 https://github.com/EVerest/everest-demo/issues/44#issuecomment-2150985185

shankari commented 1 month ago

To follow up on https://github.com/EVerest/everest-demo/issues/44#issuecomment-2150633370, 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.

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 commented 1 month ago

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). https://github.com/EVerest/everest-demo/pull/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. https://github.com/EVerest/everest-demo/pull/57#issuecomment-2157051709