Skip to content

Instantly share code, notes, and snippets.

@scottweston
Last active September 25, 2016 22:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scottweston/ee312e961ff2cc03d06b27e928bd850a to your computer and use it in GitHub Desktop.
Save scottweston/ee312e961ff2cc03d06b27e928bd850a to your computer and use it in GitHub Desktop.
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
"time"
"github.com/jsgoecke/tesla"
"github.com/spf13/viper"
"gopkg.in/redis.v4"
)
func makeTimeStamp() string {
return strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
}
func storeStatusInRedis(redis_client *redis.Client, chargeState *tesla.ChargeState) error {
if chargeState == nil || redis_client == nil {
return errors.New("nil argument passed")
}
stringChargeState := make(map[string]string)
stringChargeState["When"] = makeTimeStamp()
stringChargeState["ChargingState"] = chargeState.ChargingState
stringChargeState["ChargeLimitSoc"] = strconv.Itoa(chargeState.ChargeLimitSoc)
stringChargeState["BatteryLevel"] = strconv.Itoa(chargeState.BatteryLevel)
stringChargeState["UsableBatteryLevel"] = strconv.Itoa(chargeState.UsableBatteryLevel)
stringChargeState["ChargeCurrentRequest"] = strconv.Itoa(chargeState.ChargeCurrentRequest)
stringChargeState["ChargeCurrentRequestMax"] = strconv.Itoa(chargeState.ChargeCurrentRequestMax)
stringChargeState["BatteryRange"] = strconv.FormatFloat(chargeState.BatteryRange*1.60934, 'f', 2, 64)
stringChargeState["EstBatteryRange"] = strconv.FormatFloat(chargeState.EstBatteryRange*1.60934, 'f', 2, 64)
stringChargeState["IdealBatteryRange"] = strconv.FormatFloat(chargeState.IdealBatteryRange*1.60934, 'f', 2, 64)
stringChargeState["ChargeEnergyAdded"] = strconv.FormatFloat(chargeState.ChargeEnergyAdded, 'f', 2, 64)
stringChargeState["ChargeRangeAddedRated"] = strconv.FormatFloat(chargeState.ChargeMilesAddedRated*1.60934, 'f', 2, 64)
stringChargeState["ChargeRangeAddedIdeal"] = strconv.FormatFloat(chargeState.ChargeMilesAddedIdeal*1.60934, 'f', 2, 64)
stringChargeState["TimeToFullCharge"] = strconv.FormatFloat(chargeState.TimeToFullCharge, 'f', 2, 64)
stringChargeState["ChargeRate"] = strconv.FormatFloat(chargeState.ChargeRate, 'f', 2, 64)
stringChargeState["ChargePortDoorOpen"] = strconv.FormatBool(chargeState.ChargePortDoorOpen)
_, err := redis_client.HMSet("tesla_state", stringChargeState).Result()
if err != nil {
return (err)
}
return (nil)
}
func main() {
viper.SetConfigName("tesla")
viper.AddConfigPath("$HOME/.config/")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
redis_client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
_, err = redis_client.Ping().Result()
if err != nil {
redis_client = nil
}
client, err := tesla.NewClient(
&tesla.Auth{
ClientID: viper.GetString("client_id"),
ClientSecret: viper.GetString("client_secret"),
Email: viper.GetString("username"),
Password: viper.GetString("password"),
})
if err != nil {
panic(err)
}
vehicles, err := client.Vehicles()
if err != nil {
panic(err)
}
vehicle := vehicles[0]
if len(os.Args) == 2 {
if os.Args[1] == "start" {
vehicle.StartCharging()
} else if os.Args[1] == "stop" {
vehicle.StopCharging()
} else if os.Args[1] == "state" || os.Args[1] == "status" {
chargeState, _ := vehicle.ChargeState()
data, err := json.Marshal(chargeState)
if err != nil {
panic(err)
}
fmt.Println(string(data))
if redis_client != nil {
err = storeStatusInRedis(redis_client, chargeState)
if err != nil {
panic(err)
}
}
} else {
i, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println("Usage: tesla [state|start|stop|50-100]")
} else {
chargeState, err := vehicle.ChargeState()
if err != nil {
panic(err)
}
if chargeState.ChargeLimitSoc != i {
err := vehicle.SetChargeLimit(i)
if err != nil {
panic(err)
}
chargeState, err = vehicle.ChargeState()
if err != nil {
panic(err)
}
}
data, err := json.Marshal(chargeState)
if err != nil {
panic(err)
}
fmt.Println(string(data))
if redis_client != nil {
err = storeStatusInRedis(redis_client, chargeState)
if err != nil {
panic(err)
}
}
}
}
} else {
fmt.Println("Usage: tesla_charge [start|stop]")
}
}

Tesla Range Watch Complication

My Tesla's range (199.86kms in this screenshot) is always displayed in the black circle top right, it updates every 15m.

Ingredients

How it works

Every 15m my home automation system queries my Tesla to collect data using the GoLang library and as part of that it makes the most recent snapshot of Tesla's state available via a redis store as a hash. This snapshot data is made available via my home automation system using a simple REST endpoint.

Tasker

Created a task that performs the following steps:

  • HTTP Get
    • %API_URL/get_hash/tesla_range/IdealBatteryRange
    • I could use any of the Tesla variables (such as BatteryLevel) but I like using the ideal range as it matches what is displayed on the dash when driving
  • Variable Set
    • Name %TESLARANGE To %HTTPD
  • WM Send Variable (WatchMaker Plugin)
    • %TESLARANGE

Create a profile that runs the above task every 15m

WatchMaker

Add a text widget including the text {tteslarange} and then position and format it however you like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment