Skip to content

Commit 35acfe1

Browse files
[#108] Update watchdog_pet() (#189)
Co-authored-by: SSL-Rey <[email protected]>
1 parent 3730fc8 commit 35acfe1

File tree

2 files changed

+108
-79
lines changed

2 files changed

+108
-79
lines changed

lib/pysquared/pysquared.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
# Common CircuitPython Libs
11+
import asyncio # Add asyncio import
1112
import sys
1213
import time
1314
from collections import OrderedDict
@@ -577,11 +578,38 @@ def date(self, ymdw: tuple[int, int, int, int]) -> None:
577578
"""
578579

579580
def watchdog_pet(self) -> None:
581+
"""Pet the watchdog timer"""
582+
self.logger.debug("Petting watchdog")
580583
self.watchdog_pin.value = True
581584
time.sleep(0.01)
582585
self.watchdog_pin.value = False
583586

587+
async def _watchdog_pet_task(self) -> None:
588+
"""Async task to continuously pet the watchdog"""
589+
self.logger.info("Starting watchdog petting background task")
590+
while self.hardware.get("WDT", False):
591+
self.watchdog_pet()
592+
await asyncio.sleep(1.0) # Pet watchdog every second
593+
self.logger.info("Watchdog petting task stopped")
594+
595+
def start_watchdog_background_task(self) -> None:
596+
"""Start the watchdog petting as a true background task using asyncio"""
597+
self._last_watchdog_pet = time.monotonic()
598+
self.hardware["WDT"] = True
599+
# Create and schedule the task but don't wait for it
600+
asyncio.create_task(self._watchdog_pet_task())
601+
self.logger.info("Watchdog background task created")
602+
603+
def check_watchdog(self) -> None:
604+
"""
605+
Legacy method maintained for compatibility
606+
The actual petting is now handled by the background task
607+
"""
608+
pass # No-op as this is now handled by the async task
609+
584610
def check_reboot(self) -> None:
611+
"""Check if system needs to be rebooted"""
612+
# No need to check watchdog anymore as it's handled by the background task
585613
self.UPTIME: int = self.get_system_uptime
586614
self.logger.debug("Current up time stat:", uptime=self.UPTIME)
587615
if self.UPTIME > self.reboot_time:

main.py

Lines changed: 80 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
Published: Nov 19, 2024
99
"""
1010

11-
import gc
11+
import asyncio
1212
import time
1313

1414
import digitalio
@@ -53,7 +53,10 @@
5353
config: Config = Config("config.json")
5454

5555
c = pysquared.Satellite(config, logger, __version__)
56-
c.watchdog_pet()
56+
57+
# Start the watchdog background task
58+
c.start_watchdog_background_task()
59+
5760
sleep_helper = SleepHelper(c, logger)
5861

5962
radio_manager = RFM9xManager(
@@ -69,109 +72,107 @@
6972

7073
f = functions.functions(c, logger, config, sleep_helper, radio_manager)
7174

72-
def initial_boot():
73-
c.watchdog_pet()
75+
async def initial_boot():
7476
f.beacon()
75-
c.watchdog_pet()
7677
f.listen()
77-
c.watchdog_pet()
78-
79-
try:
80-
c.boot_count.increment()
8178

82-
logger.info(
83-
"FC Board Stats",
84-
bytes_remaining=gc.mem_free(),
85-
boot_number=c.boot_count.get(),
86-
)
87-
88-
initial_boot()
89-
90-
except Exception as e:
91-
logger.error("Error in Boot Sequence", e)
79+
async def critical_power_operations():
80+
await initial_boot()
81+
# Convert blocking operation to a non-blocking one using run_in_executor
82+
# Since this is a blocking function that will likely put the device to sleep,
83+
# we accept that this will block the event loop as intended
84+
logger.info("Entering long hibernation (this will block the event loop)")
85+
sleep_helper.long_hibernate()
86+
logger.info("Woke from long hibernation")
9287

93-
finally:
94-
pass
88+
async def minimum_power_operations():
89+
await initial_boot()
90+
# Same as above - this is a blocking operation by design
91+
logger.info("Entering short hibernation (this will block the event loop)")
92+
sleep_helper.short_hibernate()
93+
logger.info("Woke from short hibernation")
9594

96-
def send_imu_data():
95+
async def send_imu_data():
9796
logger.info("Looking to get imu data...")
9897
IMUData = []
99-
c.watchdog_pet()
100-
logger.info("IMU has baton")
10198
IMUData = f.get_imu_data()
102-
c.watchdog_pet()
10399
f.send(IMUData)
104100

105-
def main():
101+
async def main():
106102
f.beacon()
107-
108103
f.listen_loiter()
109-
110104
f.state_of_health()
111-
112105
f.listen_loiter()
113-
114106
f.all_face_data()
115-
c.watchdog_pet()
116107
f.send_face()
117-
118108
f.listen_loiter()
119-
120-
send_imu_data()
121-
109+
await send_imu_data()
122110
f.listen_loiter()
123-
124111
f.joke()
125-
126112
f.listen_loiter()
127113

128-
def critical_power_operations():
129-
initial_boot()
130-
c.watchdog_pet()
131-
132-
sleep_helper.long_hibernate()
133-
134-
def minimum_power_operations():
135-
initial_boot()
136-
c.watchdog_pet()
137-
138-
sleep_helper.short_hibernate()
139-
140114
######################### MAIN LOOP ##############################
115+
async def main_loop():
116+
"""Async main loop that runs alongside the watchdog task"""
117+
try:
118+
while True:
119+
# L0 automatic tasks no matter the battery level
120+
c.check_reboot() # Now this only checks for reboot conditions
121+
122+
if c.power_mode == "critical":
123+
c.rgb = (0, 0, 0)
124+
await critical_power_operations()
125+
126+
elif c.power_mode == "minimum":
127+
c.rgb = (255, 0, 0)
128+
await minimum_power_operations()
129+
130+
elif c.power_mode == "normal":
131+
c.rgb = (255, 255, 0)
132+
await main()
133+
134+
elif c.power_mode == "maximum":
135+
c.rgb = (0, 255, 0)
136+
await main()
137+
138+
else:
139+
f.listen()
140+
141+
# Small yield to allow other tasks to run
142+
await asyncio.sleep(0.1)
143+
144+
except Exception as e:
145+
logger.critical("Critical in Main Loop", e)
146+
time.sleep(10)
147+
microcontroller.on_next_reset(microcontroller.RunMode.NORMAL)
148+
microcontroller.reset()
149+
finally:
150+
logger.info("Going Neutral!")
151+
c.rgb = (0, 0, 0)
152+
c.hardware["WDT"] = False
153+
154+
# Set up the asyncio event loop
155+
async def run_tasks():
156+
# The watchdog task is already started by c.start_watchdog_background_task()
157+
# Just run the main loop as a task
158+
main_task = asyncio.create_task(main_loop())
159+
160+
try:
161+
# Wait for the main task to complete (it should run forever)
162+
await main_task
163+
except asyncio.CancelledError:
164+
logger.info("Main task was cancelled")
165+
except Exception as e:
166+
logger.critical("Error in run_tasks", e)
167+
raise
168+
169+
# Run the asyncio event loop
141170
try:
142-
while True:
143-
# L0 automatic tasks no matter the battery level
144-
c.check_reboot()
145-
146-
if c.power_mode == "critical":
147-
c.rgb = (0, 0, 0)
148-
critical_power_operations()
149-
150-
elif c.power_mode == "minimum":
151-
c.rgb = (255, 0, 0)
152-
minimum_power_operations()
153-
154-
elif c.power_mode == "normal":
155-
c.rgb = (255, 255, 0)
156-
main()
157-
158-
elif c.power_mode == "maximum":
159-
c.rgb = (0, 255, 0)
160-
main()
161-
162-
else:
163-
f.listen()
164-
171+
asyncio.run(run_tasks())
165172
except Exception as e:
166-
logger.critical("Critical in Main Loop", e)
167-
time.sleep(10)
173+
logger.critical("Error in asyncio.run", e)
168174
microcontroller.on_next_reset(microcontroller.RunMode.NORMAL)
169175
microcontroller.reset()
170-
finally:
171-
logger.info("Going Neutral!")
172-
173-
c.rgb = (0, 0, 0)
174-
c.hardware["WDT"] = False
175176

176177
except Exception as e:
177178
logger.critical("An exception occured within main.py", e)

0 commit comments

Comments
 (0)