Circuit Design
16A 3.7 kW EV charging controller board + Type 1 EV
This is the key component for controlling charge level and auto shut off at the end of the charge. Probably saves a wee bit of energy with the power to the EVSE disconected in the off state also.
Irms = emon1.calcIrms(1480); // Calculate Irms only
Kw = (Irms * 230.0) / 1000.0;
float hours = (millis() - lastMillis) / (1000.0 * 3600.0) ;
TotalChargeHours += hours;
lastMillis = millis();
//CALC KWH
if(abs(Kw) > 0.1)
KWh += (Kw * hours);
Note: You must add a suitable burden resistor if one is not on the CT board already or the high voltage will damage the Arduino. The one shown has a burden resistor.
| PCB size | 30.0mm X 24.0mm X 1.6mm |
| Compatible interfaces | 2.54 3-pin interface and 4-pin Grove interface |
| Transformation coefficient | 1000:1 |
| Input current | 0 - 5 A |
| Output current | 0 - 5 mA |
| Sampling resistor | 200Ω |
| Sampling voltage | 0 - 1 V |
| Working frequency | 20 - 20000 Hz |
| Working temperature | -55 - 85 ℃ |
| Dielectric strength | 6 - KAC/1min |
Arduino Wemos D1 Mini
// This example uses an Adafruit Huzzah ESP8266 // to connect to shiftr.io. // #include#include #include #include "EmonLib.h" // Include Emon Library #include EnergyMonitor emon1; // Create an instance double Irms = 0.0; double Kw = 0.0; #define REALY_PIN D1 #define LED_PIN LED_BUILTIN //LED_BUILTIN is built in LED const char ssid[] = "???????????????"; const char pass[] = "???????????????"; WiFiClient net; MQTTClient MQTTclient; String inputString = ""; // a String to hold incoming data bool stringComplete = false; // whether the string is complete unsigned long UpdateCount = 6; unsigned long PacketCount = 0; int RelayState = HIGH; unsigned long lastMillis = 0; unsigned long PowerOnMillis = 0; float KWh = 0.0; unsigned long ChargeKWh = 999; float TotalChargeHours = 0.0; // EEPROM data struct { float TotalKWh = 0.0; float TotalChargeHours = 0.0; unsigned long ChargeKWh = 999; } SavedData; uint addr = 0; void setup() { pinMode(REALY_PIN, OUTPUT); digitalWrite(REALY_PIN, RelayState); pinMode(LED_PIN, OUTPUT); emon1.current(0, 13.8875); //13.8875 or 111.1 // Current: input pin, calibration. // commit 512 bytes of ESP8266 flash (for "EEPROM" emulation) // this step actually loads the content (512 bytes) of flash into // a 512-byte-array cache in RAM EEPROM.begin(512); // read bytes (i.e. sizeof(data) from "EEPROM"), // in reality, reads from byte-array cache // cast bytes into structure called data EEPROM.get(addr,SavedData); Serial.println("Old values are: TotalKWh: "+String(SavedData.TotalKWh)+", ChargeKWh: "+ String(SavedData.ChargeKWh)); KWh = SavedData.TotalKWh; ChargeKWh = SavedData.ChargeKWh; TotalChargeHours = SavedData.TotalChargeHours; KWh = 0.0; PowerOnMillis = millis(); Serial.begin(115200); WiFi.begin(ssid, pass); // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino. // You need to set the IP address directly. MQTTclient.begin("192.168.1.71", net); MQTTclient.onMessage(messageReceived); WiFiConnect(); MQTTclient.publish("/charger/status", "run"); JSONVar myArray; myArray[0] = SavedData.TotalKWh; myArray[1] = SavedData.ChargeKWh; myArray[2] = SavedData.TotalChargeHours; String jsonString = JSON.stringify(myArray); MQTTclient.publish("/charger/eeprom", jsonString ); Serial.println("/charger/eeprom" + jsonString ); } void loop() { MQTTclient.loop(); delay(10); // <- -="" a="" client.connected="" every="" fixes="" if="" issues="" lastmillis="" message="" millis="" publish="" roughly="" second.="" some="" stability="" wifi="" wificonnect="" with=""> (UpdateCount * 1000)) { Irms = emon1.calcIrms(1480); // Calculate Irms only Kw = (Irms * 230.0) / 1000.0; float hours = (millis() - lastMillis) / (1000.0 * 3600.0) ; TotalChargeHours += hours; lastMillis = millis(); //CALC KWH if(abs(Kw) > 0.1) { KWh += (Kw * hours); SaveKWh(); if(ChargeKWh > 0) { if((unsigned long)KWh >= ChargeKWh) { RelayState = LOW; digitalWrite(REALY_PIN, RelayState); MQTTclient.publish("/charger/status", "charge complete " + String(KWh) ); } else { RelayState = HIGH; digitalWrite(REALY_PIN, RelayState); MQTTclient.publish("/charger/status", "charger ON"); } } } SendSensors(); } delay(100); } void WiFiConnect() { Serial.print("checking wifi..."); Serial.println(ssid); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); digitalWrite(LED_PIN, LOW); delay(500); digitalWrite(LED_PIN, HIGH); } Serial.println("\nWiFi Connected! "); Serial.println( WiFi.localIP()); Serial.print("\nMQTT connecting..."); while (!MQTTclient.connect(WiFi.localIP().toString().c_str() , "try", "try")) { Serial.print("."); delay(800); digitalWrite(LED_PIN, LOW); delay(200); digitalWrite(LED_PIN, HIGH); } Serial.println("\nMQTT Connected!"); MQTTclient.subscribe("/charger/control"); MQTTclient.publish("/charger/status", "start"); MQTTclient.publish("/charger/localip", "/charger/localip/" + WiFi.localIP().toString()); digitalWrite(LED_PIN, HIGH); } void messageReceived(String &topic, String &payload) { Serial.println("incoming: topic:" + topic + " Payload:" + payload); digitalWrite(LED_PIN, LOW); if(topic.startsWith("/charger/control")) { if(payload.startsWith("ChargeKWh")) { ChargeKWh = (unsigned long)payload.substring(10).toInt(); KWh = 0.0; TotalChargeHours = 0.0; SaveKWh(); if(ChargeKWh == 0) { RelayState = LOW; digitalWrite(REALY_PIN, RelayState); MQTTclient.publish("/charger/status", "charge complete " + String(KWh) ); } else { RelayState = HIGH; digitalWrite(REALY_PIN, RelayState); MQTTclient.publish("/charger/status", "charger ON"); } } else if(payload.startsWith("UpdateCount")) { UpdateCount = (long)payload.substring(12).toInt(); } else if(payload.startsWith("read")) { SendSensors(); MQTTclient.publish("/charger/localip", "/charger/localip/" + WiFi.localIP().toString()); } else if(topic.indexOf("control") > 0) { if(payload.startsWith("ON")) { PowerOnMillis = millis(); RelayState = HIGH; digitalWrite(REALY_PIN, RelayState); MQTTclient.publish("/charger/status", "charger ON"); } else if(payload.startsWith("OFF")) { PowerOnMillis = 0; RelayState = LOW; digitalWrite(REALY_PIN, RelayState); MQTTclient.publish("/charger/status", "charger OFF"); } SendSensors(); } MQTTclient.publish("/charger/status", topic + "/" + payload); digitalWrite(LED_PIN, HIGH); } } void SaveKWh() { SavedData.TotalKWh = KWh; SavedData.ChargeKWh = ChargeKWh; SavedData.TotalChargeHours = TotalChargeHours; // replace values in byte-array cache with modified data // no changes made to flash, all in local byte-array cache EEPROM.put(addr,SavedData); // actually write the content of byte-array cache to // hardware flash. flash write occurs if and only if one or more byte // in byte-array cache has been changed, but if so, ALL 512 bytes are // written to flash EEPROM.commit(); JSONVar myArray; myArray[0] = SavedData.TotalKWh; myArray[1] = SavedData.ChargeKWh; myArray[2] = SavedData.TotalChargeHours; String jsonString = JSON.stringify(myArray); MQTTclient.publish("/charger/eeprom", jsonString ); Serial.println("/charger/eeprom" + jsonString ); } void SendSensors() { digitalWrite(LED_PIN, LOW); int rssi = WiFi.RSSI(); JSONVar myArray; myArray[0] = PacketCount++; myArray[1] = Irms; myArray[2] = Kw; myArray[3] = RelayState; myArray[4] = KWh; myArray[5] = ChargeKWh; myArray[6] = rssi; String jsonString = JSON.stringify(myArray); MQTTclient.publish("/charger/sensors", jsonString ); Serial.println("/charger/sensors" + jsonString ); digitalWrite(LED_PIN, HIGH); } ->
Hubitat Dashboard
Hubitat Device
Hubitat Driver Code
metadata {
metadata {
definition(name: "Greenway MQTT Charger Driver", namespace: "Greenway", author: "Nick Goodey", importURL: "https://raw.githubusercontent.com/shomegit/MQTT-Virtual-Switch-Control-Driver/master/MQTT-Virtual-Switch-Control-Driver.groovy") {
capability "Initialize"
capability "Switch"
capability "Switch Level"
capability "Sensor"
capability "Polling"
capability "Battery"
command "on"
command "off"
command "ClearStates" // Clear all device states
attribute "switch", "string"
attribute "switch", "ENUM", ["on", "off"]
//CHARGER
attribute "ChargeAmps", "Number"
attribute "ChargeRate", "Number"
attribute "ChargeLimit", "Number"
attribute "ThisCharge", "Number"
attribute "ThisChargeKw", "Number"
attribute "ChargerTile", "String"
attribute "EnergyCost", "number"
}
preferences {
input name: "MQTTBroker", type: "text", title: "MQTT Broker Address:", required: true, displayDuringSetup: true
input name: "username", type: "text", title: "MQTT Username:", description: "(blank if none)", required: false, displayDuringSetup: true
input name: "password", type: "password", title: "MQTT Password:", description: "(blank if none)", required: false, displayDuringSetup: true
input name: "topicSub", type: "text", title: "Topic to Subscribe:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
input name: "topicCon", type: "text", title: "Topic to control:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
input name: "topicPoll", type: "text", title: "Topic to poll device:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
input("logEnable", "bool", title: "Enable logging", required: true, defaultValue: true)
input("DollarsPerKwh", "number", title: "Price of energy in NZ\$ per KWh", required: false, defaultValue: 0.08)
}
}
def installed() {
log.info "installed..."
}
def poll() {
try {
topic = settings?.topicPoll
if (logEnable) log.debug "Poll: $topic/read"
interfaces.mqtt.publish(topic, "read", 1, false)
} catch (e) {
log.error "Device Poll error: ${e.message}"
}
}
// Parse incoming device messages to generate events
def parse(String description) {
did = device.getId()
state.DeviceID = did
// parse message
mqtt = interfaces.mqtt.parseMessage(description)
if (logEnable) log.debug mqtt.topic
if (logEnable) log.debug mqtt.payload
state.topic = mqtt.topic
json = new groovy.json.JsonSlurper().parseText(mqtt.payload)
ParseCharger()
//log.debug "$topic"
//log.info "$topic OFF"
//RELAY
if (state.Relay != json[0]) {
state.Relay = json[0]
if (json[0] == 1) {
sendEvent(name: "switch", value: "on")
} else if (json[0] == 0) {
sendEvent(name: "switch", value: "off")
}
}
}
def ParseCharger() {
/*
CHARGER
myArray[0] = PacketCount++;
myArray[1] = Irms;
myArray[2] = Kw;
myArray[3] = RelayState;
myArray[4] = KWh;
myArray[5] = ChargeKWh;
myArray[6] = rssi;
*/
//log.debug json
//RELAY
if (state.Relay != json[3]) {
state.Relay = json[3]
if (json[3] == 1) {
sendEvent(name: "switch", value: "on")
} else if (json[3] == 0) {
sendEvent(name: "switch", value: "off")
}
}
if (json[3] == 1)
tileHTML = "Relay ON
"
else
tileHTML = "Relay OFF
"
//CHARGE RATE
String val = json[2]
String units = "KW"
Double dval = Double.parseDouble(val).round(3)
//ChargeKW = dval;
sendEvent(name: "ChargeRate", value: "$dval")
tileHTML += "Charge Rate $dval $units
"
//CHARGE LIMIT KW
val = json[5]
state.ChargeLimit = val
units = "%"
dval = ((Double.parseDouble(val) / 30) * 100).round(1)
sendEvent(name: "ChargeLimit", value: "$dval")
sendEvent(name: "level", value: dval)
tileHTML += "Add $dval %
"
//CHARGE TOTAL
val = json[4]
state.ThisCharge = val
units = "%"
dval = ((Double.parseDouble(val) / 30) * 100).round(1)
sendEvent(name: "ThisCharge", value: "$dval")
tileHTML += "Added $dval $units
"
dval = Double.parseDouble(val).round(1)
sendEvent(name: "ThisChargeKw", value: "$dval")
tileHTML += "Charge $dval KWh
"
//DollarsPerKwh =
DPKwh = Double.parseDouble(settings?.DollarsPerKwh)
units = "NZ\$"
dval = (Double.parseDouble(val) * DPKwh).round(2)
sendEvent(name: "EnergyCost", value: "$dval")
tileHTML += "Cost $units $dval
"
//CHARGE AMPS
val = json[1]
units = "A"
dval = Double.parseDouble(val).round(3)
sendEvent(name: "ChargeAmps", value: "$dval")
sendEvent(name: "ChargerTile", value: "$tileHTML")
}
def updated() {
if (logEnable) log.info "Updated..."
initialize()
}
def uninstalled() {
if (logEnable) log.info "Disconnecting from mqtt"
interfaces.mqtt.disconnect()
}
def initialize() {
if (logEnable) runIn(900, logsOff)
state.ThisCharge = 0
state.ChargeLimit = 0
state.Relay = -1
MQTTconnect()
unschedule()
schedule("0/10 * * * * ? *", MQTTconnect)
//schedule("0/10 * * * * ? *", ZeroDailyCounters)
schedule("0 1 0 1/1 * ? *", ZeroDailyCounters)
}
def ZeroDailyCounters() {
log.debug "ZeroDailyCounters"
}
def MQTTconnect() {
try {
def mqttInt = interfaces.mqtt
if (mqttInt.isConnected()) {
//log.info "Connected to: $MQTTBroker $topicSub"
return
}
def clientID = "hubitat-" + device.deviceNetworkId
state.clientID = clientID
//open connection
mqttbroker = "tcp://" + settings?.MQTTBroker + ":1883"
mqttInt.connect(mqttbroker, clientID, settings?.username, settings?.password)
//give it a chance to start
pauseExecution(500)
mqttInt.subscribe(settings?.topicSub)
log.info "Connection established: $MQTTBroker $topicSub"
log.info "clientID: $clientID"
log.info "Subscribed to: $topicSub"
} catch (e) {
log.error "MQTTconnect error: ${e.message}"
}
}
def mqttClientStatus(String status) {
log.error "MQTTStatus- error: ${status}"
}
def logsOff() {
log.warn "Debug logging disabled."
device.updateSetting("logEnable", [value: "false", type: "bool"])
}
def off() {
try {
topic = settings?.topicCon
log.info "$topic OFF"
interfaces.mqtt.publish(topic, "OFF", 1, false)
sendEvent(name: "switch", value: "off")
} catch (e) {
log.error "MQTTconnect error: ${e.message}"
}
}
def on() {
try {
topic = settings?.topicCon
log.info "$topic ON"
interfaces.mqtt.publish(topic, "ON", 1, false)
sendEvent(name: "switch", value: "on")
} catch (e) {
log.error "MQTTconnect error: ${e.message}"
}
}
def setLevel(value, rate = null) {
//e.g. ChargeKWh 3
kwh = ((value / 100) * 30);
String cmnd = "ChargeKWh $kwh"
log.debug cmnd
topic = settings?.topicCon
interfaces.mqtt.publish(topic, cmnd, 1, false)
topic = settings?.topicCon
interfaces.mqtt.publish(topic, "read", 1, false)
if (value == 0) {
sendEvent(name: "switch", value: "off")
}
sendEvent(name: "level", value: value)
}
private def displayDebugLog(message) {
if (logEnable) log.debug "${device.displayName}: ${message}"
}
private def displayInfoLog(message) {
log.info "${device.displayName}: ${message}"
}
/**
* Clear States
*
* Clears all device states
*
**/
def ClearStates() {
log.warn("ClearStates(): Clearing device states")
state.clear()
ZeroDailyCounters()
}







