Skip to content

Commit

Permalink
separate protocol and device
Browse files Browse the repository at this point in the history
  • Loading branch information
dehydr8 committed Jan 22, 2024
1 parent 87659ea commit 95edb1f
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 155 deletions.
119 changes: 119 additions & 0 deletions device/device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package device

import (
"crypto/rsa"
"encoding/base64"
"fmt"

"github.com/dehydr8/kasa-go/model"
"github.com/dehydr8/kasa-go/protocol"
)

type Device struct {
config *model.DeviceConfig
transport protocol.Protocol
}

type DeviceInfoResult struct {
DeviceId string `json:"device_id"`
DeviceOn bool `json:"device_on"`
Model string `json:"model"`
Type string `json:"type"`
Alias string `json:"nickname"`
Rssi int `json:"rssi"`
OnTime int `json:"on_time"`
SoftwareVersion string `json:"sw_ver"`
HardwareVersion string `json:"hw_ver"`
MAC string `json:"mac"`
Overheated bool `json:"overheated"`
PowerProtectionStatus string `json:"power_protection_status"`
OvercurrentStatus string `json:"overcurrent_status"`
SignalLevel int `json:"signal_level"`
SSID string `json:"ssid"`
}

type EnergyUsageResult struct {
CurrentPower int `json:"current_power"`
MonthEnergy int `json:"month_energy"`
MonthRuntime int `json:"month_runtime"`
TodayEnergy int `json:"today_energy"`
TodayRuntime int `json:"today_runtime"`
}

type EnergyUsageResponse struct {
protocol.AesProtoBaseResponse
Result EnergyUsageResult `json:"result"`
}

type DeviceInfoResponse struct {
protocol.AesProtoBaseResponse
Result DeviceInfoResult `json:"result"`
}

func NewDevice(key *rsa.PrivateKey, config *model.DeviceConfig) (*Device, error) {
transport, err := protocol.NewAesTransport(key, config)
if err != nil {
return nil, err
}

return &Device{
config: config,
transport: transport,
}, nil
}

func (d *Device) Address() string {
return d.config.Address
}

func (d *Device) GetEnergyUsage() (*EnergyUsageResult, error) {
var response EnergyUsageResponse
req := map[string]interface{}{
"method": "get_energy_usage",
}

err := d.transport.Send(&req, &response)

if err != nil {
return nil, err
}

if response.ErrorCode != 0 {
return nil, fmt.Errorf("error code: %d", response.ErrorCode)
}

return &response.Result, nil
}

func (d *Device) GetDeviceInfo() (*DeviceInfoResult, error) {
var response DeviceInfoResponse
req := map[string]interface{}{
"method": "get_device_info",
}

err := d.transport.Send(&req, &response)

if err != nil {
return nil, err
}

if response.ErrorCode != 0 {
return nil, fmt.Errorf("error code: %d", response.ErrorCode)
}

// try decoding nickname
nickname, err := base64.StdEncoding.DecodeString(response.Result.Alias)

if err == nil {
response.Result.Alias = string(nickname)
}

// try decoding ssid
ssid, err := base64.StdEncoding.DecodeString(response.Result.SSID)

if err == nil {
response.Result.SSID = string(ssid)
}

return &response.Result, nil
}
120 changes: 16 additions & 104 deletions exporter/exporter.go
Original file line number Diff line number Diff line change
@@ -1,67 +1,23 @@
package exporter

import (
"encoding/base64"
"fmt"

"github.com/dehydr8/kasa-go/protocol"
"github.com/dehydr8/kasa-go/device"
"github.com/dehydr8/kasa-go/logger"
"github.com/prometheus/client_golang/prometheus"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
)

var _ prometheus.Collector = (*PlugExporter)(nil)

type DeviceInfoResult struct {
DeviceId string `json:"device_id"`
DeviceOn bool `json:"device_on"`
Model string `json:"model"`
Type string `json:"type"`
Alias string `json:"nickname"`
Rssi int `json:"rssi"`
OnTime int `json:"on_time"`
SoftwareVersion string `json:"sw_ver"`
HardwareVersion string `json:"hw_ver"`
MAC string `json:"mac"`
Overheated bool `json:"overheated"`
PowerProtectionStatus string `json:"power_protection_status"`
OvercurrentStatus string `json:"overcurrent_status"`
SignalLevel int `json:"signal_level"`
SSID string `json:"ssid"`
}

type EnergyUsageResult struct {
CurrentPower int `json:"current_power"`
MonthEnergy int `json:"month_energy"`
MonthRuntime int `json:"month_runtime"`
TodayEnergy int `json:"today_energy"`
TodayRuntime int `json:"today_runtime"`
}

type EnergyUsageResponse struct {
protocol.AesProtoBaseResponse
Result EnergyUsageResult `json:"result"`
}

type DeviceInfoResponse struct {
protocol.AesProtoBaseResponse
Result DeviceInfoResult `json:"result"`
}

type PlugExporter struct {
target string
proto protocol.Protocol
device *device.Device

metricsUp,
metricsRssi,
metricsPowerLoad *prometheus.Desc

logger log.Logger
}

