battery.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. 'use strict';
  2. // @ts-check;
  3. // ==================================================================================
  4. // battery.js
  5. // ----------------------------------------------------------------------------------
  6. // Description: System Information - library
  7. // for Node.js
  8. // Copyright: (c) 2014 - 2022
  9. // Author: Sebastian Hildebrandt
  10. // ----------------------------------------------------------------------------------
  11. // License: MIT
  12. // ==================================================================================
  13. // 6. Battery
  14. // ----------------------------------------------------------------------------------
  15. const exec = require('child_process').exec;
  16. const fs = require('fs');
  17. const util = require('./util');
  18. let _platform = process.platform;
  19. const _linux = (_platform === 'linux' || _platform === 'android');
  20. const _darwin = (_platform === 'darwin');
  21. const _windows = (_platform === 'win32');
  22. const _freebsd = (_platform === 'freebsd');
  23. const _openbsd = (_platform === 'openbsd');
  24. const _netbsd = (_platform === 'netbsd');
  25. const _sunos = (_platform === 'sunos');
  26. function parseWinBatteryPart(lines, designedCapacity, fullChargeCapacity) {
  27. const result = {};
  28. let status = util.getValue(lines, 'BatteryStatus', ':').trim();
  29. // 1 = "Discharging"
  30. // 2 = "On A/C"
  31. // 3 = "Fully Charged"
  32. // 4 = "Low"
  33. // 5 = "Critical"
  34. // 6 = "Charging"
  35. // 7 = "Charging High"
  36. // 8 = "Charging Low"
  37. // 9 = "Charging Critical"
  38. // 10 = "Undefined"
  39. // 11 = "Partially Charged"
  40. if (status >= 0) {
  41. const statusValue = status ? parseInt(status) : 0;
  42. result.status = statusValue;
  43. result.hasBattery = true;
  44. result.maxCapacity = fullChargeCapacity || parseInt(util.getValue(lines, 'DesignCapacity', ':') || 0);
  45. result.designedCapacity = parseInt(util.getValue(lines, 'DesignCapacity', ':') || designedCapacity);
  46. result.voltage = parseInt(util.getValue(lines, 'DesignVoltage', ':') || 0) / 1000.0;
  47. result.capacityUnit = 'mWh';
  48. result.percent = parseInt(util.getValue(lines, 'EstimatedChargeRemaining', ':') || 0);
  49. result.currentCapacity = parseInt(result.maxCapacity * result.percent / 100);
  50. result.isCharging = (statusValue >= 6 && statusValue <= 9) || statusValue === 11 || (!(statusValue === 3) && !(statusValue === 1) && result.percent < 100);
  51. result.acConnected = result.isCharging || statusValue === 2;
  52. result.model = util.getValue(lines, 'DeviceID', ':');
  53. } else {
  54. result.status = -1;
  55. }
  56. return result;
  57. }
  58. module.exports = function (callback) {
  59. return new Promise((resolve) => {
  60. process.nextTick(() => {
  61. let result = {
  62. hasBattery: false,
  63. cycleCount: 0,
  64. isCharging: false,
  65. designedCapacity: 0,
  66. maxCapacity: 0,
  67. currentCapacity: 0,
  68. voltage: 0,
  69. capacityUnit: '',
  70. percent: 0,
  71. timeRemaining: null,
  72. acConnected: true,
  73. type: '',
  74. model: '',
  75. manufacturer: '',
  76. serial: ''
  77. };
  78. if (_linux) {
  79. let battery_path = '';
  80. if (fs.existsSync('/sys/class/power_supply/BAT1/uevent')) {
  81. battery_path = '/sys/class/power_supply/BAT1/';
  82. } else if (fs.existsSync('/sys/class/power_supply/BAT0/uevent')) {
  83. battery_path = '/sys/class/power_supply/BAT0/';
  84. }
  85. let acConnected = false;
  86. let acPath = '';
  87. if (fs.existsSync('/sys/class/power_supply/AC/online')) {
  88. acPath = '/sys/class/power_supply/AC/online';
  89. } else if (fs.existsSync('/sys/class/power_supply/AC0/online')) {
  90. acPath = '/sys/class/power_supply/AC0/online';
  91. }
  92. if (acPath) {
  93. const file = fs.readFileSync(acPath);
  94. acConnected = file.toString().trim() === '1';
  95. }
  96. if (battery_path) {
  97. fs.readFile(battery_path + 'uevent', function (error, stdout) {
  98. if (!error) {
  99. let lines = stdout.toString().split('\n');
  100. result.isCharging = (util.getValue(lines, 'POWER_SUPPLY_STATUS', '=').toLowerCase() === 'charging');
  101. result.acConnected = acConnected || result.isCharging;
  102. result.voltage = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_VOLTAGE_NOW', '='), 10) / 1000000.0;
  103. result.capacityUnit = result.voltage ? 'mWh' : 'mAh';
  104. result.cycleCount = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_CYCLE_COUNT', '='), 10);
  105. result.maxCapacity = Math.round(parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_CHARGE_FULL', '=', true, true), 10) / 1000.0 * (result.voltage || 1));
  106. const desingedMinVoltage = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_VOLTAGE_MIN_DESIGN', '='), 10) / 1000000.0;
  107. result.designedCapacity = Math.round(parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_CHARGE_FULL_DESIGN', '=', true, true), 10) / 1000.0 * (desingedMinVoltage || result.voltage || 1));
  108. result.currentCapacity = Math.round(parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_CHARGE_NOW', '='), 10) / 1000.0 * (result.voltage || 1));
  109. if (!result.maxCapacity) {
  110. result.maxCapacity = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_ENERGY_FULL', '=', true, true), 10) / 1000.0;
  111. result.designedCapacity = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_ENERGY_FULL_DESIGN', '=', true, true), 10) / 1000.0 | result.maxCapacity;
  112. result.currentCapacity = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_ENERGY_NOW', '='), 10) / 1000.0;
  113. }
  114. const percent = util.getValue(lines, 'POWER_SUPPLY_CAPACITY', '=');
  115. const energy = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_ENERGY_NOW', '='), 10);
  116. const power = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_POWER_NOW', '='), 10);
  117. const current = parseInt('0' + util.getValue(lines, 'POWER_SUPPLY_CURRENT_NOW', '='), 10);
  118. result.percent = parseInt('0' + percent, 10);
  119. if (result.maxCapacity && result.currentCapacity) {
  120. result.hasBattery = true;
  121. if (!percent) {
  122. result.percent = 100.0 * result.currentCapacity / result.maxCapacity;
  123. }
  124. }
  125. if (result.isCharging) {
  126. result.hasBattery = true;
  127. }
  128. if (energy && power) {
  129. result.timeRemaining = Math.floor(energy / power * 60);
  130. } else if (current && result.currentCapacity) {
  131. result.timeRemaining = Math.floor(result.currentCapacity / current * 60);
  132. }
  133. result.type = util.getValue(lines, 'POWER_SUPPLY_TECHNOLOGY', '=');
  134. result.model = util.getValue(lines, 'POWER_SUPPLY_MODEL_NAME', '=');
  135. result.manufacturer = util.getValue(lines, 'POWER_SUPPLY_MANUFACTURER', '=');
  136. result.serial = util.getValue(lines, 'POWER_SUPPLY_SERIAL_NUMBER', '=');
  137. if (callback) { callback(result); }
  138. resolve(result);
  139. } else {
  140. if (callback) { callback(result); }
  141. resolve(result);
  142. }
  143. });
  144. } else {
  145. if (callback) { callback(result); }
  146. resolve(result);
  147. }
  148. }
  149. if (_freebsd || _openbsd || _netbsd) {
  150. exec('sysctl -i hw.acpi.battery hw.acpi.acline', function (error, stdout) {
  151. let lines = stdout.toString().split('\n');
  152. const batteries = parseInt('0' + util.getValue(lines, 'hw.acpi.battery.units'), 10);
  153. const percent = parseInt('0' + util.getValue(lines, 'hw.acpi.battery.life'), 10);
  154. result.hasBattery = (batteries > 0);
  155. result.cycleCount = null;
  156. result.isCharging = util.getValue(lines, 'hw.acpi.acline') !== '1';
  157. result.acConnected = result.isCharging;
  158. result.maxCapacity = null;
  159. result.currentCapacity = null;
  160. result.capacityUnit = 'unknown';
  161. result.percent = batteries ? percent : null;
  162. if (callback) { callback(result); }
  163. resolve(result);
  164. });
  165. }
  166. if (_darwin) {
  167. exec('ioreg -n AppleSmartBattery -r | egrep "CycleCount|IsCharging|DesignCapacity|MaxCapacity|CurrentCapacity|BatterySerialNumber|TimeRemaining|Voltage"; pmset -g batt | grep %', function (error, stdout) {
  168. if (stdout) {
  169. let lines = stdout.toString().replace(/ +/g, '').replace(/"+/g, '').replace(/-/g, '').split('\n');
  170. result.cycleCount = parseInt('0' + util.getValue(lines, 'cyclecount', '='), 10);
  171. result.voltage = parseInt('0' + util.getValue(lines, 'voltage', '='), 10) / 1000.0;
  172. result.capacityUnit = result.voltage ? 'mWh' : 'mAh';
  173. result.maxCapacity = Math.round(parseInt('0' + util.getValue(lines, 'applerawmaxcapacity', '='), 10) * (result.voltage || 1));
  174. result.currentCapacity = Math.round(parseInt('0' + util.getValue(lines, 'applerawcurrentcapacity', '='), 10) * (result.voltage || 1));
  175. result.designedCapacity = Math.round(parseInt('0' + util.getValue(lines, 'DesignCapacity', '='), 10) * (result.voltage || 1));
  176. result.manufacturer = 'Apple';
  177. result.serial = util.getValue(lines, 'BatterySerialNumber', '=');
  178. let percent = null;
  179. const line = util.getValue(lines, 'internal', 'Battery');
  180. let parts = line.split(';');
  181. if (parts && parts[0]) {
  182. let parts2 = parts[0].split('\t');
  183. if (parts2 && parts2[1]) {
  184. percent = parseFloat(parts2[1].trim().replace(/%/g, ''));
  185. }
  186. }
  187. if (parts && parts[1]) {
  188. result.isCharging = (parts[1].trim() === 'charging');
  189. result.acConnected = (parts[1].trim() !== 'discharging');
  190. } else {
  191. result.isCharging = util.getValue(lines, 'ischarging', '=').toLowerCase() === 'yes';
  192. result.acConnected = result.isCharging;
  193. }
  194. if (result.maxCapacity && result.currentCapacity) {
  195. result.hasBattery = true;
  196. result.type = 'Li-ion';
  197. result.percent = percent !== null ? percent : Math.round(100.0 * result.currentCapacity / result.maxCapacity);
  198. if (!result.isCharging) {
  199. result.timeRemaining = parseInt('0' + util.getValue(lines, 'TimeRemaining', '='), 10);
  200. }
  201. }
  202. }
  203. if (callback) { callback(result); }
  204. resolve(result);
  205. });
  206. }
  207. if (_sunos) {
  208. if (callback) { callback(result); }
  209. resolve(result);
  210. }
  211. if (_windows) {
  212. try {
  213. const workload = [];
  214. workload.push(util.powerShell('Get-WmiObject Win32_Battery | select BatteryStatus, DesignCapacity, DesignVoltage, EstimatedChargeRemaining, DeviceID | fl'));
  215. workload.push(util.powerShell('(Get-WmiObject -Class BatteryStaticData -Namespace ROOT/WMI).DesignedCapacity'));
  216. workload.push(util.powerShell('(Get-WmiObject -Class BatteryFullChargedCapacity -Namespace ROOT/WMI).FullChargedCapacity'));
  217. util.promiseAll(
  218. workload
  219. ).then(data => {
  220. if (data) {
  221. // let parts = data.results[0].split(/\n\s*\n/);
  222. let parts = data.results[0].split(/\n\s*\n/);
  223. let batteries = [];
  224. const hasValue = value => /\S/.test(value);
  225. for (let i = 0; i < parts.length; i++) {
  226. if (hasValue(parts[i]) && (!batteries.length || !hasValue(parts[i - 1]))) {
  227. batteries.push([]);
  228. }
  229. if (hasValue(parts[i])) {
  230. batteries[batteries.length - 1].push(parts[i]);
  231. }
  232. }
  233. let designCapacities = data.results[1].split('\r\n').filter(e => e);
  234. let fullChargeCapacities = data.results[2].split('\r\n').filter(e => e);
  235. if (batteries.length) {
  236. let first = false;
  237. let additionalBatteries = [];
  238. for (let i = 0; i < batteries.length; i++) {
  239. let lines = batteries[i][0].split('\r\n');
  240. const designedCapacity = designCapacities && designCapacities.length >= (i + 1) && designCapacities[i] ? util.toInt(designCapacities[i]) : 0;
  241. const fullChargeCapacity = fullChargeCapacities && fullChargeCapacities.length >= (i + 1) && fullChargeCapacities[i] ? util.toInt(fullChargeCapacities[i]) : 0;
  242. const parsed = parseWinBatteryPart(lines, designedCapacity, fullChargeCapacity);
  243. if (!first && parsed.status > 0 && parsed.status !== 10) {
  244. result.hasBattery = parsed.hasBattery;
  245. result.maxCapacity = parsed.maxCapacity;
  246. result.designedCapacity = parsed.designedCapacity;
  247. result.voltage = parsed.voltage;
  248. result.capacityUnit = parsed.capacityUnit;
  249. result.percent = parsed.percent;
  250. result.currentCapacity = parsed.currentCapacity;
  251. result.isCharging = parsed.isCharging;
  252. result.acConnected = parsed.acConnected;
  253. result.model = parsed.model;
  254. first = true;
  255. } else if (parsed.status !== -1) {
  256. additionalBatteries.push(
  257. {
  258. hasBattery: parsed.hasBattery,
  259. maxCapacity: parsed.maxCapacity,
  260. designedCapacity: parsed.designedCapacity,
  261. voltage: parsed.voltage,
  262. capacityUnit: parsed.capacityUnit,
  263. percent: parsed.percent,
  264. currentCapacity: parsed.currentCapacity,
  265. isCharging: parsed.isCharging,
  266. timeRemaining: null,
  267. acConnected: parsed.acConnected,
  268. model: parsed.model,
  269. type: '',
  270. manufacturer: '',
  271. serial: ''
  272. }
  273. );
  274. }
  275. }
  276. if (!first && additionalBatteries.length) {
  277. result = additionalBatteries[0];
  278. additionalBatteries.shift();
  279. }
  280. if (additionalBatteries.length) {
  281. result.additionalBatteries = additionalBatteries;
  282. }
  283. }
  284. }
  285. if (callback) { callback(result); }
  286. resolve(result);
  287. });
  288. } catch (e) {
  289. if (callback) { callback(result); }
  290. resolve(result);
  291. }
  292. }
  293. });
  294. });
  295. };