Обновить
16K+
5
Павел Деревянкин@FA72

Менеджер продукта MyQRcards

12
Рейтинг
3
Подписчики
Хабр КарьераХабр Карьера
Отправить сообщение

С этим вам к Mobian или postmarketOS

Буду следить за ситуацией, спасибо. В планах ещё сделать отдельную систему оповещения, которая будет срабатывать при прохождении опасных точек. А, возможно, и подключения-таки через внешнюю умную розетку. Вообще, начинаю думать, что можно совместить. Цикл 40-80 на контроллере питания с 45 градусами, а 20-90 сделать на умной розетке "на всякий случай" с границей в 50 градусов и системой оповещения, например, в бота в тг, чтобы если не отработал контроллер питания, было резервирование. Но пока я только с выходом из разряда проблемы испытывал.

Честно говоря, нет. Пытаюсь решить проблему минимальными вмешательствами пока.

А. Собственно, потому что подбирал разные значения. Да, пожалуй, это не имело смысла оставлять в таком виде. Спасибо, сейчас оставлю только в env. Заодно запушу изменения от воскресенья, которые тестировались, собственно, до сегодня. И подтвердили стабильность

В ходе подбора максимального тока на котором происходил запуск драйвера, в какой-то момент на этом значении заработало, так и осталось. Скорее, исторически. Учитывая, что у меня 10Вт зарядник, такой ток никогда не достигнется, конечно.

Нашёл один баг. Надо доработать кусок

if [ "$STATE" = "idle" ];
# =====================
    # STATE: idle
    # Driver has full control. We just watch bq27411/status.
    # When charging detected -> take over.
    # Safety: if cap drops to CHARGE_MIN, force charge ourselves
    #   (driver may be stuck after our previous writes).
    # =====================
    if [ "$STATE" = "idle" ]; then
        # Safety net: don't let battery die while waiting for driver
        if [ "$CAP" -le "$CHARGE_MIN" ]; then
            log "SAFETY: cap=${CAP}% <= ${CHARGE_MIN}% in idle -- forcing charge"
            enter_charging "$CAP"
            continue
        fi
        BQST=$(cat "$SYS_BQST" 2>/dev/null) || continue
        if [ "$BQST" = "Charging" ]; then
            log "CHARGER DETECTED (bq=${BQST}, cap=${CAP}%)"
            if [ "$CAP" -ge "$CHARGE_MAX" ]; then
                enter_paused "$CAP"
            else
                enter_charging "$CAP"
            fi
        fi
        continue
    fi

Тоже интересный вариант! Но всё же, кажется, что ddr3 память и представленный процессор сильно слабее oneplus 6 (нашёл цифру на antutu - 276510), который мне встал немногим дороже. Но, честно говоря, основной идеей была в целом проверка того, насколько просто поставить сервак на телефон и что из этого получится. Я даже немного расстроился, что вышло так просто, потому что рассчитывал поковыряться.

Так что можно сказать, что адекватные простые хорошие решения меня интересовали мало =)

Если я правильно понимаю физику процесса, взрыв всё же происходит, когда аккумулятор уже вздулся и на крайней стадии деградации. Он тем менее вероятно произойдёт, чем лучше состояние батареи, ниже ток, ниже температура, ниже заряд и легче режим работы для аккумулятора. Потому как по факту взрыв - это крайняя стадия распирания внутренними газами или тотальный перегрев.

Да, исключить такой сценарий полностью при постоянном подключении, наверное, нельзя, но, во-первых, в описанной схеме подключение именно что непостоянное (да, кабель воткнут, но батарея питается только, когда идёт заряд). Во-вторых, вероятность взрыва аккумулятора при удержании заряда в цикле 40/80 на низком питании с защитой по температуре, пожалуй, сопоставимо, если не ниже, чем вероятность взрыва аккумулятора обычного смартфона при зарядке, тем более с использованием всяких fastCharge. Да, такие случаи происходят, но это, скорее, исключение, чем правило.

