├── 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 |
45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 |

Audio Sync

57 |
58 | Hass 59 | Led 60 | Both 61 | 62 |
63 |

Home Assistant

""" 64 | if error == "entity": 65 | html += """
Invalid Entity!
""" 66 | 67 | html += """ 68 |
69 | 70 | 71 |
72 |

STOP

73 |
74 | 75 |
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 | --------------------------------------------------------------------------------