func NewPlugExporter(host string, proto protocol.Protocol, logger log.Logger) (*PlugExporter, error) {
info, err := collectDeviceInfo(proto)
func NewPlugExporter(device *device.Device) (*PlugExporter, error) {
info, err := device.GetDeviceInfo()

if err != nil {
return nil, err
Expand All @@ -75,8 +31,7 @@ func NewPlugExporter(host string, proto protocol.Protocol, logger log.Logger) (*
}

e := &PlugExporter{
target: host,
proto: proto,
device: device,
metricsPowerLoad: prometheus.NewDesc("kasa_power_load",
"Current power in Milliwatts (mW)",
nil, constLabels),
Expand All @@ -88,78 +43,35 @@ func NewPlugExporter(host string, proto protocol.Protocol, logger log.Logger) (*
metricsRssi: prometheus.NewDesc("kasa_rssi",
"Wifi received signal strength indicator",
nil, constLabels),

logger: logger,
}

return e, nil
}

func (k *PlugExporter) Collect(ch chan<- prometheus.Metric) {
logger.Debug("msg", "collecting metrics", "target", k.device.Address())

level.Debug(k.logger).Log("msg", "collecting metrics", "target", k.target)

var energyUsageResponse EnergyUsageResponse

if err := k.proto.Send(&map[string]interface{}{
"method": "get_energy_usage",
}, &energyUsageResponse); err == nil && energyUsageResponse.ErrorCode == 0 {
ch <- prometheus.MustNewConstMetric(k.metricsPowerLoad, prometheus.GaugeValue, float64(energyUsageResponse.Result.CurrentPower))
if energyUsage, err := k.device.GetEnergyUsage(); err == nil {
ch <- prometheus.MustNewConstMetric(k.metricsPowerLoad, prometheus.GaugeValue, float64(energyUsage.CurrentPower))
} else {
level.Debug(k.logger).Log("msg", "error getting energy usage", "err", err, "code", energyUsageResponse.ErrorCode)
logger.Warn("msg", "error getting energy usage", "err", err)
}

var deviceInfoResponse DeviceInfoResponse

if err := k.proto.Send(&map[string]interface{}{
"method": "get_device_info",
}, &deviceInfoResponse); err == nil && deviceInfoResponse.ErrorCode == 0 {

if deviceInfoResponse.Result.DeviceOn {
if deviceInfo, err := k.device.GetDeviceInfo(); err == nil {
if deviceInfo.DeviceOn {
ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue, 1)
} else {
ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue, 0)
}

ch <- prometheus.MustNewConstMetric(k.metricsRssi, prometheus.GaugeValue, float64(deviceInfoResponse.Result.Rssi))
ch <- prometheus.MustNewConstMetric(k.metricsRssi, prometheus.GaugeValue, float64(deviceInfo.Rssi))
} else {
level.Debug(k.logger).Log("msg", "error getting device info", "err", err, "code", deviceInfoResponse.ErrorCode)
logger.Warn("msg", "error getting device info", "err", err)
}
}

func (k *PlugExporter) Describe(ch chan<- *prometheus.Desc) {
ch <- k.metricsPowerLoad
}

func collectDeviceInfo(proto protocol.Protocol) (*DeviceInfoResult, error) {
var response DeviceInfoResponse
req := map[string]interface{}{
"method": "get_device_info",
}

err := proto.Send(&req, &response)

if err != nil {
return nil, err
}

if response.ErrorCode != 0 {
return nil, fmt.Errorf("error code: %d", response.ErrorCode)
}

// try decoding nickname
nickname, err := base64.StdEncoding.DecodeString(response.Result.Alias)

if err == nil {
response.Result.Alias = string(nickname)
}

// try decoding ssid
ssid, err := base64.StdEncoding.DecodeString(response.Result.SSID)

if err == nil {
response.Result.SSID = string(ssid)
}

return &response.Result, nil
ch <- k.metricsUp
ch <- k.metricsRssi
}
38 changes: 38 additions & 0 deletions logger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package logger

import (
"os"
"time"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
)

var (
GLOBAL_LOGGER log.Logger
)

func SetupLogging(lvl string) {
GLOBAL_LOGGER = log.NewLogfmtLogger(os.Stderr)
GLOBAL_LOGGER = level.NewFilter(GLOBAL_LOGGER, level.Allow(level.ParseDefault(lvl, level.InfoValue())))
GLOBAL_LOGGER = log.With(GLOBAL_LOGGER, "ts", log.TimestampFormat(
func() time.Time { return time.Now() },
time.DateTime,
), "caller", log.Caller(4))
}

func Info(keyvals ...interface{}) error {
return level.Info(GLOBAL_LOGGER).Log(keyvals...)
}

func Debug(keyvals ...interface{}) error {
return level.Debug(GLOBAL_LOGGER).Log(keyvals...)
}

func Warn(keyvals ...interface{}) error {
return level.Warn(GLOBAL_LOGGER).Log(keyvals...)
}

func Error(keyvals ...interface{}) error {
return level.Error(GLOBAL_LOGGER).Log(keyvals...)
}
Loading

0 comments on commit 95edb1f

Please sign in to comment.