diff --git a/src/limit.cpp b/src/limit.cpp new file mode 100644 index 00000000..a1e7af40 --- /dev/null +++ b/src/limit.cpp @@ -0,0 +1,172 @@ +#if defined(ENABLE_DEBUG) && !defined(ENABLE_DEBUG_LIMIT) +#undef ENABLE_DEBUG +#endif + +#include "limit.h" +#include "debug.h" +#include +#include +#include +#include "evse_man.h" + +// --------------------------------------------- +// +// LimitProperties Class +// +//---------------------------------------------- +LimitProperties::LimitProperties() +{ + _type = LimitType::None; + _value = 0; + _auto_release = true; +} + +LimitProperties::~LimitProperties() +{ + DBUGLN("LimitProperties Destructor"); + +} + +void LimitProperties::init() +{ + _type = LimitType::None; + _value = 0; + _auto_release = true; +} + +LimitType LimitProperties::getType() { + return _type; +}; + +bool LimitProperties::setType(LimitType type) +{ + _type = type; + return true; +}; + +uint32_t LimitProperties::getValue() { + return _value; + }; + +bool LimitProperties::setValue(uint32_t value) +{ + _value = value; + return true; +}; + +bool LimitProperties::getAutoRelease() { + return _auto_release; +} + +bool LimitProperties::deserialize(JsonObject &obj) +{ + if(obj.containsKey("type")) { + _type.fromString(obj["type"]); + } + if(obj.containsKey("value")) { + _value = obj["value"]; + } + if(obj.containsKey("auto_release")) { + _auto_release = obj["auto_release"]; + } + return _type > 0 && _value > 0; + +} + +bool LimitProperties::serialize(JsonObject &obj) +{ + + obj["type"] = _type.toString(); + obj["value"] = _value; + obj["auto_release"] = _auto_release; + return true; +} + +// --------------------------------------------- +// +// Limit Class +// +//---------------------------------------------- + +//global instance +Limit limit; + +Limit::Limit() : Limit::Task() { + _limit_properties.init(); +} + +Limit::~Limit() { + _evse -> release(EvseClient_OpenEVSE_Limit); +} + +void Limit::setup() { + +} + +void Limit::begin(EvseManager &evse) { + // todo get saved default limit + DBUGLN("Starting Limit task"); + this -> _evse = &evse; + MicroTask.startTask(this); +} + +unsigned long Limit::loop(MicroTasks::WakeReason reason) { + + + if (hasLimit()) { + LimitType type = _limit_properties.getType(); + uint32_t value = _limit_properties.getValue(); + bool auto_release = _limit_properties.getAutoRelease(); + + if (_evse->isCharging() ) { + _has_vehicle = true; + bool limit_reached = false; + switch (type) { + case LimitType::Time: + limit_reached = limitTime(value); + break; + case LimitType::Energy: + limit_reached = limitEnergy(value); + break; + } + if (limit_reached) { + // Limit reached, disabling EVSE + if (_evse->getClaimProperties(EvseClient_OpenEVSE_Limit).getState() == EvseState::None) { + DBUGLN("Limit as expired, disable evse"); + EvseProperties props; + props.setState(EvseState::Disabled); + props.setAutoRelease(true); + _evse->claim(EvseClient_OpenEVSE_Limit, EvseManager_Priority_Limit, props); + } + } + + } + else if ( _has_vehicle && !_evse->isVehicleConnected()) { + _has_vehicle = false; + // if auto release is set, reset Limit properties + if (auto_release) { + _limit_properties.init(); + } + } + + } + + return EVSE_LIMIT_LOOP_TIME; +} + +bool Limit::limitTime(uint32_t val) { + if ( val != 0 && _evse->getSessionElapsed() > 0 && _evse->getSessionElapsed()/60 >= val ) { + // Time limit done + return false; + } + else return true; +} + +bool Limit::limitEnergy(uint32_t val) { + if ( val != 0 && _evse->getSessionEnergy() > 0 && (uint32_t)_evse->getSessionEnergy() >= val ) { + // Time limit done + return false; + } + else return true; +} + diff --git a/src/limit.h b/src/limit.h new file mode 100644 index 00000000..65fdd964 --- /dev/null +++ b/src/limit.h @@ -0,0 +1,144 @@ +#ifndef _OPENEVSE_LIMIT_H +#define _OPENEVSE_LIMIT_H + + +#ifndef EVSE_LIMIT_LOOP_TIME +#define EVSE_LIMIT_LOOP_TIME 1000 +#endif +#include +#include +#include +#include "evse_man.h" + +class LimitType { + + public: + enum Value : uint8_t { + None, + Time, + Energy, + Soc, + Range + }; + + LimitType() = default; + constexpr LimitType(Value value) : _value(value) { } + uint8_t fromString(const char *value) + { + // Cheat a bit and just check the first char + switch (value[0]) { + // None + case ('n'): + _value = LimitType::None; + break; + //Time + case ('t'): + _value = LimitType::Time; + break; + //Energy + case ('e'): + _value = LimitType::Energy; + break; + //Soc + case ('s'): + _value = LimitType::Soc; + break; + //Range + case ('r'): + _value = LimitType::Range; + break; + } + return _value; + } + + const char *toString() + { + return LimitType::None == _value ? "none" : + LimitType::Time == _value ? "time" : + LimitType::Energy == _value ? "energy" : + LimitType::Soc == _value ? "soc" : + LimitType::Range == _value ? "range" : + "unknown"; + } + + operator Value() const { return _value; } + explicit operator bool() = delete; // Prevent usage: if(state) + LimitType operator= (const Value val) { + _value = val; + return *this; + } + + private: + Value _value; +}; + +class LimitProperties : virtual public JsonSerialize<512> { + private: + LimitType _type; + uint32_t _value; + bool _auto_release; + public: + LimitProperties(); + ~LimitProperties(); + void init(); + bool setType(LimitType type); + bool setValue(uint32_t value); + LimitType getType(); + uint32_t getValue(); + bool getAutoRelease(); + + using JsonSerialize::deserialize; + virtual bool deserialize(JsonObject &obj); + using JsonSerialize::serialize; + virtual bool serialize(JsonObject &obj); + +}; + +class Limit: public MicroTasks::Task +{ + private: + EvseManager *_evse; + LimitProperties _limit_properties; + bool _has_vehicle; + bool limitTime(uint32_t val); + bool limitEnergy(uint32_t val); + bool limitSoc(uint32_t val); + bool limitRange(uint32_t val); + + protected: + void setup(); + unsigned long loop(MicroTasks::WakeReason reason); + + + public: + Limit(); + ~Limit(); + void begin(EvseManager &evse); + bool hasLimit() { + return _limit_properties.getType() != LimitType::None; + } + bool set(String json) { + LimitProperties props; + if (props.deserialize(json)) { + set(props); + return true; + } + else return false; + } + bool set(LimitProperties props) { + _limit_properties = props; + return true; + }; + bool clear() { + _limit_properties.init(); + return true; + } + LimitProperties getLimitProperties() { + return _limit_properties; + }; + + +}; + +extern Limit limit; +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 65fc109e..84b60645 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,6 +51,7 @@ #include "ocpp.h" #include "rfid.h" #include "current_shaper.h" +#include "limit.h" #if defined(ENABLE_PN532) #include "pn532.h" @@ -125,7 +126,7 @@ void setup() evse.begin(); scheduler.begin(); divert.begin(); - + limit.begin(evse); lcd.begin(evse, scheduler, manual); #if defined(ENABLE_PN532) pn532.begin(); diff --git a/src/web_server.cpp b/src/web_server.cpp index 25ecf13b..0064a39b 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -39,6 +39,7 @@ typedef const __FlashStringHelper *fstr_t; #include "rfid.h" #include "current_shaper.h" #include "evse_man.h" +#include "limit.h" MongooseHttpServer server; // Create class for Web server @@ -887,6 +888,78 @@ handleSchedulePlan(MongooseHttpServerRequest *request) request->send(response); } +//---------------------------------------------------------- +// +// Limit +// +//---------------------------------------------------------- + +void handleLimitGet(MongooseHttpServerRequest *request, MongooseHttpServerResponseStream *response) +{ + if(limit.hasLimit()) + { + limit.getLimitProperties().serialize(response); + } else { + response->setCode(404); + response->print("{\"msg\":\"No limit\"}"); + } +} + +void handleLimitPost(MongooseHttpServerRequest *request, MongooseHttpServerResponseStream *response) +{ + String body = request->body().toString(); + + if (limit.set(body)) { + response->setCode(201); + response->print("{\"msg\":\"Created\"}"); + // todo: mqtt_publish_limit(); // update limit props to mqtt + } else { + // unused for now + response->setCode(500); + response->print("{\"msg\":\"Failed to parse JSON\"}"); + } +} + +void handleLimitDelete(MongooseHttpServerRequest *request, MongooseHttpServerResponseStream *response) +{ + if(limit.hasLimit()) { + if (limit.clear()) { + response->setCode(200); + response->print("{\"msg\":\"Deleted\"}"); + mqtt_publish_override(); // update override state to mqtt + } else { + response->setCode(500); + response->print("{\"msg\":\"Failed to clear Limit\"}"); + } + } else { + response->setCode(404); + response->print("{\"msg\":\"No limit to clear\"}"); + } +} + +void handleLimit(MongooseHttpServerRequest *request) +{ + MongooseHttpServerResponseStream *response; + if(false == requestPreProcess(request, response)) { + return; + } + + if(HTTP_GET == request->method()) { + handleLimitGet(request, response); + } else if(HTTP_POST == request->method()) { + handleLimitPost(request, response); + } else if(HTTP_DELETE == request->method()) { + handleLimitDelete(request, response); + } else { + response->setCode(405); + response->print("{\"msg\":\"Method not allowed\"}"); + } + + request->send(response); +} + +//---------------------------------------------------------- + void handleOverrideGet(MongooseHttpServerRequest *request, MongooseHttpServerResponseStream *response) { if(manual.isActive()) @@ -1259,6 +1332,8 @@ web_server_setup() { server.on("/logs", handleEventLogs); + server.on("/limit", handleLimit); + // Simple Firmware Update Form server.on("/update$")-> onRequest(handleUpdateRequest)->