diff --git a/README.md b/README.md index a975fdad1..55b917a31 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ EVCC is an extensible EV Charge Controller with PV integration implemented in [G ## Features - simple and clean user interface -- multiple [chargers](#charger): Wallbe, Phoenix (includes ESL Walli), go-eCharger, NRGKick, SimpleEVSE, EVSEWifi, KEBA/BMW, openWB, Mobile Charger Connect, any other charger using scripting +- multiple [chargers](#charger): Wallbe, Phoenix (includes ESL Walli), go-eCharger, NRGKick (Bluetooth and Connect), SimpleEVSE, EVSEWifi, KEBA/BMW, openWB, Mobile Charger Connect, any other charger using scripting - multiple [meters](#meter): ModBus (Eastron SDM, MPM3PM, SBC ALE3 and many more), SMA Home Manager and SMA Energy Meter, KOSTAL Smart Energy Meter (KSEM, EMxx), any Sunspec-compatible inverter or home battery devices (Fronius, SMA, SolarEdge, KOSTAL, STECA, E3DC), Tesla PowerWall - different [vehicles](#vehicle) to show battery status: Audi (eTron), BMW (i3), Tesla, Nissan (Leaf), any other vehicle using scripting - [plugins](#plugins) for integrating with hardware devices and home automation: Modbus (meters and grid inverters), MQTT and shell scripts @@ -136,7 +136,8 @@ Charger is responsible for handling EV state and adjusting charge current. Avail - `phoenix-evcc`: chargers with Phoenix EV-CC-AC1-M controllers (ModBus connection) - `simpleevse`: chargers with SimpleEVSE controllers connected via ModBus (e.g. OpenWB) - `evsewifi`: chargers with SimpleEVSE controllers using [SimpleEVSE-Wifi](https://github.com/CurtRod/SimpleEVSE-WiFi) -- `nrgkick`: NRGKick chargers with Connect module +- `nrgkick-bt`: NRGKick chargers with Bluetooth connector (Linux only, not supported on Docker) +- `nrgkick-connect`: NRGKick chargers with Connect module - `go-e`: go-eCharger chargers - `keba`: KEBA KeContact P20/P30 and BMW chargers (see [Preparation](#keba-preparation)) - `mcc`: Mobile Charger Connect devices (Audi, Bentley, Porsche) diff --git a/charger/config.go b/charger/config.go index 6bffb1406..cfb49b51c 100644 --- a/charger/config.go +++ b/charger/config.go @@ -22,8 +22,10 @@ func NewFromConfig(log *util.Logger, typ string, other map[string]interface{}) a c = NewPhoenixEMCPFromConfig(log, other) case "phoenix-evcc": c = NewPhoenixEVCCFromConfig(log, other) - case "nrgkick", "nrg", "kick": - c = NewNRGKickFromConfig(log, other) + case "nrgkick-bluetooth", "nrgkick-bt", "nrgble": + c = NewNRGKickBLEFromConfig(log, other) + case "nrgkick-connect", "nrgconnect": + c = NewNRGKickConnectFromConfig(log, other) case "go-e", "goe": c = NewGoEFromConfig(log, other) case "evsewifi": diff --git a/charger/nrgble.go b/charger/nrgble.go new file mode 100644 index 000000000..ae9fa064a --- /dev/null +++ b/charger/nrgble.go @@ -0,0 +1,14 @@ +// +build !linux + +package charger + +import ( + "github.com/andig/evcc/api" + "github.com/andig/evcc/util" +) + +// NewNRGKickBLEFromConfig creates a NRGKickBLE charger from generic config +func NewNRGKickBLEFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { + log.FATAL.Fatal("config: NRGKick bluetooth is only supported on linux") + return nil +} diff --git a/charger/nrgble/nrg_linux.go b/charger/nrgble/nrg_linux.go new file mode 100644 index 000000000..bf79b671d --- /dev/null +++ b/charger/nrgble/nrg_linux.go @@ -0,0 +1,91 @@ +package nrgble + +import ( + "errors" + "fmt" + "strings" + + "github.com/muka/go-bluetooth/api" + "github.com/muka/go-bluetooth/bluez/profile/adapter" + "github.com/muka/go-bluetooth/bluez/profile/agent" + "github.com/muka/go-bluetooth/bluez/profile/device" +) + +func FindDevice(a *adapter.Adapter1, hwaddr string) (*device.Device1, error) { + dev, err := Discover(a, hwaddr) + if err != nil { + return nil, err + } + if dev == nil { + return nil, errors.New("Device not found, is it advertising?") + } + + return dev, nil +} + +func Discover(a *adapter.Adapter1, hwaddr string) (*device.Device1, error) { + err := a.FlushDevices() + if err != nil { + return nil, err + } + + discovery, cancel, err := api.Discover(a, nil) + if err != nil { + return nil, err + } + + defer cancel() + + for ev := range discovery { + dev, err1 := device.NewDevice1(ev.Path) + if err != nil { + return nil, err1 + } + + if dev == nil || dev.Properties == nil { + continue + } + + p := dev.Properties + + if p.Address != hwaddr { + continue + } + + return dev, nil + } + + return nil, nil +} + +func Connect(dev *device.Device1, ag *agent.SimpleAgent, adapterID string) error { + props, err := dev.GetProperties() + if err != nil { + return fmt.Errorf("Failed to load props: %s", err) + } + + if props.Connected { + return nil + } + + if !props.Paired || !props.Trusted { + if err := dev.Pair(); err != nil { + return fmt.Errorf("Pair failed: %s", err) + } + + if err := agent.SetTrusted(adapterID, dev.Path()); err != nil { + return fmt.Errorf("Set trusted failed: %s", err) + } + } + + if !props.Connected { + err = dev.Connect() + if err != nil { + if !strings.Contains(err.Error(), "Connection refused") { + return fmt.Errorf("Connect failed: %s", err) + } + } + } + + return nil +} diff --git a/charger/nrgble/service.go b/charger/nrgble/service.go new file mode 100644 index 000000000..8a3b480f0 --- /dev/null +++ b/charger/nrgble/service.go @@ -0,0 +1,65 @@ +package nrgble + +const ( + InfoService = "8f75bba0-c903-11e4-9fe8-0002a5d6b15d" + EnergyService = "0379e580-ad1b-11e4-8bdd-0002a5d6b15d" + PowerService = "fd005380-b065-11e4-9ce2-0002a5d6b15d" + VoltageCurrentService = "171bad00-b066-11e4-aeda-0002a5d6b15d" + SettingsService = "14b3afc0-ad1b-11e4-baab-0002a5d6b15d" +) + +type Info struct { + Current int `struc:"int8"` // appInfo[0] + KWhPer100 int `struc:"int16"` // appInfo[1] / 10 + AmountPerKWh int `struc:"int8"` // appInfo[2] / 100 + FIEnabled int `struc:"int8"` // 1 == appInfo[3] + ErrorCode int `struc:"int8"` // appInfo[4] + Efficiency int `struc:"int8"` // appInfo[5] + ChargingActive bool `struc:"int8"` // 1 == appInfo[6] + PauseCharging bool `struc:"int8"` // 1 == appInfo[7] + ChargingCurrentMax int `struc:"int8"` // appInfo[8] + BLETransmissionPower int `struc:"int8"` // appInfo[9] +} + +type Energy struct { + TotalEnergy int `struc:"uint32"` // energie02[0] / 1000 + EnergyLastCharge int `struc:"uint32"` // energie02[1] / 1000 + Energy2ndLastCharge int `struc:"uint32"` // energie02[2] / 1000 + Energy3rdLastCharge int `struc:"uint32"` // energie02[3] / 1000 + ChargingEnergyLimit int `struc:"uint16"` // energie02[4] / 100 +} + +type Power struct { + TotalPower int `struc:"uint16"` // leistung[0] / 100 + L1 int `struc:"uint16"` // leistung[1] / 100 + L2 int `struc:"uint16"` // leistung[2] / 100 + L3 int `struc:"uint16"` // leistung[3] / 100 + PeakPower int `struc:"uint16"` // leistung[4] / 100; + Frequency int `struc:"uint16"` // leistung[5] / 100 + Temperature int `struc:"int16"` // leistung[6] + RemainingDistance int `struc:"uint16"` // leistung[7] / 10 + Costs int `struc:"uint16"` // leistung[8] / 100 + CPSignal int `struc:"int8"` // round(((leistung[9] << 8) / 100) + 0.4, 1) +} + +type VoltageCurrent struct { + VoltageL1 int `struc:"uint16"` // voltageAndCurrent[0] / 10 + VoltageL2 int `struc:"uint16"` // voltageAndCurrent[1] / 10 + VoltageL3 int `struc:"uint16"` // voltageAndCurrent[2] / 10 + CurrentL1 int `struc:"uint16"` // voltageAndCurrent[3] / 100 + CurrentL2 int `struc:"uint16"` // voltageAndCurrent[4] / 100 + CurrentL3 int `struc:"uint16"` // voltageAndCurrent[5] / 100 +} + +type Settings struct { + PIN int `struc:"uint16"` // H SettingsPIN + Current int `struc:"uint8"` // B SettingsChargingCurrentValue + ChargingEnergyLimit int `struc:"uint16"` // H SettingsChargingEnergyOff + KWhPer100 int `struc:"uint16"` // H round(SettingsKWhPer100Value * 10) + AmountPerKWh int `struc:"uint8"` // B round(SettingsAmountPerKWhValue * 100) + Pad int `struc:"[2]pad"` // xx + Efficiency int `struc:"uint8"` // B SettingsEfficacyValue + PauseCharging bool `struc:"uint8"` // B 1 if SettingsPauseCharging else 0 + BLETransmissionPower int `struc:"int8"` // b SettingsBLETransmissionPowerValue + PadTail int `struc:"[5]pad"` // xxxxx +} diff --git a/charger/nrgble_linux.go b/charger/nrgble_linux.go new file mode 100644 index 000000000..74f6bcbbb --- /dev/null +++ b/charger/nrgble_linux.go @@ -0,0 +1,365 @@ +package charger + +import ( + "bytes" + "fmt" + "os" + "strconv" + "time" + + "github.com/andig/evcc/api" + "github.com/andig/evcc/charger/nrgble" + "github.com/andig/evcc/util" + "github.com/godbus/dbus" + "github.com/lunixbochs/struc" + "github.com/muka/go-bluetooth/bluez/profile/adapter" + "github.com/muka/go-bluetooth/bluez/profile/agent" + "github.com/muka/go-bluetooth/bluez/profile/device" + "github.com/muka/go-bluetooth/hw" +) + +// NRGKickBLE charger implementation +type NRGKickBLE struct { + log *util.Logger + timer *time.Timer + adapter *adapter.Adapter1 + agent *agent.SimpleAgent + dev *device.Device1 + device string + macaddress string + pin int +} + +// NewNRGKickBLEFromConfig creates a NRGKickBLE charger from generic config +func NewNRGKickBLEFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { + cc := struct{ Device, MacAddress, PIN string }{ + Device: "hci0", + } + util.DecodeOther(log, other, &cc) + + // decode PIN with leading zero + pin, err := strconv.Atoi(cc.PIN) + if err != nil { + log.FATAL.Fatalf("config: invalid pin '%s'", cc.PIN) + } + + return NewNRGKickBLE(cc.Device, cc.MacAddress, pin) +} + +// NewNRGKickBLE creates NRGKickBLE charger +func NewNRGKickBLE(device, macaddress string, pin int) *NRGKickBLE { + logger := util.NewLogger("nrgbt") + logger.WARN.Println("-- experimental --") + + // set LE mode + btmgmt := hw.NewBtMgmt(device) + + if len(os.Getenv("DOCKER")) > 0 { + btmgmt.BinPath = "./bin/docker-btmgmt" + } + + err := btmgmt.SetPowered(false) + if err == nil { + err = btmgmt.SetLe(true) + if err == nil { + err = btmgmt.SetBredr(false) + if err == nil { + err = btmgmt.SetPowered(true) + } + } + } + + if err != nil { + logger.FATAL.Fatal(err) + } + + adapt, err := adapter.NewAdapter1FromAdapterID(device) + if err != nil { + logger.FATAL.Fatal(err) + } + + //Connect DBus System bus + conn, err := dbus.SystemBus() + if err != nil { + logger.FATAL.Fatal(err) + } + + // do not reuse agent0 from service + agent.NextAgentPath() + + ag := agent.NewSimpleAgent() + err = agent.ExposeAgent(conn, ag, agent.CapNoInputNoOutput, true) + if err != nil { + logger.FATAL.Fatal(err) + } + + nrg := &NRGKickBLE{ + log: logger, + timer: time.NewTimer(1), + device: device, + macaddress: macaddress, + pin: pin, + adapter: adapt, + agent: ag, + } + + return nrg +} + +func (nrg *NRGKickBLE) connect() (*device.Device1, error) { + dev, err := nrgble.FindDevice(nrg.adapter, nrg.macaddress) + if err != nil { + return nil, fmt.Errorf("findDevice: %s", err) + } + + err = nrgble.Connect(dev, nrg.agent, nrg.device) + if err != nil { + return nil, err + } + + return dev, nil +} + +func (nrg *NRGKickBLE) close() { + if nrg.dev != nil { + nrg.dev.Close() + nrg.dev = nil + } +} + +func (nrg *NRGKickBLE) waitTimer() { + <-nrg.timer.C +} + +func (nrg *NRGKickBLE) setTimer() { + nrg.timer.Stop() // can be stopped without reading as channel is always drained + nrg.timer.Reset(2 * time.Second) +} + +func (nrg *NRGKickBLE) read(service string) ([]byte, error) { + nrg.waitTimer() + defer nrg.setTimer() + + if nrg.dev == nil { + dev, err := nrg.connect() + if err != nil { + return []byte{}, err + } + nrg.dev = dev + } + + char, err := nrg.dev.GetCharByUUID(service) + if err != nil { + nrg.close() + return []byte{}, err + } + + b, err := char.ReadValue(map[string]interface{}{}) + if err != nil { + nrg.close() + return []byte{}, err + } + nrg.log.TRACE.Printf("read %s %0x", service, b) + + return b, nil +} + +func (nrg *NRGKickBLE) write(service string, b []byte) error { + nrg.waitTimer() + defer nrg.setTimer() + + if nrg.dev == nil { + dev, err := nrg.connect() + if err != nil { + return err + } + nrg.dev = dev + } + + nrg.log.TRACE.Printf("write %s %0x", service, b) + char, err := nrg.dev.GetCharByUUID(service) + if err != nil { + nrg.close() + return err + } + + if err := char.WriteValue(b, map[string]interface{}{}); err != nil { + nrg.close() + return err + } + + return nil +} + +func (nrg *NRGKickBLE) defaultSettings(info nrgble.Info) nrgble.Settings { + return nrgble.Settings{ + PIN: nrg.pin, + ChargingEnergyLimit: 19997, // magic const for "disable" + KWhPer100: info.KWhPer100, + AmountPerKWh: info.AmountPerKWh, + Efficiency: info.Efficiency, + PauseCharging: info.PauseCharging, + BLETransmissionPower: info.BLETransmissionPower, + } +} + +// Status implements the Charger.Status interface +func (nrg *NRGKickBLE) Status() (api.ChargeStatus, error) { + b, err := nrg.read(nrgble.PowerService) + if err != nil { + return api.StatusF, err + } + + res := nrgble.Power{} + if err := struc.Unpack(bytes.NewReader(b), &res); err != nil { + return api.StatusF, err + } + nrg.log.TRACE.Printf("power: %+v", res) + + CPSignal := float64(res.CPSignal<<8)/100 + 0.4 + + if CPSignal >= 1.3 && CPSignal <= 7.5 { + return api.StatusC, nil + } + + if CPSignal >= 1.3 && CPSignal <= 10.5 { + return api.StatusB, nil + } + + return api.StatusA, nil +} + +// Enabled implements the Charger.Enabled interface +func (nrg *NRGKickBLE) Enabled() (bool, error) { + b, err := nrg.read(nrgble.InfoService) + if err != nil { + nrg.log.TRACE.Println(err) + return false, err + } + + res := nrgble.Info{} + if err := struc.Unpack(bytes.NewReader(b), &res); err != nil { + return false, err + } + nrg.log.TRACE.Printf("info: %+v", res) + + return !res.PauseCharging, nil +} + +// Enable implements the Charger.Enable interface +func (nrg *NRGKickBLE) Enable(enable bool) error { + b, err := nrg.read(nrgble.InfoService) + if err != nil { + return err + } + + res := nrgble.Info{} + if err := struc.Unpack(bytes.NewReader(b), &res); err != nil { + return err + } + nrg.log.TRACE.Printf("info: %+v", res) + + settings := nrg.defaultSettings(res) + settings.PauseCharging = !enable + + var out bytes.Buffer + if err := struc.Pack(&out, &settings); err != nil { + return err + } + + return nrg.write(nrgble.SettingsService, out.Bytes()) +} + +// MaxCurrent implements the Charger.MaxCurrent interface +func (nrg *NRGKickBLE) MaxCurrent(current int64) error { + b, err := nrg.read(nrgble.InfoService) + if err != nil { + return err + } + + res := nrgble.Info{} + if err := struc.Unpack(bytes.NewReader(b), &res); err != nil { + return err + } + nrg.log.TRACE.Printf("info: %+v", res) + + settings := nrg.defaultSettings(res) + settings.Current = int(current) + + var out bytes.Buffer + if err := struc.Pack(&out, &settings); err != nil { + return err + } + + return nrg.write(nrgble.SettingsService, out.Bytes()) +} + +// CurrentPower implements the Meter interface. +func (nrg *NRGKickBLE) CurrentPower() (float64, error) { + b, err := nrg.read(nrgble.PowerService) + if err != nil { + return 0, err + } + + res := nrgble.Power{} + if err := struc.Unpack(bytes.NewReader(b), &res); err != nil { + return 0, err + } + nrg.log.TRACE.Printf("power: %+v", res) + + return float64(res.TotalPower) * 10, nil +} + +// TotalEnergy implements the MeterEnergy interface. +func (nrg *NRGKickBLE) TotalEnergy() (float64, error) { + b, err := nrg.read(nrgble.EnergyService) + if err != nil { + return 0, err + } + + res := nrgble.Energy{} + if err := struc.Unpack(bytes.NewReader(b), &res); err != nil { + return 0, err + } + nrg.log.TRACE.Printf("energy: %+v", res) + + return float64(res.TotalEnergy) / 1000, nil +} + +// Currents implements the MeterCurrent interface. +func (nrg *NRGKickBLE) Currents() (float64, float64, float64, error) { + b, err := nrg.read(nrgble.VoltageCurrentService) + if err != nil { + return 0, 0, 0, err + } + + res := nrgble.VoltageCurrent{} + if err := struc.Unpack(bytes.NewReader(b), &res); err != nil { + return 0, 0, 0, err + } + nrg.log.TRACE.Printf("voltage/current: %+v", res) + + return float64(res.CurrentL1) / 100, + float64(res.CurrentL2) / 100, + float64(res.CurrentL3) / 100, + nil +} + +// ChargedEnergy implements the ChargeRater interface. +// NOTE: apparently shows energy of a stopped charging session, hence substituted by TotalEnergy +// func (nrg *NRGKickBLE) ChargedEnergy() (float64, error) { +// nrg.log.TRACE.Print("read EnergyService") +// b, err := nrg.read(nrgble.EnergyService) +// if err != nil { +// return 0, err +// } + +// res := nrgble.Energy{} +// if err := struc.Unpack(bytes.NewReader(b), &res); err != nil { +// return 0, err +// } + +// nrg.log.TRACE.Printf("energy: %+v", res) + +// return float64(res.EnergyLastCharge) / 1000, nil +// } diff --git a/charger/nrgkick.go b/charger/nrgconnect.go similarity index 74% rename from charger/nrgkick.go rename to charger/nrgconnect.go index b33856924..f205c2d0b 100644 --- a/charger/nrgkick.go +++ b/charger/nrgconnect.go @@ -57,26 +57,26 @@ type NRGDeviceMetadata struct { Password string } -// NRGKick charger implementation -type NRGKick struct { +// NRGKickConnect charger implementation +type NRGKickConnect struct { *util.HTTPHelper IP string MacAddress string Password string } -// NewNRGKickFromConfig creates a NRGKick charger from generic config -func NewNRGKickFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +// NewNRGKickConnectFromConfig creates a NRGKickConnect charger from generic config +func NewNRGKickConnectFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { cc := struct{ IP, MacAddress, Password string }{} util.DecodeOther(log, other, &cc) - return NewNRGKick(cc.IP, cc.MacAddress, cc.Password) + return NewNRGKickConnect(cc.IP, cc.MacAddress, cc.Password) } -// NewNRGKick creates NRGKick charger -func NewNRGKick(IP, MacAddress, Password string) *NRGKick { - nrg := &NRGKick{ - HTTPHelper: util.NewHTTPHelper(util.NewLogger("kick")), +// NewNRGKickConnect creates NRGKickConnect charger +func NewNRGKickConnect(IP, MacAddress, Password string) *NRGKickConnect { + nrg := &NRGKickConnect{ + HTTPHelper: util.NewHTTPHelper(util.NewLogger("nrgc")), IP: IP, MacAddress: MacAddress, Password: Password, @@ -87,11 +87,11 @@ func NewNRGKick(IP, MacAddress, Password string) *NRGKick { return nrg } -func (nrg *NRGKick) apiURL(api apiFunction) string { +func (nrg *NRGKickConnect) apiURL(api apiFunction) string { return fmt.Sprintf("%s/api/%s/%s", nrg.IP, api, nrg.MacAddress) } -func (nrg *NRGKick) getJSON(url string, result interface{}) error { +func (nrg *NRGKickConnect) getJSON(url string, result interface{}) error { b, err := nrg.GetJSON(url, result) if err != nil && len(b) > 0 { var error NRGResponse @@ -105,7 +105,7 @@ func (nrg *NRGKick) getJSON(url string, result interface{}) error { return err } -func (nrg *NRGKick) putJSON(url string, request interface{}) error { +func (nrg *NRGKickConnect) putJSON(url string, request interface{}) error { b, err := nrg.PutJSON(url, request) if err != nil && len(b) == 0 { return err @@ -120,12 +120,12 @@ func (nrg *NRGKick) putJSON(url string, request interface{}) error { } // Status implements the Charger.Status interface -func (nrg *NRGKick) Status() (api.ChargeStatus, error) { +func (nrg *NRGKickConnect) Status() (api.ChargeStatus, error) { return api.StatusC, nil } // Enabled implements the Charger.Enabled interface -func (nrg *NRGKick) Enabled() (bool, error) { +func (nrg *NRGKickConnect) Enabled() (bool, error) { var settings NRGSettings err := nrg.getJSON(nrg.apiURL(apiSettings), settings) @@ -133,7 +133,7 @@ func (nrg *NRGKick) Enabled() (bool, error) { } // Enable implements the Charger.Enable interface -func (nrg *NRGKick) Enable(enable bool) error { +func (nrg *NRGKickConnect) Enable(enable bool) error { settings := NRGSettings{} settings.Values.DeviceMetadata.Password = nrg.Password settings.Values.ChargingStatus.Charging = &enable @@ -142,7 +142,7 @@ func (nrg *NRGKick) Enable(enable bool) error { } // MaxCurrent implements the Charger.MaxCurrent interface -func (nrg *NRGKick) MaxCurrent(current int64) error { +func (nrg *NRGKickConnect) MaxCurrent(current int64) error { settings := NRGSettings{} settings.Values.DeviceMetadata.Password = nrg.Password settings.Values.ChargingCurrent.Value = float64(current) @@ -151,7 +151,7 @@ func (nrg *NRGKick) MaxCurrent(current int64) error { } // CurrentPower implements the Meter interface. -func (nrg *NRGKick) CurrentPower() (float64, error) { +func (nrg *NRGKickConnect) CurrentPower() (float64, error) { var measurements NRGMeasurements err := nrg.getJSON(nrg.apiURL(apiMeasurements), measurements) @@ -159,7 +159,7 @@ func (nrg *NRGKick) CurrentPower() (float64, error) { } // ChargedEnergy implements the ChargeRater interface. -func (nrg *NRGKick) ChargedEnergy() (float64, error) { +func (nrg *NRGKickConnect) ChargedEnergy() (float64, error) { var measurements NRGMeasurements err := nrg.getJSON(nrg.apiURL(apiMeasurements), measurements) diff --git a/go.mod b/go.mod index 29c402cca..80ef2b784 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/benbjohnson/clock v1.0.0 github.com/eclipse/paho.mqtt.golang v1.2.0 github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible + github.com/godbus/dbus v4.1.0+incompatible github.com/golang/mock v1.4.3 github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.4 @@ -21,10 +22,14 @@ require ( github.com/joeshaw/carwings v0.0.0-20191118152321-61b46581307a github.com/jsgoecke/tesla v0.0.0-20190206234002-112508e1374e github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 + github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42 github.com/mitchellh/mapstructure v1.3.0 github.com/mjibson/esc v0.2.0 + github.com/muka/go-bluetooth v0.0.0-20200505070311-0c1993e4ff21 github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da github.com/pkg/errors v0.9.1 + github.com/prometheus/common v0.4.0 + github.com/smartystreets/assertions v1.0.0 // indirect github.com/spf13/cobra v1.0.0 github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.6.3 diff --git a/go.sum b/go.sum index 2d01219cc..967544130 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,10 @@ github.com/alecthomas/participle v0.4.4 h1:ZhPi9STNpixhITQx0RuoA/3UqCMu3cLsJVCfp github.com/alecthomas/participle v0.4.4/go.mod h1:HfdmEuwvr12HXQN44HPWXR0lHmVolVYe4dyL6lQ3duY= github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alvaroloes/enumer v1.1.2 h1:5khqHB33TZy1GWCO/lZwcroBFh7u+0j40T83VUbfAMY= github.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo= @@ -47,6 +49,8 @@ github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJ github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -61,6 +65,8 @@ github.com/goburrow/modbus v0.1.0 h1:DejRZY73nEM6+bt5JSP6IsFolJ9dVcqxsYbpLbeW/ro github.com/goburrow/modbus v0.1.0/go.mod h1:Kx552D5rLIS8E7TyUwQ/UdHEqvX5T8tyiGBTlzMcZBg= github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA= github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA= +github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= +github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -71,6 +77,7 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -127,6 +134,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -137,6 +146,8 @@ github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2t github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= github.com/lestrrat-go/strftime v1.0.1 h1:o7qz5pmLzPDLyGW4lG6JvTKPUfTFXwe+vOamIYWtnVU= github.com/lestrrat-go/strftime v1.0.1/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= +github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42 h1:PzBD7QuxXSgSu61TKXxRwVGzWO5d9QZ0HxFFpndZMCg= +github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -157,6 +168,8 @@ github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/muka/go-bluetooth v0.0.0-20200505070311-0c1993e4ff21 h1:ivI663FFGVCmxfn2r/f+fDpYDE9/+IhO5f19OL0zgxg= +github.com/muka/go-bluetooth v0.0.0-20200505070311-0c1993e4ff21/go.mod h1:9Y4iuJfFe4N3afRt1qKpHU7vKUqyaWm/wJk2QEk6hgM= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da h1:qiPWuGGr+1GQE6s9NPSK8iggR/6x/V+0snIoOPYsBgc= github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da/go.mod h1:DvuJJ/w1Y59rG8UTDxsMk5U+UJXJwuvUgbiJSm9yhX8= @@ -180,6 +193,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -190,8 +204,12 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -222,6 +240,7 @@ github.com/stretchr/testify v1.5.0 h1:DMOzIV76tmoDNE9pX6RSN0aDtCYeCg5VueieJaAo1u github.com/stretchr/testify v1.5.0/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/suapapa/go_eddystone v0.0.0-20190827074641-8d8c1bb79363/go.mod h1:O/oFfbntg0b1z5NM/IGoTMKYPO3lkzPSA53E+J99lDU= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k= @@ -274,6 +293,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -308,6 +328,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=