Passed
Push — all-web-control ( c78a90 )
by Matt
05:02 queued 01:27
created

PyDMXControl.web._routes.all_control()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 0
1
"""
2
 *  PyDMXControl: A Python 3 module to control DMX using uDMX.
3
 *                Featuring fixture profiles, built-in effects and a web control panel.
4
 *  <https://github.com/MattIPv4/PyDMXControl/>
5
 *  Copyright (C) 2019 Matt Cowley (MattIPv4) ([email protected])
6
"""
7
8
from re import compile as re_compile  # Regex
9
from typing import List, Union, Tuple, Dict, Callable  # Typing
10
11
from flask import Blueprint, render_template, current_app, redirect, url_for, jsonify  # Flask
12
13
from .. import Colors  # Colors
14
from ..profiles.defaults import Fixture, Vdim  # Fixtures
15
16
routes = Blueprint('', __name__, url_prefix='/')
17
18
19
def fixture_channels(this_fixture: Fixture) -> List[Tuple[str, int]]:
20
    chans = [(f['name'], fixture_channel_value(this_fixture, f['name'])) for f in this_fixture.channels.values()]
21
    if issubclass(type(this_fixture), Vdim):
22
        chans.append(("dimmer", fixture_channel_value(this_fixture, "dimmer")))
23
    return chans
24
25
26
def fixture_channel_value(this_fixture: Fixture, this_channel: Union[str, int]) -> int:
27
    if issubclass(type(this_fixture), Vdim):
28
        return this_fixture.get_channel_value(this_channel, False)[0]
29
    return this_fixture.get_channel_value(this_channel)[0]
30
31
32
helpers = ["on", "off", "locate"]
33
34
35
def fixture_helpers(this_fixture: Fixture) -> Dict[str, Callable]:
36
    return {f: this_fixture.__getattribute__(f) for f in helpers if hasattr(this_fixture, f)}
37
38
39
# Home
40
@routes.route('', methods=['GET'])
41
def home():
42
    return render_template("index.jinja2", helpers=helpers)
43
44
45
# All Control
46
@routes.route('all', methods=['GET'])
47
def all_control():
48
    return render_template("all.jinja2", colors=Colors, fixture_channels=fixture_channels)
49
50
51
# Global Intensity
52
@routes.route('intensity/<int:val>', methods=['GET'])
53
def global_intensity(val: int):
54
    if val < 0 or val > 255:
55
        return jsonify({"error": "Value {} is invalid".format(val)}), 400
56
    current_app.parent.controller.all_dim(val)
57
    return jsonify({"message": "All dimmers updated to {}".format(val)}), 200
58
59
60
# Fixture Home
61
@routes.route('fixture/<int:fid>', methods=['GET'])
62
def fixture(fid: int):
63
    this_fixture = current_app.parent.controller.get_fixture(fid)
64
    if not this_fixture:
65
        return redirect(url_for('.home'))
66
    return render_template("fixture.jinja2", fixture=this_fixture, fixture_channels=fixture_channels,
67
                           colors=Colors, helpers=helpers)
68
69
70
# Fixture Channel
71
@routes.route('fixture/<int:fid>/channel/<int:cid>', methods=['GET'])
72
def channel(fid: int, cid: int):
73
    this_fixture = current_app.parent.controller.get_fixture(fid)
74
    if not this_fixture:
75
        return redirect(url_for('.home'))
76
    chan = this_fixture.get_channel_id(cid)
77
    if chan == -1:
78
        return redirect(url_for('.fixture', fid=this_fixture.id))
79
    this_channel = fixture_channels(this_fixture)[chan]
80
    return render_template("channel.jinja2", fixture=this_fixture, channel=this_channel, cid=chan)
81
82
83
# Fixture Channel Set
84
@routes.route('fixture/<int:fid>/channel/<int:cid>/<int:val>', methods=['GET'])
85
def channel_val(fid: int, cid: int, val: int):
86
    this_fixture = current_app.parent.controller.get_fixture(fid)
87
    if not this_fixture:
88
        return jsonify({"error": "Fixture {} not found".format(fid)}), 404
89
    chan = this_fixture.get_channel_id(cid)
90
    if chan == -1:
91
        return jsonify({"error": "Channel {} not found".format(cid)}), 404
92
93
    if val < 0 or val > 255:
94
        return jsonify({"error": "Value {} is invalid".format(val)}), 400
95
96
    this_fixture.set_channel(chan, val)
97
    val = fixture_channel_value(this_fixture, chan)
98
    print_chan = (chan + this_fixture.start_channel if chan + 1 < this_fixture.next_channel else "-")
99
100
    data = {
101
        "message": "Channel {} {} updated to {}".format(
102
            print_chan,
103
            this_fixture.channels[print_chan]["name"] if print_chan != "-" else "",
104
            val),
105
        "elements": {
106
            "channel-{}-value".format(chan): val,
107
            "value": val,
108
            "slider_value": val
109
        }
110
    }
111
    if chan == this_fixture.get_channel_id("dimmer"):
112
        data["elements"]["intensity_value"] = val
113
    return jsonify(data), 200
114
115
116
# Fixture Color
117
@routes.route('fixture/<int:fid>/color/<string:val>', methods=['GET'])
118
def color(fid: int, val: str):
119
    this_fixture = current_app.parent.controller.get_fixture(fid)
