diff --git a/.gitignore b/.gitignore index 36204ea..996aa71 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build dist controller/package-lock.json controller/package.json +controller.spec diff --git a/readme.md b/README.md similarity index 100% rename from readme.md rename to README.md diff --git a/controller/controller.py b/controller/controller.py index 3c5e031..2311ba0 100755 --- a/controller/controller.py +++ b/controller/controller.py @@ -90,53 +90,7 @@ def set_pixels(color): # @click.option("-v", help="Set HEX Color as base visualizer color: -v ffffff") def main(arg): if arg == (): - # proc = subprocess.Popen(["python", "-m", "streamlit", "run", "/var/lib/lc/gui.py", "--server.headless", "True", "--theme.base", "dark", "--theme.primaryColor", "#9f0000", "--theme.backgroundColor", "#181a1b", "--theme.secondaryBackgroundColor", "#1f2123", "--theme.textColor", "#c4c0b8"], stdout=subprocess.PIPE) - # for line in proc.stdout: - # if line == b' You can now view your Streamlit app in your browser.\n': - # break - - import streamlit.web.bootstrap as bootstrap - from streamlit import config - import gui as gui - - di = {'global_disableWatchdogWarning': None, 'global_showWarningOnDirectExecution': None, 'global_developmentMode': None, 'global_logLevel': None, 'global_unitTest': None, 'global_suppressDeprecationWarnings': None, 'global_minCachedMessageSize': None, 'global_maxCachedMessageAge': None, 'global_dataFrameSerialization': None, 'logger_level': None, 'logger_messageFormat': None, 'logger_enableRich': None, 'client_caching': None, 'client_displayEnabled': None, 'client_showErrorDetails': None, 'runner_magicEnabled': None, 'runner_installTracer': None, 'runner_fixMatplotlib': None, 'runner_postScriptGC': None, 'runner_fastReruns': None, 'server_folderWatchBlacklist': None, 'server_fileWatcherType': None, 'server_cookieSecret': None, 'server_headless': True, 'server_runOnSave': None, 'server_allowRunOnSave': None, 'server_address': None, 'server_port': None, 'server_scriptHealthCheckEnabled': None, 'server_baseUrlPath': None, 'server_enableCORS': None, 'server_enableXsrfProtection': None, 'server_maxUploadSize': None, 'server_maxMessageSize': None, 'server_enableWebsocketCompression': None, 'browser_serverAddress': None, 'browser_gatherUsageStats': None, 'browser_serverPort': None, 'ui_hideTopBar': None, 'ui_hideSidebarNav': None, 'mapbox_token': None, 'deprecation_showfileUploaderEncoding': None, 'deprecation_showImageFormat': None, 'deprecation_showPyplotGlobalUse': None, 'theme_base': None, 'theme_primaryColor': None, 'theme_backgroundColor': None, 'theme_secondaryBackgroundColor': None, 'theme_textColor': None, 'theme_font': None} - - import multiprocessing - - def run(): - config.set_option('server.headless', True) - bootstrap.run(gui.__file__, 'streamlit run gui.py --server.headless True', [], di) - - p = multiprocessing.Process(target=run) - p.start() - - # import requests - - # while True: - # try: - # print(requests.get('http://localhost:8501')) - # except: - # continue - # print('asdf') - # break - # cli.main_run(str(gui.__file__)) - # from streamlit.web.server import Server - # server = Server(gui.__file__, 'streamlit run gui.py --server.headless True') - # import tornado.web - - # class MainHandler(tornado.web.RequestHandler): - # def get(self): - # self.write("Hello, world") - - # application = tornado.web.Application([ - # (r"/", ), ]) - # application.listen(8888) - - webview.create_window('LED Control', application) - webview.start() - # proc.terminate() - exit() - + pass match arg[0]: case "help": diff --git a/controller/gui.py b/controller/gui.py deleted file mode 100644 index cfc2b04..0000000 --- a/controller/gui.py +++ /dev/null @@ -1,75 +0,0 @@ - -def main(): - import streamlit as st - import controller - import colorsys - import pickle - import os - from PIL import Image - - hide_streamlit_style = """ - - - """ - st.markdown(hide_streamlit_style, unsafe_allow_html=True) - - def hex_to_rgb(hex): - r = int(hex[1:3],16) - g = int(hex[3:5],16) - b = int(hex[5:7],16) - return r,g,b - - def rgb_to_hex(r,g,b): - return "#" + hex(r)[2:].zfill(2) + hex(g)[2:].zfill(2) + hex(b)[2:].zfill(2) - - def load_config(): - try: - path = os.path.expanduser("~/.config/lc/lc.dmp") - with open(path, 'rb') as f: - return pickle.load(f) - except FileNotFoundError: - return {'color': '000000', 'saved': []} - - def save(conf): - path = os.path.expanduser("~/.config/lc/lc.dmp") - with open(path, 'wb') as f: - pickle.dump(conf, f) - - def add_to_conf(): - st.session_state.config['saved'].append(color) - - if 'config' not in st.session_state: - init = 1 - st.session_state.config = load_config() - color = st.session_state.config['color'] - - st.title("LED Control") - - # r = st.slider("Red", 0, 255, 0) - # g = st.slider("Green", 0, 255, 0) - # b = st.slider("Blue", 0, 255, 0) - h = st.slider("Hue", 0.0, 1.0, 0.0, 0.01) - s = st.slider("Saturation", 0.0, 1.0, 0.0, 0.01) - v = st.slider("Value", 0.0, 1.0, 0.0, 0.01) - - - r,g,b = colorsys.hsv_to_rgb(h,s,v) - color = rgb_to_hex(int(r * 255),int(g * 255),int(b * 255)) - - - st.session_state.config['color'] = color[1:] - save(st.session_state.config) - #controller.set_pixels(color[1:]) - img = Image.new(mode="RGB", size=(30,30), color=(int(r * 255),int(g * 255),int(b * 255))) - - st.subheader(color) - st.image(img) - st.button("Save Color", add_to_conf) - st.session_state.config['saved'] - - -if __name__ == "__main__": - main() diff --git a/controller/requirements.txt b/controller/requirements.txt index 0284c04..872969c 100644 --- a/controller/requirements.txt +++ b/controller/requirements.txt @@ -1,3 +1,54 @@ +altair==5.0.1 +altgraph==0.17.3 +attrs==23.1.0 +blinker==1.6.2 +bottle==0.12.25 +cachetools==5.3.1 +certifi==2023.5.7 +charset-normalizer==3.1.0 click==8.1.3 -pywebview==3.7.2 -streamlit==1.17.0 +decorator==5.1.1 +gitdb==4.0.10 +GitPython==3.1.31 +idna==3.4 +importlib-metadata==6.7.0 +Jinja2==3.1.2 +jsonschema==4.17.3 +markdown-it-py==3.0.0 +MarkupSafe==2.1.3 +mdurl==0.1.2 +numpy==1.25.0 +packaging==23.1 +pandas==2.0.3 +Pillow==9.5.0 +protobuf==4.23.3 +proxy-tools==0.1.0 +pyarrow==12.0.1 +pycairo==1.24.0 +pydeck==0.8.1b0 +Pygments==2.15.1 +PyGObject==3.44.1 +pyinstaller==5.13.0 +pyinstaller-hooks-contrib==2023.4 +Pympler==1.0.1 +pyrsistent==0.19.3 +python-dateutil==2.8.2 +pytz==2023.3 +pytz-deprecation-shim==0.1.0.post0 +pywebview==4.2.2 +requests==2.31.0 +rich==13.4.2 +six==1.16.0 +smmap==5.0.0 +streamlit==1.24.0 +tenacity==8.2.2 +toml==0.10.2 +toolz==0.12.0 +tornado==6.3.2 +typing_extensions==4.7.0 +tzdata==2023.3 +tzlocal==4.3.1 +urllib3==2.0.3 +validators==0.20.0 +watchdog==3.0.0 +zipp==3.15.0 diff --git a/web/app.py b/web/app.py new file mode 100644 index 0000000..67fc9bd --- /dev/null +++ b/web/app.py @@ -0,0 +1,36 @@ +import socket +import asyncio +from flask import Flask, render_template + +app = Flask(__name__) + +async def scan_for_pi() -> dict(): + ip = socket.gethostbyname(socket.gethostname()) + baseIP = '.'.join(ip.split(".")[:3]) + + def scan(ip: str) -> str: + try: + with socket.socket() as s: + s.settimeout(0.5) + s.connect((ip, 5000)) + s.send("PING".encode()) + data = s.recv(1024).decode() + if data == "PONG": + print("found:", ip) + return ip + except OSError: + pass + + async def scan_async(ip: str) -> str: + return await asyncio.to_thread(scan, ip) + + pi_list = await asyncio.gather(*[scan_async(baseIP + "." + str(i)) for i in range(255)]) + return [pi for pi in pi_list if pi is not None] + +@app.route('/') +def home(): + return render_template('index.html') + +@app.route('/set/') +def set(color): + return f'{color}' diff --git a/web/controller.py b/web/controller.py new file mode 100755 index 0000000..d9285c7 --- /dev/null +++ b/web/controller.py @@ -0,0 +1,392 @@ +#!/usr/bin/python3 +import socket +import os +import asyncio +import click +import pickle +import re +import webview + +# function generate fibonacci sequence +def fibonacci(n): + if n == 0: + return 0 + elif n == 1: + return 1 + else: + return fibonacci(n - 1) + fibonacci(n - 2) + +async def scan_for_pi() -> dict(): + ip = socket.gethostbyname(socket.gethostname()) + baseIP = '.'.join(ip.split(".")[:3]) + + def scan(ip: str) -> str: + try: + with socket.socket() as s: + s.settimeout(0.5) + s.connect((ip, 5000)) + s.send("PING".encode()) + data = s.recv(1024).decode() + if data == "PONG": + print("found:", ip) + return ip + except OSError: + pass + + async def scan_async(ip: str) -> str: + return await asyncio.to_thread(scan, ip) + + pi_list = await asyncio.gather(*[scan_async(baseIP + "." + str(i)) for i in range(255)]) + return [pi for pi in pi_list if pi is not None] + +class PI: + def __init__(self, ip, port=5000): + self.ip = ip + self.port = port + self.connected = False + self.socket = socket.socket() + + def __str__(self): + return self.ip + + def connect(self): + self.socket.connect((self.ip, self.port)) + + def send(self, data): + self.socket.send(data.encode()) + + def disconnect(self): + self.socket.close() + +def load_config(path=os.path.expanduser("~/.config/lc/lc.conf")): + try: + with open(path, 'rb') as f: + return [PI(ip) for ip in pickle.load(f)] + except FileNotFoundError: + print("Config does not exist") + exit() + +def save_config(obj, path=os.path.expanduser("~/.config/lc/lc.conf")): + os.makedirs(path[::-1].split('/',1)[-1][::-1], exist_ok=True) + with open(path, 'wb') as f: + pickle.dump(list([o.ip for o in obj]), f) + +def set_pixels(color): + if not re.match("[0-9a-f]{6}$", color): + print(f"{color} not a valid hex color code") + return + pi_list = load_config() + for pi in pi_list: + pi.connect() + pi.send(color) + pi.disconnect() + +@click.command() +@click.argument("arg", nargs=-1) +# @click.option("-s", help="Set HEX Color: -s ffffff") +# @click.option("-v", help="Set HEX Color as base visualizer color: -v ffffff") +def main(arg): + if arg == (): + # proc = subprocess.Popen(["python", "-m", "streamlit", "run", "/var/lib/lc/gui.py", "--server.headless", "True", "--theme.base", "dark", "--theme.primaryColor", "#9f0000", "--theme.backgroundColor", "#181a1b", "--theme.secondaryBackgroundColor", "#1f2123", "--theme.textColor", "#c4c0b8"], stdout=subprocess.PIPE) + # for line in proc.stdout: + # if line == b' You can now view your Streamlit app in your browser.\n': + # break + + import streamlit.web.bootstrap as bootstrap + from streamlit import config + import gui as gui + + di = {'global_disableWatchdogWarning': None, 'global_showWarningOnDirectExecution': None, 'global_developmentMode': None, 'global_logLevel': None, 'global_unitTest': None, 'global_suppressDeprecationWarnings': None, 'global_minCachedMessageSize': None, 'global_maxCachedMessageAge': None, 'global_dataFrameSerialization': None, 'logger_level': None, 'logger_messageFormat': None, 'logger_enableRich': None, 'client_caching': None, 'client_displayEnabled': None, 'client_showErrorDetails': None, 'runner_magicEnabled': None, 'runner_installTracer': None, 'runner_fixMatplotlib': None, 'runner_postScriptGC': None, 'runner_fastReruns': None, 'server_folderWatchBlacklist': None, 'server_fileWatcherType': None, 'server_cookieSecret': None, 'server_headless': True, 'server_runOnSave': None, 'server_allowRunOnSave': None, 'server_address': None, 'server_port': None, 'server_scriptHealthCheckEnabled': None, 'server_baseUrlPath': None, 'server_enableCORS': None, 'server_enableXsrfProtection': None, 'server_maxUploadSize': None, 'server_maxMessageSize': None, 'server_enableWebsocketCompression': None, 'browser_serverAddress': None, 'browser_gatherUsageStats': None, 'browser_serverPort': None, 'ui_hideTopBar': None, 'ui_hideSidebarNav': None, 'mapbox_token': None, 'deprecation_showfileUploaderEncoding': None, 'deprecation_showImageFormat': None, 'deprecation_showPyplotGlobalUse': None, 'theme_base': None, 'theme_primaryColor': None, 'theme_backgroundColor': None, 'theme_secondaryBackgroundColor': None, 'theme_textColor': None, 'theme_font': None} + + import multiprocessing + + def run(): + config.set_option('server.headless', True) + bootstrap.run(gui.__file__, 'streamlit run gui.py --server.headless True', [], di) + + p = multiprocessing.Process(target=run) + p.start() + + # import requests + + # while True: + # try: + # print(requests.get('http://localhost:8501')) + # except: + # continue + # print('asdf') + # break + # cli.main_run(str(gui.__file__)) + # from streamlit.web.server import Server + # server = Server(gui.__file__, 'streamlit run gui.py --server.headless True') + # import tornado.web + + # class MainHandler(tornado.web.RequestHandler): + # def get(self): + # self.write("Hello, world") + + # application = tornado.web.Application([ + # (r"/", ), ]) + # application.listen(8888) + + webview.create_window('LED Control', 'http://localhost:8501') + webview.start() + # proc.terminate() + exit() + + + match arg[0]: + case "help": + print("lc [help|set|search|list]") + + case "set": + if len(arg) < 2: + print("color argument missing") + set_pixels(arg[1]) + + case "search": + ip_list = asyncio.run(scan_for_pi()) + pi_list = [PI(ip) for ip in ip_list] + save_config(pi_list) + + case "list": + pi_list = load_config() + if pi_list is not None: + for pi in pi_list: + print(pi) + case "music": + pass + +if __name__ == '__main__': + main() + +# def helpmenu(): + # print("light controll\n") + # print("Options:") + # print("-h show help") + # print("-s set static color") + # print("-v visualizer") + # print("-i interactive interface") + # print("-a ambient light") + # print("-t test function (debug)") + +# def base_color(color): + # return [i // min(color) for i in color] + +# def visualizer(color, amp_strength=0.6): + # r,g,b = hex_to_rgb(color) + + # cava = subprocess.Popen(["cava", "-p", "/etc/lc/cava.conf"], stdout=subprocess.PIPE) + # sed = subprocess.Popen(["sed", "-u", "s/;.*;$//"], stdin=cava.stdout, stdout=subprocess.PIPE) + + # for line in sed.stdout: + # amp_factor = amp_strength * ((int(line) / 500) - 1) + 1 + # send(rgb_to_hex(int(r * amp_factor), int(g * amp_factor), int(b * amp_factor))) + # cava.stdout.close() + # sed.stdout.close() + +# def visualizer_cava_thread(): + # global volume_amp + + # cava = subprocess.Popen(["cava", "-p", "/etc/lc/cava.conf"], stdout=subprocess.PIPE) + # sed = subprocess.Popen(["sed", "-u", "s/;.*;$//"], stdin=cava.stdout, stdout=subprocess.PIPE) + + # for line in sed.stdout: + # volume_amp = int(line) + # print(volume_amp) + # cava.stdout.close() + # sed.stdout.close() + +# def amp_by_vol(color, amp_strength): + # global volume_amp + # # amp_strength in percentage + # amp_factor = amp_strength*((volume_amp/500)-1)+1 + # #print(amp_factor, color,[c*amp_factor for c in color], volume_amp) + # return [c*amp_factor for c in color] + +# def vibrant(r,g,b): + # intensity = 50 # usabel range 1-100 max:1000 + + # intensity = 1+intensity/1000 + # rgb = [r,g,b] + # #min_idx = rgb.index(min(rgb)) + # d = (r+g+b)/3 + # for c in range(3): + # if rgb[c] < d: + # rgb[c] = int(rgb[c]*(intensity**(rgb[c]-d))) + # elif rgb[c] > d: + # rgb[c] = int(rgb[c]*(-intensity**(-rgb[c]+d)+2)) + # if rgb[c] > 255: + # rgb[c] = 255 + # #rgb[min_idx] = int(rgb[min_idx]*(rgb[min_idx]/d)**2) + # return rgb + +# def ambient_light_thread(): + # r,g,b = 0,0,0 + # brighness = 1 + # active_color = '' + + # while True: + # # P-Regler + # r,g,b = [w+((y-w)*0.1) for y,w in zip((_r,_g,_b),(r,g,b))] + # if ((round(r),round(g),round(b)) == (_r,_g,_b)): + # active_color = '\033[0;32;40m' + # else: + # active_color = '\033[0;31;40m' + # r_out,g_out,b_out = amp_by_vol((r,g,b), 0.6) + # #r_out,g_out,b_out = r,g,b + # print(active_color, round(r),round(g),round(b), round(r_out),round(g_out),round(b_out), '\033[0;37;40m') + # send(rgb_to_hex(int(r_out*brighness),int(g_out*brighness),int(b_out*brighness))) + # time.sleep(0.01) + +# ups_counter = 0 +# start_time = time.time() + +# def ups(): + # global ups_counter + # global start_time + + # ups_counter += 1 + # time_d = time.time()-start_time + # ups = ups_counter/time_d + # print(ups) + +# def color_correction(r,g,b): + # amp = [1,1,0.8] + # threshold = 10 + # if r < threshold and g < threshold and b < threshold: + # return 0,0,0 + # return int(r*amp[0]), int(g*amp[1]), int(b*amp[2]) + +# def ambient_light(): + + # t1 = Thread(target=ambient_light_thread) + # t1.start() + + # t2 = Thread(target=visualizer_cava_thread) + # t2.start() + + # global _r,_g,_b + + # counter = 0 + # start_time = time.time() + # while True: + # # screenshot + + # # Xorg + # img = pyscreenshot.grab(backend="mss", childprocess=False, bbox=(1920,0,4480,1440)) + + # #Wayland + # #time.sleep(0.1) + # #cap = cv2.VideoCapture('/tmp/a') + # #count = cap.get(cv2.CAP_PROP_FRAME_COUNT) + # #cap.set(cv2.CAP_PROP_POS_FRAMES, count-1) + + # #ret, frame = cap.read() + + # #frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB) + # #img = Image.fromarray(frame) + # #cap.release() + + # # find dominant color + # img.thumbnail((2,2)) + # r,g,b = img.getpixel((0, 0)) + # r,g,b = vibrant(r,g,b) + # _r,_g,_b = color_correction(r,g,b) + + # time.sleep(0.05) + + +# def rgb_to_hex(r,g,b): + # return "%02x%02x%02x" % (r,g,b) + +# def hex_to_rgb(hex): + # r = int(hex[0:2],16) + # g = int(hex[2:4],16) + # b = int(hex[4:6],16) + # return r,g,b + +# def test(): + # for i in range(256): + # h = rgb_to_hex(0,i,0) + # send(h) + # print(h) + # time.sleep(0.0) + +# def tui_main(scr, *args): + # # -- Perform an action with Screen -- + # scr.border(0) + # scr.addstr(5, 5, 'Hello from Curses!', curses.A_BOLD) + # scr.addstr(6, 5, 'Press q to close this screen', curses.A_NORMAL) + # scr.addstr(8, 5, '\u250C') + + # rgb = [0,0,0] + # color_selector = 0 + + # while True: + # status = '{},{},{} {}'.format(rgb[0], rgb[1], rgb[2], color_selector) + # scr.addstr(1, 1, status) + + # ch = scr.getch() + # if ch == ord('q'): + # break + # elif ch == ord('j'): + # if rgb[color_selector] > 0: + # rgb[color_selector] -= 1 + # send(rgb_to_hex(rgb[0], rgb[1], rgb[2])) + # elif ch == ord('k'): + # if rgb[color_selector] < 255: + # rgb[color_selector] += 1 + # send(rgb_to_hex(rgb[0], rgb[1], rgb[2])) + # elif ch == ord('l'): + # if color_selector < 3: + # color_selector += 1 + # elif ch == ord('h'): + # if color_selector > 0: + # color_selector -= 1 + +# def main(argv): + # if not sys.stdin.isatty(): + # connect() + # for volume in sys.stdin: + # volume = int(volume) + # hex_color = rgb_to_hex(volume,0,0) + # send(hex_color) + # sys.exit() + + # try: + # opts, args = getopt.getopt(argv, "s:v:ahti") + # except getopt.GetoptError: + # print(sys.argv[0], "invalid option") + # print("Try", sys.argv[0], "-h for help") + # sys.exit(1) + + # for opt, arg in opts: + # if opt == "-h": + # helpmenu() + # elif opt == "-s": + # connect() + # send(arg) + # disconnect() + # elif opt == "-a": + # connect() + # ambient_light() + # disconnect() + # elif opt == "-v": + # connect() + # visualizer(arg) + # disconnect() + # elif opt == "-t": + # connect() + # test() + # disconnect() + # elif opt == '-i': + # connect() + # curses.wrapper(tui_main) + # disconnect() + + # sys.exit() + +# if __name__ == "__main__": + + # main(sys.argv[1:]) diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..9cfa395 --- /dev/null +++ b/web/index.html @@ -0,0 +1,16 @@ + + + +Input Field and Button + + + + + + +