flame-wro-fe 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,160 @@
1
+ import time
2
+
3
+
4
+ class StateMachine:
5
+ def __init__(self, behavior=None):
6
+ self.behavior = behavior or {}
7
+ self.current_state = "STARTING"
8
+ self.last_state_time = time.monotonic()
9
+ self.round_dir = int(self.behavior.get("fixed_round_dir", 0))
10
+ self.turns_left = int(self.behavior.get("turns", 12))
11
+ self.time_diff = 0.0
12
+ self.search_for_dir = self.round_dir == 0
13
+ self._round_dir_votes = 0
14
+ self._scheduled_state = None
15
+ self._scheduled_state_block = False
16
+ self._time_last_avoid = -999.0
17
+ self.next_pillar = None
18
+ self.is_pillar_round = bool(self.behavior.get("pillars", False))
19
+
20
+ if self.round_dir != 0:
21
+ self.round_dir = 1 if self.round_dir > 0 else -1
22
+
23
+ def transition_state(self, new_state):
24
+ self.current_state = new_state
25
+ self.last_state_time = time.monotonic()
26
+
27
+ def schedule_state_transition(self, new_state, delay_seconds, block=True):
28
+ self._scheduled_state = (new_state, time.monotonic() + float(delay_seconds))
29
+ self._scheduled_state_block = block
30
+
31
+ def add_round_dir_vote(self, vote):
32
+ if not self.search_for_dir or vote == 0:
33
+ return
34
+ self._round_dir_votes += 1 if vote > 0 else -1
35
+ threshold = int(self.behavior.get("round_dir_vote_threshold", 10))
36
+ if abs(self._round_dir_votes) >= threshold:
37
+ self.round_dir = 1 if self._round_dir_votes > 0 else -1
38
+ self.search_for_dir = False
39
+ self.transition_state("PD-CENTER")
40
+
41
+ def should_transition_state(self, portion_orange, portion_blue, pillars):
42
+ now = time.monotonic()
43
+ self.time_diff = now - self.last_state_time
44
+
45
+ if self._scheduled_state is not None:
46
+ new_state, scheduled_time = self._scheduled_state
47
+ if now >= scheduled_time:
48
+ self.transition_state(new_state)
49
+ self._scheduled_state = None
50
+ return True
51
+ if self._scheduled_state_block:
52
+ return False
53
+
54
+ if self.current_state == "STARTING":
55
+ if pillars and self.is_pillar_round:
56
+ next_pillar = pillars[0]
57
+ area = next_pillar.area
58
+ seen_frames = int(getattr(next_pillar, "seen_frames", 1))
59
+ if (
60
+ area > self.behavior.get("pillar_track_area", 190)
61
+ and seen_frames >= self.behavior.get("pillar_track_confirm_frames", 1)
62
+ and not next_pillar.ignore
63
+ ):
64
+ self.next_pillar = next_pillar
65
+ self.transition_state("TRACKING-PILLAR")
66
+ return True
67
+ if self.round_dir != 0 and not self.search_for_dir:
68
+ self.transition_state("PD-CENTER")
69
+ return True
70
+ return False
71
+
72
+ if self.current_state == "PD-CENTER" and self.turns_left <= 0 and self._scheduled_state is None:
73
+ self.schedule_state_transition(
74
+ "DONE",
75
+ self.behavior.get("finish_delay_seconds", 3.1),
76
+ False,
77
+ )
78
+ return True
79
+
80
+ if (
81
+ self.current_state in ("TURNING-L", "TURNING-R", "PD-CENTER")
82
+ and self.time_diff < self.behavior.get("state_hold_seconds", 0.4)
83
+ ):
84
+ return False
85
+
86
+ if pillars and self.is_pillar_round:
87
+ next_pillar = pillars[0]
88
+ area = next_pillar.area
89
+ seen_frames = int(getattr(next_pillar, "seen_frames", 1))
90
+ if self.current_state == "PD-CENTER":
91
+ self.next_pillar = next_pillar
92
+ if (
93
+ area > self.behavior.get("pillar_track_area", 190)
94
+ and seen_frames >= self.behavior.get("pillar_track_confirm_frames", 1)
95
+ and not next_pillar.ignore
96
+ ):
97
+ self.transition_state("TRACKING-PILLAR")
98
+ return True
99
+ elif self.current_state in ("TRACKING-PILLAR", "PD-CENTER"):
100
+ if (
101
+ area > self.behavior.get("pillar_avoid_area", 530)
102
+ and seen_frames >= self.behavior.get("pillar_avoid_confirm_frames", 1)
103
+ and now - self._time_last_avoid > self.behavior.get("pillar_avoid_cooldown_seconds", 1.0)
104
+ ):
105
+ self.transition_state("AVOIDING-R" if next_pillar.color == "RED" else "AVOIDING-G")
106
+ self.next_pillar = None
107
+ return True
108
+
109
+ if self.current_state == "TRACKING-PILLAR" and not pillars:
110
+ self.transition_state("PD-CENTER")
111
+ return True
112
+
113
+ if self.current_state in ("AVOIDING-R", "AVOIDING-G"):
114
+ if self.time_diff > self.behavior.get("pillar_avoid_seconds", 2.5):
115
+ self.transition_state("PD-CENTER")
116
+ self._time_last_avoid = now
117
+ return True
118
+
119
+ if self.current_state in ("TURNING-L", "TURNING-R"):
120
+ if self.time_diff > self.behavior.get("turn_timeout_seconds", 0.85):
121
+ self.transition_state("PD-CENTER")
122
+ return True
123
+ return False
124
+
125
+ min_portion = self.behavior.get("line_marker_min_portion", 0.25)
126
+ if portion_blue > min_portion:
127
+ if self.current_state != "TURNING-R" and self.round_dir < 0:
128
+ self.turns_left -= 1
129
+ delay = self.behavior.get(
130
+ "pillar_turn_delay_seconds" if self.is_pillar_round else "turn_delay_seconds",
131
+ 0.3 if self.is_pillar_round else 0.7,
132
+ )
133
+ self.schedule_state_transition("TURNING-L", delay)
134
+ else:
135
+ self.transition_state("PD-CENTER")
136
+ return True
137
+
138
+ if portion_orange > min_portion:
139
+ if self.current_state != "TURNING-L" and self.round_dir > 0:
140
+ self.turns_left -= 1
141
+ delay = self.behavior.get(
142
+ "pillar_turn_delay_seconds" if self.is_pillar_round else "turn_delay_seconds",
143
+ 0.3 if self.is_pillar_round else 0.7,
144
+ )
145
+ self.schedule_state_transition("TURNING-R", delay)
146
+ else:
147
+ self.transition_state("PD-CENTER")
148
+ return True
149
+
150
+ return False
151
+
152
+ # Backwards-compatible names for older scripts.
153
+ def transitionState(self, new_state):
154
+ return self.transition_state(new_state)
155
+
156
+ def scheduleStateTransition(self, new_state, time_diff, block=True):
157
+ return self.schedule_state_transition(new_state, time_diff, block)
158
+
159
+ def shouldTransitionState(self, portion_orange, portion_blue, pillars):
160
+ return self.should_transition_state(portion_orange, portion_blue, pillars)
@@ -0,0 +1,3 @@
1
+ sudo cp vehicle-runtime.service /lib/systemd/system/
2
+ sudo systemctl daemon-reload
3
+ sudo systemctl enable vehicle-runtime.service
@@ -0,0 +1,24 @@
1
+ import cv2
2
+ import numpy as np
3
+
4
+
5
+ def find_round_dir(black_img):
6
+ if black_img is None or black_img.size == 0:
7
+ return 0
8
+
9
+ edges_img = cv2.Canny(black_img, 30, 90, 3)
10
+ if edges_img.size == 0:
11
+ return 0
12
+
13
+ edges_img[0, :] = 0
14
+ edges_img[-1, :] = 0
15
+ wall_heights = np.argmax(edges_img, axis=0)
16
+ wall_heights = np.where(wall_heights == 0, edges_img.shape[0] - 1, wall_heights)
17
+ differences = np.diff(wall_heights)
18
+
19
+ min_jump = 9
20
+ counter_clockwise = np.sum(differences > min_jump)
21
+ clockwise = np.sum(differences < -min_jump)
22
+ if clockwise == counter_clockwise:
23
+ return 0
24
+ return -1 if clockwise > counter_clockwise else 1