move to a static file model instead of a webserver
Haldean Brown
3 years ago
0 | 0 | import anims |
1 | 1 | import argparse |
2 | 2 | import img |
3 | import os | |
3 | 4 | import requests |
4 | 5 | import screen |
5 | 6 | |
15 | 16 | "--anim", default=None, help="set screen to an animation") |
16 | 17 | p.add_argument( |
17 | 18 | "--brightness", type=int, default=255, help="set screen brightness") |
19 | p.add_argument( | |
20 | "--redir", default=None, help="set screen to redirect to this screen") | |
21 | p.add_argument( | |
22 | "--webroot", required=True, help="set webroot of bitpic server on localhost") | |
23 | ||
18 | 24 | args = p.parse_args() |
19 | ||
20 | url = "http://{}/set/{}".format(screen.HOST, args.screen) | |
25 | fname = os.path.join(args.webroot, args.screen) | |
21 | 26 | |
22 | 27 | if args.image: |
23 | 28 | print("setting to image {}".format(args.image)) |
24 | 29 | s = img.image_to_screen(args.image, args.brightness) |
25 | requests.post(url, data={"data": s.encode()}) | |
30 | with open(fname, 'w') as f: | |
31 | f.write(s.encode()) | |
26 | 32 | elif args.solid: |
27 | 33 | print("setting solid color rgb({})".format(args.solid)) |
28 | 34 | c = [int(x) for x in args.solid.split(',')] |
29 | 35 | s = screen.Screen([[c for _ in range(8)] for _ in range(8)], args.brightness) |
30 | requests.post(url, data={"data": s.encode()}) | |
36 | with open(fname, 'w') as f: | |
37 | f.write(s.encode()) | |
31 | 38 | elif args.anim: |
32 | 39 | print("setting animation {}".format(args.anim)) |
33 | requests.post(url, data={"data": anims.encode(args.anim, args.brightness)}) | |
40 | a = anims.encode(args.anim, args.brightness) | |
41 | with open(fname, 'w') as f: | |
42 | f.write(a) | |
43 | elif args.redir: | |
44 | print("redirecting {} to {}".format(args.screen, args.redir)) | |
45 | with open(fname, 'w') as f: | |
46 | f.write("redir/{}".format(args.redir)) | |
34 | 47 | else: |
35 | print("setting brightness to {}".format(args.brightness)) | |
36 | requests.post("http://{}/set_brightness/{}".format(screen.HOST, args.screen), data={"brightness": args.brightness}) | |
48 | print("no operation given") |
13 | 13 | import numpy as np |
14 | 14 | import requests |
15 | 15 | import screen |
16 | from screen import Screen | |
16 | 17 | import time |
17 | ||
18 | try: | |
19 | with open("sid.txt") as sid_f: | |
20 | SID = sid_f.read().strip() | |
21 | except IOError: | |
22 | SID = screen.DEFAULT_SID | |
23 | URL = "http://{}/get/{}".format(screen.HOST, SID) | |
24 | 18 | |
25 | 19 | |
26 | 20 | class SerialError(Exception): |
38 | 32 | |
39 | 33 | class Bitpic: |
40 | 34 | def __init__(self): |
35 | self._sid = None | |
36 | print("starting bitpic with screen ID {}".format(self.sid)) | |
41 | 37 | self.current_screen = None |
42 | 38 | self.current_anim = None |
43 | 39 | self.last_anim_check = None |
121 | 117 | return animtype(anims.KCS) |
122 | 118 | return None |
123 | 119 | |
124 | def loop(self): | |
125 | now = datetime.now() | |
126 | time_check = (self.last_anim_check is None or | |
127 | now - self.last_anim_check > timedelta(seconds=5)) | |
128 | if time_check or self.current_anim is None: | |
120 | @property | |
121 | def sid(self): | |
122 | if self._sid is not None: | |
123 | return self._sid | |
124 | try: | |
125 | with open("sid.txt") as sid_f: | |
126 | self._sid = sid_f.read().strip() | |
127 | return self._sid | |
128 | except IOError: | |
129 | return screen.DEFAULT_SID | |
130 | ||
131 | def load(self, sid=None): | |
132 | if sid is None: | |
133 | sid = self.sid | |
134 | while True: | |
135 | url = "http://{}/{}".format(screen.HOST, sid) | |
129 | 136 | try: |
130 | res = requests.get(URL).text | |
131 | self.last_anim_check = now | |
132 | except requests.ConnectionError: | |
133 | print("failed to connect, sleeping 10s before retry") | |
137 | res = requests.get(url).text | |
138 | self.last_anim_check = datetime.now() | |
139 | except requests.ConnectionError as e: | |
140 | print("failed to fetch, sleeping 10s before retry: {}".format(e)) | |
134 | 141 | time.sleep(10) |
135 | return | |
142 | continue | |
136 | 143 | if res.startswith("anim/"): |
137 | 144 | self.current_anim = anims.get(self.current_anim, res) |
145 | return None | |
146 | elif res.startswith("redir/"): | |
147 | sid = res.split('/')[1] | |
148 | continue | |
138 | 149 | else: |
150 | try: | |
151 | return Screen.decode(res) | |
152 | except ValueError as e: | |
153 | print("failed to load screen: {}".format(e)) | |
154 | return None | |
155 | ||
156 | def loop(self): | |
157 | time_check = (self.last_anim_check is None or | |
158 | datetime.now() - self.last_anim_check > timedelta(seconds=5)) | |
159 | if time_check or self.current_anim is None: | |
160 | new_screen = self.load() | |
161 | if new_screen is not None: | |
139 | 162 | self.current_anim = None |
140 | new_screen = screen.Screen.decode(res) | |
141 | 163 | if new_screen != self.current_screen: |
142 | 164 | self.fade_between(self.current_screen, new_screen) |
143 | 165 | self.current_screen = new_screen |
150 | 172 | # if we get here, we either didn't check for new data and are in an |
151 | 173 | # animation, or we did check for new data and found out we're in |
152 | 174 | # an animation. |
153 | new_screen = self.current_anim.get_screen() | |
154 | self.write_screen(new_screen) | |
175 | anim_screen = self.current_anim.get_screen() | |
176 | self.write_screen(anim_screen) | |
155 | 177 | time.sleep(self.current_anim.desired_frame_time) |
156 | 178 | |
157 | 179 | def main(self): |
166 | 188 | self.led.push_to_driver() |
167 | 189 | |
168 | 190 | if __name__ == "__main__": |
169 | print("starting bitpic with screen ID {}".format(SID)) | |
170 | 191 | bp = Bitpic() |
171 | 192 | bp.main() |
0 | 0 | DEFAULT_SID = "__default" |
1 | HOST = "localhost:5000" | |
1 | HOST = "localhost:80" | |
2 | 2 | |
3 | 3 | class Screen: |
4 | 4 | DATA_VERSION = 0 |
34 | 34 | |
35 | 35 | @classmethod |
36 | 36 | def decode(cls, screenstr): |
37 | screenstr = screenstr.strip() | |
37 | 38 | if len(screenstr) != cls.DATA_LENGTH_BYTES: |
38 | raise ValueError("bad screenstr length") | |
39 | raise ValueError("bad screenstr length {}: {}".format(len(screenstr), screenstr)) | |
39 | 40 | data_bits = screenstr.split('/') |
40 | 41 | if len(data_bits) != 3: |
41 | 42 | raise ValueError("bad screenstr format: wrong number of /'s") |
0 | import flask | |
1 | import img | |
2 | import io | |
3 | import pprint | |
4 | import screen | |
5 | from screen import Screen | |
6 | ||
7 | app = flask.Flask(__name__) | |
8 | ||
9 | screens = { | |
10 | screen.DEFAULT_SID: Screen( | |
11 | [[(0, 0, 0) for _ in range(Screen.W)] for _ in range(Screen.H)], 255).encode() | |
12 | } | |
13 | ||
14 | def get_real_sid(sid): | |
15 | if sid in screens: | |
16 | return sid | |
17 | return screen.DEFAULT_SID | |
18 | ||
19 | @app.route("/get/<sid>") | |
20 | def get(sid): | |
21 | return screens[get_real_sid(sid)] | |
22 | ||
23 | @app.route("/view/<sid>") | |
24 | def view(sid): | |
25 | s = Screen.decode(screens[get_real_sid(sid)]) | |
26 | imgfile = io.BytesIO() | |
27 | image = img.screen_to_image(s, imgfile) | |
28 | imgfile.seek(0) | |
29 | return flask.send_file(imgfile, mimetype="image/png") | |
30 | ||
31 | @app.route("/set/<sid>", methods=["POST"]) | |
32 | def set(sid): | |
33 | encoded = flask.request.form["data"] | |
34 | print("received new image for {}: {}".format(sid, encoded)) | |
35 | screens[sid] = encoded | |
36 | return flask.url_for("get", sid=sid) | |
37 | ||
38 | @app.route("/set_brightness/<sid>", methods=["POST"]) | |
39 | def set_brightness(sid): | |
40 | if sid in screens: | |
41 | screens[sid].brightness = int(flask.request.form["brightness"]) | |
42 | return flask.url_for("get", sid=sid) |