Мне кажется, что основное требование теперь - ревизия "сервера" раз в месяц на предмет отсутствия видимого вздутия. Если оно всё же будет обнаружено - в этот момент, да, риски кратно возрастают и стоит на это как-то реагировать. Как я и сказал выше, первостепенно рассматриваю, если такое произойдёт, просто замену батареи или, если будет желание поэкспериментировать, впаивание схемы-обманки для постоянного питания.

В общем, я чуть-чуть изучил вопрос, управление на стороне устройства зарядкой выглядит немного костыльным. Потому что, если начинаешь управлять зарядом программно, то драйвер зарядки "тупеет" и перестаёт показывать реальное состояние кабеля - подключен/отключен. Он стандартно всегда считает, что зарядка подключена (и на входе просто 0,01 А). Я накидал в итоге такой скрипт управления, который позволяет и состояние заряда проверять и температуру контролировать и при этом всём раз в 15 минут проверять идёт зарядка или нет.

battery-limiter.service
[Unit]
Description=Battery charge limiter (40%%-80%%)
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/local/bin/battery-limiter.sh
# Re-enable charging if service stops so phone doesn't die
ExecStopPost=/bin/sh -c 'echo 500000 > /sys/class/power_supply/pmi8998-charger/current_max'
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
battery-limiter.sh
#!/bin/bash
# battery-limiter.sh v9 -- keeps OnePlus 6 battery between 40% and 80%
# Deployed as systemd service: battery-limiter.service
#
# CONSTRAINT: any write to pmi8998-charger/current_max permanently
# corrupts the driver's readback (online/status/voltage_now freeze).
# All sensing uses bq27411-0 fuel gauge (independent i2c chip).
#
# Three states:
#   charging -- current_max=500mA, battery charging toward 80%
#   paused   -- current_max=0, battery discharging toward 40%
#               every 15 min: probe cable by restoring driver for 10s
#   idle     -- driver has full control, cable is disconnected
#               poll bq27411/status every minute to detect charger
#
# Temp safety: pause at 45C, resume below 40C
set -uo pipefail

CHARGE_MAX=80
CHARGE_MIN=40
CHARGE_CURRENT=500000   # 500mA
TEMP_MAX=450             # 45.0C
TEMP_RESUME=400          # 40.0C
CHECK_INTERVAL=60        # 1 minute
PROBE_CYCLES=15          # 15 cycles = 15 min between cable probes
PROBE_SETTLE=10          # seconds to let driver settle during probe
HEARTBEAT_CYCLES=5       # 5 cycles = 5 min

SYS_CMAX="/sys/class/power_supply/pmi8998-charger/current_max"
SYS_CAP="/sys/class/power_supply/bq27411-0/capacity"
SYS_TEMP="/sys/class/power_supply/bq27411-0/temp"
SYS_CUR="/sys/class/power_supply/bq27411-0/current_now"
SYS_BQST="/sys/class/power_supply/bq27411-0/status"

STATE="idle"
TEMP_LOCKED=0
PROBE_CTR=0
HEARTBEAT_CTR=0

log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1"; }
fmt_temp() { echo "$((${1}/10)).$((${1}%10))C"; }

write_cmax() {
    if ! echo "$1" > "$SYS_CMAX" 2>/tmp/cmax_err; then
        log "WARN: write current_max=$1 failed: $(cat /tmp/cmax_err)"
    fi
}

enter_charging() {
    write_cmax "$CHARGE_CURRENT"
    STATE="charging"
    PROBE_CTR=0
    log "CHARGING (${CHARGE_CURRENT}uA, cap=${1}%)"
}

enter_paused() {
    write_cmax 0
    STATE="paused"
    PROBE_CTR=0
    log "PAUSED (cap=${1}%)"
}