120
    if not this_fixture:
121
        return jsonify({"error": "Fixture {} not found".format(fid)}), 404
122
    pattern = re_compile(r"^\s*(\d{1,3})\s*[, ]\s*(\d{1,3})\s*[, ]\s*(\d{1,3})\s*(?:[, ]\s*(\d{1,3})\s*)*$")
123
    match = pattern.match(val)
124
    if not match:
125
        return jsonify({"error": "Invalid color {} supplied".format(val)}), 400
126
    this_color = [int(f) for f in match.groups() if f]
127
    this_fixture.color(this_color)
128
    return jsonify({"message": "Color updated to {}".format(this_color),
129
                    "elements": dict({"value": Colors.to_hex(this_fixture.get_color())},
130
                                     **{"channel-{}-value".format(i): f[1] for i, f in
131
                                        enumerate(fixture_channels(this_fixture))})}), 200
132
133
134
# Fixture Intensity
135
@routes.route('fixture/<int:fid>/intensity/<int:val>', methods=['GET'])
136
def intensity(fid: int, val: int):
137
    this_fixture = current_app.parent.controller.get_fixture(fid)
138
    if not this_fixture:
139
        return jsonify({"error": "Fixture {} not found".format(fid)}), 404
140
    chan = this_fixture.get_channel_id("dimmer")
141
    if chan == -1:
142
        return jsonify({"error": "Dimmer channel not found"}), 404
143
144
    if val < 0 or val > 255:
145
        return jsonify({"error": "Value {} is invalid".format(val)}), 400
146
147
    this_fixture.set_channel(chan, val)
148
    val = fixture_channel_value(this_fixture, chan)
149
    return jsonify({"message": "Dimmer updated to {}".format(val), "elements": {
150
        "channel-{}-value".format(chan): val,
151
        "intensity_value": val
152
    }}), 200
153
154
155
# Fixture Helpers
156
@routes.route('fixture/<int:fid>/helper/<string:val>', methods=['GET'])
157
def helper(fid: int, val: str):
158
    this_fixture = current_app.parent.controller.get_fixture(fid)
159
    if not this_fixture:
160
        return jsonify({"error": "Fixture {} not found".format(fid)}), 404
161
162
    val = val.lower()
163
    this_helpers = fixture_helpers(this_fixture)
164
    if val not in this_helpers.keys():
165
        return jsonify({"error": "Helper {} not found".format(val)}), 404
166
167
    try:
168
        this_helpers[val]()
169
    except Exception:
170
        return jsonify({"error": "Helper {} failed to execute".format(val)}), 500
171
    return jsonify({"message": "Helper {} executed".format(val), "elements": dict(
172
        {"value": Colors.to_hex(this_fixture.get_color()),
173
         "intensity_value": this_fixture.get_channel_value(this_fixture.get_channel_id("dimmer"))[0]},
174
        **{"channel-{}-value".format(i): f[1] for i, f in enumerate(fixture_channels(this_fixture))})}), 200
175
176
177
# Callbacks
178
@routes.route('callback/<string:cb>', methods=['GET'])
179
def callback(cb: str):
180
    if cb not in current_app.parent.callbacks.keys():
181
        return jsonify({"error": "Callback {} not found".format(cb)}), 404
182
    try:
183
        current_app.parent.callbacks[cb]()
184
    except Exception:
185
        return jsonify({"error": "Callback {} failed to execute".format(cb)}), 500
186
    return jsonify({"message": "Callback {} executed".format(cb)}), 200
187
188
189
# Timed Events
190
@routes.route('timed_event/<string:te>', methods=['GET'])
191
def timed_event(te: str):
192
    if te not in current_app.parent.timed_events.keys():
193
        return redirect(url_for('.home'))
194
    return render_template("timed_event.jinja2", te=te)
195
196
197
# Timed Events Data
198
@routes.route('timed_event/<string:te>/data', methods=['GET'])
199
def timed_event_data(te: str):
200
    if te not in current_app.parent.timed_events.keys():
201
        return jsonify({"error": "Timed Event {} not found".format(te)}), 404
202
    return jsonify({"data": current_app.parent.timed_events[te].data}), 200
203
204
205
# Timed Events Run
206
@routes.route('timed_event/<string:te>/run', methods=['GET'])
207
def run_timed_event(te: str):
208
    if te not in current_app.parent.timed_events.keys():
209
        return jsonify({"error": "Timed Event {} not found".format(te)}), 404
210
    try:
211
        current_app.parent.timed_events[te].run()
212
    except Exception:
213
        return jsonify({"error": "Timed Event {} failed to fire".format(te)}), 500
214
    return jsonify({"message": "Timed Event {} fired".format(te), "elements": {te + "-state": "Running"}}), 200
215
216
217
# Timed Events Stop
218
@routes.route('timed_event/<string:te>/stop', methods=['GET'])
219
def stop_timed_event(te: str):
220
    if te not in current_app.parent.timed_events.keys():
221
        return jsonify({"error": "Timed Event {} not found".format(te)}), 404
222
    try:
223
        current_app.parent.timed_events[te].stop()
224
    except Exception:
225
        return jsonify({"error": "Timed Event {} failed to stop".format(te)}), 500
226
    return jsonify({"message": "Timed Event {} stopped".format(te), "elements": {te + "-state": "Stopped"}}), 200
227