瀏覽代碼

add automatic mode

Thomas Buck 1 年之前
父節點
當前提交
2575ee857b
共有 2 個文件被更改,包括 192 次插入64 次删除
  1. 180
    50
      CatToy.py
  2. 12
    14
      toy.py

+ 180
- 50
CatToy.py 查看文件

@@ -1,45 +1,75 @@
1 1
 from wifi import Wifi
2 2
 from config import Config
3 3
 from toy import Toy
4
+import random
5
+from machine import Timer
4 6
 
5
-max_laser_power = 0.25
6
-
7
-html = """<!DOCTYPE html>
8
-<html>
9
-    <head>
10
-        <title>Cat Toy</title>
11
-    </head>
12
-    <body>
13
-        <h1>Cat Toy</h1>
14
-        %s
15
-        <h2>Laser Pointer</h2>
16
-        <form action="/laser" method="get">
17
-            Turn power:
18
-            <input type="range" name="s" min="0" max="100" step="1" value="%d">
19
-            <input type="submit" value="Set"><br>
20
-        </form>
21
-        <form action="/laser" method="get">
22
-            Or just turn it:
23
-            <input type="submit" name="s" value="Off">
24
-        </form>
25
-        <h2>Pan and Tilt Servos</h2>
26
-        <form action="/servos" method="get">
27
-            Pan: <input type="text" name="s1"><br>
28
-            Tilt: <input type="text" name="s2"><br>
29
-            <input type="submit" value="Move">
30
-        </form>
31
-        %s
32
-    </body>
33
-</html>
34
-"""
7
+max_laser_power = 0.15
35 8
 
9
+limits = [
10
+    # pan_min, pan_max, tilt_min, tilt_max, name
11
+    (84, 120, 53, 76, 'office desk, front right')
12
+]
13
+
14
+def buildPage(header, footer):
15
+    html = """<!DOCTYPE html>
16
+    <html>
17
+        <head>
18
+            <title>Cat Toy</title>
19
+        </head>
20
+        <body>
21
+            <h1>Cat Toy</h1>
22
+            %s
23
+            <h2>Laser Pointer</h2>
24
+            <form action="/laser" method="get">
25
+                Turn power:
26
+                <input type="range" name="s" min="0" max="100" step="1" value="%d">
27
+                <input type="submit" value="Set"><br>
28
+            </form>
29
+            <form action="/laser" method="get">
30
+                Or just turn it:
31
+                <input type="submit" name="s" value="Off">
32
+            </form>
33
+            <h2>Pan and Tilt Servos</h2>
34
+            <form action="/servos" method="get">
35
+                Pan: <input type="text" name="s1"><br>
36
+                Tilt: <input type="text" name="s2"><br>
37
+                <input type="submit" value="Move">
38
+            </form>
39
+            <form action="/random_move" method="get">
40
+                <input type="submit" name="s" value="Go to random position">
41
+            </form>
42
+            <h2>Auto Play</h2>
43
+            <form action="/repeat" method="get">
44
+                Limits:
45
+                <select name="limit">
46
+                    %s
47
+                </select><br>
48
+                Steps: <input type="text" name="steps" value="100"><br>
49
+                Duration: <input type="text" name="duration" value="1000">ms<br>
50
+                <input type="submit" name="s" value="Start Program">
51
+            </form>
52
+            %s
53
+        </body>
54
+    </html>
55
+    """
56
+
57
+    sl = ""
58
+    for pan_min, pan_max, tilt_min, tilt_max, name in limits:
59
+        val = name.replace(' ', '_').replace(',', '').lower()
60
+        sl += '<option value="' + val + '">' + name + '</option>'
61
+    sl += '<option value="">None</option>'
62
+
63
+    page = html % (header, int(max_laser_power * 100.0), sl, footer)
64
+    return page
65
+
66
+random.seed()
36 67
 t = Toy()
37 68
 
38 69
 def rootCallback(request):
