mirror of https://github.com/cjbassi/gotop.git
259 lines
5.7 KiB
Go
259 lines
5.7 KiB
Go
// battery
|
|
// Copyright (C) 2016-2017 Karol 'Kenji Takahashi' Woźniak
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
package battery
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os/exec"
|
|
"strconv"
|
|
)
|
|
|
|
var errValueNotFound = fmt.Errorf("Value not found")
|
|
|
|
func readFloat(val string) (float64, error) {
|
|
num, err := strconv.ParseFloat(val, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if num == math.MaxUint32 {
|
|
return 0, fmt.Errorf("Unknown value received")
|
|
}
|
|
return num, nil
|
|
}
|
|
|
|
func readVoltage(val string) (float64, error) {
|
|
voltage, err := readFloat(val)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return voltage / 1000, nil
|
|
}
|
|
|
|
func readState(val string) (State, error) {
|
|
state, err := strconv.Atoi(val)
|
|
if err != nil {
|
|
return Unknown, err
|
|
}
|
|
|
|
switch {
|
|
case state&1 != 0:
|
|
return newState("Discharging")
|
|
case state&2 != 0:
|
|
return newState("Charging")
|
|
case state&4 != 0:
|
|
return newState("Empty")
|
|
default:
|
|
return Unknown, fmt.Errorf("Invalid state flag retrieved: `%d`", state)
|
|
}
|
|
}
|
|
|
|
type errParse int
|
|
|
|
func (p errParse) Error() string {
|
|
return fmt.Sprintf("Parse error: `%d`", p)
|
|
}
|
|
|
|
type batteryReader struct {
|
|
cmdout *bufio.Scanner
|
|
li int
|
|
lline []byte
|
|
e ErrPartial
|
|
}
|
|
|
|
func (r *batteryReader) setErrParse(n int) {
|
|
if r.e.Design == errValueNotFound {
|
|
r.e.Design = errParse(n)
|
|
}
|
|
if r.e.Full == errValueNotFound {
|
|
r.e.Full = errParse(n)
|
|
}
|
|
if r.e.Current == errValueNotFound {
|
|
r.e.Current = errParse(n)
|
|
}
|
|
if r.e.ChargeRate == errValueNotFound {
|
|
r.e.ChargeRate = errParse(n)
|
|
}
|
|
if r.e.State == errValueNotFound {
|
|
r.e.State = errParse(n)
|
|
}
|
|
if r.e.Voltage == errValueNotFound {
|
|
r.e.Voltage = errParse(n)
|
|
}
|
|
if r.e.DesignVoltage == errValueNotFound {
|
|
r.e.DesignVoltage = errParse(n)
|
|
}
|
|
}
|
|
|
|
func (r *batteryReader) readValue() (string, string, int) {
|
|
var piece []byte
|
|
if r.lline != nil {
|
|
piece = r.lline
|
|
r.lline = nil
|
|
} else {
|
|
pieces := bytes.Split(r.cmdout.Bytes(), []byte{':'})
|
|
if len(pieces) < 4 {
|
|
return "", "", 4
|
|
}
|
|
|
|
i, err := strconv.Atoi(string(pieces[1]))
|
|
if err != nil {
|
|
return "", "", 1
|
|
}
|
|
|
|
if i != r.li {
|
|
r.li = i
|
|
r.lline = pieces[3]
|
|
return "", "", 666
|
|
}
|
|
|
|
piece = pieces[3]
|
|
}
|
|
|
|
values := bytes.Split(piece, []byte{'\t'})
|
|
if len(values) < 2 {
|
|
return "", "", 2
|
|
}
|
|
return string(values[0]), string(values[1]), 0
|
|
}
|
|
|
|
func (r *batteryReader) readBattery() (*Battery, bool, bool) {
|
|
b := &Battery{}
|
|
var exists, amps bool
|
|
|
|
for r.cmdout.Scan() {
|
|
exists = true
|
|
|
|
name, value, errno := r.readValue()
|
|
if errno == 666 {
|
|
break
|
|
}
|
|
if errno != 0 {
|
|
r.setErrParse(errno)
|
|
continue
|
|
}
|
|
|
|
switch name {
|
|
case "bif_design_cap":
|
|
b.Design, r.e.Design = readFloat(value)
|
|
case "bif_last_cap":
|
|
b.Full, r.e.Full = readFloat(value)
|
|
case "bif_unit":
|
|
amps = value != "0"
|
|
case "bif_voltage":
|
|
b.DesignVoltage, r.e.DesignVoltage = readVoltage(value)
|
|
case "bst_voltage":
|
|
b.Voltage, r.e.Voltage = readVoltage(value)
|
|
case "bst_rem_cap":
|
|
b.Current, r.e.Current = readFloat(value)
|
|
case "bst_rate":
|
|
b.ChargeRate, r.e.ChargeRate = readFloat(value)
|
|
case "bst_state":
|
|
b.State, r.e.State = readState(value)
|
|
}
|
|
}
|
|
|
|
return b, amps, exists
|
|
}
|
|
|
|
func (r *batteryReader) next() (*Battery, error) {
|
|
r.e = ErrPartial{
|
|
Design: errValueNotFound,
|
|
Full: errValueNotFound,
|
|
Current: errValueNotFound,
|
|
ChargeRate: errValueNotFound,
|
|
State: errValueNotFound,
|
|
Voltage: errValueNotFound,
|
|
DesignVoltage: errValueNotFound,
|
|
}
|
|
|
|
b, amps, exists := r.readBattery()
|
|
|
|
if !exists {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
if r.e.DesignVoltage != nil && r.e.Voltage == nil {
|
|
b.DesignVoltage, r.e.DesignVoltage = b.Voltage, nil
|
|
}
|
|
|
|
if amps {
|
|
if r.e.DesignVoltage == nil {
|
|
b.Design *= b.DesignVoltage
|
|
} else {
|
|
r.e.Design = r.e.DesignVoltage
|
|
}
|
|
if r.e.Voltage == nil {
|
|
b.Full *= b.Voltage
|
|
b.Current *= b.Voltage
|
|
b.ChargeRate *= b.Voltage
|
|
} else {
|
|
r.e.Full = r.e.Voltage
|
|
r.e.Current = r.e.Voltage
|
|
r.e.ChargeRate = r.e.Voltage
|
|
}
|
|
}
|
|
|
|
if b.State == Unknown && r.e.Current == nil && r.e.Full == nil && b.Current >= b.Full {
|
|
b.State, r.e.State = newState("Full")
|
|
}
|
|
|
|
return b, r.e
|
|
}
|
|
|
|
func newBatteryReader() (*batteryReader, error) {
|
|
out, err := exec.Command("kstat", "-p", "-m", "acpi_drv", "-n", "battery B*").Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &batteryReader{cmdout: bufio.NewScanner(bytes.NewReader(out))}, nil
|
|
}
|
|
|
|
func systemGet(idx int) (*Battery, error) {
|
|
br, err := newBatteryReader()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return br.next()
|
|
}
|
|
|
|
func systemGetAll() ([]*Battery, error) {
|
|
br, err := newBatteryReader()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var batteries []*Battery
|
|
var errors Errors
|
|
for b, e := br.next(); e != io.EOF; b, e = br.next() {
|
|
batteries = append(batteries, b)
|
|
errors = append(errors, e)
|
|
}
|
|
|
|
return batteries, errors
|
|
}
|