Wanted to get something out there. This reads the current charging rate and lets you set the target rate in amps.
Don't have a GitHub repo for this yet, need to resurrect my login credentials.
/**
*
* File: OpenEVSE.groovy
* Platform: Hubitat
*
* Requirements:
* 1) openEVSE EV Charger https://openevse.com/index.html
*
* Copyright (c)Tom Duffy
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Change History:
*
* Date Who What
* ---- --- ----
* 2022-06-28 Tom Duffy Original Creation
* Notes : does not do checksums, how could a TCP connection ever corrupt data?
*
*/
def version() {"v0.1"}
metadata {
definition (name: "OpenEVSE", namespace: "tomuo", author: "Tom") {
capability "Initialize"
capability "Refresh"
capability "PowerMeter"
capability "EnergyMeter"
capability "Presence Sensor" // used to determine is the openEVSE microcontroller is still reporting data or not
attribute "Status", "string"
attribute "Firmware", "string"
command "ChargeRate", [[name:"chargerate", type:"NUMBER", description:"Max Charge Amps"]]
}
preferences
{
input "deviceIP", "text", title: "openEVSE IP Address", description: "in form of 192.168.1.138", required: true, displayDuringSetup: true
input "pollingInterval", "number", title: "Polling Interval", description: "in seconds", range: "10..300", defaultValue: 15, displayDuringSetup: true
input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
}
}
def logsOff() {
log.warn "debug logging disabled..."
device.updateSetting("logEnable",[value:"false",type:"bool"])
}
def refresh() {
polling()
}
def installed() { // built in
initialize()
}
def updated() { // built in
log.info "updated() called"
unschedule()
if (logEnable) runIn(1800, logsOff)
initialize()
}
def initialize() { // built in
state.version = version()
log.info "initialize() called"
if (deviceIP) {
doRapi('GV') // Get Version
// doRapi('ST%2021%2010%208%2030') // sleep timer
polling()
}
else {
log.warn "Please enter the openEVSE IP Address and then click SAVE"
}
}
def polling()
{
getData()
runIn(pollingInterval, polling)
}
def getData()
{
doRapi('GG') // Get Gauges
doRapi('GS') // Get State
doRapi('GU') // Get Usage
}
void ChargeRate(amps)
{
log.info("charge ${amps}")
if (amps < 6) {
doRapi('FD') // disable
}
else {
doRapi('FE') // enable
doRapi('SC%20' + amps.toString())
}
polling()
}
def doRapi(query)
{
def params = [
uri: "http://${deviceIP}/r?json=1&rapi=\$${query}",
contentType: "application/json",
requestContentType: "application/json",
timeout: 5
]
if (deviceIP) {
try {
httpGet(params){response ->
if(response.status != 200) {
if (device.currentValue("presence") != "not present") {
sendEvent(name: "presence", value: "not present", descriptionText: "openEVSE not responding")
}
log.error "Received HTTP error ${response.status}. Check your IP Address and openEVSE!"
}
else {
if (device.currentValue("presence") != "present") {
sendEvent(name: "presence", value: "present", isStateChange: true, descriptionText: "openEVSE responding")
}
def (ret, checksum) = response.data.ret.tokenize('^') // ignoring checksum after the carat
cmd = response.data.cmd.substring(1)
switch (cmd) {
case "GG": // $GG -> $OK 9900 240000
def (status, milliamps, millivolts) = ret.tokenize(' ')
if (status == "\$OK") {
float amps = (milliamps as float) / 1000.0
float volts = (millivolts as float) / 1000.0
if (logEnable) {
log.debug "GG ${status} ${milliamps} ${millivolts} ${amps} ${volts}"
}
powerF = amps * volts
powerI = powerF.toInteger()
sendEvent(name: "power", value: powerI, unit: "W")
}
break
case "GS": // $GS -> $OK fe 10636 03 0200
def (status, evsestate, elapsed, pilotstate, vflags) = ret.tokenize(' ') // hex seconds hex hex
int flags = Integer.decode('0x' + vflags)
if (evsestate == "00") {
evsestate = "Unknown"
}
else if (evsestate == "fe") {
evsestate = "Sleeping"
}
else if (evsestate == "ff") {
evsestate = "Disabled"
}
else if (evsestate == "01") {
evsestate = "idle"
}
else if (evsestate == "02") {
evsestate = "ready"
}
else if (evsestate == "03") {
evsestate = "charging"
}
if (flags & 0x0100) {
evsestate += ", Connected"
}
else {
evsestate += ", No EV"
}
sendEvent(name: "Status", value: evsestate)
break
case "GV": // $OK firmware_version protocol_version
def (status, firmware, protocol) = ret.tokenize(' ')
if (status == "\$OK") {
sendEvent(name: "Firmware", value: firmware)
}
break
case "GM": // $OK voltcalefactor voltoffset
def (status, scalefactor, offset) = ret.tokenize(' ')
if (logEnable) {
log.debug "GM ${status} ${scalefactor} ${offset}"
}
break
case "GU": // OK Wattseconds Whacc
def (status, sessionwsecs, lifetimewh) = ret.tokenize(' ')
if (status == "\$OK") {
float session = (((sessionwsecs as float) / 3600.0) + 50) / 1000.0
session = session.round(1)
sendEvent(name: "energy", value: session, unit: "kWh")
float lifetime = (lifetimewh as int) / 1000.0
lifetime = lifetime.round(0)
if (logEnable) {
log.debug "Session ${session}kWh Lifetime ${lifetime}"
}
}
break;
case "SC": // Set Current.
case "FE": // Enable
case "FD": // Disable
default:
if (logEnable) {
log.debug "Unhandled response from openEVSE CMD = ${response.data.cmd} ret = ${ret}"
}
break
}
}
}
} catch (Exception e) {
if (device.currentValue("presence") != "not present") {
sendEvent(name: "presence", value: "not present", descriptionText: "Error trying to communicate with openEVSE device")
}
log.warn "openEVSE Server Returned: ${e}"
if (e == "java.net.NoRouteToHostException: No route to host (Host unreachable)") {
//Give the openEVSE extra time to recover
runIn((pollingInterval * 2), polling)
}
}
} else {
log.error "IP Address '${deviceIP}' is not properly formatted!"
}
}
def uninstalled()
{
unschedule()
}