39
-    return html % (
70
+    return buildPage(
40 71
         '<p>Welcome to the Cat Toy interface by <a href="https://www.xythobuz.de">xythobuz</a>.</p>',
41
-        round(max_laser_power * 100.0),
42
-        ''
72
+        "<p><b>Limits:</b> tMin={} tMax={} pMin={} pMax={}</p>".format(t.tilt_min, t.tilt_max, t.pan_min, t.pan_max)
43 73
     )
44 74
 
45 75
 def servoCallback(request):
@@ -47,9 +77,9 @@ def servoCallback(request):
47 77
     p1 = request.find("s1=")
48 78
     p2 = request.find("s2=")
49 79
     if (q < 0) or (p1 < 0) or (p2 < 0):
50
-        return html % (
80
+        print("servo query error: q={} p1={} p2={}".format(q, p1, p2))
81
+        return buildPage(
51 82
             '<p>Error: no servo arguments found in URL query string.</p>',
52
-            round(max_laser_power * 100.0),
53 83
             '<p><a href="/">Back to main page</a></p>'
54 84
         )
55 85
 
@@ -60,30 +90,21 @@ def servoCallback(request):
60 90
         if (pe < 0) or (p + 3 >= pe) or (pe - (p + 3) > 3):
61 91
             pe = request.find(" HTTP", p)
62 92
         if (pe < 0) or (p + 3 >= pe) or (pe - (p + 3) > 3):
63
-            return html % (
93
+            print("servo query error: p={} pe={}".format(p, pe))
94
+            return buildPage(
64 95
                 '<p>Error parsing query string.</p>',
65
-            round(max_laser_power * 100.0),
66 96
                 '<p><a href="/">Back to main page</a></p>'
67 97
             )
68 98
         r = request[p + 3 : pe]
69 99
         s = int(r)
70 100
         result.append(s)
71 101
 
72
-    if result[0] < t.pan_min:
73
-        result[0] = t.pan_min
74
-    if result[0] > t.pan_max:
75
-        result[0] = t.pan_max
102
+    print("servos: pan={} tilt={}", result[0], result[1])
76 103
     t.angle(t.pan, result[0])
77
-
78
-    if result[1] < t.tilt_min:
79
-        result[1] = t.tilt_min
80
-    if result[1] > t.tilt_max:
81
-        result[1] = t.tilt_max
82 104
     t.angle(t.tilt, result[1])
83 105
 
84
-    return html % (
106
+    return buildPage(
85 107
         '<p>Servos move to s1=' + str(result[0]) + ' s2=' + str(result[1]) + '.</p>',
86
-        round(max_laser_power * 100.0),
87 108
         '<p><a href="/">Back to main page</a></p>'
88 109
     )
89 110
 
@@ -98,10 +119,117 @@ def laserCallback(request):
98 119
             value = int(r)
99 120
             text = "to " + str(r) + '%'
100 121
 
122
+    print("laser: {}%".format(value))
101 123
     t.laser(value / 100.0)
102
-    return html % (
124
+
125
+    return buildPage(
103 126
         '<p>Laser turned ' + text + '!</p>',
104
-        round(max_laser_power * 100.0),
127
+        '<p><a href="/">Back to main page</a></p>'
128
+    )
129
+
130
+def randomMoveCallback(request):
131
+    tilt = random.randint(t.tilt_min, t.tilt_max)
132
+    pan = random.randint(t.pan_min, t.pan_max)
133
+    print("random: tilt={} pan={}".format(tilt, pan))
134
+    t.angle(t.tilt, tilt)
135
+    t.angle(t.pan, pan)
136
+    return buildPage(
137
+        '<p>Random move to pan={} tilt={}</p>'.format(pan, tilt),
138
+        '<p><a href="/">Back to main page</a></p>'
139
+    )
140
+
141
+def doMove(pan_min, pan_max, tilt_min, tilt_max, dur):
142
+    tilt = random.randint(tilt_min, tilt_max)
143
+    pan = random.randint(pan_min, pan_max)
144
+    print("random move: tilt={} pan={} duration={}".format(tilt, pan, dur))
145
+    t.angle(t.tilt, tilt)
146
+    t.angle(t.pan, pan)
147
+
148
+timerRunning = False
149
+timerData = None
150
+
151
+def timerCallback(unused):
152
+    global timerRunning, timerData
153
+
154
+    if not timerRunning:
155
+        return
156
+
157
+    pan_min, pan_max, tilt_min, tilt_max, steps, duration = timerData
158
+
159
+    dur = duration
160
+    if dur < 200:
161
+        dur = random.randint(200, 2000)
162
+    else:
163
+        dur = random.randint(200, duration)
164
+
165
+    if steps > 0:
166
+        steps -= 1
167
+        doMove(pan_min, pan_max, tilt_min, tilt_max, dur)
168
+        tim = Timer(period = dur, mode=Timer.ONE_SHOT, callback = timerCallback)
169
+    else:
170
+        timerRunning = False
171
+        t.laser(0.0)
172
+
173
+    timerData = (pan_min, pan_max, tilt_min, tilt_max, steps, duration)
174
+
175
+def startRepeat(pan_min, pan_max, tilt_min, tilt_max, steps, duration):
176
+    global timerRunning, timerData
177
+    timerData = (pan_min, pan_max, tilt_min, tilt_max, steps, duration)
178
+
179
+    if not timerRunning:
180
+        timerRunning = True
181
+        t.laser(max_laser_power)
182
+        timerCallback(None)
183
+
184
+def stopRepeat():
185
+    global timerRunning, timerData
186
+    timerRunning = False
187
+    t.laser(0.0)
188
+
189
+def repeatCallback(request):
190
+    q = request.find("/repeat?")
191
+    pl = request.find("limit=")
192
+    ps = request.find("steps=")
193
+    pd = request.find("duration=")
194
+    if (q < 0) or (pl < 0) or (ps < 0) or (pd < 0):
195
+        print("repeat query error: q={} pl={} ps={} pd={}".format(q, pl, ps, pd))
196
+        return buildPage(
197
+            '<p>Error: no repeat arguments found in URL query string.</p>',
198
+            '<p><a href="/">Back to main page</a></p>'
199
+        )
200
+
201
+    data = [("limit=", pl), ("steps=", ps), ("duration=", pd)]
202
+    result = []
203
+    for s, p in data:
204
+        pe = request.find("&", p)
205
+        if (pe < 0) or (p + len(s) > pe) or (pe - (p + 3) > 40):
206
+            pe = request.find(" HTTP", p)
207
+        if (pe < 0) or (p + len(s) > pe) or (pe - (p + 3) > 40):
208
+            print("repeat query error: p={} pe={}".format(p, pe))
209
+            return buildPage(
210
+                '<p>Error parsing query string.</p>',
211
+                '<p><a href="/">Back to main page</a></p>'
212
+            )
213
+        r = request[p + len(s) : pe]
214
+        result.append(r)
215
+
216
+    print("repeat: limit={} steps={} duration={}".format(result[0], result[1], result[2]))
217
+
218
+    if len(result[0]) == 0:
219
+        stopRepeat()
220
+        return buildPage(
221
+            '<p>Stopped repeated automatic moves!</p>',
222
+            '<p><a href="/">Back to main page</a></p>'
223
+        )
224
+
225
+    for pan_min, pan_max, tilt_min, tilt_max, name in limits:
226
+        val = name.replace(' ', '_').replace(',', '').lower()
227
+        if result[0] == val:
228
+            startRepeat(pan_min, pan_max, tilt_min, tilt_max, int(result[1]), int(result[2]))
229
+            break
230
+
231
+    return buildPage(
232
+        '<p>Starting moves with limit={} steps={} duration={}</p>'.format(result[0], result[1], result[2]),
105 233
         '<p><a href="/">Back to main page</a></p>'
106 234
     )
107 235
 
@@ -109,4 +237,6 @@ w = Wifi(Config.ssid, Config.password)
109 237
 w.add_handler("/", rootCallback)
110 238
 w.add_handler("/servos", servoCallback)
111 239
 w.add_handler("/laser", laserCallback)
240
+w.add_handler("/random_move", randomMoveCallback)
241
+w.add_handler("/repeat", repeatCallback)
112 242
 w.listen()

+ 12
- 14
toy.py 查看文件

@@ -12,13 +12,9 @@ class Toy:
12 12
         laser: GPIO pin number of the laser diode.
13 13
     """
14 14
 
15
-    # first servo
16
-    pan_min = 20
17
-    pan_max = 160
18
-
19
-    # sevond servo
20
-    tilt_min = 0
21
-    tilt_max = 90
15
+    # maximum movements on cardboard box
16
+    # pan_min, pan_max, tilt_min, tilt_max
17
+    maximum_limits = (20, 160, 0, 90)
22 18
 
23 19
     def __init__(self, servo1 = 28, servo2 = 27, laser = 2):
24 20
         self.laserPin = PWM(Pin(laser, Pin.OUT))
@@ -28,8 +24,9 @@ class Toy:
28 24
         self.pan = Servo(servo1)
29 25
         self.tilt = Servo(servo2)
30 26
 
31
-        self.angle(self.pan, round((self.pan_max - self.pan_min) / 2) + self.pan_min)
32
-        self.angle(self.tilt, round((self.tilt_max - self.tilt_min) / 2) + self.tilt_min)
27
+        pan_min, pan_max, tilt_min, tilt_max = self.maximum_limits
28
+        self.angle(self.pan, int((pan_max - pan_min) / 2) + pan_min)
29
+        self.angle(self.tilt, int((tilt_max - tilt_min) / 2) + tilt_min)
33 30
         time.sleep(0.1)
34 31
         self.pan.free()
35 32
         self.tilt.free()
@@ -42,20 +39,21 @@ class Toy:
42 39
             angle = 0
43 40
         if angle > 180:
44 41
             angle = 180
45
-        servo.goto(round(self.map_value(angle, 0, 180, 0, 1024)))
42
+        servo.goto(int(self.map_value(angle, 0, 180, 0, 1024)))
46 43
 
47 44
     def laser(self, value):
48 45
         v = 1.0 - value
49
-        self.laserPin.duty_u16(round(v * 65535))
46
+        self.laserPin.duty_u16(int(v * 65535))
50 47
 
51 48
     def test(self, steps = 10):
49
+        pan_min, pan_max, tilt_min, tilt_max = self.maximum_limits
50
+
52 51
         self.laser(1)
53 52
 
54
-        for y in range(self.tilt_min, self.tilt_max, round((self.tilt_max - self.tilt_min) / steps)):
53
+        for y in range(tilt_min, tilt_max, int((tilt_max - tilt_min) / steps)):
55 54
             self.angle(self.tilt, y)
56
-            time.sleep(0.2)
57 55
 
58
-            for x in range(self.pan_min, self.pan_max, round((self.pan_max - self.pan_min) / steps)):
56
+            for x in range(pan_min, pan_max, int((pan_max - pan_min) / steps)):
59 57
                 self.angle(self.pan, x)
60 58
                 time.sleep(0.2)
61 59
 

Loading…
取消
儲存