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() }