├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── example1.json └── variableEVSE.json ├── images └── clippednode-redexample.png ├── jsconfig.json ├── ocpp ├── OCPP_CentralSystemService_1.6.wsdl ├── OCPP_ChargePointService_1.6.wsdl ├── ocpp-cp-json.html ├── ocpp-cp-json.js ├── ocpp-cp-req.html ├── ocpp-cp-req.js ├── ocpp-remote-cp.html ├── ocpp-remote-cp.js ├── ocpp-remote-cs.html ├── ocpp-remote-cs.js ├── ocpp-req.html ├── ocpp-req.js ├── ocpp-server.html ├── ocpp-server.js ├── ocpp_centralsystemservice_1.5_final.wsdl ├── ocpp_chargepointservice_1.5_final.wsdl └── utils │ └── logdata.js ├── package-lock.json └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "strongloop" 4 | ], 5 | "env": { 6 | "es6": true, 7 | "node": true 8 | }, 9 | "rules": { 10 | "max-len":"off", 11 | "spaced-comment":"off", 12 | "eqeqeq": "off" 13 | } 14 | 15 | 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | .vscode 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2017-2023, UChicago Argonne, LLC 2 | All Rights Reserved 3 | node-red-contrib-ocpp (ANL-SF-17-129) 4 | Bryan Nystrom, Argonne National Laboratory 5 | 6 | OPEN SOURCE LICENSE 7 | 8 | Under the terms of Contract No. DE-AC02-06CH11357 with UChicago Argonne, LLC, the U.S. Government retains certain rights in this software. 9 | 10 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 11 | 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 14 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 3. Neither the names of UChicago Argonne, LLC or the Department of Energy nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 16 | 17 | 18 | 19 | ****************************************************************************************************************************************************************************** 20 | DISCLAIMER 21 | 22 | THE SOFTWARE IS SUPPLIED “AS IS” WITHOUT WARRANTY OF ANY KIND. 23 | 24 | NEITHER THE UNTED STATES GOVERNMENT, NOR THE UNITED STATES DEPARTMENT OF ENERGY, NOR UCHICAGO ARGONNE, LLC, NOR ANY OF THEIR EMPLOYEES, MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY LEGAL LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF ANY INFORMATION, DATA, APPARATUS, PRODUCT, OR PROCESS DISCLOSED, OR REPRESENTS THAT ITS USE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS. 25 | 26 | ****************************************************************************************************************************************************************************** 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-red-contrib-ocpp 2 | 3 | [![NPM](https://nodei.co/npm/node-red-contrib-ocpp.png)](https://nodei.co/npm/node-red-contrib-ocpp/) 4 | 5 | [Node-Red][4] nodes for communicating with the EVSE Charge Points and Central Systems via the [Open Charge Point Protocol][6] (hereafter OCPP). These node-red nodes 6 | allow you to take on the role of a Central System (CS) or Charge Point (CP). 7 | 8 | Based on the [OCPP 1.5][6] and [OCPP 1.6][8] specifications utilizing the Simple Object Access Protocol (hereafter SOAP) and JavaScript Object Notation (hereafter JSON) protocols. 9 | 10 | # Install 11 | 12 | Run the following command in the root directory of your Node-RED install 13 | 14 | npm install node-red-contrib-ocpp 15 | 16 | 17 | # Requirements 18 | 19 | The package currently requires [Node.js 10][1] or higher. 20 | 21 | # Nodes 22 | 23 | * [CS request SOAP](#csrequestsoap) 24 | * [CP request SOAP](#cp-request-soap) 25 | * [CS server](#cs-server) 26 | * [CP server SOAP](#cp-server-soap) 27 | * [CP client JSON](#cp-client-json) 28 | * [server response](#server-response) 29 | * [CS request JSON](#cs-request-json) 30 | * [examples](#examples) 31 | 32 | 33 | _(nodes that begin with CS refer to those that emulate a Central System. Those with CP refer to those that emulate a Charge Point/EVSE)_ 34 | 35 | --- 36 | ## CS request SOAP 37 | This node allows you to make requests to an EVSE charge point and return a message with the response from that request. The targeted EVSE charge point must support either 1.5 or 1.6 SOAP (this node does not support JSON) It is flexible in that you can either set up a default command and/or data to send when you configure the node, or you may pass in 38 | that information to override the defaults via a message payload. 39 | 40 | For example, to set up a *Reset* command request, you can select the *Reset* command from the nodes configuration dropdown. The Reset command 41 | also requires sending a parameter indicating the type of reset to be performed, either *hard* or *soft*. In the Command Params field of the configuration, you would provide a JSON formatted object similar to this: 42 | 43 | ```javascript 44 | { 45 | "type": "Soft" 46 | } 47 | ``` 48 | 49 | 50 | Alternatively, you can pass the node a payload which contains a command and/or data to override the defaults. To make a *Reset* request 51 | by passing it a message, the message payload (*msg.payload*) would look as follows: 52 | 53 | 54 | ```javascript 55 | { 56 | "command": "Reset", 57 | "data" : { "type" : "Hard" } 58 | } 59 | ``` 60 | 61 | If either the command or the data sections are missing from the message, the defaults that are set up in the node configuration will be used. If you set up an *[CS request SOAP](#cs-request-soap)* node 62 | with a default command of *Reset*, you could pass in just the following: 63 | 64 | 65 | **for hard reset** 66 | ```javascript 67 | { 68 | "data": { "type": "Hard" } 69 | } 70 | ``` 71 | 72 | **for soft reset** 73 | ```javascript 74 | { 75 | "data": { "type": "Soft"} 76 | } 77 | ``` 78 | **User Generated Message IDs** 79 | 80 | For all request nodes the option exists to also pass in a user generated message ID that will be used to identify the message. 81 | 82 | ```javascript 83 | { 84 | "command": "Reset", 85 | "data" : { "type" : "Hard" }, 86 | "MessageId": "12345678" 87 | } 88 | ``` 89 | This may make it easier for you to identify and track your message throughout your flows. By default, the node modules internally generate a unique id for request messages based on [UUID v4](http://www.ietf.org/rfc/rfc4122.txt) 90 | 91 | 92 | **Output** 93 | 94 | The output returned by the node has the following message format: 95 | 96 | ```javascript 97 | { 98 | "ocpp": { 99 | "command": "", 100 | "chargeBoxIdentity": "", 101 | "url": "", 102 | "data": "" 103 | }, 104 | "payload": { 105 | "command": "", 106 | "key": "value" 107 | } 108 | } 109 | ``` 110 | Example return message from a OCPP 1.5 SOAP 111 | 112 | ```javascript 113 | { 114 | "_msgid":"58c0fa49.ecac14", 115 | "topic":"", 116 | "ocpp":{ 117 | "command":"Reset", 118 | "MessageId":"f58ec0fb-b6fd-48a3-9a0c-2e0cba143388", 119 | "chargeBoxIdentity":"Chargion6D94", 120 | "url":"http://204.188.169.51:8080/chargePoint", 121 | "ocppVer":"1.5s", 122 | "data":{ 123 | "type":"Soft" 124 | } 125 | }, 126 | "payload":{ 127 | "command":"Reset", 128 | "data":{ 129 | "status":"Accepted" 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | The payload portion varies depending on the command and EVSE charge point vendor specifications. 136 | 137 | --- 138 | ## CP request SOAP 139 | This node is used to emulate an EVSE charge point station and is capable of making requests to a Central System service that supports either protocol 1.5 or 1.6 SOAP. Its behavior is similar to that of the *[CS request SOAP](#cs-request-soap)* node. 140 | 141 | To emulate a EVSE charge point station that utilizes OCPP 1.6 JSON, use the *[CP client JSON](#cp-client-json)* node. 142 | 143 | --- 144 | ## CS server 145 | The ocpp-server node will listen for incoming requests coming from the EVSE charge points that are targeting its address. It is capable of receiving messages via 1.5 SOAP, 1.6 SOAP, and 1.6 JSON if the protocols are enabled in its configuration. 146 | When the ocpp-server node receives a message, it will output a message in the following format: 147 | 148 | ```javascript 149 | { 150 | "ocpp": { 151 | "ocppVersion": "", 152 | "command": "", 153 | "chargeBoxIdentity": "", 154 | "From": "", 155 | "MessageID": "", 156 | "messageType": "" 157 | }, 158 | "payload": { 159 | "command": "", 160 | "data": "" 161 | } 162 | } 163 | ``` 164 | Here is an example of a OCPP 1.6 JSON Heartbeat request message. 165 | ```javascript 166 | { "ocpp":{ 167 | "ocppVersion": "1.6j", 168 | "chargeBoxIdentity": "veefil-48310","MessageId": "uuid:f1d11de1-5725-9255-854b-da6542b4d9bb", 169 | "msgType": 2, 170 | "command": "Heartbeat" 171 | }, 172 | "payload":{ 173 | "command": "Heartbeat", 174 | "data":{} 175 | }, 176 | "msgId":"e38e0e7f-3db2-4a33-ab80-859175ebfce0","_msgid":"d310afd9.b9de8" 177 | } 178 | ``` 179 | The incoming messages require a response (sent through the *[server response](#server-response)* node), and those responses should be sent within 180 | a reasonable amount of time. The ocpp-server node will cancel any outstanding responses after a 2 minute time period. The EVSE side 181 | may timeout awaiting a response even sooner than that depending on their configuration. 182 | 183 | --- 184 | ## server response 185 | To return a response to an incoming EVSE charge point request, you need to pass your message to the *[server response](#server-response)* node. Since the message 186 | coming out of the ocpp-server node contains information about how to return the response, the message itself should be passed as is through 187 | the node flow with the exception of the msg.payload section. The msg.payload should be modified to contain the response to the incoming request. 188 | 189 | For example, to accept a *BootNotification* request, set the payload of the response as: 190 | 191 | ```javascript 192 | { 193 | "status": "Accepted", 194 | "currentTime": new Date().toISOString(), 195 | "heartbeatInterval": 60 196 | } 197 | ``` 198 | 199 | (The message being passed from the server contains a unique identifier contained in msg.msgID. This needs to be present in the response message in order for the message to be returned to the proper request) 200 | 201 | --- 202 | ## CP server SOAP 203 | This node emulates an EVSE charge point station server that accepts and responds to OCPP 1.5 or 1.6 SOAP messages being sent from a Central System service. Setup and behavior are similar to that of the *[CS server](#cs-server)*. Use this node in conjunction with a *[server response](#server-response)* node to pass responses to requests back to a Central System. 204 | 205 | _Unlike the *[CS Server](#cs-server)* this node does not incorporate or support multiple protocols running concurrently, nor does it support JSON. To emulate an EVSE charge point that supports JSON, use the *[CP client JSON](#cp-client-json)* node._ 206 | 207 | --- 208 | ## CP client JSON 209 | Use this node to emulate an EVSE charge point station that supports OCPP protocol 1.6 JSON. Since the OCPP JSON implementation utilizes web sockets, this node makes the initial connection to the defined Central System, and messages are passed back and forth. Therefore it acts like both a server and a client in that it both makes and receives requests to and from the CS. 210 | 211 | ## CS request JSON 212 | Use this node to make requests to an EVSE charge point station that supports OCPP 1.6 JSON. Its behavior and functionality are similar to that of the *[CS request SOAP](#cs_request-soap)* node with the exception that it only supports OCPP 1.6 JSON commands. 213 | 214 | 215 | ## examples 216 | 217 | ![alt text](./images/clippednode-redexample.png "example flow found in the examples folder") 218 | 219 | In the root of the OCPP node module folder is a folder named examples. This is where you can find example flows that may be useful in setting up your OCCP situation. Currently a single example file exists which you can import into node-red that sets up a Central System node with a few basic Charge Point nodes. This is by no means a full production example, but just a starting point for those who may be interested in a way to set up the nodes. 220 | 221 | 222 | # Authors 223 | 224 | [Bryan Nystrom][11] 225 | 226 | [Jason D. Harper][5] 227 | 228 | [Argonne National Laboratory][10] 229 | 230 | [1]:https://nodejs.org/ 231 | [2]:https://na.chargepoint.com/UI/downloads/en/ChargePoint_Web_Services_API_Guide_Ver4.1_Rev4.pdf 232 | [3]:https://webservices.chargepoint.com/cp_api_4.1.wsdl 233 | [4]:http://nodered.org 234 | [5]:https://github.com/jayharper 235 | [6]:http://www.openchargealliance.org/protocols/ocpp/ocpp-15/ 236 | [7]:http://www.openchargealliance.org/uploads/files/protected/ocpp_specification_1.5_final.pdf 237 | [8]:http://www.openchargealliance.org/protocols/ocpp/ocpp-15/ 238 | [9]:http://www.openchargealliance.org/uploads/files/protected/ocpp_specification_1.5_final.pdf 239 | [10]:https://www.anl.gov 240 | [11]: https://github.com/bnystrom 241 | -------------------------------------------------------------------------------- /examples/example1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "acc9f35a.ec54d8", 4 | "type": "tab", 5 | "label": "Flow 2", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "44d45791.af575", 11 | "type": "inject", 12 | "z": "acc9f35a.ec54d8", 13 | "name": "", 14 | "topic": "", 15 | "payload": "", 16 | "payloadType": "date", 17 | "repeat": "", 18 | "crontab": "", 19 | "once": false, 20 | "onceDelay": 0.1, 21 | "x": 130, 22 | "y": 730, 23 | "wires": [ 24 | [ 25 | "3b4b7493.9187a4" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "8cee8d67.a2e858", 31 | "type": "CS server", 32 | "z": "acc9f35a.ec54d8", 33 | "name": "CentralSystem@8834", 34 | "port": "8834", 35 | "enabled15": true, 36 | "path15": "/ocpp15s", 37 | "enabled16": true, 38 | "path16": "/ocpp16s", 39 | "enabled16j": true, 40 | "path16j": "/ocpp", 41 | "log": true, 42 | "pathlog": "c:\\tmp\\OCPP__.log", 43 | "x": 140, 44 | "y": 130, 45 | "wires": [ 46 | [ 47 | "68a8235f.451e04", 48 | "7dcf2c29.43a464" 49 | ] 50 | ] 51 | }, 52 | { 53 | "id": "7dcf2c29.43a464", 54 | "type": "debug", 55 | "z": "acc9f35a.ec54d8", 56 | "name": "", 57 | "active": true, 58 | "tosidebar": true, 59 | "console": false, 60 | "tostatus": false, 61 | "complete": "true", 62 | "x": 230, 63 | "y": 270, 64 | "wires": [] 65 | }, 66 | { 67 | "id": "3b4b7493.9187a4", 68 | "type": "CP client JSON", 69 | "z": "acc9f35a.ec54d8", 70 | "name": "EVSE-001", 71 | "cbId": "evse-001", 72 | "remotecs": "92124697.6658f", 73 | "ocppver": "1.6j", 74 | "command": "Heartbeat", 75 | "cmddata": "{}", 76 | "log": false, 77 | "pathlog": "", 78 | "x": 320, 79 | "y": 730, 80 | "wires": [ 81 | [ 82 | "48f62a62.5a7414" 83 | ] 84 | ] 85 | }, 86 | { 87 | "id": "2e1a65fd.686fe2", 88 | "type": "debug", 89 | "z": "acc9f35a.ec54d8", 90 | "name": "", 91 | "active": true, 92 | "tosidebar": true, 93 | "console": false, 94 | "tostatus": false, 95 | "complete": "true", 96 | "x": 710, 97 | "y": 770, 98 | "wires": [] 99 | }, 100 | { 101 | "id": "a8e1cfda.c3b0a", 102 | "type": "CP client JSON", 103 | "z": "acc9f35a.ec54d8", 104 | "name": "EVSE-002", 105 | "cbId": "evse-002", 106 | "remotecs": "92124697.6658f", 107 | "ocppver": "1.6j", 108 | "command": "Heartbeat", 109 | "cmddata": "{}", 110 | "log": false, 111 | "pathlog": "", 112 | "x": 310, 113 | "y": 900, 114 | "wires": [ 115 | [ 116 | "e6afde22.51b798" 117 | ] 118 | ] 119 | }, 120 | { 121 | "id": "e6afde22.51b798", 122 | "type": "debug", 123 | "z": "acc9f35a.ec54d8", 124 | "name": "", 125 | "active": true, 126 | "tosidebar": true, 127 | "console": false, 128 | "tostatus": false, 129 | "complete": "true", 130 | "x": 460, 131 | "y": 900, 132 | "wires": [] 133 | }, 134 | { 135 | "id": "21f0f480.5ef094", 136 | "type": "inject", 137 | "z": "acc9f35a.ec54d8", 138 | "name": "Empty trigger", 139 | "topic": "", 140 | "payload": "{}", 141 | "payloadType": "json", 142 | "repeat": "", 143 | "crontab": "", 144 | "once": false, 145 | "onceDelay": 0.1, 146 | "x": 140, 147 | "y": 900, 148 | "wires": [ 149 | [ 150 | "a8e1cfda.c3b0a" 151 | ] 152 | ] 153 | }, 154 | { 155 | "id": "68a8235f.451e04", 156 | "type": "switch", 157 | "z": "acc9f35a.ec54d8", 158 | "name": "OCPP CS Command Switch", 159 | "property": "payload.command", 160 | "propertyType": "msg", 161 | "rules": [ 162 | { 163 | "t": "eq", 164 | "v": "BootNotification", 165 | "vt": "str" 166 | }, 167 | { 168 | "t": "eq", 169 | "v": "Authorize", 170 | "vt": "str" 171 | }, 172 | { 173 | "t": "eq", 174 | "v": "Heartbeat", 175 | "vt": "str" 176 | }, 177 | { 178 | "t": "eq", 179 | "v": "StatusNotification", 180 | "vt": "str" 181 | }, 182 | { 183 | "t": "eq", 184 | "v": "MeterValues", 185 | "vt": "str" 186 | }, 187 | { 188 | "t": "eq", 189 | "v": "StartTransaction", 190 | "vt": "str" 191 | }, 192 | { 193 | "t": "eq", 194 | "v": "StopTransaction", 195 | "vt": "str" 196 | }, 197 | { 198 | "t": "else" 199 | } 200 | ], 201 | "checkall": "true", 202 | "repair": false, 203 | "outputs": 8, 204 | "x": 399, 205 | "y": 135, 206 | "wires": [ 207 | [ 208 | "7194ed20.873d8c" 209 | ], 210 | [ 211 | "e936416b.681468" 212 | ], 213 | [ 214 | "6c952053.e7be8" 215 | ], 216 | [ 217 | "243ae570.3009e2", 218 | "ed70d827.974808" 219 | ], 220 | [ 221 | "243ae570.3009e2", 222 | "ed70d827.974808", 223 | "b9839e55.58e7c8", 224 | "a78523d9.80027" 225 | ], 226 | [ 227 | "af7e1d23.d8562", 228 | "ed70d827.974808" 229 | ], 230 | [ 231 | "ed70d827.974808", 232 | "e936416b.681468" 233 | ], 234 | [ 235 | "ed70d827.974808" 236 | ] 237 | ] 238 | }, 239 | { 240 | "id": "87468474.e9785", 241 | "type": "server response", 242 | "z": "acc9f35a.ec54d8", 243 | "name": "", 244 | "x": 899, 245 | "y": 156, 246 | "wires": [] 247 | }, 248 | { 249 | "id": "7194ed20.873d8c", 250 | "type": "function", 251 | "z": "acc9f35a.ec54d8", 252 | "name": "BootNotification", 253 | "func": "msg.payload = {\n interval: 120,\n currentTime: new Date().toISOString(),\n status: \"Accepted\"\n}\nreturn msg;", 254 | "outputs": 1, 255 | "noerr": 0, 256 | "x": 679, 257 | "y": 36, 258 | "wires": [ 259 | [ 260 | "87468474.e9785" 261 | ] 262 | ] 263 | }, 264 | { 265 | "id": "af7e1d23.d8562", 266 | "type": "function", 267 | "z": "acc9f35a.ec54d8", 268 | "name": "Start Transaction", 269 | "func": "msg.payload = {\n idTagInfo: {\n status: \"Accepted\",\n },\n transactionId: getRndInteger(0,100000)\n}\nreturn msg;\n\nfunction getRndInteger(min, max) {\n return Math.floor(Math.random() * (max - min + 1) ) + min;\n}\n", 270 | "outputs": 1, 271 | "noerr": 0, 272 | "x": 689, 273 | "y": 216, 274 | "wires": [ 275 | [ 276 | "87468474.e9785" 277 | ] 278 | ] 279 | }, 280 | { 281 | "id": "8e8b7998.b5fea8", 282 | "type": "function", 283 | "z": "acc9f35a.ec54d8", 284 | "name": "Stop Transaction", 285 | "func": "msg.payload = { idTagInfo: { status: \"Accepted\"} };\nreturn msg;", 286 | "outputs": 1, 287 | "noerr": 0, 288 | "x": 889, 289 | "y": 236, 290 | "wires": [ 291 | [ 292 | "87468474.e9785" 293 | ] 294 | ] 295 | }, 296 | { 297 | "id": "6c952053.e7be8", 298 | "type": "change", 299 | "z": "acc9f35a.ec54d8", 300 | "name": "Heartbeat", 301 | "rules": [ 302 | { 303 | "t": "set", 304 | "p": "payload", 305 | "pt": "msg", 306 | "to": "{}", 307 | "tot": "json" 308 | }, 309 | { 310 | "t": "set", 311 | "p": "payload.currentTime", 312 | "pt": "msg", 313 | "to": "$now()", 314 | "tot": "jsonata" 315 | } 316 | ], 317 | "action": "", 318 | "property": "", 319 | "from": "", 320 | "to": "", 321 | "reg": false, 322 | "x": 659, 323 | "y": 114, 324 | "wires": [ 325 | [ 326 | "87468474.e9785" 327 | ] 328 | ] 329 | }, 330 | { 331 | "id": "e936416b.681468", 332 | "type": "change", 333 | "z": "acc9f35a.ec54d8", 334 | "name": "Generic Status=Accepted", 335 | "rules": [ 336 | { 337 | "t": "set", 338 | "p": "payload", 339 | "pt": "msg", 340 | "to": "{\"idTagInfo\":{\"status\":\"Accepted\"}}", 341 | "tot": "json" 342 | } 343 | ], 344 | "action": "", 345 | "property": "", 346 | "from": "", 347 | "to": "", 348 | "reg": false, 349 | "x": 709, 350 | "y": 76, 351 | "wires": [ 352 | [ 353 | "87468474.e9785", 354 | "8f8ced11.203858" 355 | ] 356 | ] 357 | }, 358 | { 359 | "id": "243ae570.3009e2", 360 | "type": "change", 361 | "z": "acc9f35a.ec54d8", 362 | "name": "Generic Return", 363 | "rules": [ 364 | { 365 | "t": "set", 366 | "p": "ocpp.data", 367 | "pt": "msg", 368 | "to": "payload.data", 369 | "tot": "msg" 370 | }, 371 | { 372 | "t": "set", 373 | "p": "payload", 374 | "pt": "msg", 375 | "to": "{}", 376 | "tot": "json" 377 | } 378 | ], 379 | "action": "", 380 | "property": "", 381 | "from": "", 382 | "to": "", 383 | "reg": false, 384 | "x": 679, 385 | "y": 156, 386 | "wires": [ 387 | [ 388 | "87468474.e9785" 389 | ] 390 | ] 391 | }, 392 | { 393 | "id": "ed70d827.974808", 394 | "type": "debug", 395 | "z": "acc9f35a.ec54d8", 396 | "name": "", 397 | "active": true, 398 | "tosidebar": true, 399 | "console": false, 400 | "tostatus": false, 401 | "complete": "true", 402 | "x": 530, 403 | "y": 270, 404 | "wires": [] 405 | }, 406 | { 407 | "id": "b83cc432.dd05a", 408 | "type": "inject", 409 | "z": "acc9f35a.ec54d8", 410 | "name": "get hb", 411 | "topic": "", 412 | "payload": "{\"command\":\"GetConfiguration\",\"data\":{\"key\":[\"HeartbeatInterval\"]}}", 413 | "payloadType": "json", 414 | "repeat": "", 415 | "crontab": "", 416 | "once": false, 417 | "onceDelay": 0.1, 418 | "x": 120, 419 | "y": 470, 420 | "wires": [ 421 | [ 422 | "644438bd.ddc77" 423 | ] 424 | ] 425 | }, 426 | { 427 | "id": "8f8ced11.203858", 428 | "type": "debug", 429 | "z": "acc9f35a.ec54d8", 430 | "name": "", 431 | "active": false, 432 | "tosidebar": true, 433 | "console": false, 434 | "tostatus": false, 435 | "complete": "true", 436 | "x": 909, 437 | "y": 56, 438 | "wires": [] 439 | }, 440 | { 441 | "id": "b9839e55.58e7c8", 442 | "type": "debug", 443 | "z": "acc9f35a.ec54d8", 444 | "name": "", 445 | "active": false, 446 | "tosidebar": true, 447 | "console": false, 448 | "tostatus": false, 449 | "complete": "payload.data.meterValue[0].sampledValue[2]", 450 | "x": 870, 451 | "y": 280, 452 | "wires": [] 453 | }, 454 | { 455 | "id": "a78523d9.80027", 456 | "type": "debug", 457 | "z": "acc9f35a.ec54d8", 458 | "name": "", 459 | "active": true, 460 | "tosidebar": true, 461 | "console": false, 462 | "tostatus": false, 463 | "complete": "true", 464 | "x": 750, 465 | "y": 350, 466 | "wires": [] 467 | }, 468 | { 469 | "id": "644438bd.ddc77", 470 | "type": "CS request JSON", 471 | "z": "acc9f35a.ec54d8", 472 | "name": "CS Req evse-001", 473 | "remotecb": "2bafdaab.15b956", 474 | "command": "GetConfiguration", 475 | "cmddata": "", 476 | "log": true, 477 | "pathlog": "", 478 | "x": 400, 479 | "y": 460, 480 | "wires": [ 481 | [ 482 | "dbdbed16.716bd8" 483 | ] 484 | ] 485 | }, 486 | { 487 | "id": "dbdbed16.716bd8", 488 | "type": "debug", 489 | "z": "acc9f35a.ec54d8", 490 | "name": "", 491 | "active": true, 492 | "tosidebar": true, 493 | "console": false, 494 | "tostatus": false, 495 | "complete": "true", 496 | "x": 590, 497 | "y": 460, 498 | "wires": [] 499 | }, 500 | { 501 | "id": "8e29e544.df0ec", 502 | "type": "CP client JSON", 503 | "z": "acc9f35a.ec54d8", 504 | "name": "EVSE-003", 505 | "cbId": "evse-003", 506 | "remotecs": "92124697.6658f", 507 | "ocppver": "1.6j", 508 | "command": "Heartbeat", 509 | "cmddata": "{}", 510 | "log": false, 511 | "pathlog": "", 512 | "x": 310, 513 | "y": 970, 514 | "wires": [ 515 | [ 516 | "a548f73.e3c3288" 517 | ] 518 | ] 519 | }, 520 | { 521 | "id": "a548f73.e3c3288", 522 | "type": "debug", 523 | "z": "acc9f35a.ec54d8", 524 | "name": "", 525 | "active": true, 526 | "tosidebar": true, 527 | "console": false, 528 | "tostatus": false, 529 | "complete": "true", 530 | "x": 460, 531 | "y": 970, 532 | "wires": [] 533 | }, 534 | { 535 | "id": "1b5d0855.91b5a", 536 | "type": "inject", 537 | "z": "acc9f35a.ec54d8", 538 | "name": "Send Heartbeat", 539 | "topic": "", 540 | "payload": "{\"command\":\"Heartbeat\",\"data\":{}}", 541 | "payloadType": "json", 542 | "repeat": "", 543 | "crontab": "", 544 | "once": false, 545 | "onceDelay": 0.1, 546 | "x": 140, 547 | "y": 970, 548 | "wires": [ 549 | [ 550 | "8e29e544.df0ec" 551 | ] 552 | ] 553 | }, 554 | { 555 | "id": "2d2fff0f.147418", 556 | "type": "inject", 557 | "z": "acc9f35a.ec54d8", 558 | "name": "get hb evse-002", 559 | "topic": "", 560 | "payload": "{\"cbId\":\"evse-002\",\"command\":\"GetConfiguration\",\"data\":{\"key\":[\"HeartbeatInterval\"]}}", 561 | "payloadType": "json", 562 | "repeat": "", 563 | "crontab": "", 564 | "once": false, 565 | "onceDelay": 0.1, 566 | "x": 150, 567 | "y": 550, 568 | "wires": [ 569 | [ 570 | "644438bd.ddc77" 571 | ] 572 | ] 573 | }, 574 | { 575 | "id": "dc104c1f.79ca1", 576 | "type": "comment", 577 | "z": "acc9f35a.ec54d8", 578 | "name": "Basic OCPP Central System Server and supporting nodes", 579 | "info": "Change the port address and paths to meet your needs.\nThis is a very basic (does represent a fully functional CS) example", 580 | "x": 230, 581 | "y": 30, 582 | "wires": [] 583 | }, 584 | { 585 | "id": "2138f556.ca99d2", 586 | "type": "comment", 587 | "z": "acc9f35a.ec54d8", 588 | "name": "Send request from the Central Server to JSON based Charge Points (EVSEs)", 589 | "info": "", 590 | "x": 290, 591 | "y": 390, 592 | "wires": [] 593 | }, 594 | { 595 | "id": "48f62a62.5a7414", 596 | "type": "switch", 597 | "z": "acc9f35a.ec54d8", 598 | "name": "", 599 | "property": "payload.command", 600 | "propertyType": "msg", 601 | "rules": [ 602 | { 603 | "t": "eq", 604 | "v": "GetConfiguration", 605 | "vt": "str" 606 | }, 607 | { 608 | "t": "else" 609 | } 610 | ], 611 | "checkall": "true", 612 | "repair": false, 613 | "outputs": 2, 614 | "x": 470, 615 | "y": 730, 616 | "wires": [ 617 | [ 618 | "a04f4e8e.2730b" 619 | ], 620 | [ 621 | "2e1a65fd.686fe2" 622 | ] 623 | ] 624 | }, 625 | { 626 | "id": "a04f4e8e.2730b", 627 | "type": "function", 628 | "z": "acc9f35a.ec54d8", 629 | "name": "GetConf response", 630 | "func": "let config = {\n HeartbeatInterval: 20,\n}\n\nlet KeyValues = [];\n\nlet KeyValue = {\n key: msg.payload.data.key[0],\n value: config[msg.payload.data.key[0]],\n readonly: true\n};\n\nKeyValues.push(KeyValue);\n\n// Copy over the request messageId to be used\n// in the response\nmsg.payload.MessageId = msg.ocpp.MessageId;\n\n// Our response data gets put into the payload.data\nmsg.payload.data = KeyValues;\n\n//\n// For responses to CS requests (such as GetConfig)\n// the CP JSON Client should set the message type to 3\nmsg.payload.msgType = 3;\n\nreturn msg;", 631 | "outputs": 1, 632 | "noerr": 0, 633 | "x": 730, 634 | "y": 730, 635 | "wires": [ 636 | [ 637 | "a6651586.7ea81", 638 | "ab7ccc5a.047d1" 639 | ] 640 | ] 641 | }, 642 | { 643 | "id": "94c69594.a51728", 644 | "type": "link in", 645 | "z": "acc9f35a.ec54d8", 646 | "name": "EVSE001Response", 647 | "links": [ 648 | "a6651586.7ea81" 649 | ], 650 | "x": 225, 651 | "y": 690, 652 | "wires": [ 653 | [ 654 | "3b4b7493.9187a4" 655 | ] 656 | ] 657 | }, 658 | { 659 | "id": "a6651586.7ea81", 660 | "type": "link out", 661 | "z": "acc9f35a.ec54d8", 662 | "name": "", 663 | "links": [ 664 | "94c69594.a51728" 665 | ], 666 | "x": 955, 667 | "y": 730, 668 | "wires": [] 669 | }, 670 | { 671 | "id": "ab7ccc5a.047d1", 672 | "type": "debug", 673 | "z": "acc9f35a.ec54d8", 674 | "name": "", 675 | "active": true, 676 | "tosidebar": true, 677 | "console": false, 678 | "tostatus": false, 679 | "complete": "true", 680 | "x": 790, 681 | "y": 670, 682 | "wires": [] 683 | }, 684 | { 685 | "id": "4a0b014c.9f6e18", 686 | "type": "comment", 687 | "z": "acc9f35a.ec54d8", 688 | "name": "Target a different EVSE/cbId", 689 | "info": "", 690 | "x": 160, 691 | "y": 510, 692 | "wires": [] 693 | }, 694 | { 695 | "id": "3e5c9b9f.d97564", 696 | "type": "comment", 697 | "z": "acc9f35a.ec54d8", 698 | "name": "Inject a command with data", 699 | "info": "", 700 | "x": 160, 701 | "y": 430, 702 | "wires": [] 703 | }, 704 | { 705 | "id": "7741c50b.b6d9a4", 706 | "type": "comment", 707 | "z": "acc9f35a.ec54d8", 708 | "name": "React to GetConfiguration commands", 709 | "info": "This would usually be a larger switch block.\nFor simplicity, we are only reacting to the\nGetConfiguration command (very basic).\nYou probably want a case for every OCPP command\nthat could possibly be sent from the CS.", 710 | "x": 530, 711 | "y": 690, 712 | "wires": [] 713 | }, 714 | { 715 | "id": "8300f4bc.9d2ae8", 716 | "type": "comment", 717 | "z": "acc9f35a.ec54d8", 718 | "name": "Inject the response back through the CP JSON client to be sent to the CS", 719 | "info": "", 720 | "x": 1150, 721 | "y": 680, 722 | "wires": [] 723 | }, 724 | { 725 | "id": "aafc9533.78b61", 726 | "type": "comment", 727 | "z": "acc9f35a.ec54d8", 728 | "name": "Just 2 other CP JSON clients (simple/dumb)", 729 | "info": "", 730 | "x": 190, 731 | "y": 810, 732 | "wires": [] 733 | }, 734 | { 735 | "id": "688278b3.2851c8", 736 | "type": "comment", 737 | "z": "acc9f35a.ec54d8", 738 | "name": "Empty trigger causes the default cmd and data in node to be used", 739 | "info": "", 740 | "x": 280, 741 | "y": 860, 742 | "wires": [] 743 | }, 744 | { 745 | "id": "255e3170.72628e", 746 | "type": "comment", 747 | "z": "acc9f35a.ec54d8", 748 | "name": "EVSE / ChargePoint Clients", 749 | "info": "", 750 | "x": 160, 751 | "y": 630, 752 | "wires": [] 753 | }, 754 | { 755 | "id": "248aa5d0.dec4f2", 756 | "type": "comment", 757 | "z": "acc9f35a.ec54d8", 758 | "name": "Debug all incoming msgs", 759 | "info": "", 760 | "x": 260, 761 | "y": 230, 762 | "wires": [] 763 | }, 764 | { 765 | "id": "1eb59bcf.4e063c", 766 | "type": "comment", 767 | "z": "acc9f35a.ec54d8", 768 | "name": "Example with JSON 1.6", 769 | "info": "Keep in mind that some of the basic CS responses vary depending on OCPP 1.5 vs 1.6", 770 | "x": 190, 771 | "y": 70, 772 | "wires": [] 773 | }, 774 | { 775 | "id": "92124697.6658f", 776 | "type": "ocpp-remote-cs", 777 | "z": "", 778 | "name": "Localhost:8834/ocpp", 779 | "url": "ws://localhost:8834/ocpp" 780 | }, 781 | { 782 | "id": "2bafdaab.15b956", 783 | "type": "ocpp-remotej-cp", 784 | "z": "", 785 | "name": "", 786 | "cbId": "evse-001", 787 | "ocppver": "1.6j" 788 | } 789 | ] -------------------------------------------------------------------------------- /examples/variableEVSE.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "b1993d2a213b2bb0", 4 | "type": "tab", 5 | "label": "Example Variable EVSE", 6 | "disabled": false, 7 | "info": "", 8 | "env": [] 9 | }, 10 | { 11 | "id": "c8164157fffcd824", 12 | "type": "group", 13 | "z": "b1993d2a213b2bb0", 14 | "name": "CSMS 1 and 2", 15 | "style": { 16 | "label": true 17 | }, 18 | "nodes": [ 19 | "4b27ab1b686062f0", 20 | "3fe3a09b9c926cb3", 21 | "51b49d5f6693963b" 22 | ], 23 | "x": 114, 24 | "y": 439, 25 | "w": 492, 26 | "h": 142 27 | }, 28 | { 29 | "id": "a1b1a864ce992088", 30 | "type": "CP client JSON", 31 | "z": "b1993d2a213b2bb0", 32 | "name": "Variable cbId EVSE2", 33 | "cbId": "NR1", 34 | "remotecs": "c7fb8bebb5ae2658", 35 | "ocppver": "1.6j", 36 | "command": "", 37 | "cmddata": "", 38 | "log": false, 39 | "pathlog": "", 40 | "x": 600, 41 | "y": 200, 42 | "wires": [ 43 | [] 44 | ] 45 | }, 46 | { 47 | "id": "4b27ab1b686062f0", 48 | "type": "CS server", 49 | "z": "b1993d2a213b2bb0", 50 | "g": "c8164157fffcd824", 51 | "name": "Example CSMS 1 on Port 1234", 52 | "port": "1234", 53 | "enabled15": false, 54 | "path15": "", 55 | "enabled16": false, 56 | "path16": "", 57 | "enabled16j": true, 58 | "path16j": "/ocpp", 59 | "log": false, 60 | "pathlog": "", 61 | "x": 270, 62 | "y": 480, 63 | "wires": [ 64 | [ 65 | "3fe3a09b9c926cb3" 66 | ] 67 | ] 68 | }, 69 | { 70 | "id": "3fe3a09b9c926cb3", 71 | "type": "debug", 72 | "z": "b1993d2a213b2bb0", 73 | "g": "c8164157fffcd824", 74 | "name": "debug 1", 75 | "active": true, 76 | "tosidebar": true, 77 | "console": false, 78 | "tostatus": false, 79 | "complete": "false", 80 | "statusVal": "", 81 | "statusType": "auto", 82 | "x": 500, 83 | "y": 480, 84 | "wires": [] 85 | }, 86 | { 87 | "id": "0c15002a027787ac", 88 | "type": "inject", 89 | "z": "b1993d2a213b2bb0", 90 | "name": "reconnect basic", 91 | "props": [ 92 | { 93 | "p": "payload" 94 | }, 95 | { 96 | "p": "topic", 97 | "vt": "str" 98 | } 99 | ], 100 | "repeat": "", 101 | "crontab": "", 102 | "once": false, 103 | "onceDelay": 0.1, 104 | "topic": "", 105 | "payload": "{\"msgType\":99,\"command\":\"connect\",\"data\":\"\"}", 106 | "payloadType": "json", 107 | "x": 200, 108 | "y": 100, 109 | "wires": [ 110 | [ 111 | "a1b1a864ce992088" 112 | ] 113 | ] 114 | }, 115 | { 116 | "id": "2dc76b95802e6e3c", 117 | "type": "inject", 118 | "z": "b1993d2a213b2bb0", 119 | "name": "close websocket connection", 120 | "props": [ 121 | { 122 | "p": "payload" 123 | }, 124 | { 125 | "p": "topic", 126 | "vt": "str" 127 | } 128 | ], 129 | "repeat": "", 130 | "crontab": "", 131 | "once": false, 132 | "onceDelay": 0.1, 133 | "topic": "", 134 | "payload": "{\"msgType\":99,\"command\":\"close\",\"data\":\"\"}", 135 | "payloadType": "json", 136 | "x": 240, 137 | "y": 380, 138 | "wires": [ 139 | [ 140 | "a1b1a864ce992088" 141 | ] 142 | ] 143 | }, 144 | { 145 | "id": "079d1fbda5f65c4e", 146 | "type": "inject", 147 | "z": "b1993d2a213b2bb0", 148 | "name": "reconnect as cbId NR2", 149 | "props": [ 150 | { 151 | "p": "payload" 152 | }, 153 | { 154 | "p": "topic", 155 | "vt": "str" 156 | } 157 | ], 158 | "repeat": "", 159 | "crontab": "", 160 | "once": false, 161 | "onceDelay": 0.1, 162 | "topic": "", 163 | "payload": "{\"msgType\":99,\"command\":\"connect\",\"data\":{\"cbId\":\"NR2\"}}", 164 | "payloadType": "json", 165 | "x": 220, 166 | "y": 140, 167 | "wires": [ 168 | [ 169 | "a1b1a864ce992088" 170 | ] 171 | ] 172 | }, 173 | { 174 | "id": "14dc14c7a2b18117", 175 | "type": "inject", 176 | "z": "b1993d2a213b2bb0", 177 | "name": "reconnect as cbId NR3", 178 | "props": [ 179 | { 180 | "p": "payload" 181 | }, 182 | { 183 | "p": "topic", 184 | "vt": "str" 185 | } 186 | ], 187 | "repeat": "", 188 | "crontab": "", 189 | "once": false, 190 | "onceDelay": 0.1, 191 | "topic": "", 192 | "payload": "{\"msgType\":99,\"command\":\"connect\",\"data\":{\"cbId\":\"NR3\"}}", 193 | "payloadType": "json", 194 | "x": 220, 195 | "y": 180, 196 | "wires": [ 197 | [ 198 | "a1b1a864ce992088" 199 | ] 200 | ] 201 | }, 202 | { 203 | "id": "21841372de7337c3", 204 | "type": "inject", 205 | "z": "b1993d2a213b2bb0", 206 | "name": "reconnect CSMS 2 as cbId NR3", 207 | "props": [ 208 | { 209 | "p": "payload" 210 | }, 211 | { 212 | "p": "topic", 213 | "vt": "str" 214 | } 215 | ], 216 | "repeat": "", 217 | "crontab": "", 218 | "once": false, 219 | "onceDelay": 0.1, 220 | "topic": "", 221 | "payload": "{\"msgType\":99,\"command\":\"connect\",\"data\":{\"cbId\":\"NR3\",\"csmsUrl\":\"ws://localhost:5678/ocpp\"}}", 222 | "payloadType": "json", 223 | "x": 250, 224 | "y": 280, 225 | "wires": [ 226 | [ 227 | "a1b1a864ce992088" 228 | ] 229 | ] 230 | }, 231 | { 232 | "id": "b02bc86a19957aef", 233 | "type": "inject", 234 | "z": "b1993d2a213b2bb0", 235 | "name": "reconnect CSMS 1 as cbId NR3", 236 | "props": [ 237 | { 238 | "p": "payload" 239 | }, 240 | { 241 | "p": "topic", 242 | "vt": "str" 243 | } 244 | ], 245 | "repeat": "", 246 | "crontab": "", 247 | "once": false, 248 | "onceDelay": 0.1, 249 | "topic": "", 250 | "payload": "{\"msgType\":99,\"command\":\"connect\",\"data\":{\"cbId\":\"NR3\",\"csmsUrl\":\"ws://localhost:1234/ocpp\"}}", 251 | "payloadType": "json", 252 | "x": 250, 253 | "y": 220, 254 | "wires": [ 255 | [ 256 | "a1b1a864ce992088" 257 | ] 258 | ] 259 | }, 260 | { 261 | "id": "cab13276a30db051", 262 | "type": "inject", 263 | "z": "b1993d2a213b2bb0", 264 | "name": "reconnect CSMS 2 as cbId NR4", 265 | "props": [ 266 | { 267 | "p": "payload" 268 | }, 269 | { 270 | "p": "topic", 271 | "vt": "str" 272 | } 273 | ], 274 | "repeat": "", 275 | "crontab": "", 276 | "once": false, 277 | "onceDelay": 0.1, 278 | "topic": "", 279 | "payload": "{\"msgType\":99,\"command\":\"connect\",\"data\":{\"cbId\":\"NR4\",\"csmsUrl\":\"ws://localhost:5678/ocpp\"}}", 280 | "payloadType": "json", 281 | "x": 250, 282 | "y": 320, 283 | "wires": [ 284 | [ 285 | "a1b1a864ce992088" 286 | ] 287 | ] 288 | }, 289 | { 290 | "id": "51b49d5f6693963b", 291 | "type": "CS server", 292 | "z": "b1993d2a213b2bb0", 293 | "g": "c8164157fffcd824", 294 | "name": "Example CSMS 2 on Port 5678", 295 | "port": "5678", 296 | "enabled15": false, 297 | "path15": "", 298 | "enabled16": false, 299 | "path16": "", 300 | "enabled16j": true, 301 | "path16j": "/ocpp", 302 | "log": false, 303 | "pathlog": "", 304 | "x": 270, 305 | "y": 540, 306 | "wires": [ 307 | [ 308 | "3fe3a09b9c926cb3" 309 | ] 310 | ] 311 | }, 312 | { 313 | "id": "c7fb8bebb5ae2658", 314 | "type": "ocpp-remote-cs", 315 | "name": "localhost:1234", 316 | "url": "ws://localhost:1234/ocpp" 317 | } 318 | ] -------------------------------------------------------------------------------- /images/clippednode-redexample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argonne-vci/node-red-contrib-ocpp/b16b5e42622a31be90ee5a55942f03e2a0fae3d1/images/clippednode-redexample.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6" 4 | }, 5 | "include": [ 6 | "ocpp/**/*" 7 | ] 8 | } -------------------------------------------------------------------------------- /ocpp/OCPP_CentralSystemService_1.6.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Type of string defining identification token, e.g. RFID or credit card number. To be treated as case insensitive. 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | String of maximum 20 printable characters. To be treated as case insensitive. 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | String of maximum 25 printable characters. To be treated as case insensitive. 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | String of maximum 50 printable characters. To be treated as case insensitive. 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | String of maximum 255 printable characters. To be treated as case insensitive. 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | String of maximum 500 printable characters. To be treated as case insensitive. 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Defines the authorization-status-value 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | Defines the Authorize.req PDU 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Defines the Authorize.conf PDU 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Defines the StartTransaction.req PDU 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | Defines the StartTransaction.conf PDU 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | Reason for stopping a transaction 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | Defines the StopTransaction.req PDU 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | Defines the StopTransaction.conf PDU 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | Defines the Heartbeat.req PDU 199 | 200 | 201 | 202 | 203 | 204 | Defines the Heartbeat.conf PDU 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | Defines the SampledValue-value 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | Defines single value of the meter-value-value 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | Defines the MeterValues.req PDU 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | Defines the MeterValues.conf PDU 343 | 344 | 345 | 346 | 347 | 348 | Defines the BootNotification.req PDU 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | Defines the registration-status-value 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | Defines the BootNotification.conf PDU 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | Defines the charge-point-error-value 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | Defines the charge-point-status-value 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | Defines the StatusNotification.req PDU 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | Defines the StatusNotification.conf PDU 444 | 445 | 446 | 447 | 448 | 449 | Defines the firmware-status-value 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | Defines the FirmwareStatusNotification.req PDU 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | Defines the FirmwareStatusNotification.conf PDU 474 | 475 | 476 | 477 | 478 | 479 | Defines the diagnostics-status-value 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | Defines the DiagnosticsStatusNotification.req PDU 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | Defines the DiagnosticsStatusNotification.conf PDU 501 | 502 | 503 | 504 | 505 | 506 | Defines the DataTransfer.req PDU 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | Defines the status returned in DataTransfer.conf 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | Defines the DataTransfer.conf PDU 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | The Central System Service for the Open Charge Point Protocol 820 | 821 | 822 | 823 | 824 | 825 | 826 | -------------------------------------------------------------------------------- /ocpp/ocpp-cp-json.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 68 | 69 | 72 | 73 | 148 | 149 | 214 | -------------------------------------------------------------------------------- /ocpp/ocpp-cp-json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Websocket = require('ws'); 4 | 5 | const events = require('events'); 6 | const EventEmitter = events.EventEmitter; 7 | const Logger = require('./utils/logdata'); 8 | const debug = require('debug')('anl:ocpp:cp:server:json'); 9 | const crypto = require('crypto'); 10 | 11 | const url = require('node:url'); 12 | const path = require('node:path'); 13 | 14 | let ee = new EventEmitter(); 15 | 16 | const WSTOMIN_DEF = 5; 17 | const WSTOMAX_DEF = 360; 18 | const WSTOINC_DEF = 5; 19 | 20 | const OCPPPROTOCOL = ['ocpp1.6']; 21 | 22 | module.exports = function(RED) { 23 | function OCPPChargePointJNode(config) { 24 | RED.nodes.createNode(this, config); 25 | 26 | debug('Starting CP client JSON node'); 27 | 28 | const CALL = 2; 29 | const CALLRESULT = 3; 30 | const CONTROL = 99; 31 | 32 | // for logging... 33 | const MSGTYPESTR = ['unknown', 'unknown', 'received', 'replied', 'error']; 34 | 35 | const MSGTYPE = 0; 36 | const MSGID = 1; 37 | const MSGACTION = 2; 38 | const MSGCALLPAYLOAD = 3; 39 | const MSGRESPAYLOAD = 2; 40 | 41 | this.remotecs = RED.nodes.getNode(config.remotecs); 42 | 43 | this.csms_url = this.remotecs.url.endsWith('/') ? this.remotecs.url.slice(0, -1) : this.remotecs.url; 44 | this.cbId = config.cbId; 45 | this.ocppVer = this.ocppver; 46 | this.name = config.name || this.remotecs.name; 47 | this.command = config.command; 48 | this.cmddata = config.cmddata; 49 | this.logging = config.log || false; 50 | this.pathlog = config.pathlog; 51 | 52 | this.delay_auto_connect = config.wsdelayconnect || false; 53 | this.wstomin = (isNaN(Number.parseInt(config.wstomin))) ? WSTOMIN_DEF : Number.parseInt(config.wstomin); 54 | let _wstomax = (isNaN(Number.parseInt(config.wstomax))) ? WSTOMAX_DEF : Number.parseInt(config.wstomax); 55 | this.wstomax = parseInt((_wstomax >= this.wstomin)? _wstomax : this.wstomin); 56 | this.wstoinc = (isNaN(Number.parseInt(config.wstoinc))) ? WSTOINC_DEF : Number.parseInt(config.wstoinc); 57 | 58 | const node = this; 59 | 60 | node.status({ fill: 'blue', shape: 'ring', text: 'OCPP CS 1.6' }); 61 | 62 | node.req_map = new Map(); 63 | 64 | const logger = new Logger(this, this.pathlog, this.name); 65 | logger.enabled = (this.logging && (typeof this.pathlog === 'string') && this.pathlog !== ''); 66 | 67 | let csmsURL; 68 | let ws; 69 | let wsreconncnt = 0; 70 | let wstocur = parseInt(node.wstomin); 71 | let conto; 72 | let wsnoreconn = false; 73 | let NetStatus = 'OFFLINE'; 74 | 75 | function reconn_debug() { 76 | debug(`wstomin: ${node.wstomin}`); 77 | debug(`wstomax: ${node.wstomax}`); 78 | debug(`wstoinc: ${node.wstoinc}`); 79 | debug(`wstocur: ${wstocur}`); 80 | debug(`wsreconncnt: ${wsreconncnt}`); 81 | }; 82 | 83 | // We attemt to verify that we have a valid CMSM URL 84 | // If not, we skip doing an autoconnect at startup 85 | // Otherwise, connecting will cause NR to crash 86 | // 87 | try { 88 | debug(`startup CSMS URL: ${node.csms_url}`); 89 | csmsURL = new URL(node.csms_url); 90 | csmsURL.pathname = path.join(csmsURL.pathname, node.cbId) 91 | 92 | debug(`CSMS URL: ${csmsURL.href}`); 93 | logger.log('info', `Making websocket connection to ${csmsURL.href}`); 94 | 95 | } catch(error) { 96 | node.status( { fill: 'red', shape: 'ring', text: error }); 97 | debug(`URL error: ${error}`); 98 | // this.wsdelayconnect = true; 99 | // return; 100 | } 101 | 102 | 103 | // Add a ping timer handle 104 | let hPingTimer = null; 105 | 106 | const wsOptions = { 107 | // WebSocket: Websocket, // custom WebSocket constructor 108 | connectionTimeout: 5000, 109 | handshaketimeout: 5000, 110 | // startClosed: this.wsdelayconnect, 111 | //maxRetries: 10, //default to infinite retries 112 | }; 113 | 114 | // Not sure that either one of these will get called since using the 115 | // websockets-reconnect 116 | const wsPing = function(){ 117 | debug('Got Ping'); 118 | ws.pong(); 119 | }; 120 | const wsPong = function(){ 121 | debug('Got Pong'); 122 | }; 123 | 124 | const wsOpen = function(){ 125 | let msg = {}; 126 | msg.ocpp = {}; 127 | // msg.payload = {}; 128 | wsreconncnt = 0; 129 | wstocur = parseInt(node.wstomin); 130 | node.status({fill: 'green', shape: 'dot', text: 'Connected...'}); 131 | node.wsconnected = true; 132 | msg.ocpp.websocket = 'ONLINE'; 133 | debug(`Websocket open to URL: ${csmsURL.href}`); 134 | if (NetStatus != msg.ocpp.websocket) { 135 | node.send(msg);//send update 136 | NetStatus = msg.ocpp.websocket; 137 | } 138 | // Add a ping intervale timer 139 | hPingTimer = setInterval(() => { ws.ping(); }, 3000); 140 | }; 141 | 142 | const wsClose = function(code, reason){ 143 | let msg = {}; 144 | msg.ocpp = {}; 145 | // msg.payload = {}; 146 | logger.log('info', `Closing websocket connection to ${csmsURL.href}`); 147 | node.debug(code); 148 | debug(`Websocket closed code: ${code.code}`); 149 | debug(`Websocket closed reason: ${reason}`); 150 | debug(JSON.stringify(code)); 151 | node.status({fill: 'red', shape: 'dot', text: 'Closed...'}); 152 | node.wsconnected = false; 153 | msg.ocpp.websocket = 'OFFLINE'; 154 | if (NetStatus != msg.ocpp.websocket) { 155 | node.send(msg);//send update 156 | NetStatus = msg.ocpp.websocket; 157 | } 158 | // Stop the ping timer 159 | if (hPingTimer != null){ 160 | clearInterval(hPingTimer); 161 | hPingTimer = null; 162 | } 163 | 164 | ws.removeEventListener('open',wsOpen); 165 | ws.removeEventListener('close',wsClose); 166 | ws.removeEventListener('error',wsError); 167 | ws.removeEventListener('message',wsMessage); 168 | ws.removeEventListener('ping',wsPing); 169 | ws.removeEventListener('pong',wsPong); 170 | 171 | if (!wsnoreconn){ 172 | wsreconncnt += 1; 173 | node.status( { fill: 'red', shape: 'dot', text: `(${wsreconncnt}) Reconnecting` }); 174 | conto = setTimeout( () => wsConnect(), wstocur * 1000); 175 | debug(`ws reconnect timeout: ${wstocur}`); 176 | wstocur += +node.wstoinc; 177 | wstocur = (wstocur >= node.wstomax) ? node.wstomax : wstocur; 178 | } else { 179 | node.status({ fill: 'red', shape: 'dot', text: 'Closed' }); 180 | } 181 | }; 182 | 183 | 184 | const wsError = function(err){ 185 | node.log('Websocket error:', {err}); 186 | // debug('Websocket error:', {err}); 187 | }; 188 | 189 | const wsMessage = function(event){ 190 | debug('Got a message '); 191 | let msgIn = event.data; 192 | let msg = {}; 193 | msg.ocpp = {}; 194 | msg.payload = {}; 195 | 196 | msg.ocpp.ocppVersion = '1.6j'; 197 | 198 | let response = []; 199 | let id = crypto.randomUUID(); 200 | 201 | let msgParsed; 202 | 203 | 204 | if (msgIn[0] != '[') { 205 | msgParsed = JSON.parse('[' + msgIn + ']'); 206 | } else { 207 | msgParsed = JSON.parse(msgIn); 208 | } 209 | 210 | logger.log(MSGTYPESTR[msgParsed[MSGTYPE]], JSON.stringify(msgParsed)); 211 | 212 | if (msgParsed[MSGTYPE] == CALL) { 213 | debug(`Got a CALL Message ${msgParsed[MSGID]}`); 214 | // msg.msgId = msgParsed[msgId]; 215 | msg.msgId = id; 216 | msg.ocpp.MessageId = msgParsed[MSGID]; 217 | msg.ocpp.msgType = CALL; 218 | msg.ocpp.command = msgParsed[MSGACTION]; 219 | msg.payload.command = msgParsed[MSGACTION]; 220 | msg.payload.data = msgParsed[MSGCALLPAYLOAD]; 221 | 222 | let to = setTimeout(function(id) { 223 | // node.log("kill:" + id); 224 | if (ee.listenerCount(id) > 0) { 225 | let evList = ee.listeners(id); 226 | let x = evList[0]; 227 | ee.removeListener(id, x); 228 | } 229 | }, 120 * 1000, id); 230 | 231 | // This makes the response async so that we pass the responsibility onto the response node 232 | ee.once(id, function(returnMsg) { 233 | clearTimeout(to); 234 | response[MSGTYPE] = CALLRESULT; 235 | response[MSGID] = msgParsed[MSGID]; 236 | response[MSGRESPAYLOAD] = returnMsg; 237 | 238 | logger.log(MSGTYPESTR[response[MSGTYPE]], JSON.stringify(response).replace(/,/g, ', ')); 239 | 240 | ws.send(JSON.stringify(response)); 241 | 242 | }); 243 | node.status({fill: 'green', shape: 'dot', text: `message in: ${msg.ocpp.command}`}); 244 | debug(`${ws.url} : message in: ${msg.ocpp.command}`); 245 | node.send(msg); 246 | } else if (msgParsed[MSGTYPE] == CALLRESULT) { 247 | debug(`Got a CALLRESULT msgId ${msgParsed[MSGID]}`); 248 | msg.msgId = msgParsed[MSGID]; 249 | msg.ocpp.MessageId = msgParsed[MSGID]; 250 | msg.ocpp.msgType = CALLRESULT; 251 | msg.payload.data = msgParsed[MSGRESPAYLOAD]; 252 | 253 | msg.ocpp.websocket = (node.wsconnected)? 'ONLINE' : 'OFFLINE'; 254 | 255 | if (node.req_map.has(msg.msgId)){ 256 | msg.ocpp.command = node.req_map.get(msg.msgId); 257 | node.req_map.delete(msg.msgId); 258 | } else { 259 | msg.ocpp.command = 'unknown'; 260 | } 261 | 262 | node.status({fill: 'green', shape: 'dot', text: `response in: ${msg.ocpp.command}`}); 263 | debug(`response in: ${msg.ocpp.command}`); 264 | node.send(msg); 265 | 266 | } 267 | 268 | }; 269 | 270 | const wsConnect = function() { 271 | reconn_debug(); 272 | try { 273 | ws = new Websocket(csmsURL.href, OCPPPROTOCOL, wsOptions); 274 | ws.timeout = 5000; 275 | debug(`${node.cbId} wsConnect()`); 276 | ws.addEventListener('open',wsOpen); 277 | ws.addEventListener('close',wsClose); 278 | ws.addEventListener('error',wsError); 279 | ws.addEventListener('message',wsMessage); 280 | ws.addEventListener('ping',wsPing); 281 | ws.addEventListener('pong',wsPong); 282 | }catch(error){ 283 | debug(`Websocket Error: ${error}`); 284 | return; 285 | } 286 | }; 287 | 288 | const wsReconnect = function(){ 289 | debug('Clearing Timeout'); 290 | clearTimeout(conto); 291 | try { 292 | if (ws){ 293 | ws.removeEventListener('open',wsOpen); 294 | ws.removeEventListener('close',wsClose); 295 | ws.removeEventListener('error',wsError); 296 | ws.removeEventListener('message',wsMessage); 297 | ws.removeEventListener('ping',wsPing); 298 | ws.removeEventListener('pong',wsPong); 299 | ws.close(); 300 | } 301 | clearTimeout(conto); 302 | wsConnect(); 303 | }catch(error){ 304 | debug(`Websocket Error: ${error}`); 305 | return; 306 | } 307 | }; 308 | 309 | // Only do this if auto-connect is enabled 310 | // 311 | if (!(node.delay_auto_connect) && csmsURL){ 312 | node.status({fill: 'blue', shape: 'dot', text: `Connecting...`}); 313 | wsConnect(); 314 | } 315 | 316 | 317 | //////////////////////////////////////////// 318 | // This section is for input from a the // 319 | // Node itself // 320 | //////////////////////////////////////////// 321 | 322 | this.on('input', function(msg) { 323 | 324 | let request = []; 325 | let messageTypeStr = ['unknown', 'unknown', 'request', 'replied', 'error']; 326 | 327 | debug(JSON.stringify(msg)); 328 | 329 | request[MSGTYPE] = msg.payload.msgType || CALL; 330 | request[MSGID] = msg.payload.MessageId || crypto.randomUUID(); 331 | 332 | if (request[MSGTYPE] == CONTROL) { 333 | 334 | request[MSGACTION] = msg.payload.command || node.command; 335 | 336 | if (!request[MSGACTION]){ 337 | const errStr = 'ERROR: Missing Control Command in JSON request message'; 338 | node.error(errStr); 339 | debug(errStr); 340 | return; 341 | } 342 | 343 | switch (request[MSGACTION].toLowerCase()){ 344 | case 'connect': 345 | if (msg.payload.data && msg.payload.data.hasOwnProperty('cbId')){ 346 | this.cbId = msg.payload.data.cbId; 347 | debug(`Injected cbId: ${this.cbId}`); 348 | } 349 | if (msg.payload.data && msg.payload.data.hasOwnProperty('csmsUrl')){ 350 | this.csms_url = msg.payload.data.csmsUrl.endsWith('/') ? msg.payload.data.csmsUrl.slice(0, -1) : msg.payload.data.csmsUrl; 351 | debug(`Injected csmsURL: ${this.csms_url}`); 352 | } 353 | try { 354 | if (! csmsURL){ 355 | csmsURL = new URL(node.csms_url); 356 | }else{ 357 | csmsURL.href = node.csms_url; 358 | } 359 | csmsURL.pathname = path.join(csmsURL.pathname, node.cbId); 360 | debug(`connecting to URL: ${csmsURL.href}`); 361 | }catch(error){ 362 | node.status({ fill: 'red', shape: 'ring', text: error }); 363 | debug(`URL error: ${error}`); 364 | return; 365 | } 366 | wsnoreconn = false; 367 | wsreconncnt = 0; 368 | wstocur = parseInt(node.wstomin); 369 | wsReconnect(); 370 | break; 371 | case 'close': 372 | wsnoreconn = true; 373 | if (ws){ 374 | clearTimeout(conto); 375 | ws.close(); 376 | node.status( { fill: 'red', shape: 'dot', text: 'Closed' }); 377 | } 378 | break; 379 | case 'ws_state': 380 | msg = {}; 381 | msg.ocpp = {}; 382 | msg.ocpp.websocket = (node.wsconnected)? 'ONLINE' : 'OFFLINE'; 383 | if (csmsURL) msg.ocpp.csmsUrl = csmsURL.href; 384 | node.send(msg); 385 | break; 386 | default: 387 | break; 388 | } 389 | 390 | 391 | logger.log(messageTypeStr[request[MSGTYPE]], JSON.stringify(request).replace(/,/g, ', ')); 392 | 393 | } else if (node.wsconnected == true) { 394 | if (request[MSGTYPE] == CALL) { 395 | request[MSGACTION] = msg.payload.command || node.command; 396 | 397 | if (!request[MSGACTION]) { 398 | const errStr = 'ERROR: Missing Command in JSON request message'; 399 | node.error(errStr); 400 | debug(errStr); 401 | return; 402 | } 403 | 404 | let cmddata; 405 | if (node.cmddata) { 406 | try { 407 | cmddata = JSON.parse(node.cmddata); 408 | } catch (e) { 409 | node.warn('OCPP JSON client node invalid payload.data for message (' + msg.ocpp.command + '): ' + e.message); 410 | return; 411 | } 412 | 413 | } 414 | 415 | request[MSGCALLPAYLOAD] = msg.payload.data || cmddata || {}; 416 | if (!request[MSGCALLPAYLOAD]){ 417 | const errStr = 'ERROR: Missing Data in JSON request message'; 418 | node.error(errStr); 419 | debug(errStr); 420 | return; 421 | } 422 | 423 | node.req_map.set(request[MSGID], request[MSGACTION]); 424 | debug(`Sending message: ${request[MSGACTION]}, ${request}`); 425 | node.status({fill: 'green', shape: 'dot', text: `request out: ${request[MSGACTION]}`}); 426 | 427 | } else { // This is a type 3 "response" message 428 | 429 | // Set the response message ID either users defined or original (preferred) 430 | if (msg.payload.MessageId) { 431 | request[MSGID] = msg.payload.MessageId; 432 | } else if (msg.ocpp && msg.ocpp.MessageId) { 433 | request[MSGID] = msg.ocpp.MessageId; 434 | }; 435 | 436 | request[MSGRESPAYLOAD] = msg.payload.data || {}; 437 | debug(`Sending response message: ${JSON.stringify(request[MSGRESPAYLOAD])}`); 438 | node.status({fill: 'green', shape: 'dot', text: 'sending response'}); 439 | } 440 | 441 | logger.log(messageTypeStr[request[MSGTYPE]], JSON.stringify(request).replace(/,/g, ', ')); 442 | 443 | ws.send(JSON.stringify(request)); 444 | } 445 | }); 446 | 447 | this.on('close', function(){ 448 | let msg = {}; 449 | msg.ocpp = {}; 450 | msg.payload = {}; 451 | node.status({fill: 'red', shape: 'dot', text: 'Closed...'}); 452 | logger.log('info', 'Websocket closed'); 453 | debug('Closing CP client JSON node..'); 454 | node.wsconnected = false; 455 | msg.ocpp.websocket = 'OFFLINE'; 456 | if (NetStatus != msg.ocpp.websocket) { 457 | node.send(msg);//send update 458 | NetStatus = msg.ocpp.websocket; 459 | } 460 | wsnoreconn = true; 461 | ws.close(); 462 | }); 463 | 464 | } 465 | // register our node 466 | RED.nodes.registerType('CP client JSON', OCPPChargePointJNode); 467 | }; 468 | -------------------------------------------------------------------------------- /ocpp/ocpp-cp-req.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 67 | 68 | 71 | 72 | 130 | 131 | 177 | -------------------------------------------------------------------------------- /ocpp/ocpp-cp-req.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const soap = require('soap'); 5 | const Logger = require('./utils/logdata'); 6 | 7 | const debug = require('debug')('anl:ocpp:cp-req-soap'); 8 | 9 | module.exports = function(RED) { 10 | function OcppRequestCPNode(config) { 11 | RED.nodes.createNode(this, config); 12 | 13 | debug('Starting cp-req-soap node'); 14 | var node = this; 15 | 16 | this.remotecs = RED.nodes.getNode(config.remotecs); 17 | 18 | this.url = this.remotecs.url; 19 | // this.cbId = this.remotecs.cbId; 20 | this.cbId = config.name; 21 | this.ocppVer = config.ocppver; 22 | this.name = config.name || this.remotecs.name; 23 | this.command = config.command; 24 | this.cmddata = config.cmddata; 25 | this.logging = config.log; 26 | this.pathlog = config.pathlog; 27 | 28 | const logger = new Logger(this, this.pathlog, this.name); 29 | logger.enabled = (this.logging && (typeof this.pathlog === 'string') && this.pathlog !== ''); 30 | 31 | 32 | this.on('input', function(msg) { 33 | 34 | // set up soap requests for SOAP 1.2 headers 35 | var wsdlOptions = { 36 | forceSoap12Headers: true, 37 | }; 38 | 39 | // create the client 40 | let wsdlFile = (node.ocppVer == '1.5s') ? 'ocpp_centralsystemservice_1.5_final.wsdl' : 'OCPP_CentralSystemService_1.6.wsdl'; 41 | soap.createClient(path.join(__dirname, wsdlFile), wsdlOptions, function(err, client){ 42 | if (err) node.error(err); 43 | else { 44 | 45 | var cbId = node.cbId; 46 | 47 | msg.ocpp = {}; 48 | msg.ocpp.command = msg.payload.command || node.command; 49 | msg.ocpp.chargeBoxIdentity = cbId; 50 | msg.ocpp.url = node.url; 51 | msg.ocpp.ocppVer = node.ocppVer; 52 | let cmddata; 53 | if (node.cmddata){ 54 | cmddata = JSON.parse(node.cmddata); 55 | } 56 | msg.ocpp.data = msg.payload.data || cmddata; 57 | 58 | if (!msg.ocpp.command){ 59 | node.error('Missing Command in SOAP request message'); 60 | debug('Missing Command in SOAP request message'); 61 | return; 62 | } else if (!msg.ocpp.data){ 63 | node.error('Missing Data in SOAP request message'); 64 | debug('Missing Data in SOAP request message'); 65 | return; 66 | } 67 | 68 | 69 | // set up or target Central System 70 | client.setEndpoint(msg.ocpp.url); 71 | 72 | // add headers that are specific to OCPP specification 73 | let addressing = 'http://www.w3.org/2005/08/addressing'; 74 | 75 | client.addSoapHeader({'tns:chargeBoxIdentity': msg.ocpp.chargeBoxIdentity}); 76 | 77 | client.addSoapHeader({To: msg.ocpp.url}, null, null, addressing); 78 | 79 | if (node.ocppVer != '1.5s'){ 80 | let act = '' + msg.ocpp.command + ''; 81 | client.addSoapHeader(act); 82 | } 83 | if (node.pathlog == '') node.logging = false; 84 | if (node.logging){ 85 | client.on('request', function(xmlSoap){ 86 | logger.log('request', xmlSoap); 87 | }); 88 | 89 | client.on('response', function(xmlSoap){ 90 | logger.log('replied', xmlSoap); 91 | }); 92 | 93 | client.on('soapError', function(err){ 94 | logger.log('error', err); 95 | }); 96 | 97 | } 98 | 99 | 100 | // send the specific OCPP message 101 | client[msg.ocpp.command](msg.ocpp.data, function(err, response){ 102 | if (err) { 103 | // report any errors 104 | node.error(err); 105 | msg.payload = err; 106 | node.send(msg); 107 | } else { 108 | // put the response to the request in the message payload and send it out the flow 109 | msg.payload.data = response; 110 | node.send(msg); 111 | } 112 | }); 113 | 114 | } 115 | }); 116 | 117 | }); 118 | 119 | } 120 | // register our node 121 | RED.nodes.registerType('CP Request SOAP', OcppRequestCPNode); 122 | }; 123 | -------------------------------------------------------------------------------- /ocpp/ocpp-remote-cp.html: -------------------------------------------------------------------------------- 1 | 28 | 29 | 51 | 52 | 69 | 70 | 71 | 88 | 89 | 106 | -------------------------------------------------------------------------------- /ocpp/ocpp-remote-cp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(RED) { 4 | function OcppRemoteCPNode(n) { 5 | RED.nodes.createNode(this, n); 6 | this.cbId = n.cbId; 7 | this.url = n.url; 8 | this.name = n.name || n.cbId; 9 | this.ocppver = n.ocppver; 10 | } 11 | 12 | function OcppRemotejCPNode(n) { 13 | RED.nodes.createNode(this, n); 14 | this.cbId = n.cbId; 15 | this.name = n.name || n.cbId; 16 | this.ocppver = n.ocppver; 17 | } 18 | 19 | RED.nodes.registerType('ocpp-remote-cp', OcppRemoteCPNode); 20 | RED.nodes.registerType('ocpp-remotej-cp', OcppRemotejCPNode); 21 | }; 22 | -------------------------------------------------------------------------------- /ocpp/ocpp-remote-cs.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 49 | -------------------------------------------------------------------------------- /ocpp/ocpp-remote-cs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(RED) { 4 | function OcppRemoteCSNode(n) { 5 | RED.nodes.createNode(this, n); 6 | this.cbId = n.cbId; 7 | this.url = n.url; 8 | this.name = n.name || n.cbId; 9 | this.ocppver = n.ocppver; 10 | } 11 | RED.nodes.registerType('ocpp-remote-cs', OcppRemoteCSNode); 12 | }; 13 | -------------------------------------------------------------------------------- /ocpp/ocpp-req.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 67 | 68 | 69 | 128 | 129 | 169 | -------------------------------------------------------------------------------- /ocpp/ocpp-req.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var soap = require('soap'); 5 | //Use node built-in crypto for uuid 6 | const crypto = require('crypto'); 7 | 8 | const Logger = require('./utils/logdata'); 9 | 10 | 11 | const debug = require('debug')('anl:ocpp:req'); 12 | 13 | 14 | module.exports = function(RED) { 15 | function OcppRequestNode(config) { 16 | RED.nodes.createNode(this, config); 17 | 18 | var node = this; 19 | 20 | this.remotecb = RED.nodes.getNode(config.remotecb); 21 | 22 | this.url = this.remotecb.url; 23 | this.cbId = this.remotecb.cbId; 24 | this.ocppVer = this.remotecb.ocppver; 25 | this.name = config.name || this.remotecb.name; 26 | this.command = config.command; 27 | this.cmddata = config.cmddata; 28 | this.logging = config.log; 29 | this.pathlog = config.pathlog; 30 | 31 | node.status({fill: 'blue', shape: 'dot', text: `waiting to send: ${node.cbId}`}); 32 | 33 | const logger = new Logger(this, this.pathlog, this.name); 34 | logger.enabled = (this.logging && (typeof this.pathlog === 'string') && this.pathlog !== ''); 35 | 36 | this.on('input', function(msg) { 37 | 38 | // set up soap requests for SOAP 1.2 headers 39 | var wsdlOptions = { 40 | forceSoap12Headers: true, 41 | }; 42 | 43 | // create the client 44 | let wsdlFile = (node.ocppVer == '1.5s') ? 'ocpp_chargepointservice_1.5_final.wsdl' : 'OCPP_ChargePointService_1.6.wsdl'; 45 | soap.createClient(path.join(__dirname, wsdlFile), wsdlOptions, function(err, client){ 46 | if (err) node.error(err); 47 | else { 48 | 49 | var cbId = node.cbId; 50 | 51 | msg.ocpp = {}; 52 | msg.ocpp.command = msg.payload.command || node.command; 53 | msg.ocpp.MessageId = msg.payload.MessageId || crypto.randomUUID(); 54 | msg.ocpp.chargeBoxIdentity = cbId; 55 | msg.ocpp.url = node.url; 56 | msg.ocpp.ocppVer = node.ocppVer; 57 | let cmddata; 58 | if (node.cmddata){ 59 | cmddata = JSON.parse(node.cmddata); 60 | } 61 | msg.ocpp.data = msg.payload.data || cmddata; 62 | 63 | if (!msg.ocpp.command){ 64 | let errmsg = 'Missing Command in SOAP request message'; 65 | node.error(errmsg); 66 | debug(errmsg); 67 | return; 68 | } else if (!msg.ocpp.data){ 69 | let errmsg = 'Missing Data in SOAP request message'; 70 | node.error(errmsg); 71 | debug(errmsg); 72 | return; 73 | } 74 | 75 | // set up or target charge point 76 | client.setEndpoint(msg.ocpp.url); 77 | 78 | // add headers that are specific to OCPP specification 79 | let addressing = 'http://www.w3.org/2005/08/addressing'; 80 | 81 | client.addSoapHeader({'tns:chargeBoxIdentity': msg.ocpp.chargeBoxIdentity}); 82 | 83 | client.addSoapHeader({To: msg.ocpp.url}, null, null, addressing); 84 | 85 | // if (node.ocppVer != "1.5s"){ 86 | let act = `/${msg.ocpp.command}`; 87 | client.addSoapHeader(act); 88 | // } 89 | let repto = `
http://www.w3.org/2005/08/addressing/anonymous
`; 90 | client.addSoapHeader(repto); 91 | let msgid = `${msg.ocpp.MessageId}`; 92 | client.addSoapHeader(msgid); 93 | 94 | if (msg.ocpp && msg.ocpp.command){ 95 | node.status({fill: 'green', shape: 'dot', text: `request out: ${msg.ocpp.command}`}); 96 | debug(`request out: ${msg.ocpp.command}`); 97 | } else { 98 | node.status({fill: 'red', shape: 'dot', text: 'MISSING COMMAND'}); 99 | debug('MISSING COMMAND'); 100 | } 101 | if (node.pathlog == '') node.logging = false; 102 | if (node.logging){ 103 | client.on('request', function(xmlSoap, xchgId){ 104 | logger.log('request', xmlSoap); 105 | }); 106 | 107 | client.on('response', function(xmlSoap, fullinfo, xchgId){ 108 | logger.log('replied', xmlSoap); 109 | }); 110 | 111 | client.on('soapError', function(err, xchgId){ 112 | debug(`got SOAP error: ${err.message}`); 113 | //logData('error', err); 114 | }); 115 | 116 | } 117 | // client.addSoapHeader({Action: '/' + msg.ocpp.command||node.command }, null, null, 'http://www.w3.org/2005/08/addressing'); 118 | 119 | // send the specific OCPP message 120 | 121 | client[msg.ocpp.command](msg.ocpp.data, function(err, response){ 122 | if (err) { 123 | // report any errors 124 | node.error(err); 125 | msg.payload = err; 126 | node.send(msg); 127 | } else { 128 | // put the response to the request in the message payload and send it out the flow 129 | msg.payload.data = response; 130 | node.status({fill: 'green', shape: 'dot', text: `response in: ${msg.ocpp.command}`}); 131 | debug(`response in: ${msg.ocpp.command}`); 132 | node.send(msg); 133 | } 134 | }); 135 | 136 | } 137 | }); 138 | 139 | }); 140 | 141 | } 142 | // register our node 143 | RED.nodes.registerType('CS request SOAP', OcppRequestNode); 144 | }; 145 | -------------------------------------------------------------------------------- /ocpp/ocpp-server.html: -------------------------------------------------------------------------------- 1 | 15 | 258 | 259 | 326 | 327 | 376 | 377 | 384 | 385 | 410 | 411 | 464 | 465 | 514 | 515 | 573 | 574 | 641 | -------------------------------------------------------------------------------- /ocpp/ocpp_centralsystemservice_1.5_final.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 10 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Type of string defining identification token, e.g. RFID or credit card number. To be treated as case insensitive. 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Defines the authorization-status-value 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | String type of max 25 chars that is to be treated as case insensitive. 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | String type of max 20 chars that is to be treated as case insensitive. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | String type of max 25 chars that is to be treated as case insensitive. 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | String type of max 20 chars that is to be treated as case insensitive. 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | String type of max 50 chars that is to be treated as case insensitive. 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | String type of max 20 chars that is to be treated as case insensitive. 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | String type of max 20 chars that is to be treated as case insensitive. 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | String type of max 25 chars that is to be treated as case insensitive. 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | String type of max 25 chars that is to be treated as case insensitive. 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | Defines the Authorize.req PDU 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | Defines the Authorize.conf PDU 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | Defines the StartTransaction.req PDU 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | Defines the StartTransaction.conf PDU 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | This contains transaction usage details relevant for billing purposes in StopTransaction.req PDU 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | Defines the StopTransaction.req PDU 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | Defines the StopTransaction.conf PDU 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | Defines the Heartbeat.req PDU 223 | 224 | 225 | 226 | 227 | 228 | Defines the Heartbeat.conf PDU 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | Defines single value of the meter-value-value 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | Defines the MeterValues.req PDU 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | Defines the MeterValues.conf PDU 334 | 335 | 336 | 337 | 338 | 339 | Defines the BootNotification.req PDU 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | Defines the registration-status-value 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | Defines the BootNotification.conf PDU 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | Defines the charge-point-error-value 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | Defines the charge-point-status-value 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | Defines the StatusNotification.req PDU 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | Defines the StatusNotification.conf PDU 427 | 428 | 429 | 430 | 431 | 432 | Defines the firmware-status-value 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | Defines the FirmwareStatusNotification.req PDU 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | Defines the FirmwareStatusNotification.conf PDU 454 | 455 | 456 | 457 | 458 | 459 | Defines the diagnostics-status-value 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | Defines the DiagnosticsStatusNotification.req PDU 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | Defines the DiagnosticsStatusNotification.conf PDU 479 | 480 | 481 | 482 | 483 | 484 | Defines the DataTransfer.req PDU 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | Defines the status returned in DataTransfer.conf 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | Defines the DataTransfer.conf PDU 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | The Central System Service for the Open Charge Point Protocol 751 | 752 | 753 | 754 | 755 | 756 | 757 | -------------------------------------------------------------------------------- /ocpp/utils/logdata.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const os = require('os'); 4 | const fs = require('fs'); 5 | const debug = require('debug')('anl:ocpp:utils:logdata'); 6 | 7 | 8 | class LogData { 9 | constructor(node, logpath, label = 'info') { 10 | this._label = label; 11 | this.logpath = logpath; 12 | this._enabled = true; 13 | this._node = node; 14 | } 15 | 16 | set enabled(val) { 17 | this._enabled = val; 18 | } 19 | 20 | get enabled() { 21 | return this._enabled; 22 | } 23 | 24 | set label(label) { 25 | this._label = label; 26 | } 27 | 28 | get label() { 29 | return this._label; 30 | } 31 | 32 | log(type, data) { 33 | if (this.enabled) { 34 | // set a timestamp for the logged item 35 | let date = new Date().toLocaleString(); 36 | let dataStr = ''; 37 | if (typeof data === 'string') { 38 | dataStr = data.replace(/[\n\r]/g, ''); 39 | } 40 | // create the logged info from a template 41 | let logInfo = `${date} \t node: ${this.label} \t type: ${type} \t data: ${dataStr} ${os.EOL}`; 42 | 43 | // create/append the log info to the file 44 | fs.appendFile(this.logpath, logInfo, err => { 45 | if (err) { 46 | this._node.error(`Error writing to log file: ${err}`); 47 | debug(`Error writing to log file: ${err} from node ${this._node.name}`); 48 | // If something went wrong then turn off logging 49 | this.enabled = false; 50 | } 51 | }); 52 | } 53 | } 54 | } 55 | 56 | module.exports = LogData; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-ocpp", 3 | "version": "1.3.9", 4 | "description": "A set of nodes to communicate via OCPP to a compatible charge box or central system", 5 | "main": "ocpp-req.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "mon": "nodemon -e js,html --exec node-red" 9 | }, 10 | "engines": { 11 | "node": ">=16.0.0" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Argonne-National-Laboratory/node-red-contrib-ocpp.git" 16 | }, 17 | "keywords": [ 18 | "node-red", 19 | "ocpp", 20 | "ocpp SOAP", 21 | "ocpp JSON", 22 | "ocpp centralsystem", 23 | "ocpp chargepoint" 24 | ], 25 | "node-red": { 26 | "version": ">=2.0.0", 27 | "nodes": { 28 | "ocpp-req": "ocpp/ocpp-req.js", 29 | "ocpp-cp-req": "ocpp/ocpp-cp-req.js", 30 | "ocpp-remote-cp": "ocpp/ocpp-remote-cp.js", 31 | "ocpp-remote-cs": "ocpp/ocpp-remote-cs.js", 32 | "ocpp-server": "ocpp/ocpp-server.js", 33 | "ocpp-cp-json": "ocpp/ocpp-cp-json.js" 34 | } 35 | }, 36 | "author": "Bryan Nystrom (https://github.com/bnystrom)", 37 | "contributors": [ 38 | "Jason D. Harper (https://github.com/jayharper)", 39 | "James Golvich (https://github.com/jamesgol)", 40 | "BeenThereScrewedUp (https://github.com/BeenThereScrewedUp)", 41 | "Simonas Kazlauskas (https://github.com/nagisa)" 42 | ], 43 | "license": "BSD-3-Clause", 44 | "bugs": { 45 | "url": "https://github.com/Argonne-National-Laboratory/node-red-contrib-ocpp/issues" 46 | }, 47 | "homepage": "https://github.com/Argonne-National-Laboratory/node-red-contrib-ocpp#readme", 48 | "dependencies": { 49 | "basic-auth": "^2.0.1", 50 | "debug": "^4.3.1", 51 | "express": "^4.17.1", 52 | "express-ws": "^5.0.2", 53 | "soap": "^1.0.0", 54 | "ws": "^8.12.0", 55 | "xml-js": "^1.6.11" 56 | }, 57 | "devDependencies": { 58 | "eslint": "^9.22.0", 59 | "eslint-config-strongloop": "^2.1.0", 60 | "eslint-plugin-node": "^11.1.0", 61 | "nodemon": "^3.1.9" 62 | } 63 | } 64 | --------------------------------------------------------------------------------