Skip to content

Commit fb8409f

Browse files
committed
Update logic of Starlink manager
1 parent f6a453c commit fb8409f

File tree

1 file changed

+99
-57
lines changed

1 file changed

+99
-57
lines changed
Lines changed: 99 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python3
2+
import datetime
23
import json
34
import urllib.request
45

@@ -21,96 +22,137 @@ def send_alerts(alert_config, message):
2122

2223
def main():
2324
rospy.init_node('seatrac_starlink_manager')
24-
shutdown_reminder = rospy.get_param('~shutdown_reminder', None)
25-
force_on_threshold = rospy.get_param('~soc_force_threshold', 95)
26-
on_threshold = rospy.get_param('~soc_threshold', 80)
27-
off_delay = rospy.get_param('~off_delay', 86400)
25+
26+
# Read parameters
2827
alerts = rospy.get_param('/alerts', [])
28+
high_threshold = rospy.get_param('~soc_threshold/high', 95)
29+
medium_threshold = rospy.get_param('~soc_threshold/medium', 80)
30+
31+
shutdown_reminder = rospy.get_param('~shutdown_reminder', None)
32+
if shutdown_reminder is not None:
33+
shutdown_reminder = rospy.Duration(shutdown_reminder)
34+
35+
scheduled_on = rospy.get_param('~scheduled_on', None)
36+
if scheduled_on is not None:
37+
scheduled_on = datetime.datetime.strptime(scheduled_on, '%H:%M').time()
2938

39+
40+
# Create a publisher for controlling the Starlink outlet
3041
outlet_pub = rospy.Publisher('/seatrac/outlet/starlink/control', StdBool,
3142
queue_size=1)
3243

33-
manual_shutdown = False
34-
last_off = None
35-
nag_timer = None
3644

37-
enable_sent = False
45+
# Subscribe to power level messages and record the state of charge, and
46+
# turn Starlink on automatically if the SOC is high enough.
47+
charging = False
48+
49+
already_sent = False
3850
last_state = None
3951
soc = 0.0
4052

53+
def power_level_cb(msg: PowerLevel) -> None:
54+
nonlocal already_sent, charging, soc
55+
if last_state is None:
56+
return
57+
58+
soc = msg.soc_percentage
59+
charging = msg.pack_current < 0
60+
61+
if soc >= high_threshold and charging and \
62+
not last_state and not already_sent:
63+
rospy.loginfo(
64+
f'Enabling Starlink: SOC {soc}% >= {high_threshold}% and charging'
65+
)
66+
outlet_pub.publish(StdBool(True))
67+
already_sent = True
68+
elif soc < medium_threshold:
69+
already_sent = False
70+
71+
rospy.Subscriber('/seatrac/power', PowerLevel, power_level_cb)
72+
73+
74+
# Nag the operators if Starlink has been active for too long
75+
nag_timer = None
76+
4177
def nag_operator() -> None:
42-
nonlocal alerts, nag_timer
78+
nonlocal nag_timer
4379
nag_timer = None
4480
send_alerts(alerts, 'Starlink has been active for a while')
4581

4682
def outlet_status_cb(msg: OutletStatus) -> None:
47-
nonlocal manual_shutdown, last_off, nag_timer, last_state
48-
now = rospy.Time.now().to_sec()
49-
if last_state is None:
50-
if not msg.is_active:
51-
manual_shutdown = True
52-
last_off = now
53-
elif last_state and not msg.is_active:
54-
manual_shutdown = True
55-
last_off = now
56-
elif manual_shutdown and msg.is_active:
57-
manual_shutdown = False
83+
nonlocal nag_timer, last_state
5884
last_state = msg.is_active
5985