enter_idle() {
    # Give control back to driver -- write a normal value so it
    # can re-negotiate, then hands off.
    write_cmax "$CHARGE_CURRENT"
    STATE="idle"
    PROBE_CTR=0
    log "IDLE -- driver has control (cable absent)"
}

# --- Validate sysfs ---
for node in "$SYS_CMAX" "$SYS_CAP" "$SYS_TEMP" "$SYS_CUR" "$SYS_BQST"; do
    [ -e "$node" ] || { log "ERROR: missing $node"; exit 1; }
done

log "Starting: range ${CHARGE_MIN}%-${CHARGE_MAX}%, temp $(fmt_temp $TEMP_MAX), probe every ${PROBE_CYCLES}min"

# --- Initial state: start idle, let first cycle decide ---
CAP=$(cat "$SYS_CAP" 2>/dev/null) || CAP=50
BQST=$(cat "$SYS_BQST" 2>/dev/null) || BQST="Unknown"
if [ "$BQST" = "Charging" ]; then
    # Charger is connected right now
    if [ "$CAP" -ge "$CHARGE_MAX" ]; then
        enter_paused "$CAP"
    else
        enter_charging "$CAP"
    fi
else
    log "IDLE -- no charge detected at start (bq=${BQST}, cap=${CAP}%)"
fi

# --- Main loop ---
while true; do
    sleep "$CHECK_INTERVAL"

    CAP=$(cat "$SYS_CAP" 2>/dev/null)  || continue
    TEMP=$(cat "$SYS_TEMP" 2>/dev/null) || continue
    CUR=$(cat "$SYS_CUR" 2>/dev/null)  || continue

    # --- Heartbeat ---
    HEARTBEAT_CTR=$((HEARTBEAT_CTR + 1))
    if [ "$HEARTBEAT_CTR" -ge "$HEARTBEAT_CYCLES" ]; then
        HEARTBEAT_CTR=0
        BQST=$(cat "$SYS_BQST" 2>/dev/null) || BQST="?"
        log "STATUS state=${STATE} cap=${CAP}% temp=$(fmt_temp $TEMP) cur=${CUR}uA bq=${BQST}"
    fi

    # --- Temperature safety (always wins) ---
    if [ "$TEMP" -ge "$TEMP_MAX" ] && [ "$STATE" = "charging" ]; then
        log "OVERHEAT $(fmt_temp $TEMP) -- pausing"
        TEMP_LOCKED=1
        enter_paused "$CAP"
        continue
    fi
    if [ "$TEMP_LOCKED" = "1" ] && [ "$STATE" != "idle" ]; then
        if [ "$TEMP" -lt "$TEMP_RESUME" ]; then
            log "COOLED $(fmt_temp $TEMP) -- temp lock off"
            TEMP_LOCKED=0
        else
            # Stay paused/enforce while hot
            if [ "$STATE" = "paused" ] && [ "$CUR" -gt 0 ]; then
                write_cmax 0
                log "ENFORCE 0 (temp lock, cur=${CUR}uA)"
            fi
            continue
        fi
    fi

    # =====================
    # STATE: idle
    # Driver has full control. We just watch bq27411/status.
    # When charging detected -> take over.
    # =====================
    if [ "$STATE" = "idle" ]; then
        BQST=$(cat "$SYS_BQST" 2>/dev/null) || continue
        if [ "$BQST" = "Charging" ]; then
            log "CHARGER DETECTED (bq=${BQST}, cap=${CAP}%)"
            if [ "$CAP" -ge "$CHARGE_MAX" ]; then
                enter_paused "$CAP"
            else
                enter_charging "$CAP"
            fi
        fi
        continue
    fi

    # =====================
    # STATE: charging
    # current_max=500mA, charging toward 80%
    # =====================
    if [ "$STATE" = "charging" ]; then
        if [ "$CAP" -ge "$CHARGE_MAX" ]; then
            enter_paused "$CAP"
        fi
        # If driver reset current_max on re-plug and current is
        # flowing but higher than expected, that's fine -- we're
        # charging anyway. No enforce needed in this state.
        continue
    fi

    # =====================
    # STATE: paused
    # current_max=0, discharging toward 40%
    # Enforce if driver resets current_max (detect via positive current).
    # Probe cable every PROBE_CYCLES.
    # =====================
    if [ "$STATE" = "paused" ]; then

        # Resume charging at 40%
        if [ "$CAP" -le "$CHARGE_MIN" ]; then
            enter_charging "$CAP"
            continue
        fi

        # Enforce: if battery is gaining charge, driver must have
        # reset current_max (USB re-negotiation on cable insert)
        if [ "$CUR" -gt 0 ]; then
            write_cmax 0
            log "ENFORCE 0 (driver reset, cur=${CUR}uA)"
            PROBE_CTR=0   # just wrote -- reset probe timer
            continue
        fi

        # Periodic cable probe
        PROBE_CTR=$((PROBE_CTR + 1))
        if [ "$PROBE_CTR" -ge "$PROBE_CYCLES" ]; then
            PROBE_CTR=0
            log "PROBE: restoring driver for ${PROBE_SETTLE}s ..."
            write_cmax "$CHARGE_CURRENT"
            sleep "$PROBE_SETTLE"
            CUR2=$(cat "$SYS_CUR" 2>/dev/null) || CUR2=-1
            if [ "$CUR2" -gt 0 ]; then
                # Cable still connected -- re-pause
                write_cmax 0
                log "PROBE: cable connected (cur=${CUR2}uA) -- staying paused"
            else
                # Cable gone -- hand off to driver
                enter_idle
            fi
        fi
        continue
    fi
