Собственно код цикла cycle_ctpower.php:
Код: Выделить всё
<?php
/*
* Цикл контроля подсистемы электропитания Cubietruck.
*
* Примечание:
* Чтобы дать пользователю www-data возможность читать данные из регистров i2c-устройств,
* необходимо www-data включить в группу i2c (файл /etc/group).
*
* Copyright (C) 2014-2015 Agaphonov Dmitri aka skysilver [mailto:skysilver.da@gmail.com]
*/
chdir(dirname(__FILE__).'/../');
include_once("./config.php");
include_once("./lib/loader.php");
set_time_limit(0);
// Параметры из datasheet на AXP209
const BAT_U_SCALE = 1.1; // Battery Voltage 0mV 1.1mV 4.5045V
const TEMP_SCALE = 0.1; // Internal temperature -144.7 C 0.1C 264.8C
const APS_SCALE = 1.4; // APS voltage 0mV 1.4mV 5.733V
const VLSB = 1.1; // voltage LSB is 1.1mV
const CLSB = 0.5; // Current LSB is 0.5mA
const ADCSR = 100; // Refer to REG84H setting for ADC sample rate (100Hz)
$Pbat_k = 2*VLSB*CLSB/1000; // Коэффициент для вычисления мощности АКБ
// Команда на чтение регистров AXP209
const CMD = "/usr/sbin/i2cget -y -f 0 0x34 ";
$db = new mysql(DB_HOST, '', DB_USER, DB_PASSWORD, DB_NAME);
include_once("./load_settings.php");
$checked_time = 0;
echo date("Y-m-d H:i:s ") . " running " . basename(__FILE__) . "\n";
while(1)
{
// Если прошло 10 сек., то запрашиваем данные и отправляем их в метод объекта.
if (time() - $checked_time > 10)
{
$checked_time = time();
setGlobal((str_replace('.php', '', basename(__FILE__))).'Run', time(), 1);
// Контроль параметров батареи
$presentBAT = mb_substr(file_get_contents('/sys/class/power_supply/battery/present'), 0, -1); // наличие батареи [1 = подключена/ 0 = отключена]
$onlineBAT = mb_substr(file_get_contents('/sys/class/power_supply/battery/online'), 0, -1); // питание от батареи или нет [1/0]
$statusBAT = mb_substr(file_get_contents('/sys/class/power_supply/battery/status'), 0, -1); // текущий статус батареи [Full/Charge/Discharge]
$uBAT = (int)TwoRegsToDec(shell_exec(CMD.'0x78'),shell_exec(CMD.'0x79'))*BAT_U_SCALE;
$uBAT = number_format($uBAT/1000,3,'.',''); // напряжение на батарее [В]
$iBAT = number_format(file_get_contents('/sys/class/power_supply/battery/current_now')/1000,1,'.',''); // ток при работе от батареи [мА]
$capBATperc = base_convert(shell_exec(CMD.'0xb9'), 16, 10); // текущая емкость батареи [0-100%]
$pBAT = $Pbat_k*(int)ThreeRegsToDec(shell_exec(CMD.'0x70'),shell_exec(CMD.'0x71'),shell_exec(CMD.'0x72')); // мощность, потребляемая при работе от АКБ [мВт]
$CCCV = FourRegsToDec(shell_exec(CMD.'0xb0'),shell_exec(CMD.'0xb1'),shell_exec(CMD.'0xb2'),shell_exec(CMD.'0xb3')); // вспомогательный коэффициент (счетчик энергии заряда)
$DCCV = FourRegsToDec(shell_exec(CMD.'0xb4'),shell_exec(CMD.'0xb5'),shell_exec(CMD.'0xb6'),shell_exec(CMD.'0xb7')); // вспомогательный коэффициент (счетчик энергии разряда)
$capBATmah = 65536*CLSB*((int)$CCCV-(int)$DCCV)/3600/ADCSR;
$capBATmah = number_format($capBATmah,2,'.',''); // емкость батареи [мАч]
// Контроль параметров сетевого источиника питания (СИП)
$presentAC = mb_substr(file_get_contents('/sys/class/power_supply/ac/present'), 0, -1); // наличие СИП [1/0]
$onlineAC = mb_substr(file_get_contents('/sys/class/power_supply/ac/online'), 0, -1); // питание от СИП или нет [1/0]
$uAC = number_format(file_get_contents('/sys/class/power_supply/ac/voltage_now')/1000000,3,'.',''); // напряжение СИП [В]
$iAC = number_format(file_get_contents('/sys/class/power_supply/ac/current_now')/1000,1,'.',''); // ток при работе от СИП [мА]
// Дополнительные параметры
$tempAXP = ((int)TwoRegsToDec(shell_exec(CMD.'0x5e'),shell_exec(CMD.'0x5f'))-1447)*TEMP_SCALE;
$tempAXP = number_format($tempAXP,1,'.',''); // температура контроллера заряда AXP209
// Запускаем метод и передаем ему все собранные параметры для дальнейшей обработки
callMethod('Cubietruck.getPowerStatus',array("uAC"=>$uAC,"iAC"=>$iAC,"tempAXP"=>$tempAXP,"uBAT"=>$uBAT,"iBAT"=>$iBAT,"capBATmah"=>$capBATmah,"capBATperc"=>$capBATperc,"onlineAC"=>$onlineAC,"pBAT"=>$pBAT,"onlineBAT"=>$onlineBAT,"presentAC"=>$presentAC,"presentBAT"=>$presentBAT,"statusBAT"=>$statusBAT));
}
if (file_exists('./reboot'))
{
$db->Disconnect();
echo date("Y-m-d H:i:s ") . "Stopping by command REBOOT " . basename(__FILE__) . "\n";
exit;
}
sleep(1);
}
$db->Disconnect();
echo date("Y-m-d H:i:s ") . "Unexpected stopping " . basename(__FILE__) . "\n";
DebMes("Unexpected close of cycle: " . basename(__FILE__));
?>
Код: Выделить всё
$this->setProperty('presentBAT', $params['presentBAT']);
$this->setProperty('presentAC', $params['presentAC']);
// В историю значения напряжения и тока будет сохранять только
// с определенным интервалом (в секундах)
if ($params['statusBAT'] == "Full")
{
if ( (time() - $this->getProperty('updated')) >= 300 ) // раз в 5 минут
{
// Сохраняем данные в историю только, если батарея полностью заяржена,
// иначе сохраняем с другим периодом (см. ниже)
$this->setProperty('uAC',$params['uAC']);
$this->setProperty('iAC',$params['iAC']);
$this->setProperty('tempAXP',$params['tempAXP']);
$this->setProperty('uBAT',$params['uBAT']);
$this->setProperty('iBAT',$params['iBAT']);
$this->setProperty('capBATperc',$params['capBATperc']);
$this->setProperty('pBAT',$params['pBAT']);
$this->setProperty('capBATmah', $params['capBATmah']);
$this->setProperty("updated", time());
$this->setProperty("updatedTime", date('H:i'));
}
}
// Если в данный момент происходит зарядка или разрядка батареи,
// то увеличим частоту сохранения показаний
if ($params['statusBAT'] != "Full")
{
if ( (time() - $this->getProperty('updated')) >= 120 ) // раз в 2 минуты
{
$this->setProperty('uAC',$params['uAC']);
$this->setProperty('iAC',$params['iAC']);
$this->setProperty('tempAXP',$params['tempAXP']);
$this->setProperty('uBAT',$params['uBAT']);
$this->setProperty('iBAT',$params['iBAT']);
$this->setProperty('capBATperc',$params['capBATperc']);
$this->setProperty('pBAT',$params['pBAT']);
$this->setProperty('capBATmah', $params['capBATmah']);
$this->setProperty("updated", time());
$this->setProperty("updatedTime", date('H:i'));
}
}
// Обработаем события включения/отключения сетевого питания
$old_onlineAC = $this->getProperty('onlineAC');
$this->setProperty('onlineAC',$params['onlineAC']);
if ($params['onlineAC'] == 1 && ($params['onlineAC'] != $old_onlineAC))
{
say('Электропитание сервера восстановлено.', 1);
}
else if ($params['onlineAC'] == 0 && ($params['onlineAC'] != $old_onlineAC))
{
say('Внимание! Нарушено электропитание сервера!', 1);
}
// Обработаем события перехода на питание от батареи
$old_onlineBAT = $this->getProperty('onlineBAT');
$this->setProperty('onlineBAT',$params['onlineBAT']);
if ($params['onlineBAT'] == 1 && ($params['onlineBAT'] != $old_onlineBAT))
{
say('Перехожу на резервное питание от батареи.', 0);
}
else if ($params['onlineBAT'] == 0 && ($params['onlineBAT'] != $old_onlineBAT))
{
say('Перехожу на основное питание от сети.', 0);
}
// Обработаем события начала/окончания заряда/разряда батареи
$old_statusBAT = $this->getProperty('statusBAT');
$this->setProperty('statusBAT',$params['statusBAT']);
if ($params['statusBAT'] == "Charging" && ($params['statusBAT'] != $old_statusBAT))
{
say('Начинаю процесс заряда батареи.', 0);
// Запомним время начала заряда батареи (для дальнейшего построения графика)
$this->setProperty('LastChargeStart', time());
}
else if ($params['statusBAT'] == "Discharging" && ($params['statusBAT'] != $old_statusBAT))
{
say('Начался процесс разряда батареи.', 0);
// Запомним время начала разряда батареи (для дальнейшего построения графика)
$this->setProperty('LastDischargeStart', time());
}
else if ($params['statusBAT'] == "Full" && ($params['statusBAT'] != $old_statusBAT))
{
say('Процесс заряда батареи завершен.', 0);
// Запомним время окончания заряда батареи (для дальнейшего построения графика)
$this->setProperty('LastChargeEnd', time());
}
// Если АКБ разряжается, то будет контролировать ее напряжени и штатно
// завершим работу сервера при низком заряде АКБ.
if ($params['statusBAT'] == "Discharging") {
$ub = $params['uBAT'];
if ( $ub < $this->getProperty('shutdownVoltage')) {
$this->setProperty('LastDischargeEnd', time());
$this->callMethod('setPoweroff');
}
}

