Возникла ситуация, когда необходимо выводить звук-предупреждение о разряде ибп raspberry pi.
В статье предлагается решение с использованием датчика напряжения (Voltage Sensor), arduino nano и «любимой аудио колонки школьника» — портативной «jbl go».

*c 26 секунды

ИБП


Пара слов об используемом источнике бесперебойного питания. Для электроснабжения raspberry pi 4 был приобретен ибп, от которого одноплатник на двух аккумуляторах 18650 может держаться аж 3,5 часа:



*картинка выбрана из эстетических соображений и внешне немного отличается от варианта для raspberry pi 4:



Для варианта под raspberry pi 4 есть выход type-c для питания raspberry pi, а также monitor напряжения (на фото расположен справа), который пригодится в дальнейшем.
Этот ИБП не идеален, но он лучше аналогичных, которыми приходилось пользоваться с того же ali.

Есть два нюанса, о которых нужно помнить при его использовании.

Первый нюанс — необходимо иметь под рукой блок питания на 12 V 2А (или 3А), потому как при смене «батарей» (аккумуляторов, конечно, но далее по тексту — батареи), ибп входит в блокировку и чтобы из нее вывести нужно подать 12В, подключив блок питания к ибп. Батареи можно из ибп не вынимать и заряжать/подзаряжать их прямо в ибп, тогда не придется выводить из блокировки.

Второй нюанс — при вставке батарей в держатели ибп не нужно ориентироваться на нарисованные на пластике крупные "+" и "-", они немного перепутаны:



Тем не менее, если батареи вставить неправильно, то на приборе просто загорится светодиод, предупреждая об этом, и прибор снова уйдет в блокировку.

Ах, да, добавлю еще третий нюанс — при выключении raspberry, ибп не обесточивается, продолжая работать и необходимо вручную передвинуть рычажок на нем «on/off». И это было бы пол-беды. Если забыть про рычажок, ибп, продолжая работать, со временем «высадит» батареи.
В остальном ИБП хорош.

Взять хотя бы usb разъемы и выводы с 5V для питания периферии.

Но эта статья не про ибп, поэтому двигаемся дальше.

Как определить разряд батарей


Как известно, на raspberry pi нет своего ацп, поэтому нужен определенный огород, чтобы снимать прямо с нее показатели. Мне не захотелось паяться на gpio raspberry, тем более, что на них уже висит «шапка», поэтому пошел через старое-доброе arduino nano. Хотя хотелось взять более мелкий вариант на tiny85 — digispark. Но у digispark есть вопросы с выводом информации в serial, поэтому пока arduino nano.

На ИБП есть выход с названием «Monitor», и интуитивно понятно, что он должен что-то говорить о напряжении на ибп. Однако, найти документацию по этому выходу либо ответ на вопрос через соц. сети не удалось. Тем не менее, опытным путем было выяснено, что на ноге «low bat» этого монитора возникает напряжение, когда батареи ибп разряжены. При этом на самом ибп загорается светодиод.

Выглядит это примерно так (результат работы скетча из данной статьи):



Поэтому, эту информацию и будем мониторить.

Arduino nano скетч


Так как в бой пойдет arduino nano, то и скетч для нее.

int analogPin = A0;                 // Указываем порт OUT датчика 

const int averageValue = 500;       // Переменная для хранения значения количества считывания циклов 
long int sensorValue = 0;           // Переменная для хранения значения с датчика

float voltage = 0;                  // Переменная для хранения значения напряжения
float current = 0;                  // Переменная для хранения значения тока

void setup() 
{
  Serial.begin(9600);               //  Открываем последовательную связь на скорости 9600
}

