├── tpp.bat ├── doc ├── tpl │ ├── footer.html │ ├── header.html │ └── types.html ├── Makefile ├── gir-ocppjs-doc.js └── wsdl │ ├── ocpp_chargepointservice_1.2_final.wsdl │ ├── ocpp_centralsystemservice_1.2_final.wsdl │ └── ocpp_centralsystemservice_1.6_final.wsdl ├── lib ├── soap │ ├── index.js │ └── lib │ │ ├── http.js │ │ ├── soap.js │ │ ├── client.js │ │ └── server.js ├── main.js ├── utils.js ├── plugins.js ├── simulators.js └── ui.js ├── run.bat ├── gir-ocppjs.js ├── plugins ├── helloworld.js ├── cs-example.js ├── cbxs-automatic-mode.js └── getting_started_with_plugins.html ├── package.json ├── test ├── test-cp-1.2.js ├── test-cp-1.5.js ├── test-cs-1.2.js ├── test-cs-1.5.js └── framework.js └── README.md /tpp.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | npm install & exit -------------------------------------------------------------------------------- /doc/tpl/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /lib/soap/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/soap'); 2 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | start /wait tpp.bat 3 | set /P endp=Enter Endpoint: 4 | set /P cp=Enter Charge Point ID: 5 | cls 6 | node gir-ocppjs.js start_cp %endp% %cp% -------------------------------------------------------------------------------- /gir-ocppjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // -*- coding: utf-8 -*- 3 | "use strict" 4 | 5 | /** 6 | * [ OCPP Simulator : Central System Simulator and Charge Point Simulator ] 7 | */ 8 | 9 | var gir_ocpp_js = require('./lib/main.js'); 10 | 11 | 12 | /* main */ 13 | gir_ocpp_js.main(); 14 | 15 | -------------------------------------------------------------------------------- /plugins/helloworld.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Hello world plugins 3 | * 4 | */ 5 | 6 | 7 | var plugin = { 8 | 9 | name: 'Hello world!', 10 | description: 'Hello World plugin example', 11 | author: '', 12 | 13 | ocpp_version: '1.6', 14 | system: 'cp', 15 | 16 | onLoad: function() { 17 | this.log('Hi, I\'m the '+ plugin.name +' plugin !'); 18 | }, 19 | 20 | onUnload: function() { 21 | this.log('Goodbye !'); 22 | } 23 | 24 | }; 25 | 26 | module.exports = plugin; 27 | 28 | -------------------------------------------------------------------------------- /doc/tpl/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Documentation 5 | 6 | 7 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "girocppjs", 3 | "description": "Experimental OCPP Simulator", 4 | "version": "1.0.1", 5 | "author": "GIR", 6 | "homepage": "http://www.gir.fr/ocppjs", 7 | "license": "BSD", 8 | "engines": { 9 | "node": ">=0.6.10" 10 | }, 11 | "main": "gir-ocppjs.js", 12 | "dependencies": { 13 | "websocket": "~1.0.8", 14 | "xml2js": "~0.2.2", 15 | "node-expat": "~2.0.0", 16 | "request": "~2.16.2", 17 | "jayschema": "~0.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/test-cp-1.2.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // -*- coding: utf-8 -*- 3 | "use strict" 4 | 5 | var Test = require('./framework.js').Test; 6 | 7 | // initialize 8 | Test.init( { system: 'cp', version: '1.2' } ); 9 | 10 | // fill the array with the commands to test 11 | Test.commands = [ 12 | 'bootnotification', 13 | 'authorize', 14 | 'heartbeat', 15 | 'starttransaction', 16 | 'metervalues', 17 | 'stoptransaction', 18 | 'diagnosticsstatusnotification', 19 | 'statusnotification', 20 | 'firmwarestatusnotification' 21 | ]; 22 | 23 | // process tests 24 | Test.test(); 25 | 26 | -------------------------------------------------------------------------------- /test/test-cp-1.5.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // -*- coding: utf-8 -*- 3 | "use strict" 4 | 5 | var Test = require('./framework.js').Test; 6 | 7 | // initialize 8 | Test.init( { system: 'cp', version: '1.6' } ); 9 | 10 | // fill the array with the commands to test 11 | Test.commands = [ 12 | 'bootnotification', 13 | 'authorize', 14 | 'heartbeat', 15 | 'starttransaction', 16 | 'metervalues', 17 | 'stoptransaction', 18 | 'diagnosticsstatusnotification', 19 | 'statusnotification', 20 | 'firmwarestatusnotification', 21 | 'datatransfer' 22 | ]; 23 | 24 | // process tests 25 | Test.test(); 26 | 27 | -------------------------------------------------------------------------------- /test/test-cs-1.2.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // -*- coding: utf-8 -*- 3 | "use strict" 4 | 5 | var Test = require('./framework.js').Test; 6 | 7 | // initialize 8 | Test.init( { system: 'cs', version: '1.2' } ); 9 | 10 | // fill the array with the commands to test 11 | Test.commands = [ 12 | 'remote_reset', 13 | 'remote_changeconfiguration', 14 | 'remote_starttransaction', 15 | 'remote_stoptransaction', 16 | 'remote_unlockconnector', 17 | 'remote_getdiagnostics', 18 | 'remote_changeavailability', 19 | 'remote_updatefirmware', 20 | 'remote_clearcache', 21 | 'remote_starttransaction' 22 | ]; 23 | 24 | // process tests 25 | Test.test(); 26 | 27 | -------------------------------------------------------------------------------- /doc/tpl/types.html: -------------------------------------------------------------------------------- 1 |

Basic data types :

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
SOAP type (XML schema)JSON type
s:stringJSON string
s:intJSON number
s:dateTimeJSON string, formatted as an ISO 8601 combined date and time, with a "Z" 19 | suffix (UTC timezone).
s:anyURIJSON string
s:booleanJSON boolean
30 | -------------------------------------------------------------------------------- /plugins/cs-example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Hello world plugins 3 | * 4 | */ 5 | 6 | 7 | var plugin = { 8 | 9 | name: 'Central System Plugin', 10 | description: 'Central System plugin example', 11 | author: '', 12 | 13 | ocpp_version: '1.6', 14 | system: 'cs', 15 | 16 | onLoad: function() { 17 | var self = this; 18 | 19 | self.onClientConnectionEvent(function(type, cbId) { 20 | switch(type) { 21 | case 'connected': 22 | self.log('[CS-Example] Hi '+ cbId +' !'); 23 | self.cs.call('GetLocalListVersion'); 24 | break; 25 | case 'disconnected': 26 | self.log('[CS-Example] Goodbye '+ cbId +' !'); 27 | break; 28 | } 29 | }); 30 | 31 | self.log('[CS-Example] Started.'); 32 | }, 33 | 34 | }; 35 | 36 | module.exports = plugin; 37 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCPP 1.6 Charge Point Simulator 2 | Hacky but it works for testing purposes 3 | 4 | ChargePoint > CentralSystem all working and can be called on the commandline using "bootnotification, metervalues" etc. 5 | 6 | CentralSystem > ChargePoint : 7 | + All 1.5 features 8 | + All 1.6 features 9 | 10 | This program requires Node.js (http://nodejs.org/). Third-party packages can be installed with the npm utility. Currently, ocppjs depends on 'websocket', 'xml2js', 'node-expat', 'request' and 'jayschema' packages: 11 | 12 | ### Running the Simulator 13 | 14 | Just run "run.bat" 15 | 16 | See: [1.5-Simulator Branch](https://github.com/JavaIsJavaScript/ocpp1.6-CP-Simulator/tree/1.5-Simulator) for 1.5 Simulator, works the same as this one. 17 | 18 | ### Bugs 19 | + None known currently 20 | 21 | [CREDITS](http://www.gir.fr/ocppjs/) 22 | -------------------------------------------------------------------------------- /test/test-cs-1.5.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // -*- coding: utf-8 -*- 3 | "use strict" 4 | 5 | var Test = require('./framework.js').Test; 6 | 7 | // initialize 8 | Test.init( { system: 'cs', version: '1.6' } ); 9 | 10 | // fill the array with the commands to test 11 | Test.commands = [ 12 | 'remote_reset', 13 | 'remote_changeconfiguration', 14 | 'remote_starttransaction', 15 | 'remote_stoptransaction', 16 | 'remote_getlocallistversion', 17 | 'remote_unlockconnector', 18 | 'remote_getdiagnostics', 19 | 'remote_cancelreservation', 20 | 'remote_reservenow', 21 | 'remote_changeavailability', 22 | 'remote_updatefirmware', 23 | 'remote_datatransfer', 24 | 'remote_sendlocallist', 25 | 'remote_clearcache', 26 | 'remote_starttransaction', 27 | 'remote_getconfiguration' 28 | ]; 29 | 30 | // process tests 31 | Test.test(); 32 | 33 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | CC = node 2 | PRG = gir-ocppjs-doc.js 3 | 4 | all: ocpp_1.2 ocpp_1.6 5 | 6 | ocpp_1.2: 7 | cat tpl/header.html > ocpp_1.2.html; 8 | echo "

Experimental OCPP 1.2 over Websocket

" >> ocpp_1.2.html; 9 | cat tpl/types.html >> ocpp_1.2.html; 10 | echo "

Central System service

" >> ocpp_1.2.html; 11 | node ${PRG} wsdl/ocpp_centralsystemservice_1.2_final.wsdl >> ocpp_1.2.html; 12 | echo "

Charge Point service

" >> ocpp_1.2.html; 13 | node ${PRG} wsdl/ocpp_chargepointservice_1.2_final.wsdl >> ocpp_1.2.html; 14 | cat tpl/footer.html >> ocpp_1.2.html; 15 | 16 | ocpp_1.6: 17 | cat tpl/header.html > ocpp_1.6.html; 18 | echo "

Experimental OCPP 1.6 over Websocket

" >> ocpp_1.6.html; 19 | cat tpl/types.html >> ocpp_1.6.html; 20 | echo "

Central System service

" >> ocpp_1.6.html; 21 | node ${PRG} wsdl/ocpp_centralsystemservice_1.6_final.wsdl >> ocpp_1.6.html; 22 | echo "

Charge Point service

" >> ocpp_1.6.html; 23 | node ${PRG} wsdl/ocpp_chargepointservice_1.6_final.wsdl >> ocpp_1.6.html; 24 | cat tpl/footer.html >> ocpp_1.6.html; 25 | 26 | clean: 27 | rm ocpp_1.2.html; rm ocpp_1.6.html; 28 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | /** 4 | * 5 | * 6 | * 7 | */ 8 | 9 | var UI = require('./ui.js'), 10 | OCPP = require('./ocpp-protocol.js'), 11 | Utils = require('./utils.js'); 12 | 13 | function main() { 14 | // if arguments in shell 15 | if(process.argv.length > 2) { 16 | var args = process.argv.slice(2); 17 | 18 | // non-documented option 19 | for(var i = 0; i < args.length; i++) { 20 | if(args[i] == '--with-attr-at') { 21 | OCPP.enableAttributesWithAt(); 22 | args.splice(i, 1); 23 | break; 24 | } 25 | } 26 | 27 | if (UI.parseCommand(args) == -1) { 28 | // if error just print the usage and quit 29 | UI.rl.close(); 30 | return; 31 | } 32 | } 33 | else { 34 | console.log("== GIR ocppjs - OCPP CentralSystem & ChargePoint Simulator =="); 35 | console.log(" > Quick start :"); 36 | UI.getHelp(); 37 | } 38 | 39 | // read WSDL files and fill the data structures 40 | OCPP.readWSDLFiles(); 41 | OCPP.buildJSONSchemas(); 42 | 43 | // launch the command line interface 44 | UI.commandLine(); 45 | } 46 | 47 | exports.main = main; 48 | 49 | -------------------------------------------------------------------------------- /lib/soap/lib/http.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | var url = require('url'), 7 | req = require('request'); 8 | 9 | var VERSION = "0.2.0"; 10 | 11 | exports.request = function(rurl, data, callback, exheaders, exoptions) { 12 | var curl = url.parse(rurl); 13 | var secure = curl.protocol == 'https:'; 14 | var host = curl.hostname; 15 | var port = parseInt(curl.port || (secure ? 443 : 80)); 16 | var path = [curl.pathname || '/', curl.search || '', curl.hash || ''].join(''); 17 | var method = data ? "POST" : "GET"; 18 | var headers = { 19 | "User-Agent": "node-soap/" + VERSION, 20 | "Accept" : "text/html,application/xhtml+xml,application/xml", 21 | "Accept-Encoding": "none", 22 | "Accept-Charset": "utf-8", 23 | "Connection": "close", 24 | "Host" : host 25 | }; 26 | 27 | if (typeof data == 'string') { 28 | headers["Content-Length"] = Buffer.byteLength(data, 'utf8');; 29 | headers["Content-Type"] = "application/x-www-form-urlencoded"; 30 | } 31 | 32 | exheaders = exheaders || {}; 33 | for (var attr in exheaders) { headers[attr] = exheaders[attr]; } 34 | 35 | var options = { 36 | uri: curl, 37 | method: method, 38 | headers: headers 39 | }; 40 | 41 | exoptions = exoptions || {}; 42 | for (var attr in exoptions) { options[attr] = exoptions[attr]; } 43 | 44 | var request = req(options, function (error, res, body) { 45 | if (error) { 46 | callback(error); 47 | } else { 48 | callback(null, res, body); 49 | } 50 | }); 51 | request.on('error', callback); 52 | request.end(data); 53 | } 54 | -------------------------------------------------------------------------------- /doc/gir-ocppjs-doc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * XML-to-JSON program 3 | * The purpose of this program is to generate JSON examples from 4 | * a given XML-Schema 5 | */ 6 | 'use strict' 7 | 8 | var fs = require('fs'), 9 | xml2js = require('xml2js'), 10 | OCPP = require('../lib/ocpp-protocol.js'), 11 | Utils = require('../lib/utils.js'); 12 | 13 | 14 | var VERSION; 15 | var FILE; 16 | var TYPE; 17 | var methodTree; 18 | 19 | var parser = new xml2js.Parser(); 20 | 21 | var parseWSDL = function(err, data) { 22 | parser.parseString(data, function (err, result) { 23 | var messages = []; 24 | 25 | // if the `attribute @` option is enabled, replace the example 26 | if(OCPP.WITH_ATTR_AT) { 27 | OCPP.enableAttributesWithAt(); 28 | } 29 | 30 | // JSON Schemas 31 | OCPP.buildJSONSchemas(); 32 | var schemaTree = OCPP.JSONSchemas[VERSION][TYPE]; 33 | 34 | // fetch all the messages 35 | var msg = result['wsdl:definitions']['wsdl:message']; 36 | for (var m in msg) { 37 | var message = msg[m]['wsdl:part'][0]['$'].element; 38 | 39 | // only push request and response 40 | if (message.indexOf('Request') > 0 || message.indexOf('Response') > 0) 41 | messages.push(Utils.deleteNamespace(message)); 42 | } 43 | 44 | // for all the messages 45 | for (var i in messages) { 46 | 47 | var name = Utils.capitaliseFirstLetter(messages[i]); 48 | var args = OCPP.wsdl[VERSION][name]; 49 | 50 | // print 51 | if (messages[i].indexOf('Request') > 0) { 52 | console.log('

'+ 53 | name.replace('Request', '') 54 | +'

'); 55 | console.log(''); 79 | } 80 | } 81 | 82 | }); 83 | }; 84 | 85 | function main() { 86 | var args = process.argv.slice(2); 87 | 88 | if (args.length < 1) { 89 | console.log('Usage: node gir-ocpp-doc.js \n\n') 90 | return; 91 | } 92 | 93 | /* non-documented option */ 94 | if(args[1] && args[1] == "--with-attr-at"){ 95 | OCPP.WITH_ATTR_AT = true; 96 | } 97 | 98 | VERSION = Utils.retrieveVersion(args[0]); 99 | FILE = args[0]; 100 | 101 | TYPE = "cp"; 102 | if(FILE.search("centralsystem") != -1) 103 | TYPE = "cs"; 104 | 105 | var vers = TYPE +"_"+ VERSION; 106 | 107 | OCPP.readFile(vers, FILE); 108 | methodTree = OCPP.methodTree[VERSION][TYPE]; 109 | 110 | fs.readFile(FILE, parseWSDL); 111 | } 112 | 113 | main(); 114 | 115 | -------------------------------------------------------------------------------- /test/framework.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // -*- coding: utf-8 -*- 3 | "use strict" 4 | 5 | /** 6 | * Test framework for the girocppjs simulator. 7 | * 8 | */ 9 | 10 | var exec = require('child_process').exec; 11 | 12 | var Test = { 13 | 14 | centralSystem: null, 15 | chargePoint: null, 16 | 17 | cp: 'gir-ocppjs.js start_cp', 18 | cs: 'gir-ocppjs.js start_cs', 19 | 20 | options: null, 21 | args: null, 22 | 23 | currentCommandIndex: 0, 24 | testStarted: false, 25 | 26 | /** 27 | * 28 | */ 29 | init: function(opt) { 30 | if(process.argv.length < 3) { 31 | console.log('Usage: node [url] [id] [from]'); 32 | console.log(' .ex: node test-cp-1.2.js websocket'); 33 | console.log(' .ex: node test-cp-1.2.js soap http://192.168.1.12:9000/ '+ 34 | 'boxid http://192.168.1.25:9001'); 35 | process.exit(1); 36 | } 37 | 38 | var args = process.argv.slice(2), 39 | options = { 40 | system: opt.system || 'cp', 41 | version: opt.version || '1.6', 42 | transport: opt.transport || args[0] || 'websocket' 43 | }, 44 | args = args.slice(1); 45 | 46 | Test.options = options; 47 | Test.args = args; 48 | 49 | Test.startCentralSystem(); 50 | // delayed started 51 | setTimeout(function() { 52 | Test.startChargePoint(); 53 | 54 | if(options.system == 'cs') { 55 | setTimeout(function() { 56 | Test.chargePoint.stdin.write('bootnotification\n'); 57 | }, 1000); 58 | } 59 | }, 500); 60 | }, 61 | 62 | /** 63 | * 64 | */ 65 | startCentralSystem: function() { 66 | // only start a cs if no url specified 67 | if(Test.args.length != 0) 68 | return; 69 | 70 | var tp = '-t '+ Test.options.transport, 71 | prot = '-p ocpp'+ Test.options.version, 72 | cmd = 'node ../'+ Test.cs +' 9000 '+ prot +' '+ tp; 73 | 74 | Test.centralSystem = exec(cmd); 75 | Test.centralSystem.stdout.on('data', Test.stdOutHandler); 76 | }, 77 | 78 | /** 79 | * 80 | */ 81 | startChargePoint: function() { 82 | var tp = '-t '+ Test.options.transport, 83 | prot = '-p ocpp'+ Test.options.version, 84 | uriProt = Test.options.transport == 'websocket' ? 'ws' : 'http', 85 | uri = Test.args[0] || uriProt +'://localhost:9000', 86 | cpId = Test.args[1] || 'boxid', 87 | from = Test.args[2] || uriProt +'://localhost:9001', 88 | cmd = 'node ../'+ Test.cp +' '+ uri +' '+ cpId +' '+ prot +' '+ tp; 89 | 90 | Test.chargePoint = exec(cmd); 91 | Test.chargePoint.stdout.on('data', Test.stdOutHandler); 92 | }, 93 | 94 | /** 95 | * 96 | */ 97 | retrieveError: function(data) { 98 | if(data.toLowerCase().indexOf('error:') > -1) 99 | return true; 100 | 101 | var errors = ['NotImplemented', 'NotSupported', 'InternalError', 102 | 'ProtocolError', 'SecurityError', 'PropertyConstraintViolation', 103 | 'OccurenceConstraintViolation', 'TypeConstraintViolation', 104 | 'GenericError']; 105 | 106 | for(var e in errors) { 107 | if(data.indexOf(errors[e]) > -1) 108 | return true; 109 | } 110 | 111 | return false; 112 | }, 113 | 114 | /** 115 | * 116 | */ 117 | stdOutHandler: function(data) { 118 | if(Test.retrieveError(data)) { 119 | console.log('[ERROR] Test failed for `'+ 120 | Test.commands[Test.currentCommandIndex] +'\'.'); 121 | console.log('Output: '+ data); 122 | process.exit(1); 123 | } 124 | else { 125 | // TODO: find better way 126 | // only trigger the next action when the response of the current is 127 | // received 128 | if(Test.testStarted) { 129 | var next = false; 130 | 131 | if(Test.options.transport == 'websocket') { 132 | if(data.indexOf('<<') > 0 && data.indexOf('[3,') > 0) 133 | next = true; 134 | } 135 | else if(Test.options.transport == 'soap') { 136 | if(Test.options.system == 'cp' && data.indexOf('< 0) 137 | next = true; 138 | else if(Test.options.system == 'cs' && data.indexOf('< 0) 139 | next = true; 140 | } 141 | 142 | if(next) 143 | Test.testCommand(++Test.currentCommandIndex); 144 | } 145 | } 146 | }, 147 | 148 | /** 149 | * 150 | */ 151 | testCommand: function(index) { 152 | // if no command to test: end program 153 | if(Test.commands[index] == undefined) { 154 | process.exit(0); 155 | return; 156 | } 157 | 158 | var ptr = Test.commands[index].indexOf('remote_') > -1 159 | ? Test.centralSystem 160 | : Test.chargePoint; 161 | 162 | ptr.stdin.write(Test.commands[index] +'\n'); 163 | }, 164 | 165 | /** 166 | * 167 | */ 168 | test: function() { 169 | setTimeout(function() { 170 | Test.testStarted = true; 171 | Test.testCommand(Test.currentCommandIndex); 172 | }, 2000); 173 | } 174 | 175 | }; 176 | 177 | 178 | exports.Test = Test; 179 | -------------------------------------------------------------------------------- /lib/soap/lib/soap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | var Client = require('./client').Client, 7 | Server = require('./server').Server, 8 | open_wsdl = require('./wsdl').open_wsdl, 9 | crypto = require('crypto'), 10 | WSDL = require('./wsdl').WSDL, 11 | https = require('https'), 12 | fs = require('fs'); 13 | 14 | var WSDL = require('./wsdl').WSDL; 15 | var _wsdlCache = {}; 16 | 17 | function _requestWSDL(url, options, callback) { 18 | if (typeof options === 'function') { 19 | callback = options; 20 | options = {}; 21 | } 22 | 23 | var wsdl = _wsdlCache[url]; 24 | if (wsdl) { 25 | callback(null, wsdl); 26 | } 27 | else { 28 | open_wsdl(url, options, function(err, wsdl) { 29 | if (err) 30 | return callback(err); 31 | else 32 | _wsdlCache[url] = wsdl; 33 | callback(null, wsdl); 34 | }) 35 | } 36 | } 37 | 38 | function createClient(url, options, callback, endpoint) { 39 | if (typeof options === 'function') { 40 | endpoint = callback; 41 | callback = options; 42 | options = {}; 43 | } 44 | endpoint = options.endpoint || endpoint; 45 | _requestWSDL(url, options, function(err, wsdl) { 46 | callback(err, wsdl && new Client(wsdl, endpoint)); 47 | }) 48 | } 49 | 50 | function listen(server, pathOrOptions, services, xml) { 51 | var options = {}, 52 | path = pathOrOptions; 53 | 54 | if (typeof pathOrOptions === 'object') { 55 | options = pathOrOptions; 56 | path = options.path; 57 | services = options.services; 58 | xml = options.xml; 59 | } 60 | 61 | var wsdl = new WSDL(xml || services, null, options); 62 | return new Server(server, path, services, wsdl); 63 | } 64 | 65 | function BasicAuthSecurity(username, password) { 66 | this._username = username; 67 | this._password = password; 68 | } 69 | 70 | BasicAuthSecurity.prototype.addHeaders = function (headers) { 71 | headers['Authorization'] = "Basic " + new Buffer((this._username + ':' + this._password) || '').toString('base64'); 72 | } 73 | 74 | BasicAuthSecurity.prototype.toXML = function() { 75 | return ""; 76 | } 77 | 78 | function ClientSSLSecurity(keyPath, certPath) { 79 | this.key = fs.readFileSync(keyPath); 80 | this.cert = fs.readFileSync(certPath); 81 | } 82 | 83 | ClientSSLSecurity.prototype.toXML = function (headers) { 84 | return ""; 85 | } 86 | 87 | ClientSSLSecurity.prototype.addOptions = function (options) { 88 | options.key = this.key; 89 | options.cert = this.cert; 90 | options.agent = new https.Agent(options); 91 | } 92 | 93 | function WSSecurity(username, password, passwordType) { 94 | this._username = username; 95 | this._password = password; 96 | this._passwordType = passwordType || 'PasswordText'; 97 | } 98 | 99 | var passwordDigest = function(nonce, created, password) { 100 | // digest = base64 ( sha1 ( nonce + created + password ) ) 101 | var pwHash = crypto.createHash('sha1'); 102 | var rawNonce = new Buffer(nonce || '', 'base64').toString('binary'); 103 | pwHash.update(rawNonce + created + password); 104 | var passwordDigest = pwHash.digest('base64'); 105 | return passwordDigest; 106 | } 107 | 108 | WSSecurity.prototype.toXML = function() { 109 | // avoid dependency on date formatting libraries 110 | function getDate(d) { 111 | function pad(n){return n<10 ? '0'+n : n} 112 | return d.getUTCFullYear()+'-' 113 | + pad(d.getUTCMonth()+1)+'-' 114 | + pad(d.getUTCDate())+'T' 115 | + pad(d.getUTCHours())+':' 116 | + pad(d.getUTCMinutes())+':' 117 | + pad(d.getUTCSeconds())+'Z'; 118 | } 119 | var now = new Date(); 120 | var created = getDate( now ); 121 | var expires = getDate( new Date(now.getTime() + (1000 * 600)) ); 122 | 123 | // nonce = base64 ( sha1 ( created + random ) ) 124 | var nHash = crypto.createHash('sha1'); 125 | nHash.update(created + Math.random()); 126 | var nonce = nHash.digest('base64'); 127 | 128 | return "" + 129 | "" + 130 | ""+created+"" + 131 | ""+expires+"" + 132 | "" + 133 | "" + 134 | ""+this._username+"" + 135 | (this._passwordType === 'PasswordText' ? 136 | ""+this._password+"" 137 | : 138 | ""+passwordDigest(nonce, created, this._password)+"" 139 | ) + 140 | ""+nonce+"" + 141 | ""+created+"" + 142 | "" + 143 | "" 144 | } 145 | 146 | exports.BasicAuthSecurity = BasicAuthSecurity; 147 | exports.WSSecurity = WSSecurity; 148 | exports.ClientSSLSecurity = ClientSSLSecurity; 149 | exports.createClient = createClient; 150 | exports.passwordDigest = passwordDigest; 151 | exports.listen = listen; 152 | exports.WSDL = WSDL; 153 | 154 | // XXX modif lib: log function 155 | exports.log = Server.log; 156 | 157 | -------------------------------------------------------------------------------- /lib/soap/lib/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | function findKey(obj, val) { 7 | for (var n in obj) if (obj[n] === val) return n; 8 | } 9 | 10 | var http = require('./http'), 11 | assert = require('assert'), 12 | url = require('url'); 13 | 14 | var Client = function(wsdl, endpoint) { 15 | this.wsdl = wsdl; 16 | this._initializeServices(endpoint); 17 | 18 | this.lastReponse = null; 19 | } 20 | 21 | Client.prototype.addSoapHeader = function(soapHeader, name, namespace, xmlns) { 22 | if(!this.soapHeaders){ 23 | this.soapHeaders = []; 24 | } 25 | if(typeof soapHeader == 'object'){ 26 | soapHeader = this.wsdl.objectToXML(soapHeader, name, namespace, xmlns, true); 27 | } 28 | 29 | this.soapHeaders.push(soapHeader); 30 | } 31 | 32 | Client.prototype.setEndpoint = function(endpoint) { 33 | this.endpoint = endpoint; 34 | this._initializeServices(endpoint); 35 | } 36 | 37 | Client.prototype.describe = function() { 38 | var types = this.wsdl.definitions.types; 39 | return this.wsdl.describeServices(); 40 | } 41 | 42 | Client.prototype.setSecurity = function(security) { 43 | this.security = security; 44 | } 45 | 46 | Client.prototype.setSOAPAction = function(SOAPAction) { 47 | this.SOAPAction = SOAPAction; 48 | } 49 | 50 | Client.prototype._initializeServices = function(endpoint) { 51 | var definitions = this.wsdl.definitions, 52 | services = definitions.services; 53 | for (var name in services) { 54 | this[name] = this._defineService(services[name], endpoint); 55 | } 56 | } 57 | 58 | Client.prototype._defineService = function(service, endpoint) { 59 | var ports = service.ports, 60 | def = {}; 61 | for (var name in ports) { 62 | def[name] = this._definePort(ports[name], endpoint ? endpoint : ports[name].location); 63 | } 64 | 65 | 66 | return def; 67 | } 68 | 69 | Client.prototype._definePort = function(port, endpoint) { 70 | var location = endpoint, 71 | binding = port.binding, 72 | methods = binding.methods, 73 | def = {}; 74 | for (var name in methods) { 75 | def[name] = this._defineMethod(methods[name], location); 76 | if (!this[name]) this[name] = def[name]; 77 | } 78 | return def; 79 | } 80 | 81 | Client.prototype._defineMethod = function(method, location) { 82 | var self = this; 83 | return function(args, callback) { 84 | if (typeof args === 'function') { 85 | callback = args; 86 | args = {}; 87 | } 88 | self._invoke(method, args, location, function(error, result, raw) { 89 | callback(error, result, raw); 90 | }) 91 | } 92 | } 93 | 94 | Client.prototype._invoke = function(method, arguments, location, callback) { 95 | var self = this, 96 | name = method.$name, 97 | input = method.input, 98 | output = method.output, 99 | style = method.style, 100 | defs = this.wsdl.definitions, 101 | ns = defs.$targetNamespace, 102 | encoding = '', 103 | message = '', 104 | xml = null, 105 | soapAction = this.SOAPAction ? this.SOAPAction(ns, name) : (method.soapAction || (((ns.lastIndexOf("/") != ns.length - 1) ? ns + "/" : ns) + name)), 106 | headers = { 107 | SOAPAction: '"' + soapAction + '"', 108 | //'Content-Type': "text/xml; charset=utf-8" 109 | 'Content-Type': "application/soap+xml; charset=utf-8" 110 | }, 111 | options = {}, 112 | alias = findKey(defs.xmlns, ns); 113 | 114 | // Allow the security object to add headers 115 | if (self.security && self.security.addHeaders) 116 | self.security.addHeaders(headers); 117 | if (self.security && self.security.addOptions) 118 | self.security.addOptions(options); 119 | 120 | if (input.parts) { 121 | assert.ok(!style || style == 'rpc', 'invalid message definition for document style binding'); 122 | message = self.wsdl.objectToRpcXML(name, arguments, alias, ns); 123 | (method.inputSoap === 'encoded') && (encoding = 'soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" '); 124 | } 125 | else if (typeof(arguments) === 'string') { 126 | message = arguments; 127 | } 128 | else { 129 | assert.ok(!style || style == 'document', 'invalid message definition for rpc style binding'); 130 | 131 | // XXX change namespace 132 | message = self.wsdl.objectToDocumentXML(input.$name, arguments, input.targetNSAlias, input.targetNamespace); 133 | } 134 | 135 | xml = "' + 139 | "" + 140 | (self.soapHeaders ? self.soapHeaders.join("\n") : "") + 141 | (self.security ? self.security.toXML() : "") + 142 | "" + 143 | "" + 144 | message + 145 | "" + 146 | ""; 147 | 148 | self.lastRequest = xml; 149 | 150 | http.request(location, xml, function(err, response, body) { 151 | if (err) { 152 | callback(err); 153 | } 154 | else { 155 | self.lastResponse = body; 156 | 157 | try { 158 | var obj = self.wsdl.xmlToObject(body); 159 | } 160 | catch (error) { 161 | return callback(error, response, body); 162 | } 163 | var result = obj.Body[output.$name]; 164 | 165 | // RPC/literal response body may contain element named after the method + 'Response' 166 | // This doesn't necessarily equal the ouput message name. See WSDL 1.1 Section 2.4.5 167 | /*if(!result) { 168 | result = obj.Body[name + 'Response']; 169 | }*/ 170 | callback(null, result, body); 171 | } 172 | }, headers, options); 173 | } 174 | 175 | exports.Client = Client; 176 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | /** 4 | * 5 | * 6 | * 7 | */ 8 | 9 | 10 | /** Utils 11 | * Set of independant utilitary functions 12 | * 13 | * @api public 14 | */ 15 | var Utils = { 16 | 17 | /** 18 | * Log function 19 | * 20 | * @param {String} message to display 21 | * @param 22 | */ 23 | log: function(msg, type) { 24 | var d = new Date(), 25 | log = Utils.dateToString(d) + " "; 26 | 27 | if(type != undefined) { 28 | if(type == "cs") 29 | log += "cs: "; 30 | else if(type == "cp") 31 | log += "cp: "; 32 | else 33 | log += "cp#" + type +": "; 34 | } 35 | 36 | console.log(log + msg); 37 | }, 38 | 39 | /** 40 | * Convert a Date to String 41 | * Format: [YY-mm-dd hh:mm:ss] 42 | * 43 | * @param {Date} 44 | * @param {String} 45 | */ 46 | dateToString: function(date) { 47 | var year = Utils.addZeroPadding(date.getFullYear()), 48 | month = Utils.addZeroPadding(date.getMonth() + 1), 49 | day = Utils.addZeroPadding(date.getDate()), 50 | hours = Utils.addZeroPadding(date.getHours()), 51 | minutes = Utils.addZeroPadding(date.getMinutes()), 52 | seconds = Utils.addZeroPadding(date.getSeconds()); 53 | 54 | return "["+ year +"-"+ month +"-"+ day +" "+ hours +":"+ minutes +":"+ 55 | seconds +"]"; 56 | }, 57 | 58 | /** 59 | * Add zero-padding if needed 60 | * 61 | * @param {Number} Number of the day/month 62 | * @param {String} 63 | */ 64 | addZeroPadding: function(n) { 65 | return n < 10 ? '0' + n : '' + n; 66 | }, 67 | 68 | /** 69 | * Generate a random ID 70 | * 71 | * @return String 72 | */ 73 | makeId: function() { 74 | var text = "", 75 | possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 76 | 77 | for(var i = 0; i < 32; i++) 78 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 79 | 80 | return text; 81 | }, 82 | 83 | /** 84 | * Return the id of the element on an object if there's only one element. 85 | * Purpose: get the id of the unique CP for shortened UI commands 86 | * 87 | * @param {Object} Object 88 | * @return {Number} Number = id, -1 = error 89 | */ 90 | getId: function(obj) { 91 | var nb = 0, id = -1; 92 | 93 | for(var o in obj) { 94 | id = o; 95 | // more than 1 element = return error 96 | if (++nb > 1) 97 | return -1; 98 | } 99 | 100 | return id; 101 | }, 102 | 103 | /** 104 | * Retrieve OCPP version from string 105 | */ 106 | retrieveVersion: function(str) { 107 | // if array, last occurence 108 | if(str instanceof Array) { 109 | str = str[str.length - 1]; 110 | } 111 | 112 | var v = []; 113 | for(var i in str) { 114 | if(str[i] >= 0 && str[i] < 10) { 115 | v.push(str[i]); 116 | } 117 | } 118 | 119 | return v.join('.'); 120 | }, 121 | 122 | /* 123 | * Delete the namespace of a xml node 124 | */ 125 | deleteNamespace: function(str) { 126 | return str.split(':')[1]; 127 | }, 128 | 129 | isEmpty: function(obj) { 130 | for(var i in obj) { return false; } 131 | return true; 132 | }, 133 | 134 | capitaliseFirstLetter: function(string) { 135 | return string.charAt(0).toUpperCase() + string.slice(1); 136 | }, 137 | 138 | lowerFirstLetter: function(string) { 139 | return string.charAt(0).toLowerCase() + string.slice(1); 140 | }, 141 | 142 | getDateISOFormat: function() { 143 | return new Date().toISOString().split('.')[0] + 'Z'; 144 | }, 145 | 146 | validURL: function(str) { 147 | var re = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;-]*)/ig; 148 | 149 | return re.test(str); 150 | }, 151 | 152 | clone: function(obj) { 153 | if (null == obj || "object" != typeof obj) return obj; 154 | var copy = obj.constructor(); 155 | for (var attr in obj) { 156 | if (obj.hasOwnProperty(attr)) 157 | if(typeof obj[attr] == "object") 158 | copy[attr] = Utils.clone(obj[attr]); 159 | else 160 | copy[attr] = obj[attr]; 161 | } 162 | 163 | return copy; 164 | }, 165 | 166 | /* 167 | * Functions related to WSDL parsing 168 | */ 169 | 170 | JSON: { 171 | 172 | complexTypes: {}, 173 | simpleTypes: {}, 174 | 175 | /* 176 | * Get the object from a given type 177 | */ 178 | getPathFromType: function(types, type) { 179 | for (var c in types) { 180 | if(!Utils.isEmpty(types[c]['$']['name']) && !Utils.isEmpty(type)) { 181 | if (types[c]['$']['name'].toLowerCase() == type.toLowerCase()) { 182 | return types[c]; 183 | } 184 | } 185 | } 186 | 187 | return null; 188 | }, 189 | 190 | /* 191 | * Get elements of a type 192 | */ 193 | getSequenceOfType: function(types, type) { 194 | var seq = []; 195 | 196 | // get path 197 | var path = Utils.JSON.getPathFromType(types, type); 198 | 199 | // retrieve sequence 200 | var elements = null; 201 | if (path != null) { 202 | if (path['s:sequence'] != undefined) 203 | elements = path['s:sequence'][0]['s:element']; 204 | } 205 | 206 | // get all the elements of the sequence 207 | if (elements != null) { 208 | for (var e in elements) { 209 | seq.push(elements[e]['$']); 210 | 211 | // specific case for the 'value', add complex type 212 | if(elements[e]['$']['name'] == 'value') { 213 | seq[seq.length - 1]['s:complexType'] = elements[e]['s:complexType']; 214 | } 215 | } 216 | } 217 | 218 | return seq; 219 | } 220 | 221 | }, 222 | 223 | // Get network interface IPs, from: 224 | // http://stackoverflow.com/questions/3653065/get-local-ip-address-in-node-js 225 | getNetworkIPs: function() { 226 | var ignoreRE = /^(127\.0\.0\.1|::1|fe80(:1)?::1(%.*)?)$/i; 227 | 228 | var exec = require('child_process').exec; 229 | var cached; 230 | var command; 231 | var filterRE; 232 | 233 | switch (process.platform) { 234 | case 'win32': 235 | //case 'win64': // TODO: test 236 | command = 'ipconfig'; 237 | filterRE = /\bIPv[46][^:\r\n]+:\s*([^\s]+)/g; 238 | break; 239 | case 'darwin': 240 | command = 'ifconfig'; 241 | filterRE = /\binet\s+([^\s]+)/g; 242 | // filterRE = /\binet6\s+([^\s]+)/g; // IPv6 243 | break; 244 | default: 245 | command = 'ifconfig'; 246 | filterRE = /\binet\b[^:]+:\s*([^\s]+)/g; 247 | // filterRE = /\binet6[^:]+:\s*([^\s]+)/g; // IPv6 248 | break; 249 | } 250 | 251 | return function (callback, bypassCache) { 252 | if (cached && !bypassCache) { 253 | callback(null, cached); 254 | return; 255 | } 256 | // system call 257 | exec(command, function (error, stdout, sterr) { 258 | cached = []; 259 | var ip; 260 | var matches = stdout.match(filterRE) || []; 261 | //if (!error) { 262 | for (var i = 0; i < matches.length; i++) { 263 | ip = matches[i].replace(filterRE, '$1') 264 | if (!ignoreRE.test(ip)) { 265 | cached.push(ip); 266 | } 267 | } 268 | //} 269 | callback(error, cached); 270 | }); 271 | }; 272 | } 273 | 274 | }; 275 | 276 | 277 | module.exports = Utils; 278 | 279 | -------------------------------------------------------------------------------- /lib/plugins.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | 4 | var Utils = require('./utils.js'), 5 | OCPP = require('./ocpp-protocol.js'); 6 | 7 | /** 8 | * 9 | * 10 | * 11 | */ 12 | 13 | var Plugins = { 14 | 15 | plugins: {}, 16 | 17 | PLUGINS_DIR: 'plugins/', 18 | 19 | // ref to Simulators 20 | Simulators: null, 21 | // ref to UI 22 | UI: null, 23 | 24 | /** 25 | * 26 | */ 27 | API: { 28 | log: Utils.log, 29 | 30 | parse: function(line) { 31 | return Plugins.UI.commandToObject(Plugins.UI.parseLineToTokens(line)); 32 | }, 33 | 34 | cp: { 35 | call: function(procName, args) { 36 | setTimeout(function() { 37 | Plugins.escapeStringArguments(args); 38 | Plugins.Simulators.commandCPtoCS(-1, procName, args); 39 | }, 500); 40 | } 41 | }, 42 | 43 | cs: { 44 | call: function(procName, args) { 45 | setTimeout(function() { 46 | Plugins.escapeStringArguments(args); 47 | Plugins.Simulators.commandCStoCP(-1, procName, args); 48 | }, 500); 49 | } 50 | }, 51 | 52 | ocpp_version: null, 53 | system: null, 54 | protocol: null, 55 | chargePointId: null 56 | 57 | }, 58 | 59 | /** 60 | * TODO: give pointers 61 | * 62 | */ 63 | init: function() {}, 64 | 65 | /** 66 | * TODO: give pointers 67 | * 68 | */ 69 | setAPIFields: function(prot, system, version, cbId) { 70 | Plugins.API.protocol = prot; 71 | Plugins.API.system = system; 72 | Plugins.API.ocpp_version = version; 73 | Plugins.API.chargePointId = cbId; 74 | }, 75 | 76 | /** 77 | * 78 | */ 79 | load: function(filename) { 80 | // if already exist, unload it 81 | if(Plugins.plugins[filename] != undefined) { 82 | Plugins.unload(filename); 83 | } 84 | 85 | var plugin = null; 86 | try { 87 | plugin = require(__dirname + '/../' + Plugins.PLUGINS_DIR + filename + 88 | '.js'); 89 | } 90 | catch(exception) { 91 | console.log('Error with the '+ filename +' plugin, invalid or '+ 92 | 'doesn\'t exist.'); 93 | return; 94 | } 95 | 96 | // check if plugin is compliant 97 | if(!Plugins.isPluginCompliant(filename, plugin)) { 98 | // errors are printed in the isPluginCompliant function 99 | return; 100 | } 101 | 102 | // 103 | Plugins.addHandlers(plugin); 104 | 105 | // add to array 106 | Plugins.plugins[filename] = plugin; 107 | 108 | // create a special scope 109 | var scope = Utils.clone(Plugins.API); 110 | scope.onResult = plugin.onResult; 111 | scope.onCall = plugin.onCall; 112 | scope.onClientConnectionEvent = plugin.onClientConnectionEvent; 113 | scope.onCommand = plugin.onCommand; 114 | scope.onConnectionEvent = plugin.onConnectionEvent; 115 | scope.onIdle = plugin.onIdle; 116 | scope.unload = function() { 117 | Plugins.unload(filename); 118 | }; 119 | 120 | // call the main function 121 | Plugins.plugins[filename].onLoad.call(scope); 122 | }, 123 | 124 | /** 125 | * 126 | */ 127 | unload: function(filename) { 128 | if(Plugins.plugins[filename] == undefined) { 129 | console.log('Error for unloading `'+ filename +'\': file does not exist.'); 130 | return; 131 | } 132 | 133 | if(Plugins.plugins[filename].onUnload != undefined) { 134 | // call the unload function 135 | Plugins.plugins[filename].onUnload.call(Plugins.API); 136 | } 137 | 138 | // delete from array 139 | delete Plugins.plugins[filename]; 140 | 141 | // delete from require cache 142 | delete require.cache[__dirname.replace('lib', '') + Plugins.PLUGINS_DIR + 143 | filename +'.js']; 144 | }, 145 | 146 | /** 147 | * 148 | */ 149 | addHandlers: function(plugin) { 150 | 151 | plugin.handlers = { 152 | onResultHandler: {}, 153 | onCallHandler: {}, 154 | onClientConnectionEventHandler: null, 155 | onCommandHandler: null, 156 | onConnectionEventHandler: null, 157 | onIdleHandler: null 158 | }; 159 | 160 | plugin.onResult = function(procName, handlerFunc) { 161 | plugin.handlers.onResultHandler[procName] = handlerFunc; 162 | }; 163 | 164 | plugin.onCall = function(procName, handlerFunc) { 165 | plugin.handlers.onCallHandler[procName] = handlerFunc; 166 | }; 167 | 168 | plugin.onClientConnectionEvent = function(handlerFunc) { 169 | plugin.handlers.onClientConnectionEventHandler = handlerFunc; 170 | }; 171 | 172 | plugin.onCommand = function(handlerFunc) { 173 | plugin.handlers.onCommandHandler = handlerFunc; 174 | }; 175 | 176 | plugin.onConnectionEvent = function(handlerFunc) { 177 | plugin.handlers.onConnectionEventHandler = handlerFunc; 178 | }; 179 | 180 | plugin.onIdle = function(handlerFunc) { 181 | plugin.handlers.onIdleHandler = handlerFunc; 182 | }; 183 | }, 184 | 185 | /** 186 | * 187 | */ 188 | isPluginCompliant: function(filename, plugin) { 189 | // entry point 190 | if(plugin.onLoad == undefined) { 191 | console.log('Error when reading the `'+ filename +'\' plugin: '+ 192 | 'no entry point `onLoad\' function found.'); 193 | return false; 194 | } 195 | 196 | return true; 197 | }, 198 | 199 | /** 200 | * 201 | */ 202 | callResultHandlers: function(procName, values, scope) { 203 | try { 204 | for(var pl in Plugins.plugins) { 205 | Plugins.plugins[pl].handlers.onResultHandler[procName] 206 | .call(scope, values); 207 | } 208 | } catch(e) {} 209 | }, 210 | 211 | /** 212 | * 213 | */ 214 | callHandlers: function(procName, values, scope) { 215 | var response; 216 | try { 217 | for(var pl in Plugins.plugins) { 218 | response = Plugins.plugins[pl].handlers.onCallHandler[procName] 219 | .call(scope, values); 220 | } 221 | } catch(e) {} 222 | 223 | return response; 224 | }, 225 | 226 | /** 227 | * 228 | */ 229 | callClientConnectionEventHandlers: function(type, cbId, scope) { 230 | try { 231 | for(var pl in Plugins.plugins) { 232 | Plugins.plugins[pl].handlers.onClientConnectionEventHandler 233 | .call(scope, type, cbId); 234 | } 235 | } catch(e) {} 236 | }, 237 | 238 | /** 239 | * 240 | */ 241 | callCommandHandlers: function(command, scope) { 242 | var ret = false; 243 | try { 244 | for(var pl in Plugins.plugins) { 245 | if(Plugins.plugins[pl].handlers.onCommandHandler.call(scope, command)) 246 | ret = true; 247 | } 248 | } catch(e) {} 249 | 250 | return ret; 251 | }, 252 | 253 | /** 254 | * 255 | */ 256 | callConnectionEventHandlers: function(type, cbId, scope) { 257 | try { 258 | for(var pl in Plugins.plugins) { 259 | Plugins.plugins[pl].handlers.onConnectionEventHandler 260 | .call(scope, type, cbId); 261 | } 262 | } catch(e) {} 263 | }, 264 | 265 | /** 266 | * 267 | */ 268 | callIdleHandlers: function(scope) { 269 | try { 270 | for(var pl in Plugins.plugins) { 271 | Plugins.plugins[pl].handlers.onIdleHandler 272 | .call(scope); 273 | } 274 | } catch(e) {} 275 | }, 276 | 277 | /** 278 | * Escape string arguments. 279 | * Example: {'status': 'Download'} -> {'status': '"Download"'} 280 | * 281 | * This is mandoratory for JSON parsing functiona afterwards. 282 | */ 283 | escapeStringArguments: function(args) { 284 | for(var arg in args) { 285 | if(typeof args[arg] == 'string') { 286 | args[arg] = '"'+ args[arg] +'"'; 287 | } 288 | } 289 | }, 290 | 291 | }; 292 | 293 | module.exports = Plugins; 294 | 295 | -------------------------------------------------------------------------------- /lib/soap/lib/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | function findKey(obj, val) { 7 | for (var n in obj) if (obj[n] === val) return n; 8 | } 9 | 10 | var url = require('url'), 11 | compress = null; 12 | 13 | try { compress = require("compress"); } catch(e) {} 14 | 15 | var Server = function(server, path, services, wsdl) { 16 | var self = this, 17 | listeners = server.listeners('request'); 18 | 19 | this.services = services; 20 | this.wsdl = wsdl; 21 | if (path[path.length-1] != '/') path += '/'; 22 | wsdl.onReady(function(err) { 23 | server.removeAllListeners('request'); 24 | server.addListener('request', function(req, res) { 25 | if (typeof self.authorizeConnection === 'function') { 26 | if (!self.authorizeConnection(req.connection.remoteAddress)) { 27 | res.end(); 28 | return; 29 | } 30 | } 31 | var reqPath = url.parse(req.url).pathname; 32 | if (reqPath[reqPath.length-1] != '/') reqPath += '/'; 33 | if (path === reqPath) { 34 | self._requestListener(req, res); 35 | } 36 | else { 37 | for (var i = 0, len = listeners.length; i < len; i++){ 38 | listeners[i].call(this, req, res); 39 | } 40 | } 41 | }); 42 | }) 43 | } 44 | 45 | // XXX modif lib: check error function 46 | Server.prototype.checkError = function(result) { 47 | 48 | }; 49 | 50 | // XXX modif lib: check error function 51 | Server.prototype._returnError = function(result) { 52 | 53 | }; 54 | 55 | Server.prototype._requestListener = function(req, res) { 56 | var self = this; 57 | if (req.method === 'GET') { 58 | var search = url.parse(req.url).search; 59 | if (search && search.toLowerCase() === '?wsdl') { 60 | res.setHeader("Content-Type", "application/xml"); 61 | res.write(self.wsdl.toXML()); 62 | } 63 | res.end(); 64 | } 65 | else if (req.method === 'POST') { 66 | res.setHeader('Content-Type', req.headers['content-type']); 67 | var chunks = [], gunzip; 68 | if (compress && req.headers["content-encoding"] == "gzip") { 69 | gunzip = new compress.Gunzip; 70 | gunzip.init(); 71 | } 72 | req.on('data', function(chunk) { 73 | if (gunzip) chunk = gunzip.inflate(chunk, "binary"); 74 | chunks.push(chunk); 75 | }) 76 | req.on('end', function() { 77 | var xml = chunks.join(''), result; 78 | if (gunzip) { 79 | gunzip.end(); 80 | gunzip = null; 81 | } 82 | try { 83 | self.log(xml, 'in'); 84 | 85 | // XXX modif lib: for checking errors 86 | if(!self.checkErrors(xml, res)) { 87 | self._process(xml, req.url, function(result) { 88 | res.write(result); 89 | res.end(); 90 | 91 | self.log(result, 'out'); 92 | }); 93 | } 94 | } 95 | catch(err) { 96 | console.log(err) 97 | err = err.stack || err; 98 | res.write(err); 99 | res.end(); 100 | if (typeof self.log === 'function') { 101 | self.log("error", err); 102 | } 103 | } 104 | }); 105 | } 106 | else { 107 | res.end(); 108 | } 109 | } 110 | 111 | // XXX modif lib 112 | Server.prototype.setRemoteAddress = function(address) { 113 | // to be overridden 114 | }; 115 | 116 | // XXX modif lib 117 | Server.prototype.postProcess = function(address) { 118 | // to be overridden 119 | }; 120 | 121 | Server.prototype._process = function(input, URL, callback) { 122 | var self = this, 123 | pathname = url.parse(URL).pathname.replace(/\/$/,''), 124 | obj = this.wsdl.xmlToObject(input), 125 | body = obj.Body, 126 | bindings = this.wsdl.definitions.bindings, binding, 127 | methods, method, methodName, 128 | serviceName, portName; 129 | 130 | if (typeof self.authenticate === 'function') { 131 | if (obj.Header == null || obj.Header.Security == null) { 132 | throw new Error('No security header'); 133 | } 134 | if (!self.authenticate(obj.Header.Security)) { 135 | throw new Error('Invalid username or password'); 136 | } 137 | } 138 | 139 | // XXX modif lib: get remote address 140 | self.setRemoteAddress(obj.Header.chargeBoxIdentity, 141 | obj.Header.From && obj.Header.From.Address, 142 | obj.Header.Action.slice(1)); 143 | 144 | // XXX modif lib: for adding chargeboxidentity header 145 | this.cbId = obj.Header.chargeBoxIdentity; 146 | this.action = obj.Header.Action; 147 | 148 | // use port.location and current url to find the right binding 149 | binding = (function(self){ 150 | var services = self.wsdl.definitions.services; 151 | for(serviceName in services) { 152 | var service = services[serviceName]; 153 | var ports = service.ports; 154 | for(portName in ports) { 155 | var port = ports[portName]; 156 | var portPathname = url.parse(port.location).pathname.replace(/\/$/,''); 157 | // XXX modif lib: permit customized endpoint URL 158 | //if(portPathname===pathname) 159 | return port.binding; 160 | } 161 | } 162 | })(this); 163 | 164 | methods = binding.methods; 165 | 166 | // XXX modif lib: always rpc 167 | // if(binding.style === 'rpc') { 168 | // XXX end 169 | methodName = Object.keys(body)[0]; 170 | 171 | body = body[methodName]; 172 | 173 | // XXX modif lib: OCPP Command = Command / CommandResponse 174 | methodName = methodName.replace('Request',''); 175 | methodName = methodName.charAt(0).toUpperCase() + methodName.slice(1); 176 | // XXX end 177 | 178 | self._executeMethod({ 179 | serviceName: serviceName, 180 | portName: portName, 181 | methodName: methodName, 182 | outputName: methodName + 'Response', 183 | // XXX modif lib: body instead of body[methodName] 184 | args: body, 185 | style: 'rpc' 186 | }, callback); 187 | 188 | self.postProcess(); 189 | 190 | /* XXX modif lib: always use RPC 191 | } else { 192 | var messageElemName = Object.keys(body)[0]; 193 | var pair = binding.topElements[messageElemName]; 194 | self._executeMethod({ 195 | serviceName: serviceName, 196 | portName: portName, 197 | methodName: pair.methodName, 198 | outputName: pair.outputName, 199 | args: body[messageElemName], 200 | style: 'document' 201 | }, callback); 202 | } 203 | */ 204 | 205 | } 206 | 207 | Server.prototype._executeMethod = function(options, callback) { 208 | options = options || {}; 209 | var self = this, 210 | method, body, 211 | serviceName = options.serviceName, 212 | portName = options.portName, 213 | methodName = options.methodName, 214 | outputName = options.outputName, 215 | args = options.args, 216 | style = options.style, 217 | handled = false; 218 | 219 | try { 220 | method = this.services[serviceName][portName][methodName]; 221 | } catch(e) { 222 | return callback(this._envelope('')); 223 | } 224 | 225 | function handleResult(result) { 226 | if (handled) return; 227 | handled = true; 228 | 229 | if(style==='rpc') { 230 | body = self.wsdl.objectToRpcXML(outputName, result, '', self.wsdl.definitions.$targetNamespace); 231 | } else { 232 | var element = self.wsdl.definitions.services[serviceName].ports[portName].binding.methods[methodName].output; 233 | body = self.wsdl.objectToDocumentXML(outputName, result, element.targetNSAlias, element.targetNamespace); 234 | } 235 | callback(self._envelope(body)); 236 | } 237 | 238 | /// XXX modif lib: forward request body 239 | var result = method(args, handleResult); 240 | if (typeof result !== 'undefined') { 241 | handleResult(result); 242 | } 243 | } 244 | 245 | Server.prototype._envelope = function(body) { 246 | var defs = this.wsdl.definitions, 247 | ns = defs.$targetNamespace, 248 | encoding = '', 249 | alias = findKey(defs.xmlns, ns), 250 | // XXX modif lib: add chargeboxidentity header 251 | cbIdHeader = this.cbId ? 252 | ""+ 253 | this.cbId +"" 254 | : "", 255 | actionHeader = this.action ? 256 | ""+ 257 | this.action +"Response" 258 | : ""; 259 | 260 | var xml = "" + 261 | "' + 264 | ""+ cbIdHeader + actionHeader +"" + 265 | "" + 266 | body + 267 | "" + 268 | ""; 269 | return xml; 270 | } 271 | 272 | exports.Server = Server; 273 | -------------------------------------------------------------------------------- /lib/simulators.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | /** 4 | * 5 | * 6 | * 7 | */ 8 | 9 | 10 | var OCPP = require('./ocpp-protocol.js'), 11 | Utils = require('./utils.js'), 12 | Transport = require('./transport.js'), 13 | Plugins = require('./plugins.js'); 14 | 15 | /** 16 | * Central System Simulator constructor. 17 | * 18 | * - {CentralSystemSimulator} implements an OCPP Central System. 19 | * Constructor options: TCP port. 20 | * When started, a CentralSystemSimulator creates a {WebSocketServer} on the 21 | * specified port. 22 | * When the WebSocketServer receives a connection request, a 23 | * {WebSocketConnection} is created (by the websocket lib). 24 | * The CentralSystemSimulator uses this connection to create: 25 | * - a {SRPCServerConnection}, to handle RPC calls 26 | * - a {SRPCClientConnection}, to emit RPC calls 27 | * 28 | * @param {int} Port 29 | * @return {CentralSystemSimulator} 30 | * @api public 31 | */ 32 | 33 | function CentralSystemSimulator(port, transport) { 34 | this.port = port; 35 | this._wsServer = null; 36 | 37 | this._connections = {}; 38 | 39 | Plugins.setAPIFields(transport, 'cs', OCPP.SUB_PROTOCOL); 40 | 41 | this.transportLayer = new Transport.TransportLayerServer(this, 42 | transport, 'cs', 'server'); 43 | 44 | // lib fonction redefinitions 45 | // TODO move this to transport.js, can't at the moment because of circular 46 | // require 47 | var _this = this; 48 | if(transport == 'soap') { 49 | this.transportLayer.layer.soapServ.setRemoteAddress = 50 | function(cbId, address, action) { 51 | // if not bootnotification, stop 52 | if(action != 'BootNotification') 53 | return; 54 | 55 | Plugins.callClientConnectionEventHandlers('connected', cbId, this); 56 | 57 | Utils.log('ChargePoint #'+ cbId +' connected.', 'cs'); 58 | _this._connections[cbId] = { 59 | client: new Transport.TransportLayerClient(this, 60 | transport, 'cs', 'client', { fromHeader: address }).layer 61 | }; 62 | }; 63 | 64 | this.transportLayer.layer.soapServ.log = logSoap; 65 | this.transportLayer.layer.soapServ.postProcess 66 | = function() { Plugins.callIdleHandlers(this); }; 67 | } 68 | }; 69 | 70 | CentralSystemSimulator.prototype = { 71 | 72 | /** 73 | * Stop the CentralSystem 74 | * 75 | 76 | stop: function() { 77 | this._wsServer.closeAllConnections(); 78 | this._wsServer.shutDown(); 79 | this._wsServer = null; 80 | 81 | this._httpServer.close(); 82 | this._httpServer = null; 83 | }, 84 | */ 85 | 86 | /* 87 | * Calls a remote procedure 88 | * 89 | * @param {Number} the client ID 90 | * @param {String} the procedure URI 91 | * @api public 92 | */ 93 | remoteAction: function(clientId, procName, args) { 94 | var prot = Utils.retrieveVersion(OCPP.SUB_PROTOCOL); 95 | 96 | if(this._connections[clientId] == undefined) { 97 | Utils.log("Error: charge point #"+ clientId +" does not exist"); 98 | return; 99 | } 100 | 101 | var resultFunction = function(){}; 102 | 103 | if(procName != '') { 104 | if(OCPP.procedures[prot]['cp'][procName] != undefined 105 | && OCPP.procedures[prot]['cp'][procName].resultFunction != undefined) { 106 | resultFunction = OCPP.procedures[prot]['cp'][procName].resultFunction; 107 | } 108 | } 109 | 110 | this._connections[clientId].client.rpcCall(procName, args || {}, 111 | OCPP.TIMEOUT, resultFunction, {to: "cp#" + clientId}); 112 | } 113 | 114 | }; 115 | 116 | /** 117 | * Charge Point Simulator constructor. 118 | * 119 | * - {ChargePointSimulator} implements an OCPP Charge Point. 120 | * Constructor options: server URL. 121 | * A ChargePointSimulator permanently trys to connect to its CentralSystem, 122 | * with a {WebSocketClient}. On successfull connection, a 123 | * {WebSocketConnection} is created (by the websocket lib). 124 | * The ChargePointSimulator uses this connection to create: 125 | * - a {SRPCServerConnection}, to handle RPC calls 126 | * - a {SRPCClientConnection}, to emit RPC calls 127 | * 128 | * @param {String} URI 129 | * @return {ChargePointSimulator} 130 | * @api public 131 | */ 132 | 133 | function ChargePointSimulator(uri, identifier, protocol, transport, 134 | soapOptions) { 135 | this.uri = uri; 136 | this.protocol = protocol || "ocpp1.6"; 137 | this.transport = transport; 138 | 139 | this.chargePointId = identifier; 140 | this.clientConnection = null; 141 | 142 | Plugins.setAPIFields(transport, 'cp', OCPP.SUB_PROTOCOL, this.chargePointId); 143 | 144 | this.transportLayer = new Transport.TransportLayerClient(this, 145 | transport, 'cp', 'client', soapOptions); 146 | 147 | if(this.transport == 'soap') { 148 | this.transportLayer.layer.soapServ.log = logSoap; 149 | this.transportLayer.layer.soapServ.postProcess 150 | = function() { Plugins.callIdleHandlers(this); }; 151 | } 152 | }; 153 | 154 | ChargePointSimulator.prototype = { 155 | 156 | /** 157 | * Calls a client procedure 158 | * 159 | * @param {String} Procedure URI 160 | * @param {Array} Arguments 161 | */ 162 | clientAction: function(procUri, args) { 163 | var resultFunction = function(){}, 164 | version = Utils.retrieveVersion(this.transportLayer.simulator.protocol); 165 | 166 | if(OCPP.procedures[version]['cs'][procUri] != undefined 167 | && OCPP.procedures[version]['cs'][procUri].resultFunction != undefined) 168 | resultFunction = OCPP.procedures[version]['cs'][procUri].resultFunction; 169 | 170 | if(this.clientConnection) 171 | this.clientConnection.rpcCall(procUri, args, OCPP.TIMEOUT, 172 | resultFunction, {to: "cs"}); 173 | else 174 | console.log('Error: not connected to any central system.'); 175 | } 176 | 177 | }; 178 | 179 | /** 180 | * 181 | * 182 | */ 183 | function commandCPtoCS(cpId, procName, args) { 184 | var prot = Utils.retrieveVersion(OCPP.SUB_PROTOCOL); 185 | 186 | if(cpId == -1) { 187 | cpId = Utils.getId(Simulators.chargePoints); 188 | 189 | if(cpId == -1) { 190 | console.log("Error: cannot use shortened command : there is no or more"+ 191 | " than one charge point connected."); 192 | return; 193 | } 194 | } 195 | 196 | if(Simulators.chargePoints[cpId] == undefined) { 197 | Utils.log("Error: charge point #"+ cpId +" does not exist."); 198 | return; 199 | } 200 | 201 | // specific case: WebSocket Ping 202 | if(procName == "WebSocketPing") { 203 | if(Simulators.chargePoints[cpId].clientConnection != undefined) 204 | Simulators.chargePoints[cpId].clientConnection._connection._connection 205 | .ping(); 206 | return; 207 | } 208 | 209 | // if charge point protocol is 1.2 210 | if(prot == '1.2') { 211 | if(OCPP.wsdl[prot][procName + 'Request'] == undefined) { 212 | console.log("Error: procedure `"+ procName +"' is not available for a"+ 213 | " OCPP 1.2 Charge Point Simulator"); 214 | return; 215 | } 216 | } 217 | 218 | // if send or send_raw 219 | var values = typeof args == 'string' ? 220 | args : 221 | OCPP.getRequestValues(procName, args); 222 | 223 | // if error 224 | if(values == -1) 225 | return; 226 | 227 | 228 | Simulators.chargePoints[cpId].clientAction(procName, values); 229 | }; 230 | 231 | 232 | /** 233 | * 234 | * 235 | */ 236 | function commandCStoCP(cpId, procName, args) { 237 | var prot = Utils.retrieveVersion(OCPP.SUB_PROTOCOL); 238 | 239 | if(cpId == -1) { 240 | if (Simulators.centralSystem != null) 241 | cpId = Utils.getId(Simulators.centralSystem._connections); 242 | 243 | if(cpId == -1) { 244 | console.log("Error: cannot use shortened command : there is no or more"+ 245 | " than one charge point connected."); 246 | return; 247 | } 248 | } 249 | 250 | // if cp does not exit 251 | if(Simulators.centralSystem._connections[cpId] == undefined) { 252 | Utils.log("Error: remote charge point #"+ cpId +" does not exist."); 253 | return; 254 | } 255 | 256 | // if charge point protocol is 1.2 257 | if(prot == '1.2') { 258 | if(OCPP.wsdl[prot][procName + 'Request'] == undefined) { 259 | console.log("Error: procedure `"+ procName +"' is not available for a"+ 260 | " OCPP 1.2 Charge Point Simulator"); 261 | return; 262 | } 263 | } 264 | 265 | // specific case: WebSocket Ping 266 | if(procName == "WebSocketPing") { 267 | if(Simulators.centralSystem._connections[cpId].client != undefined) 268 | Simulators.centralSystem._connections[cpId] 269 | .client._connection._connection.ping(); 270 | return; 271 | } 272 | 273 | var values = OCPP.getRequestValues(procName, args); 274 | 275 | // if error 276 | if(values == -1) 277 | return; 278 | 279 | Simulators.centralSystem.remoteAction(cpId, procName, values); 280 | }; 281 | 282 | 283 | /** 284 | * 285 | * 286 | */ 287 | 288 | function logSoap(xml, direction) { 289 | // @scope: soap server 290 | var direction = direction || 'in', 291 | prefix = direction == 'in' ? '<<' : '>>', 292 | rawContent = xml, 293 | content = this.wsdl.xmlToObject(xml), 294 | from = null, 295 | action = null; 296 | 297 | // if no content then do nothing 298 | if(!content) { 299 | if(direction == 'in') 300 | Utils.log('< '+ 66 | 'for simulating a connection interruption.'); 67 | return true; 68 | } 69 | 70 | var connectorId = parseInt(commandObj.arguments[0]), 71 | isLiaisonWorking = !!(commandOjb.arguments[1] == 'on'); 72 | 73 | for(var connector in cbxs.connectors) { 74 | if(connector.connectorId == connectorId) { 75 | connector.isLiaisonWorking = isLiaisonWorking; 76 | } 77 | } 78 | 79 | return true; 80 | } 81 | 82 | if(commandObj.command == 'identification') { 83 | if(commandObj.arguments.length == 0) { 84 | console.log(cbxs.logPrefix +'Usage: identification '); 85 | return true; 86 | } 87 | 88 | var id = commandObj.arguments[0]; 89 | 90 | // check if connector related to id 91 | for(var index in cbxs.connectors) { 92 | var connector = cbxs.connectors[index]; 93 | if(connector.idTagRelated == id) { 94 | cbxs.actionsQueue.push({ 95 | procedure: 'StopTransaction', 96 | arguments: {idTag: id} 97 | }); 98 | return true; 99 | } 100 | } 101 | 102 | var found = false; 103 | for(var key in cbxs.whiteList) { 104 | if(key == id) 105 | found = true; 106 | } 107 | 108 | if(found) { 109 | var connector = cbxs.getAvailableConnector(); 110 | connector.idTagRelated = id; 111 | cbxs.startTransactionIfPossible(connector.connectorId); 112 | } 113 | else { 114 | cbxs.idTagTmp = id; 115 | self.cp.call('Authorize', {idTag: id}); 116 | } 117 | 118 | return true; 119 | } 120 | }); 121 | 122 | /** 123 | * what to do when receiving a call result: 124 | */ 125 | self.onResult('BootNotification', function(values) { 126 | cbxs.heartbeatInterval = values.heartbeatInterval; 127 | cbxs.sendHB(self, true); 128 | }); 129 | 130 | self.onResult('Authorize', function(values) { 131 | if(values.idTagInfo.status == "Accepted") { 132 | var connector = cbxs.getAvailableConnector(); 133 | connector.isCharging = true; 134 | connector.transactionIdClient = cbxs.transactionIdClient++; 135 | connector.idTagRelated = cbxs.idTagTmp; 136 | cbxs.idTagTmp = null; 137 | cbxs.startTransactionIfPossible(connector.connectorId); 138 | } 139 | }); 140 | 141 | self.onResult('StartTransaction', function(values) { 142 | var transactionId = values.transactionId; 143 | 144 | for(var index in cbxs.connectors) { 145 | var connector = cbxs.connectors[index]; 146 | if(connector.isCharging && connector.transactionIdServer == null) { 147 | connector.transactionIdServer = transactionId; 148 | } 149 | } 150 | }); 151 | 152 | /** 153 | * when a remote call is received: 154 | */ 155 | 156 | self.onCall('ClearCache', function(values) { 157 | // clear the white list 158 | cbxs.whiteList = []; 159 | self.log(cbxs.logPrefix + ' White list cleared.'); 160 | }); 161 | 162 | self.onCall('ChangeAvailability', function(values) { 163 | for(var index in cbxs.connectors) { 164 | var connector = cbxs.connectors[index]; 165 | if(connector.connectorId == values.connectorId) { 166 | connector.isBlocked = !!(values.type == "Operative"); 167 | } 168 | } 169 | }); 170 | 171 | self.onCall('RemoteStartTransaction', function(values) { 172 | var connector = cbxs.getConnectorFromConnectorId(values.connectorId); 173 | 174 | if(connector.isCharging && !connector.isBlocked 175 | && connector.isLiaisonWorking) { 176 | connector.idTagRelation = values.idTag; 177 | cbxs.startTransactionIfPossible(values.connectorId); 178 | } 179 | else { 180 | return { status: "Rejected" }; 181 | } 182 | }); 183 | 184 | self.onCall('RemoteStopTransaction', function(values) { 185 | var found = false; 186 | 187 | for(var index in cbxs.connectors) { 188 | var connector = cbxs.connectors[index]; 189 | if(connector.transactionIdServer == values.transactionId) { 190 | connector.isCharging = false; 191 | connector.transactionIdClient = null; 192 | connector.transactionIdServer = null; 193 | 194 | found = true; 195 | } 196 | } 197 | 198 | if (found) { 199 | cbxs.actionsQueue.push({ 200 | procedure: 'StopTransaction', 201 | arguments: { 202 | transactionId: values.transactionId 203 | } 204 | }); 205 | } 206 | else { 207 | return { status: "Rejected" }; 208 | } 209 | }); 210 | 211 | self.onCall('GetDiagnostics', function(values) { 212 | self.cp.call('DiagnosticsStatusNotification', {"status": "Uploaded"}); 213 | }); 214 | 215 | self.onCall('UpdateFirmware', function(values) { 216 | self.cp.call('FirmwareStatusNotification', {"status": "Downloaded"}); 217 | self.cp.call('FirmwareStatusNotification', {"status": "Installed"}); 218 | }); 219 | 220 | self.onIdle(function() { 221 | cbxs.processActionsQueue(); 222 | 223 | clearTimeout(cbxs.hbTimeout); 224 | cbxs.sendHB(self, true); 225 | }); 226 | }, 227 | 228 | /** 229 | * Customs fields 230 | */ 231 | 232 | self: null, 233 | 234 | logPrefix: '[CBXS] ', 235 | 236 | whiteList: [], 237 | actionsQueue: [], 238 | transactionsQueue: [], 239 | 240 | hbTimeout: null, 241 | heartbeatInterval: null, 242 | 243 | idTagTmp: null, 244 | 245 | transactionIdClient: 0, 246 | 247 | connectors: [ 248 | { 249 | connectorId: 1, 250 | isCharging: false, 251 | isBlocked: false, 252 | isLiaisonWorking: true, 253 | idTagRelated: "", 254 | 255 | transactionIdClient: null, // generated by client 256 | transactionIdServer: null, // response from server 257 | },{ 258 | connectorId: 2, 259 | isCharging: false, 260 | isBlocked: false, 261 | isLiaisonWorking: true, 262 | idTagRelated: "", 263 | 264 | transactionIdClient: null, // generated by client 265 | transactionIdServer: null, // response from server 266 | } 267 | ], 268 | 269 | processActionsQueue: function() { 270 | var msg = null; 271 | while(msg = cbxs.actionsQueue.pop()) { 272 | switch(msg.procedure) { 273 | case 'StartTransaction': 274 | var connector 275 | = cbxs.getConnectorFromConnectorId(msg.arguments.connectorId); 276 | if(!connector.isBlocked && connector.isLiaisonWorking) { 277 | cbxs.transactionsQueue.push(msg); 278 | cbxs.processTransactionsQueue(); 279 | } 280 | break; 281 | default: 282 | cbxs.transactionsQueue.push(msg); 283 | cbxs.processTransactionsQueue(); 284 | } 285 | } 286 | }, 287 | 288 | processTransactionsQueue: function() { 289 | var msg = null; 290 | while(msg = cbxs.transactionsQueue.pop()) { 291 | cbxs.self.cp.call(msg.procedure, msg.arguments); 292 | } 293 | }, 294 | 295 | sendHB: function(self, dropFirst) { 296 | if(!cbxs.heartbeatInterval) 297 | return; 298 | 299 | if(!dropFirst) 300 | self.cp.call('Heartbeat'); 301 | 302 | cbxs.hbTimeout = setTimeout(cbxs.sendHB, cbxs.heartbeatInterval * 1000, 303 | self); 304 | }, 305 | 306 | startTransactionIfPossible: function(connectorId) { 307 | var connector = null; 308 | for(var index in cbxs.connectors) { 309 | var c = cbxs.connectors[index]; 310 | if(c.connectorId == connectorId) 311 | connector = c; 312 | } 313 | 314 | if(connector == null) 315 | return; 316 | 317 | if(!connector.isBlocked && connector.isLiaisonWorking) { 318 | cbxs.actionsQueue.push({ 319 | procedure: 'StartTransaction', 320 | arguments: { 321 | connectorId: connectorId, 322 | idTag: connector.idTagRelated, 323 | timestamp: new Date().toISOString(), 324 | meterStart: 0, 325 | reservationId: 0 326 | } 327 | }); 328 | } 329 | else { 330 | cbxs.self.log(cbxs.logPrefix + "Can't start transaction on connector #"+ 331 | connectorId); 332 | } 333 | }, 334 | 335 | getAvailableConnector: function() { 336 | for(var index in cbxs.connectors) { 337 | var connector = cbxs.connectors[index]; 338 | if(!connector.isCharging) 339 | return connector; 340 | } 341 | 342 | return null; 343 | }, 344 | 345 | getConnectorFromConnectorId: function(connectorId) { 346 | for(var index in cbxs.connectors) { 347 | var c = cbxs.connectors[index]; 348 | if(c.connectorId == connectorId) 349 | return c; 350 | } 351 | 352 | return null; 353 | } 354 | 355 | }; 356 | 357 | module.exports = cbxs; 358 | 359 | -------------------------------------------------------------------------------- /plugins/getting_started_with_plugins.html: -------------------------------------------------------------------------------- 1 |

Getting Started With Plugins

2 | 3 |

OCPPJS 1.0.0 introduces a new feature : the plugins. This document is here for helping developers to write plugins for the simulator. It also contains the API documentation.

4 | 5 |

Writing plugins allow yourself to define the behavior of the simulator without modifying the source code of the program. A plugin consists of one JavaScript file which can be loaded by the simulator.

6 | 7 |

API Documentation

8 | 9 |

Input/Output API

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 24 | 26 | 27 |
FunctionExample
log(message)
18 | Display message to screen.
log("Plugin Started !");
parse(line)
23 | Parse a raw command line to an object.
parse('show cache'); 
 25 |         => {command: 'show', arguments: ['cache']};
28 | 29 |

Simulator API

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 44 | 45 | 46 |
FunctionExample
cp.call(procName, args)
38 | Process a RPC call from charge point to central system.
cp.call('BootNotification');
cs.call(procName, args)
43 | Process a remote RPC call from a central system to a charge point.
cs.call('ClearCache');
47 | 48 |

Callback API

49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 65 | 66 | 67 | 69 | 76 | 77 | 78 | 80 | 90 | 91 | 92 | 94 | 104 | 105 | 106 | 108 | 118 | 119 | 120 | 122 | 129 | 130 |
FunctionExample
onResult(procName, handlerFunc)
57 | Trigger the handlerFunc function when a call result for the procName procedure is received.
59 |
 60 | onResult('BootNotification', function(values)   {
 61 |   log('Heartbeat interval: '+ values.heartbeatInterval);
 62 | });
 63 |         
64 |
onCall(procName, handlerFunc)
68 | Trigger the handlerFunc function when a call message for the procName procedure is received.
70 |
 71 | onCall('ClearCache', function(values) {
 72 |   // clear the cache
 73 | });
 74 |         
75 |
onConnectionEvent(handlerFunc)
79 | For a Charge Point simulator, trigger the handlerFunc function when a new connection event occurs. Event: connected, disconnected
81 |
 82 | onConnectionEvent(function(type, cpId) {
 83 |   if(type == 'connected')
 84 |     log('Connected to Central System.');
 85 |   else if (type == 'disconnected')
 86 |     log('Disconnected to Central System.');
 87 | });
 88 |         
89 |
onClientConnectionEvent(handlerFunc)
93 | For a Central System. simulator, trigger the handlerFunc function when a new connection event occurs. Event: connected, disconnected
95 |
 96 | onClientConnectionEvent(function(type, cpId) {
 97 |   if(type == 'connected')
 98 |     log('Connected to Central System.');
 99 |   else if (type == 'disconnected')
100 |     log('Disconnected to Central System.');
101 | });
102 |         
103 |
onCommand(handlerFunc)
107 | Trigger the handlerFunc function when a new command is entered on the command line interface. If the handlerFunc function interprets the command, it must return true.
109 |
110 | onCommand(function(command) {
111 |   var commandObj = parse(command);
112 | 
113 |   if(commandObj.command == 'cache')
114 |     log('Cache content:'+ cache);
115 | });
116 |         
117 |
onIdle(handlerFunc)
121 | Trigger the handlerFunc function when the simulator has no message to send.
123 |
124 | onIdle(function(command) {
125 |   // ...
126 | });
127 |         
128 |
131 | 132 |

A Plugin Example

133 | 134 |

First of all, the plugins need to be in the plugins/ folder which already contains some examples.

135 | 136 |

A primitive plugin template file looks like:

137 | 138 |
var myPlugin = {
139 |   name: '',
140 |   description: '',
141 |   author: '',
142 | 
143 |   ocpp_version: '',
144 |   system: '',
145 | 
146 |   onLoad: function() {
147 | 
148 |   }
149 | };
150 | 
151 | export.modules = myPlugin;
152 | 
153 | 154 |

Every plugin must have a onLoad function, this is the entry point, the function triggered right after loading the plugin. All the other fields are optional.

155 | 156 |

Within the framework of this document, we are going to build a charge point plugin which:

157 | 158 |
    159 |
  • Sends a BootNotification at start.
  • 160 |
  • Sets a result function for the BootNotification procedure.
  • 161 |
  • Calls a StartTransaction procedure when a RemoteStartTransaction is received.
  • 162 |
  • Displays a message when the connection is lost.
  • 163 |
  • Create a new "hello" command for introducing the plugin.
  • 164 |
165 | 166 |

Calling a procedure

167 | 168 |

Right after loading our plugin, this one sends a BootNotification to the Central System.

169 | 170 |
var myPlugin = {
171 |   name: 'My Plugin',
172 |   description: 'Plugin example with the tutorial',
173 |   author: 'myself',
174 | 
175 |   ocpp_version: '1.6',
176 |   system: 'cs',
177 | 
178 |   onLoad: function() {
179 |     var self = this;
180 | 
181 |     self.cp.call('BootNotification');
182 |   }
183 | };
184 | 
185 | export.modules = myPlugin;
186 | 
187 | 188 |

The API also provides a second parameter for the call function in order to customize the arguments of the message.

189 | 190 |
var myPlugin = {
191 |   name: 'My Plugin',
192 |   description: 'Charge point plugin example for the tutorial',
193 |   author: 'myself',
194 | 
195 |   ocpp_version: '1.6',
196 |   system: 'cs',
197 | 
198 |   onLoad: function() {
199 |     var self = this;
200 | 
201 |     self.cp.call('BootNotification', {
202 |       chargePointVendor: "Vendor",
203 |       chargePointModel: "Model"
204 |     });
205 |   }
206 | };
207 | 
208 | export.modules = myPlugin;
209 | 
210 | 211 |

Setting a result function

212 | 213 |

We want our plugin to display the heartbeatInterval when it receives a response from the BootNotification call. So, we need to write an onResult callback function.

214 | 215 |
var myPlugin = {
216 |   name: 'My Plugin',
217 |   description: 'Charge point plugin example for the tutorial',
218 |   author: 'myself',
219 | 
220 |   ocpp_version: '1.6',
221 |   system: 'cs',
222 | 
223 |   onLoad: function() {
224 |     var self = this;
225 | 
226 |     self.cp.call('BootNotification', {
227 |       chargePointVendor: "Vendor",
228 |       chargePointModel: "Model"
229 |     });
230 | 
231 |     self.onResult('BootNotification', function(values) {
232 |       self.log('Heartbeat interval value:' + values.heartbeatInterval);
233 |     });
234 |   }
235 | };
236 | 
237 | export.modules = myPlugin;
238 | 
239 | 240 |

Setting a call function

241 | 242 |

We want our charge point to start a new transaction when the central system calls a RemoteStartTransaction remote procedure.

243 | 244 |
var myPlugin = {
245 |   name: 'My Plugin',
246 |   description: 'Charge point plugin example for the tutorial',
247 |   author: 'myself',
248 | 
249 |   ocpp_version: '1.6',
250 |   system: 'cs',
251 | 
252 |   onLoad: function() {
253 |     var self = this;
254 | 
255 |     self.cp.call('BootNotification', {
256 |       chargePointVendor: "Vendor",
257 |       chargePointModel: "Model"
258 |     });
259 | 
260 |     self.onResult('BootNotification', function(values) {
261 |       self.log('Heartbeat interval value:' + values.heartbeatInterval);
262 |     });
263 | 
264 |     self.onCall('RemoteStartTransaction', function(values) {
265 |       self.cp.call('StartTransaction');
266 |     });
267 | 
268 |   }
269 | };
270 | 
271 | export.modules = myPlugin;
272 | 
273 | 274 |

Setting a function when a connection event occurs

275 | 276 |

When the connection between the charge point and the central system is lost, we want to display a message.

277 | 278 |
var myPlugin = {
279 |   name: 'My Plugin',
280 |   description: 'Charge point plugin example for the tutorial',
281 |   author: 'myself',
282 | 
283 |   ocpp_version: '1.6',
284 |   system: 'cs',
285 | 
286 |   onLoad: function() {
287 |     var self = this;
288 | 
289 |     self.cp.call('BootNotification', {
290 |       chargePointVendor: "Vendor",
291 |       chargePointModel: "Model"
292 |     });
293 | 
294 |     self.onResult('BootNotification', function(values) {
295 |       self.log('Heartbeat interval value:' + values.heartbeatInterval);
296 |     });
297 | 
298 |     self.onCall('RemoteStartTransaction', function(values) {
299 |       self.cp.call('StartTransaction');
300 |     });
301 | 
302 |     self.onConnectEvent(function(type, cbId) {
303 |       if(type == 'disconnected')
304 |         self.log('Connection to the Central System lost !');
305 |     });
306 | 
307 |   }
308 | };
309 | 
310 | export.modules = myPlugin;
311 | 
312 | 313 |

Creating a custom command

314 | 315 |

The API provides functions for parsing what the user enters in the command line interface. This process also allows the developer to create new commands. In our case, we want to create a "hello" command for displaying informations about the plugin.

316 | 317 |
var myPlugin = {
318 |   name: 'My Plugin',
319 |   description: 'Charge point plugin example for the tutorial',
320 |   author: 'myself',
321 | 
322 |   ocpp_version: '1.6',
323 |   system: 'cs',
324 | 
325 |   onLoad: function() {
326 |     var self = this;
327 | 
328 |     self.cp.call('BootNotification', {
329 |       chargePointVendor: "Vendor",
330 |       chargePointModel: "Model"
331 |     });
332 | 
333 |     self.onResult('BootNotification', function(values) {
334 |       self.log('Heartbeat interval value:' + values.heartbeatInterval);
335 |     });
336 | 
337 |     self.onCall('RemoteStartTransaction', function(values) {
338 |       self.cp.call('StartTransaction');
339 |     });
340 | 
341 |     self.onConnectEvent(function(type, cbId) {
342 |       if(type == 'disconnected')
343 |         self.log('Connection to the Central System lost !');
344 |     });
345 | 
346 |     self.onCommand(function(command) {
347 |       var commandObj = self.parse(command);
348 | 
349 |       if(commandObj.command == 'hello') {
350 |         self.log("Hi! I'm "+ myPlugin.name +", created by "+ myPlugin.author +"!");
351 |         return true;
352 |       }
353 |     });
354 | 
355 | 
356 |   }
357 | };
358 | 
359 | export.modules = myPlugin;
360 | 
361 | 362 |

Important: If you want the simulator not to parse the command afterwards, you must return true.

363 | 364 |

Running the plugin

365 | 366 |

For testing the plugin, at first, run a charge point simulator. Available plugins can be viewed using the 'plugins' command:

367 | 368 |
> plugins
369 | List of plugins:
370 | [ ] cbxs-automatic-mode.js
371 | [ ] cs-example.js
372 | [ ] helloworld.js
373 | [ ] myplugin.js
374 | 
375 | 376 |

For loading our plugin, just type:

377 | 378 |
> load myplugin
379 | 
380 | > plugins
381 | List of plugins:
382 | [ ] cbxs-automatic-mode.js
383 | [ ] cs-example.js
384 | [ ] helloworld.js
385 | [X] myplugin.js
386 | 
387 | 388 |

If you want to stop the plugin without stopping the program, just type:

389 | 390 |
> unload myplugin
391 | 
392 | -------------------------------------------------------------------------------- /doc/wsdl/ocpp_chargepointservice_1.2_final.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Defines the unlock-status-value 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Defines the UnlockConnector.req PDU 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Defines the UnlockConnector.conf PDU 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Defines the reset-type-value 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Defines the Reset.req PDU 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Defines the reset-status-value 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | Defines the availability-type-value 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Defines the ChangeAvailability.req PDU 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | Defines the availability-status-value 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | Defines the ChangeAvailability.conf PDU 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | Defines the GetDiagnostics.req PDU 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | Defines the GetDiagnostics.conf PDU 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | Defines the ClearCache.req PDU 162 | 163 | 164 | 165 | 166 | 167 | Defines the clear-cache-status-value 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | Defines the ClearCache.conf PDU 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | Defines the ChangeConfiguration.req PDU 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | Defines the configuration-status-value 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | Defines the ChangeConfiguration.conf PDU 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | Defines the RemoteStartTransaction.req PDU 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | Defines the remote-start-stop-status-value 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | Defines the RemoteStartTransaction.conf PDU 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | Defines the RemoteStopTransaction.req PDU 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | Defines the RemoteStopTransaction.conf PDU 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 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | The ChargePoint Service for the Open Charge Point Protocol 529 | 530 | 531 | 532 | 533 | -------------------------------------------------------------------------------- /doc/wsdl/ocpp_centralsystemservice_1.2_final.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Defines the authorization-status-value 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Defines the Authorize.req PDU 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Defines the Authorize.conf PDU 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Defines the StartTransaction.req PDU 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | Defines the StartTransaction.conf PDU 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | Defines the StopTransaction.req PDU 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Defines the StopTransaction.conf PDU 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | Defines the Heartbeat.req PDU 119 | 120 | 121 | 122 | 123 | 124 | Defines the Heartbeat.conf PDU 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Defines single value of the meter-value-value 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | Defines the MeterValues.req PDU 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | Defines the MeterValues.conf PDU 154 | 155 | 156 | 157 | 158 | 159 | Defines the BootNotification.req PDU 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | Defines the registration-status-value 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | Defines the BootNotification.conf PDU 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | Defines the charge-point-error-value 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | Defines the charge-point-status-value 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | Defines the StatusNotification.req PDU 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | Defines the StatusNotification.conf PDU 237 | 238 | 239 | 240 | 241 | 242 | Defines the firmware-status-value 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | Defines the FirmwareStatusNotification.req PDU 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | Defines the FirmwareStatusNotification.conf PDU 264 | 265 | 266 | 267 | 268 | 269 | Defines the diagnostics-status-value 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | Defines the DiagnosticsStatusNotification.req PDU 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | Defines the DiagnosticsStatusNotification.conf PDU 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 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 | The Central System Service for the Open Charge Point Protocol 547 | 548 | 549 | 550 | 551 | -------------------------------------------------------------------------------- /lib/ui.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | /** 4 | * 5 | * 6 | * 7 | */ 8 | 9 | 10 | 11 | var readline = require('readline'), 12 | fs = require('fs'); 13 | 14 | 15 | var OCPP = require('./ocpp-protocol.js'), 16 | Utils = require('./utils.js'), 17 | Simulators = require('./simulators.js'), 18 | Transport = require('./transport.js'), 19 | Plugins = require('./plugins.js'); 20 | 21 | var UI = { 22 | 23 | /** 24 | * IMPORTANT: optional field must be prefixed by - 25 | */ 26 | commands: { 27 | 28 | /* 29 | command: { 30 | options: { 31 | mandatory: { 32 | 'option': 'description' 33 | }, 34 | optional: { 35 | '-o': 'description' 36 | } 37 | }, 38 | func: function(args) { 39 | 40 | } 41 | } 42 | */ 43 | 44 | start_cs: { 45 | options: { 46 | mandatory: { 47 | 'port': 'port', 48 | }, 49 | optional: { 50 | '-i': 'websocket ping interval, default: '+ 51 | OCPP.HEARTBEAT_INTERVAL, 52 | '-u': 'endpoint URL, default: '+ OCPP.ENDPOINTURL, 53 | '-p': 'websocket subprotocol, default: '+ OCPP.SUB_PROTOCOL, 54 | '-t': 'transport layer mode (websocket or soap), default: '+ 55 | Transport.TRANSPORT_LAYER 56 | } 57 | }, 58 | func: function(args) { 59 | if(Simulators.centralSystem != null) { 60 | Utils.log("Central System already started."); 61 | return; 62 | } 63 | 64 | var port = args['port']; 65 | OCPP.KEEP_ALIVE_INTERVAL = args['-i'] || 66 | OCPP.KEEP_ALIVE_INTERVAL; 67 | OCPP.ENDPOINTURL = args['-u'] || OCPP.ENDPOINTURL; 68 | 69 | if(args['-p'] != undefined) { 70 | if(args['-p'].indexOf(',') > -1) 71 | OCPP.SUB_PROTOCOL = args['-p'].split(','); 72 | else 73 | OCPP.SUB_PROTOCOL = [args['-p']]; 74 | } 75 | 76 | Transport.TRANSPORT_LAYER = args['-t'] || Transport.TRANSPORT_LAYER; 77 | 78 | Simulators.centralSystem = new Simulators.CentralSystemSimulator(port, 79 | Transport.TRANSPORT_LAYER); 80 | Utils.log("CentralSystem started."); 81 | } 82 | }, 83 | 84 | start_cp: { 85 | options: { 86 | mandatory: { 87 | 'url': 'url, ex: http://localhost:9000/simulator', 88 | 'identifier': 'boxid01' 89 | }, 90 | optional: { 91 | '-p': 'websocket subprotocol, default: '+ OCPP.SUB_PROTOCOL, 92 | '-i': 'websocket ping interval (use 0 for disabling), default: '+ 93 | OCPP.KEEP_ALIVE_INTERVAL, 94 | '-t': 'transport layer mode (websocket or soap), default: '+ 95 | Transport.TRANSPORT_LAYER, 96 | '-f': 'In SOAP mode, value for FROM header.', 97 | '-r': 'In SOAP mode, define the port for remote actions.' 98 | } 99 | }, 100 | func: function(args) { 101 | var protocol = OCPP.SUB_PROTOCOL = args['-p'] || "ocpp1.6"; 102 | OCPP.KEEP_ALIVE_INTERVAL = parseInt(args['-i'], 10) 103 | || OCPP.KEEP_ALIVE_INTERVAL; 104 | 105 | Transport.TRANSPORT_LAYER = args['-t'] || Transport.TRANSPORT_LAYER; 106 | // In SOAP mode 107 | Transport.retrieveIPAddress(); 108 | var fromHeader = args['-f'], 109 | remoteActionPort = args['-r'] || Transport.retrievePort(fromHeader); 110 | 111 | var cp = new Simulators.ChargePointSimulator(args['url'], 112 | args['identifier'], protocol, Transport.TRANSPORT_LAYER, 113 | { 114 | fromHeader: fromHeader, 115 | remoteActionPort: remoteActionPort 116 | }); 117 | Simulators.chargePoints[cp.chargePointId] = cp; 118 | } 119 | }, 120 | 121 | /* 122 | status: { 123 | func: function(args) { 124 | if (centralSystem == null) 125 | return; 126 | 127 | console.log("+ Central System on port " + centralSystem._port); 128 | for(var i in chargePoints) { 129 | console.log("|-- Charge Point #"+ i); 130 | } 131 | } 132 | }, 133 | */ 134 | 135 | help: { 136 | func: function(args) { 137 | console.log("Available commands : "); 138 | var n = UI.getHelp(); 139 | 140 | // don't print the N first ones 141 | var i = 0; 142 | for(var c in UI.commands) { 143 | if(i++ < n) 144 | continue; 145 | 146 | console.log(' - '+ c); 147 | } 148 | 149 | console.log(" - quit"); 150 | } 151 | }, 152 | 153 | set: { 154 | options:{ 155 | mandatory:{ 156 | 'cs|cp': 'change target configuration, value: `cs` or `cp`' 157 | }, 158 | optional:{} 159 | }, 160 | func: function(args) { 161 | if(Utils.isEmpty(args.custom)) { 162 | console.log('Error for command "set": syntax: set ' 163 | +'variable=value ...'); 164 | console.log('Supported settings:'); 165 | console.log('- websocket_ping_interval= : set the websocket'+ 166 | ' ping interval (0 for disabling).'); 167 | console.log('- print_xml= : in SOAP mode, print the raw '+ 168 | 'SOAP content.'); 169 | return; 170 | } 171 | 172 | // keepalive_interval, only supported feature at the moment 173 | 174 | // if keepalive_interval 175 | if(args.custom.websocket_ping_interval != undefined) { 176 | var interval = args.custom.websocket_ping_interval; 177 | if(isNaN(interval)) { 178 | console.log('Error: argument provided for websocket_ping_interval'+ 179 | ' is not a number.'); 180 | return; 181 | } 182 | 183 | // if central system 184 | if(args['cs|cp'] == 'cs') { 185 | if (Simulators.centralSystem != null) { 186 | Simulators.centralSystem.transportLayer.layer 187 | .setWebSocketPingInterval(interval); 188 | } 189 | else { 190 | console.log('Error, no central system detected in this simulator.'); 191 | } 192 | } 193 | 194 | // if charge points 195 | if (args['cs|cp'] == 'cp') { 196 | if(!Utils.isEmpty(Simulators.chargePoints)) { 197 | for(var cp in Simulators.chargePoints) { 198 | Simulators.chargePoints[cp].transportLayer.layer 199 | .setWebSocketPingInterval(interval); 200 | } 201 | } 202 | else { 203 | console.log('Error, no charge points detected in this simulator.'); 204 | } 205 | } 206 | } 207 | 208 | // if print_xml 209 | if(args.custom.print_xml != undefined) { 210 | Transport.PRINT_XML = args.custom.print_xml == 'true'; 211 | } 212 | } 213 | }, 214 | 215 | send_raw: { 216 | options: { 217 | optional: { 218 | '--id': 'id of charge point', 219 | '-e': 'whole envelope of the message without spaces', 220 | '-h': 'print headers (true of false)' 221 | } 222 | }, 223 | func: function(args) { 224 | // if soap, disable 225 | /*if(Transport.TRANSPORT_LAYER == 'soap') { 226 | console.log('Cannot use send_raw command in SOAP mode.'); 227 | return; 228 | }*/ 229 | Transport.PRINT_HEADERS = !!(args['-h'] && args['-h'] == 'true'); 230 | 231 | var cpId = args['--id'] || -1; 232 | if(args['-e'] == undefined) { 233 | Utils.log('Error for command "send": syntax: send_raw -e '); 234 | return; 235 | } 236 | Simulators.commandCPtoCS(cpId, '', args['-e']); 237 | } 238 | }, 239 | 240 | send: { 241 | options: { 242 | optional: { 243 | '--id': 'id of charge point', 244 | '-n': 'procedure name', 245 | '-p': 'payload' 246 | } 247 | }, 248 | func: function(args) { 249 | // if soap, disable 250 | if(Transport.TRANSPORT_LAYER == 'soap') { 251 | console.log('Cannot use send command in SOAP mode.'); 252 | return; 253 | } 254 | 255 | var cpId = args['--id'] || -1; 256 | if(args['-n'] == undefined || args['-p'] == undefined) { 257 | Utils.log('Error for command "send": syntax: send -n '+ 258 | ' -p '); 259 | return; 260 | } 261 | 262 | Simulators.commandCPtoCS(cpId, args['-n'], args['-p']); 263 | } 264 | }, 265 | 266 | load: { 267 | options: { 268 | mandatory: { 269 | 'filename': 'filename of the plugin without the extension' 270 | } 271 | }, 272 | func: function(args) { 273 | Plugins.load(args['filename']); 274 | } 275 | }, 276 | 277 | unload: { 278 | options: { 279 | mandatory: { 280 | 'filename': 'filename of the plugin without the extension' 281 | } 282 | }, 283 | func: function(args) { 284 | Plugins.unload(args['filename']); 285 | } 286 | }, 287 | 288 | plugins: { 289 | options: {}, 290 | func: function(args) { 291 | console.log('List of plugins:'); 292 | var path = __dirname +'/../plugins/', 293 | files = fs.readdirSync(path); 294 | 295 | for(var file in files) { 296 | if(files[file].indexOf('.js') == -1) 297 | continue; 298 | 299 | var pluginFile = files[file].replace('.js', ''), 300 | enabled = Plugins.plugins[pluginFile] ? 301 | ' [X] ' : ' [ ] '; 302 | 303 | // dont display hidden file 304 | if(pluginFile[0] != '.') 305 | console.log(enabled + pluginFile); 306 | } 307 | } 308 | }, 309 | 310 | remote_send_raw: { 311 | options: { 312 | optional: { 313 | '--remote-id': 'id of charge point', 314 | '-e': 'whole envelope of the message without spaces', 315 | '-h': 'print http headers (true of false)' 316 | } 317 | }, 318 | func: function(args) { 319 | // if soap, disable 320 | /*if(Transport.TRANSPORT_LAYER == 'soap') { 321 | console.log('Cannot use send command in SOAP mode.'); 322 | return; 323 | }*/ 324 | Transport.PRINT_HEADERS = !!(args['-h'] && args['-h'] == 'true'); 325 | 326 | var cpId = args['--remote-id'] || -1; 327 | if(args['-e'] == undefined) { 328 | Utils.log('Error for command "send": syntax: remote_send_raw'+ 329 | ' -e '); 330 | return; 331 | } 332 | Simulators.commandCStoCP(cpId, '', args['-e']); 333 | } 334 | }, 335 | 336 | remote_send: { 337 | options: { 338 | optional: { 339 | '--remote-id': 'id of charge point', 340 | '-n': 'procedure name', 341 | '-p': 'payload' 342 | } 343 | }, 344 | func: function(args) { 345 | // if soap, disable 346 | if(Transport.TRANSPORT_LAYER == 'soap') { 347 | console.log('Cannot use send command in SOAP mode.'); 348 | return; 349 | } 350 | 351 | var cpId = args['--remote-id'] || -1; 352 | if(args['-n'] == undefined || args['-p'] == undefined) { 353 | Utils.log('Error for command "send": syntax: remote_send -n '+ 354 | ' -p '); 355 | return; 356 | } 357 | 358 | Simulators.commandCStoCP(cpId, args['-n'], args['-p']); 359 | } 360 | }, 361 | 362 | /* */ 363 | 364 | bootnotification: { 365 | options: { 366 | optional: { 367 | '--id': 'id of charge point' 368 | } 369 | }, 370 | func: function(args, values) { 371 | var cpId = args['--id'] || -1; 372 | Simulators.commandCPtoCS(cpId, "BootNotification", values); 373 | } 374 | }, 375 | 376 | heartbeat: { 377 | options: { 378 | optional: { 379 | '--id': 'id of charge point' 380 | } 381 | }, 382 | func: function(args, values) { 383 | var cpId = args['--id'] || -1; 384 | Simulators.commandCPtoCS(cpId, "Heartbeat", values); 385 | } 386 | }, 387 | 388 | metervalues: { 389 | options: { 390 | optional: { 391 | '--id': 'id of charge point' 392 | } 393 | }, 394 | func: function(args, values) { 395 | var cpId = args['--id'] || -1; 396 | Simulators.commandCPtoCS(cpId, "MeterValues", values); 397 | } 398 | }, 399 | 400 | starttransaction: { 401 | options: { 402 | optional: { 403 | '--id': 'id of charge point' 404 | } 405 | }, 406 | func: function(args, values) { 407 | var cpId = args['--id'] || -1; 408 | Simulators.commandCPtoCS(cpId, "StartTransaction", values); 409 | } 410 | }, 411 | 412 | triggermessage: { 413 | options: { 414 | optional: { 415 | '--id': 'id of charge point' 416 | } 417 | }, 418 | func: function(args, values) { 419 | var cpId = args['--id'] || -1; 420 | Simulators.commandCPtoCS(cpId, "TriggerMessage", values); 421 | } 422 | }, 423 | 424 | stoptransaction: { 425 | options: { 426 | optional: { 427 | '--id': 'id of charge point' 428 | } 429 | }, 430 | func: function(args, values) { 431 | var cpId = args['--id'] || -1; 432 | Simulators.commandCPtoCS(cpId, "StopTransaction", values); 433 | } 434 | }, 435 | 436 | statusnotification: { 437 | options: { 438 | optional: { 439 | '--id': 'id of charge point' 440 | } 441 | }, 442 | func: function(args, values) { 443 | var cpId = args['--id'] || -1; 444 | Simulators.commandCPtoCS(cpId, "StatusNotification", values); 445 | } 446 | }, 447 | 448 | authorize: { 449 | options: { 450 | optional: { 451 | '--id': 'id of charge point' 452 | } 453 | }, 454 | func: function(args, values) { 455 | var cpId = args['--id'] || -1; 456 | Simulators.commandCPtoCS(cpId, "Authorize", values); 457 | } 458 | }, 459 | 460 | firmwarestatusnotification: { 461 | options: { 462 | optional: { 463 | '--id': 'id of charge point' 464 | } 465 | }, 466 | func: function(args, values) { 467 | var cpId = args['--id'] || -1; 468 | Simulators.commandCPtoCS(cpId, "FirmwareStatusNotification", values); 469 | } 470 | }, 471 | 472 | diagnosticsstatusnotification: { 473 | options: { 474 | optional: { 475 | '--id': 'id of charge point' 476 | } 477 | }, 478 | func: function(args, values) { 479 | var cpId = args['--id'] || -1; 480 | Simulators.commandCPtoCS(cpId, "DiagnosticsStatusNotification", values); 481 | } 482 | }, 483 | 484 | datatransfer: { 485 | options: { 486 | optional: { 487 | '--id': 'id of remote charge point' 488 | } 489 | }, 490 | func: function(args, values) { 491 | var cpId = args['--id'] || -1; 492 | Simulators.commandCPtoCS(cpId, "DataTransfer", values); 493 | } 494 | }, 495 | 496 | websocket_ping_cs: { 497 | options: { 498 | optional: { 499 | '--id': 'id of charge point' 500 | } 501 | }, 502 | func: function(args, values) { 503 | // if soap, disable 504 | if(Transport.TRANSPORT_LAYER == 'soap') { 505 | console.log('Cannot use send command in SOAP mode.'); 506 | return; 507 | } 508 | 509 | var cpId = args['--id'] || -1; 510 | // not a true OCPP command 511 | Simulators.commandCPtoCS(cpId, "WebSocketPing", values); 512 | } 513 | }, 514 | 515 | websocket_ping_cp: { 516 | options: { 517 | optional: { 518 | '--remote-id': 'id of remote charge point' 519 | } 520 | }, 521 | func: function(args, values) { 522 | // if soap, disable 523 | if(Transport.TRANSPORT_LAYER == 'soap') { 524 | console.log('Cannot use send command in SOAP mode.'); 525 | return; 526 | } 527 | 528 | var cpId = args['--remote-id'] || -1; 529 | // not a true OCPP command 530 | Simulators.commandCStoCP(cpId, "WebSocketPing", values); 531 | } 532 | }, 533 | 534 | remote_reset: { 535 | options: { 536 | optional: { 537 | '--remote-id': 'id of remote charge point' 538 | } 539 | }, 540 | func: function(args, values) { 541 | var cpId = args['--remote-id'] || -1; 542 | Simulators.commandCStoCP(cpId, "Reset", values); 543 | } 544 | }, 545 | 546 | remote_unlockconnector: { 547 | options: { 548 | optional: { 549 | '--remote-id': 'id of remote charge point' 550 | } 551 | }, 552 | func: function(args, values) { 553 | var cpId = args['--remote-id'] || -1; 554 | Simulators.commandCStoCP(cpId, "UnlockConnector", values); 555 | } 556 | }, 557 | 558 | remote_changeavailability: { 559 | options: { 560 | optional: { 561 | '--remote-id': 'id of remote charge point' 562 | } 563 | }, 564 | func: function(args, values) { 565 | var cpId = args['--remote-id'] || -1; 566 | Simulators.commandCStoCP(cpId, "ChangeAvailability", values); 567 | } 568 | }, 569 | 570 | remote_clearcache: { 571 | options: { 572 | optional: { 573 | '--remote-id': 'id of remote charge point' 574 | } 575 | }, 576 | func: function(args, values) { 577 | var cpId = args['--remote-id'] || -1; 578 | Simulators.commandCStoCP(cpId, "ClearCache", values); 579 | } 580 | }, 581 | 582 | remote_changeconfiguration: { 583 | options: { 584 | optional: { 585 | '--remote-id': 'id of remote charge point' 586 | } 587 | }, 588 | func: function(args, values) { 589 | var cpId = args['--remote-id'] || -1; 590 | Simulators.commandCStoCP(cpId, "ChangeConfiguration", values); 591 | } 592 | }, 593 | 594 | remote_getdiagnostics: { 595 | options: { 596 | optional: { 597 | '--remote-id': 'id of remote charge point' 598 | } 599 | }, 600 | func: function(args, values) { 601 | var cpId = args['--remote-id'] || -1; 602 | Simulators.commandCStoCP(cpId, "GetDiagnostics", values); 603 | } 604 | }, 605 | 606 | remote_updatefirmware: { 607 | options: { 608 | optional: { 609 | '--remote-id': 'id of remote charge point' 610 | } 611 | }, 612 | func: function(args, values) { 613 | var cpId = args['--remote-id'] || -1; 614 | Simulators.commandCStoCP(cpId, "UpdateFirmware", values); 615 | } 616 | }, 617 | 618 | remote_starttransaction: { 619 | options: { 620 | optional: { 621 | '--remote-id': 'id of remote charge point' 622 | } 623 | }, 624 | func: function(args, values) { 625 | var cpId = args['--remote-id'] || -1; 626 | Simulators.commandCStoCP(cpId, "RemoteStartTransaction", values); 627 | } 628 | }, 629 | 630 | remote_stoptransaction: { 631 | options: { 632 | optional: { 633 | '--remote-id': 'id of remote charge point' 634 | } 635 | }, 636 | func: function(args, values) { 637 | var cpId = args['--remote-id'] || -1; 638 | Simulators.commandCStoCP(cpId, "RemoteStopTransaction", values); 639 | } 640 | }, 641 | 642 | remote_cancelreservation: { 643 | options: { 644 | optional: { 645 | '--remote-id': 'id of remote charge point' 646 | } 647 | }, 648 | func: function(args, values) { 649 | var cpId = args['--remote-id'] || -1; 650 | Simulators.commandCStoCP(cpId, "CancelReservation", values); 651 | } 652 | }, 653 | 654 | remote_datatransfer: { 655 | options: { 656 | optional: { 657 | '--remote-id': 'id of remote charge point' 658 | } 659 | }, 660 | func: function(args, values) { 661 | var cpId = args['--remote-id'] || -1; 662 | Simulators.commandCStoCP(cpId, "DataTransfer", values); 663 | } 664 | }, 665 | 666 | remote_getconfiguration: { 667 | options: { 668 | optional: { 669 | '--remote-id': 'id of remote charge point' 670 | } 671 | }, 672 | func: function(args, values) { 673 | var cpId = args['--remote-id'] || -1; 674 | Simulators.commandCStoCP(cpId, "GetConfiguration", values); 675 | } 676 | }, 677 | 678 | remote_getlocallistversion: { 679 | options: { 680 | optional: { 681 | '--remote-id': 'id of remote charge point' 682 | } 683 | }, 684 | func: function(args, values) { 685 | var cpId = args['--remote-id'] || -1; 686 | Simulators.commandCStoCP(cpId, "GetLocalListVersion", values); 687 | } 688 | }, 689 | 690 | remote_reservenow: { 691 | options: { 692 | optional: { 693 | '--remote-id': 'id of remote charge point' 694 | } 695 | }, 696 | func: function(args, values) { 697 | var cpId = args['--remote-id'] || -1; 698 | Simulators.commandCStoCP(cpId, "ReserveNow", values); 699 | } 700 | }, 701 | 702 | remote_sendlocallist: { 703 | options: { 704 | optional: { 705 | '--remote-id': 'id of remote charge point' 706 | } 707 | }, 708 | func: function(args, values) { 709 | var cpId = args['--remote-id'] || -1; 710 | Simulators.commandCStoCP(cpId, "SendLocalList", values); 711 | } 712 | } 713 | 714 | }, 715 | 716 | 717 | /** 718 | */ 719 | parseCommand: function(line, centralSystem, chargePoints) { 720 | var value = null; 721 | 722 | // if it's a known command 723 | if(UI.commands[line[0]] != undefined) { 724 | var args = UI.argumentsToArray(line); 725 | 726 | // if error 727 | if(args == -1) { 728 | var cmd = line[0]; 729 | for(var m in UI.commands[line[0]].options.mandatory) { 730 | cmd += ' <'+ m + '>'; 731 | } 732 | var optionals = UI.commands[line[0]].options.optional; 733 | if(optionals != undefined) { 734 | cmd += '\nWith optional arguments:\n'; 735 | for(var o in optionals) { 736 | cmd += ' '+ o+ ' : '+ optionals[o] +'\n'; 737 | } 738 | } 739 | Utils.log('Error in command "'+ line[0] +'", syntax: '+ cmd); 740 | return -1; 741 | } 742 | 743 | UI.commands[line[0]].func(args, args.custom, centralSystem, chargePoints); 744 | } 745 | else { 746 | console.log("Error: `"+ line[0] +"' unknown command. Type help for more"+ 747 | " informations."); 748 | return -1; 749 | } 750 | 751 | }, 752 | 753 | /** 754 | */ 755 | argumentsToArray: function(args) { 756 | var cmd = args[0], 757 | ret = { custom: {} }, 758 | prot = Utils.retrieveVersion(OCPP.SUB_PROTOCOL); 759 | 760 | if(UI.commands[cmd].options == undefined) 761 | return ret; 762 | 763 | var i = 1; // first after the cmd 764 | 765 | try { 766 | // Retrieve mandatory arguments 767 | var mands = UI.commands[cmd].options.mandatory; 768 | for(var m in mands) { 769 | if(args[i][0] != '-') 770 | ret[m] = args[m[0] == '-' ? ++i : i++]; // if --arg next else current 771 | else 772 | throw ""; 773 | } 774 | 775 | // Retrieve optional arguments 776 | for( ; i < args.length; i++) { 777 | if (UI.commands[cmd].options.optional[args[i]] != undefined 778 | && args[i + 1] != undefined && args[i + 1][0] != '-') 779 | ret[args[i]] = args[++i]; 780 | // if argument of type argument=value for payload customization 781 | else if(args[i].indexOf('=') > 0) { // don't start with = 782 | var tokens = args[i].split('='), 783 | argument = tokens[0], 784 | value = tokens[1]; 785 | 786 | ret.custom[argument] = value; 787 | } 788 | else { 789 | throw ""; 790 | } 791 | } 792 | } catch (e) { 793 | ret = -1; 794 | } 795 | 796 | return ret; 797 | }, 798 | 799 | /** 800 | * Command line completer 801 | */ 802 | completer: function(line) { 803 | var completions = []; 804 | for(var c in UI.commands) { 805 | completions.push(c); 806 | } 807 | var hits = completions.filter(function(c) { return c.indexOf(line) == 0 }) 808 | return [hits.length ? hits : completions, line] 809 | }, 810 | 811 | 812 | /** 813 | * Help 814 | */ 815 | getHelp: function() { 816 | console.log(" - start_cs [options] .ex: start_cs 9000 -u /simulator"); 817 | console.log(" - start_cp .ex: "+ 818 | "start_cp ws://localhost:9000/simulator id01"); 819 | /* console.log(" - status"); */ 820 | console.log(" - help"); 821 | return 3; 822 | }, 823 | 824 | /** 825 | * Simple automata for converting a line to tokens. 826 | * Handle string and remove useless spaces 827 | * 828 | */ 829 | parseLineToTokens: function(line) { 830 | var arr = [], 831 | tmp = "", 832 | in_string = false; 833 | 834 | for(var c in line) { 835 | if(line[c] == " " && !in_string) { 836 | if (tmp != "") { 837 | arr.push(tmp); 838 | tmp = ""; 839 | } 840 | continue; 841 | } 842 | 843 | if(line[c] == '"') { 844 | in_string = !in_string; 845 | } 846 | 847 | tmp += line[c]; 848 | } 849 | 850 | if (tmp != "") 851 | arr.push(tmp); 852 | 853 | return arr; 854 | }, 855 | 856 | commandToObject: function(lineArray) { 857 | var cmd = { 858 | command: '', 859 | arguments: [], 860 | options: {} 861 | }, 862 | i = 0; 863 | 864 | // command = first token 865 | cmd.command = lineArray[i++]; 866 | 867 | var x = i; 868 | for( ; x < lineArray.length; x++) { 869 | if(lineArray[x][0] == '-') { 870 | cmd.options[lineArray[x]] = lineArray[++x]; 871 | } 872 | else { 873 | cmd.arguments.push(lineArray[x]); 874 | } 875 | } 876 | 877 | return cmd; 878 | }, 879 | 880 | /** 881 | * 882 | */ 883 | 884 | commandLine: function() { 885 | rl.setPrompt("> "); 886 | rl.prompt(); 887 | 888 | rl.on("line", function(line) { 889 | var lineRaw = line; 890 | line = UI.parseLineToTokens(line); 891 | 892 | if(line.length == 0) { 893 | rl.prompt(); 894 | return; 895 | } 896 | 897 | // strips \n 898 | line[0] = line[0].replace('\n', ''); 899 | 900 | if(line[0] == "quit") 901 | rl.close(); 902 | 903 | if(!Plugins.callCommandHandlers(lineRaw, line)) 904 | UI.parseCommand(line); 905 | 906 | rl.prompt(); 907 | }).on("close", function() { 908 | Utils.log("End of simulation : interrupted by user."); 909 | process.exit(0); 910 | }).on('SIGTSTP', function() { 911 | // This will override SIGTSTP and prevent the program from going to the 912 | // background. 913 | //console.log('Caught SIGTSTP.'); 914 | rl.prompt(); 915 | }); 916 | } 917 | 918 | }; 919 | 920 | 921 | Plugins.UI = UI; 922 | 923 | var rl = readline.createInterface(process.stdin, process.stdout, UI.completer); 924 | 925 | UI.rl = rl; 926 | 927 | module.exports = UI; 928 | 929 | -------------------------------------------------------------------------------- /doc/wsdl/ocpp_centralsystemservice_1.6_final.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 | --------------------------------------------------------------------------------