Browse Source

initial commit

Thomas Buck 1 year ago
commit
edf8a7679a
9 changed files with 390 additions and 0 deletions
  1. 112
    0
      CatToy.py
  2. 5
    0
      README.md
  3. 4
    0
      config.py
  4. 21
    0
      copy.sh
  5. 56
    0
      servo.py
  6. 4
    0
      test_servo.py
  7. 5
    0
      test_wifi.py
  8. 65
    0
      toy.py
  9. 118
    0
      wifi.py

+ 112
- 0
CatToy.py View File

@@ -0,0 +1,112 @@
1
+from wifi import Wifi
2
+from config import Config
3
+from toy import Toy
4
+
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
+"""
35
+
36
+t = Toy()
37
+
38
+def rootCallback(request):
39
+    return html % (
40
+        '<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
+        ''
43
+    )
44
+
45
+def servoCallback(request):
46
+    q = request.find("/servos?")
47
+    p1 = request.find("s1=")
48
+    p2 = request.find("s2=")
49
+    if (q < 0) or (p1 < 0) or (p2 < 0):
50
+        return html % (
51
+            '<p>Error: no servo arguments found in URL query string.</p>',
52
+            round(max_laser_power * 100.0),
53
+            '<p><a href="/">Back to main page</a></p>'
54
+        )
55
+
56
+    servos = [p1, p2]
57
+    result = []
58
+    for p in servos:
59
+        pe = request.find("&s", p)
60
+        if (pe < 0) or (p + 3 >= pe) or (pe - (p + 3) > 3):
61
+            pe = request.find(" HTTP", p)
62
+        if (pe < 0) or (p + 3 >= pe) or (pe - (p + 3) > 3):
63
+            return html % (
64
+                '<p>Error parsing query string.</p>',
65
+            round(max_laser_power * 100.0),
66
+                '<p><a href="/">Back to main page</a></p>'
67
+            )
68
+        r = request[p + 3 : pe]
69
+        s = int(r)
70
+        result.append(s)
71
+
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
76
+    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
+    t.angle(t.tilt, result[1])
83
+
84
+    return html % (
85
+        '<p>Servos move to s1=' + str(result[0]) + ' s2=' + str(result[1]) + '.</p>',
86
+        round(max_laser_power * 100.0),
87
+        '<p><a href="/">Back to main page</a></p>'
88
+    )
89
+
90
+def laserCallback(request):
91
+    value = 0.0
92
+    text = "off"
93
+
94
+    if request.find("?s=") == 10:
95
+        pe = request.find(" HTTP", 10)
96
+        r = request[13 : pe]
97
+        if r != "Off":
98
+            value = int(r)
99
+            text = "to " + str(r) + '%'
100
+
101
+    t.laser(value / 100.0)
102
+    return html % (
103
+        '<p>Laser turned ' + text + '!</p>',
104
+        round(max_laser_power * 100.0),
105
+        '<p><a href="/">Back to main page</a></p>'
106
+    )
107
+
108
+w = Wifi(Config.ssid, Config.password)
109
+w.add_handler("/", rootCallback)
110
+w.add_handler("/servos", servoCallback)
111
+w.add_handler("/laser", laserCallback)
112
+w.listen()

+ 5
- 0
README.md View File

@@ -0,0 +1,5 @@
1
+
2
+https://how2electronics.com/how-to-control-servo-motor-with-raspberry-pi-pico/
3
+https://github.com/dhylands/rshell/blob/master/README.rst
4
+https://github.com/raspberrypi/pico-examples/blob/master/pico_w/wifi/python_test_tcp/micropython_test_tcp_server.py
5
+https://github.com/raspberrypi/pico-micropython-examples/blob/master/wireless/webserver.py

+ 4
- 0
config.py View File

@@ -0,0 +1,4 @@
1
+class Config:
2
+# Set your wifi ssid and password here
3
+    ssid = const('')
4
+    password = const('')

+ 21
- 0
copy.sh View File

@@ -0,0 +1,21 @@
1
+#!/bin/bash
2
+
3
+PORT=/dev/ttyACM1
4
+
5
+if [ $# -ne 0 ] ; then
6
+cat << EOF | rshell -p $PORT
7
+cp config.py /pyboard
8
+cp servo.py /pyboard
9
+cp toy.py /pyboard
10
+cp wifi.py /pyboard
11
+cp $1 /pyboard/main.py
12
+EOF
13
+else
14
+cat << EOF | rshell -p $PORT
15
+cp config.py /pyboard
16
+cp servo.py /pyboard
17
+cp toy.py /pyboard
18
+cp wifi.py /pyboard
19
+cp CatToy.py /pyboard
20
+EOF
21
+fi

+ 56
- 0
servo.py View File

@@ -0,0 +1,56 @@
1
+# https://how2electronics.com/how-to-control-servo-motor-with-raspberry-pi-pico/
2
+
3
+from machine import Pin, PWM
4
+
5
+class Servo:
6
+    """ A simple class for controlling a 9g servo with the Raspberry Pi Pico.
7
+    Attributes:
8
+        minVal: An integer denoting the minimum duty value for the servo motor.
9
+        maxVal: An integer denoting the maximum duty value for the servo motor.
10
+    """
11
+
12
+    def __init__(self, pin: int or Pin or PWM, minVal=2500, maxVal=7500):
13
+        """ Creates a new Servo Object.
14
+        args:
15
+            pin (int or machine.Pin or machine.PWM): Either an integer denoting the number of the GPIO pin or an already constructed Pin or PWM object that is connected to the servo.
16
+            minVal (int): Optional, denotes the minimum duty value to be used for this servo.
17
+            maxVal (int): Optional, denotes the maximum duty value to be used for this servo.
18
+        """
19
+
20
+        if isinstance(pin, int):
21
+            pin = Pin(pin, Pin.OUT)
22
+        if isinstance(pin, Pin):
23
+            self.__pwm = PWM(pin)
24
+        if isinstance(pin, PWM):
25
+            self.__pwm = pin
26
+        self.__pwm.freq(50)
27
+        self.minVal = minVal
28
+        self.maxVal = maxVal
29
+
30
+    def deinit(self):
31
+        """ Deinitializes the underlying PWM object.
32
+        """
33
+        self.__pwm.deinit()
34
+
35
+    def goto(self, value: int):
36
+        """ Moves the servo to the specified position.
37
+        args:
38
+            value (int): The position to move to, represented by a value from 0 to 1024 (inclusive).
39
+        """
40
+        if value < 0:
41
+            value = 0
42
+        if value > 1024:
43
+            value = 1024
44
+        delta = self.maxVal-self.minVal
45
+        target = int(self.minVal + ((value / 1024) * delta))
46
+        self.__pwm.duty_u16(target)
47
+
48
+    def middle(self):
49
+        """ Moves the servo to the middle.
50
+        """
51
+        self.goto(512)
52
+
53
+    def free(self):
54
+        """ Allows the servo to be moved freely.
55
+        """
56
+        self.__pwm.duty_u16(0)

+ 4
- 0
test_servo.py View File

@@ -0,0 +1,4 @@
1
+from toy import Toy
2
+
3
+t = Toy()
4
+t.test()

+ 5
- 0
test_wifi.py View File

@@ -0,0 +1,5 @@
1
+from wifi import Wifi
2
+from config import Config
3
+
4
+w = Wifi(Config.ssid, Config.password)
5
+w.listen()

+ 65
- 0
toy.py View File

@@ -0,0 +1,65 @@
1
+# https://how2electronics.com/how-to-control-servo-motor-with-raspberry-pi-pico/
2
+
3
+import time
4
+from machine import Pin, PWM
5
+from servo import Servo
6
+
7
+class Toy:
8
+    """ Tools to control the Cat Toy hardware.
9
+    Attributes:
10
+        servo1: GPIO pin number of the pan servo.
11
+        servo2: GPIO pin number of the tilt servo.
12
+        laser: GPIO pin number of the laser diode.
13
+    """
14
+
15
+    # first servo
16
+    pan_min = 20
17
+    pan_max = 160
18
+
19
+    # sevond servo
20
+    tilt_min = 0
21
+    tilt_max = 90
22
+
23
+    def __init__(self, servo1 = 28, servo2 = 27, laser = 2):
24
+        self.laserPin = PWM(Pin(laser, Pin.OUT))
25
+        self.laserPin.freq(1000)
26
+        self.laser(0)
27
+
28
+        self.pan = Servo(servo1)
29
+        self.tilt = Servo(servo2)
30
+
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)
33
+        time.sleep(0.1)
34
+        self.pan.free()
35
+        self.tilt.free()
36
+
37
+    def map_value(self, x, in_min, in_max, out_min, out_max):
38
+        return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
39
+
40
+    def angle(self, servo, angle):
41
+        if angle < 0:
42
+            angle = 0
43
+        if angle > 180:
44
+            angle = 180
45
+        servo.goto(round(self.map_value(angle, 0, 180, 0, 1024)))
46
+
47
+    def laser(self, value):
48
+        v = 1.0 - value
49
+        self.laserPin.duty_u16(round(v * 65535))
50
+
51
+    def test(self, steps = 10):
52
+        self.laser(1)
53
+
54
+        for y in range(self.tilt_min, self.tilt_max, round((self.tilt_max - self.tilt_min) / steps)):
55
+            self.angle(self.tilt, y)
56
+            time.sleep(0.2)
57
+
58
+            for x in range(self.pan_min, self.pan_max, round((self.pan_max - self.pan_min) / steps)):
59
+                self.angle(self.pan, x)
60
+                time.sleep(0.2)
61
+
62
+        self.tilt.free()
63
+        self.pan.free()
64
+
65
+        self.laser(0)

+ 118
- 0
wifi.py View File

@@ -0,0 +1,118 @@
1
+import network
2
+import socket
3
+import time
4
+from machine import Pin
5
+
6
+class Wifi:
7
+    html = """<!DOCTYPE html>
8
+    <html>
9
+        <head>
10
+            <title>Pico W</title>
11
+        </head>
12
+        <body>
13
+            <h1>%s</h1>
14
+            <p>%s</p>
15
+            <pre>%s</pre>
16
+        </body>
17
+    </html>
18
+    """
19
+
20
+    def __init__(self, ssid, password, port = 80):
21
+        # Check if wifi details have been set
22
+        if len(ssid) == 0 or len(password) == 0:
23
+            raise RuntimeError('Please set wifi ssid and password in config.py')
24
+            self.led.value(1)
25
+
26
+        self.led = Pin("LED", Pin.OUT)
27
+
28
+        # Start connection
29
+        self.wlan = network.WLAN(network.STA_IF)
30
+        self.wlan.active(True)
31
+        self.wlan.connect(ssid, password)
32
+
33
+        # Wait for connect success or failure
34
+        max_wait = 20
35
+        error_count = 20
36
+        while max_wait > 0:
37
+            if self.wlan.status() >= 3:
38
+                break
39
+            elif self.wlan.status() < 0:
40
+                self.wlan.connect(ssid, password)
41
+                error_count -= 1
42
+                if error_count <= 0:
43
+                    break
44
+            else:
45
+                max_wait -= 1
46
+            print('waiting for connection...')
47
+            self.led.value(not self.led.value())
48
+            time.sleep(0.5)
49
+
50
+        # Handle connection error
51
+        if self.wlan.status() != 3:
52
+            raise RuntimeError('wifi connection failed %d' % self.wlan.status())
53
+            self.led.value(1)
54
+
55
+        print('connected')
56
+        status = self.wlan.ifconfig()
57
+        print('ip = ' + status[0])
58
+
59
+        # Open socket to the server
60
+        self.sock = socket.socket()
61
+        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
62
+        addr = socket.getaddrinfo('0.0.0.0', port)[0][-1]
63
+        self.sock.bind(addr)
64
+        self.sock.listen(1)
65
+        print('listening on', addr)
66
+
67
+        self.handlers = []
68
+        self.led.value(0)
69
+
70
+    def add_handler(self, path, callback):
71
+        for hp, hc in self.handlers:
72
+            if hp == path:
73
+                raise RuntimeError('path is already registered %s' % path)
74
+                self.led.value(1)
75
+        h = (path, callback)
76
+        self.handlers.append(h)
77
+
78
+
79
+    def listen_once(self):
80
+        # Listen for connections
81
+        try:
82
+            cl, addr = self.sock.accept()
83
+            print('client connected from', addr)
84
+
85
+            request = cl.recv(1024).decode('utf-8')
86
+            #print(request)
87
+
88
+            response = ""
89
+            found = False
90
+            for path, callback in self.handlers:
91
+                pos = request.find(path)
92
+                if ((pos == 4) or (pos == 5)) and ((request[pos + len(path)] == ' ') or (request[pos + len(path)] == '?')):
93
+                    found = True
94
+                    response = callback(request)
95
+                    break
96
+
97
+            code = 200
98
+            title = "OK"
99
+            if not found:
100
+                code = 404
101
+                title = "Not Found"
102
+                response = self.html % (str(code), title, request)
103
+            elif len(response) == 0:
104
+                code = 503
105
+                title = "Internal Server Error"
106
+                response = self.html % (str(code), title, request)
107
+
108
+            cl.send('HTTP/1.0 ' + str(code) + ' ' + title + '\r\nContent-type: text/html\r\n\r\n')
109
+            cl.send(response)
110
+        except OSError as e:
111
+            print(e)
112
+        finally:
113
+            cl.close()
114
+            print('connection closed')
115
+
116
+    def listen(self):
117
+        while True:
118
+            self.listen_once()

Loading…
Cancel
Save