void loop() 
{
  for (int i = 0; i < averageValue; i++)    // Повторяем цикл
  {
    sensorValue += analogRead(analogPin);   // Считываем и записываем показания
    delay(2);                               // Пауза 2 мкс
  }

  sensorValue = sensorValue / averageValue; // Делим полученное значение 
  voltage = sensorValue * 5.0 / 1024.0;     // Расчет напряжения
  current = (voltage - 2.5) / 0.185;        // Расчет тока

  Serial.print("ADC Value: ");              // Отправка данных в последовательный порт
  Serial.print(sensorValue);

  Serial.print("   ADC Voltage: ");         // Отправка данных в последовательный порт
  Serial.print(voltage);                    // Отправка напряжения
  Serial.print("V");                        // Отправка данных в последовательный порт

  Serial.print("   Current: ");             // Отправка данных в последовательный порт
  Serial.print(current);                    // Отправка тока
  Serial.println("A");                      // Отправка данных в последовательный порт
}

Тут все предельно просто и щедро усыпано комментариями. Все, что нужно знать A0 — pin arduino, сама arduino подключается через usb port к raspberry.

Кстати, некоторые китайские nano не шьются простым методом через arduino ide и на них надо
зажимать reset во время компиляции скетча и отпускать его когда в ide загорится «загрузка».
Естественно, это связано не содержанием скетча, а со спецификой arduino nano от ali.

Соединяем компоненты


Как уже упоминалось, для мониторинга используется датчик напряжения (Voltage Sensor):


и лучше брать без распаянных клемм, т.к. они слишком громоздкие. Сам датчик неприхотлив, однако дает искажения при воздействии на него магнитом и другими непотребными вещами.

Итак GND порта Monitor ИБП -> "-" клемма(!) датчика,
«low bat» порта Monitor ИБП -> "+" клемма(!) датчика.
«S» датчика -> A0 arduino nano,
"+" датчика -> 5V arduino nano,
"-" датчика -> GND arduino nano.



Часть raspberry pi


Пришла очередь raspberry pi, на которую через usb port от arduino будут приходить показания с датчика.

После подключения arduino для начала проверим, что вообще что-то приходит в порт:

#! /usr/bin/env python
# coding: utf-8
import serial
#python -m serial.tools.list_ports 
ser = serial.Serial("/dev/ttyS0",baudrate=9600,timeout=0.1)
ser.flush()
print(ser)
while True :
    line = ser.readline().decode().strip()
    if line:
        print(line)
ser.close()

Если в консоль выводятся значения, то все в порядке. Если нет, то необходимо поменять порт "/dev/ttyS0".

Теперь напишем основной скрипт, который будет делать всю работу и добавим его в servicemd.
Содержание следующее:

#! /usr/bin/env python
# coding: utf-8
import schedule
 
#speech speak
from rhvoice_wrapper import TTS;import subprocess
from time import sleep

###языковая часть
def say(text):
    data = tts.get(text, format_='wav') 
    #print('data size: ', len(data), ' bytes')
    subprocess.check_output(['aplay', '-r','16000','-q'], input=data) 

tts = TTS(threads=1)

import serial
#python -m serial.tools.list_ports 
#ser = serial.Serial("/dev/ttyS0",baudrate=9600,timeout=0.1)
def func():
    """open serial every ___ min and check voltage drop"""
    ser = serial.Serial("/dev/ttyUSB0",baudrate=9600,timeout=0.1)
    ser.flush()
    #print(ser)    
     
    while True :
        try:
            line = ser.readline().decode().strip()
            if line:
                print(line.split(' ')[7].strip('V'))
                if float(line.split(' ')[7].strip('V')) >0.5:
                    print('разряжено')
                    say('разряжено')                
                    ser.close()
                    return False
        except:
            pass    
    ser.close()


if __name__ == "__main__":
    schedule.every(1).minutes.do(func)
  
    while True:
        schedule.run_pending()
        sleep(1)

В части озвучивания используется rhvoice_wrapper, который нужно будет установить через pip install.

Также понадобится schedule, который тоже через pip install.

Суть скрипта — каждую минуту открывается serial порт, читается значение датчика и если оно больше 0.5 (if float(line.split(' ')[7].strip('V')) >0.5), то произносится «разряжено».

Каждую минуту делать это не обязательно, поэтому в строке schedule.every(1).minutes.do(func) можно поменять значение.