60-
if shutdown_reminder is not None and last_state and nag_timer is None:
86+
# Set up the nag timer if Starlink is active (except if we are at a
87+
# high state of charge and charging). When the outlet is off, cancel
88+
# the nag timer.
89+
if shutdown_reminder is None:
90+
return
91+
92+
if msg.is_active and nag_timer is None and \
93+
not (soc >= high_threshold and charging):
94+
# Assume we'll be getting outlet status messages regularly, so we
95+
# can do a one-shot timer.
6196
nag_timer = rospy.Timer(
62-
rospy.Duration(shutdown_reminder),
97+
shutdown_reminder,
6398
nag_operator,
6499
oneshot=True
65100
)
66101
elif not msg.is_active and nag_timer:
67102
nag_timer.shutdown()
68103
nag_timer = None
69104

70-
def power_level_cb(msg: PowerLevel) -> None:
71-
nonlocal enable_sent, last_state, soc, manual_shutdown, last_off
72-
if last_state is None:
105+
rospy.Subscriber('/seatrac/outlet/starlink/status', OutletStatus,
106+
outlet_status_cb)
107+
108+
109+
# Schedule Starlink to turn on at a specific time every day
110+
daily_timer = None
111+
112+
def schedule_daily_timer():
113+
nonlocal daily_timer
114+
now = datetime.datetime.now(datetime.timezone.utc)
115+
target = now.replace(hour=scheduled_on.hour,
116+
minute=scheduled_on.minute,
117+
second=0, microsecond=0)
118+
if target <= now:
119+
target += datetime.timedelta(days=1)
120+
121+
daily_timer = rospy.Timer(
122+
rospy.Duration((target - now).total_seconds()),
123+
daily_timer_cb,
124+
oneshot=True
125+
)
126+
127+
def daily_timer_cb(event):
128+
nonlocal already_sent
129+
130+
# Starlink is already enabled, no need to enable it again. We don't
131+
# check already_sent because the operator may have manually turned it
132+
# off yesterday, and we want to turn it on today.
133+
if last_state:
73134
return
74135

75-
soc = msg.soc_percentage
76-
is_charging = msg.pack_current < 0
77-
now = rospy.Time.now().to_sec()
78-
79-
if (
80-
soc >= force_on_threshold
81-
and is_charging
82-
and not enable_sent
83-
and not manual_shutdown
84-
):
136+
if soc >= medium_threshold:
85137
rospy.loginfo(
86-
f'Enabling Starlink: SOC {soc}% > {force_on_threshold} and charging'
138+
f'Enabling Starlink: SOC {soc:.1f}% >= {medium_threshold}% '
139+
'at scheduled time'
87140
)
88141
outlet_pub.publish(StdBool(True))
89-
enable_sent = True
90-
elif (
91-
soc >= on_threshold
92-
and not enable_sent
93-
and not manual_shutdown
94-
and (last_off is None or now - last_off > off_delay)
95-
):
96-
rospy.loginfo(
97-
f'Enabling Starlink: SOC {soc}% > {on_threshold}'
98-
f' and last_off {int(now-last_off)}s > {off_delay}'
142+
already_sent = True
143+
elif soc < medium_threshold:
144+
send_alerts(
145+
alerts,
146+
'Not enabling Starlink at scheduled time because '
147+
f'SOC {soc:.1f}% below medium threshold {medium_threshold}%'
99148
)
100-
outlet_pub.publish(StdBool(True))
101-
enable_sent = True
102-
elif soc < on_threshold:
103-
enable_sent = False
149+
schedule_daily_timer()
104150

105-
rospy.Subscriber('/seatrac/outlet/starlink/status', OutletStatus,
106-
outlet_status_cb)
107-
rospy.Subscriber('/seatrac/power', PowerLevel, power_level_cb)
151+
if scheduled_on is not None:
152+
schedule_daily_timer()
108153

109154
rospy.spin()
110155

111156

112157
if __name__ == '__main__':
113-
try:
114-
main()
115-
except rospy.ROSInterruptException:
116-
pass
158+
main()

0 commit comments

Comments
 (0)