Skip to content

Commit ba454a2

Browse files
committed
Add power status and charging retry
1 parent 8f0baeb commit ba454a2

6 files changed

Lines changed: 195 additions & 28 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ More details about this firmware can be found in [documentation](https://jkaflik
2121

2222
## Usage
2323

24+
### Power ROS API
25+
26+
Battery state is published at 1 Hz:
27+
28+
| Topic | Type | Description |
29+
| --- | --- | --- |
30+
| `power` | `sensor_msgs/msg/BatteryState` | Standard battery status. `percentage` is normalized to `0.0..1.0`; current is the measured charge current in amps. |
31+
| `power/status` | `omros2_firmware_msgs/msg/PowerStatus` | Firmware-specific power and charging snapshot. |
32+
33+
`PowerStatus` includes battery voltage and percentage, charge voltage/current, charger presence, the actual charge-enable output, whether dock charging is allowed, the charging state, inhibit reason, and retry delay remaining.
34+
2435
### LED Status Indicators
2536

2637
The firmware uses onboard NeoPixel LED(s) to display the current system status. Multiple status conditions can be active simultaneously and will be displayed in sequence.

extra_packages/omros2_firmware_msgs/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ cmake_minimum_required(VERSION 3.5)
22
project(omros2_firmware_msgs)
33

44
find_package(ament_cmake REQUIRED)
5+
find_package(builtin_interfaces REQUIRED)
56
find_package(rosidl_default_generators REQUIRED)
67

78
rosidl_generate_interfaces(${PROJECT_NAME}
89
"msg/EmergencyStatus.msg"
10+
"msg/PowerStatus.msg"
11+
DEPENDENCIES builtin_interfaces
912
)
1013

1114
ament_package()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Charging state values.
2+
uint8 CHARGING_STATE_DISCHARGING=0
3+
uint8 CHARGING_STATE_NOT_CHARGING=1
4+
uint8 CHARGING_STATE_CHARGING=2
5+
uint8 CHARGING_STATE_RETRY_WAIT=3
6+
uint8 CHARGING_STATE_REGEN_PATH=4
7+
8+
# Charging inhibit reason values.
9+
uint8 INHIBIT_NONE=0
10+
uint8 INHIBIT_NO_BATTERY=1
11+
uint8 INHIBIT_BATTERY_FULL=2
12+
uint8 INHIBIT_BATTERY_OVERVOLTAGE=3
13+
uint8 INHIBIT_CHARGE_OVERVOLTAGE=4
14+
uint8 INHIBIT_OVERCURRENT=5
15+
uint8 INHIBIT_RETRY_DELAY=6
16+
17+
builtin_interfaces/Time stamp
18+
19+
float32 battery_voltage
20+
float32 battery_percentage
21+
bool battery_present
22+
23+
float32 charge_voltage
24+
float32 charge_current
25+
bool charger_present
26+
27+
# Actual PIN_ENABLE_CHARGE output state.
28+
bool charge_path_enabled
29+
30+
# True only when dock charging is allowed by firmware limits.
31+
bool charging_allowed
32+
uint8 charging_state
33+
uint8 inhibit_reason
34+
35+
uint32 retry_remaining_ms

extra_packages/omros2_firmware_msgs/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<license>Apache-2.0</license>
99

1010
<buildtool_depend>ament_cmake</buildtool_depend>
11+
<depend>builtin_interfaces</depend>
1112
<build_depend>rosidl_default_generators</build_depend>
1213
<exec_depend>rosidl_default_runtime</exec_depend>
1314
<member_of_group>rosidl_interface_packages</member_of_group>

src/hardware.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#define CHARGE_VOLTAGE_CUTOFF 30.0f
1212
#define CHARGE_VOLTAGE_MIN 28.8f
1313
#define CHARGER_PRESENT_VOLTAGE 1.0f
14+
#define CHARGE_REGEN_VOLTAGE 3.0f
15+
#define CHARGING_RETRY_MILLIS 10000UL
1416

1517
#define BATT_PRESENT_VOLTAGE 1.0f
1618
#define BATT_ABSOLUTE_MAX 28.7f

src/main.cpp

Lines changed: 143 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
#include <std_msgs/msg/bool.h>
2-
#include <std_msgs/msg/float32.h>
32

43
#include <Arduino.h>
54
#include <EMA.h>
65
#include <micro_ros_platformio.h>
76
#include <omros2_firmware_msgs/msg/emergency_status.h>
7+
#include <omros2_firmware_msgs/msg/power_status.h>
88
#include <rcl/rcl.h>
99
#include <rclc/executor.h>
1010
#include <rclc/rclc.h>
1111
#include <sensor_msgs/msg/battery_state.h>
1212
#include <sensor_msgs/msg/imu.h>
1313

14+
#include <algorithm>
15+
1416
#include "emergency.h"
1517
#include "hardware.h"
1618
#include "imu.h"
@@ -39,8 +41,57 @@ uint8_t powerSupplyStatus = 0;
3941
uint8_t powerSupplyHealth = 0;
4042
bool batteryPresent = false;
4143
bool chargeEnabled = false;
44+
bool chargingAllowed = false;
45+
bool batteryFullLatched = false;
46+
uint8_t chargingState = omros2_firmware_msgs__msg__PowerStatus__CHARGING_STATE_DISCHARGING;
47+
uint8_t chargeInhibitReason = omros2_firmware_msgs__msg__PowerStatus__INHIBIT_NONE;
48+
uint32_t chargeRetryRemainingMs = 0;
49+
ulong chargingDisabledTime = 0;
4250
ulong lastChargingLoop = 0;
4351

52+
bool shouldStartRetryDelay(uint8_t reason)
53+
{
54+
return reason == omros2_firmware_msgs__msg__PowerStatus__INHIBIT_BATTERY_OVERVOLTAGE
55+
|| reason == omros2_firmware_msgs__msg__PowerStatus__INHIBIT_CHARGE_OVERVOLTAGE
56+
|| reason == omros2_firmware_msgs__msg__PowerStatus__INHIBIT_OVERCURRENT;
57+
}
58+
59+
uint8_t getChargingInhibitReason()
60+
{
61+
if (!batteryPresent)
62+
{
63+
return omros2_firmware_msgs__msg__PowerStatus__INHIBIT_NO_BATTERY;
64+
}
65+
66+
if (batteryVoltage > BATT_ABSOLUTE_MAX)
67+
{
68+
return omros2_firmware_msgs__msg__PowerStatus__INHIBIT_BATTERY_OVERVOLTAGE;
69+
}
70+
71+
if (chargeVoltage > CHARGE_VOLTAGE_CUTOFF)
72+
{
73+
return omros2_firmware_msgs__msg__PowerStatus__INHIBIT_CHARGE_OVERVOLTAGE;
74+
}
75+
76+
if (chargeCurrent >= CHARGE_CURRENT_CUTOFF)
77+
{
78+
return omros2_firmware_msgs__msg__PowerStatus__INHIBIT_OVERCURRENT;
79+
}
80+
81+
if (batteryFullLatched)
82+
{
83+
return omros2_firmware_msgs__msg__PowerStatus__INHIBIT_BATTERY_FULL;
84+
}
85+
86+
return omros2_firmware_msgs__msg__PowerStatus__INHIBIT_NONE;
87+
}
88+
89+
void setChargeEnabled(bool enabled)
90+
{
91+
chargeEnabled = enabled;
92+
digitalWrite(PIN_ENABLE_CHARGE, chargeEnabled ? HIGH : LOW);
93+
}
94+
4495
void manageBatteryCharging()
4596
{
4697
uint16_t readFiltered;
@@ -58,23 +109,79 @@ void manageBatteryCharging()
58109

59110
isChargerPresent = chargeVoltage >= CHARGER_PRESENT_VOLTAGE;
60111

61-
batteryPercentage =
62-
std::min((batteryVoltage - BATT_EMPTY) / (BATT_FULL - BATT_EMPTY) * 100, 100.0f);
112+
batteryPercentage = std::min(
113+
std::max((batteryVoltage - BATT_EMPTY) / (BATT_FULL - BATT_EMPTY) * 100, 0.0f), 100.0f);
63114

64-
bool shouldCharge = chargeVoltage <= CHARGE_VOLTAGE_CUTOFF
65-
&& chargeCurrent < CHARGE_CURRENT_CUTOFF
66-
&& batteryVoltage <= BATT_ABSOLUTE_MAX && batteryVoltage;
67-
chargeEnabled = shouldCharge && batteryVoltage < BATT_FULL_HYSTERESIS;
115+
if (batteryVoltage >= BATT_FULL)
116+
{
117+
batteryFullLatched = true;
118+
}
119+
else if (batteryVoltage <= BATT_FULL_HYSTERESIS)
120+
{
121+
batteryFullLatched = false;
122+
}
68123

69-
digitalWrite(PIN_ENABLE_CHARGE, chargeEnabled ? HIGH : LOW);
124+
const auto now = millis();
125+
const uint8_t previousInhibitReason = chargeInhibitReason;
70126

71-
bool isCharging = chargeVoltage > batteryVoltage;
127+
chargeRetryRemainingMs = 0;
128+
chargeInhibitReason = omros2_firmware_msgs__msg__PowerStatus__INHIBIT_NONE;
72129

73-
if (shouldCharge)
130+
if (chargeVoltage < CHARGE_REGEN_VOLTAGE)
74131
{
75-
powerSupplyStatus = isCharging
76-
? sensor_msgs__msg__BatteryState__POWER_SUPPLY_STATUS_CHARGING
77-
: sensor_msgs__msg__BatteryState__POWER_SUPPLY_STATUS_NOT_CHARGING;
132+
setChargeEnabled(true);
133+
chargingAllowed = false;
134+
chargingDisabledTime = 0;
135+
chargingState = omros2_firmware_msgs__msg__PowerStatus__CHARGING_STATE_REGEN_PATH;
136+
}
137+
else
138+
{
139+
const uint8_t limitReason = getChargingInhibitReason();
140+
if (limitReason != omros2_firmware_msgs__msg__PowerStatus__INHIBIT_NONE)
141+
{
142+
if (shouldStartRetryDelay(limitReason)
143+
&& (chargingAllowed || chargeEnabled || previousInhibitReason != limitReason))
144+
{
145+
chargingDisabledTime = now;
146+
}
147+
else if (!shouldStartRetryDelay(limitReason))
148+
{
149+
chargingDisabledTime = 0;
150+
}
151+
152+
setChargeEnabled(false);
153+
chargingAllowed = false;
154+
chargeInhibitReason = limitReason;
155+
chargingState = omros2_firmware_msgs__msg__PowerStatus__CHARGING_STATE_NOT_CHARGING;
156+
}
157+
else if (chargingDisabledTime != 0 && now - chargingDisabledTime < CHARGING_RETRY_MILLIS)
158+
{
159+
setChargeEnabled(false);
160+
chargingAllowed = false;
161+
chargeInhibitReason = omros2_firmware_msgs__msg__PowerStatus__INHIBIT_RETRY_DELAY;
162+
chargeRetryRemainingMs = CHARGING_RETRY_MILLIS - (now - chargingDisabledTime);
163+
chargingState = omros2_firmware_msgs__msg__PowerStatus__CHARGING_STATE_RETRY_WAIT;
164+
}
165+
else
166+
{
167+
setChargeEnabled(true);
168+
chargingAllowed = true;
169+
chargingDisabledTime = 0;
170+
chargingState = omros2_firmware_msgs__msg__PowerStatus__CHARGING_STATE_NOT_CHARGING;
171+
if (chargeVoltage > batteryVoltage)
172+
{
173+
chargingState = omros2_firmware_msgs__msg__PowerStatus__CHARGING_STATE_CHARGING;
174+
}
175+
}
176+
}
177+
178+
if (chargingAllowed)
179+
{
180+
powerSupplyStatus = sensor_msgs__msg__BatteryState__POWER_SUPPLY_STATUS_NOT_CHARGING;
181+
if (chargeVoltage > batteryVoltage)
182+
{
183+
powerSupplyStatus = sensor_msgs__msg__BatteryState__POWER_SUPPLY_STATUS_CHARGING;
184+
}
78185
}
79186
else
80187
{
@@ -89,7 +196,7 @@ void manageBatteryCharging()
89196
}
90197
else if (batteryVoltage > BATT_ABSOLUTE_MAX)
91198
{
92-
powerSupplyHealth = sensor_msgs__msg__BatteryState__POWER_SUPPLY_HEALTH_OVERHEAT;
199+
powerSupplyHealth = sensor_msgs__msg__BatteryState__POWER_SUPPLY_HEALTH_OVERVOLTAGE;
93200
}
94201
else
95202
{
@@ -168,35 +275,43 @@ void setup1()
168275

169276
auto batteryStatePublisher = new uros::Publisher<sensor_msgs__msg__BatteryState>(
170277
*node, "power", UROS_GET_MSG_TYPE_SUPPORT(sensor_msgs, msg, BatteryState));
171-
auto chargeVoltagePublisher = new uros::Publisher<std_msgs__msg__Float32>(
172-
*node, "power/charge_voltage", UROS_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Float32));
173-
auto chargerPresentPublisher = new uros::Publisher<std_msgs__msg__Bool>(
174-
*node, "power/charger_present", UROS_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Bool));
278+
auto powerStatusPublisher = new uros::Publisher<omros2_firmware_msgs__msg__PowerStatus>(
279+
*node, "power/status", UROS_GET_MSG_TYPE_SUPPORT(omros2_firmware_msgs, msg, PowerStatus));
175280
auto powerTimer = new uros::Timer(
176281
*node,
177282
1000,
178-
[batteryStatePublisher, chargeVoltagePublisher, chargerPresentPublisher]()
283+
[batteryStatePublisher, powerStatusPublisher]()
179284
{
180285
auto& batteryMsg = batteryStatePublisher->get_message();
181286

182287
auto ns = rmw_uros_epoch_nanos();
183288
batteryMsg.header.stamp.sec = ns / 1000000000;
184289
batteryMsg.header.stamp.nanosec = ns % 1000000000;
185290
batteryMsg.voltage = batteryVoltage;
186-
batteryMsg.charge = chargeCurrent;
187-
batteryMsg.percentage = batteryPercentage;
291+
batteryMsg.current = chargeCurrent;
292+
batteryMsg.capacity = BATT_DESIGNED_CAPACITY;
293+
batteryMsg.design_capacity = BATT_DESIGNED_CAPACITY;
294+
batteryMsg.percentage = batteryPercentage / 100.0f;
188295
batteryMsg.present = batteryPresent;
189296
batteryMsg.power_supply_status = powerSupplyStatus;
190297
batteryMsg.power_supply_health = powerSupplyHealth;
191298
batteryStatePublisher->publish();
192299

193-
auto& chargeVoltageMsg = chargeVoltagePublisher->get_message();
194-
chargeVoltageMsg.data = chargeVoltage;
195-
chargeVoltagePublisher->publish();
196-
197-
auto& chargerPresentMsg = chargerPresentPublisher->get_message();
198-
chargerPresentMsg.data = isChargerPresent;
199-
chargerPresentPublisher->publish();
300+
auto& powerStatusMsg = powerStatusPublisher->get_message();
301+
powerStatusMsg.stamp.sec = ns / 1000000000;
302+
powerStatusMsg.stamp.nanosec = ns % 1000000000;
303+
powerStatusMsg.battery_voltage = batteryVoltage;
304+
powerStatusMsg.battery_percentage = batteryPercentage;
305+
powerStatusMsg.battery_present = batteryPresent;
306+
powerStatusMsg.charge_voltage = chargeVoltage;
307+
powerStatusMsg.charge_current = chargeCurrent;
308+
powerStatusMsg.charger_present = isChargerPresent;
309+
powerStatusMsg.charge_path_enabled = chargeEnabled;
310+
powerStatusMsg.charging_allowed = chargingAllowed;
311+
powerStatusMsg.charging_state = chargingState;
312+
powerStatusMsg.inhibit_reason = chargeInhibitReason;
313+
powerStatusMsg.retry_remaining_ms = chargeRetryRemainingMs;
314+
powerStatusPublisher->publish();
200315
});
201316

202317
auto emergencyCommandSubscription = new uros::Subscriber<std_msgs__msg__Bool>(

0 commit comments

Comments
 (0)