Skip to content

Commit 799ad4d

Browse files
committed
Implement emergency safety handling
1 parent e004cdf commit 799ad4d

7 files changed

Lines changed: 530 additions & 5 deletions

File tree

README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ More details about this firmware can be found in [documentation](https://jkaflik
1414
- [x] Charging
1515
- [x] LED status
1616
- [x] IMU
17-
- [ ] Emergency mode
18-
- [ ] Emergency publisher
19-
- [ ] Emergency restart service
17+
- [x] Emergency mode
18+
- [x] Emergency publishers
19+
- [x] Latched emergency command topic
2020
- [ ] Cover panel support
2121

2222
## Usage
@@ -31,8 +31,35 @@ The firmware uses onboard NeoPixel LED(s) to display the current system status.
3131
- **Magenta**: Battery discharging
3232
- **Red**: Battery low
3333
- **Blue**: IMU sensor failure
34+
- **Blinking red**: Emergency latch active
3435

3536
When multiple statuses are active, each status will be shown for approximately 800ms with a 200ms black separator between them. The sequence will continue to cycle through all active statuses.
37+
Emergency has priority over the normal status sequence and is shown as a blinking red LED.
38+
39+
### Emergency ROS API
40+
41+
Emergency state is published at 10 Hz:
42+
43+
| Topic | Type | Description |
44+
| --- | --- | --- |
45+
| `emergency/active` | `std_msgs/msg/Bool` | Emergency latch is active. |
46+
| `emergency/stop_active` | `std_msgs/msg/Bool` | Stop input is active after debounce. |
47+
| `emergency/lift_active` | `std_msgs/msg/Bool` | Lift emergency is active. |
48+
| `emergency/tilt_active` | `std_msgs/msg/Bool` | Tilt emergency is active. |
49+
| `emergency/software_requested` | `std_msgs/msg/Bool` | Emergency was requested by ROS. |
50+
| `emergency/release_blocked` | `std_msgs/msg/Bool` | Release is blocked by an active physical input. |
51+
| `emergency/lifted_wheels` | `std_msgs/msg/UInt8` | Number of active lift inputs. |
52+
53+
Commands are accepted on `emergency/command` as `std_msgs/msg/Bool`:
54+
55+
| Value | Meaning |
56+
| --- | --- |
57+
| `true` | Request/latch emergency. |
58+
| `false` | Request emergency release. |
59+
60+
The same command value must be received three times within one second before it is accepted.
61+
Release requests are ignored while any physical stop/lift/tilt input is active.
62+
The emergency latch starts active after boot and must be released explicitly.
3663

3764
## Build
3865

src/emergency.cpp

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#include "emergency.h"
2+
3+
#include <pico/mutex.h>
4+
5+
#include "pins.h"
6+
7+
namespace emergency
8+
{
9+
10+
namespace
11+
{
12+
constexpr unsigned long STOP_DEBOUNCE_MS = 20;
13+
constexpr unsigned long LIFT_PERIOD_MS = 100;
14+
constexpr unsigned long TILT_PERIOD_MS = 2500;
15+
constexpr unsigned long COMMAND_WINDOW_MS = 1000;
16+
constexpr uint8_t COMMAND_CONFIRMATIONS_REQUIRED = 3;
17+
18+
auto_init_mutex(emergency_mutex);
19+
20+
bool latch_active = true;
21+
bool stop_active = false;
22+
bool lift_active = false;
23+
bool tilt_active = false;
24+
bool software_requested = false;
25+
bool release_blocked = false;
26+
uint8_t lifted_wheels = 0;
27+
28+
bool raw_stop_active = false;
29+
bool raw_lift_input_active = false;
30+
31+
unsigned long stop_started_ms = 0;
32+
unsigned long lift_started_ms = 0;
33+
unsigned long tilt_started_ms = 0;
34+
35+
bool command_pending = false;
36+
bool pending_command = false;
37+
uint8_t pending_command_count = 0;
38+
unsigned long pending_command_started_ms = 0;
39+
40+
bool isActiveLowInput(uint8_t pin)
41+
{
42+
return digitalRead(pin) == LOW;
43+
}
44+
45+
bool elapsedSinceStart(bool condition,
46+
unsigned long& started_ms,
47+
unsigned long threshold_ms,
48+
unsigned long now)
49+
{
50+
if (!condition)
51+
{
52+
started_ms = 0;
53+
return false;
54+
}
55+
56+
if (started_ms == 0)
57+
{
58+
started_ms = now;
59+
return false;
60+
}
61+
62+
return now - started_ms >= threshold_ms;
63+
}
64+
65+
bool physicalInputPresent()
66+
{
67+
return raw_stop_active || raw_lift_input_active;
68+
}
69+
70+
void updateUnlocked()
71+
{
72+
const unsigned long now = millis();
73+
74+
lifted_wheels = 0;
75+
if (isActiveLowInput(PIN_EMERGENCY_1))
76+
{
77+
lifted_wheels++;
78+
}
79+
if (isActiveLowInput(PIN_EMERGENCY_2))
80+
{
81+
lifted_wheels++;
82+
}
83+
84+
raw_lift_input_active = lifted_wheels > 0;
85+
raw_stop_active = isActiveLowInput(PIN_EMERGENCY_3) || isActiveLowInput(PIN_EMERGENCY_4);
86+
87+
stop_active = elapsedSinceStart(raw_stop_active, stop_started_ms, STOP_DEBOUNCE_MS, now);
88+
lift_active = elapsedSinceStart(lifted_wheels >= 2, lift_started_ms, LIFT_PERIOD_MS, now);
89+
tilt_active = elapsedSinceStart(raw_lift_input_active, tilt_started_ms, TILT_PERIOD_MS, now);
90+
91+
if (stop_active || lift_active || tilt_active)
92+
{
93+
latch_active = true;
94+
}
95+
96+
release_blocked = latch_active && physicalInputPresent();
97+
}
98+
99+
void requestLatch()
100+
{
101+
software_requested = true;
102+
latch_active = true;
103+
}
104+
105+
void requestRelease()
106+
{
107+
updateUnlocked();
108+
109+
if (physicalInputPresent())
110+
{
111+
release_blocked = true;
112+
return;
113+
}
114+
115+
latch_active = false;
116+
software_requested = false;
117+
release_blocked = false;
118+
}
119+
120+
} // namespace
121+
122+
void init()
123+
{
124+
pinMode(PIN_EMERGENCY_1, INPUT);
125+
pinMode(PIN_EMERGENCY_2, INPUT);
126+
pinMode(PIN_EMERGENCY_3, INPUT);
127+
pinMode(PIN_EMERGENCY_4, INPUT);
128+
}
129+
130+
void update()
131+
{
132+
mutex_enter_blocking(&emergency_mutex);
133+
updateUnlocked();
134+
mutex_exit(&emergency_mutex);
135+
}
136+
137+
void handleCommand(bool latch_requested)
138+
{
139+
mutex_enter_blocking(&emergency_mutex);
140+
141+
const unsigned long now = millis();
142+
143+
if (!command_pending || pending_command != latch_requested
144+
|| now - pending_command_started_ms > COMMAND_WINDOW_MS)
145+
{
146+
command_pending = true;
147+
pending_command = latch_requested;
148+
pending_command_count = 1;
149+
pending_command_started_ms = now;
150+
mutex_exit(&emergency_mutex);
151+
return;
152+
}
153+
154+
pending_command_count++;
155+
if (pending_command_count < COMMAND_CONFIRMATIONS_REQUIRED)
156+
{
157+
mutex_exit(&emergency_mutex);
158+
return;
159+
}
160+
161+
command_pending = false;
162+
pending_command_count = 0;
163+
164+
if (latch_requested)
165+
{
166+
requestLatch();
167+
}
168+
else
169+
{
170+
requestRelease();
171+
}
172+
173+
mutex_exit(&emergency_mutex);
174+
}
175+
176+
State getState()
177+
{
178+
mutex_enter_blocking(&emergency_mutex);
179+
State state = {latch_active,
180+
stop_active,
181+
lift_active,
182+
tilt_active,
183+
software_requested,
184+
release_blocked,
185+
lifted_wheels};
186+
mutex_exit(&emergency_mutex);
187+
return state;
188+
}
189+
190+
} // namespace emergency

src/emergency.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
#include <Arduino.h>
4+
5+
namespace emergency
6+
{
7+
8+
struct State
9+
{
10+
bool active;
11+
bool stop_active;
12+
bool lift_active;
13+
bool tilt_active;
14+
bool software_requested;
15+
bool release_blocked;
16+
uint8_t lifted_wheels;
17+
};
18+
19+
void init();
20+
void update();
21+
void handleCommand(bool latch_requested);
22+
State getState();
23+
24+
} // namespace emergency

src/led_status.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ enum LedStatusFlag
1313
LED_STATUS_DISCHARGING = (1 << 2),
1414
LED_STATUS_BATTERY_LOW = (1 << 3),
1515
LED_STATUS_IMU_FAILED = (1 << 4),
16+
LED_STATUS_EMERGENCY = (1 << 5),
1617
};
1718

1819
struct StatusColor
@@ -130,6 +131,12 @@ class LedStatus
130131

131132
std::vector<LedStatusFlag> activeStatuses = getActiveStatuses();
132133

134+
if (getFlag(LED_STATUS_EMERGENCY))
135+
{
136+
setColor(255, 0, 0, true);
137+
return;
138+
}
139+
133140
// with no active statuses, turn off the LED
134141
if (activeStatuses.empty())
135142
{
@@ -178,6 +185,7 @@ class LedStatus
178185
statusColorMap.push_back({LED_STATUS_DISCHARGING, {255, 0, 255}}); // Magenta for discharging
179186
statusColorMap.push_back({LED_STATUS_BATTERY_LOW, {255, 0, 0}}); // Red for battery low
180187
statusColorMap.push_back({LED_STATUS_IMU_FAILED, {0, 0, 255}}); // Blue for IMU failed
188+
statusColorMap.push_back({LED_STATUS_EMERGENCY, {255, 0, 0}}); // Red blink for emergency
181189
}
182190

183191
std::vector<LedStatusFlag> getActiveStatuses() const

0 commit comments

Comments
 (0)