Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Current shaper module #382

Merged
merged 7 commits into from
Aug 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ OpenEVSE can post its status values to [emoncms.org](https://emoncms.org) or any

Data can be posted using HTTP or HTTPS.

### Current Shaper

OpenEVSE can shape charge current according to your real time house load, preventing to exceed the maximum power your energy plan can handle.
Once the module is toggled on, it will have highest priority to other claims.
However it's possible to temporary disable it if needed with HTTP or MQTT

**Note #1**: this service is dependant to MQTT, it needs a topic with the whole household live power in watts ( 'Live power load MQTT Topic' )
**Note #2**: set 'Max Power Allowed' according to your energy plan.


### MQTT

Refer to [MQTT API documentation](mqtt.md)
Expand Down
3 changes: 3 additions & 0 deletions models/Config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ x-examples:
mqtt_solar: ''
mqtt_grid_ie: emon/test/grid_ie
mqtt_vrms: emon/rightbar/voltage
mqtt_live_pwr: /live/power/topic
mqtt_vehicle_soc: ''
mqtt_vehicle_range: ''
mqtt_vehicle_eta: ''
Expand All @@ -59,6 +60,7 @@ x-examples:
divert_attack_smoothing_factor: 0.4
divert_decay_smoothing_factor: 0.05
divert_min_charge_time: 10
current_shaper_max_pwr: 9000
tesla_access_token: _DUMMY_PASSWORD
tesla_refresh_token: _DUMMY_PASSWORD
tesla_created_at: 2479357952
Expand All @@ -73,6 +75,7 @@ x-examples:
sntp_enabled: true
tesla_enabled: true
divert_enabled: true
current_shaper_enabled: true
pause_uses_disabled: false
mqtt_vehicle_range_miles: false
ocpp_enabled: true
Expand Down
15 changes: 14 additions & 1 deletion src/app_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "emoncms.h"
#include "input.h"
#include "LedManagerTask.h"
#include "current_shaper.h"

#include "app_config.h"
#include "app_config_mqtt.h"
Expand Down Expand Up @@ -47,6 +48,7 @@ String mqtt_pass;
String mqtt_solar;
String mqtt_grid_ie;
String mqtt_vrms;
String mqtt_live_pwr;
String mqtt_vehicle_soc;
String mqtt_vehicle_range;
String mqtt_vehicle_eta;
Expand All @@ -73,6 +75,9 @@ double divert_attack_smoothing_factor;
double divert_decay_smoothing_factor;
uint32_t divert_min_charge_time;

// Current Shaper settings
uint32_t current_shaper_max_pwr;

// Tesla Client settings
String tesla_access_token;
String tesla_refresh_token;
Expand Down Expand Up @@ -131,6 +136,7 @@ ConfigOpt *opts[] =
new ConfigOptDefenition<String>(mqtt_solar, "", "mqtt_solar", "mo"),
new ConfigOptDefenition<String>(mqtt_grid_ie, "emon/emonpi/power1", "mqtt_grid_ie", "mg"),
new ConfigOptDefenition<String>(mqtt_vrms, "emon/emonpi/vrms", "mqtt_vrms", "mv"),
new ConfigOptDefenition<String>(mqtt_live_pwr, "", "mqtt_live_pwr", "map"),
new ConfigOptDefenition<String>(mqtt_vehicle_soc, "", "mqtt_vehicle_soc", "mc"),
new ConfigOptDefenition<String>(mqtt_vehicle_range, "", "mqtt_vehicle_range", "mr"),
new ConfigOptDefenition<String>(mqtt_vehicle_eta, "", "mqtt_vehicle_eta", "met"),
Expand All @@ -151,6 +157,9 @@ ConfigOpt *opts[] =
new ConfigOptDefenition<double>(divert_decay_smoothing_factor, 0.05, "divert_decay_smoothing_factor", "dd"),
new ConfigOptDefenition<uint32_t>(divert_min_charge_time, (10 * 60), "divert_min_charge_time", "dt"),

// Current Shaper settings
new ConfigOptDefenition<uint32_t>(current_shaper_max_pwr, 0 , "current_shaper_max_pwr", "smp"),

// Tesla client settings
new ConfigOptSecret(tesla_access_token, "", "tesla_access_token", "tat"),
new ConfigOptSecret(tesla_refresh_token, "", "tesla_refresh_token", "trt"),
Expand Down Expand Up @@ -181,6 +190,7 @@ ConfigOpt *opts[] =
new ConfigOptVirtualBool(flagsOpt, CONFIG_SERVICE_SNTP, CONFIG_SERVICE_SNTP, "sntp_enabled", "se"),
new ConfigOptVirtualBool(flagsOpt, CONFIG_SERVICE_TESLA, CONFIG_SERVICE_TESLA, "tesla_enabled", "te"),
new ConfigOptVirtualBool(flagsOpt, CONFIG_SERVICE_DIVERT, CONFIG_SERVICE_DIVERT, "divert_enabled", "de"),
new ConfigOptVirtualBool(flagsOpt, CONFIG_SERVICE_CUR_SHAPER, CONFIG_SERVICE_CUR_SHAPER, "current_shaper_enabled", "cse"),
new ConfigOptVirtualBool(flagsOpt, CONFIG_PAUSE_USES_DISABLED, CONFIG_PAUSE_USES_DISABLED, "pause_uses_disabled", "pd"),
new ConfigOptVirtualBool(flagsOpt, CONFIG_VEHICLE_RANGE_MILES, CONFIG_VEHICLE_RANGE_MILES, "mqtt_vehicle_range_miles", "mvru"),
new ConfigOptVirtualBool(flagsOpt, CONFIG_SERVICE_OCPP, CONFIG_SERVICE_OCPP, "ocpp_enabled", "ope"),
Expand Down Expand Up @@ -250,6 +260,8 @@ void config_changed(String name)
DBUGVAR(config_divert_enabled());
DBUGVAR(config_charge_mode());
divertmode_update((config_divert_enabled() && 1 == config_charge_mode()) ? DIVERT_MODE_ECO : DIVERT_MODE_NORMAL);
} else if(name.startsWith("current_shaper_")) {
shaper.notifyConfigChanged(config_current_shaper_enabled()?1:0,current_shaper_max_pwr);
} else if(name == "tesla_vehicle_id") {
teslaClient.setVehicleId(tesla_vehicle_id);
} else if(name.startsWith("tesla_")) {
Expand Down Expand Up @@ -320,7 +332,7 @@ void config_save_emoncms(bool enable, String server, String node, String apikey,
}

void
config_save_mqtt(bool enable, int protocol, String server, uint16_t port, String topic, bool retained, String user, String pass, String solar, String grid_ie, bool reject_unauthorized)
config_save_mqtt(bool enable, int protocol, String server, uint16_t port, String topic, bool retained, String user, String pass, String solar, String grid_ie, String live_pwr, bool reject_unauthorized)
{
uint32_t newflags = flags & ~(CONFIG_SERVICE_MQTT | CONFIG_MQTT_PROTOCOL | CONFIG_MQTT_ALLOW_ANY_CERT);
if(enable) {
Expand All @@ -339,6 +351,7 @@ config_save_mqtt(bool enable, int protocol, String server, uint16_t port, String
config.set("mqtt_pass", pass);
config.set("mqtt_solar", solar);
config.set("mqtt_grid_ie", grid_ie);
config.set("mqtt_live_pwr", live_pwr);
config.set("flags", newflags);
config.commit();
}
Expand Down
13 changes: 11 additions & 2 deletions src/app_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ extern uint32_t divert_min_charge_time;
// Scheduler settings
extern uint32_t scheduler_start_window;

//Shaper settings
extern uint32_t current_shaper_max_pwr;

// 24-bits of Flags
extern uint32_t flags;

Expand All @@ -86,7 +89,9 @@ extern uint32_t flags;
#define CONFIG_OCPP_ACCESS_ENERGIZE (1 << 16)
#define CONFIG_VEHICLE_RANGE_MILES (1 << 17)
#define CONFIG_RFID (1 << 18)
#define CONFIG_MQTT_RETAINED (1 << 19)
#define CONFIG_SERVICE_CUR_SHAPER (1 << 19)
#define CONFIG_MQTT_RETAINED (1 << 20)


inline bool config_emoncms_enabled() {
return CONFIG_SERVICE_EMONCMS == (flags & CONFIG_SERVICE_EMONCMS);
Expand Down Expand Up @@ -136,6 +141,10 @@ inline bool config_divert_enabled() {
return CONFIG_SERVICE_DIVERT == (flags & CONFIG_SERVICE_DIVERT);
}

inline bool config_current_shaper_enabled() {
return CONFIG_SERVICE_CUR_SHAPER == (flags & CONFIG_SERVICE_CUR_SHAPER);
}

inline uint8_t config_charge_mode() {
return (flags & CONFIG_CHARGE_MODE) >> 10;
}
Expand Down Expand Up @@ -169,7 +178,7 @@ extern void config_save_emoncms(bool enable, String server, String node, String
// -------------------------------------------------------------------
// Save the MQTT broker details
// -------------------------------------------------------------------
extern void config_save_mqtt(bool enable, int protocol, String server, uint16_t port, String topic, bool retained, String user, String pass, String solar, String grid_ie, bool reject_unauthorized);
extern void config_save_mqtt(bool enable, int protocol, String server, uint16_t port, String topic, bool retained, String user, String pass, String solar, String grid_ie, String live_pwr, bool reject_unauthorized);

// -------------------------------------------------------------------
// Save the admin/web interface details
Expand Down
127 changes: 127 additions & 0 deletions src/current_shaper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "current_shaper.h"

//global instance
CurrentShaperTask shaper;

CurrentShaperTask::CurrentShaperTask() : MicroTasks::Task() {
_changed = false;
_enabled = false;
}

CurrentShaperTask::~CurrentShaperTask() {
// should be useless but just in case
evse.release(EvseClient_OpenEVSE_Shaper);
}

void CurrentShaperTask::setup() {

}

unsigned long CurrentShaperTask::loop(MicroTasks::WakeReason reason) {
if (_enabled) {
EvseProperties props;
if (_changed) {
props.setChargeCurrent(_chg_cur);
if (_chg_cur < evse.getMinCurrent() ) {
// pause temporary, not enough amps available
props.setState(EvseState::Disabled);
}
else {
props.setState(EvseState::None);
}
_changed = false;
_timer = millis();
evse.claim(EvseClient_OpenEVSE_Shaper,EvseManager_Priority_Safety, props);
StaticJsonDocument<128> event;
event["shaper"] = 1;
event["shaper_live_pwr"] = _live_pwr;
event["shaper_max_pwr"] = _max_pwr;
event["shaper_cur"] = _chg_cur;
event_send(event);
}
if (millis() - _timer > EVSE_SHAPER_FAILSAFE_TIME) {
//available power has not been updated since EVSE_SHAPER_FAILSAFE_TIME, pause charge
DBUGF("MQTT avl_pwr has not been updated in time, pausing charge");
props.setState(EvseState::Disabled);
evse.claim(EvseClient_OpenEVSE_Shaper,EvseManager_Priority_Limit, props);
StaticJsonDocument<128> event;
event["shaper"] = 1;
event["shaper_live_pwr"] = _live_pwr;
event["shaper_max_pwr"] = _max_pwr;
event["shaper_cur"] = _chg_cur;
event_send(event);
}
}


return EVSE_SHAPER_LOOP_TIME;
}

void CurrentShaperTask::begin(EvseManager &evse) {
this -> _timer = millis();
this -> _enabled = config_current_shaper_enabled();
this -> _evse = &evse;
this -> _max_pwr = current_shaper_max_pwr;
this -> _live_pwr = 0;
this -> _chg_cur = 0;
MicroTask.startTask(this);
StaticJsonDocument<128> event;
event["shaper"] = 1;
event_send(event);
}

void CurrentShaperTask::notifyConfigChanged( bool enabled, uint32_t max_pwr) {
DBUGF("CurrentShaper: got config changed");
_enabled = enabled;
_max_pwr = max_pwr;
if (!enabled) evse.release(EvseClient_OpenEVSE_Shaper);
StaticJsonDocument<128> event;
event["shaper"] = enabled;
event["shaper_max_pwr"] = max_pwr;
event_send(event);
}

void CurrentShaperTask::setMaxPwr(int max_pwr) {
_max_pwr = max_pwr;
shapeCurrent();
}

void CurrentShaperTask::setLivePwr(int live_pwr) {
_live_pwr = live_pwr;
shapeCurrent();
}

// temporary change Current Shaper state without changing configuration
void CurrentShaperTask::setState(bool state) {
_enabled = state;
if (!_enabled) {
//remove claim
evse.release(EvseClient_OpenEVSE_Shaper);

}
StaticJsonDocument<128> event;
event["shaper"] = state?1:0;
event_send(event);
}

void CurrentShaperTask::shapeCurrent() {
_chg_cur = round(((_max_pwr - _live_pwr) / evse.getVoltage()) + (evse.getAmps()));
_changed = true; // update claim in the loop
}

int CurrentShaperTask::getMaxPwr() {
return _max_pwr;
}
int CurrentShaperTask::getLivePwr() {
return _live_pwr;
}
uint8_t CurrentShaperTask::getChgCur() {
return _chg_cur;
}
bool CurrentShaperTask::getState() {
return _enabled;
}

bool CurrentShaperTask::isActive() {
return _evse->clientHasClaim(EvseClient_OpenEVSE_Shaper);
}
57 changes: 57 additions & 0 deletions src/current_shaper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#ifndef _OPENEVSE_CUR_SHAPER_H
#define _OPENEVSE_CUR_SHAPER_H

// Time between loop polls
#ifndef EVSE_SHAPER_LOOP_TIME
#define EVSE_SHAPER_LOOP_TIME 2000
#endif
#ifndef EVSE_SHAPER_FAILSAFE_TIME
#define EVSE_SHAPER_FAILSAFE_TIME 360000
#endif

#include "emonesp.h"
#include <MicroTasks.h>
#include "evse_man.h"
#include "mqtt.h"
#include "app_config.h"
#include "http_update.h"
#include "input.h"
#include "event.h"

class CurrentShaperTask: public MicroTasks::Task
{
private:
EvseManager *_evse;
bool _enabled;
bool _changed;
int _max_pwr; // total current available from the grid
int _live_pwr; // current available to EVSE
uint8_t _chg_cur; // calculated charge current to claim
uint32_t _timer;

protected:
void setup();
unsigned long loop(MicroTasks::WakeReason reason);
void shapeCurrent();


public:
CurrentShaperTask();
~CurrentShaperTask();
void begin(EvseManager &evse);

void setMaxPwr(int max_pwr);
void setLivePwr(int live_pwr);
void setState(bool state);
bool getState();
int getMaxPwr();
int getLivePwr();
uint8_t getChgCur();
bool isActive();

void notifyConfigChanged(bool enabled, uint32_t max_pwr);
};

extern CurrentShaperTask shaper;

#endif // CURRENT_SHAPER
8 changes: 5 additions & 3 deletions src/event_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void EventLog::begin()
}
}

void EventLog::log(EventType type, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode)
void EventLog::log(EventType type, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode, uint8_t shaper)
{
time_t now = time(NULL);
struct tm timeinfo;
Expand Down Expand Up @@ -123,6 +123,7 @@ void EventLog::log(EventType type, EvseState managerState, uint8_t evseState, ui
line["tp"] = temperature;
line["tm"] = temperatureMax;
line["dm"] = divertMode;
line["sh"] = shaper;

serializeJson(line, eventFile);
eventFile.println("");
Expand All @@ -136,7 +137,7 @@ void EventLog::log(EventType type, EvseState managerState, uint8_t evseState, ui
}
}

void EventLog::enumerate(uint32_t index, std::function<void(String time, EventType type, const String &logEntry, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode)> callback)
void EventLog::enumerate(uint32_t index, std::function<void(String time, EventType type, const String &logEntry, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode, uint8_t shaper)> callback)
{
String filename = filenameFromIndex(index);
File eventFile = LittleFS.open(filename);
Expand Down Expand Up @@ -168,8 +169,9 @@ void EventLog::enumerate(uint32_t index, std::function<void(String time, EventTy
double temperature = json["tp"];
double temperatureMax = json["tm"];
uint8_t divertMode = json["dm"];
uint8_t shaper = json["sh"];
KipK marked this conversation as resolved.
Show resolved Hide resolved

callback(time, type, line, managerState, evseState, evseFlags, pilot, energy, elapsed, temperature, temperatureMax, divertMode);
callback(time, type, line, managerState, evseState, evseFlags, pilot, energy, elapsed, temperature, temperatureMax, divertMode, shaper);
}
}
eventFile.close();
Expand Down
4 changes: 2 additions & 2 deletions src/event_log.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ class EventLog
return _max_log_index;
}

void log(EventType type, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode);
void enumerate(uint32_t index, std::function<void(String time, EventType type, const String &logEntry, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode)> callback);
void log(EventType type, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode, uint8_t shaper);
void enumerate(uint32_t index, std::function<void(String time, EventType type, const String &logEntry, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode, uint8_t shaper)> callback);
};


Expand Down
Loading