Alexa Integration
Circuit Design
16A 3.7 kW EV charging controller board + Type 1 EV
Current Sensor
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()
}		