Теперь добавим этот скрипт в servicemd raspberry pi:

sudo nano /etc/systemd/system/charge_check.service

И внесем содержимое:

[Unit]
Description=charge_check   
After=multi-user.target
[Service]
User=pi
Type=simple
Restart=always
RestartSec=5
WorkingDirectory=/home/pi/Desktop
ExecStart=/usr/bin/python3 charge_check.py
[Install]
WantedBy=multi-user.target

Если скрипт лежит не на Deskrop и называется не charge_check.py, то эти моменты нужно поправить.

Перезагружаем демонов и запускаем свой сервис:

sudo systemctl daemon-reload
sudo systemctl enable charge_check.service
sudo systemctl start charge_check.service

Проверим, что сервис работает:



Если сервис не работает, то можно посмотреть, что ему не нравится следующей командой:

journalctl -u charge_check.service -b

На этом все, спасибо за внимание.

Комментарии (13)


  1. VelocidadAbsurda
    01.09.2022 14:38
    +2

    С софтом - здорово, но с железом - так делать (использовать датчик тока для опроса напряжения) нельзя.

    Вас не взволновали показания тока в 10А? Сам датчик тока между выходом, выдающим напряжение и землёй, эквивалентен замыканию этого пина на землю. В зависимости от того, что является источником напряжения на пине, эти 10А могут быть вполне реальными (аккумулятору они по силам, а дальше что-то сгорит).


    1. zoldaten Автор
      01.09.2022 14:53

      думаю все же, что это просто мусорная информация, т.к. при разомкнутом состоянии датчика со стороны клемм, показывает также "Current: -13.51A"


      1. Javian
        01.09.2022 15:10

        А измерять напряжение вместо тока не пробовали?


        1. zoldaten Автор
          01.09.2022 15:32

          Именно оно и измеряется, используется voltage sensor, не датчик тока acs712! На картинках он (voltage sensor).
          В коде ничего не меняется, и можно его упростить в этой связи. Спасибо, что заметили.
          Вот что значит долго разглядывать acs712!


          1. Javian
            01.09.2022 17:08
            +1

            Не заметить было сложно - это было единственное упоминание модели "датчика". После чтения даташит стало еще непонятнее как же это работает и что считывается.
            Судя по фото этот "voltage sensor" датчик представляет собой просто делитель на двух резисторах.


      1. Woffko
        01.09.2022 16:43

        По хорошему бы узнать какое напряжение поступает на ардуино и уже это значение умножать на коэффициент резисторного от делителя(voltage sensor представляет из себя резисторный делитель).

        Сила тока тут расчётная от напряжения, причём почему рассчитывается именно так не понятно (current = (voltage - 2.5) / 0.185; когда на V=0 это будет -2,5/0,185)

         Скорее всего на гребенку дублируются выводы с светодиодов индикации и там просто будет подниматься высокий уровень.


  1. Sergey78
    01.09.2022 17:22
    +1

    Не проще взять внешний АЦП и использовать его вместо ардуины? В ядре есть поддержка разных АЦП, можно выбрать что-то простое. Вся работа с АЦП будет сведена к чтению из "файла" значений. Ну можно и на питоне конечно, если у вас много батарей :)

    Можно АЦП с функцией оповещения о изменении уровня и просто по прерыванию с gpio реагировать. На вскидку не подскажу конкретной модели, но не думаю, что это редкость. Кстати, возможно в ядре есть уже готовый драйвер, который за значением АЦП следит.

    Если вас интересует только превышение конкретного уровня напряжения (выше-ниже), а не его значение, можно и без АЦП вообще обойтись, решив задачу вообще пассивными элементами. Просто получая на gpio 1 или 0.


    1. Sergey78
      01.09.2022 17:26

      Вероятно подойдет вам ADS1115. Есть на али готовые платы, есть выход с компаратора, есть драйвер в ядре.


  1. kiltum
    02.09.2022 14:52
    +1

    Ярчайший пример, как не надо делать.

    1) Заставить ардуинку спамить в порт каждые несколько миллисекунд - отличное решение

    2) Заставить писать человекочитаемым текстом, что бы потом его парсить - офигенное

    3) Зачем-то прилепить лишний датчик тока к ардуинке - гениально.

    4) Зачем-то вообще взять ардуинку, мерять не понятно что, вместо использования двух (!!!) резисторов для получения бинарного сигнала - достойно войти в лучшие анналы костылестроения.

    (аж плакать хочется)

    Правильное решение навскидку:

    1) замерить уровень low battery

    2) в любом калькуляторе (гугл voltage divide calculator) подобрать номиналы резисторов, что бы на любую свободную ножку GPIO приходило 3в

    3) написать одну микропрограммку "на ножке 1 - играем файл"

    4) добавить одну строчку в crontab, что бы звалось раз в минуту.

    Все :)


    1. zoldaten Автор
      02.09.2022 15:42

      1. Кто вам мешает delay поменять ? Кроме того, порт не обязательно открытым держать.

      2. А с чего вы взяли, что c arduino только это будет сыпаться ?

      3. Точно, 30 руб. не лишние.

      4. См. п. 2 + отсутствие возможности озвучивания, например "низкий заряд" + "разряжено".

        *Кроме того, возможность остаться без gpio raspberry, если на выходе прыгает напряжение, например, при замене батарей. Конденсатор ? Возможно.

        **Через cron не работает озвучивание, как ни странно. Ни через crontab -e, ни через sudo nano /etc/crontab.


      1. kiltum
        02.09.2022 16:21

        1. Вы автор этого безобразия и вы пытаетесь рассказать, как это надо делать, а не я. То, что ардуинка жрет миллиамперы (лень вспоминать/читать сколько) - вам даже в голову не пришло. А это батарейное питание все-таки.

        2. В статье про это ни слова. Но суть в другом, с одной стороны вы спамите, с другой стороны вы очищается все, что попало в буфер до и читаете строку. Потом ищите совпадения. На такой длинной строке и коротком совпадении вам удается поймать. Но когда параметров станет больше и строка тоже станет длинной, то вы будете чаще читать головы-хвосты сообщений с рандомными смещениями, а не сообщения целиком. И тогда начнутся боли. По этому не надо так. Совсем не надо. Если уж есть желание сделать нормально, то воспользуйтесь моделью клиент-сервер с нормальным протоколом. Например modbus - в него умееют все.

        3. Проблема в том, что тут 30 рублей, потом там .. в итоге потом то ножек не хватает, то питания, то еще чего-то.

        4. какой конденсатор? зачем?? если вы уж так боитесь высокого напряжения на ножке (хотя почему-то не боитесь остального типа "ардуинка сгорит"), ок добавьте защиту из еще одного элемента. гугл "ESD protection" и "overvoltage protection". Но вобще я подозреваю, что в этом "UPS" стоит банальный DC-DC со схемой зарядки и напряжений выше 5в там нет

        *** смотрите логи/дебаг. сильно подозреваю, что у вас разные окружения для питона под пользователем/кроном и питон не может найти нужного. просто aplay прекрасно работает под кронтабом, только что проверил под ubuntu 22


        1. zoldaten Автор
          02.09.2022 16:58

          1. сколько ж она ест ?

          Hidden text

          1. вовсе нет. достаточно timeout выставить при открытии порта. космические скорости тут не обязательны.
            3. ...
            4. нога raspberry может быть дороже, чем arduino. но это не повод для спора.

            *** да, смотрел. по логам все "ок". но звука нет. и root и pi в кроне побывали.


          1. kiltum
            02.09.2022 18:13

            1. У меня тут даже слов нет. Смените показометр. Гугл "arduino nano current consumption". там один usb-uart потребляет около 15мА. Мелочь на фоне потребления малинки, но из таких мелочей все и состоит.

            2. вы вообще не поняли про что речь. (и потом) и загуглите, что означает таймаут в функции открытия последовательного порта.