├── public
└── css
│ └── style.css
├── README.md
├── color_server.py
└── color_script.py
/public/css/style.css:
--------------------------------------------------------------------------------
1 | .controller {
2 | text-align: center;
3 | margin-top: 10%;
4 | padding-top: 2%;
5 | padding-bottom: 2%;
6 | margin-bottom: 10%;
7 | background-color: #FDF3E7;
8 | color: #3B3738;
9 | }
10 |
11 | .outside {
12 | background-color: #C63D0F;
13 | }
14 |
15 | #all, #wipe, #theater, #rainbow, #audioSync, #hassEntity, #turnOff {
16 | background: #3B3738;
17 | color: #FDF3E7;
18 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # audio-led-sync
2 | Sync LED lights and home assistant lights to audio
3 |
4 | It is a work in progress.
5 |
6 | Made for raspberry pi. ws2812 led or home assistant instance on another server.
7 | Options -c, -a, w, t, r, e, x for ws2812 leds
8 | Options -e, -x for home assistant
9 | -e to check home assistant for an entity and display a color on the leds
10 |
11 | -x hass,led, or both to sync audio via usb mic connected to rpi and display colors based on pitch/volume.
12 |
13 | NOTE: sudo is required for ws2821 leds depending on the pin used. If not using leds or you are using
14 | a supported pin that does not require root access, then no sudo commands are required for these scripts.
15 |
16 | There is a cherrypy server script that is included.
17 | Spin up the server ```sudo python3 color_server.py```
18 | Open a web browser to your ip address and port 8080.
19 |
20 | cli options:
21 | ```
22 | usage: color_script.py [-h] [-c] [-a] [-w] [-t] [-r] [-x {hass,led,both}]
23 | [-e ENTITY] [-s]
24 |
25 | optional arguments:
26 | -h, --help show this help message and exit
27 | -c, --clear clear the display on exit
28 | -a, --all All Examples
29 | -w, --wipe Color Wipe
30 | -t, --theater Theater Chase
31 | -r, --rainbow Rainbow Cycle
32 | -x {hass,led,both}, --audio {hass,led,both}
33 | Audio Sync LED or HASS
34 | -e ENTITY, --entity ENTITY
35 | Entity
36 | -s, --stop Stop
37 | ```
--------------------------------------------------------------------------------
/color_server.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | import cherrypy
5 | import subprocess
6 | import signal
7 | import re
8 | import logging
9 |
10 | logging.basicConfig(level=logging.DEBUG)
11 | _LOG = logging.getLogger(__name__)
12 |
13 |
14 | class ColorServer():
15 | def __init__(self):
16 | self._theprocess = None
17 | self._processArgs = None
18 |
19 | def _popen(self):
20 | if self._theprocess:
21 | _LOG.debug("PID: %s" % self._theprocess.pid)
22 | self._theprocess.send_signal(signal.SIGINT)
23 | self._theprocess.wait()
24 | self._theprocess = None
25 | _LOG.debug("Killed the Process.")
26 |
27 | if self._processArgs:
28 | _LOG.debug("Starting a new Process.")
29 | self._theprocess = subprocess.Popen(self._processArgs)
30 | _LOG.debug("PID: %s" % self._theprocess.pid)
31 |
32 | @cherrypy.expose
33 | def index(self, error=None):
34 | html = """
35 |
36 | Color Controller
37 |
38 |
39 |
40 |
41 |
42 |
43 |
LED Demos
44 |
47 |
50 |
53 |
56 |
Audio Sync
57 |
63 |
Home Assistant
"""
64 | if error == "entity":
65 | html += """
Invalid Entity!
"""
66 |
67 | html += """
68 |
72 |
STOP
73 |
76 |
77 |
78 | """
79 |
80 | return html
81 |
82 | @cherrypy.expose
83 | def all(self):
84 | _LOG.info("All Demos")
85 | self._processArgs = ["python3", "color_script.py", "-a"]
86 | self._popen()
87 | raise cherrypy.HTTPRedirect("/index")
88 |
89 | @cherrypy.expose
90 | def wipe(self):
91 | _LOG.info("Wipe Demo")
92 | self._processArgs = ["python3", "color_script.py", "-w"]
93 | self._popen()
94 | raise cherrypy.HTTPRedirect("/index")
95 |
96 | @cherrypy.expose
97 | def theater(self):
98 | _LOG.info("Theater Chase Demo")
99 | self._processArgs = ["python3", "color_script.py", "-t"]
100 | self._popen()
101 | raise cherrypy.HTTPRedirect("/index")
102 |
103 | @cherrypy.expose
104 | def rainbow(self):
105 | _LOG.info("Rainbow Demo")
106 | self._processArgs = ["python3", "color_script.py", "-r"]
107 | self._popen()
108 | raise cherrypy.HTTPRedirect("/index")
109 |
110 | @cherrypy.expose
111 | def audioSync(self, toSync):
112 | _LOG.info("Audio Sync")
113 | self._processArgs = ["python3", "color_script.py", "-x=" + toSync]
114 | self._popen()
115 | raise cherrypy.HTTPRedirect("/index")
116 |
117 | @cherrypy.expose
118 | def hassEntity(self, entity):
119 | _LOG.info("Hass Entity")
120 | rex = re.compile("^[a-z_]+.[a-z_0-9]+$")
121 | if rex.match(entity):
122 | _LOG.debug("True")
123 | self._processArgs = ["python3", "color_script.py", "-e " + entity]
124 | self._popen()
125 | raise cherrypy.HTTPRedirect("/index")
126 | else:
127 | raise cherrypy.HTTPRedirect("/index?error=entity")
128 |
129 | @cherrypy.expose
130 | def turnOff(self):
131 | _LOG.info("Turn Off")
132 | self._processArgs = ["python3", "color_script.py", "-s"]
133 | self._popen()
134 | raise cherrypy.HTTPRedirect("/index")
135 |
136 |
137 | if __name__ == "__main__":
138 | conf = {
139 | "/": {"tools.sessions.on": True, "tools.staticdir.root": os.path.abspath(os.getcwd())},
140 | "/static": {"tools.staticdir.on": True, "tools.staticdir.dir": "./public"},
141 | }
142 | if os.geteuid() == 0:
143 | cherrypy.process.plugins.DropPrivileges(cherrypy.engine, uid=0, gid=0).subscribe()
144 | cherrypy.config.update({"server.socket_host": "0.0.0.0"})
145 | cherrypy.quickstart(ColorServer(), "/", conf)
146 |
--------------------------------------------------------------------------------
/color_script.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """ Audio pitch/volume to light color/brightness.
3 |
4 | Author: Rob Landry (rob.a.landry@gmail.com)"""
5 | import os
6 | import sys
7 | import contextlib
8 | import time
9 | import requests
10 | import json
11 | import argparse
12 |
13 | # For Music
14 | # import alsaaudio as aa
15 | import pyaudio
16 | import aubio
17 | import numpy as np
18 | import colorsys
19 | import webcolors
20 |
21 | # For NeoPixel
22 | from neopixel import *
23 |
24 | SLEEP = 0.5
25 |
26 | # AUDIO CONFIGURATION
27 | #
28 | # FORMAT = pyaudio.paInt16
29 | # CHANNELS =
30 | # RATE =
31 | # CHUNK =
32 | # DEVICE_INDEX =
33 | FORMAT = pyaudio.paFloat32
34 | CHANNELS = 1
35 | RATE = 16000
36 | CHUNK = 1024
37 | DEVICE_INDEX = 0
38 |
39 | # LED STRIP CONFIGURATION
40 | # LED_COUNT = 10 # Number of LED pixels.
41 | # LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!).
42 | # LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
43 | # LED_DMA = 10 # DMA channel to use for generating signal (try 10)
44 | # LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest
45 | # LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
46 | # LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53
47 | LED_COUNT = 10
48 | LED_PIN = 18
49 | LED_FREQ_HZ = 800000
50 | LED_DMA = 10
51 | LED_BRIGHTNESS = 255
52 | LED_INVERT = False
53 | LED_CHANNEL = 0
54 |
55 | # HASS CONFIGURATION
56 | # HASS_URL = "http://192.168.1.X:8123"
57 | # HASS_PASS = "API TOKEN"
58 | HASS_URL = "http://IP:8123"
59 | HASS_PASS = (
60 | "API"
61 | "KEY"
62 | "HERE"
63 | )
64 | COLOR_LIGHTS = "light.living_room, light.garden_lights"
65 | WHITE_LIGHTS = ""
66 |
67 | # prevents same colors repeating by changing hs +/- 30
68 | PREVENT_STATIC = False
69 |
70 |
71 | @contextlib.contextmanager
72 | def silence():
73 | devnull = os.open(os.devnull, os.O_WRONLY)
74 | old_stderr = os.dup(2)
75 | sys.stderr.flush()
76 | os.dup2(devnull, 2)
77 | os.close(devnull)
78 | try:
79 | yield
80 | finally:
81 | os.dup2(old_stderr, 2)
82 | os.close(old_stderr)
83 |
84 |
85 | def get_colour_name(rgb_triplet):
86 | min_colours = {}
87 | for key, name in webcolors.css21_hex_to_names.items():
88 | r_c, g_c, b_c = webcolors.hex_to_rgb(key)
89 | rd = (r_c - rgb_triplet[0]) ** 2
90 | gd = (g_c - rgb_triplet[1]) ** 2
91 | bd = (b_c - rgb_triplet[2]) ** 2
92 | min_colours[(rd + gd + bd)] = name
93 | return min_colours[min(min_colours.keys())]
94 |
95 |
96 | # pylint: disable=undefined-variable
97 | class ProcessColor:
98 | """ Docstring. """
99 |
100 | def __init__(self, **kwargs):
101 | """ Docstring. """
102 | self.color = 0
103 | self.kwargs = kwargs
104 | with silence():
105 | self.audioSync()
106 |
107 | def audioSync(self): # pylint: disable=too-many-locals
108 | """ Docstring. """
109 |
110 | hassSync = self.kwargs.get("hass")
111 | ledSync = self.kwargs.get("led")
112 |
113 | p = pyaudio.PyAudio()
114 |
115 | stream = p.open(
116 | format=FORMAT,
117 | channels=CHANNELS,
118 | rate=RATE,
119 | input=True,
120 | frames_per_buffer=CHUNK,
121 | input_device_index=DEVICE_INDEX,
122 | )
123 |
124 | # aubio pitch detection
125 | pDetection = aubio.pitch("default", 2048, 2048 // 2, 16000)
126 | pDetection.set_unit("Hz")
127 | pDetection.set_silence(-40)
128 |
129 | print("Audio Controlled LEDs.")
130 |
131 | while True:
132 | # Read data from device
133 | if stream.is_stopped():
134 | stream.start_stream()
135 | data = stream.read(CHUNK)
136 | stream.stop_stream()
137 |
138 | # determine pitch
139 | samples = np.fromstring(data, dtype=aubio.float_type)
140 | pitch = pDetection(samples)[0]
141 | # print(pitch)
142 |
143 | # determine volume
144 | volume = np.sum(samples ** 2) / len(samples)
145 | volume = "{:.6f}".format(volume)
146 | # print(volume)
147 |
148 | # calculate a brightness based on volume level
149 | brightness = self.calc_bright(volume)
150 |
151 | # get color based on pitch
152 | hs_color = self.calc_hs(pitch)
153 | if PREVENT_STATIC:
154 | if (self.color <= (hs_color + 5) and self.color >= (hs_color - 5)):
155 | if int(hs_color) <= 30:
156 | hs_color = hs_color + 30
157 | else:
158 | hs_color = hs_color - 30
159 | self.color = hs_color
160 |
161 | # print(self.color)
162 | rgb_color = self.hs_to_rbg(hs_color)
163 | r, g, b = rgb_color
164 |
165 | # output something to console
166 | print(get_colour_name(rgb_color))
167 | print("HS Color: %s" % hs_color)
168 | print("RGB Color: (%s, %s, %s)" % rgb_color)
169 | print("Brightness: %s\n" % brightness)
170 |
171 | # For NeoPixels
172 | if ledSync:
173 | neoPixelStrip(rgb_color=(r, g, b), brightness=brightness)
174 |
175 | # For HASS Lights
176 | if hassSync:
177 | self.exec_hass(hs_color, brightness)
178 |
179 | time.sleep(SLEEP)
180 |
181 | stream.stop_stream()
182 | stream.close()
183 |
184 | def calc_hs(self, pitch):
185 | """ calculate the hs color based off max of 500Hz? thats about the highest ive seen. """
186 | hs_color = pitch / 500
187 | hs_color = hs_color * 360
188 | if hs_color > 360:
189 | hs_color = 360
190 | return hs_color
191 |
192 | def hs_to_rbg(self, hs_color):
193 | """ Get RGB color from HS. """
194 | r, g, b = colorsys.hsv_to_rgb(hs_color / 360.0, 1, 1)
195 | r = int(r * 255)
196 | g = int(g * 255)
197 | b = int(b * 255)
198 | rgb_color = (r, g, b)
199 | return rgb_color
200 |
201 | def calc_bright(self, brightness):
202 | """ calculate a brightness based on volume level. """
203 | brightness = int(float(brightness) * 100)
204 | if brightness < 10:
205 | brightness = 10
206 | return brightness
207 |
208 | def exec_hass(self, hs_color=0, brightness=100):
209 | saturation = 100
210 | if hs_color is 0:
211 | saturation = 0
212 |
213 | url = "/api/services/light/turn_on"
214 |
215 | # color lights
216 | payload = {
217 | "entity_id": COLOR_LIGHTS,
218 | "hs_color": [int(hs_color), saturation],
219 | "brightness_pct": brightness,
220 | "transition": 0.5,
221 | }
222 |
223 | hassConn(url=url, payload=payload)
224 |
225 |
226 | class hassConn:
227 | """ Format request to HASS. """
228 |
229 | def __init__(self, **kwargs):
230 | """ Initialize the Class. """
231 | self._url = None
232 | self._headers = None
233 | self._payload = None
234 |
235 | if "url" in kwargs:
236 | self._url = kwargs.get("url")
237 | if "headers" in kwargs:
238 | self._headers = kwargs.get("headers")
239 | if "payload" in kwargs:
240 | self._payload = kwargs.get("payload")
241 |
242 | self.setUrl(self._url)
243 | self.setHeaders(self._headers)
244 | self.setPayload(self._payload)
245 |
246 | if kwargs.get("theType") == "GET":
247 | self.get()
248 | else:
249 | self.post()
250 |
251 | def setUrl(self, url):
252 | """ Assign URL to var.
253 |
254 | Format: '/api/services/light/turn_on' """
255 | self._url = HASS_URL + url
256 |
257 | def setHeaders(self, headers):
258 | """ Assign header var. """
259 | if not headers:
260 | headers = {
261 | "Authorization": "Bearer " + HASS_PASS,
262 | "content-type": "application/json"
263 | }
264 | self._headers = headers
265 |
266 | def setPayload(self, payload):
267 | """ Verify payload is valid JSON and assign to var. """
268 | try:
269 | json.loads(json.dumps(payload))
270 | except ValueError:
271 | print("Invalid JSON!")
272 | self._payload = payload
273 |
274 | def post(self):
275 | """ POST the request. """
276 | response = requests.post(self._url, json=self._payload, headers=self._headers)
277 | if response.status_code != 200:
278 | print(response.text)
279 |
280 | def get(self):
281 | """ GET the request. """
282 | try:
283 | response = requests.get(self._url, headers=self._headers)
284 | response.raise_for_status()
285 | # print(response.text)
286 | except requests.exceptions.HTTPError as err:
287 | print("HTTP Error")
288 | print(err)
289 | exit()
290 | return "exception"
291 |
292 | except requests.exceptions.Timeout:
293 | # Maybe set up for a retry, or continue in a retry loop
294 | print("Connection Timeout!")
295 | exit()
296 | return "exception"
297 |
298 | except requests.exceptions.TooManyRedirects:
299 | # Tell the user their URL was bad and try a different one
300 | print("Too Many Redirects!")
301 | exit()
302 | return "exception"
303 |
304 | except requests.exceptions.RequestException as e:
305 | # catastrophic error. bail.
306 | print("Request Exception!")
307 | print(e)
308 | return "exception"
309 | # exit()
310 |
311 | return response
312 |
313 |
314 | # pylint: disable=undefined-variable
315 | class neoPixelStrip:
316 | """ NeoPixel library strandtest example.
317 |
318 | Author: Tony DiCola (tony@tonydicola.com)
319 | Direct port of the Arduino NeoPixel library strandtest example. Showcases
320 | various animations on a strip of NeoPixels.
321 | """
322 |
323 | def __init__(self, **kwargs):
324 | """ Create NeoPixel object with appropriate configuration. """
325 | self.strip = Adafruit_NeoPixel(
326 | LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL
327 | )
328 |
329 | self.function = kwargs.get("function")
330 | self.color = kwargs.get("color")
331 | self.wait_ms = kwargs.get("wait_ms")
332 | self.iterations = kwargs.get("iterations")
333 | self.entity = kwargs.get("entity")
334 | self.rgb_color = kwargs.get("rgb_color")
335 | self.brightness = kwargs.get("brightness")
336 |
337 | if self.rgb_color or self.brightness:
338 | self.audioColor()
339 | else:
340 | self.execDemos()
341 |
342 | def audioColor(self):
343 | r, g, b = self.rgb_color
344 |
345 | self.strip.begin()
346 | self.clearPixels()
347 | for i in range(self.strip.numPixels()):
348 | self.strip.setBrightness(self.brightness)
349 | self.strip.setPixelColor(i, Color(g, r, b))
350 | self.strip.show()
351 |
352 | def execDemos(self):
353 | self.strip.begin()
354 | self.clearPixels()
355 | if self.function in ("colorWipe", "allDemo"):
356 | self.doColor()
357 | elif self.function in ("theaterChase", "allDemo"):
358 | self.doTheater()
359 | elif self.function in ("rainbowCycle", "allDemo"):
360 | self.doRainbow()
361 | elif self.function is "hassEntity":
362 | self.doHass()
363 | elif self.function is "clear":
364 | self.clearPixels()
365 |
366 | def doColor(self):
367 | print("Color wipe animations.")
368 | if self.color or self.wait_ms:
369 | self.colorWipe(self.color, self.wait_ms)
370 | else:
371 | self.colorWipe(Color(255, 0, 0)) # Red wipe
372 | self.colorWipe(Color(0, 255, 0)) # Blue wipe
373 | self.colorWipe(Color(0, 0, 255)) # Green wipe
374 |
375 | def doTheater(self):
376 | print("Theater chase animations.")
377 | if self.color or self.wait_ms or self.iterations:
378 | self.theaterChase(self.color, self.wait_ms, self.iterations)
379 | else:
380 | self.theaterChase(Color(127, 127, 127)) # White theater chase
381 | self.theaterChase(Color(127, 0, 0)) # Red theater chase
382 | self.theaterChase(Color(0, 0, 127)) # Blue theater chase
383 |
384 | def doRainbow(self):
385 | print("Rainbow animations.")
386 | if self.color or self.wait_ms or self.iterations:
387 | self.rainbow(wait_ms, iterations)
388 | self.rainbowCycle(wait_ms, iterations)
389 | self.theaterChaseRainbow(wait_ms)
390 | else:
391 | self.rainbow()
392 | self.rainbowCycle()
393 | self.theaterChaseRainbow()
394 |
395 | def doHass(self):
396 | print("HASS Entity State.")
397 | theState = self.checkState()
398 | # print('returned state ' + theState)
399 |
400 | if theState is False:
401 | self.colorWipe(Color(0, 0, 0), 10)
402 | exit()
403 | elif theState == "on":
404 | self.colorWipe(Color(0, 255, 0)) # Green wipe
405 | elif theState == "off":
406 | self.colorWipe(Color(255, 0, 0)) # Red wipe
407 | elif theState == "armed_home":
408 | self.colorWipe(Color(0, 0, 255)) # Red wipe
409 | elif theState == "pending":
410 | self.theaterChase(Color(127, 0, 0)) # Red theater chase
411 | elif theState == "exception":
412 | self.blink(Color(0, 127, 0)) # White theater chase
413 |
414 | # Define functions which animate LEDs in various ways.
415 | def colorWipe(self, color, wait_ms=50):
416 | """Wipe color across display a pixel at a time."""
417 | for i in range(self.strip.numPixels()):
418 | self.strip.setPixelColor(i, color)
419 | self.strip.show()
420 | time.sleep(wait_ms / 1000.0)
421 |
422 | def theaterChase(self, color, wait_ms=50, iterations=10):
423 | """Movie theater light style chaser animation."""
424 | for j in range(iterations):
425 | for q in range(3):
426 | for i in range(0, self.strip.numPixels(), 3):
427 | self.strip.setPixelColor(i + q, color)
428 | self.strip.show()
429 | time.sleep(wait_ms / 1000.0)
430 | for i in range(0, self.strip.numPixels(), 3):
431 | self.strip.setPixelColor(i + q, 0)
432 |
433 | def wheel(self, pos):
434 | """Generate rainbow colors across 0-255 positions."""
435 | if pos < 85:
436 | theColor = Color(pos * 3, 255 - pos * 3, 0)
437 | elif pos < 170:
438 | pos -= 85
439 | theColor = Color(255 - pos * 3, 0, pos * 3)
440 | else:
441 | pos -= 170
442 | theColor = Color(0, pos * 3, 255 - pos * 3)
443 | return theColor
444 |
445 | def rainbow(self, wait_ms=20, iterations=1):
446 | """Draw rainbow that fades across all pixels at once."""
447 | for j in range(256 * iterations):
448 | for i in range(self.strip.numPixels()):
449 | self.strip.setPixelColor(i, self.wheel((i + j) & 255))
450 | self.strip.show()
451 | time.sleep(wait_ms / 1000.0)
452 |
453 | def rainbowCycle(self, wait_ms=20, iterations=5):
454 | """Draw rainbow that uniformly distributes itself across all pixels."""
455 | for j in range(256 * iterations):
456 | for i in range(self.strip.numPixels()):
457 | self.strip.setPixelColor(
458 | i, self.wheel((int(i * 256 / self.strip.numPixels()) + j) & 255)
459 | )
460 | self.strip.show()
461 | time.sleep(wait_ms / 1000.0)
462 |
463 | def theaterChaseRainbow(self, wait_ms=50):
464 | """Rainbow movie theater light style chaser animation."""
465 | for j in range(256):
466 | for q in range(3):
467 | for i in range(0, self.strip.numPixels(), 3):
468 | self.strip.setPixelColor(i + q, self.wheel((i + j) % 255))
469 | self.strip.show()
470 | time.sleep(wait_ms / 1000.0)
471 | for i in range(0, self.strip.numPixels(), 3):
472 | self.strip.setPixelColor(i + q, 0)
473 |
474 | def clearPixels(self):
475 | """ Clear the LEDs. """
476 | for i in range(self.strip.numPixels()):
477 | self.strip.setPixelColor(i, Color(0, 0, 0))
478 | self.strip.show()
479 |
480 | def blink(self, color, wait_ms=150, n=3):
481 | """Blink Color."""
482 | for x in range(n):
483 | for i in range(self.strip.numPixels()):
484 | self.strip.setPixelColor(i, color)
485 | self.strip.show()
486 | time.sleep(wait_ms / 1000.0)
487 | self.clearPixels()
488 | time.sleep(wait_ms / 1000.0)
489 |
490 | def checkState(self):
491 | """ Connect to HASS and display state via LED. """
492 | url = "/api/states/" + self.entity
493 | response = hassConn(url=url, theType="GET")
494 |
495 | while True:
496 | try:
497 | theState = response.json()
498 | theState = theState["state"]
499 | print(entity + " is " + theState)
500 | # print(theState)
501 | # return theState
502 | if theState == "locked":
503 | theState = "on"
504 | elif theState == "unlocked":
505 | theState = "off"
506 | elif theState == "open":
507 | theState = "on"
508 | elif theState == "closed":
509 | theState = "off"
510 | elif theState == "armed":
511 | theState = "on"
512 | elif theState == "armed_away":
513 | theState = "on"
514 | elif theState == "disarmed":
515 | theState = "off"
516 | elif theState == "home":
517 | theState = "off"
518 | elif theState == "not_home":
519 | theState = "on"
520 | break
521 |
522 | except KeyError:
523 | theText = json.loads(response.text)
524 | # print(theText)
525 |
526 | if theText["message"] == "Entity not found.":
527 | print(entity + " " + theText["message"])
528 | # return False
529 | theState = False
530 |
531 | return theState
532 |
533 |
534 | # Main program logic follows:
535 | if __name__ == "__main__":
536 | # Process arguments
537 | parser = argparse.ArgumentParser(add_help=True)
538 | parser.add_argument(
539 | "-c", "--clear", action="store_true", help="clear the display on exit", default=True
540 | )
541 | parser.add_argument("-a", "--all", action="store_true", help="All Examples")
542 | parser.add_argument("-w", "--wipe", action="store_true", help="Color Wipe")
543 | parser.add_argument("-t", "--theater", action="store_true", help="Theater Chase")
544 | parser.add_argument("-r", "--rainbow", action="store_true", help="Rainbow Cycle")
545 | parser.add_argument("-x", "--audio", choices=("hass","led", "both"), help="Audio Sync LED or HASS")
546 | parser.add_argument("-e", "--entity", action="store", help="Entity")
547 | parser.add_argument("-s", "--stop", action="store_true", help="Stop")
548 | args = parser.parse_args()
549 |
550 | try:
551 | print("----------------------------------------------")
552 | print("----------- Starting Color Server ------------")
553 | print("----------------------------------------------")
554 | while True:
555 |
556 | if args.wipe or args.all:
557 | neoPixelStrip(function="colorWipe")
558 |
559 | if args.theater or args.all:
560 | neoPixelStrip(function="theaterChase")
561 |
562 | if args.rainbow or args.all:
563 | neoPixelStrip(function="rainbow")
564 |
565 | if args.entity:
566 | neoPixelStrip(function="hassEntity", entity=args.entity)
567 |
568 | if args.audio:
569 | hass = True
570 | led = True
571 | if args.audio == "hass":
572 | led = False
573 | elif args.audio == "led":
574 | hass = False
575 |
576 | ProcessColor(hass=hass, led=led)
577 |
578 | if args.stop:
579 | print("Stop function")
580 | neoPixelStrip(function="clear")
581 | ProcessColor.exec_hass(0)
582 | print("----------------------------------------------")
583 | print("--------------- Shutting Down! ---------------")
584 | print("----------------------------------------------")
585 | exit(0)
586 |
587 | time.sleep(SLEEP)
588 |
589 | except KeyboardInterrupt:
590 | if args.clear: # pylint: disable=too-many-function-args
591 | neoPixelStrip(function="clear")
592 | ProcessColor.exec_hass(0)
593 | print("----------------------------------------------")
594 | print("--------------- Shutting Down! ---------------")
595 | print("----------------------------------------------")
596 | exit(0)
597 |
598 |
--------------------------------------------------------------------------------