|
1 | 1 | #!/usr/bin/env python |
2 | | - |
3 | 2 | from sys import exit |
4 | | -import argparse |
5 | | -import time |
6 | 3 |
|
7 | 4 | try: |
8 | 5 | import paho.mqtt.client as mqtt |
9 | 6 | except ImportError: |
10 | | - exit("This example requires the paho-mqtt module\nInstall with: sudo pip install paho-mqtt") |
| 7 | + exit('This example requires the paho-mqtt module\nInstall with: sudo pip install paho-mqtt') |
11 | 8 |
|
12 | 9 | import blinkt |
13 | 10 |
|
14 | 11 |
|
15 | | -MQTT_SERVER = "iot.eclipse.org" |
| 12 | +MQTT_SERVER = 'iot.eclipse.org' |
16 | 13 | MQTT_PORT = 1883 |
17 | | -MQTT_TOPIC = "pimoroni/blinkt" |
| 14 | +MQTT_TOPIC = 'pimoroni/blinkt' |
18 | 15 |
|
19 | 16 | # Set these to use authorisation |
20 | 17 | MQTT_USER = None |
21 | 18 | MQTT_PASS = None |
22 | 19 |
|
23 | | -description = """\ |
| 20 | +print(""" |
24 | 21 | MQTT Blinkt! Control |
25 | 22 |
|
26 | 23 | This example uses public MQTT messages from {server} on port {port} to control Blinkt! |
|
29 | 26 |
|
30 | 27 | rgb,<pixel>,<r>,<g>,<b> - Set a single pixel to an RGB colour. Example: rgb,1,255,0,255 |
31 | 28 | clr - Clear Blinkt! |
32 | | -bri,<val> - Set global brightness (for val in range 0.0-1.0) |
33 | 29 |
|
34 | 30 | You can use the online MQTT tester at http://www.hivemq.com/demos/websocket-client/ to send messages. |
35 | 31 |
|
|
38 | 34 | server=MQTT_SERVER, |
39 | 35 | port=MQTT_PORT, |
40 | 36 | topic=MQTT_TOPIC |
41 | | -) |
42 | | -parser = argparse.ArgumentParser(description = description, formatter_class = argparse.RawDescriptionHelpFormatter) |
43 | | -parser.add_argument( '-H', '--host', default = MQTT_SERVER, |
44 | | - help = 'MQTT broker to connect to' ) |
45 | | -parser.add_argument( '-P', '--port', default = MQTT_PORT, type = int, |
46 | | - help = 'port on MQTT broker to connect to' ) |
47 | | -parser.add_argument( '-T', '--topic', action = 'append', |
48 | | - help = 'MQTT topic to subscribe to; can be repeated for multiple topics' ) |
49 | | -parser.add_argument( '-u', '--user', |
50 | | - help = 'MQTT broker user name' ) |
51 | | -parser.add_argument( '-p', '--pass', dest = 'pw', |
52 | | - help = 'MQTT broker password' ) |
53 | | -parser.add_argument( '-q', '--quiet', default = False, action = 'store_true', |
54 | | - help = 'Minimal output (eg for running as a daemon)' ) |
55 | | -parser.add_argument( '-g', '--green-hack', default = False, action = 'store_true', |
56 | | - help = 'Apply hack to green channel to improve colour saturation' ) |
57 | | -parser.add_argument( '--timeout', default = '0', |
58 | | - help = 'Pixel timeout(s). Pixel will blank if last update older than X seconds. May be a single number or comma-separated list. Use 0 for no timeout' ) |
59 | | -parser.add_argument( '-D', '--daemon', metavar='PIDFILE', |
60 | | - help = 'Run as a daemon (implies -q)' ) |
61 | | -args = parser.parse_args() |
62 | | - |
63 | | -# Get timeout list into expected form |
64 | | -args.timeout = args.timeout.split( ',' ) |
65 | | -if len(args.timeout) == 1: |
66 | | - args.timeout = args.timeout * blinkt.NUM_PIXELS |
67 | | -elif len(args.timeout) != blinkt.NUM_PIXELS: |
68 | | - exit("--timeout list must be %s elements long" % (blinkt.NUM_PIXELS,)) |
69 | | -try: |
70 | | - args.timeout = [int(x) for x in args.timeout] |
71 | | -except ValueError as e: |
72 | | - exit("Bad timeout value: %s" % (e,)) |
73 | | -args.timeout = [x and x or None for x in args.timeout] |
74 | | -args.min_timeout = min(args.timeout) |
75 | | - |
76 | | -if args.daemon: |
77 | | - import signal |
78 | | - try: |
79 | | - import daemon |
80 | | - except ImportError: |
81 | | - exit("--daemon requires the daemon module. Install with: sudo pip install python-daemon") |
82 | | - try: |
83 | | - import lockfile.pidlockfile |
84 | | - except ImportError: |
85 | | - exit("--daemon requires the lockfile module. Install with: sudo pip install lockfile") |
86 | | -if not args.topic: |
87 | | - args.topic = [MQTT_TOPIC] |
88 | | - |
89 | | -class PixelClient( mqtt.Client ): |
90 | | - def __init__( self, prog_args, *args, **kwargs ): |
91 | | - super( PixelClient, self ).__init__( *args, **kwargs ) |
92 | | - self.args = prog_args |
93 | | - self.update_time = [None] * blinkt.NUM_PIXELS |
94 | | - self.on_connect = self.on_connect |
95 | | - self.on_message = self.on_message |
96 | | - |
97 | | - blinkt.set_clear_on_exit() |
98 | | - # Some stuff doesn't get set up until the first time show() is called |
99 | | - blinkt.show() |
| 37 | +)) |
| 38 | + |
| 39 | + |
| 40 | +def on_connect(client, userdata, flags, rc): |
| 41 | + print('Connected with result code ' + str(rc)) |
100 | 42 |
|
101 | | - if self.args.user is not None and self.args.pw is not None: |
102 | | - self.username_pw_set(username=self.args.user, password=self.args.pw) |
103 | | - self.connect(self.args.host, self.args.port, 60) |
| 43 | + client.subscribe(MQTT_TOPIC) |
104 | 44 |
|
105 | | - def cmd_clear( self ): |
| 45 | + |
| 46 | +def on_message(client, userdata, msg): |
| 47 | + |
| 48 | + data = msg.payload |
| 49 | + if type(data) is bytes: |
| 50 | + data = data.decode('utf-8') |
| 51 | + data = data.split(',') |
| 52 | + command = data.pop(0) |
| 53 | + |
| 54 | + if command == 'clr' and len(data) == 0: |
106 | 55 | blinkt.clear() |
107 | 56 | blinkt.show() |
| 57 | + return |
108 | 58 |
|
109 | | - def cmd_brightness( self, bri ): |
| 59 | + if command == 'rgb' and len(data) == 4: |
110 | 60 | try: |
111 | | - bri = float(bri) |
112 | | - except ValueError: |
113 | | - print("Malformed command: ", str(msg.payload)) |
114 | | - return |
115 | | - blinkt.set_brightness(bri) |
116 | | - blinkt.show() |
| 61 | + pixel = data.pop(0) |
117 | 62 |
|
118 | | - def cmd_rgb( self, pixel, data ): |
119 | | - try: |
120 | | - if pixel == "*": |
| 63 | + if pixel == '*': |
121 | 64 | pixel = None |
122 | 65 | else: |
123 | 66 | pixel = int(pixel) |
124 | 67 | if pixel > 7: |
125 | | - print("Pixel out of range: " + str(pixel)) |
| 68 | + print('Pixel out of range: ' + str(pixel)) |
126 | 69 | return |
127 | 70 |
|
128 | 71 | r, g, b = [int(x) & 0xff for x in data] |
129 | | - if self.args.green_hack: |
130 | | - # Green is about twice the luminosity for a given value |
131 | | - # than red or blue, so apply a hackish linear compensation |
132 | | - # here taking care of corner cases at 0 and 255. To do it |
133 | | - # properly, it should really be a curve but this approximation |
134 | | - # is quite a lot better than nothing. |
135 | | - if r not in [0,255]: |
136 | | - r = r + 1 |
137 | | - if g not in [0]: |
138 | | - g = g/2 + 1 |
139 | | - if b not in [0,255]: |
140 | | - b = b + 1 |
141 | | - |
142 | | - if not self.args.quiet: |
143 | | - print('rgb', pixel, r, g, b) |
| 72 | + |
| 73 | + print(command, pixel, r, g, b) |
144 | 74 |
|
145 | 75 | except ValueError: |
146 | | - print("Malformed command: " + str(msg.payload)) |
| 76 | + print('Malformed command: ' + str(msg.payload)) |
147 | 77 | return |
148 | 78 |
|
149 | 79 | if pixel is None: |
150 | 80 | for x in range(blinkt.NUM_PIXELS): |
151 | 81 | blinkt.set_pixel(x, r, g, b) |
152 | | - self.update_time[x] = time.time() |
153 | 82 | else: |
154 | 83 | blinkt.set_pixel(pixel, r, g, b) |
155 | | - self.update_time[pixel] = time.time() |
156 | 84 |
|
157 | 85 | blinkt.show() |
| 86 | + return |
158 | 87 |
|
159 | | - def on_connect(self, client, userdata, flags, rc): |
160 | | - if not self.args.quiet: |
161 | | - print("Connected to {s}:{p} listening for topics {t} with result code {r}.\nSee {c} --help for options.".format(s = self.args.host, p = self.args.port, t = ', '.join(self.args.topic), r = rc, c = parser.prog)) |
162 | | - |
163 | | - for topic in self.args.topic: |
164 | | - client.subscribe(topic) |
165 | | - |
166 | | - def on_message(self, client, userdata, msg): |
167 | 88 |
|
168 | | - data = msg.payload.split(',') |
169 | | - command = data.pop(0) |
| 89 | +blinkt.set_clear_on_exit() |
170 | 90 |
|
171 | | - if command == "clr" and len(data) == 0: |
172 | | - self.cmd_clear() |
173 | | - return |
| 91 | +client = mqtt.Client() |
| 92 | +client.on_connect = on_connect |
| 93 | +client.on_message = on_message |
174 | 94 |
|
175 | | - if command == "bri" and len(data) == 1: |
176 | | - self.cmd_brightness( data[0] ) |
177 | | - return |
| 95 | +if MQTT_USER is not None and MQTT_PASS is not None: |
| 96 | + print('Using username: {un} and password: {pw}'.format(un=MQTT_USER, pw='*' * len(MQTT_PASS))) |
| 97 | + client.username_pw_set(username=MQTT_USER, password=MQTT_PASS) |
178 | 98 |
|
179 | | - if command == "rgb" and len(data) == 4: |
180 | | - self.cmd_rgb( data[0], data[1:] ) |
181 | | - return |
182 | | - |
183 | | - def blank_timed_out_pixels( self ): |
184 | | - now = time.time() |
185 | | - to_upt_pairs = zip(self.args.timeout,self.update_time) |
186 | | - for pixel, (to, uptime) in enumerate(to_upt_pairs): |
187 | | - if to is not None and uptime is not None and uptime < now - to: |
188 | | - blinkt.set_pixel( pixel, 0, 0, 0 ) |
189 | | - self.update_time[pixel] = None |
190 | | - blinkt.show() |
| 99 | +client.connect(MQTT_SERVER, MQTT_PORT, 60) |
191 | 100 |
|
192 | | - def loop( self, timeout = 1.0, max_packets = 1 ): |
193 | | - self.blank_timed_out_pixels() |
194 | | - return super( PixelClient, self ).loop( timeout, max_packets ) |
195 | | - |
196 | | - |
197 | | -def sigterm( signum, frame ): |
198 | | - client._thread_terminate = True |
199 | | - |
200 | | -if args.daemon: |
201 | | - # Monkey-patch daemon so's the daemon starts reasonably quickly. FDs don't |
202 | | - # strictly speaking need closing anyway because we haven't opened any (yet). |
203 | | - daemon.daemon.get_maximum_file_descriptors = lambda: 32 |
204 | | - args.quiet = True |
205 | | - pidlf = lockfile.pidlockfile.PIDLockFile( args.daemon ) |
206 | | - with daemon.DaemonContext( |
207 | | - pidfile = pidlf, |
208 | | - signal_map = {signal.SIGTERM: sigterm} ): |
209 | | - client = PixelClient( args ) |
210 | | - client.loop_forever() |
211 | | -else: |
212 | | - client = PixelClient( args ) |
213 | | - client.loop_forever() |
| 101 | +client.loop_forever() |
0 commit comments