Всем привет, меня зовут Алексей Ляховский, я на протяжение последних 10 лет занимаюсь изучением, разработкой и развитием экосистемы часов Xiaomi для глобального сообщества.

Сегодня у меня в работе самый популярный продукт линейки часов Xiaomi - Mi Band 9.
Предыдущее поколение часов этой серии, но это не так важно, поскольку текущий Mi Band 10 не сильно отличается от нашего обозреваемого пациента.

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

Temperature overheat shutdown
Temperature overheat shutdown

Что же под капотом

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

Во-первых, с 9ки Xiaomi стали использовать китайские процессоры BES2700, BES2800 компании Bestechnic, они достаточно производительные, имеют прилично flash памяти, в нашем случае 8Mb, PSRAM 16Mb, графический ускоритель, интегрированный BT контроллер и куча разной периферии.

Блок схема BES2700iMP
Блок схема BES2700iMP

Дополнительно у mb9 имеется 256Mb nand flash памяти для системы и хранения данных.

Так называемая HyperOS тут - это операционная система - Apache NuttX, очень приличная realtime OS уровня простенького Lunix, c shell на борту и пакетом поставляемых команд (вот тут можно ознакомиться подробнее - https://nuttx.apache.org/docs/latest/applications/nsh/index.html)
Например bootloader на базе NuttX для mb9 занимает всего 128Kb.

Также, система HyperOS, которую Xiaomi еще называет VelaOS (она так и называлась, до глобального переименовывания всех продуктов под HyperOS) включает в себя 2 движка для приложений: Lua и AiotJS (в девичестве JerryScript). Графический интерфейс системы реализован на Lvgl - очень популярная графическая библиотека в embedded (подробнее - https://lvgl.io/)

Движок AiotJS используется для приложений, а движок Lua для циферблатов.
Lua в данных часах версии 5.4 с библиотекой lvgl, собственно чтобы можно было рисовать свои интерфейсы в циферблатах.

Анализ прошивки браслета

Чтобы понять что и как можно сделать с данной проблемой перегрева,
я провел анализ прошивки с помощью всеми любимой IDA Pro.
В детали вдаваться не буду, это отдельная тема для разговоров.
Вот что я обнаружил в итоге:

Функция system_callback_temp
int __fastcall system_callback_temp(float **_temp)
{
  float board_temp; // s16
  int result; // r0
  int v3; // r4
  int v4; // r4
  __int64 v5; // [sp+10h] [bp+0h] BYREF

  board_temp = **_temp;
  unk_200A509F = dword_2009D6E0[31];
  unk_200A52A0 = unk_200A529C;
  time_diff = get_timestamp(&v5) - body_hand_wrist_time;
  result = syslog(
             3,
             "[%s] %s: callback_temp called %d %d %d\n",
             "system",
             "callback_temp",
             unk_200A52A0,
             unk_200A529C,
             *(_DWORD *)&is_watch_is_on_hand);
  if ( *(_DWORD *)&is_watch_is_on_hand == 1 )
  {
    if ( cool_strategy_43_copy && board_temp < 41.0 )
    {
      cool_strategy_43_copy = 0;
      cool_strategy_43 = 0;
      result = syslog(4, "[%s] %s: Exit the 43-degree cooling strategy\n", "system", "callback_temp");
    }
    if ( cool_strategy_41_copy && board_temp < 39.0 )
    {
      cool_strategy_41_copy = 0;
      cool_strategy_41 = 0;
      result = syslog(4, "[%s] %s: Exit the 41-degree cooling strategy\n", "system", "callback_temp");
    }
    if ( temp_strategy_autobritghness_off && board_temp < 37.0 )
    {
      temp_strategy_autobritghness_off = 0;
      cool_strategy_39 = 0;
      if ( is_auto_brightness )
        sub_2C442BA8(1, &cool_strategy_39);
      result = syslog(4, "[%s] %s: Exit the 39-degree cooling strategy\n", "system", "callback_temp");
    }
    if ( board_temp >= 39.0 && time_diff > 179 )
    {
      if ( !cool_strategy_39 )
      {
        cool_strategy_39 = 1;
        syslog(4, "[%s] %s: Start the 39-degree cooling strategy\n", "system", "callback_temp");
        result = system_ui_and_strategy(39);
      }
      if ( board_temp >= 41.0 )
      {
        v3 = cool_strategy_41;
        if ( !cool_strategy_41 )
        {
          cool_strategy_41 = 1;
          syslog(4, "[%s] %s: Start the 41-degree cooling strategy\n", "system", "callback_temp");
          cool_strategy_41_prev = v3;
          cool_strategy_41_val = 41;
          cool_strategy_41_started = 1;
          result = temp_relative_alarm(v3, (int)&cool_strategy_41_val);
        }
        if ( board_temp >= 43.0 )
        {
          v4 = cool_strategy_43;
          if ( !cool_strategy_43 )
          {
            cool_strategy_43 = 1;
            syslog(4, "[%s] %s: Start the 43-degree cooling strategy\n", "system", "callback_temp");
            cool_strategy_43_prev = v4;
            cool_strategy_43_val = 43;
            cool_strategy_43_started = 1;
            return temp_relative_alarm(v4, (int)&cool_strategy_43_val);
          }
        }
      }
    }
  }
  else
  {
    if ( cool_strategy_45 && board_temp < 46.0 )
    {
      cool_strategy_45 = 0;
      result = syslog(4, "[%s] %s: Exit the 48-degree cooling strategy\n", "system", "callback_temp");
    }
    if ( cool_strategy_48 && board_temp < 43.0 )
    {
      cool_strategy_48 = 0;
      result = syslog(4, "[%s] %s: Exit the 45-degree cooling strategy\n", "system", "callback_temp");
    }
    if ( board_temp >= 45.0 )
    {
      if ( !cool_strategy_45 )
      {
        cool_strategy_45 = 1;
        cool_strategy_45_val = 45;
        temp_relative_alarm(600, (int)&cool_strategy_45_val);
        result = syslog(4, "[%s] %s: Start the 45-degree cooling strategy\n", "system", "callback_temp");
      }
      if ( board_temp >= 48.0 && !cool_strategy_48 )
      {
        cool_strategy_48 = 1;
        cool_strategy_48_val = 48;
        temp_relative_alarm(60, (int)&cool_strategy_48_val);
        return syslog(4, "[%s] %s: Start the 48-degree cooling strategy\n", "system", "callback_temp");
      }
    }
  }
  return result;
}
Функция system_ui_and_strategy
int __fastcall system_ui_and_strategy(int temp)
{
  int result; // r0
  int v2; // r4
  __int64 v3; // r0

  result = temp - 39;
  switch ( result )
  {
    case 0:
      syslog(3, "[%s] %s: 39 temp will set brightness\n", "system", "ui_and_strategy");
      is_auto_brightness = is_auto_brightness_0;
      syslog(
        6,
        "[%s] %s:  is_auto_brightness:%d",
        "system",
        "brightness_temp_charge",
        (unsigned __int8)is_auto_brightness_0);
      v2 = *(_DWORD *)&brightness_level;
      if ( is_auto_brightness )
      {
        v3 = syslog(
               6,
               "[%s] %s:  brightness_current_now:%d",
               "system",
               "brightness_temp_charge",
               *(_DWORD *)&brightness_level);
        brightness_set_auto_adjustment(0, HIDWORD(v3));
        if ( v2 <= 128 )
        {
          if ( (unsigned int)(v2 - 0x20) > 96 )
          {
            brightness_set_value(31);
            syslog(6, "[%s] %s: thermal set brightness to 31\n", "system", "brightness_temp_charge");
          }
          else
          {
            brightness_set_value(v2);
            v2 = *(_DWORD *)&brightness_level;
            syslog(
              6,
              "[%s] %s: thermal set brightness to %d\n",
              "system",
              "brightness_temp_charge",
              *(_DWORD *)&brightness_level);
          }
        }
        else
        {
          brightness_set_value(128);
          v2 = *(_DWORD *)&brightness_level;
          syslog(6, "[%s] %s: thermal set brightness to 128\n", "system", "brightness_temp_charge");
        }
        result = syslog(
                   6,
                   "[%s] %s:  auto_brightness:ON->OFF brightness_current_after :%d",
                   "system",
                   "brightness_temp_charge",
                   v2);
      }
      else
      {
        result = syslog(
                   6,
                   "[%s] %s:  brightness_current_now :%d",
                   "system",
                   "brightness_temp_charge",
                   *(_DWORD *)&brightness_level);
        if ( v2 > 128 )
        {
          apply_brightness(128);
          result = syslog(
                     6,
                     "[%s] %s:  brightness_current_now afer :%d",
                     "system",
                     "brightness_temp_charge",
                     *(_DWORD *)&brightness_level);
        }
      }
      temp_strategy_autobritghness_off = 1;
      return result;
    case 2:
      if ( cool_strategy_41_started == 2 )
      {
        syslog(3, "[%s] %s:  41 temp start reminder window shutdown\n", "system", "ui_and_strategy");
        goto LABEL_16;
      }
      return result;
    case 4:
      syslog(3, "[%s] %s:  43 temp start reminder window shutdown\n", "system", "ui_and_strategy");
      goto LABEL_16;
    case 6:
      syslog(3, "[%s] %s:  45 temp start reminder window shutdown\n", "system", "ui_and_strategy");
      goto LABEL_16;
    case 9:
      syslog(3, "[%s] %s:  48 temp start reminder window shutdown\n", "system", "ui_and_strategy");
LABEL_16:
      if ( paired_flag == 2 )
      {
        sub_2C1641D4(3u);
        result = system("poweroff");
      }
      else
      {
        result = temp_rise_reminder(0);
      }
      break;
    default:
      return result;
  }
  return result;
}

Как видно из кода, у алгоритма мониторинга на пороге 39 градусов Цельсия выключает автоматический уровень яркости, а затем на порогах 41, 43, 45 выключает браслет, если браслет на руке, когда же он лежит отдельно - пороги отключения 45, 48.

Посмотрим внимательно в код, запуск уведомления системы и последующее выключение происходит в методе temp_rise_reminder(int)

Функция temp_rise_reminder
int __fastcall temp_rise_reminder(int prm)
{
  int v1; // r0
  int v2; // r4
  int result; // r0
  char _prm; // [sp+7h] [bp+7h] BYREF
  int v5[40]; // [sp+8h] [bp+8h] BYREF

  _prm = prm;
  syslog(6, "[%s] %s: enter temp reminder \n", "temp_reminder", "temp_rise_reminder");
  v1 = malloc_(1);
  v2 = v1;
  if ( !v1 )
    return syslog(3, "[%s] %s: temp data malloc fail\n", "temp_reminder", "temp_rise_reminder");
  memcpy_(v1, &_prm, 1);
  result = memset(&v5[1], 0, 156);
  v5[0] = 0x220008;
  v5[7] = -1;
  LOWORD(v5[3]) = 2049;
  v5[10] = temp_reminder_create_cb;
  v5[2] = v2;
  v5[11] = temp_reminder_delete_cb;
  BYTE1(v5[9]) = -94;
  v5[14] = temp_reminder_cb;
  if ( !temp_reminder_in_progress )
  {
    result = lvx_reminder_start(v5);
    if ( !result )
    {
      result = syslog(3, "[%s] %s: High and low temperature startup failure\n", "temp_reminder", "temp_rise_reminder");
    }
  }
  return result;
}

и в данном методе есть замечательный флаг temp_reminder_in_progress, предотвращающий повторный вызов уведомления. Дак вот, его замечательность в том, что это статическая константа в памяти, линкер прошивки выделил ей статическое место в SRAM.

Расположение в SRAM
Расположение в SRAM

И это прекрасно, фокус в том, что прописав в эту переменную значение отличное от нуля, метод никогда не запустит уведомление, поскольку будет думать, что уведомление уже запущено.

Заодно найдем переменную в SRAM где хранится значение температуры браслета.
Вот код отвечающий за это

Функция get_temperature_finally
int __fastcall get_temperature_finally(int a1, int a2, int a3)
{
  int handle; // r0
  int v4; // r3
  int _handle; // r4
  __int16 temp; // [sp+Eh] [bp+6h] BYREF

  temp = 0;
  handle = open("/dev/charge", 65, a3, 0);
  _handle = handle;
  if ( handle < 0 )
    return syslog(3, "[%s] %s: Failed to open charger device\n", "system", "get_temperature_finally");

if ( fb_ioctl(handle, 0xE0A, (int)&temp, v4) < 0 )
    return syslog(3, "[%s] %s: failed to get temperature!", "system", "get_temperature_finally");
  *(float *)&board_temp_val = (float)temp;
  syslog(3, "[%s] %s: temp_reminder now temp is %d", "system", "get_temperature_finally", temp);
  write((int)board_temp, dword_200A52A8, (int)&unk_200A4E00, (int)&dword_200A52A8);
  return file_close(_handle);
}
Расположение в SRAM
Расположение в SRAM

и переменная температуры

Создаем циферблат на LUA

Использую полученные данные на предыдущем этапе, создадим Lua циферблат.Для работы с циферблатами я использую Xiaomi приложение Easyface с моим компилятором для нового формата циферблатов, в том числе для mb9 и mb10.

Проект циферблата в Easyface
Проект циферблата в Easyface

В нем идет линк на запускаемый файл проекта, все просто.
Главный код проекта выглядит так

main.lua
local lvgl = require("lvgl")
local temp = require("temperature")
local dataman = require("dataman")

local fsRoot = SCRIPT_PATH
local DEBUG_ENABLE = false

local selfFlag = false

local printf = DEBUG_ENABLE and print or function(...)
    end

local function imgPath(src)
    return fsRoot .. src
end

local rootbase = lvgl.Object(nil, {
        w = lvgl.HOR_RES(),
        h = lvgl.VER_RES(),
        bg_color = 0,
        bg_opa = lvgl.OPA(100),
        border_width = 0,
    })

rootbase:clear_flag(lvgl.FLAG.SCROLLABLE)
rootbase:add_flag(lvgl.FLAG.EVENT_BUBBLE)

local root = lvgl.Object(rootbase, {
        outline_width = 0,
        border_width = 0,
        pad_all = 0,
        bg_opa = 0,
        bg_color = 0,
        align = lvgl.ALIGN.CENTER,
        w = lvgl.HOR_RES(),
        h = lvgl.VER_RES(),
        flex = {
            flex_direction = "row",
            flex_wrap = "wrap",
            justify_content = "center",
            align_items = "center",
            align_content = "center",
        }        
    })

root:clear_flag(lvgl.FLAG.SCROLLABLE)
root:add_flag(lvgl.FLAG.EVENT_BUBBLE)

local font60 = lvgl.Font("MiSans-Regular", 60)
local font30 = lvgl.Font("MiSans-Regular", 30)
local font26 = lvgl.Font("MiSans-Regular", 26)

local function createText(wgt)
    return lvgl.Label(wgt, {
        text_font = font60,
        text = "Temp",
        align = lvgl.ALIGN.CENTER,
        border_color = '#eee',
        border_width = 0,
        text_color = '#eee'
        })
end

local time = lvgl.Label(root, {
    text_font = font26,
    text = "00:00",
    text_color = '#eee',
    pad_bottom = 5
})

local title = lvgl.Label(root, {
    text_font = font30,
    text = "Band Temp",
    text_color = '#eee',
    pad_bottom = 20
})
title:add_flag(lvgl.FLAG.EVENT_BUBBLE)

local txt = createText(root)
txt:add_flag(lvgl.FLAG.EVENT_BUBBLE)

local function setText(str)
    txt:set { text = str }
end

local temp1 = lvgl.Label(root, {
    text_font = font26,
    text = "Temperature",
    text_color = '#eee',
    pad_top = 40
})
local temp2 = lvgl.Label(root, {
    text_font = font26,
    text = "shutdowns",
    text_color = '#eee'
})
temp1:add_flag(lvgl.FLAG.EVENT_BUBBLE)
temp2:add_flag(lvgl.FLAG.EVENT_BUBBLE)

local installWd = lvgl.Checkbox(root, {
    text_font = font26,
    text = "turned off",
    text_color = '#eee',
    pad_top = 10
})

local tempEnabled = temp:isEnabled()

if tempEnabled then
    installWd:add_state(lvgl.STATE.CHECKED)
    installWd:set { text = "turned on"}
end

installWd:add_flag(lvgl.FLAG.CLICKABLE)
installWd:onevent(lvgl.EVENT.CLICKED, function(obj, code)

    if tempEnabled then
        temp:disable()
        installWd:set { text = "turned off"}
        tempEnabled = false
    else
        temp:enable()
        installWd:set { text = "turned on"}
        tempEnabled = true
    end

end)

dataman.subscribe("timeMinuteLow", time, function(obj, value)
    local t = os.time()
    local time_str = os.date("%H:%M", t)
    time:set { text = time_str }
end)

dataman.subscribe("timeSecond", txt, function(obj, value)
    setText(string.format("%.1f", temp:getTempFloat()))
end)

Здесь используется собственный модуль temperature, вот именно он и представляет самый большой интерес, остальное - это стандартный код циферблата.

temperature.lua
local watchVersion = require("watchVersion")
local memory = require("memory")
local math = require("math")

local temp = {
    g_temp_ptr = 0,
    g_reminder_ptr = 0,
    init_done = false
}

local bodyTempMapping = {
    ["miwear.watch.n66cn"] = {
        ["1.3.206"] = 0x200A15E8
    },
    ["miwear.watch.n66nfc"] = {
        ["1.3.206"] = 0x200A15E8
    },
    ["miwear.watch.n66tc"] = {
        ["1.3.206"] = 0x200A15E8
    },
    ["miwear.watch.n66gl"] = {
        ["2.3.97"] = 0x200A55A0
    },
    ["unknown"] = {
        ["version1"] = 0x20000000,
        ["version2"] = 0x20000000
    }
}

local tempReminder = {
    -- MiBand 9
    ["miwear.watch.n66cn"] = {
        ["1.3.206"] = 0x200A1174
    },
    ["miwear.watch.n66nfc"] = {
        ["1.3.206"] = 0x200A1174
    },
    ["miwear.watch.n66tc"] = {
        ["1.3.206"] = 0x200A1174
    },
    ["miwear.watch.n66gl"] = {
        ["2.3.97"] = 0x200A5150
    },
    ["unknown"] = {
        ["version1"] = 0x20000000,
        ["version2"] = 0x20000000
    }	
}

local function getTempValueByVersion(model, version)
    local modelVersions = bodyTempMapping[model]
    if modelVersions then
        return modelVersions[version]
    end
    return nil
end

local function getReminderValueByVersion(model, version)
    local modelVersions = tempReminder[model]
    if modelVersions then
        return modelVersions[version]
    end
    return nil
end

local function uint32_to_float(u)
    local sign = ((u >> 31) & 0x01)
    local exponent = ((u >> 23) & 0xFF)
    local mantissa = u & 0x7FFFFF

    if exponent == 255 then
        if mantissa == 0 then
            return sign == 1 and -math.huge or math.huge
        else
            return 0/0  -- NaN
        end
    end

    local value
    if exponent == 0 then
        -- denormalized
        value = (mantissa / 2^23) * 2^-126
    else
        -- normalized
        value = (1 + mantissa / 2^23) * 2^(exponent - 127)
    end

    return sign == 1 and -value or value
end

local function init()

    if temp.init_done then
        return
    end

    local model = watchVersion.get_model()
    local ver = watchVersion.get_version()
    
    local addr = getTempValueByVersion(model, ver)
    if addr ~= nil then
        temp.g_temp_ptr = addr
    end

    addr = getReminderValueByVersion(model, ver)
    if addr ~= nil then
        temp.g_reminder_ptr = addr
    end

    temp.init_done = true
end

function temp:readIntByAddress(addr)

    if addr == 0 then
        return
    end

    local maddr, res = memory:readAddr(addr)
    if res == "OK" then
        return maddr
    end

    return 0
end

function temp:getTemp()

    if not self.init_done then
        init()
    end

    return self:readIntByAddress(self.g_temp_ptr)

end

function temp:getTempFloat()

    if not self.init_done then
        init()
    end

    local res = self:readIntByAddress(self.g_temp_ptr)
    if res ~= 0 then
        return uint32_to_float(res)
    end

    return res
end

function temp:isEnabled()

    if not self.init_done then
        init()
    end

    local res = self:readIntByAddress(self.g_reminder_ptr)
    return res == 0
end

function temp:enable()

    if not self.init_done then
        init()
    end

    memory:writeAddr(self.g_reminder_ptr, 0x00000000)
end

function temp:disable()

    if not self.init_done then
        init()
    end

    memory:writeAddr(self.g_reminder_ptr, 0x01010101)
end

return temp

Вот именно тут нам понадобятся адреса, которые получилось добыть в результате анализа прошивки, надеюсь, тут тоже все понятно, остается самый интересный момент - как происходит обращение к памяти, чтение и запись, а это и есть та самая киллер фича NuttX shell.

Модуль memory тоже является самописным, посмотрим же как там все устроено

memory.lua
local memory = {
    tempFile = "/data/tmp_mem_".. os.date("%Y%m%d_%H%M%S")
}

local function reverse_uint32_bytes(n)
    local b1 = (n >> 24) & 0xFF
    local b2 = (n >> 16) & 0xFF
    local b3 = (n >> 8) & 0xFF
    local b4 = n & 0xFF

    return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1
end

function memory:readAddr(addr, byteEndianIsLittle)

    byteEndianIsLittle = byteEndianIsLittle or false

    local hexAddr = string.format("%x", addr)
    os.execute("mw 0x" .. hexAddr .. " > ".. self.tempFile)

    for line in io.lines(self.tempFile) do
        local value = string.match(line, "= 0x(%w+)")
        if value then
            dwordValue = tonumber(value, 16)
            if byteEndianIsLittle then
                dwordValue = reverse_uint32_bytes(dwordValue)
            end

            return dwordValue, "OK"
        end
    end

    return 0, "ERR_FAIL"
end

function memory:readBytes(addr)
    local hexAddr = string.format("%x", addr)
    os.execute("mw 0x" .. hexAddr .. " > " .. self.tempFile)

    for line in io.lines(self.tempFile) do
        local value = string.match(line, "= 0x(%w+)")
        if value then
            -- Convert the 4-byte integer to a number
            local intValue = tonumber(value, 16)

            -- Split the 4-byte integer into individual bytes (little-endian order)
            local byteArray = {}
            for i = 0, 3 do
                local byte = (intValue >> (i * 8)) & 0xFF
                table.insert(byteArray, byte)  -- Insert at the end for little-endian order
            end

            return byteArray, "OK"
        end
    end

    return {}, "ERR_FAIL"
end

function memory:readUtf8String(addr)
    local str = ""
    local i = 0
    while true do
        -- Read 4 bytes (1 integer) at the current address
        local bytes, status = self:readBytes(addr + i)
        if status ~= "OK" then
            return "", status
        end

        -- Process each byte in the 4-byte array
        for _, byte in ipairs(bytes) do
            if byte == 0 then  -- Stop at the null terminator
                return str, "OK"
            end
            str = str .. string.char(byte)  -- Append the byte as a character
        end

        i = i + 4  -- Move to the next 4-byte chunk
    end
end

function memory:writeAddr(addr, value)
    local hexAddr = string.format("%X", addr)
    local hexVal = string.format("%X", value)
    os.execute("mw 0x" .. hexAddr .. "=" .. hexVal)
end

return memory

Дак вот для работы с памятью используется стандартная команда NuttX shell - mw
https://nuttx.apache.org/docs/latest/applications/nsh/commands.html#mb-mh-and-mw-access-memory
Вот ее подробная документация,
если кратко, то чтение выглядит так

mw 0x200A5150 4
>> 0x200A5150 = 0x00000000

запись так

mw 0x200A5150=0x01010101

Соответственно все что нам нужно, чтобы отключить функционал уведомлений и выключений
это сохранить по адресу переменной temp_reminder_in_progress значений не нуль,
что и делает метод temp:disable() в помощью модуля memory:

memory:writeAddr(self.g_reminder_ptr, 0x01010101)

А чтобы включить, то записать туда 0ль

memory:writeAddr(self.g_reminder_ptr, 0x00000000)


В итоге получился вот такой циферблат,
который прекрасно справляется в поставленной задачей

Готовый циферблат
Готовый циферблат

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

В данном решение конечно же есть и недостаток, как понимаете, состояние RAM сбрасывается при перезагрузке или отключению системы, но так как данные часы могут работать по 2 недели, это не такой большой минус.

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

Я провел анализ часов, и выяснил что на текущий момент используются аккумуляторы Li-ion,
у которых рекомендуемый предел эксплуатации 55-60 гр, а пользователи которые обращались мне с проблемой мониторили температуру бенда, и она в топе оказалась 45 гр, соответственно требовалась всего небольшая коррекция температуры, чтобы он остался в рабочем состоянии.

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

Подробнее с темой вы можете ознакомиться
Apache NuttX - https://nuttx.apache.org/
NuttShell - https://nuttx.apache.org/docs/latest/applications/nsh/index.html
LVGL - https://lvgl.io/
Easyface - https://github.com/m0tral/EasyFace
TempControl - https://github.com/m0tral/MiWatchLuaWatchfaces/tree/master/MiBand9/TempControl

Если вас интересует готовое решение, данные циферблаты готовы
и доступны в моей приложении MiFitness mod
в телеграмм канале @mi_watch_int @mi_watch_news

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


  1. aegelsky
    16.07.2025 18:40

    ибо это чревато еще и 

    взрывом акума на руке и пожаром который невозможно потушить