Обновление от 28.04.2015 г.
Доработал исходные коды цикла и метода.
Теперь, за счет прямого чтения из регистров контроллера заряда AXP209 по шине i2c, появилась возможность получать такие параметры, как мощность, потребляемая от АКБ, емкость АКБ в мАч (закачанная и отданная батареей), температура контроллера. Имея в распоряжении емкость (мАч) и силу тока, можно в принципе вычислять примерное время, которое Cubietruck сможет работать от батареи.
Набор параметров, собираемых циклом:
- presentBAT - наличие батареи [1 = подключена/ 0 = отключена]
- onlineBAT - питание от батареи или нет [1/0]
- statusBAT - текущий статус батареи [Full/Charge/Discharge]
- uBAT - напряжение на батарее [В]
- iBAT - ток при работе от батареи [мА]
- capBATperc - текущая емкость батареи [0-100%]
- pBAT - мощность, потребляемая при работе от АКБ [мВт]
- capBATmah - емкость батареи [мАч]
- presentAC - наличие СИП [1/0]
- onlineAC - питание от СИП или нет [1/0]
- uAC - напряжение СИП [В]
- iAC - ток при работе от СИП [мА]
- tempAXP - температура контроллера заряда AXP209
Порядок установки
- 1. Файл cycle_ctpower.php положить в каталог scripts и дать ему права доступа и назначить владельца такие же, как и у других файлов cycle_*.php. Убедиться, что в содержимое файла не добавились символы ^M в конце строк (такое бывает, если использовать для редактирования виндовый блокнот и т.п.).
2. Файл my.class.php положить в каталог lib и дать ему права доступа и назначить владельца такие же, как и у других файлов в этом каталоге.
3. Если не установлены пакеты i2c-tools и libi2c-dev, то установить командой4. Если главный цикл MajorDoMo запущен от имени www-data, то нужно добавить этого пользователя в группу i2c.Код: Выделить всё
apt-get install i2c-tools libi2c-dev
5. Создать какой-нибудь класс (либо использовать какой-либо существующий), а в нем объект Cubietruck. У меня создан класс "PC", в нем подкласс "Server", а там уж объект Cubietruck.СпойлерПоказатьДва способа:
1) Открыть на редактирование файл /etc/group в любом удобном Вам редакторе. Найти строку, начинающуюся на i2c. И дописать в конце этой строки www-data. Должно получиться примерно так (цифры могут отличаться):2) В консоли выполнить командуКод: Выделить всё
i2c:x:115:www-data
Для проверки результата выполните в консоли команду:Код: Выделить всё
useradd -G i2c www-data
Если все настроено верно, то в ответе должно быть примерно так:Код: Выделить всё
sudo -u www-data i2cdump -y -f 0 0x34
Это дамп содержимого регистров контроллера питания кубика.Код: Выделить всё
root@ihome:~# sudo -u www-data i2cdump -y -f 0 0x34 No size specified (using byte-data access) 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00: c0 30 00 41 00 e4 00 00 00 00 00 00 00 00 00 0f ?0.A.?.........? 10: 05 00 5f 00 00 00 00 00 00 00 00 00 00 00 00 00 ?._............. 20: 00 00 00 16 00 00 00 18 cb 54 00 00 00 00 00 00 ...?...??T...... 30: 43 07 42 c3 47 22 9d 08 a5 1f 71 67 fc 16 00 00 C?B?G"????qg??.. 40: 6c cc 03 00 00 00 00 00 00 00 00 00 00 00 00 00 l??............. 50: ea 0d 00 00 00 01 b9 0c 27 02 00 0e 00 00 79 03 ??...???'?.?..y? 60: db 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??.............. 70: 00 07 56 00 00 00 00 00 ea 0d 00 00 00 01 dc 08 .?V.....??...??? 80: e0 f9 ff 80 b2 00 ff 00 00 00 00 00 00 00 00 ad ??.??..........? 90: 07 a5 07 07 00 02 00 00 00 00 00 00 00 00 00 00 ????.?.......... a0: 00 00 00 00 00 00 00 00 ea 0e 00 00 00 00 dc 00 ........??....?. b0: 00 00 00 00 00 00 00 00 00 61 00 70 ea 0d 00 00 .........a.p??.. c0: 00 00 00 00 05 0b 0d 0f 13 20 32 3a 47 51 59 64 ....????? 2:GQYd d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ f0: 41 00 80 03 00 00 00 00 00 00 00 00 00 00 00 00 A.??............
6. У объекта Cubietruck создать метод getPowerStatus и в него код из первого сообщения этой темы.
7. У объекта Cubietruck создать все перечисленные в первом сообщении темы свойства. Важно! Свойства, у которых предполагается хранение истории значений, необходимо создать на уровне класса и указать период хранения истории значений. Свойства без истории достаточно создать на уровне объекта (либо вообще не создавать, т.к. метод сам их создаст при первом запуске). У себя все свойства создал на уровне класcа Server.
8. Перезапустить главный цикл.
9. Понаблюдать за логами Apache, DebMes на появление каких-либо ошибок.
10. В XRay убедиться, что данные обновляются.
В основе измерения данного показателя лежат два счетчика CCCV и DCCV. Первый растет при заряде АКБ, второй соответственно при разряде АКБ.
Емкость АКБ высчитывается по формуле Сакб = 65536*CLSB*(CCCV-DCCV)/3600/ADCSR, где CLSB = 0.5 и ADCSR = 100 - константы, взятые из datasheet на AXP209. Чтобы счетчики активировать в регистр 0хB8 нужно записать значение 0х80, иначе счетчики ничего считать не будут. Чтобы у нас не получилась отрицательная емкость Сакб, то правильный алгоритм будет такой:
- 1) полностью разряжаем АКБ кубика;
2) активируем счетчики командой в консоли:3) на всякий случай обнулим счетчики CCCV и DCCV, чтобы начать с чистого листаКод: Выделить всё
i2cset -y -f 0 0x34 0xB8 0x80
:
4) заряжаем полностью АКБ.Код: Выделить всё
i2cset -y -f 0 0x34 0xB8 0xA0
Завершение работы Cubietruck при низком заряде АКБ
Данный механизм реализован, но еще ниразу не тестировался, т.к. дольше 8 часов электричество у меня еще не отключали, и батарея кубика вытягивает больше.

