1212#include < esp_system.h>
1313#include < esp_timer.h>
1414#include < cinttypes>
15+ #include < mutex>
1516#include " esp_idf_version.h"
1617
1718#define ESP_VOCAT_ENABLE_CAP_TOUCH_SENSOR (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL (6 , 0 , 0 ))
@@ -289,6 +290,23 @@ static RTC_NOINIT_ATTR uint8_t s_pcb_version_cached;
289290
290291class EspVocat ;
291292
293+ // Battery retries run only on its worker. Touch uses one checked transfer so
294+ // a failed sample cannot synthesize a press/release or stall other callbacks.
295+ static esp_err_t ReadPeripheralRegs (i2c_master_dev_handle_t device, uint8_t reg, uint8_t * data,
296+ size_t length, int attempts) {
297+ esp_err_t error = ESP_FAIL ;
298+ for (int attempt = 0 ; attempt < attempts; ++attempt) {
299+ error = i2c_master_transmit_receive (device, ®, 1 , data, length, 50 );
300+ if (error == ESP_OK )
301+ return ESP_OK ;
302+ if (error != ESP_ERR_TIMEOUT && error != ESP_ERR_INVALID_RESPONSE && error != ESP_FAIL )
303+ break ;
304+ if (attempt + 1 < attempts)
305+ vTaskDelay (pdMS_TO_TICKS (10 << attempt));
306+ }
307+ return error;
308+ }
309+
292310class Charge : public I2cDevice {
293311public:
294312 static constexpr uint8_t kRegVoltage = 0x08 ;
@@ -299,55 +317,61 @@ class Charge : public I2cDevice {
299317 static constexpr int kChargingCurrentMa = 30 ;
300318 static constexpr uint16_t kBatteryStatusDsg = BIT0 ;
301319
302- Charge (i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
303- read_buffer_ = new uint8_t [8 ];
304- }
305- ~Charge () { delete[] read_buffer_; }
320+ Charge (i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {}
306321
307- int16_t ReadWord (uint8_t reg) {
322+ bool ReadWord (uint8_t reg, uint16_t & value ) {
308323 uint8_t data[2 ] = {0 };
309- ReadRegs (reg, data, 2 );
310- return static_cast <int16_t >(static_cast <uint16_t >(data[0 ]) |
311- (static_cast <uint16_t >(data[1 ]) << 8 ));
312- }
313-
314- int GetBatteryLevel () {
315- int level = ReadWord (kRegStateOfCharge );
316- if (level < 0 ) {
317- return 0 ;
318- }
319- if (level > 100 ) {
320- return 100 ;
321- }
322- return level;
323- }
324-
325- bool IsCharging () {
326- const uint16_t status = static_cast <uint16_t >(ReadWord (kRegBatteryStatus ));
327- if ((status & kBatteryStatusDsg ) == 0 ) {
328- return true ;
324+ const esp_err_t error = ReadPeripheralRegs (i2c_device_, reg, data, sizeof (data), 3 );
325+ if (error != ESP_OK ) {
326+ const int64_t now = esp_timer_get_time () / 1000 ;
327+ if (now >= next_error_log_ms_) {
328+ ESP_LOGW (TAG , " Battery read unavailable reg=0x%02x error=%s" , reg,
329+ esp_err_to_name (error));
330+ next_error_log_ms_ = now + 10000 ;
331+ }
332+ return false ;
329333 }
330-
331- const int16_t avg_current_ma = ReadWord (kRegAverageCurrent );
332- const int16_t current_ma = ReadWord (kRegCurrent );
333- // Current is a fallback only: AverageCurrent can lag charger insertion noticeably.
334- return avg_current_ma > kChargingCurrentMa || current_ma > kChargingCurrentMa ;
334+ value = static_cast <uint16_t >(data[0 ]) | (static_cast <uint16_t >(data[1 ]) << 8 );
335+ return true ;
335336 }
336337
337- bool IsDischarging () {
338- return (static_cast <uint16_t >(ReadWord (kRegBatteryStatus )) & kBatteryStatusDsg ) != 0 ;
338+ bool GetSnapshot (int & level, bool & charging, bool & discharging) {
339+ std::lock_guard<std::mutex> lock (sample_mutex_);
340+ const int64_t now = esp_timer_get_time () / 1000 ;
341+ if (!sample_valid_ || now - sample_at_ms_ > 10000 )
342+ return false ;
343+ level = level_;
344+ charging = charging_;
345+ discharging = discharging_;
346+ return true ;
339347 }
340348
341- void Update () {
342- const int16_t voltage = ReadWord (kRegVoltage );
343- const int16_t current = ReadWord (kRegCurrent );
349+ bool Update () {
350+ uint16_t level, status, average, current;
351+ if (!ReadWord (kRegStateOfCharge , level) || !ReadWord (kRegBatteryStatus , status) ||
352+ !ReadWord (kRegAverageCurrent , average) || !ReadWord (kRegCurrent , current) ||
353+ level > 100 )
354+ return false ;
344355 ESP_ERROR_CHECK (temperature_sensor_get_celsius (temp_sensor, &tsens_value));
345- (void )voltage;
346- (void )current;
356+ std::lock_guard<std::mutex> lock (sample_mutex_);
357+ level_ = level;
358+ discharging_ = (status & kBatteryStatusDsg ) != 0 ;
359+ // Current is a fallback: AverageCurrent can lag charger insertion.
360+ charging_ = !discharging_ || static_cast <int16_t >(average) > kChargingCurrentMa ||
361+ static_cast <int16_t >(current) > kChargingCurrentMa ;
362+ sample_at_ms_ = esp_timer_get_time () / 1000 ;
363+ sample_valid_ = true ;
364+ return true ;
347365 }
348366
349367private:
350- uint8_t * read_buffer_ = nullptr ;
368+ std::mutex sample_mutex_;
369+ int level_ = 0 ;
370+ bool charging_ = false ;
371+ bool discharging_ = false ;
372+ bool sample_valid_ = false ;
373+ int64_t sample_at_ms_ = 0 ;
374+ int64_t next_error_log_ms_ = 0 ;
351375};
352376
353377class Cst816s : public I2cDevice {
@@ -382,11 +406,13 @@ class Cst816s : public I2cDevice {
382406 }
383407 }
384408
385- void UpdateTouchPoint () {
386- ReadRegs (0x02 , read_buffer_, 6 );
409+ bool UpdateTouchPoint () {
410+ if (ReadPeripheralRegs (i2c_device_, 0x02 , read_buffer_, 6 , 1 ) != ESP_OK )
411+ return false ;
387412 tp_.num = read_buffer_[0 ] & 0x0F ;
388413 tp_.x = ((read_buffer_[1 ] & 0x0F ) << 8 ) | read_buffer_[2 ];
389414 tp_.y = ((read_buffer_[3 ] & 0x0F ) << 8 ) | read_buffer_[4 ];
415+ return true ;
390416 }
391417
392418 const TouchPoint_t& GetTouchPoint () { return tp_; }
@@ -470,6 +496,7 @@ class EspVocat : public WifiBoard {
470496 esp_timer_handle_t emotion_reset_timer_ = nullptr ;
471497 bool bmi270_ready_ = false ;
472498 bool was_charging_ = false ;
499+ bool battery_status_initialized_ = false ;
473500 uint8_t low_battery_alert_mask_ = 0 ;
474501 int low_battery_plays_left_ = 0 ;
475502 int64_t next_low_battery_play_ms_ = 0 ;
@@ -534,8 +561,14 @@ class EspVocat : public WifiBoard {
534561 return ;
535562 }
536563
537- const int level = charge_->GetBatteryLevel ();
538- const bool charging = charge_->IsCharging ();
564+ int level;
565+ bool charging, discharging;
566+ if (!charge_->GetSnapshot (level, charging, discharging))
567+ return ;
568+ if (!battery_status_initialized_) {
569+ was_charging_ = charging;
570+ battery_status_initialized_ = true ;
571+ }
539572
540573 if (charging && !was_charging_) {
541574 PlayBatteryEmotion (" battery_connected" , 4000 );
@@ -580,11 +613,13 @@ class EspVocat : public WifiBoard {
580613 static void battery_task (void * arg) {
581614 auto * self = static_cast <EspVocat*>(arg);
582615 while (true ) {
616+ bool healthy = false ;
583617 if (self != nullptr && self->charge_ != nullptr ) {
584- self->charge_ ->Update ();
585- self->HandleBatteryEmotions ();
618+ healthy = self->charge_ ->Update ();
619+ if (healthy)
620+ self->HandleBatteryEmotions ();
586621 }
587- vTaskDelay (pdMS_TO_TICKS (300 ));
622+ vTaskDelay (pdMS_TO_TICKS (healthy ? 2000 : 5000 ));
588623 }
589624 }
590625
@@ -774,7 +809,8 @@ class EspVocat : public WifiBoard {
774809 auto & board = (EspVocat&)Board::GetInstance ();
775810
776811 ESP_LOGD (TAG , " Touch event, TP_PIN_NUM_INT: %d" , gpio_get_level (TP_PIN_NUM_INT ));
777- touchpad->UpdateTouchPoint ();
812+ if (!touchpad->UpdateTouchPoint ())
813+ continue ;
778814 auto touch_event = touchpad->CheckTouchEvent ();
779815
780816 if (touch_event == Cst816s::TOUCH_RELEASE ) {
@@ -790,7 +826,6 @@ class EspVocat : public WifiBoard {
790826
791827 void InitializeCharge () {
792828 charge_ = new Charge (i2c_bus_, 0x55 );
793- was_charging_ = charge_->IsCharging ();
794829 xTaskCreatePinnedToCore (battery_task, " batteryTask" , 3 * 1024 , this , 6 ,
795830 &charge_task_handle_, 0 );
796831 }
@@ -1170,10 +1205,7 @@ class EspVocat : public WifiBoard {
11701205 if (charge_ == nullptr ) {
11711206 return false ;
11721207 }
1173- level = charge_->GetBatteryLevel ();
1174- charging = charge_->IsCharging ();
1175- discharging = charge_->IsDischarging ();
1176- return true ;
1208+ return charge_->GetSnapshot (level, charging, discharging);
11771209 }
11781210};
11791211
0 commit comments