1 /**
2  * battery-d - simple library for reading battery info on linux laptops.
3  * License: MIT
4  * Copyright: Copyright © 2017, Azbuka
5  * Authors: Azbuka
6  */
7 module battery.d;
8 
9 import core.time : Duration, dur;
10 
11 /**
12  * Current battery status
13  */
14 enum BatteryStatus : ubyte {
15 	/// Discharging
16 	DISCHARGING,
17 	/// Charging
18 	CHARGING,
19 	/// Full (100%)
20 	FULL
21 }
22 
23 /**
24  * Gets list of all aviable batteries in system
25  * Examples:
26  * ---
27  * import std.array;
28  * import battery.d;
29  * getBatteryList().array; // ["BAT0"], laptops in general have only 1 battery
30  * getBatteryList().front; // "BAT1"
31  * ---
32  * Returns: range of battery names
33  */
34 auto getBatteryList() {
35 	import std.file : dirEntries, SpanMode;
36 	import std.algorithm : map, filter, startsWith;
37 	import std.path : baseName;
38 	return "/sys/class/power_supply".dirEntries(SpanMode.shallow)
39 		.map!(a => a.name.baseName)
40 		.filter!(a => a.startsWith("BAT"));
41 }
42 
43 /**
44  * Battery exception. Thrown on errors.
45  */
46 class BatteryException : Exception {
47 	pure nothrow @nogc @safe this(string msg,
48 		string file = __FILE__,
49 		size_t line = __LINE__,
50 		Throwable next = null) {
51 			super(msg, file, line, next);
52 	}
53 }
54 
55 /**
56  * Main battery class
57  */
58 class Battery {
59 	private {
60 		string bname;
61 		string[string] rawdata;
62 		float lvl;
63 		Duration untilfull;
64 		Duration remaining;
65 		BatteryStatus stat;
66 	}
67 
68 	/**
69 	 * Constructor.
70 	 * Params:
71 	 *  battery_name = name of battery (example: BAT0)
72 	 * Throws: BatteryException if there is no such battery
73 	 */
74 	this(string battery_name) {
75 		import std.file : exists;
76 		if(!("/sys/class/power_supply/" ~ battery_name).exists)
77 			throw new BatteryException("There is no such battery");
78 		this.bname = battery_name;
79 		this.update;
80 	}
81 
82 	/**
83 	 * Cunstructor. Uses first battery, returned by `getBatteryList()`
84 	 * Throws: battery exception, if no batteries found
85 	 */
86 	this() {
87 		auto a = getBatteryList;
88 		if(a.empty)
89 			throw new BatteryException("Battery not found");
90 		this(a.front);
91 	}
92 
93 	/**
94 	 * Updates battery info
95 	 */
96 	void update() {
97 		import std.stdio : File;
98 		import std.array : split, replaceFirst;
99 		import std.conv : to;
100 		import core.exception : RangeError;
101 
102 		auto f = File("/sys/class/power_supply/" ~ this.bname ~ "/uevent");
103 		foreach(line; f.byLine) {
104 			auto s = line.split("=");
105 			this.rawdata[s[0]
106 				     .replaceFirst("POWER_SUPPLY_", "")
107 				     .to!string] = s[1].to!string;
108 		}
109 
110 		size_t rate, full, curr;
111 		try {
112 			rate = this.rawdata["CURRENT_NOW"].to!size_t;
113 			full = this.rawdata["CHARGE_FULL"].to!size_t;
114 			curr = this.rawdata["CHARGE_NOW"].to!size_t;
115 		} catch (RangeError e) {
116 			rate = this.rawdata["POWER_NOW"].to!size_t;
117 			full = this.rawdata["ENERGY_FULL"].to!size_t;
118 			curr = this.rawdata["ENERGY_NOW"].to!size_t;
119 		}
120 
121 		this.lvl = (float(curr) / full) * 100;
122 
123 		if(rate) {
124 			switch(this.rawdata["STATUS"]) {
125 			case "Discharging":
126 				this.remaining = ((float(curr) / rate) * 3600)
127 					.to!long
128 					.dur!"seconds";
129 				this.untilfull = Duration.zero;
130 				this.stat = BatteryStatus.DISCHARGING;
131 				break;
132 			case "Charging":
133 				this.remaining = Duration.zero;
134 				this.untilfull = ((float(full - curr) / rate) * 3600)
135 					.to!long
136 					.dur!"seconds";
137 				this.stat = BatteryStatus.CHARGING;
138 				break;
139 			case "Full":
140 				this.stat = BatteryStatus.FULL;
141 				goto default;
142 			default:
143 				this.remaining = Duration.zero;
144 				this.untilfull = Duration.zero;
145 				break;
146 			}
147 		} else {
148 			this.remaining = Duration.zero;
149 			this.untilfull = Duration.zero;
150 		}
151 	}
152 
153 	/**
154 	 * Current battery level. 0-100%
155 	 * Returns: current battery level in %
156 	 */
157 	float level() {
158 		return this.lvl;
159 	}
160 
161 	/**
162 	 * Time until battery is full.
163 	 * `Duration.zero` if battery full or discharging.
164 	 * Returns: time until battery is full
165 	 */
166 	Duration timeUntilFull() {
167 		return this.untilfull;
168 	}
169 
170 	/**
171 	 * Time remaining.
172 	 * `Duration.zero` if battery full or charging.
173 	 * Returns: time remaining
174 	 */
175 	Duration timeRemaining() {
176 		return this.remaining;
177 	}
178 
179 	/**
180 	 * Current battery status.
181 	 * Returns: battery status
182 	 */
183 	BatteryStatus status() {
184 		return this.stat;
185 	}
186 
187 	/**
188 	 * Raw battery data.
189 	 * Returns: raw battery data
190 	 */
191 	string[string] raw() {
192 		return this.rawdata;
193 	}
194 
195 	/**
196 	 * Battery name.
197 	 * Returns: battery name
198 	 */
199 	string name() {
200 		return this.bname;
201 	}
202 }