В качестве порога отключения Кубика я взял напряжение батареи (возможно, в последствие перейду к емкости АКБ в % или мАч, но пока так). Порог отключения задаю в свойстве объекта Cubietruck.shutdownVoltage. Пока что принял его равным 3.5В. В методе getPowerStatus уже добавлен раздел контроля порогового напряжения. Выглядит вот так:
Код: Выделить всё
// Если АКБ разряжается, то будет контролировать ее напряжени и штатно
// завершим работу сервера при низком заряде АКБ.
if ($params['statusBAT'] == "Discharging") {
$ub = $params['uBAT'];
if ( $ub < $this->getProperty('shutdownVoltage')) {
$this->setProperty('LastDischargeEnd', time());
$this->callMethod('setPoweroff');
}
}
Код метода setPoweroff у меня следующий:
Код: Выделить всё
say('Внимание! У сервера низкий заряд батареи. Завершаю работу.', 1);
DebMes('Низкий заряд батареи '.$this->getProperty('capBATperc').'%. Сбат = '.$this->getProperty('capBATmah').'. Uбат = '.$this->getProperty('uBAT').'. Завершаю работу.');
$cmd = "sudo /var/www/lib/poweroff.sh";
safe_exec($cmd);
Тут могут быть вариации. Если главный цикл запущен от root, то в качестве команды можно просто указать:
Код: Выделить всё
$cmd = "poweroff";
Код: Выделить всё
www-data ALL=(root) NOPASSWD: /var/www/lib/poweroff.sh
При тестировании учитывайте особенность Кубика. Если одновременно подключены и АКБ, и блок питания, то команда poweroff не выключает Кубик, а отправляет в перезагрузку. Как побороть такой косяк не знаю.

Все вышеизложенное Вы используете на свой страх и риск.

С уважением, skysilver.