// matth-x/ArduinoOcpp // Copyright Matthias Akstaller 2019 - 2022 // MIT License #include "ArduinoOcpp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ArduinoOcpp { namespace Facade { #ifndef AO_CUSTOM_WS WebSocketsClient *webSocket {nullptr}; OcppSocket *ocppSocket {nullptr}; #endif OcppEngine *ocppEngine {nullptr}; FilesystemOpt fileSystemOpt {}; float voltage_eff {230.f}; #define OCPP_NUMCONNECTORS 2 #define OCPP_ID_OF_CONNECTOR 1 #define OCPP_ID_OF_CP 0 boolean OCPP_booted = false; //if BootNotification succeeded } //end namespace ArduinoOcpp::Facade } //end namespace ArduinoOcpp using namespace ArduinoOcpp; using namespace ArduinoOcpp::Facade; using namespace ArduinoOcpp::Ocpp16; #ifndef AO_CUSTOM_WS void OCPP_initialize(const char *CS_hostname, uint16_t CS_port, const char *CS_url, float V_eff, ArduinoOcpp::FilesystemOpt fsOpt, ArduinoOcpp::OcppClock system_time) { if (ocppEngine) { AO_DBG_WARN("Can't be called two times. Either restart ESP, or call OCPP_deinitialize() before"); return; } if (!webSocket) webSocket = new WebSocketsClient(); // server address, port and URL webSocket->begin(CS_hostname, CS_port, CS_url, "ocpp1.6"); // try ever 5000 again if connection has failed webSocket->setReconnectInterval(5000); // start heartbeat (optional) // ping server every 15000 ms // expect pong from server within 3000 ms // consider connection disconnected if pong is not received 2 times webSocket->enableHeartbeat(15000, 3000, 2); //comment this one out to for specific OCPP servers delete ocppSocket; ocppSocket = new EspWiFi::OcppClientSocket(webSocket); OCPP_initialize(*ocppSocket, V_eff, fsOpt); } #endif void OCPP_initialize(OcppSocket& ocppSocket, float V_eff, ArduinoOcpp::FilesystemOpt fsOpt, ArduinoOcpp::OcppClock system_time) { if (ocppEngine) { AO_DBG_WARN("Can't be called two times. To change the credentials, either restart ESP, or call OCPP_deinitialize() before"); return; } voltage_eff = V_eff; fileSystemOpt = fsOpt; configuration_init(fileSystemOpt); //call before each other library call ocppEngine = new OcppEngine(ocppSocket, system_time); auto& model = ocppEngine->getOcppModel(); model.setChargePointStatusService(std::unique_ptr( new ChargePointStatusService(*ocppEngine, OCPP_NUMCONNECTORS))); model.setHeartbeatService(std::unique_ptr( new HeartbeatService(*ocppEngine))); #if !defined(AO_CUSTOM_UPDATER) && !defined(AO_CUSTOM_WS) model.setFirmwareService(std::unique_ptr( EspWiFi::makeFirmwareService(*ocppEngine, "1234578901"))); //instantiate FW service + ESP installation routine #else model.setFirmwareService(std::unique_ptr( new FirmwareService(*ocppEngine))); //only instantiate FW service #endif #if !defined(AO_CUSTOM_DIAGNOSTICS) && !defined(AO_CUSTOM_WS) model.setDiagnosticsService(std::unique_ptr( EspWiFi::makeDiagnosticsService(*ocppEngine))); //will only return "Rejected" because logging is not implemented yet #else model.setDiagnosticsService(std::unique_ptr( new DiagnosticsService(*ocppEngine))); #endif ocppEngine->setRunOcppTasks(false); //prevent OCPP classes from doing anything while booting } void OCPP_deinitialize() { AO_DBG_DEBUG("Still experimental function. If you find problems, it would be great if you publish them on the GitHub page"); delete ocppEngine; ocppEngine = nullptr; #ifndef AO_CUSTOM_WS delete ocppSocket; ocppSocket = nullptr; delete webSocket; webSocket = nullptr; #endif simpleOcppFactory_deinitialize(); fileSystemOpt = FilesystemOpt(); voltage_eff = 230.f; OCPP_booted = false; } void OCPP_loop() { if (!ocppEngine) { AO_DBG_WARN("Please call OCPP_initialize before"); delay(200); //Prevent this message from flooding the Serial monitor. return; } ocppEngine->loop(); auto& model = ocppEngine->getOcppModel(); if (!OCPP_booted) { auto csService = model.getChargePointStatusService(); if (!csService || csService->isBooted()) { OCPP_booted = true; ocppEngine->setRunOcppTasks(true); } else { return; //wait until the first BootNotification succeeded } } } void setPowerActiveImportSampler(std::function power) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto& model = ocppEngine->getOcppModel(); if (!model.getMeteringService()) { model.setMeteringSerivce(std::unique_ptr( new MeteringService(*ocppEngine, OCPP_NUMCONNECTORS))); } model.getMeteringService()->setPowerSampler(OCPP_ID_OF_CONNECTOR, power); //connectorId=1 } void setEnergyActiveImportSampler(std::function energy) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto& model = ocppEngine->getOcppModel(); if (!model.getMeteringService()) { model.setMeteringSerivce(std::unique_ptr( new MeteringService(*ocppEngine, OCPP_NUMCONNECTORS))); } model.getMeteringService()->setEnergySampler(OCPP_ID_OF_CONNECTOR, energy); //connectorId=1 } void setEvRequestsEnergySampler(std::function evRequestsEnergy) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return; } connector->setEvRequestsEnergySampler(evRequestsEnergy); } void setConnectorEnergizedSampler(std::function connectorEnergized) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return; } connector->setConnectorEnergizedSampler(connectorEnergized); } void setConnectorPluggedSampler(std::function connectorPlugged) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return; } connector->setConnectorPluggedSampler(connectorPlugged); } void addConnectorErrorCodeSampler(std::function connectorErrorCode) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return; } connector->addConnectorErrorCodeSampler(connectorErrorCode); } void setOnChargingRateLimitChange(std::function chargingRateChanged) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto& model = ocppEngine->getOcppModel(); if (!model.getSmartChargingService()) { model.setSmartChargingService(std::unique_ptr( new SmartChargingService(*ocppEngine, 11000.0f, voltage_eff, OCPP_NUMCONNECTORS, fileSystemOpt))); //default charging limit: 11kW } model.getSmartChargingService()->setOnLimitChange(chargingRateChanged); } void setOnUnlockConnector(std::function unlockConnector) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return; } connector->setOnUnlockConnector(unlockConnector); } void setOnSetChargingProfileRequest(OnReceiveReqListener onReceiveReq) { setOnSetChargingProfileRequestListener(onReceiveReq); } void setOnRemoteStartTransactionSendConf(OnSendConfListener onSendConf) { setOnRemoteStartTransactionSendConfListener(onSendConf); } void setOnRemoteStopTransactionReceiveReq(OnReceiveReqListener onReceiveReq) { setOnRemoteStopTransactionReceiveRequestListener(onReceiveReq); } void setOnRemoteStopTransactionSendConf(OnSendConfListener onSendConf) { setOnRemoteStopTransactionSendConfListener(onSendConf); } void setOnResetSendConf(OnSendConfListener onSendConf) { setOnResetSendConfListener(onSendConf); } void setOnResetReceiveReq(OnReceiveReqListener onReceiveReq) { setOnResetReceiveRequestListener(onReceiveReq); } void authorize(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { AO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", IDTAG_LEN_MAX); return; } auto authorize = makeOcppOperation( new Authorize(idTag)); if (onConf) authorize->setOnReceiveConfListener(onConf); if (onAbort) authorize->setOnAbortListener(onAbort); if (onTimeout) authorize->setOnTimeoutListener(onTimeout); if (onError) authorize->setOnReceiveErrorListener(onError); if (timeout) authorize->setTimeout(std::move(timeout)); else authorize->setTimeout(std::unique_ptr(new FixedTimeout(20000))); ocppEngine->initiateOperation(std::move(authorize)); } void bootNotification(const char *chargePointModel, const char *chargePointVendor, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto bootNotification = makeOcppOperation( new BootNotification(chargePointModel, chargePointVendor)); if (onConf) bootNotification->setOnReceiveConfListener(onConf); if (onAbort) bootNotification->setOnAbortListener(onAbort); if (onTimeout) bootNotification->setOnTimeoutListener(onTimeout); if (onError) bootNotification->setOnReceiveErrorListener(onError); if (timeout) bootNotification->setTimeout(std::move(timeout)); else bootNotification->setTimeout(std::unique_ptr (new SuppressedTimeout())); ocppEngine->initiateOperation(std::move(bootNotification)); } void bootNotification(DynamicJsonDocument *payload, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto bootNotification = makeOcppOperation( new BootNotification(payload)); if (onConf) bootNotification->setOnReceiveConfListener(onConf); if (onAbort) bootNotification->setOnAbortListener(onAbort); if (onTimeout) bootNotification->setOnTimeoutListener(onTimeout); if (onError) bootNotification->setOnReceiveErrorListener(onError); if (timeout) bootNotification->setTimeout(std::move(timeout)); else bootNotification->setTimeout(std::unique_ptr(new SuppressedTimeout())); ocppEngine->initiateOperation(std::move(bootNotification)); } void startTransaction(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { AO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", IDTAG_LEN_MAX); return; } auto startTransaction = makeOcppOperation( new StartTransaction(OCPP_ID_OF_CONNECTOR, idTag)); if (onConf) startTransaction->setOnReceiveConfListener(onConf); if (onAbort) startTransaction->setOnAbortListener(onAbort); if (onTimeout) startTransaction->setOnTimeoutListener(onTimeout); if (onError) startTransaction->setOnReceiveErrorListener(onError); if (timeout) startTransaction->setTimeout(std::move(timeout)); else startTransaction->setTimeout(std::unique_ptr(new SuppressedTimeout())); ocppEngine->initiateOperation(std::move(startTransaction)); } void stopTransaction(OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto stopTransaction = makeOcppOperation( new StopTransaction(OCPP_ID_OF_CONNECTOR)); if (onConf) stopTransaction->setOnReceiveConfListener(onConf); if (onAbort) stopTransaction->setOnAbortListener(onAbort); if (onTimeout) stopTransaction->setOnTimeoutListener(onTimeout); if (onError) stopTransaction->setOnReceiveErrorListener(onError); if (timeout) stopTransaction->setTimeout(std::move(timeout)); else stopTransaction->setTimeout(std::unique_ptr(new SuppressedTimeout())); ocppEngine->initiateOperation(std::move(stopTransaction)); } int getTransactionId() { if (!ocppEngine) { AO_DBG_WARN("Please call OCPP_initialize before"); return -1; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return -1; } return connector->getTransactionId(); } bool ocppPermitsCharge() { if (!ocppEngine) { AO_DBG_WARN("Please call OCPP_initialize before"); return false; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return false; } return connector->ocppPermitsCharge(); } bool isAvailable() { if (!ocppEngine) { AO_DBG_WARN("Please call OCPP_initialize before"); return true; //assume "true" as default state } auto& model = ocppEngine->getOcppModel(); auto chargePoint = model.getConnectorStatus(OCPP_ID_OF_CP); auto connector = model.getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!chargePoint || !connector) { AO_DBG_ERR("Could not find connector. Ignore"); return true; //assume "true" as default state } return (chargePoint->getAvailability() != AVAILABILITY_INOPERATIVE) && (connector->getAvailability() != AVAILABILITY_INOPERATIVE); } void beginSession(const char *idTag) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { AO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", IDTAG_LEN_MAX); return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return; } connector->beginSession(idTag); } void endSession() { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return; } connector->endSession(); } bool isInSession() { return getSessionIdTag() != nullptr; } const char *getSessionIdTag() { if (!ocppEngine) { AO_DBG_WARN("Please call OCPP_initialize before"); return nullptr; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); if (!connector) { AO_DBG_ERR("Could not find connector. Ignore"); return nullptr; } return connector->getSessionIdTag(); } #if defined(AO_CUSTOM_UPDATER) || defined(AO_CUSTOM_WS) ArduinoOcpp::FirmwareService *getFirmwareService() { auto& model = ocppEngine->getOcppModel(); return model.getFirmwareService(); } #endif #if defined(AO_CUSTOM_DIAGNOSTICS) || defined(AO_CUSTOM_WS) ArduinoOcpp::DiagnosticsService *getDiagnosticsService() { auto& model = ocppEngine->getOcppModel(); return model.getDiagnosticsService(); } #endif