diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..02a4c94 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "jukebox-web"] + path = jukebox-web + url = https://git.myitr.org/Jukebox/jukebox-web diff --git a/Dockerfile b/Dockerfile index a465d74..5a0d059 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,11 +3,11 @@ FROM python:3.11-slim # Get runtime dependencies # glx for OpenCV, ghostscript for datasheet PDF rendering, zbar for barcode scanning, git for cloning repos RUN apt-get update && apt-get install -y libgl1-mesa-glx ghostscript libzbar0 git && apt-get clean && rm -rf /var/lib/apt/lists -COPY *.py *.yml *.sh *.txt *.html static templates ./ +COPY requirements.txt ./ #COPY config-server.yml config.yml RUN pip3 install -r requirements.txt - -CMD ["python3", "run.py"] +COPY *.py *.yml *.sh *.txt *.html static templates ./ +CMD ["sh", "-c", "python3 run.py"] EXPOSE 5000 EXPOSE 8000 EXPOSE 9000 diff --git a/compose-search-only.yml b/compose-search-only.yml new file mode 100644 index 0000000..5106c70 --- /dev/null +++ b/compose-search-only.yml @@ -0,0 +1,13 @@ +services: + meilisearch: + image: "getmeili/meilisearch:v1.6.2" + ports: + - "7700:7700" + environment: + MEILI_MASTER_KEY: fluffybunnyrabbit + MEILI_NO_ANALYTICS: true + volumes: + - "meili_data:/meili_data" + +volumes: + meili_data: \ No newline at end of file diff --git a/compose.yml b/compose.yml index 51434a5..2a85d3e 100644 --- a/compose.yml +++ b/compose.yml @@ -8,6 +8,23 @@ services: MEILI_NO_ANALYTICS: true volumes: - "meili_data:/meili_data" + + jukebox-software: + build: . + init: true + ports: + - "5000:5000" + - "8000:8000" + - "9000:9000" + environment: + - PYTHONUNBUFFERED=1 + depends_on: + - meilisearch + + jukebox-web: + build: jukebox-web + ports: + - "3000:3000" volumes: meili_data: \ No newline at end of file diff --git a/get_specs.py b/get_specs.py index 455df59..4e183dd 100755 --- a/get_specs.py +++ b/get_specs.py @@ -344,9 +344,13 @@ def get_multi(partnums, delay=0.25, dir="cables/", cache=True, bar=None): fprint("Using cached hi-res part image for " + partnum) out = __use_cached_datasheet(partnum, path, output_dir, dstype) returnval = [partnum, dstype, False, out] + actualpartnums.append(returnval) return True for fullpartnum in partnums: + if fullpartnum is False: + actualpartnums.append(False) + continue if fullpartnum[0:2] == "BL": # catalog.belden.com entry partnum = fullpartnum[2:] dstype = "Belden" diff --git a/jukebox-web b/jukebox-web new file mode 160000 index 0000000..f3d8ec0 --- /dev/null +++ b/jukebox-web @@ -0,0 +1 @@ +Subproject commit f3d8ec0cc421cb8f8b7bb4b30339f3663f70b297 diff --git a/read_datasheet.py b/read_datasheet.py index 757ab2a..0d89a59 100755 --- a/read_datasheet.py +++ b/read_datasheet.py @@ -301,7 +301,7 @@ def parse(filename, output_dir, partnum, dstype): output_table["id"] = id #output_table["position"] = id #output_table["brand"] = brand - output_table["fullspecs"] = tables + output_table["fullspecs"] = {"partnum": partnum, "id": id, **tables} output_table["searchspecs"] = {"partnum": partnum, **flatten(tables)} output_table["searchspecs"]["id"] = id diff --git a/run.py b/run.py index 10924c9..be09c92 100755 --- a/run.py +++ b/run.py @@ -26,7 +26,7 @@ import json import process_video import search from search import JukeboxSearch - +#multiprocessing.set_start_method('spawn', True) config = None @@ -90,7 +90,15 @@ def wait_for(val, name): while val is False: sleep(0.1) -def start_server_socket(): +def send_data(type, call, data, client_id="*"): + out = dict() + out["type"] = type + out["call"] = call + out["data"] = data + to_server_queue.put((client_id, json.dumps(out))) + +def start_server_socket(cable_list): + global jbs """app = Flask(__name__) @app.route('/report_ip', methods=['POST']) @@ -122,69 +130,92 @@ def start_server_socket(): # Message handler try: decoded = json.loads(message) - if "type" not in decoded: - fprint("Missing \"type\" field.") - continue - if "call" not in decoded: - fprint("Missing \"call\" field.") - continue - if "data" not in decoded: - fprint("Missing \"data\" field.") - continue + except: + fprint("Non-JSON message recieved") + continue - # if we get here, we have a "valid" data packet - data = decoded["data"] - call = decoded["call"] + if "type" not in decoded: + fprint("Missing \"type\" field.") + continue + if "call" not in decoded: + fprint("Missing \"call\" field.") + continue + if "data" not in decoded: + fprint("Missing \"data\" field.") + continue + # if we get here, we have a "valid" data packet + data = decoded["data"] + call = decoded["call"] + try: match decoded["type"]: case "log": fprint("log message") if call == "send": fprint("webapp: " + str(data), sendqueue=to_server_queue) elif call == "request": - fprint("") - + pass case "cable_map": fprint("cable_map message") if call == "send": - fprint("") + pass elif call == "request": - fprint("") - + tmp = list() + for idx in range(len(cable_list)): + if cable_list[idx] is not False: + tmp1 = {"part_number": cable_list[idx], "position": idx, "name": cable_list[idx], "brand": "Belden", "description": "Blah", "short_description": "Bla"} + tmp.append(tmp1) + out = {"map": tmp} + fprint(out) + send_data(decoded["type"], "send", out, client_id) case "ping": fprint("Pong!!!") - # Lucas' notes # Add a ping pong :) response/handler # Add a get cable response/handler # this will tell the robot arm to move # Call for turning off everything - # TODO Helper for converting Python Dictionaries to JSON # make function: pythonData --> { { "type": "...", "call": "...", "data": pythonData } } - + # to send: to_server_queue.put(("*", "JSON STRING HERE")) # replace * with UUID of client to send to one specific location - + case "cable_details": fprint("cable_details message") if call == "send": - fprint("") + pass elif call == "request": - fprint("") + dataout = dict() + dataout["cables"] = list() + print(data) + if "part_number" in data: + for part in data["part_number"]: + #print(part) + #print(jbs.get_partnum(part)) + dataout["cables"].append(jbs.get_partnum(part)["fullspecs"]) + if "position" in data: + for pos in data["position"]: + #print(pos) + #print(jbs.get_position(str(pos))) + dataout["cables"].append(jbs.get_position(str(pos))["fullspecs"]) + send_data(decoded["type"], "send", dataout, client_id) case "cable_search": fprint("cable_search message") if call == "send": - fprint("") + pass elif call == "request": - fprint("") - + results = jbs.search(data["string"])["hits"] + dataout = dict() + dataout["cables"] = list() + for result in results: + dataout["cables"].append(result["fullspecs"]) + send_data(decoded["type"], "send", dataout, client_id) case "keyboard": fprint("keyboard message") if call == "send": - fprint("") + pass elif call == "request": - fprint("") if data["enabled"] == True: # todo : send this to client p = Process(target=run_cmd, args=("./keyboard-up.ps1",)) @@ -192,21 +223,19 @@ def start_server_socket(): elif data["enabled"] == False: p = Process(target=run_cmd, args=("./keyboard-down.ps1",)) p.start() - case "machine_settings": fprint("machine_settings message") if call == "send": - fprint("") + pass elif call == "request": - fprint("") - + pass case _: fprint("Unknown/unimplemented data type: " + decoded["type"]) + except Exception as e: + fprint(traceback.format_exc()) + fprint(e) - - except: - fprint("Non-JSON message recieved") - continue + sleep(0.001) # Sleep to prevent tight loop @@ -262,8 +291,8 @@ def setup_server(pool): ledsys = LEDSystem() pool.apply_async(ledsys.init, callback=led_start_callback) #pool.apply_async(sensor_control.init, callback=sensor_start_callback) - serverproc = Process(target=start_server_socket) - serverproc.start() + jbs = JukeboxSearch() + if led_ready is False: @@ -293,7 +322,7 @@ def setup_server(pool): fprint("Arm initialized.", sendqueue=to_server_queue) - jbs = JukeboxSearch() + return True @@ -338,9 +367,12 @@ def mainloop_server(pool): elif camera_ready: fprint("Adding cable to list...") global scan_value - if scan_value.find("bldn.app/") > -1: + if scan_value is False: + cable_list.append(scan_value) + elif scan_value.find("bldn.app/") > -1: scan_value = scan_value[scan_value.find("bldn.app/")+9:] - cable_list.append((counter, scan_value)) + else: + cable_list.append(scan_value) fprint(scan_value) #pool.apply_async(arm.return cable, callback=arm_start_callback) arm_state = "RETURN" @@ -354,19 +386,8 @@ def mainloop_server(pool): pass else: # scanned everything - tmp = list() - for cable in cable_list: - tmp.append(cable[1]) - tmp = [ # Actual cables in Jukebox - - "AW86104CY", - "AW3050", - "AW6714", - "AW1172C", - "AWFIT-221-1_4", - "BLTF-1LF-006-RS5", "BLTF-SD9-006-RI5", "BLTT-SLG-024-HTN", @@ -388,10 +409,17 @@ def mainloop_server(pool): "BL8760", "BL6300UE", "BL6300FE", - "BLRA500P" + "BLRA500P", + "AW86104CY", + "AW3050", + "AW6714", + "AW1172C", + "AWFIT-221-1_4" ] - cable_list = tmp - pool.apply_async(get_specs.get_multi, (tmp, 0.5), callback=cable_search_callback) + while len(tmp) < 54: + tmp.append(False) # must have 54 entries + cable_list = tmp # remove for real demo + pool.apply_async(get_specs.get_multi, (tmp, 0.3), callback=cable_search_callback) mode = "Parsing" fprint("All cables scanned. Finding & parsing datasheets...") if mode == "Parsing": @@ -403,29 +431,35 @@ def mainloop_server(pool): # done global parse_res success, partnums = parse_res - #partnums = list() - # debug - #success = True + for idx in range(len(partnums)): + if partnums[idx] is not False: + cable_list[idx] = partnums[idx][0].replace("/", "_") + else: + cable_list[idx] = False - #cable_list = list(range(len(partnums))) + print(partnums) if success: # easy mode fprint("All cables inventoried and parsed.") - for x in range(len(cable_list)): - #cable_list[x] = (cable_list[x][0], partnums[x]) - cable_list[x] = (x, cable_list[x]) fprint("Adding to database...") - for idx,partnum in cable_list: - with open("cables/" + partnum[2:] + "/search.json", "rb") as f: - searchdata = json.load(f) - searchdata["position"] = idx - with open("cables/" + partnum[2:] + "/specs.json", "rb") as f: - specs = json.load(f) - searchdata["fullspecs"] = specs - jbs.add_document(searchdata) + for idx in range(len(cable_list)): + partnum = cable_list[idx] + if partnum is not False: + with open("cables/" + partnum + "/search.json", "rb") as f: + searchdata = json.load(f) + searchdata["position"] = idx + with open("cables/" + partnum + "/specs.json", "rb") as f: + specs = json.load(f) + searchdata["fullspecs"] = specs + searchdata["fullspecs"]["position"] = idx + jbs.add_document(searchdata) + #sleep(0.5) + #print(jbs.get_position("1")) fprint("All cables added to database.") mode = "Idle" + serverproc = Process(target=start_server_socket, args=(cable_list,)) + serverproc.start() else: # TODO: manual input pass @@ -562,7 +596,7 @@ if __name__ == "__main__": with open('config.yml', 'r') as fileread: #global config config = yaml.safe_load(fileread) - + fprint("Config loaded.") with Manager() as manager: fprint("Spawning threads...") pool = LoggingPool(processes=10) @@ -583,5 +617,12 @@ if __name__ == "__main__": fprint("Entering main loop...") while(keeprunning): mainloop_server(pool) + else: + fprint("Mode unspecified - assuming server") + fprint("Starting in server mode.") + if setup_server(pool): + fprint("Entering main loop...") + while(keeprunning): + mainloop_server(pool) diff --git a/search.py b/search.py index e031ce9..7b5201c 100644 --- a/search.py +++ b/search.py @@ -6,7 +6,7 @@ from meilisearch.task import TaskInfo from meilisearch.errors import MeilisearchApiError import time -DEFAULT_URL = "http://localhost:7700" +DEFAULT_URL = "http://127.0.0.1:7700" DEFAULT_APIKEY = "fluffybunnyrabbit" # I WOULD RECOMMEND SOMETHING MORE SECURE DEFAULT_INDEX = "cables" DEFAULT_FILTERABLE_ATTRS = ["partnum", "uuid", "position"] # default filterable attributes @@ -70,11 +70,10 @@ class JukeboxSearch: :param filterables: List of all filterable attributes""" - existing_filterables = self.idxref.get_filterable_attributes() - if len(set(existing_filterables).difference(set(filterables))) > 0: - taskref = self.idxref.update_filterable_attributes(filterables) - - self.client.wait_for_task(taskref.index_uid) + #existing_filterables = self.idxref.get_filterable_attributes() + #if len(set(existing_filterables).difference(set(filterables))) > 0: + taskref = self.idxref.update_filterable_attributes(filterables) + #self.client.wait_for_task(taskref.index_uid) def search(self, query: str, filters: str = None): """Execute a search query on the Meilisearch index. @@ -95,7 +94,7 @@ class JukeboxSearch: :returns: A dict containing the results; If no results found, an empty dict.""" q = self.search("", filter) if q["estimatedTotalHits"] != 0: - return ["hits"][0] + return q["hits"][0] else: return dict() diff --git a/setup-alpine-vm.sh b/setup-alpine-vm.sh new file mode 100644 index 0000000..c0accb2 --- /dev/null +++ b/setup-alpine-vm.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# This script must run as root! + +echo "https://dl-cdn.alpinelinux.org/alpine/latest-stable/main +https://dl-cdn.alpinelinux.org/alpine/latest-stable/community" > /etc/apk/repositories + +apk upgrade +apk add git docker docker-cli-compose + +rc-update add docker +service docker start + +git clone https://git.myitr.org/Jukebox/jukebox-software +cd jukebox-software +git submodule init +git submodule update + +docker compose build +docker compose up -d \ No newline at end of file diff --git a/websocket_test.html b/websocket_test.html index c6489bc..ab65944 100644 --- a/websocket_test.html +++ b/websocket_test.html @@ -81,7 +81,7 @@ socket.send(message); console.log('Message sent', message); } - setInterval(ping, 1500); + //setInterval(ping, 1500); // setInterval(() => { // updateServiceStatus('serviceA', 'down');