battman 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Guardfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/battman.gemspec +31 -0
- data/battman_example.rb +57 -0
- data/lib/battman/acpi_battery.rb +64 -0
- data/lib/battman/battery.rb +101 -0
- data/lib/battman/dsl/every_block.rb +31 -0
- data/lib/battman/dsl/watch_block.rb +20 -0
- data/lib/battman/dsl.rb +48 -0
- data/lib/battman/errors.rb +12 -0
- data/lib/battman/smapi_battery.rb +59 -0
- data/lib/battman/version.rb +3 -0
- data/lib/battman.rb +8 -0
- data/spec/lib/battman/acpi_battery_spec.rb +232 -0
- data/spec/lib/battman/battery_spec.rb +111 -0
- data/spec/lib/battman/dsl/every_block_spec.rb +62 -0
- data/spec/lib/battman/dsl/watch_block_spec.rb +27 -0
- data/spec/lib/battman/dsl_spec.rb +137 -0
- data/spec/lib/battman/smapi_battery_spec.rb +173 -0
- data/spec/spec_helper.rb +83 -0
- metadata +204 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5fa62d0122ed26fd4632d0052367a1cd815b3d87
|
4
|
+
data.tar.gz: fb5d61ca34fc8d864e2536c4cfcf340c8399777c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae56456c53bbcd44cd51539bbcce3cf1d8b3171ab1300c07b502c4ab58cf3d382a455f8e84c957db582201632d33e313a536248cc42ffe87253b6206fc0f7218
|
7
|
+
data.tar.gz: 48bc96909040afcc6b246d7265abecdc2e4c80a80ac32798c68b4457ffcb1795d9c7ecee188bb20a5c5227aea4531a4e59868b9d06474a4adb4c7e16dae51d1b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
5
|
+
# rspec may be run, below are examples of the most common uses.
|
6
|
+
# * bundler: 'bundle exec rspec'
|
7
|
+
# * bundler binstubs: 'bin/rspec'
|
8
|
+
# * spring: 'bin/rsspec' (This will use spring if running and you have
|
9
|
+
# installed the spring binstubs per the docs)
|
10
|
+
# * zeus: 'zeus rspec' (requires the server to be started separetly)
|
11
|
+
# * 'just' rspec: 'rspec'
|
12
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
13
|
+
watch(%r{^spec/.+_spec\.rb$})
|
14
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
15
|
+
watch('spec/spec_helper.rb') { "spec" }
|
16
|
+
end
|
17
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Joakim Reinert
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Battman
|
2
|
+
|
3
|
+
Battman allows specifying actions to perform depending on the value of some battery attributes
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'battman'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install battman
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it ( https://github.com/[my-github-username]/battman/fork )
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/battman.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'battman/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "battman"
|
8
|
+
spec.version = Battman::VERSION
|
9
|
+
spec.authors = ["Joakim Reinert"]
|
10
|
+
spec.email = ["mail@jreinert.com"]
|
11
|
+
spec.summary = %q{A simple dsl for polling battery info}
|
12
|
+
spec.description = %q{Battman allows specifying actions to perform depending on the value of some battery attribute}
|
13
|
+
spec.homepage = "https://github.com/jreinert/battman"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "guard-rspec"
|
25
|
+
spec.add_development_dependency "pry"
|
26
|
+
spec.add_development_dependency "pry-doc"
|
27
|
+
spec.add_development_dependency "pry-byebug"
|
28
|
+
spec.add_development_dependency "simplecov"
|
29
|
+
|
30
|
+
spec.add_dependency "activesupport"
|
31
|
+
end
|
data/battman_example.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'battman'
|
2
|
+
|
3
|
+
Battman::Battman.new do |battman|
|
4
|
+
|
5
|
+
battman.watch :acpi do |watcher|
|
6
|
+
|
7
|
+
current_state = :discharging # to be on the safe side
|
8
|
+
|
9
|
+
watcher.every 2.seconds do |status|
|
10
|
+
|
11
|
+
status.check(:state) do |value, last_value|
|
12
|
+
current_state = value
|
13
|
+
state_changed = value != last_value
|
14
|
+
|
15
|
+
if state_changed
|
16
|
+
`notify-send 'Battery is #{current_state}'`
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
watcher.every 60.seconds do |status|
|
23
|
+
|
24
|
+
suspend = false
|
25
|
+
|
26
|
+
status.check(:remaining_percent) do |value|
|
27
|
+
|
28
|
+
if current_state == :discharging && value.in?(0...5)
|
29
|
+
`i3lock && systemctl suspend` if suspend
|
30
|
+
`notify-send -u critical 'Battery is critical'`
|
31
|
+
|
32
|
+
if value.in?(0..2)
|
33
|
+
`notify-send -u critical 'Suspending in 60 seconds! (plug in cable to abort)'`
|
34
|
+
suspend = true
|
35
|
+
end
|
36
|
+
else
|
37
|
+
supend = false
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
watcher.every 5.minutes do |status|
|
45
|
+
|
46
|
+
status.check(:remaining_percent) do |value|
|
47
|
+
if current_state == :discharging && value.in?(5...10)
|
48
|
+
`notify-send -u normal 'Battery is low'`
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
battman.run
|
57
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'battman/battery'
|
2
|
+
|
3
|
+
module Battman
|
4
|
+
class AcpiBattery < Battery
|
5
|
+
|
6
|
+
def initialize(index = 0, **opts)
|
7
|
+
super(index)
|
8
|
+
|
9
|
+
@precision = opts[:precision] || 1000
|
10
|
+
end
|
11
|
+
|
12
|
+
def path
|
13
|
+
@path ||= "/sys/class/power_supply/BAT#{@index}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def remaining_percent
|
17
|
+
energy_full_file = File.join(path, 'energy_full')
|
18
|
+
energy_now_file = File.join(path, 'energy_now')
|
19
|
+
|
20
|
+
energy_full = File.read(energy_full_file)
|
21
|
+
energy_now = File.read(energy_now_file)
|
22
|
+
|
23
|
+
(energy_now.to_f / energy_full.to_f) * 100
|
24
|
+
end
|
25
|
+
|
26
|
+
def power
|
27
|
+
power_now_file = File.join(path, 'power_now')
|
28
|
+
|
29
|
+
power = File.read(power_now_file).to_f / (1000 * @precision)
|
30
|
+
|
31
|
+
state == :discharging ? -1 * power : power
|
32
|
+
end
|
33
|
+
|
34
|
+
def state
|
35
|
+
state_file = File.join(path, 'status')
|
36
|
+
|
37
|
+
state = File.read(state_file).chomp.downcase.to_sym
|
38
|
+
|
39
|
+
state == :unknown ? :idle : state
|
40
|
+
end
|
41
|
+
|
42
|
+
def remaining_energy
|
43
|
+
energy_file = File.join(path, 'energy_now')
|
44
|
+
|
45
|
+
File.read(energy_file).to_f / (1000 * @precision)
|
46
|
+
end
|
47
|
+
|
48
|
+
def remaining_running_time
|
49
|
+
raise WrongStateError if state != :discharging
|
50
|
+
(remaining_energy / power) * 60
|
51
|
+
end
|
52
|
+
|
53
|
+
def full_energy
|
54
|
+
energy_file = File.join(path, 'energy_full')
|
55
|
+
|
56
|
+
File.read(energy_file).to_f / (1000 * @precision)
|
57
|
+
end
|
58
|
+
|
59
|
+
def remaining_charging_time
|
60
|
+
raise WrongStateError if state != :charging
|
61
|
+
((full_energy - remaining_energy) / power) * 60
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'battman/errors'
|
2
|
+
require 'active_support/core_ext/object/inclusion'
|
3
|
+
require 'active_support/core_ext/numeric/time'
|
4
|
+
|
5
|
+
module Battman
|
6
|
+
class Battery
|
7
|
+
|
8
|
+
CONVERSIONS = {
|
9
|
+
power: {
|
10
|
+
watts: lambda {|value| value},
|
11
|
+
milliwatts: lambda {|value| value * 1000}
|
12
|
+
},
|
13
|
+
time: {
|
14
|
+
seconds: lambda {|value| value},
|
15
|
+
minutes: lambda {|value| value.to_f / 1.minute},
|
16
|
+
hours: lambda {|value| value.to_f / 1.hour}
|
17
|
+
},
|
18
|
+
energy: {
|
19
|
+
watt_hours: lambda {|value| value},
|
20
|
+
milliwatt_hours: lambda {|value| value * 1000}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
def initialize(battery_index = 0, **opts)
|
25
|
+
if self.class == Battery
|
26
|
+
raise AbstractError.new('cannot instantiate Battery')
|
27
|
+
end
|
28
|
+
|
29
|
+
@index = battery_index
|
30
|
+
end
|
31
|
+
|
32
|
+
def state
|
33
|
+
raise NotImplementedError.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def remaining_percent
|
37
|
+
raise NotImplementedError.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def power
|
41
|
+
raise NotImplementedError.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def power_in(unit)
|
45
|
+
unless unit.in?(CONVERSIONS[:power].keys)
|
46
|
+
raise UnsupportedUnitError.new(unit)
|
47
|
+
end
|
48
|
+
|
49
|
+
CONVERSIONS[:power][unit].call(power)
|
50
|
+
end
|
51
|
+
|
52
|
+
def remaining_running_time
|
53
|
+
raise NotImplementedError.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def remaining_running_time_in(unit)
|
57
|
+
unless unit.in?(CONVERSIONS[:time].keys)
|
58
|
+
raise UnsupportedUnitError.new(unit)
|
59
|
+
end
|
60
|
+
|
61
|
+
CONVERSIONS[:time][unit].call(remaining_running_time)
|
62
|
+
end
|
63
|
+
|
64
|
+
def remaining_charging_time
|
65
|
+
raise NotImplementedError.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def remaining_charging_time_in(unit)
|
69
|
+
unless unit.in?(CONVERSIONS[:time].keys)
|
70
|
+
raise UnsupportedUnitError.new(unit)
|
71
|
+
end
|
72
|
+
|
73
|
+
CONVERSIONS[:time][unit].call(remaining_charging_time)
|
74
|
+
end
|
75
|
+
|
76
|
+
def remaining_energy
|
77
|
+
raise NotImplementedError.new
|
78
|
+
end
|
79
|
+
|
80
|
+
def remaining_energy_in(unit)
|
81
|
+
unless unit.in?(CONVERSIONS[:energy].keys)
|
82
|
+
raise UnsupportedUnitError.new(unit)
|
83
|
+
end
|
84
|
+
|
85
|
+
CONVERSIONS[:energy][unit].call(remaining_energy)
|
86
|
+
end
|
87
|
+
|
88
|
+
def full_energy
|
89
|
+
raise NotImplementedError.new
|
90
|
+
end
|
91
|
+
|
92
|
+
def full_energy_in(unit)
|
93
|
+
unless unit.in?(CONVERSIONS[:energy].keys)
|
94
|
+
raise UnsupportedUnitError.new(unit)
|
95
|
+
end
|
96
|
+
|
97
|
+
CONVERSIONS[:energy][unit].call(full_energy)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Battman
|
2
|
+
module DSL
|
3
|
+
class EveryBlock
|
4
|
+
|
5
|
+
def initialize(battman, battery, interval)
|
6
|
+
@battery = battery
|
7
|
+
@interval = interval
|
8
|
+
@battman = battman
|
9
|
+
@last_values = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def check(attribute, *args, &block)
|
13
|
+
raise ArgumentError.new('no block given') unless block_given?
|
14
|
+
|
15
|
+
unless @battery.respond_to?(attribute)
|
16
|
+
raise ArgumentError.new("invalid method #{attribute}")
|
17
|
+
end
|
18
|
+
|
19
|
+
last_values_hash = (attribute.hash + args.hash).hash
|
20
|
+
|
21
|
+
task = Proc.new do
|
22
|
+
value = @battery.send(attribute, *args)
|
23
|
+
block.call(value, @last_values[last_values_hash])
|
24
|
+
@last_values[last_values_hash] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
@battman.register(@interval, task)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'battman/dsl/every_block'
|
2
|
+
|
3
|
+
module Battman
|
4
|
+
module DSL
|
5
|
+
class WatchBlock
|
6
|
+
|
7
|
+
def initialize(battman, battery)
|
8
|
+
@battman = battman
|
9
|
+
@battery = battery
|
10
|
+
end
|
11
|
+
|
12
|
+
def every(interval)
|
13
|
+
raise ArgumentError.new('no block given') unless block_given?
|
14
|
+
|
15
|
+
yield EveryBlock.new(@battman, @battery, interval)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/battman/dsl.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
require 'battman/dsl/watch_block'
|
3
|
+
|
4
|
+
module Battman
|
5
|
+
module DSL
|
6
|
+
Thread.abort_on_exception = true
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@blocks = Hash.new {|hash, key| hash[key] = []}
|
10
|
+
@intervals_due = Hash.new {|hash, key| hash[key] = 0}
|
11
|
+
|
12
|
+
yield self if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
def register(interval, block)
|
16
|
+
@blocks[interval.to_i] << block
|
17
|
+
@greatest_common_interval = @blocks.keys.inject(&:gcd)
|
18
|
+
end
|
19
|
+
|
20
|
+
def watch(type, index = 0, **opts)
|
21
|
+
raise ArgumentError.new('no block given') unless block_given?
|
22
|
+
|
23
|
+
require "battman/#{type}_battery"
|
24
|
+
battery_class = ("Battman::" + "#{type}_battery".camelize).constantize
|
25
|
+
|
26
|
+
yield WatchBlock.new(self, battery_class.new(index, **opts))
|
27
|
+
end
|
28
|
+
|
29
|
+
def run_once
|
30
|
+
@blocks.each do |interval, blocks|
|
31
|
+
if @intervals_due[interval] <= @greatest_common_interval
|
32
|
+
blocks.map(&:call)
|
33
|
+
@intervals_due[interval] = interval
|
34
|
+
else
|
35
|
+
@intervals_due[interval] -= @greatest_common_interval
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
loop do
|
42
|
+
run_once
|
43
|
+
sleep @greatest_common_interval
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Battman
|
2
|
+
class AbstractError < RuntimeError; end
|
3
|
+
class NotImplementedError < RuntimeError; end
|
4
|
+
class WrongStateError < RuntimeError; end
|
5
|
+
|
6
|
+
class UnsupportedUnitError < ArgumentError
|
7
|
+
def initialize(unit)
|
8
|
+
super("unit #{unit} is not supported")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'battman/battery'
|
2
|
+
|
3
|
+
module Battman
|
4
|
+
class SmapiBattery < Battery
|
5
|
+
|
6
|
+
def path
|
7
|
+
@path ||= "/sys/devices/platform/smapi/BAT#{@index}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def remaining_percent
|
11
|
+
percent_file = File.join(path, 'remaining_percent')
|
12
|
+
|
13
|
+
File.read(percent_file).to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
def remaining_running_time
|
17
|
+
running_time_file = File.join(path, 'remaining_running_time')
|
18
|
+
|
19
|
+
file_content = File.read(running_time_file)
|
20
|
+
|
21
|
+
raise WrongStateError if file_content == "not_discharging\n"
|
22
|
+
file_content.to_i * 60
|
23
|
+
end
|
24
|
+
|
25
|
+
def remaining_charging_time
|
26
|
+
charging_time_file = File.join(path, 'remaining_charging_time')
|
27
|
+
|
28
|
+
file_content = File.read(charging_time_file)
|
29
|
+
|
30
|
+
raise WrongStateError if file_content == "not_charging\n"
|
31
|
+
file_content.to_i * 60
|
32
|
+
end
|
33
|
+
|
34
|
+
def power
|
35
|
+
power_file = File.join(path, 'power_avg')
|
36
|
+
|
37
|
+
File.read(power_file).to_f / 1000
|
38
|
+
end
|
39
|
+
|
40
|
+
def state
|
41
|
+
state_file = File.join(path, 'state')
|
42
|
+
|
43
|
+
File.read(state_file).chomp.to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
def remaining_energy
|
47
|
+
energy_file = File.join(path, 'remaining_capacity')
|
48
|
+
|
49
|
+
File.read(energy_file).to_f / 1000
|
50
|
+
end
|
51
|
+
|
52
|
+
def full_energy
|
53
|
+
energy_file = File.join(path, 'last_full_capacity')
|
54
|
+
|
55
|
+
File.read(energy_file).to_f / 1000
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|