done

Мне кажется, лучшее и самое правильное решение для сохранения жизни аккумулятора - это такая же схема, но с умной розеткой. Путь такой:
Телефон только контролирует своё состояние заряда. Если нужно зарядится (ниже 40%) - отправляется сигнал умной розетке, она врубается. Если нужно перейти в разрядку (выше 80% или перегрев) - тоже сигнал розетке, она вырубается. При этом розетка должна управляться по локальной сети, чтобы, если интернет-соединение потеряется, телефон не мог уйти в перегрев.

Ну и да, всё же рано или поздно менять батарею или удалять её в любом случае придётся. Когда момент придёт - я задумаюсь, что именно из этого хочу сделать. Но пока пусть живёт в таком режиме.

Скажу честно, именно касательно вздутия особо не думал, но понимал, что деградация батареи и перегрев - довольно большие проблемы для данного решения. Я уже купил под него отдельный 10-ваттный адаптер, чтобы питание шло не с очень большим током и начал изучать вопрос ограничения заряда, чтобы батарея держалась в зоне 40-80 процентов. Но пока до конца это не реализовал.

Вообще, честно говоря, я готов пойти даже на радикальные меры вплоть до перепайки платы питания, чтобы полностью удалить батарею из устройства (например, прямо сейчас нашёл, как это сделали с аналогичным устройством вот тут -https://blog.kedio.co/post/how-to-run-a-oneplus-6t-without-battery/ с примерно той же мотивацией). Это, конечно, чуть-чуть ухудшит "надёжность сервера", что в нём по сути есть встроенный бесперебойник.

Но на текущий момент план - настроить потребление-разряд, чтобы держать в безопасном диапазоне, не перегревать, смотреть, что будет дальше. Думаю, что буду "ревизировать" устройство на состояние батареи раз в какое-то время (месяц) и, если проблема всё же возникнет, то уже ставить вопрос о "хирургическом вмешательстве". Либо с целью замены, либо с целью удаления батареи.

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

Информация

В рейтинге
655-й
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Фулстек разработчик, Менеджер продукта
Средний
От 220 000 ₽
Amplitude
Управление продуктами
A/B тестирование
Продуктовая аналитика
Разработка продуктовой стратегии
Приоритизация
Unit-экономика
.NET
Управление людьми
Планирование