Support NRGKick bluetooth (#120)

This commit is contained in:
andig 2020-05-12 11:24:03 +02:00 • committed by GitHub
parent a44f4242fa
commit 12ca72ec2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 586 additions and 22 deletions

View file

@ -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)

View file

@ -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":

14
charger/nrgble.go Normal file
View file

@ -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
}

View file

@ -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
}

65
charger/nrgble/service.go Normal file
View file

@ -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
}

365
charger/nrgble_linux.go Normal file
View file

@ -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
// }

View file

@ -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)

5
go.mod
View file

@ -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

21
go.sum
View file

@ -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=