mirror of https://github.com/cjbassi/gotop.git
221 lines
5.3 KiB
Go
221 lines
5.3 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 (
|
|
"errors"
|
|
"math"
|
|
"sort"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
plist "howett.net/plist"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
type plistref struct {
|
|
pref_plist unsafe.Pointer
|
|
pref_len uint64
|
|
}
|
|
|
|
type values struct {
|
|
Description string `plist:"description"`
|
|
CurValue int `plist:"cur-value"`
|
|
MaxValue int `plist:"max-value"`
|
|
State string `plist:"state"`
|
|
Type string `plist:"type"`
|
|
}
|
|
|
|
type prop []values
|
|
|
|
type props map[string]prop
|
|
|
|
func readBytes(ptr unsafe.Pointer, length uint64) []byte {
|
|
buf := make([]byte, length-1)
|
|
var i uint64
|
|
for ; i < length-1; i++ {
|
|
buf[i] = *(*byte)(unsafe.Pointer(uintptr(ptr) + uintptr(i)))
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func readProps() (props, error) {
|
|
fd, err := unix.Open("/dev/sysmon", unix.O_RDONLY, 0777)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer unix.Close(fd)
|
|
|
|
var retptr plistref
|
|
|
|
if err = ioctl(fd, 0, 'E', unsafe.Sizeof(retptr), unsafe.Pointer(&retptr)); err != nil {
|
|
return nil, err
|
|
}
|
|
bytes := readBytes(retptr.pref_plist, retptr.pref_len)
|
|
|
|
var props props
|
|
if _, err = plist.Unmarshal(bytes, &props); err != nil {
|
|
return nil, err
|
|
}
|
|
return props, nil
|
|
}
|
|
|
|
func handleValue(val values, div float64, res *float64, amps *[]string) error {
|
|
if val.State == "invalid" || val.State == "unknown" {
|
|
return errors.New("Unknown value received")
|
|
}
|
|
|
|
*res = float64(val.CurValue) / div
|
|
|
|
if amps != nil && strings.HasPrefix(val.Type, "Amp") {
|
|
*amps = append(*amps, val.Description)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func deriveState(cr1, cr2 error, current float64, max int) (State, error) {
|
|
if cr1 == nil && cr2 != nil {
|
|
return Charging, nil
|
|
}
|
|
if cr1 != nil && cr2 == nil {
|
|
return Discharging, nil
|
|
}
|
|
if cr1 != nil && cr2 != nil && current == float64(max)/1000 {
|
|
return Full, nil
|
|
}
|
|
return Unknown, errors.New("Contradicting values received")
|
|
}
|
|
|
|
func handleVoltage(amps []string, b *Battery, e *ErrPartial) {
|
|
if e.DesignVoltage != nil && e.Voltage == nil {
|
|
b.DesignVoltage, e.DesignVoltage = b.Voltage, nil
|
|
}
|
|
|
|
for _, val := range amps {
|
|
switch val {
|
|
case "design cap":
|
|
if e.DesignVoltage == nil {
|
|
b.Design *= b.DesignVoltage
|
|
} else {
|
|
e.Design = e.DesignVoltage
|
|
}
|
|
case "last full cap":
|
|
if e.Voltage == nil {
|
|
b.Full *= b.Voltage
|
|
} else {
|
|
e.Full = e.Voltage
|
|
}
|
|
case "charge":
|
|
if e.Voltage == nil {
|
|
b.Current *= b.Voltage
|
|
} else {
|
|
e.Current = e.Voltage
|
|
}
|
|
case "charge rate", "discharge rate":
|
|
if e.Voltage == nil {
|
|
b.ChargeRate *= b.Voltage
|
|
} else {
|
|
e.ChargeRate = e.Voltage
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func sortFilterProps(props props) []string {
|
|
var keys []string
|
|
for key := range props {
|
|
if key[:7] != "acpibat" {
|
|
continue
|
|
}
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|
|
|
|
func convertBattery(prop prop) (*Battery, error) {
|
|
b := &Battery{}
|
|
e := ErrPartial{}
|
|
|
|
amps := []string{}
|
|
var cr1, cr2 error
|
|
var maxCharge int
|
|
|
|
for _, val := range prop {
|
|
switch val.Description {
|
|
case "voltage":
|
|
e.Voltage = handleValue(val, 1000000, &b.Voltage, nil)
|
|
case "design voltage":
|
|
e.DesignVoltage = handleValue(val, 1000000, &b.DesignVoltage, nil)
|
|
case "design cap":
|
|
e.Design = handleValue(val, 1000, &b.Design, &s)
|
|
case "last full cap":
|
|
e.Full = handleValue(val, 1000, &b.Full, &s)
|
|
case "charge":
|
|
e.Current = handleValue(val, 1000, &b.Current, &s)
|
|
maxCharge = val.MaxValue
|
|
case "charge rate":
|
|
cr1 = handleValue(val, 1000, &b.ChargeRate, &s)
|
|
case "discharge rate":
|
|
cr2 = handleValue(val, 1000, &b.ChargeRate, &s)
|
|
b.ChargeRate = math.Abs(b.ChargeRate)
|
|
}
|
|
}
|
|
|
|
b.State, e.State = deriveState(cr1, cr2, b.Current, maxCharge)
|
|
|
|
handleVoltage(amps, b, &e)
|
|
|
|
return b, e
|
|
}
|
|
|
|
func systemGet(idx int) (*Battery, error) {
|
|
props, err := readProps()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keys := sortFilterProps(props)
|
|
if idx >= len(keys) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return convertBattery(props[keys[idx]])
|
|
}
|
|
|
|
func systemGetAll() ([]*Battery, error) {
|
|
props, err := readProps()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keys := sortFilterProps(props)
|
|
batteries := make([]*Battery, len(keys))
|
|
errors := make(Errors, len(keys))
|
|
for i, key := range keys {
|
|
batteries[i], errors[i] = convertBattery(props[key])
|
|
}
|
|
|
|
return batteries, errors
|
|
}
|