Потребовалось измерить ток потребления одного устройства
+ хранить полученные значения в таблице БД (PostgreSQL)
Первые 5 минут гугления показали — почти все производители полупроводников, имеют красивые решения, на одном кристалле
Сложные схемы на ОУ, остались далеко в прошлом
Выбор пал на INA260
Напряжение до 36v, простой для монтажа корпус, компромиссная стоимость
Но самый решающий аргумент, он уже валялся в тумбе :D Среди прочих образцов
Настало время его задействовать
Включаемая схема ничем не отличается от приведенной в даташите
Ее не высокая сложность, позволяет все собрать на коленке
Современный Current/Power Monitor ток измерит, напряжение отобразит
Если потребуется, даже Alert-ом сообщит о превышении порогового значения!
Для сборки потребуется минимальное количество компонентов (5 R + 2 C)
Можно поискать готовые модули на азиатских площадках, например на INA219
Тогда вероятно потребуется несложная доработка функции пересчета
Написал класс опроса
// синглтон для записи логов
static logger& loggerInstance = logger::Instance();
I2cPoll::I2cPoll() {
// создаем класс БД
db = new DbConnection();
if(db->isConnecting()) {
loggerInstance.appendToLog("I2cPoll: db-connected-OKrn");
} else {
loggerInstance.appendToLog("I2cPoll: db opening -ERRORrn");
}
// структура позволят опрашивать больше одного устройства
// указываем с которого начинать опрос
this->currentDeviceType = startDeviceType;
}
// функция опроса
void I2cPoll::pollExect() {
uint8_t *p_data = NULL;
uint16_t word = 0;
bool res = false;
char cmd_command[128] = {0};
S_insertData device_data;
int device_addr = 0;
// формируем запрос
switch(currentDeviceType) {
case dev_i2c_ina260: {
// извлекаем из БД id устройства
device_addr = db->getDeviceAddrFromName(
i2c_Dev_typeName.ina260.name_text.c_str()
);
// если извлекли правильно
if(device_addr != 0) {
// интересует 3 параметра - ток, напряжение и мощность
for(int i=0; i<3; i++) {
switch(i) {
// curent
case 0:
sprintf(cmd_command, "i2cget -y 0 0x%X 0x01 wrn", device_addr);
// отправляем в консоль
res = readWord(cmd_command, &word);
if(res) { // проверяем ответ
p_data = (uint8_t*)&word;
float raw_data;
fprintf(stdout, "Raw u16 %xrn", word);
// преобразование по описанию из даташита
raw_data = (0xFF & p_data[1]) | ((0xFF & (p_data[0])) << 8);
device_data.parameter.power_monitor.currnetFlowing =
raw_data * 1.25 / 1000;
fprintf(stdout, "Current %4.2frn",
device_data.parameter.power_monitor.currnetFlowing
);
}
break;
// voltage
case 1:
sprintf(cmd_command, "i2cget -y 0 0x%X 0x02 w", device_addr);
// отправляем в консоль
res = readWord(cmd_command, &word);
if(res) { // проверяем ответ
p_data = (uint8_t*)&word;
float raw_data;
fprintf(stdout, "Raw u16 %xrn", word);
// преобразование по описанию из даташита
raw_data = (0xFF & p_data[1]) | ((0xFF & (p_data[0])) << 8);
device_data.parameter.power_monitor.voltage =
raw_data * 1.25 / 1000;
fprintf(stdout, "Volage %4.2frn",
device_data.parameter.power_monitor.voltage
);
}
break;
case 2:
// power
sprintf(cmd_command, "i2cget -y 0 0x%X 0x03 wrn", device_addr);
// отправляем в консоль
res = readWord(cmd_command, &word);
if(res) { // проверяем ответ
p_data = (uint8_t*)&word;
float raw_data;
fprintf(stdout, "Raw u16 %xrn", word);
// преобразование по описанию из даташита
raw_data = (0xFF & p_data[1]) | ((0xFF & (p_data[0])) << 8);
device_data.parameter.power_monitor.currentPower =
raw_data * 1.25;
fprintf(stdout, "Power %4.2frn",
device_data.parameter.power_monitor.currentPower
);
}
break;
}
}
// если все опросили, отправляем данные в БД
if(res) {
db->insertData(device_data);
}
}
}
break;
default : // если что-то не так, начинаем с первого устройства
currentDeviceType = startDeviceType;
break;
}
// после конца цикла, начинаем с первого устройства
if(currentDeviceType >= (E_I2c_device)endTypeDeviceType) {
currentDeviceType = startDeviceType;
fprintf(stdout, "I2c parce -endDevrn");
} else { // иначе опрашиваем следующий
currentDeviceType++;
fprintf(stdout, "I2c parce -nextDevrn");
}
}
// формируем запрос на шину i2c
// парсим данные в ответе
bool I2cPoll::readWord(char *cmd_command, uint16_t *p_word) {
int res = false;
int word = 0;
FILE *stream;
// отправляем в консоль
stream = popen(cmd_command, "r");
fprintf(stdout, "cmd_command - %srn", cmd_command);
std::string data;
if (stream) {
char reply_buff[128] = {0};
while (!feof(stream))
if(fgets(reply_buff, sizeof(reply_buff), stream) != NULL) {
data.append(reply_buff);
}
pclose(stream);
}
// проверяем ответ
fprintf(stdout, "shell result :rn%srn", data.c_str());
if(data.length() != 0) {
char *p_start = strstr((char*)data.c_str(), "0x");
if(p_start != NULL) {
res = sscanf(p_start, "%x", &word);
if(res) {
*p_word = (uint16_t)word;
}
fprintf(stdout, "getWord %xrn", *p_word);
}
}
return res;
}
I2cPoll::~I2cPoll() {
// TODO Auto-generated destructor stub
}
Запускать файл вручную не интересно, это может привести к дублированию процессов
Правильнее использовать демон
После прочтения поста @shevmax, смысл написания собственной реализации fork отпал
Ограничился небольшим рефакторингом
// функция которая инициализирует рабочие потоки
int initWorkThread() {
loggerInstance.appendToLog("[DAEMON] Init...rn");
i2c_poller = new I2cPoll();
std::thread thr(thread_proc);
threads.emplace_back(std::move(thr));
loggerInstance.appendToLog("[DAEMON] Init -Okrn");
return 0;
}
void thread_proc(void) {
for(;;) {
{
std::lock_guard<std::mutex> lock(mtx_thread_poll);
i2c_poller->pollExect();
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
После добавления в проект fork, нужен скрипт init.d
Сохраним его как /etc/init.d/i2c_poller.sh
#!/bin/sh
dir="/home/khomin/Project/i2c_poller/bin/"
cmd="/home/khomin/Project/i2c_poller/bin/i2c_poller"
user="root"
name="i2c_poller"
pid_file="/var/run/$name.pid"
log_dir="/var/log/i2c_poller/"
stdout_log="$log_dir/$name.log"
stderr_log="$log_dir/$name.err"
get_pid() {
cat "$pid_file"
}
is_running() {
[ -f "$pid_file" ] && ps -p `get_pid` > /dev/null 2>&1
}
case "$1" in
start)
if is_running; then
echo "Already started"
else
echo "Starting $name"
rm -rf $log_dir
mkdir $log_dir
cd "$dir"
if [ -z "$user" ]; then
sudo $cmd >> "$stdout_log" 2>> "$stderr_log" &
else
sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" &
fi
echo $! > "$pid_file"
if ! is_running; then
echo "Unable to start, see $stdout_log and $stderr_log"
exit 1
fi
fi
;;
stop)
if is_running; then
echo -n "Stopping $name.."
kill `get_pid`
for i in 1 2 3 4 5 6 7 8 9 10
# for i in `seq 10`
do
if ! is_running; then
break
fi
echo -n "."
sleep 1
done
echo
if is_running; then
echo "Not stopped; may still be shutting down or shutdown may have failed"
exit 1
else
echo "Stopped"
if [ -f "$pid_file" ]; then
rm "$pid_file"
fi
fi
else
echo "Not running"
fi
;;
restart)
$0 stop
if is_running; then
echo "Unable to stop, will not attempt to start"
exit 1
fi
$0 start
;;
status)
if is_running; then
echo "Running"
else
echo "Stopped"
exit 1
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit 0
Проверим, что запускается
После выполнения /etc/init.d/i2c_poller.sh start
Тут же видим данные в базе, значит опрос работает
Проект на git
https://github.com/khomin/i2c_poller
TODO: использование i2cget — не самое рациональное решение
В зависимости от типа одноплатника, есть смысл включить драйвер i2c при сборке ядра
Автор: Khomin